mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-16 04:02:52 -06:00
More work on IP Address/Range and Prefix relationship
This commit is contained in:
parent
c5e7b21147
commit
b19f81cede
@ -44,6 +44,7 @@ class AggregateSerializer(NetBoxModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class PrefixSerializer(NetBoxModelSerializer):
|
class PrefixSerializer(NetBoxModelSerializer):
|
||||||
|
# TODO: Alter for parent prefix
|
||||||
family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True)
|
family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True)
|
||||||
vrf = VRFSerializer(nested=True, required=False, allow_null=True)
|
vrf = VRFSerializer(nested=True, required=False, allow_null=True)
|
||||||
scope_type = ContentTypeField(
|
scope_type = ContentTypeField(
|
||||||
@ -134,6 +135,7 @@ class AvailablePrefixSerializer(serializers.Serializer):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class IPRangeSerializer(NetBoxModelSerializer):
|
class IPRangeSerializer(NetBoxModelSerializer):
|
||||||
|
# TODO: Alter for prefix
|
||||||
family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True)
|
family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True)
|
||||||
start_address = IPAddressField()
|
start_address = IPAddressField()
|
||||||
end_address = IPAddressField()
|
end_address = IPAddressField()
|
||||||
@ -158,6 +160,7 @@ class IPRangeSerializer(NetBoxModelSerializer):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class IPAddressSerializer(NetBoxModelSerializer):
|
class IPAddressSerializer(NetBoxModelSerializer):
|
||||||
|
# TODO: Alter for prefix
|
||||||
family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True)
|
family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True)
|
||||||
address = IPAddressField()
|
address = IPAddressField()
|
||||||
vrf = VRFSerializer(nested=True, required=False, allow_null=True)
|
vrf = VRFSerializer(nested=True, required=False, allow_null=True)
|
||||||
|
@ -291,6 +291,8 @@ class RoleFilterSet(OrganizationalModelFilterSet):
|
|||||||
|
|
||||||
|
|
||||||
class PrefixFilterSet(NetBoxModelFilterSet, ScopedFilterSet, TenancyFilterSet, ContactModelFilterSet):
|
class PrefixFilterSet(NetBoxModelFilterSet, ScopedFilterSet, TenancyFilterSet, ContactModelFilterSet):
|
||||||
|
# TODO: Alter for aggregate
|
||||||
|
# TODO: Alter for parent prefix
|
||||||
family = django_filters.NumberFilter(
|
family = django_filters.NumberFilter(
|
||||||
field_name='prefix',
|
field_name='prefix',
|
||||||
lookup_expr='family'
|
lookup_expr='family'
|
||||||
@ -457,6 +459,7 @@ class PrefixFilterSet(NetBoxModelFilterSet, ScopedFilterSet, TenancyFilterSet, C
|
|||||||
|
|
||||||
|
|
||||||
class IPRangeFilterSet(TenancyFilterSet, NetBoxModelFilterSet, ContactModelFilterSet):
|
class IPRangeFilterSet(TenancyFilterSet, NetBoxModelFilterSet, ContactModelFilterSet):
|
||||||
|
# TODO: Alter for prefix
|
||||||
family = django_filters.NumberFilter(
|
family = django_filters.NumberFilter(
|
||||||
field_name='start_address',
|
field_name='start_address',
|
||||||
lookup_expr='family'
|
lookup_expr='family'
|
||||||
@ -473,6 +476,16 @@ class IPRangeFilterSet(TenancyFilterSet, NetBoxModelFilterSet, ContactModelFilte
|
|||||||
method='search_contains',
|
method='search_contains',
|
||||||
label=_('Ranges which contain this prefix or IP'),
|
label=_('Ranges which contain this prefix or IP'),
|
||||||
)
|
)
|
||||||
|
prefix_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=Prefix.objects.all(),
|
||||||
|
label=_('Prefix (ID)'),
|
||||||
|
)
|
||||||
|
prefix = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='prefix__prefix',
|
||||||
|
queryset=Prefix.objects.all(),
|
||||||
|
to_field_name='prefix',
|
||||||
|
label=_('Prefix'),
|
||||||
|
)
|
||||||
vrf_id = django_filters.ModelMultipleChoiceFilter(
|
vrf_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=VRF.objects.all(),
|
queryset=VRF.objects.all(),
|
||||||
label=_('VRF'),
|
label=_('VRF'),
|
||||||
@ -549,6 +562,7 @@ class IPRangeFilterSet(TenancyFilterSet, NetBoxModelFilterSet, ContactModelFilte
|
|||||||
|
|
||||||
|
|
||||||
class IPAddressFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet):
|
class IPAddressFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet):
|
||||||
|
# TODO: Alter for prefix
|
||||||
family = django_filters.NumberFilter(
|
family = django_filters.NumberFilter(
|
||||||
field_name='address',
|
field_name='address',
|
||||||
lookup_expr='family'
|
lookup_expr='family'
|
||||||
|
@ -207,6 +207,7 @@ class RoleBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
|
|
||||||
class PrefixBulkEditForm(ScopedBulkEditForm, NetBoxModelBulkEditForm):
|
class PrefixBulkEditForm(ScopedBulkEditForm, NetBoxModelBulkEditForm):
|
||||||
|
# TODO: Alter for parent prefix
|
||||||
vlan_group = DynamicModelChoiceField(
|
vlan_group = DynamicModelChoiceField(
|
||||||
queryset=VLANGroup.objects.all(),
|
queryset=VLANGroup.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -276,6 +277,7 @@ class PrefixBulkEditForm(ScopedBulkEditForm, NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
|
|
||||||
class IPRangeBulkEditForm(NetBoxModelBulkEditForm):
|
class IPRangeBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
|
# TODO: Alter for prefix
|
||||||
vrf = DynamicModelChoiceField(
|
vrf = DynamicModelChoiceField(
|
||||||
queryset=VRF.objects.all(),
|
queryset=VRF.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -323,6 +325,7 @@ class IPRangeBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
|
|
||||||
class IPAddressBulkEditForm(NetBoxModelBulkEditForm):
|
class IPAddressBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
|
# TODO: Alter for prefix
|
||||||
prefix = DynamicModelChoiceField(
|
prefix = DynamicModelChoiceField(
|
||||||
queryset=Prefix.objects.all(),
|
queryset=Prefix.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
|
@ -156,6 +156,8 @@ class RoleImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
|
|
||||||
class PrefixImportForm(ScopedImportForm, NetBoxModelImportForm):
|
class PrefixImportForm(ScopedImportForm, NetBoxModelImportForm):
|
||||||
|
# TODO: Alter for aggregate
|
||||||
|
# TODO: Alter for parent prefix
|
||||||
vrf = CSVModelChoiceField(
|
vrf = CSVModelChoiceField(
|
||||||
label=_('VRF'),
|
label=_('VRF'),
|
||||||
queryset=VRF.objects.all(),
|
queryset=VRF.objects.all(),
|
||||||
@ -245,6 +247,7 @@ class PrefixImportForm(ScopedImportForm, NetBoxModelImportForm):
|
|||||||
|
|
||||||
|
|
||||||
class IPRangeImportForm(NetBoxModelImportForm):
|
class IPRangeImportForm(NetBoxModelImportForm):
|
||||||
|
# TODO: Alter for prefix
|
||||||
vrf = CSVModelChoiceField(
|
vrf = CSVModelChoiceField(
|
||||||
label=_('VRF'),
|
label=_('VRF'),
|
||||||
queryset=VRF.objects.all(),
|
queryset=VRF.objects.all(),
|
||||||
@ -281,6 +284,7 @@ class IPRangeImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
|
|
||||||
class IPAddressImportForm(NetBoxModelImportForm):
|
class IPAddressImportForm(NetBoxModelImportForm):
|
||||||
|
# TODO: Alter for prefix
|
||||||
prefix = CSVModelChoiceField(
|
prefix = CSVModelChoiceField(
|
||||||
label=_('Prefix'),
|
label=_('Prefix'),
|
||||||
queryset=Prefix.objects.all(),
|
queryset=Prefix.objects.all(),
|
||||||
|
@ -278,10 +278,18 @@ class IPRangeFilterForm(ContactModelFilterForm, TenancyFilterForm, NetBoxModelFi
|
|||||||
model = IPRange
|
model = IPRange
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('q', 'filter_id', 'tag'),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
FieldSet('family', 'vrf_id', 'status', 'role_id', 'mark_populated', 'mark_utilized', name=_('Attributes')),
|
FieldSet(
|
||||||
|
'prefix', 'family', 'vrf_id', 'status', 'role_id', 'mark_populated', 'mark_utilized', name=_('Attributes')
|
||||||
|
),
|
||||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||||
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
|
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
|
||||||
)
|
)
|
||||||
|
prefix = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=Prefix.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label=_('Prefix'),
|
||||||
|
null_option='None'
|
||||||
|
)
|
||||||
family = forms.ChoiceField(
|
family = forms.ChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
choices=add_blank_choice(IPAddressFamilyChoices),
|
choices=add_blank_choice(IPAddressFamilyChoices),
|
||||||
|
@ -250,6 +250,11 @@ class PrefixForm(TenancyForm, ScopedForm, NetBoxModelForm):
|
|||||||
|
|
||||||
|
|
||||||
class IPRangeForm(TenancyForm, NetBoxModelForm):
|
class IPRangeForm(TenancyForm, NetBoxModelForm):
|
||||||
|
prefix = DynamicModelChoiceField(
|
||||||
|
queryset=Prefix.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label=_('Prefix')
|
||||||
|
)
|
||||||
vrf = DynamicModelChoiceField(
|
vrf = DynamicModelChoiceField(
|
||||||
queryset=VRF.objects.all(),
|
queryset=VRF.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -265,8 +270,8 @@ class IPRangeForm(TenancyForm, NetBoxModelForm):
|
|||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet(
|
FieldSet(
|
||||||
'vrf', 'start_address', 'end_address', 'role', 'status', 'mark_populated', 'mark_utilized', 'description',
|
'prefix', 'vrf', 'start_address', 'end_address', 'role', 'status', 'mark_populated', 'mark_utilized',
|
||||||
'tags', name=_('IP Range')
|
'description', 'tags', name=_('IP Range')
|
||||||
),
|
),
|
||||||
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||||
)
|
)
|
||||||
@ -274,8 +279,8 @@ class IPRangeForm(TenancyForm, NetBoxModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = IPRange
|
model = IPRange
|
||||||
fields = [
|
fields = [
|
||||||
'vrf', 'start_address', 'end_address', 'status', 'role', 'tenant_group', 'tenant', 'mark_populated',
|
'prefix', 'vrf', 'start_address', 'end_address', 'status', 'role', 'tenant_group', 'tenant',
|
||||||
'mark_utilized', 'description', 'comments', 'tags',
|
'mark_populated', 'mark_utilized', 'description', 'comments', 'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
# Generated by Django 5.0.9 on 2025-02-20 16:49
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ipam', '0081_remove_service_device_virtual_machine_add_parent_gfk_index'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='prefix',
|
||||||
|
name='parent',
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name='children',
|
||||||
|
to='ipam.prefix',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='ipaddress',
|
||||||
|
name='prefix',
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name='ip_addresses',
|
||||||
|
to='ipam.prefix',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='iprange',
|
||||||
|
name='prefix',
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name='ip_ranges',
|
||||||
|
to='ipam.prefix',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='prefix',
|
||||||
|
name='aggregate',
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name='prefixes',
|
||||||
|
to='ipam.aggregate',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -3,11 +3,14 @@
|
|||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
from ipam.choices import PrefixStatusChoices
|
||||||
|
|
||||||
|
|
||||||
def draw_progress(count, total, length=20):
|
def draw_progress(count, total, length=20):
|
||||||
|
if total == 0:
|
||||||
|
return
|
||||||
progress = count / total
|
progress = count / total
|
||||||
percent = int(progress * 100)
|
percent = int(progress * 100)
|
||||||
bar = int(progress * length)
|
bar = int(progress * length)
|
||||||
@ -24,8 +27,9 @@ def set_ipaddress_prefix(apps, schema_editor):
|
|||||||
addresses = IPAddress.objects.all()
|
addresses = IPAddress.objects.all()
|
||||||
i = 0
|
i = 0
|
||||||
total = addresses.count()
|
total = addresses.count()
|
||||||
if total > 0:
|
if total == 0:
|
||||||
print('\r\n')
|
return
|
||||||
|
print('\r\n')
|
||||||
draw_progress(i, total, 50)
|
draw_progress(i, total, 50)
|
||||||
for address in addresses:
|
for address in addresses:
|
||||||
i += 1
|
i += 1
|
||||||
@ -55,8 +59,10 @@ def set_iprange_prefix(apps, schema_editor):
|
|||||||
addresses = IPRange.objects.all()
|
addresses = IPRange.objects.all()
|
||||||
i = 0
|
i = 0
|
||||||
total = addresses.count()
|
total = addresses.count()
|
||||||
if total > 0:
|
if total == 0:
|
||||||
print('\r\n')
|
return
|
||||||
|
|
||||||
|
print('\r\n')
|
||||||
draw_progress(i, total, 50)
|
draw_progress(i, total, 50)
|
||||||
for address in addresses:
|
for address in addresses:
|
||||||
i += 1
|
i += 1
|
||||||
@ -86,8 +92,10 @@ def set_prefix_aggregate(apps, schema_editor):
|
|||||||
addresses = Prefix.objects.all()
|
addresses = Prefix.objects.all()
|
||||||
i = 0
|
i = 0
|
||||||
total = addresses.count()
|
total = addresses.count()
|
||||||
if total > 0:
|
if total == 0:
|
||||||
print('\r\n')
|
return
|
||||||
|
|
||||||
|
print('\r\n')
|
||||||
draw_progress(i, total, 50)
|
draw_progress(i, total, 50)
|
||||||
for address in addresses:
|
for address in addresses:
|
||||||
i += 1
|
i += 1
|
||||||
@ -108,47 +116,54 @@ def unset_prefix_aggregate(apps, schema_editor):
|
|||||||
Prefix.objects.update(aggregate=None)
|
Prefix.objects.update(aggregate=None)
|
||||||
|
|
||||||
|
|
||||||
|
def set_prefix_parent(apps, schema_editor):
|
||||||
|
Prefix = apps.get_model('ipam', 'Prefix')
|
||||||
|
start = time.time()
|
||||||
|
addresses = Prefix.objects.all()
|
||||||
|
i = 0
|
||||||
|
total = addresses.count()
|
||||||
|
if total == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
print('\r\n')
|
||||||
|
draw_progress(i, total, 50)
|
||||||
|
for address in addresses:
|
||||||
|
i += 1
|
||||||
|
prefixes = Prefix.objects.exclude(pk=address.pk).filter(
|
||||||
|
models.Q(
|
||||||
|
vrf=address.vrf,
|
||||||
|
prefix__net_contains=str(address.prefix.ip)
|
||||||
|
) | models.Q(
|
||||||
|
vrf=None,
|
||||||
|
status=PrefixStatusChoices.STATUS_CONTAINER,
|
||||||
|
prefix__net_contains=str(address.prefix.ip),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if not prefixes.exists():
|
||||||
|
draw_progress(i, total, 50)
|
||||||
|
continue
|
||||||
|
|
||||||
|
address.parent = prefixes.last()
|
||||||
|
address.save()
|
||||||
|
draw_progress(i, total, 50)
|
||||||
|
end = time.time()
|
||||||
|
print(f"\r\nElapsed Time: {end - start:.2f}s")
|
||||||
|
|
||||||
|
|
||||||
|
def unset_prefix_parent(apps, schema_editor):
|
||||||
|
Prefix = apps.get_model('ipam', 'Prefix')
|
||||||
|
Prefix.objects.update(parent=None)
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('ipam', '0081_remove_service_device_virtual_machine_add_parent_gfk_index'),
|
('ipam', '0082_ipaddress_iprange_prefix_parent'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
|
||||||
model_name='ipaddress',
|
|
||||||
name='prefix',
|
|
||||||
field=models.ForeignKey(
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.SET_NULL,
|
|
||||||
related_name='ip_addresses',
|
|
||||||
to='ipam.prefix',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='iprange',
|
|
||||||
name='prefix',
|
|
||||||
field=models.ForeignKey(
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
|
||||||
related_name='ip_ranges',
|
|
||||||
to='ipam.prefix',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='prefix',
|
|
||||||
name='aggregate',
|
|
||||||
field=models.ForeignKey(
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
|
||||||
related_name='prefixes',
|
|
||||||
to='ipam.aggregate',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.RunPython(set_ipaddress_prefix, unset_ipaddress_prefix),
|
migrations.RunPython(set_ipaddress_prefix, unset_ipaddress_prefix),
|
||||||
migrations.RunPython(set_iprange_prefix, unset_iprange_prefix),
|
migrations.RunPython(set_iprange_prefix, unset_iprange_prefix),
|
||||||
migrations.RunPython(set_prefix_aggregate, unset_prefix_aggregate),
|
migrations.RunPython(set_prefix_aggregate, unset_prefix_aggregate),
|
||||||
|
migrations.RunPython(set_prefix_parent, unset_prefix_parent),
|
||||||
]
|
]
|
@ -6,6 +6,7 @@ from django.db.models import F
|
|||||||
from django.db.models.functions import Cast
|
from django.db.models.functions import Cast
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from netaddr.ip import IPNetwork
|
||||||
|
|
||||||
from core.models import ObjectType
|
from core.models import ObjectType
|
||||||
from dcim.models.mixins import CachedScopeMixin
|
from dcim.models.mixins import CachedScopeMixin
|
||||||
@ -206,12 +207,20 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary
|
|||||||
"""
|
"""
|
||||||
aggregate = models.ForeignKey(
|
aggregate = models.ForeignKey(
|
||||||
to='ipam.Aggregate',
|
to='ipam.Aggregate',
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.SET_NULL,
|
||||||
related_name='prefixes',
|
related_name='prefixes',
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
verbose_name=_('aggregate')
|
verbose_name=_('aggregate')
|
||||||
)
|
)
|
||||||
|
parent = models.ForeignKey(
|
||||||
|
to='ipam.Prefix',
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name='children',
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
verbose_name=_('Prefix')
|
||||||
|
)
|
||||||
prefix = IPNetworkField(
|
prefix = IPNetworkField(
|
||||||
verbose_name=_('prefix'),
|
verbose_name=_('prefix'),
|
||||||
help_text=_('IPv4 or IPv6 network with mask')
|
help_text=_('IPv4 or IPv6 network with mask')
|
||||||
@ -299,6 +308,8 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary
|
|||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
if self.prefix:
|
if self.prefix:
|
||||||
|
if not isinstance(self.prefix, IPNetwork):
|
||||||
|
self.prefix = IPNetwork(self.prefix)
|
||||||
|
|
||||||
# /0 masks are not acceptable
|
# /0 masks are not acceptable
|
||||||
if self.prefix.prefixlen == 0:
|
if self.prefix.prefixlen == 0:
|
||||||
@ -306,6 +317,17 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary
|
|||||||
'prefix': _("Cannot create prefix with /0 mask.")
|
'prefix': _("Cannot create prefix with /0 mask.")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if self.parent:
|
||||||
|
if self.prefix not in self.parent.prefix:
|
||||||
|
raise ValidationError({
|
||||||
|
'parent': _("Prefix must be part of parent prefix.")
|
||||||
|
})
|
||||||
|
|
||||||
|
if self.parent.status != PrefixStatusChoices.STATUS_CONTAINER and self.vrf != self.parent.vrf:
|
||||||
|
raise ValidationError({
|
||||||
|
'vrf': _("VRF must match the parent VRF.")
|
||||||
|
})
|
||||||
|
|
||||||
# Enforce unique IP space (if applicable)
|
# Enforce unique IP space (if applicable)
|
||||||
if (self.vrf is None and get_config().ENFORCE_GLOBAL_UNIQUE) or (self.vrf and self.vrf.enforce_unique):
|
if (self.vrf is None and get_config().ENFORCE_GLOBAL_UNIQUE) or (self.vrf and self.vrf.enforce_unique):
|
||||||
duplicate_prefixes = self.get_duplicates()
|
duplicate_prefixes = self.get_duplicates()
|
||||||
@ -319,6 +341,14 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary
|
|||||||
})
|
})
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
vrf_id = self.vrf.pk if self.vrf else None
|
||||||
|
|
||||||
|
if not self.pk and not self.parent:
|
||||||
|
parent = self.find_parent_prefix(self)
|
||||||
|
self.parent = parent
|
||||||
|
elif self.parent and (self.prefix != self._prefix or vrf_id != self._vrf_id):
|
||||||
|
parent = self.find_parent_prefix(self)
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
if isinstance(self.prefix, netaddr.IPNetwork):
|
if isinstance(self.prefix, netaddr.IPNetwork):
|
||||||
|
|
||||||
@ -483,6 +513,20 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary
|
|||||||
|
|
||||||
return min(utilization, 100)
|
return min(utilization, 100)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def find_parent_prefix(cls, network):
|
||||||
|
prefixes = Prefix.objects.filter(
|
||||||
|
models.Q(
|
||||||
|
vrf=network.vrf,
|
||||||
|
prefix__net_contains=str(network)
|
||||||
|
) | models.Q(
|
||||||
|
vrf=None,
|
||||||
|
status=PrefixStatusChoices.STATUS_CONTAINER,
|
||||||
|
prefix__net_contains=str(network),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return prefixes.last()
|
||||||
|
|
||||||
|
|
||||||
class IPRange(ContactsMixin, PrimaryModel):
|
class IPRange(ContactsMixin, PrimaryModel):
|
||||||
"""
|
"""
|
||||||
@ -490,7 +534,7 @@ class IPRange(ContactsMixin, PrimaryModel):
|
|||||||
"""
|
"""
|
||||||
prefix = models.ForeignKey(
|
prefix = models.ForeignKey(
|
||||||
to='ipam.Prefix',
|
to='ipam.Prefix',
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.SET_NULL,
|
||||||
related_name='ip_ranges',
|
related_name='ip_ranges',
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
@ -565,6 +609,27 @@ class IPRange(ContactsMixin, PrimaryModel):
|
|||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
if self.start_address and self.end_address:
|
if self.start_address and self.end_address:
|
||||||
|
# If prefix is set, validate suitability
|
||||||
|
if self.prefix:
|
||||||
|
# Check that start address and end address are within the prefix range
|
||||||
|
if self.start_address not in self.prefix.prefix and self.end_address not in self.prefix.prefix:
|
||||||
|
raise ValidationError({
|
||||||
|
'start_address': _("Start address must be part of the selected prefix"),
|
||||||
|
'end_address': _("End address must be part of the selected prefix.")
|
||||||
|
})
|
||||||
|
elif self.start_address not in self.prefix.prefix:
|
||||||
|
raise ValidationError({
|
||||||
|
'start_address': _("Start address must be part of the selected prefix")
|
||||||
|
})
|
||||||
|
elif self.end_address not in self.prefix.prefix:
|
||||||
|
raise ValidationError({
|
||||||
|
'end_address': _("End address must be part of the selected prefix.")
|
||||||
|
})
|
||||||
|
# Check that VRF matches prefix VRF
|
||||||
|
if self.vrf != self.prefix.vrf:
|
||||||
|
raise ValidationError({
|
||||||
|
'vrf': _("VRF must match the prefix VRF.")
|
||||||
|
})
|
||||||
|
|
||||||
# Check that start & end IP versions match
|
# Check that start & end IP versions match
|
||||||
if self.start_address.version != self.end_address.version:
|
if self.start_address.version != self.end_address.version:
|
||||||
@ -828,6 +893,7 @@ class IPAddress(ContactsMixin, PrimaryModel):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self._address = self.address
|
||||||
# Denote the original assigned object (if any) for validation in clean()
|
# Denote the original assigned object (if any) for validation in clean()
|
||||||
self._original_assigned_object_id = self.__dict__.get('assigned_object_id')
|
self._original_assigned_object_id = self.__dict__.get('assigned_object_id')
|
||||||
self._original_assigned_object_type_id = self.__dict__.get('assigned_object_type_id')
|
self._original_assigned_object_type_id = self.__dict__.get('assigned_object_type_id')
|
||||||
@ -869,11 +935,16 @@ class IPAddress(ContactsMixin, PrimaryModel):
|
|||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
if self.address:
|
if self.address:
|
||||||
|
# If prefix is set, validate suitability
|
||||||
if self.prefix:
|
if self.prefix:
|
||||||
if self.address not in self.prefix.prefix:
|
if self.address not in self.prefix.prefix:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'prefix': _("IP address must be part of the selected prefix.")
|
'prefix': _("IP address must be part of the selected prefix.")
|
||||||
})
|
})
|
||||||
|
if self.vrf != self.prefix.vrf:
|
||||||
|
raise ValidationError({
|
||||||
|
'vrf': _("IP address VRF must match the prefix VRF.")
|
||||||
|
})
|
||||||
|
|
||||||
# /0 masks are not acceptable
|
# /0 masks are not acceptable
|
||||||
if self.address.prefixlen == 0:
|
if self.address.prefixlen == 0:
|
||||||
|
@ -66,10 +66,11 @@ class IPRangeIndex(SearchIndex):
|
|||||||
fields = (
|
fields = (
|
||||||
('start_address', 100),
|
('start_address', 100),
|
||||||
('end_address', 300),
|
('end_address', 300),
|
||||||
|
('prefix', 400),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
display_attrs = ('vrf', 'tenant', 'status', 'role', 'description')
|
display_attrs = ('prefix', 'vrf', 'tenant', 'status', 'role', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -77,10 +78,12 @@ class PrefixIndex(SearchIndex):
|
|||||||
model = models.Prefix
|
model = models.Prefix
|
||||||
fields = (
|
fields = (
|
||||||
('prefix', 110),
|
('prefix', 110),
|
||||||
|
('parent', 200),
|
||||||
|
('aggregate', 300),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
display_attrs = ('scope', 'vrf', 'tenant', 'vlan', 'status', 'role', 'description')
|
display_attrs = ('scope', 'aggregate', 'parent', 'vrf', 'tenant', 'vlan', 'status', 'role', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.db.models.signals import post_delete, post_save, pre_delete
|
from django.db.models.signals import post_delete, post_save, pre_delete
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
from netaddr.ip import IPNetwork
|
||||||
|
|
||||||
from dcim.models import Device
|
from dcim.models import Device
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine
|
||||||
from .models import IPAddress, Prefix
|
from .choices import PrefixStatusChoices
|
||||||
|
from .models import IPAddress, Prefix, IPRange
|
||||||
|
|
||||||
|
|
||||||
def update_parents_children(prefix):
|
def update_parents_children(prefix):
|
||||||
@ -46,6 +48,7 @@ def update_ipaddress_prefix(prefix, delete=False):
|
|||||||
Q(address__net_contained_or_equal=prefix.prefix, vrf=prefix.vrf) |
|
Q(address__net_contained_or_equal=prefix.prefix, vrf=prefix.vrf) |
|
||||||
Q(prefix=prefix)
|
Q(prefix=prefix)
|
||||||
)
|
)
|
||||||
|
|
||||||
for address in addresses:
|
for address in addresses:
|
||||||
if not address.prefix or (prefix.prefix in address.prefix.prefix and address.address in prefix.prefix):
|
if not address.prefix or (prefix.prefix in address.prefix.prefix and address.address in prefix.prefix):
|
||||||
# Set to new Prefix as the prefix is a child of the old prefix and the address is contained in the
|
# Set to new Prefix as the prefix is a child of the old prefix and the address is contained in the
|
||||||
@ -65,6 +68,126 @@ def update_ipaddress_prefix(prefix, delete=False):
|
|||||||
IPAddress.objects.bulk_update(addresses, ['prefix'], batch_size=100)
|
IPAddress.objects.bulk_update(addresses, ['prefix'], batch_size=100)
|
||||||
|
|
||||||
|
|
||||||
|
def update_iprange_prefix(prefix, delete=False):
|
||||||
|
if delete:
|
||||||
|
# Get all possible addresses
|
||||||
|
addresses = IPRange.objects.filter(prefix=prefix)
|
||||||
|
# Find a new containing prefix
|
||||||
|
prefix = Prefix.objects.filter(
|
||||||
|
prefix__net_contains_or_equals=prefix.prefix,
|
||||||
|
vrf=prefix.vrf
|
||||||
|
).exclude(pk=prefix.pk).last()
|
||||||
|
|
||||||
|
for address in addresses:
|
||||||
|
# Set contained addresses to the containing prefix if it exists
|
||||||
|
address.prefix = prefix
|
||||||
|
else:
|
||||||
|
# Get all possible modified addresses
|
||||||
|
addresses = IPRange.objects.filter(
|
||||||
|
Q(start_address__net_contained_or_equal=prefix.prefix, vrf=prefix.vrf) |
|
||||||
|
Q(prefix=prefix)
|
||||||
|
)
|
||||||
|
|
||||||
|
for address in addresses:
|
||||||
|
if not address.prefix or (
|
||||||
|
prefix.prefix in address.prefix.prefix and address.start_address in prefix.prefix and
|
||||||
|
address.end_address in prefix.prefix
|
||||||
|
):
|
||||||
|
# Set to new Prefix as the prefix is a child of the old prefix and the address is contained in the
|
||||||
|
# prefix
|
||||||
|
address.prefix = prefix
|
||||||
|
elif address.prefix and address.address not in prefix.prefix:
|
||||||
|
# Find a new prefix as the prefix no longer contains the address
|
||||||
|
address.prefix = Prefix.objects.filter(Q(prefix__net_contains_or_equals=address.start_address) &
|
||||||
|
Q(prefix__net_contains_or_equals=address.end_address),
|
||||||
|
vrf=prefix.vrf
|
||||||
|
).last()
|
||||||
|
else:
|
||||||
|
# No-OP as the prefix does not require modification
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Update the addresses
|
||||||
|
IPAddress.objects.bulk_update(addresses, ['prefix'], batch_size=100)
|
||||||
|
|
||||||
|
|
||||||
|
def update_prefix_parents(prefix, delete=False):
|
||||||
|
if delete:
|
||||||
|
# Get all possible addresses
|
||||||
|
prefixes = prefix.children.all()
|
||||||
|
|
||||||
|
for pfx in prefixes:
|
||||||
|
parent = Prefix.objects.exclude(pk=pfx.pk).exclude(pk=prefix.pk).filter(
|
||||||
|
Q(
|
||||||
|
vrf=pfx.vrf,
|
||||||
|
prefix__net_contains=str(pfx.prefix)
|
||||||
|
) | Q(
|
||||||
|
vrf=None,
|
||||||
|
status=PrefixStatusChoices.STATUS_CONTAINER,
|
||||||
|
prefix__net_contains=str(pfx.prefix),
|
||||||
|
)
|
||||||
|
).last()
|
||||||
|
# Set contained addresses to the containing prefix if it exists
|
||||||
|
pfx.parent = parent
|
||||||
|
else:
|
||||||
|
# Get all possible addresses
|
||||||
|
prefixes = prefix.children.all() | Prefix.objects.filter(
|
||||||
|
Q(
|
||||||
|
parent=prefix.parent,
|
||||||
|
vrf=prefix.vrf,
|
||||||
|
prefix__net_contained=str(prefix.prefix)
|
||||||
|
) | Q(
|
||||||
|
parent=prefix.parent,
|
||||||
|
vrf=None,
|
||||||
|
status=PrefixStatusChoices.STATUS_CONTAINER,
|
||||||
|
prefix__net_contained=str(prefix.prefix),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if isinstance(prefix.prefix, str):
|
||||||
|
prefix.prefix = IPNetwork(prefix.prefix)
|
||||||
|
for pfx in prefixes:
|
||||||
|
if isinstance(pfx.prefix, str):
|
||||||
|
pfx.prefix = IPNetwork(pfx.prefix)
|
||||||
|
|
||||||
|
if pfx.parent == prefix and pfx.prefix.ip not in prefix.prefix:
|
||||||
|
# Find new parents for orphaned prefixes
|
||||||
|
parent = Prefix.objects.exclude(pk=pfx.pk).filter(
|
||||||
|
Q(
|
||||||
|
vrf=pfx.vrf,
|
||||||
|
prefix__net_contains=str(pfx.prefix)
|
||||||
|
) | Q(
|
||||||
|
vrf=None,
|
||||||
|
status=PrefixStatusChoices.STATUS_CONTAINER,
|
||||||
|
prefix__net_contains=str(pfx.prefix),
|
||||||
|
)
|
||||||
|
).last()
|
||||||
|
# Set contained addresses to the containing prefix if it exists
|
||||||
|
pfx.parent = parent
|
||||||
|
elif pfx.parent == prefix and pfx.vrf != prefix.vrf:
|
||||||
|
# Find new parents for orphaned prefixes
|
||||||
|
parent = Prefix.objects.exclude(pk=pfx.pk).filter(
|
||||||
|
Q(
|
||||||
|
vrf=pfx.vrf,
|
||||||
|
prefix__net_contains=str(pfx.prefix)
|
||||||
|
) | Q(
|
||||||
|
vrf=None,
|
||||||
|
status=PrefixStatusChoices.STATUS_CONTAINER,
|
||||||
|
prefix__net_contains=str(pfx.prefix),
|
||||||
|
)
|
||||||
|
).last()
|
||||||
|
# Set contained addresses to the containing prefix if it exists
|
||||||
|
pfx.parent = parent
|
||||||
|
elif pfx.parent != prefix and pfx.vrf == prefix.vrf and pfx.prefix in prefix.prefix:
|
||||||
|
# Set the parent to the prefix
|
||||||
|
pfx.parent = prefix
|
||||||
|
else:
|
||||||
|
# No-OP as the prefix does not require modification
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Update the prefixes
|
||||||
|
Prefix.objects.bulk_update(prefixes, ['parent'], batch_size=100)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Prefix)
|
@receiver(post_save, sender=Prefix)
|
||||||
def handle_prefix_saved(instance, created, **kwargs):
|
def handle_prefix_saved(instance, created, **kwargs):
|
||||||
|
|
||||||
@ -72,6 +195,7 @@ def handle_prefix_saved(instance, created, **kwargs):
|
|||||||
if created or instance.vrf_id != instance._vrf_id or instance.prefix != instance._prefix:
|
if created or instance.vrf_id != instance._vrf_id or instance.prefix != instance._prefix:
|
||||||
|
|
||||||
update_ipaddress_prefix(instance)
|
update_ipaddress_prefix(instance)
|
||||||
|
update_prefix_parents(instance)
|
||||||
update_parents_children(instance)
|
update_parents_children(instance)
|
||||||
update_children_depth(instance)
|
update_children_depth(instance)
|
||||||
|
|
||||||
@ -85,6 +209,7 @@ def handle_prefix_saved(instance, created, **kwargs):
|
|||||||
@receiver(pre_delete, sender=Prefix)
|
@receiver(pre_delete, sender=Prefix)
|
||||||
def pre_handle_prefix_deleted(instance, **kwargs):
|
def pre_handle_prefix_deleted(instance, **kwargs):
|
||||||
update_ipaddress_prefix(instance, True)
|
update_ipaddress_prefix(instance, True)
|
||||||
|
update_prefix_parents(instance, delete=True)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=Prefix)
|
@receiver(post_delete, sender=Prefix)
|
||||||
@ -93,6 +218,7 @@ def handle_prefix_deleted(instance, **kwargs):
|
|||||||
update_parents_children(instance)
|
update_parents_children(instance)
|
||||||
update_children_depth(instance)
|
update_children_depth(instance)
|
||||||
update_ipaddress_prefix(instance, delete=True)
|
update_ipaddress_prefix(instance, delete=True)
|
||||||
|
update_prefix_parents(instance, delete=True)
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_delete, sender=IPAddress)
|
@receiver(pre_delete, sender=IPAddress)
|
||||||
|
@ -155,6 +155,7 @@ class PrefixUtilizationColumn(columns.UtilizationColumn):
|
|||||||
|
|
||||||
|
|
||||||
class PrefixTable(TenancyColumnsMixin, NetBoxTable):
|
class PrefixTable(TenancyColumnsMixin, NetBoxTable):
|
||||||
|
# TODO: Alter for parent prefix
|
||||||
prefix = columns.TemplateColumn(
|
prefix = columns.TemplateColumn(
|
||||||
verbose_name=_('Prefix'),
|
verbose_name=_('Prefix'),
|
||||||
template_code=PREFIX_LINK_WITH_DEPTH,
|
template_code=PREFIX_LINK_WITH_DEPTH,
|
||||||
@ -253,6 +254,7 @@ class PrefixTable(TenancyColumnsMixin, NetBoxTable):
|
|||||||
# IP ranges
|
# IP ranges
|
||||||
#
|
#
|
||||||
class IPRangeTable(TenancyColumnsMixin, NetBoxTable):
|
class IPRangeTable(TenancyColumnsMixin, NetBoxTable):
|
||||||
|
# TODO: Alter for prefix
|
||||||
start_address = tables.Column(
|
start_address = tables.Column(
|
||||||
verbose_name=_('Start address'),
|
verbose_name=_('Start address'),
|
||||||
linkify=True
|
linkify=True
|
||||||
@ -309,6 +311,7 @@ class IPRangeTable(TenancyColumnsMixin, NetBoxTable):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class IPAddressTable(TenancyColumnsMixin, NetBoxTable):
|
class IPAddressTable(TenancyColumnsMixin, NetBoxTable):
|
||||||
|
# TODO: Alter for prefix
|
||||||
address = tables.TemplateColumn(
|
address = tables.TemplateColumn(
|
||||||
template_code=IPADDRESS_LINK,
|
template_code=IPADDRESS_LINK,
|
||||||
verbose_name=_('IP Address')
|
verbose_name=_('IP Address')
|
||||||
|
@ -356,6 +356,7 @@ class RoleTest(APIViewTestCases.APIViewTestCase):
|
|||||||
|
|
||||||
class PrefixTest(APIViewTestCases.APIViewTestCase):
|
class PrefixTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = Prefix
|
model = Prefix
|
||||||
|
# TODO: Alter for parent prefix
|
||||||
brief_fields = ['_depth', 'description', 'display', 'family', 'id', 'prefix', 'url']
|
brief_fields = ['_depth', 'description', 'display', 'family', 'id', 'prefix', 'url']
|
||||||
create_data = [
|
create_data = [
|
||||||
{
|
{
|
||||||
@ -535,6 +536,7 @@ class PrefixTest(APIViewTestCases.APIViewTestCase):
|
|||||||
|
|
||||||
class IPRangeTest(APIViewTestCases.APIViewTestCase):
|
class IPRangeTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = IPRange
|
model = IPRange
|
||||||
|
# TODO: Alter for parent prefix
|
||||||
brief_fields = ['description', 'display', 'end_address', 'family', 'id', 'start_address', 'url']
|
brief_fields = ['description', 'display', 'end_address', 'family', 'id', 'start_address', 'url']
|
||||||
create_data = [
|
create_data = [
|
||||||
{
|
{
|
||||||
@ -634,6 +636,7 @@ class IPRangeTest(APIViewTestCases.APIViewTestCase):
|
|||||||
|
|
||||||
class IPAddressTest(APIViewTestCases.APIViewTestCase):
|
class IPAddressTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
|
# TODO: Alter for parent prefix
|
||||||
brief_fields = ['address', 'description', 'display', 'family', 'id', 'url']
|
brief_fields = ['address', 'description', 'display', 'family', 'id', 'url']
|
||||||
create_data = [
|
create_data = [
|
||||||
{
|
{
|
||||||
|
@ -901,6 +901,10 @@ class PrefixTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'description': ['foobar1', 'foobar2']}
|
params = {'description': ['foobar1', 'foobar2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
# TODO: Test for parent prefix
|
||||||
|
# TODO: Test for children?
|
||||||
|
# TODO: Test for aggregate
|
||||||
|
|
||||||
|
|
||||||
class IPRangeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class IPRangeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = IPRange.objects.all()
|
queryset = IPRange.objects.all()
|
||||||
@ -1079,6 +1083,7 @@ class IPRangeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_parent(self):
|
def test_parent(self):
|
||||||
|
# TODO: Alter for prefix
|
||||||
params = {'parent': ['10.0.1.0/24', '10.0.2.0/24']}
|
params = {'parent': ['10.0.1.0/24', '10.0.2.0/24']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
params = {'parent': ['10.0.1.0/25']} # Range 10.0.1.100-199 is not fully contained by 10.0.1.0/25
|
params = {'parent': ['10.0.1.0/25']} # Range 10.0.1.100-199 is not fully contained by 10.0.1.0/25
|
||||||
@ -1315,6 +1320,7 @@ class IPAddressTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_parent(self):
|
def test_parent(self):
|
||||||
|
# TODO: Alter for prefix
|
||||||
params = {'parent': ['10.0.0.0/30', '2001:db8::/126']}
|
params = {'parent': ['10.0.0.0/30', '2001:db8::/126']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 8)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 8)
|
||||||
|
|
||||||
|
@ -39,6 +39,26 @@ class TestAggregate(TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class TestIPRange(TestCase):
|
class TestIPRange(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
cls.vrf = VRF.objects.create(name='VRF A', rd='1:1')
|
||||||
|
|
||||||
|
cls.prefixes = (
|
||||||
|
|
||||||
|
# IPv4
|
||||||
|
Prefix(prefix='192.0.0.0/16'),
|
||||||
|
Prefix(prefix='192.0.2.0/24'),
|
||||||
|
Prefix(prefix='192.0.0.0/16', vrf=cls.vrf),
|
||||||
|
|
||||||
|
# IPv6
|
||||||
|
Prefix(prefix='2001:db8::/32'),
|
||||||
|
Prefix(prefix='2001:db8::/64'),
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
for prefix in cls.prefixes:
|
||||||
|
prefix.clean()
|
||||||
|
prefix.save()
|
||||||
|
|
||||||
def test_overlapping_range(self):
|
def test_overlapping_range(self):
|
||||||
iprange_192_168 = IPRange.objects.create(
|
iprange_192_168 = IPRange.objects.create(
|
||||||
@ -87,6 +107,69 @@ class TestIPRange(TestCase):
|
|||||||
)
|
)
|
||||||
iprange_4_198_201.clean()
|
iprange_4_198_201.clean()
|
||||||
|
|
||||||
|
def test_parent_prefix(self):
|
||||||
|
ranges = (
|
||||||
|
IPRange(
|
||||||
|
start_address=IPNetwork('192.0.0.1/24'),
|
||||||
|
end_address=IPNetwork('192.0.0.254/24'),
|
||||||
|
prefix=self.prefixes[0]
|
||||||
|
),
|
||||||
|
IPRange(
|
||||||
|
start_address=IPNetwork('192.0.2.1/24'),
|
||||||
|
end_address=IPNetwork('192.0.2.254/24'),
|
||||||
|
prefix=self.prefixes[1]
|
||||||
|
),
|
||||||
|
IPRange(
|
||||||
|
start_address=IPNetwork('192.0.2.1/24'),
|
||||||
|
end_address=IPNetwork('192.0.2.254/24'),
|
||||||
|
vrf=self.vrf,
|
||||||
|
prefix=self.prefixes[2]
|
||||||
|
),
|
||||||
|
IPRange(
|
||||||
|
start_address=IPNetwork('2001:db8::/64'),
|
||||||
|
end_address=IPNetwork('2001:db8::ffff/64'),
|
||||||
|
prefix=self.prefixes[4]
|
||||||
|
),
|
||||||
|
IPRange(
|
||||||
|
start_address=IPNetwork('2001:db8:2::/64'),
|
||||||
|
end_address=IPNetwork('2001:db8:2::ffff/64'),
|
||||||
|
prefix=self.prefixes[3]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
for range in ranges:
|
||||||
|
range.clean()
|
||||||
|
range.save()
|
||||||
|
|
||||||
|
self.assertEqual(ranges[0].prefix, self.prefixes[0])
|
||||||
|
self.assertEqual(ranges[1].prefix, self.prefixes[1])
|
||||||
|
self.assertEqual(ranges[2].prefix, self.prefixes[2])
|
||||||
|
self.assertEqual(ranges[3].prefix, self.prefixes[4])
|
||||||
|
|
||||||
|
def test_parent_prefix_change(self):
|
||||||
|
|
||||||
|
range = IPRange(
|
||||||
|
start_address=IPNetwork('192.0.1.1/24'),
|
||||||
|
end_address=IPNetwork('192.0.1.254/24'),
|
||||||
|
prefix=self.prefixes[0]
|
||||||
|
)
|
||||||
|
range.clean()
|
||||||
|
range.save()
|
||||||
|
|
||||||
|
prefix = Prefix(prefix='192.0.1.0/17')
|
||||||
|
prefix.clean()
|
||||||
|
prefix.save()
|
||||||
|
|
||||||
|
range.refresh_from_db()
|
||||||
|
|
||||||
|
self.assertEqual(range.prefix, prefix)
|
||||||
|
|
||||||
|
# TODO: Prefix Altered
|
||||||
|
# TODO: Prefix Deleted
|
||||||
|
|
||||||
|
# TODO: Prefix falls outside range
|
||||||
|
# TODO: Prefix VRF does not match range VRF
|
||||||
|
|
||||||
|
|
||||||
class TestPrefix(TestCase):
|
class TestPrefix(TestCase):
|
||||||
|
|
||||||
@ -344,17 +427,21 @@ class TestPrefixHierarchy(TestCase):
|
|||||||
prefixes = (
|
prefixes = (
|
||||||
|
|
||||||
# IPv4
|
# IPv4
|
||||||
Prefix(prefix='10.0.0.0/8', _depth=0, _children=2),
|
Prefix(prefix='10.0.0.0/8'),
|
||||||
Prefix(prefix='10.0.0.0/16', _depth=1, _children=1),
|
Prefix(prefix='10.0.0.0/16'),
|
||||||
Prefix(prefix='10.0.0.0/24', _depth=2, _children=0),
|
Prefix(prefix='10.0.0.0/24'),
|
||||||
|
Prefix(prefix='192.168.0.0/16'),
|
||||||
|
|
||||||
# IPv6
|
# IPv6
|
||||||
Prefix(prefix='2001:db8::/32', _depth=0, _children=2),
|
Prefix(prefix='2001:db8::/32'),
|
||||||
Prefix(prefix='2001:db8::/40', _depth=1, _children=1),
|
Prefix(prefix='2001:db8::/40'),
|
||||||
Prefix(prefix='2001:db8::/48', _depth=2, _children=0),
|
Prefix(prefix='2001:db8::/48'),
|
||||||
|
|
||||||
)
|
)
|
||||||
Prefix.objects.bulk_create(prefixes)
|
|
||||||
|
for prefix in prefixes:
|
||||||
|
prefix.clean()
|
||||||
|
prefix.save()
|
||||||
|
|
||||||
def test_create_prefix4(self):
|
def test_create_prefix4(self):
|
||||||
# Create 10.0.0.0/12
|
# Create 10.0.0.0/12
|
||||||
@ -362,15 +449,19 @@ class TestPrefixHierarchy(TestCase):
|
|||||||
|
|
||||||
prefixes = Prefix.objects.filter(prefix__family=4)
|
prefixes = Prefix.objects.filter(prefix__family=4)
|
||||||
self.assertEqual(prefixes[0].prefix, IPNetwork('10.0.0.0/8'))
|
self.assertEqual(prefixes[0].prefix, IPNetwork('10.0.0.0/8'))
|
||||||
|
self.assertEqual(prefixes[0].parent, None)
|
||||||
self.assertEqual(prefixes[0]._depth, 0)
|
self.assertEqual(prefixes[0]._depth, 0)
|
||||||
self.assertEqual(prefixes[0]._children, 3)
|
self.assertEqual(prefixes[0]._children, 3)
|
||||||
self.assertEqual(prefixes[1].prefix, IPNetwork('10.0.0.0/12'))
|
self.assertEqual(prefixes[1].prefix, IPNetwork('10.0.0.0/12'))
|
||||||
|
self.assertEqual(prefixes[1].parent.prefix, IPNetwork('10.0.0.0/8'))
|
||||||
self.assertEqual(prefixes[1]._depth, 1)
|
self.assertEqual(prefixes[1]._depth, 1)
|
||||||
self.assertEqual(prefixes[1]._children, 2)
|
self.assertEqual(prefixes[1]._children, 2)
|
||||||
self.assertEqual(prefixes[2].prefix, IPNetwork('10.0.0.0/16'))
|
self.assertEqual(prefixes[2].prefix, IPNetwork('10.0.0.0/16'))
|
||||||
|
self.assertEqual(prefixes[2].parent.prefix, IPNetwork('10.0.0.0/12'))
|
||||||
self.assertEqual(prefixes[2]._depth, 2)
|
self.assertEqual(prefixes[2]._depth, 2)
|
||||||
self.assertEqual(prefixes[2]._children, 1)
|
self.assertEqual(prefixes[2]._children, 1)
|
||||||
self.assertEqual(prefixes[3].prefix, IPNetwork('10.0.0.0/24'))
|
self.assertEqual(prefixes[3].prefix, IPNetwork('10.0.0.0/24'))
|
||||||
|
self.assertEqual(prefixes[3].parent.prefix, IPNetwork('10.0.0.0/16'))
|
||||||
self.assertEqual(prefixes[3]._depth, 3)
|
self.assertEqual(prefixes[3]._depth, 3)
|
||||||
self.assertEqual(prefixes[3]._children, 0)
|
self.assertEqual(prefixes[3]._children, 0)
|
||||||
|
|
||||||
@ -380,15 +471,19 @@ class TestPrefixHierarchy(TestCase):
|
|||||||
|
|
||||||
prefixes = Prefix.objects.filter(prefix__family=6)
|
prefixes = Prefix.objects.filter(prefix__family=6)
|
||||||
self.assertEqual(prefixes[0].prefix, IPNetwork('2001:db8::/32'))
|
self.assertEqual(prefixes[0].prefix, IPNetwork('2001:db8::/32'))
|
||||||
|
self.assertEqual(prefixes[0].parent, None)
|
||||||
self.assertEqual(prefixes[0]._depth, 0)
|
self.assertEqual(prefixes[0]._depth, 0)
|
||||||
self.assertEqual(prefixes[0]._children, 3)
|
self.assertEqual(prefixes[0]._children, 3)
|
||||||
self.assertEqual(prefixes[1].prefix, IPNetwork('2001:db8::/36'))
|
self.assertEqual(prefixes[1].prefix, IPNetwork('2001:db8::/36'))
|
||||||
|
self.assertEqual(prefixes[1].parent.prefix, IPNetwork('2001:db8::/32'))
|
||||||
self.assertEqual(prefixes[1]._depth, 1)
|
self.assertEqual(prefixes[1]._depth, 1)
|
||||||
self.assertEqual(prefixes[1]._children, 2)
|
self.assertEqual(prefixes[1]._children, 2)
|
||||||
self.assertEqual(prefixes[2].prefix, IPNetwork('2001:db8::/40'))
|
self.assertEqual(prefixes[2].prefix, IPNetwork('2001:db8::/40'))
|
||||||
|
self.assertEqual(prefixes[2].parent.prefix, IPNetwork('2001:db8::/36'))
|
||||||
self.assertEqual(prefixes[2]._depth, 2)
|
self.assertEqual(prefixes[2]._depth, 2)
|
||||||
self.assertEqual(prefixes[2]._children, 1)
|
self.assertEqual(prefixes[2]._children, 1)
|
||||||
self.assertEqual(prefixes[3].prefix, IPNetwork('2001:db8::/48'))
|
self.assertEqual(prefixes[3].prefix, IPNetwork('2001:db8::/48'))
|
||||||
|
self.assertEqual(prefixes[3].parent.prefix, IPNetwork('2001:db8::/40'))
|
||||||
self.assertEqual(prefixes[3]._depth, 3)
|
self.assertEqual(prefixes[3]._depth, 3)
|
||||||
self.assertEqual(prefixes[3]._children, 0)
|
self.assertEqual(prefixes[3]._children, 0)
|
||||||
|
|
||||||
@ -400,12 +495,15 @@ class TestPrefixHierarchy(TestCase):
|
|||||||
|
|
||||||
prefixes = Prefix.objects.filter(prefix__family=4)
|
prefixes = Prefix.objects.filter(prefix__family=4)
|
||||||
self.assertEqual(prefixes[0].prefix, IPNetwork('10.0.0.0/8'))
|
self.assertEqual(prefixes[0].prefix, IPNetwork('10.0.0.0/8'))
|
||||||
|
self.assertEqual(prefixes[0].parent, None)
|
||||||
self.assertEqual(prefixes[0]._depth, 0)
|
self.assertEqual(prefixes[0]._depth, 0)
|
||||||
self.assertEqual(prefixes[0]._children, 2)
|
self.assertEqual(prefixes[0]._children, 2)
|
||||||
self.assertEqual(prefixes[1].prefix, IPNetwork('10.0.0.0/12'))
|
self.assertEqual(prefixes[1].prefix, IPNetwork('10.0.0.0/12'))
|
||||||
|
self.assertEqual(prefixes[1].parent.prefix, IPNetwork('10.0.0.0/8'))
|
||||||
self.assertEqual(prefixes[1]._depth, 1)
|
self.assertEqual(prefixes[1]._depth, 1)
|
||||||
self.assertEqual(prefixes[1]._children, 1)
|
self.assertEqual(prefixes[1]._children, 1)
|
||||||
self.assertEqual(prefixes[2].prefix, IPNetwork('10.0.0.0/16'))
|
self.assertEqual(prefixes[2].prefix, IPNetwork('10.0.0.0/16'))
|
||||||
|
self.assertEqual(prefixes[2].parent.prefix, IPNetwork('10.0.0.0/12'))
|
||||||
self.assertEqual(prefixes[2]._depth, 2)
|
self.assertEqual(prefixes[2]._depth, 2)
|
||||||
self.assertEqual(prefixes[2]._children, 0)
|
self.assertEqual(prefixes[2]._children, 0)
|
||||||
|
|
||||||
@ -417,12 +515,15 @@ class TestPrefixHierarchy(TestCase):
|
|||||||
|
|
||||||
prefixes = Prefix.objects.filter(prefix__family=6)
|
prefixes = Prefix.objects.filter(prefix__family=6)
|
||||||
self.assertEqual(prefixes[0].prefix, IPNetwork('2001:db8::/32'))
|
self.assertEqual(prefixes[0].prefix, IPNetwork('2001:db8::/32'))
|
||||||
|
self.assertEqual(prefixes[0].parent, None)
|
||||||
self.assertEqual(prefixes[0]._depth, 0)
|
self.assertEqual(prefixes[0]._depth, 0)
|
||||||
self.assertEqual(prefixes[0]._children, 2)
|
self.assertEqual(prefixes[0]._children, 2)
|
||||||
self.assertEqual(prefixes[1].prefix, IPNetwork('2001:db8::/36'))
|
self.assertEqual(prefixes[1].prefix, IPNetwork('2001:db8::/36'))
|
||||||
|
self.assertEqual(prefixes[1].parent.prefix, IPNetwork('2001:db8::/32'))
|
||||||
self.assertEqual(prefixes[1]._depth, 1)
|
self.assertEqual(prefixes[1]._depth, 1)
|
||||||
self.assertEqual(prefixes[1]._children, 1)
|
self.assertEqual(prefixes[1]._children, 1)
|
||||||
self.assertEqual(prefixes[2].prefix, IPNetwork('2001:db8::/40'))
|
self.assertEqual(prefixes[2].prefix, IPNetwork('2001:db8::/40'))
|
||||||
|
self.assertEqual(prefixes[2].parent.prefix, IPNetwork('2001:db8::/36'))
|
||||||
self.assertEqual(prefixes[2]._depth, 2)
|
self.assertEqual(prefixes[2]._depth, 2)
|
||||||
self.assertEqual(prefixes[2]._children, 0)
|
self.assertEqual(prefixes[2]._children, 0)
|
||||||
|
|
||||||
@ -437,14 +538,17 @@ class TestPrefixHierarchy(TestCase):
|
|||||||
|
|
||||||
prefixes = Prefix.objects.filter(vrf__isnull=True, prefix__family=4)
|
prefixes = Prefix.objects.filter(vrf__isnull=True, prefix__family=4)
|
||||||
self.assertEqual(prefixes[0].prefix, IPNetwork('10.0.0.0/8'))
|
self.assertEqual(prefixes[0].prefix, IPNetwork('10.0.0.0/8'))
|
||||||
|
self.assertEqual(prefixes[0].parent, None)
|
||||||
self.assertEqual(prefixes[0]._depth, 0)
|
self.assertEqual(prefixes[0]._depth, 0)
|
||||||
self.assertEqual(prefixes[0]._children, 1)
|
self.assertEqual(prefixes[0]._children, 1)
|
||||||
self.assertEqual(prefixes[1].prefix, IPNetwork('10.0.0.0/24'))
|
self.assertEqual(prefixes[1].prefix, IPNetwork('10.0.0.0/24'))
|
||||||
|
self.assertEqual(prefixes[1].parent.prefix, IPNetwork('10.0.0.0/8'))
|
||||||
self.assertEqual(prefixes[1]._depth, 1)
|
self.assertEqual(prefixes[1]._depth, 1)
|
||||||
self.assertEqual(prefixes[1]._children, 0)
|
self.assertEqual(prefixes[1]._children, 0)
|
||||||
|
|
||||||
prefixes = Prefix.objects.filter(vrf=vrf)
|
prefixes = Prefix.objects.filter(vrf=vrf)
|
||||||
self.assertEqual(prefixes[0].prefix, IPNetwork('10.0.0.0/16'))
|
self.assertEqual(prefixes[0].prefix, IPNetwork('10.0.0.0/16'))
|
||||||
|
self.assertEqual(prefixes[0].parent, None)
|
||||||
self.assertEqual(prefixes[0]._depth, 0)
|
self.assertEqual(prefixes[0]._depth, 0)
|
||||||
self.assertEqual(prefixes[0]._children, 0)
|
self.assertEqual(prefixes[0]._children, 0)
|
||||||
|
|
||||||
@ -459,14 +563,17 @@ class TestPrefixHierarchy(TestCase):
|
|||||||
|
|
||||||
prefixes = Prefix.objects.filter(vrf__isnull=True, prefix__family=6)
|
prefixes = Prefix.objects.filter(vrf__isnull=True, prefix__family=6)
|
||||||
self.assertEqual(prefixes[0].prefix, IPNetwork('2001:db8::/32'))
|
self.assertEqual(prefixes[0].prefix, IPNetwork('2001:db8::/32'))
|
||||||
|
self.assertEqual(prefixes[0].parent, None)
|
||||||
self.assertEqual(prefixes[0]._depth, 0)
|
self.assertEqual(prefixes[0]._depth, 0)
|
||||||
self.assertEqual(prefixes[0]._children, 1)
|
self.assertEqual(prefixes[0]._children, 1)
|
||||||
self.assertEqual(prefixes[1].prefix, IPNetwork('2001:db8::/48'))
|
self.assertEqual(prefixes[1].prefix, IPNetwork('2001:db8::/48'))
|
||||||
|
self.assertEqual(prefixes[1].parent.prefix, IPNetwork('2001:db8::/32'))
|
||||||
self.assertEqual(prefixes[1]._depth, 1)
|
self.assertEqual(prefixes[1]._depth, 1)
|
||||||
self.assertEqual(prefixes[1]._children, 0)
|
self.assertEqual(prefixes[1]._children, 0)
|
||||||
|
|
||||||
prefixes = Prefix.objects.filter(vrf=vrf)
|
prefixes = Prefix.objects.filter(vrf=vrf)
|
||||||
self.assertEqual(prefixes[0].prefix, IPNetwork('2001:db8::/40'))
|
self.assertEqual(prefixes[0].prefix, IPNetwork('2001:db8::/40'))
|
||||||
|
self.assertEqual(prefixes[0].parent, None)
|
||||||
self.assertEqual(prefixes[0]._depth, 0)
|
self.assertEqual(prefixes[0]._depth, 0)
|
||||||
self.assertEqual(prefixes[0]._children, 0)
|
self.assertEqual(prefixes[0]._children, 0)
|
||||||
|
|
||||||
@ -476,9 +583,11 @@ class TestPrefixHierarchy(TestCase):
|
|||||||
|
|
||||||
prefixes = Prefix.objects.filter(prefix__family=4)
|
prefixes = Prefix.objects.filter(prefix__family=4)
|
||||||
self.assertEqual(prefixes[0].prefix, IPNetwork('10.0.0.0/8'))
|
self.assertEqual(prefixes[0].prefix, IPNetwork('10.0.0.0/8'))
|
||||||
|
self.assertEqual(prefixes[0].parent, None)
|
||||||
self.assertEqual(prefixes[0]._depth, 0)
|
self.assertEqual(prefixes[0]._depth, 0)
|
||||||
self.assertEqual(prefixes[0]._children, 1)
|
self.assertEqual(prefixes[0]._children, 1)
|
||||||
self.assertEqual(prefixes[1].prefix, IPNetwork('10.0.0.0/24'))
|
self.assertEqual(prefixes[1].prefix, IPNetwork('10.0.0.0/24'))
|
||||||
|
self.assertEqual(prefixes[1].parent.prefix, IPNetwork('10.0.0.0/8'))
|
||||||
self.assertEqual(prefixes[1]._depth, 1)
|
self.assertEqual(prefixes[1]._depth, 1)
|
||||||
self.assertEqual(prefixes[1]._children, 0)
|
self.assertEqual(prefixes[1]._children, 0)
|
||||||
|
|
||||||
@ -488,9 +597,11 @@ class TestPrefixHierarchy(TestCase):
|
|||||||
|
|
||||||
prefixes = Prefix.objects.filter(prefix__family=6)
|
prefixes = Prefix.objects.filter(prefix__family=6)
|
||||||
self.assertEqual(prefixes[0].prefix, IPNetwork('2001:db8::/32'))
|
self.assertEqual(prefixes[0].prefix, IPNetwork('2001:db8::/32'))
|
||||||
|
self.assertEqual(prefixes[0].parent, None)
|
||||||
self.assertEqual(prefixes[0]._depth, 0)
|
self.assertEqual(prefixes[0]._depth, 0)
|
||||||
self.assertEqual(prefixes[0]._children, 1)
|
self.assertEqual(prefixes[0]._children, 1)
|
||||||
self.assertEqual(prefixes[1].prefix, IPNetwork('2001:db8::/48'))
|
self.assertEqual(prefixes[1].prefix, IPNetwork('2001:db8::/48'))
|
||||||
|
self.assertEqual(prefixes[1].parent.prefix, IPNetwork('2001:db8::/32'))
|
||||||
self.assertEqual(prefixes[1]._depth, 1)
|
self.assertEqual(prefixes[1]._depth, 1)
|
||||||
self.assertEqual(prefixes[1]._children, 0)
|
self.assertEqual(prefixes[1]._children, 0)
|
||||||
|
|
||||||
@ -500,15 +611,20 @@ class TestPrefixHierarchy(TestCase):
|
|||||||
|
|
||||||
prefixes = Prefix.objects.filter(prefix__family=4)
|
prefixes = Prefix.objects.filter(prefix__family=4)
|
||||||
self.assertEqual(prefixes[0].prefix, IPNetwork('10.0.0.0/8'))
|
self.assertEqual(prefixes[0].prefix, IPNetwork('10.0.0.0/8'))
|
||||||
|
self.assertEqual(prefixes[0].parent, None)
|
||||||
self.assertEqual(prefixes[0]._depth, 0)
|
self.assertEqual(prefixes[0]._depth, 0)
|
||||||
self.assertEqual(prefixes[0]._children, 3)
|
self.assertEqual(prefixes[0]._children, 3)
|
||||||
self.assertEqual(prefixes[1].prefix, IPNetwork('10.0.0.0/16'))
|
self.assertEqual(prefixes[1].prefix, IPNetwork('10.0.0.0/16'))
|
||||||
|
self.assertEqual(prefixes[1].parent.prefix, IPNetwork('10.0.0.0/8'))
|
||||||
self.assertEqual(prefixes[1]._depth, 1)
|
self.assertEqual(prefixes[1]._depth, 1)
|
||||||
self.assertEqual(prefixes[1]._children, 1)
|
self.assertEqual(prefixes[1]._children, 1)
|
||||||
self.assertEqual(prefixes[2].prefix, IPNetwork('10.0.0.0/16'))
|
self.assertEqual(prefixes[2].prefix, IPNetwork('10.0.0.0/16'))
|
||||||
|
self.assertEqual(prefixes[2].parent.prefix, IPNetwork('10.0.0.0/8'))
|
||||||
self.assertEqual(prefixes[2]._depth, 1)
|
self.assertEqual(prefixes[2]._depth, 1)
|
||||||
self.assertEqual(prefixes[2]._children, 1)
|
self.assertEqual(prefixes[2]._children, 1)
|
||||||
self.assertEqual(prefixes[3].prefix, IPNetwork('10.0.0.0/24'))
|
self.assertEqual(prefixes[3].prefix, IPNetwork('10.0.0.0/24'))
|
||||||
|
# TODO: How to we resolve the parent for duplicate prefixes
|
||||||
|
self.assertEqual(prefixes[3].parent.prefix, IPNetwork('10.0.0.0/16'))
|
||||||
self.assertEqual(prefixes[3]._depth, 2)
|
self.assertEqual(prefixes[3]._depth, 2)
|
||||||
self.assertEqual(prefixes[3]._children, 0)
|
self.assertEqual(prefixes[3]._children, 0)
|
||||||
|
|
||||||
@ -518,20 +634,48 @@ class TestPrefixHierarchy(TestCase):
|
|||||||
|
|
||||||
prefixes = Prefix.objects.filter(prefix__family=6)
|
prefixes = Prefix.objects.filter(prefix__family=6)
|
||||||
self.assertEqual(prefixes[0].prefix, IPNetwork('2001:db8::/32'))
|
self.assertEqual(prefixes[0].prefix, IPNetwork('2001:db8::/32'))
|
||||||
|
self.assertEqual(prefixes[0].parent, None)
|
||||||
self.assertEqual(prefixes[0]._depth, 0)
|
self.assertEqual(prefixes[0]._depth, 0)
|
||||||
self.assertEqual(prefixes[0]._children, 3)
|
self.assertEqual(prefixes[0]._children, 3)
|
||||||
self.assertEqual(prefixes[1].prefix, IPNetwork('2001:db8::/40'))
|
self.assertEqual(prefixes[1].prefix, IPNetwork('2001:db8::/40'))
|
||||||
|
self.assertEqual(prefixes[1].parent.prefix, IPNetwork('2001:db8::/32'))
|
||||||
self.assertEqual(prefixes[1]._depth, 1)
|
self.assertEqual(prefixes[1]._depth, 1)
|
||||||
self.assertEqual(prefixes[1]._children, 1)
|
self.assertEqual(prefixes[1]._children, 1)
|
||||||
self.assertEqual(prefixes[2].prefix, IPNetwork('2001:db8::/40'))
|
self.assertEqual(prefixes[2].prefix, IPNetwork('2001:db8::/40'))
|
||||||
|
self.assertEqual(prefixes[2].parent.prefix, IPNetwork('2001:db8::/32'))
|
||||||
self.assertEqual(prefixes[2]._depth, 1)
|
self.assertEqual(prefixes[2]._depth, 1)
|
||||||
self.assertEqual(prefixes[2]._children, 1)
|
self.assertEqual(prefixes[2]._children, 1)
|
||||||
self.assertEqual(prefixes[3].prefix, IPNetwork('2001:db8::/48'))
|
self.assertEqual(prefixes[3].prefix, IPNetwork('2001:db8::/48'))
|
||||||
|
self.assertEqual(prefixes[3].parent.prefix, IPNetwork('2001:db8::/40'))
|
||||||
self.assertEqual(prefixes[3]._depth, 2)
|
self.assertEqual(prefixes[3]._depth, 2)
|
||||||
self.assertEqual(prefixes[3]._children, 0)
|
self.assertEqual(prefixes[3]._children, 0)
|
||||||
|
|
||||||
|
|
||||||
class TestIPAddress(TestCase):
|
class TestIPAddress(TestCase):
|
||||||
|
"""
|
||||||
|
Test the automatic updating of depth and child count in response to changes made within
|
||||||
|
the prefix hierarchy.
|
||||||
|
"""
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
cls.vrf = VRF.objects.create(name='VRF A', rd='1:1')
|
||||||
|
|
||||||
|
cls.prefixes = (
|
||||||
|
|
||||||
|
# IPv4
|
||||||
|
Prefix(prefix='192.0.0.0/16'),
|
||||||
|
Prefix(prefix='192.0.2.0/24'),
|
||||||
|
Prefix(prefix='192.0.0.0/16', vrf=cls.vrf),
|
||||||
|
|
||||||
|
# IPv6
|
||||||
|
Prefix(prefix='2001:db8::/32'),
|
||||||
|
Prefix(prefix='2001:db8::/64'),
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
for prefix in cls.prefixes:
|
||||||
|
prefix.clean()
|
||||||
|
prefix.save()
|
||||||
|
|
||||||
def test_get_duplicates(self):
|
def test_get_duplicates(self):
|
||||||
ips = IPAddress.objects.bulk_create((
|
ips = IPAddress.objects.bulk_create((
|
||||||
@ -543,6 +687,44 @@ class TestIPAddress(TestCase):
|
|||||||
|
|
||||||
self.assertSetEqual(set(duplicate_ip_pks), {ips[1].pk, ips[2].pk})
|
self.assertSetEqual(set(duplicate_ip_pks), {ips[1].pk, ips[2].pk})
|
||||||
|
|
||||||
|
def test_parent_prefix(self):
|
||||||
|
ips = (
|
||||||
|
IPAddress(address=IPNetwork('192.0.0.1/24'), prefix=self.prefixes[0]),
|
||||||
|
IPAddress(address=IPNetwork('192.0.2.1/24'), prefix=self.prefixes[1]),
|
||||||
|
IPAddress(address=IPNetwork('192.0.2.1/24'), vrf=self.vrf, prefix=self.prefixes[2]),
|
||||||
|
IPAddress(address=IPNetwork('2001:db8::/64'), prefix=self.prefixes[4]),
|
||||||
|
IPAddress(address=IPNetwork('2001:db8:2::/64'), prefix=self.prefixes[3]),
|
||||||
|
)
|
||||||
|
|
||||||
|
for ip in ips:
|
||||||
|
ip.clean()
|
||||||
|
ip.save()
|
||||||
|
|
||||||
|
self.assertEqual(ips[0].prefix, self.prefixes[0])
|
||||||
|
self.assertEqual(ips[1].prefix, self.prefixes[1])
|
||||||
|
self.assertEqual(ips[2].prefix, self.prefixes[2])
|
||||||
|
self.assertEqual(ips[3].prefix, self.prefixes[4])
|
||||||
|
self.assertEqual(ips[4].prefix, self.prefixes[3])
|
||||||
|
|
||||||
|
def test_parent_prefix_change(self):
|
||||||
|
ip = IPAddress(address=IPNetwork('192.0.1.1/24'), prefix=self.prefixes[0])
|
||||||
|
ip.clean()
|
||||||
|
ip.save()
|
||||||
|
|
||||||
|
prefix = Prefix(prefix='192.0.1.0/17')
|
||||||
|
prefix.clean()
|
||||||
|
prefix.save()
|
||||||
|
|
||||||
|
ip.refresh_from_db()
|
||||||
|
|
||||||
|
self.assertEqual(ip.prefix, prefix)
|
||||||
|
|
||||||
|
# TODO: Prefix Altered
|
||||||
|
# TODO: Prefix Deleted
|
||||||
|
|
||||||
|
# TODO: Prefix does not contain IP Address
|
||||||
|
# TODO: Prefix VRF does not match IP Address VRF
|
||||||
|
|
||||||
#
|
#
|
||||||
# Uniqueness enforcement tests
|
# Uniqueness enforcement tests
|
||||||
#
|
#
|
||||||
|
@ -421,6 +421,7 @@ class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
|
|
||||||
tags = create_tags('Alpha', 'Bravo', 'Charlie')
|
tags = create_tags('Alpha', 'Bravo', 'Charlie')
|
||||||
|
|
||||||
|
# TODO: Alter for prefix
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'prefix': IPNetwork('192.0.2.0/24'),
|
'prefix': IPNetwork('192.0.2.0/24'),
|
||||||
'scope_type': ContentType.objects.get_for_model(Site).pk,
|
'scope_type': ContentType.objects.get_for_model(Site).pk,
|
||||||
@ -436,6 +437,7 @@ class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
site = sites[0].pk
|
site = sites[0].pk
|
||||||
|
# TODO: Alter for prefix
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
"vrf,prefix,status,scope_type,scope_id",
|
"vrf,prefix,status,scope_type,scope_id",
|
||||||
f"VRF 1,10.4.0.0/16,active,dcim.site,{site}",
|
f"VRF 1,10.4.0.0/16,active,dcim.site,{site}",
|
||||||
@ -443,6 +445,7 @@ class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
f"VRF 1,10.6.0.0/16,active,dcim.site,{site}",
|
f"VRF 1,10.6.0.0/16,active,dcim.site,{site}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# TODO: Alter for prefix
|
||||||
cls.csv_update_data = (
|
cls.csv_update_data = (
|
||||||
"id,description,status",
|
"id,description,status",
|
||||||
f"{prefixes[0].pk},New description 7,{PrefixStatusChoices.STATUS_RESERVED}",
|
f"{prefixes[0].pk},New description 7,{PrefixStatusChoices.STATUS_RESERVED}",
|
||||||
@ -450,6 +453,7 @@ class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
f"{prefixes[2].pk},New description 9,{PrefixStatusChoices.STATUS_RESERVED}",
|
f"{prefixes[2].pk},New description 9,{PrefixStatusChoices.STATUS_RESERVED}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# TODO: Alter for prefix
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
'vrf': vrfs[1].pk,
|
'vrf': vrfs[1].pk,
|
||||||
'tenant': None,
|
'tenant': None,
|
||||||
@ -594,6 +598,7 @@ class IPRangeTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
|
|
||||||
tags = create_tags('Alpha', 'Bravo', 'Charlie')
|
tags = create_tags('Alpha', 'Bravo', 'Charlie')
|
||||||
|
|
||||||
|
# TODO: Alter for prefix
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'start_address': IPNetwork('192.0.5.10/24'),
|
'start_address': IPNetwork('192.0.5.10/24'),
|
||||||
'end_address': IPNetwork('192.0.5.100/24'),
|
'end_address': IPNetwork('192.0.5.100/24'),
|
||||||
@ -607,6 +612,7 @@ class IPRangeTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
'tags': [t.pk for t in tags],
|
'tags': [t.pk for t in tags],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# TODO: Alter for prefix
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
"vrf,start_address,end_address,status",
|
"vrf,start_address,end_address,status",
|
||||||
"VRF 1,10.1.0.1/16,10.1.9.254/16,active",
|
"VRF 1,10.1.0.1/16,10.1.9.254/16,active",
|
||||||
@ -614,6 +620,7 @@ class IPRangeTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
"VRF 1,10.3.0.1/16,10.3.9.254/16,active",
|
"VRF 1,10.3.0.1/16,10.3.9.254/16,active",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# TODO: Alter for prefix
|
||||||
cls.csv_update_data = (
|
cls.csv_update_data = (
|
||||||
"id,description,status",
|
"id,description,status",
|
||||||
f"{ip_ranges[0].pk},New description 7,{IPRangeStatusChoices.STATUS_RESERVED}",
|
f"{ip_ranges[0].pk},New description 7,{IPRangeStatusChoices.STATUS_RESERVED}",
|
||||||
@ -621,6 +628,7 @@ class IPRangeTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
f"{ip_ranges[2].pk},New description 9,{IPRangeStatusChoices.STATUS_RESERVED}",
|
f"{ip_ranges[2].pk},New description 9,{IPRangeStatusChoices.STATUS_RESERVED}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# TODO: Alter for prefix
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
'vrf': vrfs[1].pk,
|
'vrf': vrfs[1].pk,
|
||||||
'tenant': None,
|
'tenant': None,
|
||||||
@ -687,6 +695,7 @@ class IPAddressTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
FHRPGroup.objects.bulk_create(fhrp_groups)
|
FHRPGroup.objects.bulk_create(fhrp_groups)
|
||||||
|
# TODO: Alter for prefix
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'vrf': vrfs[1].pk,
|
'vrf': vrfs[1].pk,
|
||||||
'address': IPNetwork('192.0.2.99/24'),
|
'address': IPNetwork('192.0.2.99/24'),
|
||||||
@ -699,6 +708,7 @@ class IPAddressTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
'tags': [t.pk for t in tags],
|
'tags': [t.pk for t in tags],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# TODO: Alter for prefix
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
"vrf,address,status,fhrp_group",
|
"vrf,address,status,fhrp_group",
|
||||||
"VRF 1,192.0.2.4/24,active,FHRP Group 1",
|
"VRF 1,192.0.2.4/24,active,FHRP Group 1",
|
||||||
@ -706,6 +716,7 @@ class IPAddressTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
"VRF 1,192.0.2.6/24,active,FHRP Group 3",
|
"VRF 1,192.0.2.6/24,active,FHRP Group 3",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# TODO: Alter for prefix
|
||||||
cls.csv_update_data = (
|
cls.csv_update_data = (
|
||||||
"id,description,status",
|
"id,description,status",
|
||||||
f"{ipaddresses[0].pk},New description 7,{IPAddressStatusChoices.STATUS_RESERVED}",
|
f"{ipaddresses[0].pk},New description 7,{IPAddressStatusChoices.STATUS_RESERVED}",
|
||||||
@ -713,6 +724,7 @@ class IPAddressTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
f"{ipaddresses[2].pk},New description 9,{IPAddressStatusChoices.STATUS_RESERVED}",
|
f"{ipaddresses[2].pk},New description 9,{IPAddressStatusChoices.STATUS_RESERVED}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# TODO: Alter for prefix
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
'vrf': vrfs[1].pk,
|
'vrf': vrfs[1].pk,
|
||||||
'tenant': None,
|
'tenant': None,
|
||||||
|
Loading…
Reference in New Issue
Block a user