mirror of
https://github.com/netbox-community/netbox.git
synced 2026-02-15 13:27:47 +01:00
Update Triggers and add new functions to triggers to handle certain cases
This commit is contained in:
@@ -17,7 +17,7 @@ class Migration(migrations.Migration):
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||
related_name='children',
|
||||
to='ipam.prefix',
|
||||
),
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# 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', '0089_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',
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -1,43 +0,0 @@
|
||||
# Generated by Django 5.2.5 on 2025-11-06 03:24
|
||||
|
||||
import pgtrigger.compiler
|
||||
import pgtrigger.migrations
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ipam', '0088_ipaddress_iprange_prefix_parent_data'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
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\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',
|
||||
table='ipam_prefix',
|
||||
when='BEFORE',
|
||||
),
|
||||
),
|
||||
),
|
||||
pgtrigger.migrations.AddTrigger(
|
||||
model_name='prefix',
|
||||
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", # noqa: E501
|
||||
hash='0e05bbe61861227a9eb710b6c94bae9e0cc7119e',
|
||||
operation='INSERT',
|
||||
pgid='pgtrigger_ipam_prefix_insert_46c72',
|
||||
table='ipam_prefix',
|
||||
when='AFTER',
|
||||
),
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -1,65 +0,0 @@
|
||||
# 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', '0089_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',
|
||||
),
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -262,15 +262,6 @@ class TestPrefix(TestCase):
|
||||
# Global container should return all children
|
||||
self.assertSetEqual(child_ip_pks, {ips[0].pk, ips[1].pk, ips[2].pk, ips[3].pk})
|
||||
|
||||
parent_prefix.vrf = vrfs[0]
|
||||
parent_prefix.save()
|
||||
|
||||
parent_prefix.refresh_from_db()
|
||||
child_ip_pks = {p.pk for p in parent_prefix.ip_addresses.all()}
|
||||
|
||||
# VRF container is limited to its own VRF
|
||||
self.assertSetEqual(child_ip_pks, {ips[1].pk})
|
||||
|
||||
def test_get_available_prefixes(self):
|
||||
|
||||
prefixes = Prefix.objects.bulk_create((
|
||||
@@ -417,6 +408,63 @@ class TestPrefix(TestCase):
|
||||
duplicate_prefix = Prefix(vrf=vrf, prefix=IPNetwork('192.0.2.0/24'))
|
||||
self.assertRaises(ValidationError, duplicate_prefix.clean)
|
||||
|
||||
def test_parent_container_prefix_change(self):
|
||||
vrfs = VRF.objects.bulk_create((
|
||||
VRF(name='VRF 1'),
|
||||
VRF(name='VRF 2'),
|
||||
VRF(name='VRF 3'),
|
||||
))
|
||||
parent_prefix = Prefix.objects.create(
|
||||
prefix=IPNetwork('10.0.0.0/16'), status=PrefixStatusChoices.STATUS_CONTAINER
|
||||
)
|
||||
ips = IPAddress.objects.bulk_create((
|
||||
IPAddress(prefix=parent_prefix, address=IPNetwork('10.0.0.1/24'), vrf=None),
|
||||
IPAddress(prefix=parent_prefix, address=IPNetwork('10.0.1.1/24'), vrf=vrfs[0]),
|
||||
IPAddress(prefix=parent_prefix, address=IPNetwork('10.0.2.1/24'), vrf=vrfs[1]),
|
||||
IPAddress(prefix=parent_prefix, address=IPNetwork('10.0.3.1/24'), vrf=vrfs[2]),
|
||||
))
|
||||
child_ip_pks = {p.pk for p in parent_prefix.ip_addresses.all()}
|
||||
|
||||
# Global container should return all children
|
||||
self.assertSetEqual(child_ip_pks, {ips[0].pk, ips[1].pk, ips[2].pk, ips[3].pk})
|
||||
|
||||
parent_prefix.vrf = vrfs[0]
|
||||
parent_prefix.save()
|
||||
|
||||
parent_prefix.refresh_from_db()
|
||||
child_ip_pks = {p.pk for p in parent_prefix.ip_addresses.all()}
|
||||
|
||||
# VRF container is limited to its own VRF
|
||||
self.assertSetEqual(child_ip_pks, {ips[1].pk})
|
||||
|
||||
def test_parent_container_vrf_change(self):
|
||||
vrfs = VRF.objects.bulk_create((
|
||||
VRF(name='VRF 1'),
|
||||
VRF(name='VRF 2'),
|
||||
VRF(name='VRF 3'),
|
||||
))
|
||||
parent_prefix = Prefix.objects.create(
|
||||
prefix=IPNetwork('10.0.0.0/16'), status=PrefixStatusChoices.STATUS_CONTAINER
|
||||
)
|
||||
ips = IPAddress.objects.bulk_create((
|
||||
IPAddress(prefix=parent_prefix, address=IPNetwork('10.0.0.1/24'), vrf=None),
|
||||
IPAddress(prefix=parent_prefix, address=IPNetwork('10.0.1.1/24'), vrf=vrfs[0]),
|
||||
IPAddress(prefix=parent_prefix, address=IPNetwork('10.0.2.1/24'), vrf=vrfs[1]),
|
||||
IPAddress(prefix=parent_prefix, address=IPNetwork('10.0.3.1/24'), vrf=vrfs[2]),
|
||||
))
|
||||
child_ip_pks = {p.pk for p in parent_prefix.ip_addresses.all()}
|
||||
|
||||
# Global container should return all children
|
||||
self.assertSetEqual(child_ip_pks, {ips[0].pk, ips[1].pk, ips[2].pk, ips[3].pk})
|
||||
|
||||
parent_prefix.prefix = '10.0.0.0/25'
|
||||
parent_prefix.save()
|
||||
|
||||
parent_prefix.refresh_from_db()
|
||||
child_ip_pks = {p.pk for p in parent_prefix.ip_addresses.all()}
|
||||
|
||||
self.assertSetEqual(child_ip_pks, {ips[0].pk, ips[1].pk})
|
||||
|
||||
|
||||
class TestPrefixHierarchy(TestCase):
|
||||
"""
|
||||
|
||||
@@ -45,7 +45,7 @@ RETURN NEW;
|
||||
|
||||
|
||||
ipam_prefix_update_adjust_prefix_parent = """
|
||||
-- When a prefix changes, reassign any IPAddresses that no longer
|
||||
-- When a prefix changes, reassign any child prefixes that no longer
|
||||
-- fall within the new prefix range to the parent prefix (or set null if no parent exists)
|
||||
UPDATE ipam_prefix
|
||||
SET parent_id = OLD.parent_id
|
||||
@@ -54,38 +54,167 @@ WHERE
|
||||
-- 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
|
||||
-- When a prefix changes, reassign any ip addresses that no longer
|
||||
-- fall within the new prefix range to the parent prefix (or set null if no parent exists)
|
||||
UPDATE ipam_ipaddress
|
||||
SET prefix_id = OLD.parent_id
|
||||
WHERE
|
||||
prefix << NEW.prefix
|
||||
prefix_id = NEW.id
|
||||
-- IP address no longer contained within the updated prefix
|
||||
AND
|
||||
(
|
||||
(vrf_id = NEW.vrf_id OR (vrf_id IS NULL AND NEW.vrf_id IS NULL))
|
||||
OR
|
||||
NOT (address << NEW.prefix)
|
||||
;
|
||||
|
||||
-- When a prefix changes, reassign any ip ranges that no longer
|
||||
-- fall within the new prefix range to the parent prefix (or set null if no parent exists)
|
||||
UPDATE ipam_iprange
|
||||
SET prefix_id = OLD.parent_id
|
||||
WHERE
|
||||
prefix_id = NEW.id
|
||||
-- IP address no longer contained within the updated prefix
|
||||
AND
|
||||
NOT (start_address << NEW.prefix)
|
||||
AND
|
||||
NOT (end_address << NEW.prefix)
|
||||
;
|
||||
|
||||
-- When a prefix changes, reassign any ip addresses that are in-scope but
|
||||
-- no longer within the same VRF
|
||||
UPDATE ipam_ipaddress
|
||||
SET prefix_id = OLD.parent_id
|
||||
WHERE
|
||||
prefix_id = NEW.id
|
||||
AND
|
||||
address << OLD.prefix
|
||||
AND
|
||||
(
|
||||
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
|
||||
NOT address << NEW.prefix
|
||||
OR
|
||||
(
|
||||
vrf_id is NULL
|
||||
AND
|
||||
NEW.vrf_id IS NOT NULL
|
||||
)
|
||||
OR
|
||||
(
|
||||
OLD.vrf_id IS NULL
|
||||
AND
|
||||
NEW.vrf_id IS NOT NULL
|
||||
AND
|
||||
NEW.vrf_id != vrf_id
|
||||
)
|
||||
)
|
||||
)
|
||||
AND id != NEW.id
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM ipam_prefix p
|
||||
WHERE
|
||||
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')
|
||||
;
|
||||
|
||||
-- When a prefix changes, reassign any ip ranges that are in-scope but
|
||||
-- no longer within the same VRF
|
||||
UPDATE ipam_iprange
|
||||
SET prefix_id = OLD.parent_id
|
||||
WHERE
|
||||
prefix_id = NEW.id
|
||||
AND
|
||||
start_address << OLD.prefix
|
||||
AND
|
||||
end_address << OLD.prefix
|
||||
AND
|
||||
(
|
||||
NOT start_address << NEW.prefix
|
||||
OR
|
||||
NOT end_address << NEW.prefix
|
||||
OR
|
||||
(
|
||||
vrf_id is NULL
|
||||
AND
|
||||
NEW.vrf_id IS NOT NULL
|
||||
)
|
||||
AND p.id != NEW.id
|
||||
)
|
||||
OR
|
||||
(
|
||||
OLD.vrf_id IS NULL
|
||||
AND
|
||||
NEW.vrf_id IS NOT NULL
|
||||
AND
|
||||
NEW.vrf_id != vrf_id
|
||||
)
|
||||
)
|
||||
;
|
||||
|
||||
-- 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
|
||||
(
|
||||
(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
|
||||
NOT EXISTS(
|
||||
SELECT 1 FROM ipam_prefix p WHERE p.prefix >> prefix AND p.vrf_id = vrf_id
|
||||
)
|
||||
)
|
||||
)
|
||||
AND id != NEW.id
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM ipam_prefix p
|
||||
WHERE
|
||||
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
|
||||
)
|
||||
;
|
||||
UPDATE ipam_ipaddress
|
||||
SET prefix_id = NEW.id
|
||||
WHERE
|
||||
prefix_id != NEW.id
|
||||
AND
|
||||
address << NEW.prefix
|
||||
AND (
|
||||
(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
|
||||
NOT EXISTS(
|
||||
SELECT 1 FROM ipam_prefix p WHERE p.prefix >> address AND p.vrf_id = vrf_id
|
||||
)
|
||||
)
|
||||
)
|
||||
;
|
||||
UPDATE ipam_iprange
|
||||
SET prefix_id = NEW.id
|
||||
WHERE
|
||||
prefix_id != NEW.id
|
||||
AND
|
||||
start_address << NEW.prefix
|
||||
AND
|
||||
end_address << NEW.prefix
|
||||
AND (
|
||||
(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
|
||||
NOT EXISTS(
|
||||
SELECT 1 FROM ipam_prefix p WHERE
|
||||
p.prefix >> start_address
|
||||
AND
|
||||
p.prefix >> end_address
|
||||
AND
|
||||
p.vrf_id = vrf_id
|
||||
)
|
||||
)
|
||||
)
|
||||
;
|
||||
RETURN NEW;
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user