Fixes #6289: Fix assignment of VC member interfaces to LAG interfaces

This commit is contained in:
jeremystretch 2021-04-27 09:36:48 -04:00
parent f408ad16e4
commit 9a588231c5
8 changed files with 20 additions and 12 deletions

View File

@ -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
---

View File

@ -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()

View File

@ -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(

View File

@ -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)

View File

@ -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

View File

@ -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
)

View File

@ -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(

View File

@ -68,7 +68,7 @@
<li role="presentation" {% if active_tab == 'device' %} class="active"{% endif %}>
<a href="{% url 'dcim:device' pk=object.pk %}">Device</a>
</li>
{% with interface_count=object.vc_interfaces.count %}
{% with interface_count=object.interfaces_count %}
{% if interface_count %}
<li role="presentation" {% if active_tab == 'interfaces' %} class="active"{% endif %}>
<a href="{% url 'dcim:device_interfaces' pk=object.pk %}">Interfaces {% badge interface_count %}</a>