diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f2fda94f..88270a0df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ v2.6.6 (FUTURE) ## Bug Fixes +* [#3458](https://github.com/netbox-community/netbox/issues/3458) - Prevent primary IP address for a device/VM from being reassigned * [#3463](https://github.com/netbox-community/netbox/issues/3463) - Correct CSV headers for exported power feeds * [#3474](https://github.com/netbox-community/netbox/issues/3474) - Fix device status page loading when NAPALM call fails * [#3571](https://github.com/netbox-community/netbox/issues/3571) - Prevent erroneous redirects when editing tags diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index 3304c27dd..8f9b64b59 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -9,10 +9,11 @@ from django.db.models.expressions import RawSQL from django.urls import reverse from taggit.managers import TaggableManager -from dcim.models import Interface +from dcim.models import Device, Interface from extras.models import CustomFieldModel, ObjectChange, TaggedItem from utilities.models import ChangeLoggedModel from utilities.utils import serialize_object +from virtualization.models import VirtualMachine from .constants import * from .fields import IPNetworkField, IPAddressField from .querysets import PrefixQuerySet @@ -636,6 +637,34 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel): ) }) + if self.pk: + + # Check for primary IP assignment that doesn't match the assigned device/VM + device = Device.objects.filter(Q(primary_ip4=self) | Q(primary_ip6=self)).first() + if device: + if self.interface is None: + raise ValidationError({ + 'interface': "IP address is primary for device {} but not assigned".format(device) + }) + elif (device.primary_ip4 == self or device.primary_ip6 == self) and self.interface.device != device: + raise ValidationError({ + 'interface': "IP address is primary for device {} but assigned to {} ({})".format( + device, self.interface.device, self.interface + ) + }) + vm = VirtualMachine.objects.filter(Q(primary_ip4=self) | Q(primary_ip6=self)).first() + if vm: + if self.interface is None: + raise ValidationError({ + 'interface': "IP address is primary for virtual machine {} but not assigned".format(vm) + }) + elif (vm.primary_ip4 == self or vm.primary_ip6 == self) and self.interface.virtual_machine != vm: + raise ValidationError({ + 'interface': "IP address is primary for virtual machine {} but assigned to {} ({})".format( + vm, self.interface.virtual_machine, self.interface + ) + }) + def save(self, *args, **kwargs): # Record address family