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:
Daniel Sheppard 2025-11-25 00:01:23 -06:00
parent 905656f13e
commit bdde4b7e94
8 changed files with 218 additions and 148 deletions

View File

@ -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):

View File

@ -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',

View 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',
),
),
]

View 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',
),
),
),
]

View File

@ -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()

View File

@ -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):

View File

@ -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):

View File

@ -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;
""" """