Browse Source

Closes #1218: Added IEEE 802.11 wireless interface types

Jeremy Stretch 7 years ago
parent
commit
68ebe85a98

+ 2 - 2
netbox/circuits/forms.py

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

+ 26 - 1
netbox/dcim/constants.py

@@ -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
@@ -118,6 +124,16 @@ IFACE_FF_CHOICES = [
         ]
         ]
     ],
     ],
     [
     [
+        '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',
         [
         [
             [IFACE_FF_1GFC_SFP, 'SFP (1GFC)'],
             [IFACE_FF_1GFC_SFP, 'SFP (1GFC)'],
@@ -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

+ 9 - 9
netbox/dcim/filters.py

@@ -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,
-    PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, Region, Site, VIRTUAL_IFACE_TYPES,
+    InterfaceTemplate, Manufacturer, InventoryItem, NONCONNECTABLE_IFACE_TYPES, Platform, PowerOutlet,
+    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 queryset.exclude(form_factor__in=VIRTUAL_IFACE_TYPES)
-        elif value == 'virtual':
-            return queryset.filter(form_factor__in=VIRTUAL_IFACE_TYPES)
-        elif value == 'lag':
-            return queryset.filter(form_factor=IFACE_FF_LAG)
-        return queryset
+        return {
+            'physical': queryset.exclude(form_factor__in=NONCONNECTABLE_IFACE_TYPES),
+            'virtual': queryset.filter(form_factor__in=VIRTUAL_IFACE_TYPES),
+            'wireless': queryset.filter(form_factor__in=WIRELESS_IFACE_TYPES),
+            'lag': queryset.filter(form_factor=IFACE_FF_LAG),
+        }.get(value, queryset.none())
 
 
     def _mac_address(self, queryset, name, value):
     def _mac_address(self, queryset, name, value):
         value = value.strip()
         value = value.strip()

+ 3 - 5
netbox/dcim/forms.py

@@ -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(
-            form_factor__in=VIRTUAL_IFACE_TYPES
-        ).select_related(
+        device_a_interfaces = Interface.objects.connectable().order_naturally().filter(device=device_a).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 = [

File diff suppressed because it is too large
+ 25 - 0
netbox/dcim/migrations/0038_wireless_interfaces.py


+ 15 - 4
netbox/dcim/models.py

@@ -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 "
-                               "interface or choose a physical form factor."
+                'form_factor': "Virtual and wireless interfaces cannot be connected to another interface or circuit. "
+                               "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())
             })
             })
@@ -1167,6 +1174,10 @@ class Interface(models.Model):
         return self.form_factor in VIRTUAL_IFACE_TYPES
         return self.form_factor in VIRTUAL_IFACE_TYPES
 
 
     @property
     @property
+    def is_wireless(self):
+        return self.form_factor in WIRELESS_IFACE_TYPES
+
+    @property
     def is_lag(self):
     def is_lag(self):
         return self.form_factor == IFACE_FF_LAG
         return self.form_factor == IFACE_FF_LAG
 
 

+ 3 - 1
netbox/templates/dcim/inc/interface.html

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