diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e3fa157c..092fc7ab5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ v2.4.5 (FUTURE) * [#2442](https://github.com/digitalocean/netbox/issues/2442) - Nullify "next" link in API when limit=0 is passed * [#2443](https://github.com/digitalocean/netbox/issues/2443) - Enforce JSON object format when creating config contexts * [#2444](https://github.com/digitalocean/netbox/issues/2444) - Improve validation of interface MAC addresses +* [#2455](https://github.com/digitalocean/netbox/issues/2455) - Ignore unique address enforcement for IPs with a shared/virtual role --- diff --git a/netbox/ipam/constants.py b/netbox/ipam/constants.py index e2b98a1ef..a675d3ca9 100644 --- a/netbox/ipam/constants.py +++ b/netbox/ipam/constants.py @@ -51,6 +51,16 @@ IPADDRESS_ROLE_CHOICES = ( (IPADDRESS_ROLE_CARP, 'CARP'), ) +IPADDRESS_ROLES_NONUNIQUE = ( + # IPAddress roles which are exempt from unique address enforcement + IPADDRESS_ROLE_ANYCAST, + IPADDRESS_ROLE_VIP, + IPADDRESS_ROLE_VRRP, + IPADDRESS_ROLE_HSRP, + IPADDRESS_ROLE_GLBP, + IPADDRESS_ROLE_CARP, +) + # VLAN statuses VLAN_STATUS_ACTIVE = 1 VLAN_STATUS_RESERVED = 2 diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index 1b109f939..f9170cd58 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -596,7 +596,11 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel): if self.address: # Enforce unique IP space (if applicable) - if (self.vrf is None and settings.ENFORCE_GLOBAL_UNIQUE) or (self.vrf and self.vrf.enforce_unique): + if self.role not in IPADDRESS_ROLES_NONUNIQUE and ( + self.vrf is None and settings.ENFORCE_GLOBAL_UNIQUE + ) or ( + self.vrf and self.vrf.enforce_unique + ): duplicate_ips = self.get_duplicates() if duplicate_ips: raise ValidationError({ diff --git a/netbox/ipam/tests/test_models.py b/netbox/ipam/tests/test_models.py index 790b665cd..d17e8f5ef 100644 --- a/netbox/ipam/tests/test_models.py +++ b/netbox/ipam/tests/test_models.py @@ -4,6 +4,7 @@ import netaddr from django.core.exceptions import ValidationError from django.test import TestCase, override_settings +from ipam.constants import IPADDRESS_ROLE_VIP from ipam.models import IPAddress, Prefix, VRF @@ -59,3 +60,8 @@ class TestIPAddress(TestCase): IPAddress.objects.create(vrf=vrf, address=netaddr.IPNetwork('192.0.2.1/24')) duplicate_ip = IPAddress(vrf=vrf, address=netaddr.IPNetwork('192.0.2.1/24')) self.assertRaises(ValidationError, duplicate_ip.clean) + + @override_settings(ENFORCE_GLOBAL_UNIQUE=True) + def test_duplicate_nonunique_role(self): + IPAddress.objects.create(address=netaddr.IPNetwork('192.0.2.1/24'), role=IPADDRESS_ROLE_VIP) + IPAddress.objects.create(address=netaddr.IPNetwork('192.0.2.1/24'), role=IPADDRESS_ROLE_VIP)