Closes #7852: Enable assigning interfaces to VRFs

This commit is contained in:
jeremystretch 2022-01-07 14:57:43 -05:00
parent 453f2ab02d
commit 3e277de82d
17 changed files with 141 additions and 26 deletions

View File

@ -1,6 +1,6 @@
## Interfaces ## Interfaces
Interfaces in NetBox represent network interfaces used to exchange data with connected devices. On modern networks, these are most commonly Ethernet, but other types are supported as well. Each interface must be assigned a type, and may optionally be assigned a MAC address, MTU, and IEEE 802.1Q mode (tagged or access). Each interface can also be enabled or disabled, and optionally designated as management-only (for out-of-band management). Interfaces in NetBox represent network interfaces used to exchange data with connected devices. On modern networks, these are most commonly Ethernet, but other types are supported as well. Each interface must be assigned a type, and may optionally be assigned a MAC address, MTU, and IEEE 802.1Q mode (tagged or access). Each interface can also be enabled or disabled, and optionally designated as management-only (for out-of-band management). Additionally, each interface may optionally be assigned to a VRF.
!!! note !!! note
Although devices and virtual machines both can have interfaces, a separate model is used for each. Thus, device interfaces have some properties that are not present on virtual machine interfaces and vice versa. Although devices and virtual machines both can have interfaces, a separate model is used for each. Thus, device interfaces have some properties that are not present on virtual machine interfaces and vice versa.

View File

