mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-12 03:19:36 -06:00
Switch to using triggers
Still outstanding: * IPAddress and IPRange triggers * Triggers for VRF changes on Prefix * Triggers for changing to "container" on Prefix * Rework logic for saving on all models
This commit is contained in:
parent
905656f13e
commit
bdde4b7e94
@ -6,7 +6,6 @@ import time
|
|||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
from ipam.choices import PrefixStatusChoices
|
from ipam.choices import PrefixStatusChoices
|
||||||
from ipam.models import Prefix
|
|
||||||
|
|
||||||
|
|
||||||
def draw_progress(count, total, length=20):
|
def draw_progress(count, total, length=20):
|
||||||
|
|||||||
@ -20,7 +20,7 @@ class Migration(migrations.Migration):
|
|||||||
trigger=pgtrigger.compiler.Trigger(
|
trigger=pgtrigger.compiler.Trigger(
|
||||||
name='ipam_prefix_delete',
|
name='ipam_prefix_delete',
|
||||||
sql=pgtrigger.compiler.UpsertTriggerSql(
|
sql=pgtrigger.compiler.UpsertTriggerSql(
|
||||||
func="\n-- Update Child Prefix's with Prefix's PARENT\nUPDATE ipam_prefix SET parent_id=OLD.parent_id WHERE parent_id=OLD.id;\nRETURN OLD;\n",
|
func="\n-- Update Child Prefix's with Prefix's PARENT\nUPDATE ipam_prefix SET parent_id=OLD.parent_id WHERE parent_id=OLD.id;\nRETURN OLD;\n", # noqa: E501
|
||||||
hash='899e1943cb201118be7ef02f36f49747224774f2',
|
hash='899e1943cb201118be7ef02f36f49747224774f2',
|
||||||
operation='DELETE',
|
operation='DELETE',
|
||||||
pgid='pgtrigger_ipam_prefix_delete_e7810',
|
pgid='pgtrigger_ipam_prefix_delete_e7810',
|
||||||
@ -34,7 +34,7 @@ class Migration(migrations.Migration):
|
|||||||
trigger=pgtrigger.compiler.Trigger(
|
trigger=pgtrigger.compiler.Trigger(
|
||||||
name='ipam_prefix_insert',
|
name='ipam_prefix_insert',
|
||||||
sql=pgtrigger.compiler.UpsertTriggerSql(
|
sql=pgtrigger.compiler.UpsertTriggerSql(
|
||||||
func="\nUPDATE ipam_prefix\nSET parent_id=NEW.id \nWHERE \n prefix << NEW.prefix\n AND\n (\n (vrf_id = NEW.vrf_id OR (vrf_id IS NULL AND NEW.vrf_id IS NULL))\n OR\n (\n NEW.vrf_id IS NULL\n AND\n NEW.status = 'container'\n AND\n NOT EXISTS(\n SELECT 1 FROM ipam_prefix p WHERE p.prefix >> ipam_prefix.prefix AND p.vrf_id = ipam_prefix.vrf_id\n )\n )\n )\n AND id != NEW.id\n AND NOT EXISTS (\n SELECT 1 FROM ipam_prefix p\n WHERE\n p.prefix >> ipam_prefix.prefix\n AND p.prefix << NEW.prefix\n AND (\n (p.vrf_id = ipam_prefix.vrf_id OR (p.vrf_id IS NULL AND ipam_prefix.vrf_id IS NULL))\n OR\n (p.vrf_id IS NULL AND p.status = 'container')\n )\n AND p.id != NEW.id\n )\n;\nRETURN NEW;\n",
|
func="\nUPDATE ipam_prefix\nSET parent_id=NEW.id \nWHERE \n prefix << NEW.prefix\n AND\n (\n (vrf_id = NEW.vrf_id OR (vrf_id IS NULL AND NEW.vrf_id IS NULL))\n OR\n (\n NEW.vrf_id IS NULL\n AND\n NEW.status = 'container'\n AND\n NOT EXISTS(\n SELECT 1 FROM ipam_prefix p WHERE p.prefix >> ipam_prefix.prefix AND p.vrf_id = ipam_prefix.vrf_id\n )\n )\n )\n AND id != NEW.id\n AND NOT EXISTS (\n SELECT 1 FROM ipam_prefix p\n WHERE\n p.prefix >> ipam_prefix.prefix\n AND p.prefix << NEW.prefix\n AND (\n (p.vrf_id = ipam_prefix.vrf_id OR (p.vrf_id IS NULL AND ipam_prefix.vrf_id IS NULL))\n OR\n (p.vrf_id IS NULL AND p.status = 'container')\n )\n AND p.id != NEW.id\n )\n;\nRETURN NEW;\n", # noqa: E501
|
||||||
hash='0e05bbe61861227a9eb710b6c94bae9e0cc7119e',
|
hash='0e05bbe61861227a9eb710b6c94bae9e0cc7119e',
|
||||||
operation='INSERT',
|
operation='INSERT',
|
||||||
pgid='pgtrigger_ipam_prefix_insert_46c72',
|
pgid='pgtrigger_ipam_prefix_insert_46c72',
|
||||||
|
|||||||
25
netbox/ipam/migrations/0085_alter_prefix_parent.py
Normal file
25
netbox/ipam/migrations/0085_alter_prefix_parent.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 5.2.5 on 2025-11-25 03:53
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ipam', '0084_prefix_ipam_prefix_delete_prefix_ipam_prefix_insert'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='prefix',
|
||||||
|
name='parent',
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||||
|
related_name='children',
|
||||||
|
to='ipam.prefix',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
65
netbox/ipam/migrations/0086_update_trigger.py
Normal file
65
netbox/ipam/migrations/0086_update_trigger.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# Generated by Django 5.2.5 on 2025-11-25 06:00
|
||||||
|
|
||||||
|
import pgtrigger.compiler
|
||||||
|
import pgtrigger.migrations
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ipam', '0085_alter_prefix_parent'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
pgtrigger.migrations.RemoveTrigger(
|
||||||
|
model_name='prefix',
|
||||||
|
name='ipam_prefix_delete',
|
||||||
|
),
|
||||||
|
pgtrigger.migrations.RemoveTrigger(
|
||||||
|
model_name='prefix',
|
||||||
|
name='ipam_prefix_insert',
|
||||||
|
),
|
||||||
|
pgtrigger.migrations.AddTrigger(
|
||||||
|
model_name='prefix',
|
||||||
|
trigger=pgtrigger.compiler.Trigger(
|
||||||
|
name='ipam_prefix_delete',
|
||||||
|
sql=pgtrigger.compiler.UpsertTriggerSql(
|
||||||
|
func="\n-- Update Child Prefix's with Prefix's PARENT This is a safe assumption based on the fact that the parent would be the\n-- next direct parent for anything else that could contain this prefix\nUPDATE ipam_prefix SET parent_id=OLD.parent_id WHERE parent_id=OLD.id;\nRETURN OLD;\n", # noqa: E501
|
||||||
|
hash='ee3f890009c05a3617428158e7b6f3d77317885d',
|
||||||
|
operation='DELETE',
|
||||||
|
pgid='pgtrigger_ipam_prefix_delete_e7810',
|
||||||
|
table='ipam_prefix',
|
||||||
|
when='BEFORE',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pgtrigger.migrations.AddTrigger(
|
||||||
|
model_name='prefix',
|
||||||
|
trigger=pgtrigger.compiler.Trigger(
|
||||||
|
name='ipam_prefix_insert',
|
||||||
|
sql=pgtrigger.compiler.UpsertTriggerSql(
|
||||||
|
func="\n-- Update the prefix with the new parent if the parent is the most appropriate prefix\nUPDATE ipam_prefix\nSET parent_id=NEW.id\nWHERE\n prefix << NEW.prefix\n AND\n (\n (vrf_id = NEW.vrf_id OR (vrf_id IS NULL AND NEW.vrf_id IS NULL))\n OR\n (\n NEW.vrf_id IS NULL\n AND\n NEW.status = 'container'\n AND\n NOT EXISTS(\n SELECT 1 FROM ipam_prefix p WHERE p.prefix >> ipam_prefix.prefix AND p.vrf_id = ipam_prefix.vrf_id\n )\n )\n )\n AND id != NEW.id\n AND NOT EXISTS (\n SELECT 1 FROM ipam_prefix p\n WHERE\n p.prefix >> ipam_prefix.prefix\n AND p.prefix << NEW.prefix\n AND (\n (p.vrf_id = ipam_prefix.vrf_id OR (p.vrf_id IS NULL AND ipam_prefix.vrf_id IS NULL))\n OR\n (p.vrf_id IS NULL AND p.status = 'container')\n )\n AND p.id != NEW.id\n )\n;\nRETURN NEW;\n", # noqa: E501
|
||||||
|
hash='1d71498f09e767183d3b0d29c06c9ac9e2cc000a',
|
||||||
|
operation='INSERT',
|
||||||
|
pgid='pgtrigger_ipam_prefix_insert_46c72',
|
||||||
|
table='ipam_prefix',
|
||||||
|
when='AFTER',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pgtrigger.migrations.AddTrigger(
|
||||||
|
model_name='prefix',
|
||||||
|
trigger=pgtrigger.compiler.Trigger(
|
||||||
|
name='ipam_prefix_update',
|
||||||
|
sql=pgtrigger.compiler.UpsertTriggerSql(
|
||||||
|
func="\n-- When a prefix changes, reassign any IPAddresses that no longer\n-- fall within the new prefix range to the parent prefix (or set null if no parent exists)\nUPDATE ipam_prefix\nSET parent_id = OLD.parent_id\nWHERE\n parent_id = NEW.id\n -- IP address no longer contained within the updated prefix\n AND NOT (prefix << NEW.prefix);\n\n-- Update the prefix with the new parent if the parent is the most appropriate prefix\nUPDATE ipam_prefix\nSET parent_id=NEW.id\nWHERE\n prefix << NEW.prefix\n AND\n (\n (vrf_id = NEW.vrf_id OR (vrf_id IS NULL AND NEW.vrf_id IS NULL))\n OR\n (\n NEW.vrf_id IS NULL\n AND\n NEW.status = 'container'\n AND\n NOT EXISTS(\n SELECT 1 FROM ipam_prefix p WHERE p.prefix >> ipam_prefix.prefix AND p.vrf_id = ipam_prefix.vrf_id\n )\n )\n )\n AND id != NEW.id\n AND NOT EXISTS (\n SELECT 1 FROM ipam_prefix p\n WHERE\n p.prefix >> ipam_prefix.prefix\n AND p.prefix << NEW.prefix\n AND (\n (p.vrf_id = ipam_prefix.vrf_id OR (p.vrf_id IS NULL AND ipam_prefix.vrf_id IS NULL))\n OR\n (p.vrf_id IS NULL AND p.status = 'container')\n )\n AND p.id != NEW.id\n )\n;\nRETURN NEW;\n", # noqa: E501
|
||||||
|
hash='747230a84703df5a4aa3d32e7f45b5a32525b799',
|
||||||
|
operation='UPDATE',
|
||||||
|
pgid='pgtrigger_ipam_prefix_update_e5fca',
|
||||||
|
table='ipam_prefix',
|
||||||
|
when='AFTER',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -18,7 +18,8 @@ from ipam.fields import IPNetworkField, IPAddressField
|
|||||||
from ipam.lookups import Host
|
from ipam.lookups import Host
|
||||||
from ipam.managers import IPAddressManager
|
from ipam.managers import IPAddressManager
|
||||||
from ipam.querysets import PrefixQuerySet
|
from ipam.querysets import PrefixQuerySet
|
||||||
from ipam.triggers import ipam_prefix_delete_adjust_prefix_parent, ipam_prefix_insert_adjust_prefix_parent
|
from ipam.triggers import ipam_prefix_delete_adjust_prefix_parent, ipam_prefix_insert_adjust_prefix_parent, \
|
||||||
|
ipam_prefix_update_adjust_prefix_parent
|
||||||
from ipam.validators import DNSValidator
|
from ipam.validators import DNSValidator
|
||||||
from netbox.config import get_config
|
from netbox.config import get_config
|
||||||
from netbox.models import OrganizationalModel, PrimaryModel
|
from netbox.models import OrganizationalModel, PrimaryModel
|
||||||
@ -196,7 +197,7 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary
|
|||||||
"""
|
"""
|
||||||
aggregate = models.ForeignKey(
|
aggregate = models.ForeignKey(
|
||||||
to='ipam.Aggregate',
|
to='ipam.Aggregate',
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL, # This is handled by triggers
|
||||||
related_name='prefixes',
|
related_name='prefixes',
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
@ -204,7 +205,7 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary
|
|||||||
)
|
)
|
||||||
parent = models.ForeignKey(
|
parent = models.ForeignKey(
|
||||||
to='ipam.Prefix',
|
to='ipam.Prefix',
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.DO_NOTHING,
|
||||||
related_name='children',
|
related_name='children',
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
@ -302,6 +303,12 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary
|
|||||||
when=pgtrigger.After,
|
when=pgtrigger.After,
|
||||||
func=ipam_prefix_insert_adjust_prefix_parent,
|
func=ipam_prefix_insert_adjust_prefix_parent,
|
||||||
),
|
),
|
||||||
|
pgtrigger.Trigger(
|
||||||
|
name='ipam_prefix_update',
|
||||||
|
operation=pgtrigger.Update,
|
||||||
|
when=pgtrigger.After,
|
||||||
|
func=ipam_prefix_update_adjust_prefix_parent,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -533,11 +540,11 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary
|
|||||||
prefixes = Prefix.objects.filter(
|
prefixes = Prefix.objects.filter(
|
||||||
models.Q(
|
models.Q(
|
||||||
vrf=network.vrf,
|
vrf=network.vrf,
|
||||||
prefix__net_contains=str(network)
|
prefix__net_contains=str(network.prefix)
|
||||||
) | models.Q(
|
) | models.Q(
|
||||||
vrf=None,
|
vrf=None,
|
||||||
status=PrefixStatusChoices.STATUS_CONTAINER,
|
status=PrefixStatusChoices.STATUS_CONTAINER,
|
||||||
prefix__net_contains=str(network),
|
prefix__net_contains=str(network.prefix),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return prefixes.last()
|
return prefixes.last()
|
||||||
|
|||||||
@ -1,12 +1,9 @@
|
|||||||
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 .choices import PrefixStatusChoices
|
from .models import IPAddress, Prefix
|
||||||
from .models import IPAddress, Prefix, IPRange
|
|
||||||
|
|
||||||
|
|
||||||
def update_parents_children(prefix):
|
def update_parents_children(prefix):
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
from time import sleep
|
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
@ -664,109 +662,105 @@ class TestTriggers(TestCase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
|
||||||
prefixes = (
|
|
||||||
# IPv4
|
|
||||||
Prefix(prefix='10.0.0.0/8'),
|
|
||||||
Prefix(prefix='10.0.0.0/16'),
|
|
||||||
Prefix(prefix='10.0.0.0/24'),
|
|
||||||
Prefix(prefix='192.168.0.0/16'),
|
|
||||||
# IPv6
|
|
||||||
Prefix(prefix='2001:db8::/32'),
|
|
||||||
Prefix(prefix='2001:db8::/40'),
|
|
||||||
Prefix(prefix='2001:db8::/48'),
|
|
||||||
)
|
|
||||||
|
|
||||||
for prefix in prefixes:
|
|
||||||
prefix.clean()
|
|
||||||
prefix.save()
|
|
||||||
|
|
||||||
vrfs = (
|
vrfs = (
|
||||||
VRF(name='VRF A'),
|
VRF(name='VRF A'),
|
||||||
VRF(name='VRF B'),
|
VRF(name='VRF B'),
|
||||||
)
|
)
|
||||||
|
|
||||||
for prefix in prefixes:
|
|
||||||
prefix.clean()
|
|
||||||
prefix.save()
|
|
||||||
|
|
||||||
for vrf in vrfs:
|
for vrf in vrfs:
|
||||||
vrf.clean()
|
vrf.clean()
|
||||||
vrf.save()
|
vrf.save()
|
||||||
|
|
||||||
|
cls.prefixes = (
|
||||||
|
# IPv4
|
||||||
|
Prefix(prefix='10.0.0.0/8'),
|
||||||
|
Prefix(prefix='10.0.0.0/16'),
|
||||||
|
Prefix(prefix='10.0.0.0/22'),
|
||||||
|
Prefix(prefix='10.0.0.0/23'),
|
||||||
|
Prefix(prefix='10.0.2.0/23'),
|
||||||
|
Prefix(prefix='10.0.0.0/24'),
|
||||||
|
Prefix(prefix='10.0.1.0/24'),
|
||||||
|
Prefix(prefix='10.0.2.0/24'),
|
||||||
|
Prefix(prefix='10.0.3.0/24'),
|
||||||
|
Prefix(prefix='10.1.0.0/16', status='container'),
|
||||||
|
Prefix(prefix='10.1.0.0/22', vrf=vrfs[0]),
|
||||||
|
Prefix(prefix='10.1.0.0/23', vrf=vrfs[0]),
|
||||||
|
Prefix(prefix='10.1.2.0/23', vrf=vrfs[0]),
|
||||||
|
Prefix(prefix='10.1.0.0/24', vrf=vrfs[0]),
|
||||||
|
Prefix(prefix='10.1.1.0/24', vrf=vrfs[0]),
|
||||||
|
Prefix(prefix='10.1.2.0/24', vrf=vrfs[0]),
|
||||||
|
Prefix(prefix='10.1.3.0/24', vrf=vrfs[0]),
|
||||||
|
)
|
||||||
|
|
||||||
|
for prefix in cls.prefixes:
|
||||||
|
prefix.clean()
|
||||||
|
prefix.save()
|
||||||
|
|
||||||
def test_current_hierarchy(self):
|
def test_current_hierarchy(self):
|
||||||
self.assertIsNone(Prefix.objects.get(prefix='10.0.0.0/8').parent)
|
self.assertIsNone(Prefix.objects.get(prefix='10.0.0.0/8').parent)
|
||||||
self.assertIsNone(Prefix.objects.get(prefix='192.168.0.0/16').parent)
|
self.assertEqual(Prefix.objects.get(prefix='10.0.0.0/16').parent, Prefix.objects.get(prefix='10.0.0.0/8'))
|
||||||
self.assertIsNone(Prefix.objects.get(prefix='2001:db8::/32').parent)
|
self.assertEqual(Prefix.objects.get(prefix='10.0.0.0/22').parent, Prefix.objects.get(prefix='10.0.0.0/16'))
|
||||||
|
self.assertEqual(Prefix.objects.get(prefix='10.0.0.0/23').parent, Prefix.objects.get(prefix='10.0.0.0/22'))
|
||||||
self.assertIsNotNone(Prefix.objects.get(prefix='10.0.0.0/16').parent)
|
self.assertEqual(Prefix.objects.get(prefix='10.0.2.0/23').parent, Prefix.objects.get(prefix='10.0.0.0/22'))
|
||||||
self.assertIsNotNone(Prefix.objects.get(prefix='10.0.0.0/24').parent)
|
self.assertEqual(Prefix.objects.get(prefix='10.0.0.0/24').parent, Prefix.objects.get(prefix='10.0.0.0/23'))
|
||||||
|
self.assertEqual(Prefix.objects.get(prefix='10.0.1.0/24').parent, Prefix.objects.get(prefix='10.0.0.0/23'))
|
||||||
self.assertIsNotNone(Prefix.objects.get(prefix='2001:db8::/40').parent)
|
self.assertEqual(Prefix.objects.get(prefix='10.0.2.0/24').parent, Prefix.objects.get(prefix='10.0.2.0/23'))
|
||||||
self.assertIsNotNone(Prefix.objects.get(prefix='2001:db8::/48').parent)
|
self.assertEqual(Prefix.objects.get(prefix='10.0.3.0/24').parent, Prefix.objects.get(prefix='10.0.2.0/23'))
|
||||||
|
|
||||||
def test_basic_insert(self):
|
def test_basic_insert(self):
|
||||||
pfx = Prefix.objects.create(prefix='2001:db8::/44')
|
pfx = Prefix.objects.create(prefix='10.0.0.0/21')
|
||||||
self.assertIsNotNone(Prefix.objects.get(prefix='2001:db8::/48').parent)
|
self.assertIsNotNone(Prefix.objects.get(prefix='10.0.0.0/22').parent)
|
||||||
self.assertEqual(Prefix.objects.get(prefix='2001:db8::/48').parent, pfx)
|
self.assertEqual(Prefix.objects.get(prefix='10.0.0.0/22').parent, pfx)
|
||||||
|
|
||||||
def test_vrf_insert(self):
|
def test_vrf_insert(self):
|
||||||
vrf = VRF.objects.get(name='VRF A')
|
vrf = VRF.objects.get(name='VRF A')
|
||||||
pfx = Prefix.objects.create(prefix='2001:db8::/44', vrf=vrf)
|
pfx = Prefix.objects.create(prefix='10.1.0.0/21', vrf=vrf)
|
||||||
parent = Prefix.objects.get(prefix='2001:db8::/40')
|
parent = Prefix.objects.get(prefix='10.1.0.0/16')
|
||||||
self.assertIsNotNone(Prefix.objects.get(prefix='2001:db8::/48').parent)
|
|
||||||
self.assertNotEqual(Prefix.objects.get(prefix='2001:db8::/48').parent, pfx)
|
|
||||||
self.assertEqual(Prefix.objects.get(prefix='2001:db8::/48').parent, parent)
|
|
||||||
|
|
||||||
prefixes = (
|
self.assertIsNotNone(Prefix.objects.get(prefix='10.1.0.0/21').parent)
|
||||||
Prefix(prefix='10.2.0.0/16', vrf=vrf),
|
self.assertEqual(Prefix.objects.get(prefix='10.1.0.0/21').parent, parent)
|
||||||
Prefix(prefix='10.2.0.0/24', vrf=vrf),
|
self.assertIsNotNone(Prefix.objects.get(prefix='10.1.0.0/22').parent)
|
||||||
)
|
self.assertEqual(Prefix.objects.get(prefix='10.1.0.0/22').parent, pfx)
|
||||||
|
|
||||||
for prefix in prefixes:
|
|
||||||
prefix.clean()
|
|
||||||
prefix.save()
|
|
||||||
|
|
||||||
self.assertIsNone(Prefix.objects.get(pk=prefixes[0].pk).parent)
|
|
||||||
self.assertEqual(Prefix.objects.get(pk=prefixes[1].pk).parent, prefixes[0])
|
|
||||||
|
|
||||||
new_pfx = Prefix.objects.create(prefix='10.2.0.0/23', vrf=vrf)
|
|
||||||
|
|
||||||
self.assertIsNone(Prefix.objects.get(pk=prefixes[0].pk).parent)
|
|
||||||
self.assertEqual(new_pfx.parent, prefixes[0])
|
|
||||||
self.assertEqual(Prefix.objects.get(pk=prefixes[1].pk).parent, new_pfx)
|
|
||||||
|
|
||||||
def test_basic_delete(self):
|
def test_basic_delete(self):
|
||||||
prefixes = (
|
Prefix.objects.get(prefix='10.0.0.0/23').delete()
|
||||||
Prefix(prefix='10.2.0.0/16'),
|
parent = Prefix.objects.get(prefix='10.0.0.0/22')
|
||||||
Prefix(prefix='10.2.0.0/23'),
|
self.assertEqual(Prefix.objects.get(prefix='10.0.0.0/24').parent, parent)
|
||||||
Prefix(prefix='10.2.0.0/24'),
|
self.assertEqual(Prefix.objects.get(prefix='10.0.1.0/24').parent, parent)
|
||||||
)
|
self.assertEqual(Prefix.objects.get(prefix='10.0.2.0/24').parent, Prefix.objects.get(prefix='10.0.2.0/23'))
|
||||||
for prefix in prefixes:
|
|
||||||
prefix.clean()
|
|
||||||
prefix.save()
|
|
||||||
|
|
||||||
def test_vrf_delete(self):
|
def test_vrf_delete(self):
|
||||||
vrf = VRF.objects.get(name='VRF A')
|
Prefix.objects.get(prefix='10.1.0.0/23').delete()
|
||||||
|
parent = Prefix.objects.get(prefix='10.1.0.0/22')
|
||||||
|
self.assertEqual(Prefix.objects.get(prefix='10.1.0.0/24').parent, parent)
|
||||||
|
self.assertEqual(Prefix.objects.get(prefix='10.1.1.0/24').parent, parent)
|
||||||
|
self.assertEqual(Prefix.objects.get(prefix='10.1.2.0/24').parent, Prefix.objects.get(prefix='10.1.2.0/23'))
|
||||||
|
|
||||||
prefixes = (
|
def test_basic_update(self):
|
||||||
Prefix(prefix='10.2.0.0/16', vrf=vrf),
|
pfx = Prefix.objects.get(prefix='10.0.0.0/23')
|
||||||
Prefix(prefix='10.2.0.0/23', vrf=vrf),
|
parent = Prefix.objects.get(prefix='10.0.0.0/22')
|
||||||
Prefix(prefix='10.2.0.0/24', vrf=vrf),
|
pfx.prefix = '10.3.0.0/23'
|
||||||
)
|
pfx.parent = Prefix.objects.get(prefix='10.0.0.0/8')
|
||||||
|
pfx.clean()
|
||||||
|
pfx.save()
|
||||||
|
|
||||||
for prefix in prefixes:
|
self.assertEqual(Prefix.objects.get(prefix='10.0.0.0/24').parent, parent)
|
||||||
prefix.clean()
|
self.assertEqual(Prefix.objects.get(prefix='10.0.1.0/24').parent, parent)
|
||||||
prefix.save()
|
self.assertEqual(Prefix.objects.get(prefix='10.0.2.0/24').parent, Prefix.objects.get(prefix='10.0.2.0/23'))
|
||||||
|
|
||||||
self.assertIsNone(prefixes[0].parent)
|
def test_vrf_update(self):
|
||||||
self.assertEqual(prefixes[1].parent, prefixes[0])
|
pfx = Prefix.objects.get(prefix='10.1.0.0/23')
|
||||||
self.assertEqual(prefixes[2].parent, prefixes[1])
|
parent = Prefix.objects.get(prefix='10.1.0.0/22')
|
||||||
|
pfx.prefix = '10.3.0.0/23'
|
||||||
|
pfx.parent = None
|
||||||
|
pfx.clean()
|
||||||
|
pfx.save()
|
||||||
|
|
||||||
prefixes[1].delete()
|
self.assertEqual(Prefix.objects.get(prefix='10.1.0.0/24').parent, parent)
|
||||||
prefixes[2].refresh_from_db()
|
self.assertEqual(Prefix.objects.get(prefix='10.1.1.0/24').parent, parent)
|
||||||
|
self.assertEqual(Prefix.objects.get(prefix='10.1.2.0/24').parent, Prefix.objects.get(prefix='10.1.2.0/23'))
|
||||||
|
|
||||||
self.assertIsNone(prefixes[0].parent)
|
# TODO: Test VRF Changes
|
||||||
self.assertEqual(prefixes[2].parent, prefixes[0])
|
|
||||||
|
|
||||||
|
|
||||||
class TestIPAddress(TestCase):
|
class TestIPAddress(TestCase):
|
||||||
|
|||||||
@ -1,28 +1,16 @@
|
|||||||
ipam_prefix_delete_adjust_prefix_parent = """
|
ipam_prefix_delete_adjust_prefix_parent = """
|
||||||
-- Update Child Prefix's with Prefix's PARENT
|
-- Update Child Prefix's with Prefix's PARENT This is a safe assumption based on the fact that the parent would be the
|
||||||
|
-- next direct parent for anything else that could contain this prefix
|
||||||
UPDATE ipam_prefix SET parent_id=OLD.parent_id WHERE parent_id=OLD.id;
|
UPDATE ipam_prefix SET parent_id=OLD.parent_id WHERE parent_id=OLD.id;
|
||||||
RETURN OLD;
|
RETURN OLD;
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
ipam_prefix_delete_adjust_ipaddress_prefix = """
|
|
||||||
-- Update IP Address with prefix's PARENT
|
|
||||||
UPDATE ipam_ipaddress SET prefix_id=OLD.parent_id WHERE prefix_id=OLD.id;
|
|
||||||
RETURN OLD;
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
ipam_prefix_delete_adjust_iprange_prefix = """
|
|
||||||
-- Update IP Range with prefix's PARENT
|
|
||||||
UPDATE ipam_iprange SET prefix_id=OLD.parent_id WHERE prefix_id=OLD.id;
|
|
||||||
RETURN OLD;
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
ipam_prefix_insert_adjust_prefix_parent = """
|
ipam_prefix_insert_adjust_prefix_parent = """
|
||||||
|
-- Update the prefix with the new parent if the parent is the most appropriate prefix
|
||||||
UPDATE ipam_prefix
|
UPDATE ipam_prefix
|
||||||
SET parent_id=NEW.id
|
SET parent_id=NEW.id
|
||||||
WHERE
|
WHERE
|
||||||
prefix << NEW.prefix
|
prefix << NEW.prefix
|
||||||
AND
|
AND
|
||||||
(
|
(
|
||||||
@ -56,53 +44,48 @@ RETURN NEW;
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
ipam_prefix_insert_adjust_ipaddress_prefix = """
|
ipam_prefix_update_adjust_prefix_parent = """
|
||||||
|
-- When a prefix changes, reassign any IPAddresses that no longer
|
||||||
|
-- fall within the new prefix range to the parent prefix (or set null if no parent exists)
|
||||||
UPDATE ipam_prefix
|
UPDATE ipam_prefix
|
||||||
SET prefix_id=NEW.id
|
SET parent_id = OLD.parent_id
|
||||||
WHERE
|
WHERE
|
||||||
NEW.prefix >> ipaddress.address
|
parent_id = NEW.id
|
||||||
|
-- IP address no longer contained within the updated prefix
|
||||||
|
AND NOT (prefix << NEW.prefix);
|
||||||
|
|
||||||
|
-- Update the prefix with the new parent if the parent is the most appropriate prefix
|
||||||
|
UPDATE ipam_prefix
|
||||||
|
SET parent_id=NEW.id
|
||||||
|
WHERE
|
||||||
|
prefix << NEW.prefix
|
||||||
AND
|
AND
|
||||||
(
|
(
|
||||||
(NEW.vrf = ipaddress.vrf_id OR (NEW.vrf_id IS NULL and ipaddress.vrf_id IS NULL))
|
(vrf_id = NEW.vrf_id OR (vrf_id IS NULL AND NEW.vrf_id IS NULL))
|
||||||
OR
|
OR
|
||||||
(NEW.vrf_id IS NULL AND NEW.status = 'container')
|
(
|
||||||
)
|
NEW.vrf_id IS NULL
|
||||||
AND (
|
AND
|
||||||
ipaddress.prefix_id IS NULL
|
NEW.status = 'container'
|
||||||
OR
|
AND
|
||||||
EXISTS (
|
NOT EXISTS(
|
||||||
SELECT 1 from prefix p WHERE
|
SELECT 1 FROM ipam_prefix p WHERE p.prefix >> ipam_prefix.prefix AND p.vrf_id = ipam_prefix.vrf_id
|
||||||
p.id = ipaddress.prefix_id
|
|
||||||
AND NEW.prefix << p.prefix
|
|
||||||
)
|
|
||||||
)
|
|
||||||
AND
|
|
||||||
-- Check to ensure current parent PREFIX is not in a VRF
|
|
||||||
NOT EXISTS (
|
|
||||||
SELECT 1 from prefix p WHERE (
|
|
||||||
p.id = ipaddress.prefix_id
|
|
||||||
AND
|
|
||||||
p.vrf_id IS NOT NULL
|
|
||||||
AND
|
|
||||||
ipaddress.vrf_id IS NOT NULL
|
|
||||||
AND
|
|
||||||
(
|
|
||||||
NEW.vrf_id IS NULL AND NEW.status = 'container'
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
AND
|
)
|
||||||
NOT EXISTS (
|
AND id != NEW.id
|
||||||
SELECT 1 FROM prefix p
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM ipam_prefix p
|
||||||
WHERE
|
WHERE
|
||||||
p.prefix >> ipaddress.address
|
p.prefix >> ipam_prefix.prefix
|
||||||
AND p.id != NEW.id
|
AND p.prefix << NEW.prefix
|
||||||
AND p.prefix << NEW.prefix
|
AND (
|
||||||
AND (
|
(p.vrf_id = ipam_prefix.vrf_id OR (p.vrf_id IS NULL AND ipam_prefix.vrf_id IS NULL))
|
||||||
(p.vrf_id = ipaddress.vrf_id OR (p.vrf_id IS NULL AND ipaddress.vrf_id IS NULL))
|
OR
|
||||||
OR
|
(p.vrf_id IS NULL AND p.status = 'container')
|
||||||
(p.vrf_id IS NULL AND NEW.status = 'container')
|
)
|
||||||
)
|
AND p.id != NEW.id
|
||||||
);
|
)
|
||||||
|
;
|
||||||
RETURN NEW;
|
RETURN NEW;
|
||||||
"""
|
"""
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user