Closes #1218: Added IEEE 802.11 wireless interface types

This commit is contained in:
Jeremy Stretch 2017-06-16 17:52:09 -04:00
parent 789ac5dfd4
commit 68ebe85a98
7 changed files with 83 additions and 22 deletions

View File

@ -3,7 +3,7 @@ from __future__ import unicode_literals
from django import forms from django import forms
from django.db.models import Count from django.db.models import Count
from dcim.models import Site, Device, Interface, Rack, VIRTUAL_IFACE_TYPES from dcim.models import Site, Device, Interface, Rack
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
from tenancy.forms import TenancyForm from tenancy.forms import TenancyForm
from tenancy.models import Tenant from tenancy.models import Tenant
@ -210,7 +210,7 @@ class CircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm
) )
) )
interface = ChainedModelChoiceField( interface = ChainedModelChoiceField(
queryset=Interface.objects.exclude(form_factor__in=VIRTUAL_IFACE_TYPES).select_related( queryset=Interface.objects.connectable().select_related(
'circuit_termination', 'connected_as_a', 'connected_as_b' 'circuit_termination', 'connected_as_a', 'connected_as_b'
), ),
chains=( chains=(

View File

@ -66,6 +66,12 @@ IFACE_FF_25GE_SFP28 = 1350
IFACE_FF_40GE_QSFP_PLUS = 1400 IFACE_FF_40GE_QSFP_PLUS = 1400
IFACE_FF_100GE_CFP = 1500 IFACE_FF_100GE_CFP = 1500
IFACE_FF_100GE_QSFP28 = 1600 IFACE_FF_100GE_QSFP28 = 1600
# Wireless
IFACE_FF_80211A = 2600
IFACE_FF_80211G = 2610
IFACE_FF_80211N = 2620
IFACE_FF_80211AC = 2630
IFACE_FF_80211AD = 2640
# Fibrechannel # Fibrechannel
IFACE_FF_1GFC_SFP = 3010 IFACE_FF_1GFC_SFP = 3010
IFACE_FF_2GFC_SFP = 3020 IFACE_FF_2GFC_SFP = 3020
@ -117,6 +123,16 @@ IFACE_FF_CHOICES = [
[IFACE_FF_100GE_QSFP28, 'QSFP28 (100GE)'], [IFACE_FF_100GE_QSFP28, 'QSFP28 (100GE)'],
] ]
], ],
[
'Wireless',
[
[IFACE_FF_80211A, 'IEEE 802.11a'],
[IFACE_FF_80211G, 'IEEE 802.11b/g'],
[IFACE_FF_80211N, 'IEEE 802.11n'],
[IFACE_FF_80211AC, 'IEEE 802.11ac'],
[IFACE_FF_80211AD, 'IEEE 802.11ad'],
]
],
[ [
'FibreChannel', 'FibreChannel',
[ [
@ -134,7 +150,6 @@ IFACE_FF_CHOICES = [
[IFACE_FF_E1, 'E1 (2.048 Mbps)'], [IFACE_FF_E1, 'E1 (2.048 Mbps)'],
[IFACE_FF_T3, 'T3 (45 Mbps)'], [IFACE_FF_T3, 'T3 (45 Mbps)'],
[IFACE_FF_E3, 'E3 (34 Mbps)'], [IFACE_FF_E3, 'E3 (34 Mbps)'],
[IFACE_FF_E3, 'E3 (34 Mbps)'],
] ]
], ],
[ [
@ -160,6 +175,16 @@ VIRTUAL_IFACE_TYPES = [
IFACE_FF_LAG, IFACE_FF_LAG,
] ]
WIRELESS_IFACE_TYPES = [
IFACE_FF_80211A,
IFACE_FF_80211G,
IFACE_FF_80211N,
IFACE_FF_80211AC,
IFACE_FF_80211AD,
]
NONCONNECTABLE_IFACE_TYPES = VIRTUAL_IFACE_TYPES + WIRELESS_IFACE_TYPES
# Device statuses # Device statuses
STATUS_OFFLINE = 0 STATUS_OFFLINE = 0
STATUS_ACTIVE = 1 STATUS_ACTIVE = 1

View File

@ -11,8 +11,9 @@ from utilities.filters import NullableModelMultipleChoiceFilter, NumericInFilter
from .models import ( from .models import (
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, STATUS_CHOICES, IFACE_FF_LAG, Interface, InterfaceConnection, DeviceBayTemplate, DeviceRole, DeviceType, STATUS_CHOICES, IFACE_FF_LAG, Interface, InterfaceConnection,
InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, InterfaceTemplate, Manufacturer, InventoryItem, NONCONNECTABLE_IFACE_TYPES, Platform, PowerOutlet,
PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, Region, Site, VIRTUAL_IFACE_TYPES, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, Region, Site,
VIRTUAL_IFACE_TYPES, WIRELESS_IFACE_TYPES,
) )
@ -513,13 +514,12 @@ class InterfaceFilter(django_filters.FilterSet):
def filter_type(self, queryset, name, value): def filter_type(self, queryset, name, value):
value = value.strip().lower() value = value.strip().lower()
if value == 'physical': return {
return queryset.exclude(form_factor__in=VIRTUAL_IFACE_TYPES) 'physical': queryset.exclude(form_factor__in=NONCONNECTABLE_IFACE_TYPES),
elif value == 'virtual': 'virtual': queryset.filter(form_factor__in=VIRTUAL_IFACE_TYPES),
return queryset.filter(form_factor__in=VIRTUAL_IFACE_TYPES) 'wireless': queryset.filter(form_factor__in=WIRELESS_IFACE_TYPES),
elif value == 'lag': 'lag': queryset.filter(form_factor=IFACE_FF_LAG),
return queryset.filter(form_factor=IFACE_FF_LAG) }.get(value, queryset.none())
return queryset
def _mac_address(self, queryset, name, value): def _mac_address(self, queryset, name, value):
value = value.strip() value = value.strip()

View File

@ -24,7 +24,7 @@ from .models import (
IFACE_FF_CHOICES, IFACE_FF_LAG, IFACE_ORDERING_CHOICES, InterfaceConnection, InterfaceTemplate, Manufacturer, IFACE_FF_CHOICES, IFACE_FF_LAG, IFACE_ORDERING_CHOICES, InterfaceConnection, InterfaceTemplate, Manufacturer,
InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, RACK_FACE_CHOICES, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, RACK_FACE_CHOICES,
RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, Rack, RackGroup, RackReservation, RackRole, RACK_WIDTH_19IN, RACK_WIDTH_23IN, RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, Rack, RackGroup, RackReservation, RackRole, RACK_WIDTH_19IN, RACK_WIDTH_23IN,
Region, Site, STATUS_CHOICES, SUBDEVICE_ROLE_CHILD, SUBDEVICE_ROLE_PARENT, VIRTUAL_IFACE_TYPES, Region, Site, STATUS_CHOICES, SUBDEVICE_ROLE_CHILD, SUBDEVICE_ROLE_PARENT,
) )
@ -1574,7 +1574,7 @@ class InterfaceConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelFor
) )
) )
interface_b = ChainedModelChoiceField( interface_b = ChainedModelChoiceField(
queryset=Interface.objects.exclude(form_factor__in=VIRTUAL_IFACE_TYPES).select_related( queryset=Interface.objects.connectable().select_related(
'circuit_termination', 'connected_as_a', 'connected_as_b' 'circuit_termination', 'connected_as_a', 'connected_as_b'
), ),
chains=( chains=(
@ -1596,9 +1596,7 @@ class InterfaceConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelFor
super(InterfaceConnectionForm, self).__init__(*args, **kwargs) super(InterfaceConnectionForm, self).__init__(*args, **kwargs)
# Initialize interface A choices # Initialize interface A choices
device_a_interfaces = Interface.objects.order_naturally().filter(device=device_a).exclude( device_a_interfaces = Interface.objects.connectable().order_naturally().filter(device=device_a).select_related(
form_factor__in=VIRTUAL_IFACE_TYPES
).select_related(
'circuit_termination', 'connected_as_a', 'connected_as_b' 'circuit_termination', 'connected_as_a', 'connected_as_b'
) )
self.fields['interface_a'].choices = [ self.fields['interface_a'].choices = [

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.1 on 2017-06-16 21:38
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0037_unicode_literals'),
]
operations = [
migrations.AlterField(
model_name='interface',
name='form_factor',
field=models.PositiveSmallIntegerField(choices=[['Virtual interfaces', [[0, 'Virtual'], [200, 'Link Aggregation Group (LAG)']]], ['Ethernet (fixed)', [[800, '100BASE-TX (10/100ME)'], [1000, '1000BASE-T (1GE)'], [1150, '10GBASE-T (10GE)']]], ['Ethernet (modular)', [[1050, 'GBIC (1GE)'], [1100, 'SFP (1GE)'], [1200, 'SFP+ (10GE)'], [1300, 'XFP (10GE)'], [1310, 'XENPAK (10GE)'], [1320, 'X2 (10GE)'], [1350, 'SFP28 (25GE)'], [1400, 'QSFP+ (40GE)'], [1500, 'CFP (100GE)'], [1600, 'QSFP28 (100GE)']]], ['Wireless', [[2600, 'IEEE 802.11a'], [2610, 'IEEE 802.11b/g'], [2620, 'IEEE 802.11n'], [2630, 'IEEE 802.11ac'], [2640, 'IEEE 802.11ad']]], ['FibreChannel', [[3010, 'SFP (1GFC)'], [3020, 'SFP (2GFC)'], [3040, 'SFP (4GFC)'], [3080, 'SFP+ (8GFC)'], [3160, 'SFP+ (16GFC)']]], ['Serial', [[4000, 'T1 (1.544 Mbps)'], [4010, 'E1 (2.048 Mbps)'], [4040, 'T3 (45 Mbps)'], [4050, 'E3 (34 Mbps)']]], ['Stacking', [[5000, 'Cisco StackWise'], [5050, 'Cisco StackWise Plus'], [5100, 'Cisco FlexStack'], [5150, 'Cisco FlexStack Plus'], [5200, 'Juniper VCP']]], ['Other', [[32767, 'Other']]]], default=1200),
),
migrations.AlterField(
model_name='interfacetemplate',
name='form_factor',
field=models.PositiveSmallIntegerField(choices=[['Virtual interfaces', [[0, 'Virtual'], [200, 'Link Aggregation Group (LAG)']]], ['Ethernet (fixed)', [[800, '100BASE-TX (10/100ME)'], [1000, '1000BASE-T (1GE)'], [1150, '10GBASE-T (10GE)']]], ['Ethernet (modular)', [[1050, 'GBIC (1GE)'], [1100, 'SFP (1GE)'], [1200, 'SFP+ (10GE)'], [1300, 'XFP (10GE)'], [1310, 'XENPAK (10GE)'], [1320, 'X2 (10GE)'], [1350, 'SFP28 (25GE)'], [1400, 'QSFP+ (40GE)'], [1500, 'CFP (100GE)'], [1600, 'QSFP28 (100GE)']]], ['Wireless', [[2600, 'IEEE 802.11a'], [2610, 'IEEE 802.11b/g'], [2620, 'IEEE 802.11n'], [2630, 'IEEE 802.11ac'], [2640, 'IEEE 802.11ad']]], ['FibreChannel', [[3010, 'SFP (1GFC)'], [3020, 'SFP (2GFC)'], [3040, 'SFP (4GFC)'], [3080, 'SFP+ (8GFC)'], [3160, 'SFP+ (16GFC)']]], ['Serial', [[4000, 'T1 (1.544 Mbps)'], [4010, 'E1 (2.048 Mbps)'], [4040, 'T3 (45 Mbps)'], [4050, 'E3 (34 Mbps)']]], ['Stacking', [[5000, 'Cisco StackWise'], [5050, 'Cisco StackWise Plus'], [5100, 'Cisco FlexStack'], [5150, 'Cisco FlexStack Plus'], [5200, 'Juniper VCP']]], ['Other', [[32767, 'Other']]]], default=1200),
),
]

View File

@ -661,6 +661,13 @@ class InterfaceQuerySet(models.QuerySet):
'_vc': "COALESCE(CAST(SUBSTRING({} FROM '\.([0-9]+)$') AS integer), 0)".format(sql_col), '_vc': "COALESCE(CAST(SUBSTRING({} FROM '\.([0-9]+)$') AS integer), 0)".format(sql_col),
}).order_by(*ordering) }).order_by(*ordering)
def connectable(self):
"""
Return only physical interfaces which are capable of being connected to other interfaces (i.e. not virtual or
wireless).
"""
return self.exclude(form_factor__in=NONCONNECTABLE_IFACE_TYPES)
@python_2_unicode_compatible @python_2_unicode_compatible
class InterfaceTemplate(models.Model): class InterfaceTemplate(models.Model):
@ -1134,10 +1141,10 @@ class Interface(models.Model):
def clean(self): def clean(self):
# Virtual interfaces cannot be connected # Virtual interfaces cannot be connected
if self.form_factor in VIRTUAL_IFACE_TYPES and self.is_connected: if self.form_factor in NONCONNECTABLE_IFACE_TYPES and self.is_connected:
raise ValidationError({ raise ValidationError({
'form_factor': "Virtual interfaces cannot be connected to another interface or circuit. Disconnect the " 'form_factor': "Virtual and wireless interfaces cannot be connected to another interface or circuit. "
"interface or choose a physical form factor." "Disconnect the interface or choose a suitable form factor."
}) })
# An interface's LAG must belong to the same device # An interface's LAG must belong to the same device
@ -1149,7 +1156,7 @@ class Interface(models.Model):
}) })
# A virtual interface cannot have a parent LAG # A virtual interface cannot have a parent LAG
if self.form_factor in VIRTUAL_IFACE_TYPES and self.lag is not None: if self.form_factor in NONCONNECTABLE_IFACE_TYPES and self.lag is not None:
raise ValidationError({ raise ValidationError({
'lag': "{} interfaces cannot have a parent LAG interface.".format(self.get_form_factor_display()) 'lag': "{} interfaces cannot have a parent LAG interface.".format(self.get_form_factor_display())
}) })
@ -1166,6 +1173,10 @@ class Interface(models.Model):
def is_virtual(self): def is_virtual(self):
return self.form_factor in VIRTUAL_IFACE_TYPES return self.form_factor in VIRTUAL_IFACE_TYPES
@property
def is_wireless(self):
return self.form_factor in WIRELESS_IFACE_TYPES
@property @property
def is_lag(self): def is_lag(self):
return self.form_factor == IFACE_FF_LAG return self.form_factor == IFACE_FF_LAG

