From bdde4b7e948d3fec1315a8fe1a89893465086a6b Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Tue, 25 Nov 2025 00:01:23 -0600 Subject: [PATCH] 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 --- ...83_ipaddress_iprange_prefix_parent_data.py | 1 - ...prefix_delete_prefix_ipam_prefix_insert.py | 4 +- .../migrations/0085_alter_prefix_parent.py | 25 +++ netbox/ipam/migrations/0086_update_trigger.py | 65 ++++++++ netbox/ipam/models/ip.py | 17 +- netbox/ipam/signals.py | 5 +- netbox/ipam/tests/test_models.py | 152 +++++++++--------- netbox/ipam/triggers.py | 97 +++++------ 8 files changed, 218 insertions(+), 148 deletions(-) create mode 100644 netbox/ipam/migrations/0085_alter_prefix_parent.py create mode 100644 netbox/ipam/migrations/0086_update_trigger.py diff --git a/netbox/ipam/migrations/0083_ipaddress_iprange_prefix_parent_data.py b/netbox/ipam/migrations/0083_ipaddress_iprange_prefix_parent_data.py index 8e0435803..e3d75bd5b 100644 --- a/netbox/ipam/migrations/0083_ipaddress_iprange_prefix_parent_data.py +++ b/netbox/ipam/migrations/0083_ipaddress_iprange_prefix_parent_data.py @@ -6,7 +6,6 @@ import time from django.db import migrations, models from ipam.choices import PrefixStatusChoices -from ipam.models import Prefix def draw_progress(count, total, length=20): diff --git a/netbox/ipam/migrations/0084_prefix_ipam_prefix_delete_prefix_ipam_prefix_insert.py b/netbox/ipam/migrations/0084_prefix_ipam_prefix_delete_prefix_ipam_prefix_insert.py index 05ba17747..f001715cf 100644 --- a/netbox/ipam/migrations/0084_prefix_ipam_prefix_delete_prefix_ipam_prefix_insert.py +++ b/netbox/ipam/migrations/0084_prefix_ipam_prefix_delete_prefix_ipam_prefix_insert.py @@ -20,7 +20,7 @@ class Migration(migrations.Migration): trigger=pgtrigger.compiler.Trigger( name='ipam_prefix_delete', 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', operation='DELETE', pgid='pgtrigger_ipam_prefix_delete_e7810', @@ -34,7 +34,7 @@ class Migration(migrations.Migration): trigger=pgtrigger.compiler.Trigger( name='ipam_prefix_insert', 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', operation='INSERT', pgid='pgtrigger_ipam_prefix_insert_46c72', diff --git a/netbox/ipam/migrations/0085_alter_prefix_parent.py b/netbox/ipam/migrations/0085_alter_prefix_parent.py new file mode 100644 index 000000000..b2deef156 --- /dev/null +++ b/netbox/ipam/migrations/0085_alter_prefix_parent.py @@ -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', + ), + ), + ] diff --git a/netbox/ipam/migrations/0086_update_trigger.py b/netbox/ipam/migrations/0086_update_trigger.py new file mode 100644 index 000000000..9aaa041cd --- /dev/null +++ b/netbox/ipam/migrations/0086_update_trigger.py @@ -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', + ), + ), + ), + ] diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py index 5f0c0caaa..529611815 100644 --- a/netbox/ipam/models/ip.py +++ b/netbox/ipam/models/ip.py @@ -18,7 +18,8 @@ from ipam.fields import IPNetworkField, IPAddressField from ipam.lookups import Host from ipam.managers import IPAddressManager 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 netbox.config import get_config from netbox.models import OrganizationalModel, PrimaryModel @@ -196,7 +197,7 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary """ aggregate = models.ForeignKey( to='ipam.Aggregate', - on_delete=models.SET_NULL, + on_delete=models.SET_NULL, # This is handled by triggers related_name='prefixes', blank=True, null=True, @@ -204,7 +205,7 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary ) parent = models.ForeignKey( to='ipam.Prefix', - on_delete=models.SET_NULL, + on_delete=models.DO_NOTHING, related_name='children', blank=True, null=True, @@ -302,6 +303,12 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary when=pgtrigger.After, 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): @@ -533,11 +540,11 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary prefixes = Prefix.objects.filter( models.Q( vrf=network.vrf, - prefix__net_contains=str(network) + prefix__net_contains=str(network.prefix) ) | models.Q( vrf=None, status=PrefixStatusChoices.STATUS_CONTAINER, - prefix__net_contains=str(network), + prefix__net_contains=str(network.prefix), ) ) return prefixes.last() diff --git a/netbox/ipam/signals.py b/netbox/ipam/signals.py index dc64ff5b8..3b36b561f 100644 --- a/netbox/ipam/signals.py +++ b/netbox/ipam/signals.py @@ -1,12 +1,9 @@ -from django.db.models import Q from django.db.models.signals import post_delete, post_save, pre_delete from django.dispatch import receiver -from netaddr.ip import IPNetwork from dcim.models import Device from virtualization.models import VirtualMachine -from .choices import PrefixStatusChoices -from .models import IPAddress, Prefix, IPRange +from .models import IPAddress, Prefix def update_parents_children(prefix): diff --git a/netbox/ipam/tests/test_models.py b/netbox/ipam/tests/test_models.py index dc1163033..4fd81d94f 100644 --- a/netbox/ipam/tests/test_models.py +++ b/netbox/ipam/tests/test_models.py @@ -1,5 +1,3 @@ -from time import sleep - from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.test import TestCase, override_settings @@ -664,109 +662,105 @@ class TestTriggers(TestCase): @classmethod 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 = ( VRF(name='VRF A'), VRF(name='VRF B'), ) - for prefix in prefixes: - prefix.clean() - prefix.save() - for vrf in vrfs: vrf.clean() 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): 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.assertIsNone(Prefix.objects.get(prefix='2001:db8::/32').parent) - - self.assertIsNotNone(Prefix.objects.get(prefix='10.0.0.0/16').parent) - self.assertIsNotNone(Prefix.objects.get(prefix='10.0.0.0/24').parent) - - self.assertIsNotNone(Prefix.objects.get(prefix='2001:db8::/40').parent) - self.assertIsNotNone(Prefix.objects.get(prefix='2001:db8::/48').parent) + self.assertEqual(Prefix.objects.get(prefix='10.0.0.0/16').parent, Prefix.objects.get(prefix='10.0.0.0/8')) + 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.assertEqual(Prefix.objects.get(prefix='10.0.2.0/23').parent, Prefix.objects.get(prefix='10.0.0.0/22')) + 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.assertEqual(Prefix.objects.get(prefix='10.0.2.0/24').parent, Prefix.objects.get(prefix='10.0.2.0/23')) + 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): - pfx = Prefix.objects.create(prefix='2001:db8::/44') - self.assertIsNotNone(Prefix.objects.get(prefix='2001:db8::/48').parent) - self.assertEqual(Prefix.objects.get(prefix='2001:db8::/48').parent, pfx) + pfx = Prefix.objects.create(prefix='10.0.0.0/21') + self.assertIsNotNone(Prefix.objects.get(prefix='10.0.0.0/22').parent) + self.assertEqual(Prefix.objects.get(prefix='10.0.0.0/22').parent, pfx) def test_vrf_insert(self): vrf = VRF.objects.get(name='VRF A') - pfx = Prefix.objects.create(prefix='2001:db8::/44', vrf=vrf) - parent = Prefix.objects.get(prefix='2001:db8::/40') - 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) + pfx = Prefix.objects.create(prefix='10.1.0.0/21', vrf=vrf) + parent = Prefix.objects.get(prefix='10.1.0.0/16') - prefixes = ( - Prefix(prefix='10.2.0.0/16', vrf=vrf), - Prefix(prefix='10.2.0.0/24', vrf=vrf), - ) - - 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) + self.assertIsNotNone(Prefix.objects.get(prefix='10.1.0.0/21').parent) + self.assertEqual(Prefix.objects.get(prefix='10.1.0.0/21').parent, parent) + 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) def test_basic_delete(self): - prefixes = ( - Prefix(prefix='10.2.0.0/16'), - Prefix(prefix='10.2.0.0/23'), - Prefix(prefix='10.2.0.0/24'), - ) - for prefix in prefixes: - prefix.clean() - prefix.save() + Prefix.objects.get(prefix='10.0.0.0/23').delete() + parent = Prefix.objects.get(prefix='10.0.0.0/22') + self.assertEqual(Prefix.objects.get(prefix='10.0.0.0/24').parent, parent) + 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')) 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 = ( - Prefix(prefix='10.2.0.0/16', vrf=vrf), - Prefix(prefix='10.2.0.0/23', vrf=vrf), - Prefix(prefix='10.2.0.0/24', vrf=vrf), - ) + def test_basic_update(self): + pfx = Prefix.objects.get(prefix='10.0.0.0/23') + parent = Prefix.objects.get(prefix='10.0.0.0/22') + 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: - prefix.clean() - prefix.save() + self.assertEqual(Prefix.objects.get(prefix='10.0.0.0/24').parent, parent) + 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')) - self.assertIsNone(prefixes[0].parent) - self.assertEqual(prefixes[1].parent, prefixes[0]) - self.assertEqual(prefixes[2].parent, prefixes[1]) + def test_vrf_update(self): + pfx = Prefix.objects.get(prefix='10.1.0.0/23') + 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() - prefixes[2].refresh_from_db() + 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')) - self.assertIsNone(prefixes[0].parent) - self.assertEqual(prefixes[2].parent, prefixes[0]) + # TODO: Test VRF Changes class TestIPAddress(TestCase): diff --git a/netbox/ipam/triggers.py b/netbox/ipam/triggers.py index a866bb098..dd2983ac2 100644 --- a/netbox/ipam/triggers.py +++ b/netbox/ipam/triggers.py @@ -1,28 +1,16 @@ 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; 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 = """ +-- Update the prefix with the new parent if the parent is the most appropriate prefix UPDATE ipam_prefix -SET parent_id=NEW.id -WHERE +SET parent_id=NEW.id +WHERE prefix << NEW.prefix 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 -SET prefix_id=NEW.id +SET parent_id = OLD.parent_id 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 ( - (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 - (NEW.vrf_id IS NULL AND NEW.status = 'container') - ) - AND ( - ipaddress.prefix_id IS NULL - OR - EXISTS ( - SELECT 1 from prefix p WHERE - 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' - ) + ( + NEW.vrf_id IS NULL + AND + NEW.status = 'container' + AND + NOT EXISTS( + SELECT 1 FROM ipam_prefix p WHERE p.prefix >> ipam_prefix.prefix AND p.vrf_id = ipam_prefix.vrf_id ) ) - AND - NOT EXISTS ( - SELECT 1 FROM prefix p + ) + AND id != NEW.id + AND NOT EXISTS ( + SELECT 1 FROM ipam_prefix p WHERE - p.prefix >> ipaddress.address - AND p.id != NEW.id - AND p.prefix << NEW.prefix - AND ( - (p.vrf_id = ipaddress.vrf_id OR (p.vrf_id IS NULL AND ipaddress.vrf_id IS NULL)) - OR - (p.vrf_id IS NULL AND NEW.status = 'container') - ) - ); + p.prefix >> ipam_prefix.prefix + AND p.prefix << NEW.prefix + AND ( + (p.vrf_id = ipam_prefix.vrf_id OR (p.vrf_id IS NULL AND ipam_prefix.vrf_id IS NULL)) + OR + (p.vrf_id IS NULL AND p.status = 'container') + ) + AND p.id != NEW.id + ) +; RETURN NEW; """