Skip to content

Commit 0d31449

Browse files
committed
Optimize prefix assignment. Fix tests
1 parent 76e8568 commit 0d31449

File tree

3 files changed

+49
-61
lines changed

3 files changed

+49
-61
lines changed

netbox/ipam/models/ip.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,14 @@ def utilization(self):
786786

787787
return min(float(child_count) / self.size * 100, 100)
788788

789+
@classmethod
790+
def find_prefix(self, address):
791+
prefixes = Prefix.objects.filter(
792+
models.Q(prefix__net_contains=address.start_address) & Q(prefix__net_contains=address.end_address),
793+
vrf=address.vrf,
794+
)
795+
return prefixes.last()
796+
789797

790798
class IPAddress(ContactsMixin, PrimaryModel):
791799
"""
@@ -1085,3 +1093,8 @@ def get_status_color(self):
10851093

10861094
def get_role_color(self):
10871095
return IPAddressRoleChoices.colors.get(self.role)
1096+
1097+
@classmethod
1098+
def find_prefix(self, address):
1099+
prefixes = Prefix.objects.filter(prefix__net_contains=address.address, vrf=address.vrf)
1100+
return prefixes.last()

netbox/ipam/signals.py

Lines changed: 24 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,11 @@ def update_children_depth(prefix):
2929
Prefix.objects.bulk_update(children, ['_depth'], batch_size=100)
3030

3131

32-
def update_ipaddress_prefix(prefix, delete=False):
32+
def update_object_prefix(prefix, delete=False, parent_model=Prefix, child_model=IPAddress):
3333
if delete:
3434
# Get all possible addresses
35-
addresses = IPAddress.objects.filter(prefix=prefix)
36-
# Find a new containing prefix
37-
prefix = Prefix.objects.filter(
35+
addresses = child_model.objects.filter(prefix=prefix)
36+
prefix = parent_model.objects.filter(
3837
prefix__net_contains_or_equals=prefix.prefix,
3938
vrf=prefix.vrf
4039
).exclude(pk=prefix.pk).last()
@@ -43,71 +42,38 @@ def update_ipaddress_prefix(prefix, delete=False):
4342
# Set contained addresses to the containing prefix if it exists
4443
address.prefix = prefix
4544
else:
46-
# Get all possible modified addresses
47-
addresses = IPAddress.objects.filter(
48-
Q(address__net_contained_or_equal=prefix.prefix, vrf=prefix.vrf) |
49-
Q(prefix=prefix)
50-
)
45+
filter = Q(prefix=prefix)
46+
47+
if child_model == IPAddress:
48+
filter |= Q(address__net_contained_or_equal=prefix.prefix, vrf=prefix.vrf)
49+
elif child_model == IPRange:
50+
filter |= Q(
51+
start_address__net_contained_or_equal=prefix.prefix,
52+
end_address__net_contained_or_equal=prefix.prefix,
53+
vrf=prefix.vrf
54+
)
5155

56+
addresses = child_model.objects.filter(filter)
5257
for address in addresses:
53-
if not address.prefix or (prefix.prefix in address.prefix.prefix and address.address in prefix.prefix):
54-
# Set to new Prefix as the prefix is a child of the old prefix and the address is contained in the
55-
# prefix
58+
# If addresses prefix is not set then this model is the only option
59+
if not address.prefix:
5660
address.prefix = prefix
57-
elif address.prefix and address.address not in prefix.prefix:
58-
# Find a new prefix as the prefix no longer contains the address
59-
address.prefix = Prefix.objects.filter(
60-
prefix__net_contains_or_equals=address.address,
61-
vrf=prefix.vrf
62-
).last()
61+
# This address has a different VRF so the prefix cannot be the parent prefix
62+
elif address.prefix != address.find_prefix(address):
63+
address.prefix = address.find_prefix(address)
6364
else:
64-
# No-OP as the prefix does not require modification
6565
pass
6666

6767
# Update the addresses
68-
IPAddress.objects.bulk_update(addresses, ['prefix'], batch_size=100)
68+
child_model.objects.bulk_update(addresses, ['prefix'], batch_size=100)
6969

7070

71-
def update_iprange_prefix(prefix, delete=False):
72-
if delete:
73-
# Get all possible addresses
74-
addresses = IPRange.objects.filter(prefix=prefix)
75-
# Find a new containing prefix
76-
prefix = Prefix.objects.filter(
77-
prefix__net_contains_or_equals=prefix.prefix,
78-
vrf=prefix.vrf
79-
).exclude(pk=prefix.pk).last()
80-
81-
for address in addresses:
82-
# Set contained addresses to the containing prefix if it exists
83-
address.prefix = prefix
84-
else:
85-
# Get all possible modified addresses
86-
addresses = IPRange.objects.filter(
87-
Q(start_address__net_contained_or_equal=prefix.prefix, vrf=prefix.vrf) |
88-
Q(prefix=prefix)
89-
)
71+
def update_ipaddress_prefix(prefix, delete=False):
72+
update_object_prefix(prefix, delete, child_model=IPAddress)
9073

91-
for address in addresses:
92-
if not address.prefix or (
93-
prefix.prefix in address.prefix.prefix and address.start_address in prefix.prefix and
94-
address.end_address in prefix.prefix
95-
):
96-
# Set to new Prefix as the prefix is a child of the old prefix and the address is contained in the
97-
# prefix
98-
address.prefix = prefix
99-
elif address.prefix and address.address not in prefix.prefix:
100-
# Find a new prefix as the prefix no longer contains the address
101-
address.prefix = Prefix.objects.filter(Q(prefix__net_contains_or_equals=address.start_address) &
102-
Q(prefix__net_contains_or_equals=address.end_address),
103-
vrf=prefix.vrf
104-
).last()
105-
else:
106-
# No-OP as the prefix does not require modification
107-
pass
10874

109-
# Update the addresses
110-
IPAddress.objects.bulk_update(addresses, ['prefix'], batch_size=100)
75+
def update_iprange_prefix(prefix, delete=False):
76+
update_object_prefix(prefix, delete, child_model=IPRange)
11177

11278

11379
def update_prefix_parents(prefix, delete=False):

netbox/ipam/tests/test_models.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ def test_parent_prefix_change(self):
156156
range.clean()
157157
range.save()
158158

159-
prefix = Prefix(prefix='192.0.1.0/17')
159+
prefix = Prefix(prefix='192.0.0.0/17')
160160
prefix.clean()
161161
prefix.save()
162162

@@ -264,6 +264,8 @@ def test_get_child_ips(self):
264264

265265
parent_prefix.vrf = vrfs[0]
266266
parent_prefix.save()
267+
268+
parent_prefix.refresh_from_db()
267269
child_ip_pks = {p.pk for p in parent_prefix.ip_addresses.all()}
268270

269271
# VRF container is limited to its own VRF
@@ -741,13 +743,20 @@ def test_duplicate_global_unique(self):
741743
self.assertRaises(ValidationError, duplicate_ip.clean)
742744

743745
def test_duplicate_vrf(self):
744-
vrf = VRF.objects.create(name='Test', rd='1:1', enforce_unique=False)
746+
vrf = VRF.objects.get(rd='1:1')
747+
vrf.enforce_unique = False
748+
vrf.clean()
749+
vrf.save()
750+
745751
IPAddress.objects.create(vrf=vrf, address=IPNetwork('192.0.2.1/24'))
746752
duplicate_ip = IPAddress(vrf=vrf, address=IPNetwork('192.0.2.1/24'))
747753
self.assertIsNone(duplicate_ip.clean())
748754

749755
def test_duplicate_vrf_unique(self):
750-
vrf = VRF.objects.create(name='Test', rd='1:1', enforce_unique=True)
756+
vrf = VRF.objects.get(rd='1:1')
757+
vrf.enforce_unique = True
758+
vrf.clean()
759+
vrf.save()
751760
IPAddress.objects.create(vrf=vrf, address=IPNetwork('192.0.2.1/24'))
752761
duplicate_ip = IPAddress(vrf=vrf, address=IPNetwork('192.0.2.1/24'))
753762
self.assertRaises(ValidationError, duplicate_ip.clean)

0 commit comments

Comments
 (0)