From 9a588231c559e5a534d9ce20cbeb6f7ad7f911fe Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 27 Apr 2021 09:36:48 -0400 Subject: [PATCH] Fixes #6289: Fix assignment of VC member interfaces to LAG interfaces --- docs/release-notes/version-2.11.md | 1 + netbox/dcim/filters.py | 4 ++-- netbox/dcim/forms.py | 2 +- netbox/dcim/models/devices.py | 15 +++++++++++---- netbox/dcim/views.py | 4 ++-- netbox/ipam/filters.py | 2 +- netbox/ipam/forms.py | 2 +- netbox/templates/dcim/device/base.html | 2 +- 8 files changed, 20 insertions(+), 12 deletions(-) diff --git a/docs/release-notes/version-2.11.md b/docs/release-notes/version-2.11.md index 73ca3982a..33de756e9 100644 --- a/docs/release-notes/version-2.11.md +++ b/docs/release-notes/version-2.11.md @@ -17,6 +17,7 @@ * [#6254](https://github.com/netbox-community/netbox/issues/6254) - Disable ordering of space column in racks table * [#6258](https://github.com/netbox-community/netbox/issues/6258) - Fix parent assignment for SiteGroup API serializer * [#6267](https://github.com/netbox-community/netbox/issues/6267) - Fix cable tracing API endpoint for circuit terminations +* [#6289](https://github.com/netbox-community/netbox/issues/6289) - Fix assignment of VC member interfaces to LAG interfaces --- diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index 2dc4faefb..dce0d38e2 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -984,7 +984,7 @@ class InterfaceFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminati devices = Device.objects.filter(**{'{}__in'.format(name): value}) vc_interface_ids = [] for device in devices: - vc_interface_ids.extend(device.vc_interfaces.values_list('id', flat=True)) + vc_interface_ids.extend(device.vc_interfaces().values_list('id', flat=True)) return queryset.filter(pk__in=vc_interface_ids) except Device.DoesNotExist: return queryset.none() @@ -995,7 +995,7 @@ class InterfaceFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminati try: devices = Device.objects.filter(pk__in=id_list) for device in devices: - vc_interface_ids += device.vc_interfaces.values_list('id', flat=True) + vc_interface_ids += device.vc_interfaces().values_list('id', flat=True) return queryset.filter(pk__in=vc_interface_ids) except Device.DoesNotExist: return queryset.none() diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 51be3f269..fea5142e4 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -2153,7 +2153,7 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): ip_choices = [(None, '---------')] # Gather PKs of all interfaces belonging to this Device or a peer VirtualChassis member - interface_ids = self.instance.vc_interfaces.values_list('pk', flat=True) + interface_ids = self.instance.vc_interfaces().values_list('pk', flat=True) # Collect interface IPs interface_ips = IPAddress.objects.filter( diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index 551fac2d4..2fe7c28e5 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -716,7 +716,7 @@ class Device(PrimaryModel, ConfigContextModel): pass # Validate primary IP addresses - vc_interfaces = self.vc_interfaces.all() + vc_interfaces = self.vc_interfaces() if self.primary_ip4: if self.primary_ip4.family != 4: raise ValidationError({ @@ -854,20 +854,27 @@ class Device(PrimaryModel, ConfigContextModel): else: return None + @property + def interfaces_count(self): + if self.virtual_chassis and self.virtual_chassis.master == self: + return self.vc_interfaces().count() + return self.interfaces.count() + def get_vc_master(self): """ If this Device is a VirtualChassis member, return the VC master. Otherwise, return None. """ return self.virtual_chassis.master if self.virtual_chassis else None - @property - def vc_interfaces(self): + def vc_interfaces(self, if_master=False): """ Return a QuerySet matching all Interfaces assigned to this Device or, if this Device is a VC master, to another Device belonging to the same VirtualChassis. + + :param if_master: If True, return VC member interfaces only if this Device is the VC master. """ filter = Q(device=self) - if self.virtual_chassis and self.virtual_chassis.master == self: + if self.virtual_chassis and (not if_master or self.virtual_chassis.master == self): filter |= Q(device__virtual_chassis=self.virtual_chassis, mgmt_only=False) return Interface.objects.filter(filter) diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index a9aee80f1..69e043425 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1405,7 +1405,7 @@ class DeviceInterfacesView(generic.ObjectView): template_name = 'dcim/device/interfaces.html' def get_extra_context(self, request, instance): - interfaces = instance.vc_interfaces.restrict(request.user, 'view').prefetch_related( + interfaces = instance.vc_interfaces(if_master=True).restrict(request.user, 'view').prefetch_related( Prefetch('ip_addresses', queryset=IPAddress.objects.restrict(request.user)), Prefetch('member_interfaces', queryset=Interface.objects.restrict(request.user)), 'lag', 'cable', '_path__destination', 'tags', @@ -1527,7 +1527,7 @@ class DeviceLLDPNeighborsView(generic.ObjectView): template_name = 'dcim/device/lldp_neighbors.html' def get_extra_context(self, request, instance): - interfaces = instance.vc_interfaces.restrict(request.user, 'view').prefetch_related( + interfaces = instance.vc_interfaces(if_master=True).restrict(request.user, 'view').prefetch_related( '_path__destination' ).exclude( type__in=NONCONNECTABLE_IFACE_TYPES diff --git a/netbox/ipam/filters.py b/netbox/ipam/filters.py index 141d50139..cac3d0eea 100644 --- a/netbox/ipam/filters.py +++ b/netbox/ipam/filters.py @@ -515,7 +515,7 @@ class IPAddressFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilter return queryset.none() interface_ids = [] for device in devices: - interface_ids.extend(device.vc_interfaces.values_list('id', flat=True)) + interface_ids.extend(device.vc_interfaces().values_list('id', flat=True)) return queryset.filter( interface__in=interface_ids ) diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 63e093422..6a3753859 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -1561,7 +1561,7 @@ class ServiceForm(BootstrapMixin, CustomFieldModelForm): # Limit IP address choices to those assigned to interfaces of the parent device/VM if self.instance.device: self.fields['ipaddresses'].queryset = IPAddress.objects.filter( - interface__in=self.instance.device.vc_interfaces.values_list('id', flat=True) + interface__in=self.instance.device.vc_interfaces().values_list('id', flat=True) ) elif self.instance.virtual_machine: self.fields['ipaddresses'].queryset = IPAddress.objects.filter( diff --git a/netbox/templates/dcim/device/base.html b/netbox/templates/dcim/device/base.html index 3ffef01ac..84a4c1d3e 100644 --- a/netbox/templates/dcim/device/base.html +++ b/netbox/templates/dcim/device/base.html @@ -68,7 +68,7 @@ - {% with interface_count=object.vc_interfaces.count %} + {% with interface_count=object.interfaces_count %} {% if interface_count %}