@ -61,6 +61,7 @@ Inventory item templates can be arranged hierarchically within a device type, an
* [#7759](https://github.com/netbox-community/netbox/issues/7759) - Improved the user preferences form * [#7759](https://github.com/netbox-community/netbox/issues/7759) - Improved the user preferences form
* [#7784](https://github.com/netbox-community/netbox/issues/7784) - Support cluster type assignment for config contexts * [#7784](https://github.com/netbox-community/netbox/issues/7784) - Support cluster type assignment for config contexts
* [#7846](https://github.com/netbox-community/netbox/issues/7846) - Enable associating inventory items with device components * [#7846](https://github.com/netbox-community/netbox/issues/7846) - Enable associating inventory items with device components
* [#7852](https://github.com/netbox-community/netbox/issues/7852) - Enable assigning interfaces to VRFs
* [#8168](https://github.com/netbox-community/netbox/issues/8168) - Add `min_vid` and `max_vid` fields to VLAN group * [#8168](https://github.com/netbox-community/netbox/issues/8168) - Add `min_vid` and `max_vid` fields to VLAN group
### Other Changes ### Other Changes
@ -88,7 +89,7 @@ Inventory item templates can be arranged hierarchically within a device type, an
* dcim.FrontPort * dcim.FrontPort
* Added `module` field * Added `module` field
* dcim.Interface * dcim.Interface
* Added `module` field * Added `module` and `vrf` fields
* dcim.InventoryItem * dcim.InventoryItem
* Added `component_type`, `component_id`, and `role` fields * Added `component_type`, `component_id`, and `role` fields
* Added read-only `component` field * Added read-only `component` field

View File

@ -6,7 +6,9 @@ from timezone_field.rest_framework import TimeZoneSerializerField
from dcim.choices import * from dcim.choices import *
from dcim.constants import * from dcim.constants import *
from dcim.models import * from dcim.models import *
from ipam.api.nested_serializers import NestedASNSerializer, NestedIPAddressSerializer, NestedVLANSerializer from ipam.api.nested_serializers import (
NestedASNSerializer, NestedIPAddressSerializer, NestedVLANSerializer, NestedVRFSerializer,
)
from ipam.models import ASN, VLAN from ipam.models import ASN, VLAN
from netbox.api import ChoiceField, ContentTypeField, SerializedPKRelatedField from netbox.api import ChoiceField, ContentTypeField, SerializedPKRelatedField
from netbox.api.serializers import ( from netbox.api.serializers import (
@ -728,6 +730,7 @@ class InterfaceSerializer(PrimaryModelSerializer, LinkTerminationSerializer, Con
required=False, required=False,
many=True many=True
) )
vrf = NestedVRFSerializer(required=False, allow_null=True)
cable = NestedCableSerializer(read_only=True) cable = NestedCableSerializer(read_only=True)
wireless_link = NestedWirelessLinkSerializer(read_only=True) wireless_link = NestedWirelessLinkSerializer(read_only=True)
wireless_lans = SerializedPKRelatedField( wireless_lans = SerializedPKRelatedField(
@ -745,7 +748,7 @@ class InterfaceSerializer(PrimaryModelSerializer, LinkTerminationSerializer, Con
'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'enabled', 'parent', 'bridge', 'lag', 'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'enabled', 'parent', 'bridge', 'lag',
'mtu', 'mac_address', 'wwn', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel', 'mtu', 'mac_address', 'wwn', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel',
'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan', 'tagged_vlans', 'mark_connected', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan', 'tagged_vlans', 'mark_connected',
'cable', 'wireless_link', 'link_peer', 'link_peer_type', 'wireless_lans', 'connected_endpoint', 'cable', 'wireless_link', 'link_peer', 'link_peer_type', 'wireless_lans', 'vrf', 'connected_endpoint',
'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created',
'last_updated', 'count_ipaddresses', 'count_fhrp_groups', '_occupied', 'last_updated', 'count_ipaddresses', 'count_fhrp_groups', '_occupied',
] ]

View File

@ -583,7 +583,7 @@ class PowerOutletViewSet(PathEndpointMixin, ModelViewSet):
class InterfaceViewSet(PathEndpointMixin, ModelViewSet): class InterfaceViewSet(PathEndpointMixin, ModelViewSet):
queryset = Interface.objects.prefetch_related( queryset = Interface.objects.prefetch_related(
'device', 'module__module_bay', 'parent', 'bridge', 'lag', '_path__destination', 'cable', '_link_peer', 'device', 'module__module_bay', 'parent', 'bridge', 'lag', '_path__destination', 'cable', '_link_peer',
'wireless_lans', 'untagged_vlan', 'tagged_vlans', 'ip_addresses', 'fhrp_group_assignments', 'tags' 'wireless_lans', 'untagged_vlan', 'tagged_vlans', 'vrf', 'ip_addresses', 'fhrp_group_assignments', 'tags'
) )
serializer_class = serializers.InterfaceSerializer serializer_class = serializers.InterfaceSerializer
filterset_class = filtersets.InterfaceFilterSet filterset_class = filtersets.InterfaceFilterSet

View File

@ -3,7 +3,7 @@ from django.contrib.auth.models import User
from extras.filters import TagFilter from extras.filters import TagFilter
from extras.filtersets import LocalConfigContextFilterSet from extras.filtersets import LocalConfigContextFilterSet
from ipam.models import ASN from ipam.models import ASN, VRF
from netbox.filtersets import ( from netbox.filtersets import (
BaseFilterSet, ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet, BaseFilterSet, ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet,
) )
@ -1217,6 +1217,17 @@ class InterfaceFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableT
rf_channel = django_filters.MultipleChoiceFilter( rf_channel = django_filters.MultipleChoiceFilter(
choices=WirelessChannelChoices choices=WirelessChannelChoices
) )
vrf_id = django_filters.ModelMultipleChoiceFilter(
field_name='vrf',
queryset=VRF.objects.all(),
label='VRF',
)
vrf = django_filters.ModelMultipleChoiceFilter(
field_name='vrf__rd',
queryset=VRF.objects.all(),
to_field_name='rd',
label='VRF (RD)',
)
class Meta: class Meta:
model = Interface model = Interface

View File

@ -7,7 +7,7 @@ from dcim.choices import *
from dcim.constants import * from dcim.constants import *
from dcim.models import * from dcim.models import *
from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
from ipam.models import VLAN, ASN from ipam.models import ASN, VLAN, VRF
from tenancy.models import Tenant from tenancy.models import Tenant
from utilities.forms import ( from utilities.forms import (
add_blank_choice, BulkEditForm, BulkEditNullBooleanSelect, ColorField, CommentField, DynamicModelChoiceField, add_blank_choice, BulkEditForm, BulkEditNullBooleanSelect, ColorField, CommentField, DynamicModelChoiceField,
@ -1061,7 +1061,8 @@ class InterfaceBulkEditForm(
required=False, required=False,
query_params={ query_params={
'type': 'lag', 'type': 'lag',
} },
label='LAG'
) )
mgmt_only = forms.NullBooleanField( mgmt_only = forms.NullBooleanField(
required=False, required=False,
@ -1080,11 +1081,16 @@ class InterfaceBulkEditForm(
queryset=VLAN.objects.all(), queryset=VLAN.objects.all(),
required=False required=False
) )
vrf = DynamicModelChoiceField(
queryset=VRF.objects.all(),
required=False,
label='VRF'
)
class Meta: class Meta:
nullable_fields = [ nullable_fields = [
'label', 'parent', 'bridge', 'lag', 'mac_address', 'wwn', 'mtu', 'description', 'mode', 'rf_channel', 'label', 'parent', 'bridge', 'lag', 'mac_address', 'wwn', 'mtu', 'description', 'mode', 'rf_channel',
'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan', 'tagged_vlans', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan', 'tagged_vlans', 'vrf',
] ]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View File

@ -8,6 +8,7 @@ from dcim.choices import *
from dcim.constants import * from dcim.constants import *
from dcim.models import * from dcim.models import *
from extras.forms import CustomFieldModelCSVForm from extras.forms import CustomFieldModelCSVForm
from ipam.models import VRF
from tenancy.models import Tenant from tenancy.models import Tenant
from utilities.forms import CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVTypedChoiceField, SlugField from utilities.forms import CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVTypedChoiceField, SlugField
from virtualization.models import Cluster from virtualization.models import Cluster
@ -622,6 +623,12 @@ class InterfaceCSVForm(CustomFieldModelCSVForm):
required=False, required=False,
help_text='IEEE 802.1Q operational mode (for L2 interfaces)' help_text='IEEE 802.1Q operational mode (for L2 interfaces)'
) )
vrf = CSVModelChoiceField(
queryset=VRF.objects.all(),
required=False,
to_field_name='rd',
help_text='Assigned VRF'
)
rf_role = CSVChoiceField( rf_role = CSVChoiceField(
choices=WirelessRoleChoices, choices=WirelessRoleChoices,
required=False, required=False,
@ -632,7 +639,7 @@ class InterfaceCSVForm(CustomFieldModelCSVForm):
model = Interface model = Interface
fields = ( fields = (
'device', 'name', 'label', 'parent', 'bridge', 'lag', 'type', 'enabled', 'mark_connected', 'mac_address', 'device', 'name', 'label', 'parent', 'bridge', 'lag', 'type', 'enabled', 'mark_connected', 'mac_address',
'wwn', 'mtu', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'wwn', 'mtu', 'mgmt_only', 'description', 'mode', 'vrf', 'rf_role', 'rf_channel', 'rf_channel_frequency',
'rf_channel_width', 'tx_power', 'rf_channel_width', 'tx_power',
) )

View File

@ -6,7 +6,7 @@ from dcim.choices import *
from dcim.constants import * from dcim.constants import *
from dcim.models import * from dcim.models import *
from extras.forms import CustomFieldModelFilterForm, LocalConfigContextFilterForm from extras.forms import CustomFieldModelFilterForm, LocalConfigContextFilterForm
from ipam.models import ASN from ipam.models import ASN, VRF
from tenancy.forms import TenancyFilterForm from tenancy.forms import TenancyFilterForm
from utilities.forms import ( from utilities.forms import (
APISelectMultiple, add_blank_choice, ColorField, DynamicModelMultipleChoiceField, FilterForm, StaticSelect, APISelectMultiple, add_blank_choice, ColorField, DynamicModelMultipleChoiceField, FilterForm, StaticSelect,
@ -920,7 +920,8 @@ class InterfaceFilterForm(DeviceComponentFilterForm):
model = Interface model = Interface
field_groups = [ field_groups = [
['q', 'tag'], ['q', 'tag'],
['name', 'label', 'kind', 'type', 'enabled', 'mgmt_only', 'mac_address', 'wwn'], ['name', 'label', 'kind', 'type', 'enabled', 'mgmt_only'],
['vrf_id', 'mac_address', 'wwn'],
['rf_role', 'rf_channel', 'rf_channel_width', 'tx_power'], ['rf_role', 'rf_channel', 'rf_channel_width', 'tx_power'],
['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'], ['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'],
] ]
@ -980,6 +981,11 @@ class InterfaceFilterForm(DeviceComponentFilterForm):
min_value=0, min_value=0,
max_value=127 max_value=127
) )
vrf_id = DynamicModelMultipleChoiceField(
queryset=VRF.objects.all(),
required=False,
label='VRF'
)
tag = TagFilterField(model) tag = TagFilterField(model)

View File

@ -9,7 +9,7 @@ from dcim.constants import *
from dcim.models import * from dcim.models import *
from extras.forms import CustomFieldModelForm from extras.forms import CustomFieldModelForm
from extras.models import Tag from extras.models import Tag
from ipam.models import IPAddress, VLAN, VLANGroup, ASN from ipam.models import ASN, IPAddress, VLAN, VLANGroup, VRF
from tenancy.forms import TenancyForm from tenancy.forms import TenancyForm
from utilities.forms import ( from utilities.forms import (
APISelect, add_blank_choice, BootstrapMixin, ClearableFileInput, CommentField, ContentTypeChoiceField, APISelect, add_blank_choice, BootstrapMixin, ClearableFileInput, CommentField, ContentTypeChoiceField,
@ -1261,6 +1261,11 @@ class InterfaceForm(InterfaceCommonForm, CustomFieldModelForm):
'available_on_device': '$device', 'available_on_device': '$device',
} }
) )
vrf = DynamicModelChoiceField(
queryset=VRF.objects.all(),
required=False,
label='VRF'
)
tags = DynamicModelMultipleChoiceField( tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(), queryset=Tag.objects.all(),
required=False required=False
@ -1271,11 +1276,11 @@ class InterfaceForm(InterfaceCommonForm, CustomFieldModelForm):
fields = [ fields = [
'device', 'name', 'label', 'type', 'enabled', 'parent', 'bridge', 'lag', 'mac_address', 'wwn', 'mtu', 'device', 'name', 'label', 'type', 'enabled', 'parent', 'bridge', 'lag', 'mac_address', 'wwn', 'mtu',
'mgmt_only', 'mark_connected', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'mgmt_only', 'mark_connected', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency',
'rf_channel_width', 'tx_power', 'wireless_lans', 'untagged_vlan', 'tagged_vlans', 'tags', 'rf_channel_width', 'tx_power', 'wireless_lans', 'untagged_vlan', 'tagged_vlans', 'vrf', 'tags',
] ]
fieldsets = ( fieldsets = (
('Interface', ('device', 'name', 'type', 'label', 'description', 'tags')), ('Interface', ('device', 'name', 'type', 'label', 'description', 'tags')),
('Addressing', ('mac_address', 'wwn')), ('Addressing', ('vrf', 'mac_address', 'wwn')),
('Operation', ('mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')), ('Operation', ('mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
('Related Interfaces', ('parent', 'bridge', 'lag')), ('Related Interfaces', ('parent', 'bridge', 'lag')),
('802.1Q Switching', ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')), ('802.1Q Switching', ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')),

View File

@ -0,0 +1,20 @@
# Generated by Django 3.2.11 on 2022-01-07 18:34
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('ipam', '0054_vlangroup_min_max_vids'),
('dcim', '0148_inventoryitem_templates'),
]
operations = [
migrations.AddField(
model_name='interface',
name='vrf',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='interfaces', to='ipam.vrf'),
),
]

View File

@ -616,6 +616,14 @@ class Interface(ModularComponentModel, BaseInterface, LinkTermination, PathEndpo
blank=True, blank=True,
verbose_name='Tagged VLANs' verbose_name='Tagged VLANs'
) )
vrf = models.ForeignKey(
to='ipam.VRF',
on_delete=models.SET_NULL,
related_name='interfaces',
null=True,
blank=True,
verbose_name='VRF'
)
ip_addresses = GenericRelation( ip_addresses = GenericRelation(
to='ipam.IPAddress', to='ipam.IPAddress',
content_type_field='assigned_object_type', content_type_field='assigned_object_type',

View File

@ -521,6 +521,9 @@ class InterfaceTable(ModularDeviceComponentTable, BaseInterfaceTable, PathEndpoi
orderable=False, orderable=False,
verbose_name='Wireless LANs' verbose_name='Wireless LANs'
) )
vrf = tables.Column(
linkify=True
)
tags = TagColumn( tags = TagColumn(
url_name='dcim:interface_list' url_name='dcim:interface_list'
) )
@ -531,7 +534,7 @@ class InterfaceTable(ModularDeviceComponentTable, BaseInterfaceTable, PathEndpoi
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'enabled', 'type', 'mgmt_only', 'mtu', 'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'enabled', 'type', 'mgmt_only', 'mtu',
'mode', 'mac_address', 'wwn', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'mode', 'mac_address', 'wwn', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width',
'tx_power', 'description', 'mark_connected', 'cable', 'cable_color', 'wireless_link', 'wireless_lans', 'tx_power', 'description', 'mark_connected', 'cable', 'cable_color', 'wireless_link', 'wireless_lans',
'link_peer', 'connection', 'tags', 'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans', 'link_peer', 'connection', 'tags', 'vrf', 'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans',
) )
default_columns = ('pk', 'name', 'device', 'label', 'enabled', 'type', 'description') default_columns = ('pk', 'name', 'device', 'label', 'enabled', 'type', 'description')

View File

@ -6,7 +6,7 @@ from rest_framework import status
from dcim.choices import * from dcim.choices import *
from dcim.constants import * from dcim.constants import *
from dcim.models import * from dcim.models import *
from ipam.models import ASN, RIR, VLAN from ipam.models import ASN, RIR, VLAN, VRF
from utilities.testing import APITestCase, APIViewTestCases, create_test_device from utilities.testing import APITestCase, APIViewTestCases, create_test_device
from virtualization.models import Cluster, ClusterType from virtualization.models import Cluster, ClusterType
from wireless.models import WirelessLAN from wireless.models import WirelessLAN
@ -1424,6 +1424,13 @@ class InterfaceTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase
) )
WirelessLAN.objects.bulk_create(wireless_lans) WirelessLAN.objects.bulk_create(wireless_lans)
vrfs = (
VRF(name='VRF 1'),
VRF(name='VRF 2'),
VRF(name='VRF 3'),
)
VRF.objects.bulk_create(vrfs)
cls.create_data = [ cls.create_data = [
{ {
'device': device.pk, 'device': device.pk,
@ -1431,6 +1438,7 @@ class InterfaceTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase
'type': '1000base-t', 'type': '1000base-t',
'mode': InterfaceModeChoices.MODE_TAGGED, 'mode': InterfaceModeChoices.MODE_TAGGED,
'tx_power': 10, 'tx_power': 10,
'vrf': vrfs[0].pk,
'tagged_vlans': [vlans[0].pk, vlans[1].pk], 'tagged_vlans': [vlans[0].pk, vlans[1].pk],
'untagged_vlan': vlans[2].pk, 'untagged_vlan': vlans[2].pk,
'wireless_lans': [wireless_lans[0].pk, wireless_lans[1].pk], 'wireless_lans': [wireless_lans[0].pk, wireless_lans[1].pk],
@ -1442,6 +1450,7 @@ class InterfaceTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase
'mode': InterfaceModeChoices.MODE_TAGGED, 'mode': InterfaceModeChoices.MODE_TAGGED,
'bridge': interfaces[0].pk, 'bridge': interfaces[0].pk,
'tx_power': 10, 'tx_power': 10,
'vrf': vrfs[1].pk,
'tagged_vlans': [vlans[0].pk, vlans[1].pk], 'tagged_vlans': [vlans[0].pk, vlans[1].pk],
'untagged_vlan': vlans[2].pk, 'untagged_vlan': vlans[2].pk,
'wireless_lans': [wireless_lans[0].pk, wireless_lans[1].pk], 'wireless_lans': [wireless_lans[0].pk, wireless_lans[1].pk],
@ -1453,6 +1462,7 @@ class InterfaceTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase
'mode': InterfaceModeChoices.MODE_TAGGED, 'mode': InterfaceModeChoices.MODE_TAGGED,
'parent': interfaces[1].pk, 'parent': interfaces[1].pk,
'tx_power': 10, 'tx_power': 10,
'vrf': vrfs[2].pk,
'tagged_vlans': [vlans[0].pk, vlans[1].pk], 'tagged_vlans': [vlans[0].pk, vlans[1].pk],
'untagged_vlan': vlans[2].pk, 'untagged_vlan': vlans[2].pk,
'wireless_lans': [wireless_lans[0].pk, wireless_lans[1].pk], 'wireless_lans': [wireless_lans[0].pk, wireless_lans[1].pk],

View File

@ -4,7 +4,7 @@ from django.test import TestCase
from dcim.choices import * from dcim.choices import *
from dcim.filtersets import * from dcim.filtersets import *
from dcim.models import * from dcim.models import *
from ipam.models import ASN, IPAddress, RIR from ipam.models import ASN, IPAddress, RIR, VRF
from tenancy.models import Tenant, TenantGroup from tenancy.models import Tenant, TenantGroup
from utilities.choices import ColorChoices from utilities.choices import ColorChoices
from utilities.testing import ChangeLoggedFilterSetTests, create_test_device from utilities.testing import ChangeLoggedFilterSetTests, create_test_device
@ -2370,15 +2370,22 @@ class InterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
) )
Device.objects.bulk_create(devices) Device.objects.bulk_create(devices)
vrfs = (
VRF(name='VRF 1', rd='65000:1'),
VRF(name='VRF 2', rd='65000:2'),
VRF(name='VRF 3', rd='65000:3'),
)
VRF.objects.bulk_create(vrfs)
# VirtualChassis assignment for filtering # VirtualChassis assignment for filtering
virtual_chassis = VirtualChassis.objects.create(master=devices[0]) virtual_chassis = VirtualChassis.objects.create(master=devices[0])
Device.objects.filter(pk=devices[0].pk).update(virtual_chassis=virtual_chassis, vc_position=1, vc_priority=1) Device.objects.filter(pk=devices[0].pk).update(virtual_chassis=virtual_chassis, vc_position=1, vc_priority=1)
Device.objects.filter(pk=devices[1].pk).update(virtual_chassis=virtual_chassis, vc_position=2, vc_priority=2) Device.objects.filter(pk=devices[1].pk).update(virtual_chassis=virtual_chassis, vc_position=2, vc_priority=2)
interfaces = ( interfaces = (
Interface(device=devices[0], name='Interface 1', label='A', type=InterfaceTypeChoices.TYPE_1GE_SFP, enabled=True, mgmt_only=True, mtu=100, mode=InterfaceModeChoices.MODE_ACCESS, mac_address='00-00-00-00-00-01', description='First'), Interface(device=devices[0], name='Interface 1', label='A', type=InterfaceTypeChoices.TYPE_1GE_SFP, enabled=True, mgmt_only=True, mtu=100, mode=InterfaceModeChoices.MODE_ACCESS, mac_address='00-00-00-00-00-01', description='First', vrf=vrfs[0]),
Interface(device=devices[1], name='Interface 2', label='B', type=InterfaceTypeChoices.TYPE_1GE_GBIC, enabled=True, mgmt_only=True, mtu=200, mode=InterfaceModeChoices.MODE_TAGGED, mac_address='00-00-00-00-00-02', description='Second'), Interface(device=devices[1], name='Interface 2', label='B', type=InterfaceTypeChoices.TYPE_1GE_GBIC, enabled=True, mgmt_only=True, mtu=200, mode=InterfaceModeChoices.MODE_TAGGED, mac_address='00-00-00-00-00-02', description='Second', vrf=vrfs[1]),
Interface(device=devices[2], name='Interface 3', label='C', type=InterfaceTypeChoices.TYPE_1GE_FIXED, enabled=False, mgmt_only=False, mtu=300, mode=InterfaceModeChoices.MODE_TAGGED_ALL, mac_address='00-00-00-00-00-03', description='Third'), Interface(device=devices[2], name='Interface 3', label='C', type=InterfaceTypeChoices.TYPE_1GE_FIXED, enabled=False, mgmt_only=False, mtu=300, mode=InterfaceModeChoices.MODE_TAGGED_ALL, mac_address='00-00-00-00-00-03', description='Third', vrf=vrfs[2]),
Interface(device=devices[3], name='Interface 4', label='D', type=InterfaceTypeChoices.TYPE_OTHER, enabled=True, mgmt_only=True, tx_power=40), Interface(device=devices[3], name='Interface 4', label='D', type=InterfaceTypeChoices.TYPE_OTHER, enabled=True, mgmt_only=True, tx_power=40),
Interface(device=devices[3], name='Interface 5', label='E', type=InterfaceTypeChoices.TYPE_OTHER, enabled=True, mgmt_only=True, tx_power=40), Interface(device=devices[3], name='Interface 5', label='E', type=InterfaceTypeChoices.TYPE_OTHER, enabled=True, mgmt_only=True, tx_power=40),
Interface(device=devices[3], name='Interface 6', label='F', type=InterfaceTypeChoices.TYPE_OTHER, enabled=False, mgmt_only=False, tx_power=40), Interface(device=devices[3], name='Interface 6', label='F', type=InterfaceTypeChoices.TYPE_OTHER, enabled=False, mgmt_only=False, tx_power=40),
@ -2550,6 +2557,13 @@ class InterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'tx_power': [40]} params = {'tx_power': [40]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
def test_vrf(self):
vrfs = VRF.objects.all()[:2]
params = {'vrf_id': [vrfs[0].pk, vrfs[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'vrf': [vrfs[0].rd, vrfs[1].rd]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class FrontPortTestCase(TestCase, ChangeLoggedFilterSetTests): class FrontPortTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = FrontPort.objects.all() queryset = FrontPort.objects.all()

View File

@ -11,7 +11,7 @@ from netaddr import EUI
from dcim.choices import * from dcim.choices import *
from dcim.constants import * from dcim.constants import *
from dcim.models import * from dcim.models import *
from ipam.models import ASN, RIR, VLAN from ipam.models import ASN, RIR, VLAN, VRF
from tenancy.models import Tenant from tenancy.models import Tenant
from utilities.testing import ViewTestCases, create_tags, create_test_device from utilities.testing import ViewTestCases, create_tags, create_test_device
from wireless.models import WirelessLAN from wireless.models import WirelessLAN
@ -2105,6 +2105,13 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
) )
WirelessLAN.objects.bulk_create(wireless_lans) WirelessLAN.objects.bulk_create(wireless_lans)
vrfs = (
VRF(name='VRF 1'),
VRF(name='VRF 2'),
VRF(name='VRF 3'),
)
VRF.objects.bulk_create(vrfs)
tags = create_tags('Alpha', 'Bravo', 'Charlie') tags = create_tags('Alpha', 'Bravo', 'Charlie')
cls.form_data = { cls.form_data = {
@ -2124,6 +2131,7 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
'untagged_vlan': vlans[0].pk, 'untagged_vlan': vlans[0].pk,
'tagged_vlans': [v.pk for v in vlans[1:4]], 'tagged_vlans': [v.pk for v in vlans[1:4]],
'wireless_lans': [wireless_lans[0].pk, wireless_lans[1].pk], 'wireless_lans': [wireless_lans[0].pk, wireless_lans[1].pk],
'vrf': vrfs[0].pk,
'tags': [t.pk for t in tags], 'tags': [t.pk for t in tags],
} }
@ -2143,6 +2151,7 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
'untagged_vlan': vlans[0].pk, 'untagged_vlan': vlans[0].pk,
'tagged_vlans': [v.pk for v in vlans[1:4]], 'tagged_vlans': [v.pk for v in vlans[1:4]],
'wireless_lans': [wireless_lans[0].pk, wireless_lans[1].pk], 'wireless_lans': [wireless_lans[0].pk, wireless_lans[1].pk],
'vrf': vrfs[0].pk,
'tags': [t.pk for t in tags], 'tags': [t.pk for t in tags],
} }
@ -2159,13 +2168,14 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
'tx_power': 10, 'tx_power': 10,
'untagged_vlan': vlans[0].pk, 'untagged_vlan': vlans[0].pk,
'tagged_vlans': [v.pk for v in vlans[1:4]], 'tagged_vlans': [v.pk for v in vlans[1:4]],
'vrf': vrfs[1].pk,
} }
cls.csv_data = ( cls.csv_data = (
"device,name,type", f"device,name,type,vrf.pk",
"Device 1,Interface 4,1000base-t", f"Device 1,Interface 4,1000base-t,{vrfs[0].pk}",
"Device 1,Interface 5,1000base-t", f"Device 1,Interface 5,1000base-t,{vrfs[0].pk}",
"Device 1,Interface 6,1000base-t", f"Device 1,Interface 6,1000base-t,{vrfs[0].pk}",
) )
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*']) @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])

View File

@ -108,6 +108,16 @@
<th scope="row">802.1Q Mode</th> <th scope="row">802.1Q Mode</th>
<td>{{ object.get_mode_display|placeholder }}</td> <td>{{ object.get_mode_display|placeholder }}</td>
</tr> </tr>
<tr>
<th scope="row">VRF</th>
<td>
{% if object.vrf %}
<a href="{{ object.vrf.get_absolute_url }}">{{ object.vrf }}</a>
{% else %}
<span class="text-muted">None</span>
{% endif %}
</td>
</tr>
</table> </table>
</div> </div>
</div> </div>

View File

@ -25,6 +25,7 @@
<div class="row mb-2"> <div class="row mb-2">
<h5 class="offset-sm-3">Addressing</h5> <h5 class="offset-sm-3">Addressing</h5>
</div> </div>
{% render_field form.vrf %}
{% render_field form.mac_address %} {% render_field form.mac_address %}
{% render_field form.wwn %} {% render_field form.wwn %}
</div> </div>