View File

@ -5,7 +5,7 @@
</td> </td>
{% endif %} {% endif %}
<td> <td>
<i class="fa fa-fw fa-{% if iface.mgmt_only %}wrench{% else %}exchange{% endif %}"></i> <i class="fa fa-fw fa-{% if iface.mgmt_only %}wrench{% elif iface.is_virtual %}square{% elif iface.is_wireless %}wifi{% else %}exchange{% endif %}"></i>
<span title="{{ iface.get_form_factor_display }}">{{ iface.name }}</span> <span title="{{ iface.get_form_factor_display }}">{{ iface.name }}</span>
{% if iface.lag %} {% if iface.lag %}
<span class="label label-primary">{{ iface.lag.name }}</span> <span class="label label-primary">{{ iface.lag.name }}</span>
@ -22,6 +22,8 @@
</td> </td>
{% elif iface.is_virtual %} {% elif iface.is_virtual %}
<td colspan="2" class="text-muted">Virtual interface</td> <td colspan="2" class="text-muted">Virtual interface</td>
{% elif iface.is_wireless %}
<td colspan="2" class="text-muted">Wireless interface</td>
{% elif iface.connection %} {% elif iface.connection %}
{% with iface.connected_interface as connected_iface %} {% with iface.connected_interface as connected_iface %}
<td> <td>