소스 검색

#7852: Extend VRF assignment to VM interfaces

jeremystretch 4 년 전
부모
커밋
3651ef53e3

+ 1 - 1
docs/models/virtualization/vminterface.md

@@ -1,3 +1,3 @@
 ## Interfaces
 ## Interfaces
 
 
-Virtual machine interfaces behave similarly to device interfaces, and can be assigned IP addresses, VLANs, and services. However, given their virtual nature, they lack properties pertaining to physical attributes. For example, VM interfaces do not have a physical type and cannot have cables attached to them.
+Virtual machine interfaces behave similarly to device interfaces, and can be assigned to VRFs, and may have IP addresses, VLANs, and services attached to them. However, given their virtual nature, they lack properties pertaining to physical attributes. For example, VM interfaces do not have a physical type and cannot have cables attached to them.

+ 2 - 0
docs/release-notes/version-3.2.md

@@ -134,3 +134,5 @@ A new REST API endpoint has been added at `/api/ipam/vlan-groups/<pk>/available-
 * ipam.VLANGroup
 * ipam.VLANGroup
     * Added the `/availables-vlans/` endpoint
     * Added the `/availables-vlans/` endpoint
     * Added the `min_vid` and `max_vid` fields
     * Added the `min_vid` and `max_vid` fields
+* virtualization.VMInterface
+    * Added `vrf` field

+ 10 - 0
netbox/templates/virtualization/vminterface.html

@@ -59,6 +59,16 @@
                             {% endif %}
                             {% endif %}
                         </td>
                         </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>
                     <tr>
                     <tr>
                         <th scope="row">Description</th>
                         <th scope="row">Description</th>
                         <td>{{ object.description|placeholder }} </td>
                         <td>{{ object.description|placeholder }} </td>

+ 1 - 0
netbox/templates/virtualization/vminterface_edit.html

@@ -22,6 +22,7 @@
       {% render_field form.name %}
       {% render_field form.name %}
       {% render_field form.description %}
       {% render_field form.description %}
       {% render_field form.mac_address %}
       {% render_field form.mac_address %}
+      {% render_field form.vrf %}
       {% render_field form.mtu %}
       {% render_field form.mtu %}
       {% render_field form.tags %}
       {% render_field form.tags %}
       {% render_field form.enabled %}
       {% render_field form.enabled %}

+ 4 - 3
netbox/virtualization/api/serializers.py

@@ -3,7 +3,7 @@ from rest_framework import serializers
 
 
 from dcim.api.nested_serializers import NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedSiteSerializer
 from dcim.api.nested_serializers import NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedSiteSerializer
 from dcim.choices import InterfaceModeChoices
 from dcim.choices import InterfaceModeChoices
-from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer
+from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer, NestedVRFSerializer
 from ipam.models import VLAN
 from ipam.models import VLAN
 from netbox.api import ChoiceField, SerializedPKRelatedField
 from netbox.api import ChoiceField, SerializedPKRelatedField
 from netbox.api.serializers import PrimaryModelSerializer
 from netbox.api.serializers import PrimaryModelSerializer
@@ -116,6 +116,7 @@ class VMInterfaceSerializer(PrimaryModelSerializer):
         required=False,
         required=False,
         many=True
         many=True
     )
     )
+    vrf = NestedVRFSerializer(required=False, allow_null=True)
     count_ipaddresses = serializers.IntegerField(read_only=True)
     count_ipaddresses = serializers.IntegerField(read_only=True)
     count_fhrp_groups = serializers.IntegerField(read_only=True)
     count_fhrp_groups = serializers.IntegerField(read_only=True)
 
 
@@ -123,8 +124,8 @@ class VMInterfaceSerializer(PrimaryModelSerializer):
         model = VMInterface
         model = VMInterface
         fields = [
         fields = [
             'id', 'url', 'display', 'virtual_machine', 'name', 'enabled', 'parent', 'bridge', 'mtu', 'mac_address',
             'id', 'url', 'display', 'virtual_machine', 'name', 'enabled', 'parent', 'bridge', 'mtu', 'mac_address',
-            'description', 'mode', 'untagged_vlan', 'tagged_vlans', 'tags', 'custom_fields', 'created', 'last_updated',
-            'count_ipaddresses', 'count_fhrp_groups',
+            'description', 'mode', 'untagged_vlan', 'tagged_vlans', 'vrf', 'tags', 'custom_fields', 'created',
+            'last_updated', 'count_ipaddresses', 'count_fhrp_groups',
         ]
         ]
 
 
     def validate(self, data):
     def validate(self, data):

+ 2 - 1
netbox/virtualization/api/views.py

@@ -80,7 +80,8 @@ class VirtualMachineViewSet(ConfigContextQuerySetMixin, CustomFieldModelViewSet)
 
 
 class VMInterfaceViewSet(ModelViewSet):
 class VMInterfaceViewSet(ModelViewSet):
     queryset = VMInterface.objects.prefetch_related(
     queryset = VMInterface.objects.prefetch_related(
-        'virtual_machine', 'parent', 'tags', 'untagged_vlan', 'tagged_vlans', 'ip_addresses', 'fhrp_group_assignments',
+        'virtual_machine', 'parent', 'tags', 'untagged_vlan', 'tagged_vlans', 'vrf', 'ip_addresses',
+        'fhrp_group_assignments',
     )
     )
     serializer_class = serializers.VMInterfaceSerializer
     serializer_class = serializers.VMInterfaceSerializer
     filterset_class = filtersets.VMInterfaceFilterSet
     filterset_class = filtersets.VMInterfaceFilterSet

+ 12 - 0
netbox/virtualization/filtersets.py

@@ -3,6 +3,7 @@ from django.db.models import Q
 
 
 from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
 from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
 from extras.filtersets import LocalConfigContextFilterSet
 from extras.filtersets import LocalConfigContextFilterSet
+from ipam.models import VRF
 from netbox.filtersets import OrganizationalModelFilterSet, NetBoxModelFilterSet
 from netbox.filtersets import OrganizationalModelFilterSet, NetBoxModelFilterSet
 from tenancy.filtersets import TenancyFilterSet
 from tenancy.filtersets import TenancyFilterSet
 from utilities.filters import MultiValueMACAddressFilter, TreeNodeMultipleChoiceFilter
 from utilities.filters import MultiValueMACAddressFilter, TreeNodeMultipleChoiceFilter
@@ -273,6 +274,17 @@ class VMInterfaceFilterSet(NetBoxModelFilterSet):
     mac_address = MultiValueMACAddressFilter(
     mac_address = MultiValueMACAddressFilter(
         label='MAC address',
         label='MAC address',
     )
     )
+    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 = VMInterface
         model = VMInterface

+ 8 - 3
netbox/virtualization/forms/bulk_edit.py

@@ -3,7 +3,7 @@ from django import forms
 from dcim.choices import InterfaceModeChoices
 from dcim.choices import InterfaceModeChoices
 from dcim.constants import INTERFACE_MTU_MAX, INTERFACE_MTU_MIN
 from dcim.constants import INTERFACE_MTU_MAX, INTERFACE_MTU_MIN
 from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
 from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
-from ipam.models import VLAN
+from ipam.models import VLAN, VRF
 from netbox.forms import NetBoxModelBulkEditForm
 from netbox.forms import NetBoxModelBulkEditForm
 from tenancy.models import Tenant
 from tenancy.models import Tenant
 from utilities.forms import (
 from utilities.forms import (
@@ -190,15 +190,20 @@ class VMInterfaceBulkEditForm(NetBoxModelBulkEditForm):
         queryset=VLAN.objects.all(),
         queryset=VLAN.objects.all(),
         required=False
         required=False
     )
     )
+    vrf = DynamicModelChoiceField(
+        queryset=VRF.objects.all(),
+        required=False,
+        label='VRF'
+    )
 
 
     model = VMInterface
     model = VMInterface
     fieldsets = (
     fieldsets = (
-        (None, ('mtu', 'enabled', 'description')),
+        (None, ('mtu', 'enabled', 'vrf', 'description')),
         ('Related Interfaces', ('parent', 'bridge')),
         ('Related Interfaces', ('parent', 'bridge')),
         ('802.1Q Switching', ('mode', 'untagged_vlan', 'tagged_vlans')),
         ('802.1Q Switching', ('mode', 'untagged_vlan', 'tagged_vlans')),
     )
     )
     nullable_fields = (
     nullable_fields = (
-        'parent', 'bridge', 'mtu', 'description',
+        'parent', 'bridge', 'mtu', 'vrf', 'description',
     )
     )
 
 
     def __init__(self, *args, **kwargs):
     def __init__(self, *args, **kwargs):

+ 8 - 0
netbox/virtualization/forms/bulk_import.py

@@ -1,5 +1,6 @@
 from dcim.choices import InterfaceModeChoices
 from dcim.choices import InterfaceModeChoices
 from dcim.models import DeviceRole, Platform, Site
 from dcim.models import DeviceRole, Platform, Site
+from ipam.models import VRF
 from netbox.forms import NetBoxModelCSVForm
 from netbox.forms import NetBoxModelCSVForm
 from tenancy.models import Tenant
 from tenancy.models import Tenant
 from utilities.forms import CSVChoiceField, CSVModelChoiceField, SlugField
 from utilities.forms import CSVChoiceField, CSVModelChoiceField, SlugField
@@ -121,11 +122,18 @@ class VMInterfaceCSVForm(NetBoxModelCSVForm):
         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'
+    )
 
 
     class Meta:
     class Meta:
         model = VMInterface
         model = VMInterface
         fields = (
         fields = (
             'virtual_machine', 'name', 'parent', 'bridge', 'enabled', 'mac_address', 'mtu', 'description', 'mode',
             'virtual_machine', 'name', 'parent', 'bridge', 'enabled', 'mac_address', 'mtu', 'description', 'mode',
+            'vrf',
         )
         )
 
 
     def clean_enabled(self):
     def clean_enabled(self):

+ 7 - 1
netbox/virtualization/forms/filtersets.py

@@ -3,6 +3,7 @@ from django.utils.translation import gettext as _
 
 
 from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
 from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
 from extras.forms import LocalConfigContextFilterForm
 from extras.forms import LocalConfigContextFilterForm
+from ipam.models import VRF
 from netbox.forms import NetBoxModelFilterSetForm
 from netbox.forms import NetBoxModelFilterSetForm
 from tenancy.forms import TenancyFilterForm
 from tenancy.forms import TenancyFilterForm
 from utilities.forms import (
 from utilities.forms import (
@@ -157,7 +158,7 @@ class VMInterfaceFilterForm(NetBoxModelFilterSetForm):
     fieldsets = (
     fieldsets = (
         (None, ('q', 'tag')),
         (None, ('q', 'tag')),
         ('Virtual Machine', ('cluster_id', 'virtual_machine_id')),
         ('Virtual Machine', ('cluster_id', 'virtual_machine_id')),
-        ('Attributes', ('enabled', 'mac_address')),
+        ('Attributes', ('enabled', 'mac_address', 'vrf_id')),
     )
     )
     cluster_id = DynamicModelMultipleChoiceField(
     cluster_id = DynamicModelMultipleChoiceField(
         queryset=Cluster.objects.all(),
         queryset=Cluster.objects.all(),
@@ -182,4 +183,9 @@ class VMInterfaceFilterForm(NetBoxModelFilterSetForm):
         required=False,
         required=False,
         label='MAC address'
         label='MAC address'
     )
     )
+    vrf_id = DynamicModelMultipleChoiceField(
+        queryset=VRF.objects.all(),
+        required=False,
+        label='VRF'
+    )
     tag = TagFilterField(model)
     tag = TagFilterField(model)

+ 7 - 2
netbox/virtualization/forms/models.py

@@ -6,7 +6,7 @@ from dcim.forms.common import InterfaceCommonForm
 from dcim.forms.models import INTERFACE_MODE_HELP_TEXT
 from dcim.forms.models import INTERFACE_MODE_HELP_TEXT
 from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site, SiteGroup
 from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site, SiteGroup
 from extras.models import Tag
 from extras.models import Tag
-from ipam.models import IPAddress, VLAN, VLANGroup
+from ipam.models import IPAddress, VLAN, VLANGroup, VRF
 from netbox.forms import NetBoxModelForm
 from netbox.forms import NetBoxModelForm
 from tenancy.forms import TenancyForm
 from tenancy.forms import TenancyForm
 from utilities.forms import (
 from utilities.forms import (
@@ -313,6 +313,11 @@ class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
             'available_on_virtualmachine': '$virtual_machine',
             'available_on_virtualmachine': '$virtual_machine',
         }
         }
     )
     )
+    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
@@ -322,7 +327,7 @@ class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
         model = VMInterface
         model = VMInterface
         fields = [
         fields = [
             'virtual_machine', 'name', 'parent', 'bridge', 'enabled', 'mac_address', 'mtu', 'description', 'mode',
             'virtual_machine', 'name', 'parent', 'bridge', 'enabled', 'mac_address', 'mtu', 'description', 'mode',
-            'tags', 'untagged_vlan', 'tagged_vlans',
+            'untagged_vlan', 'tagged_vlans', 'vrf', 'tags',
         ]
         ]
         widgets = {
         widgets = {
             'virtual_machine': forms.HiddenInput(),
             'virtual_machine': forms.HiddenInput(),

+ 20 - 0
netbox/virtualization/migrations/0028_vminterface_vrf.py

@@ -0,0 +1,20 @@
+# Generated by Django 3.2.12 on 2022-02-07 14:39
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('ipam', '0056_standardize_id_fields'),
+        ('virtualization', '0027_standardize_id_fields'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='vminterface',
+            name='vrf',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vminterfaces', to='ipam.vrf'),
+        ),
+    ]

+ 8 - 0
netbox/virtualization/models.py

@@ -384,6 +384,14 @@ class VMInterface(NetBoxModel, BaseInterface):
         object_id_field='assigned_object_id',
         object_id_field='assigned_object_id',
         related_query_name='vminterface'
         related_query_name='vminterface'
     )
     )
+    vrf = models.ForeignKey(
+        to='ipam.VRF',
+        on_delete=models.SET_NULL,
+        related_name='vminterfaces',
+        null=True,
+        blank=True,
+        verbose_name='VRF'
+    )
     fhrp_group_assignments = GenericRelation(
     fhrp_group_assignments = GenericRelation(
         to='ipam.FHRPGroupAssignment',
         to='ipam.FHRPGroupAssignment',
         content_type_field='interface_type',
         content_type_field='interface_type',

+ 4 - 1
netbox/virtualization/tables.py

@@ -168,6 +168,9 @@ class VMInterfaceTable(BaseInterfaceTable):
     name = tables.Column(
     name = tables.Column(
         linkify=True
         linkify=True
     )
     )
+    vrf = tables.Column(
+        linkify=True
+    )
     tags = columns.TagColumn(
     tags = columns.TagColumn(
         url_name='virtualization:vminterface_list'
         url_name='virtualization:vminterface_list'
     )
     )
@@ -176,7 +179,7 @@ class VMInterfaceTable(BaseInterfaceTable):
         model = VMInterface
         model = VMInterface
         fields = (
         fields = (
             'pk', 'id', 'name', 'virtual_machine', 'enabled', 'mac_address', 'mtu', 'mode', 'description', 'tags',
             'pk', 'id', 'name', 'virtual_machine', 'enabled', 'mac_address', 'mtu', 'mode', 'description', 'tags',
-            'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans', 'created', 'last_updated',
+            'vrf', 'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans', 'created', 'last_updated',
         )
         )
         default_columns = ('pk', 'name', 'virtual_machine', 'enabled', 'description')
         default_columns = ('pk', 'name', 'virtual_machine', 'enabled', 'description')
 
 

+ 11 - 1
netbox/virtualization/tests/test_api.py

@@ -2,7 +2,7 @@ from django.urls import reverse
 from rest_framework import status
 from rest_framework import status
 
 
 from dcim.choices import InterfaceModeChoices
 from dcim.choices import InterfaceModeChoices
-from ipam.models import VLAN
+from ipam.models import VLAN, VRF
 from utilities.testing import APITestCase, APIViewTestCases
 from utilities.testing import APITestCase, APIViewTestCases
 from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
 from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
 
 
@@ -234,6 +234,13 @@ class VMInterfaceTest(APIViewTestCases.APIViewTestCase):
         )
         )
         VLAN.objects.bulk_create(vlans)
         VLAN.objects.bulk_create(vlans)
 
 
+        vrfs = (
+            VRF(name='VRF 1'),
+            VRF(name='VRF 2'),
+            VRF(name='VRF 3'),
+        )
+        VRF.objects.bulk_create(vrfs)
+
         cls.create_data = [
         cls.create_data = [
             {
             {
                 'virtual_machine': virtualmachine.pk,
                 'virtual_machine': virtualmachine.pk,
@@ -241,6 +248,7 @@ class VMInterfaceTest(APIViewTestCases.APIViewTestCase):
                 'mode': InterfaceModeChoices.MODE_TAGGED,
                 'mode': InterfaceModeChoices.MODE_TAGGED,
                 '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,
+                'vrf': vrfs[0].pk,
             },
             },
             {
             {
                 'virtual_machine': virtualmachine.pk,
                 'virtual_machine': virtualmachine.pk,
@@ -249,6 +257,7 @@ class VMInterfaceTest(APIViewTestCases.APIViewTestCase):
                 'bridge': interfaces[0].pk,
                 'bridge': interfaces[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,
+                'vrf': vrfs[1].pk,
             },
             },
             {
             {
                 'virtual_machine': virtualmachine.pk,
                 'virtual_machine': virtualmachine.pk,
@@ -257,5 +266,6 @@ class VMInterfaceTest(APIViewTestCases.APIViewTestCase):
                 'parent': interfaces[1].pk,
                 'parent': interfaces[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,
+                'vrf': vrfs[2].pk,
             },
             },
         ]
         ]

+ 18 - 4
netbox/virtualization/tests/test_filtersets.py

@@ -1,7 +1,7 @@
 from django.test import TestCase
 from django.test import TestCase
 
 
 from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
 from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
-from ipam.models import IPAddress
+from ipam.models import IPAddress, VRF
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup
 from utilities.testing import ChangeLoggedFilterSetTests
 from utilities.testing import ChangeLoggedFilterSetTests
 from virtualization.choices import *
 from virtualization.choices import *
@@ -414,6 +414,13 @@ class VMInterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
         )
         )
         Cluster.objects.bulk_create(clusters)
         Cluster.objects.bulk_create(clusters)
 
 
+        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)
+
         vms = (
         vms = (
             VirtualMachine(name='Virtual Machine 1', cluster=clusters[0]),
             VirtualMachine(name='Virtual Machine 1', cluster=clusters[0]),
             VirtualMachine(name='Virtual Machine 2', cluster=clusters[1]),
             VirtualMachine(name='Virtual Machine 2', cluster=clusters[1]),
@@ -422,9 +429,9 @@ class VMInterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
         VirtualMachine.objects.bulk_create(vms)
         VirtualMachine.objects.bulk_create(vms)
 
 
         interfaces = (
         interfaces = (
-            VMInterface(virtual_machine=vms[0], name='Interface 1', enabled=True, mtu=100, mac_address='00-00-00-00-00-01'),
-            VMInterface(virtual_machine=vms[1], name='Interface 2', enabled=True, mtu=200, mac_address='00-00-00-00-00-02'),
-            VMInterface(virtual_machine=vms[2], name='Interface 3', enabled=False, mtu=300, mac_address='00-00-00-00-00-03'),
+            VMInterface(virtual_machine=vms[0], name='Interface 1', enabled=True, mtu=100, mac_address='00-00-00-00-00-01', vrf=vrfs[0]),
+            VMInterface(virtual_machine=vms[1], name='Interface 2', enabled=True, mtu=200, mac_address='00-00-00-00-00-02', vrf=vrfs[1]),
+            VMInterface(virtual_machine=vms[2], name='Interface 3', enabled=False, mtu=300, mac_address='00-00-00-00-00-03', vrf=vrfs[2]),
         )
         )
         VMInterface.objects.bulk_create(interfaces)
         VMInterface.objects.bulk_create(interfaces)
 
 
@@ -478,3 +485,10 @@ class VMInterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
     def test_mac_address(self):
     def test_mac_address(self):
         params = {'mac_address': ['00-00-00-00-00-01', '00-00-00-00-00-02']}
         params = {'mac_address': ['00-00-00-00-00-01', '00-00-00-00-00-02']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+    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)

+ 14 - 5
netbox/virtualization/tests/test_views.py

@@ -4,7 +4,7 @@ from netaddr import EUI
 
 
 from dcim.choices import InterfaceModeChoices
 from dcim.choices import InterfaceModeChoices
 from dcim.models import DeviceRole, Platform, Site
 from dcim.models import DeviceRole, Platform, Site
-from ipam.models import VLAN
+from ipam.models import VLAN, VRF
 from utilities.testing import ViewTestCases, create_tags
 from utilities.testing import ViewTestCases, create_tags
 from virtualization.choices import *
 from virtualization.choices import *
 from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
 from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
@@ -263,6 +263,13 @@ class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
         )
         )
         VLAN.objects.bulk_create(vlans)
         VLAN.objects.bulk_create(vlans)
 
 
+        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 = {
@@ -276,6 +283,7 @@ class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
             'mode': InterfaceModeChoices.MODE_TAGGED,
             'mode': InterfaceModeChoices.MODE_TAGGED,
             '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[0].pk,
             'tags': [t.pk for t in tags],
             'tags': [t.pk for t in tags],
         }
         }
 
 
@@ -290,14 +298,15 @@ class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
             'mode': InterfaceModeChoices.MODE_TAGGED,
             'mode': InterfaceModeChoices.MODE_TAGGED,
             '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[0].pk,
             'tags': [t.pk for t in tags],
             'tags': [t.pk for t in tags],
         }
         }
 
 
         cls.csv_data = (
         cls.csv_data = (
-            "virtual_machine,name",
-            "Virtual Machine 2,Interface 4",
-            "Virtual Machine 2,Interface 5",
-            "Virtual Machine 2,Interface 6",
+            f"virtual_machine,name,vrf.pk",
+            f"Virtual Machine 2,Interface 4,{vrfs[0].pk}",
+            f"Virtual Machine 2,Interface 5,{vrfs[0].pk}",
+            f"Virtual Machine 2,Interface 6,{vrfs[0].pk}",
         )
         )
 
 
         cls.bulk_edit_data = {
         cls.bulk_edit_data = {