diff --git a/netbox_routing/api/_serializers/ospf.py b/netbox_routing/api/_serializers/ospf.py index cf28518..3fa82dc 100644 --- a/netbox_routing/api/_serializers/ospf.py +++ b/netbox_routing/api/_serializers/ospf.py @@ -2,6 +2,7 @@ from dcim.api.serializers_.device_components import InterfaceSerializer from dcim.api.serializers_.devices import DeviceSerializer +from ipam.api.serializers_.vrfs import VRFSerializer from netbox.api.serializers import NetBoxModelSerializer from netbox_routing.models import OSPFInstance, OSPFArea, OSPFInterface @@ -16,11 +17,14 @@ class OSPFInstanceSerializer(NetBoxModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:ospfinstance-detail') device = DeviceSerializer(nested=True) + vrf = VRFSerializer(nested=True) class Meta: model = OSPFInstance - fields = ('url', 'id', 'display', 'name', 'router_id', 'process_id', 'device', 'description', 'comments', ) - brief_fields = ('url', 'id', 'display', 'name', 'router_id', 'process_id', 'device', ) + fields = ( + 'url', 'id', 'display', 'name', 'router_id', 'process_id', 'device', 'vrf', 'description', 'comments', + ) + brief_fields = ('url', 'id', 'display', 'name', 'router_id', 'process_id', 'device', 'vrf', ) class OSPFAreaSerializer(NetBoxModelSerializer): diff --git a/netbox_routing/filtersets/ospf.py b/netbox_routing/filtersets/ospf.py index 597425c..677c05b 100644 --- a/netbox_routing/filtersets/ospf.py +++ b/netbox_routing/filtersets/ospf.py @@ -4,6 +4,7 @@ from django.utils.translation import gettext as _ from dcim.models import Device, Interface +from ipam.models import VRF from utilities.filters import MultiValueCharFilter from netbox.filtersets import NetBoxModelFilterSet @@ -30,6 +31,17 @@ class OSPFInstanceFilterSet(NetBoxModelFilterSet): to_field_name='name', label='Device', ) + vrf_id = django_filters.ModelMultipleChoiceFilter( + field_name='vrf', + queryset=VRF.objects.all(), + label='VRF (ID)', + ) + vrf = django_filters.ModelMultipleChoiceFilter( + field_name='vrf__name', + queryset=VRF.objects.all(), + to_field_name='name', + label='VRF', + ) router_id = MultiValueCharFilter( method='filter_rid', @@ -38,7 +50,7 @@ class OSPFInstanceFilterSet(NetBoxModelFilterSet): class Meta: model = OSPFInstance - fields = ('device_id', 'device', 'name', 'device', 'router_id', 'process_id') + fields = ('device_id', 'device', 'name', 'vrf_id', 'vrf', 'router_id', 'process_id') def search(self, queryset, name, value): if not value.strip(): @@ -94,6 +106,17 @@ class OSPFInterfaceFilterSet(NetBoxModelFilterSet): to_field_name='name', label='Instance', ) + vrf_id = django_filters.ModelMultipleChoiceFilter( + field_name='instance__vrf', + queryset=VRF.objects.all(), + label='VRF (ID)', + ) + vrf = django_filters.ModelMultipleChoiceFilter( + field_name='instance__vrf__name', + queryset=VRF.objects.all(), + to_field_name='name', + label='VRF', + ) area_id = django_filters.ModelMultipleChoiceFilter( field_name='area', queryset=OSPFArea.objects.all(), diff --git a/netbox_routing/forms/bulk_edit/ospf.py b/netbox_routing/forms/bulk_edit/ospf.py index 8a79d12..34cddec 100644 --- a/netbox_routing/forms/bulk_edit/ospf.py +++ b/netbox_routing/forms/bulk_edit/ospf.py @@ -2,6 +2,7 @@ from django.utils.translation import gettext as _ from dcim.models import Device +from ipam.models import VRF from netbox.forms import NetBoxModelBulkEditForm from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, add_blank_choice from utilities.forms.fields import DynamicModelChoiceField, CommentField @@ -25,6 +26,12 @@ class OSPFInstanceBulkEditForm(NetBoxModelBulkEditForm): required=False, selector=True ) + vrf = DynamicModelChoiceField( + queryset=VRF.objects.all(), + label=_('VRF'), + required=False, + selector=True + ) description = forms.CharField( label=_('Description'), @@ -35,10 +42,10 @@ class OSPFInstanceBulkEditForm(NetBoxModelBulkEditForm): model = OSPFInstance fieldsets = ( - FieldSet('device', name='OSPF'), + FieldSet('device', 'vrf', name='OSPF'), FieldSet('description', ), ) - nullable_fields = () + nullable_fields = ('vrf', 'description', ) class OSPFAreaBulkEditForm(NetBoxModelBulkEditForm): diff --git a/netbox_routing/forms/bulk_import/ospf.py b/netbox_routing/forms/bulk_import/ospf.py index 084516b..a66445e 100644 --- a/netbox_routing/forms/bulk_import/ospf.py +++ b/netbox_routing/forms/bulk_import/ospf.py @@ -1,6 +1,7 @@ from django.utils.translation import gettext as _ -from dcim.models import Interface +from dcim.models import Interface, Device +from ipam.models import VRF from netbox.forms import NetBoxModelImportForm from utilities.forms.fields import CSVModelChoiceField @@ -16,15 +17,21 @@ class OSPFInstanceImportForm(NetBoxModelImportForm): device = CSVModelChoiceField( - queryset=Interface.objects.all(), - required=False, + queryset=Device.objects.all(), + required=True, to_field_name='name', help_text=_('Name of device') ) + vrf = CSVModelChoiceField( + queryset=VRF.objects.all(), + required=False, + to_field_name='name', + help_text=_('Name of VRF') + ) class Meta: model = OSPFInstance - fields = ('name', 'router_id', 'process_id', 'device', 'description', 'comments', 'tags',) + fields = ('name', 'router_id', 'process_id', 'device', 'vrf', 'description', 'comments', 'tags',) class OSPFAreaImportForm(NetBoxModelImportForm): diff --git a/netbox_routing/forms/filtersets/ospf.py b/netbox_routing/forms/filtersets/ospf.py index 2139a6e..ca21861 100644 --- a/netbox_routing/forms/filtersets/ospf.py +++ b/netbox_routing/forms/filtersets/ospf.py @@ -2,6 +2,7 @@ from django.utils.translation import gettext as _ from dcim.models import Interface, Device +from ipam.models import VRF from netbox.forms import NetBoxModelFilterSetForm from netbox_routing.choices import AuthenticationChoices from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, add_blank_choice @@ -26,6 +27,12 @@ class OSPFInstanceFilterForm(NetBoxModelFilterSetForm): selector=True, label=_('Device'), ) + vrf = DynamicModelMultipleChoiceField( + queryset=VRF.objects.all(), + required=False, + selector=True, + label=_('VRF'), + ) model = OSPFInstance fieldsets = ( FieldSet('q', 'filter_id', 'tag', 'device'), @@ -49,19 +56,31 @@ class OSPFInterfaceFilterForm(NetBoxModelFilterSetForm): FieldSet('interface', name=_('Device')), FieldSet('priority', 'bfd', 'authentication', name=_('Attributes')) ) - instance = DynamicModelMultipleChoiceField( + device_id = DynamicModelMultipleChoiceField( + queryset=Device.objects.all(), + required=False, + selector=True, + label=_('Device'), + ) + vrf_id = DynamicModelMultipleChoiceField( + queryset=VRF.objects.all(), + required=False, + selector=True, + label=_('VRF'), + ) + instance_id = DynamicModelMultipleChoiceField( queryset=OSPFInstance.objects.all(), required=False, selector=True, label=_('Instance'), ) - area = DynamicModelMultipleChoiceField( + area_id = DynamicModelMultipleChoiceField( queryset=OSPFArea.objects.all(), required=False, selector=True, label=_('Area'), ) - interface = DynamicModelMultipleChoiceField( + interface_id = DynamicModelMultipleChoiceField( queryset=Interface.objects.all(), required=False, selector=True, diff --git a/netbox_routing/forms/ospf.py b/netbox_routing/forms/ospf.py index 522e50d..46dd363 100644 --- a/netbox_routing/forms/ospf.py +++ b/netbox_routing/forms/ospf.py @@ -3,6 +3,7 @@ from django.utils.translation import gettext as _ from dcim.models import Interface, Device +from ipam.models import VRF from netbox.forms import NetBoxModelForm from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField, CommentField @@ -24,11 +25,17 @@ class OSPFInstanceForm(NetBoxModelForm): selector=True, label=_('Device'), ) + vrf = DynamicModelChoiceField( + queryset=VRF.objects.all(), + required=False, + selector=True, + label=_('VRF'), + ) comments = CommentField() class Meta: model = OSPFInstance - fields = ('name', 'router_id', 'process_id', 'device', 'description', 'comments', ) + fields = ('name', 'router_id', 'process_id', 'device', 'vrf', 'description', 'comments', ) class OSPFAreaForm(NetBoxModelForm): diff --git a/netbox_routing/graphql/types.py b/netbox_routing/graphql/types.py index 11d089b..f21962a 100644 --- a/netbox_routing/graphql/types.py +++ b/netbox_routing/graphql/types.py @@ -47,6 +47,7 @@ class OSPFInstanceType(NetBoxObjectType): name: str device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')] + vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None router_id: str process_id: str diff --git a/netbox_routing/migrations/0012_osfp_instance_vrf.py b/netbox_routing/migrations/0012_osfp_instance_vrf.py new file mode 100644 index 0000000..90d4590 --- /dev/null +++ b/netbox_routing/migrations/0012_osfp_instance_vrf.py @@ -0,0 +1,26 @@ +# Generated by Django 5.0.8 on 2024-09-28 04:42 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ipam', '0070_vlangroup_vlan_id_ranges'), + ('netbox_routing', '0011_osfp_passive_interface'), + ] + + operations = [ + migrations.AddField( + model_name='ospfinstance', + name='vrf', + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name='ospf_instances', + to='ipam.vrf', + ), + ), + ] diff --git a/netbox_routing/models/ospf.py b/netbox_routing/models/ospf.py index 2c8287f..1a6f959 100644 --- a/netbox_routing/models/ospf.py +++ b/netbox_routing/models/ospf.py @@ -28,6 +28,14 @@ class OSPFInstance(PrimaryModel): blank=False, null=False ) + vrf = models.ForeignKey( + verbose_name=_('VRF'), + to='ipam.VRF', + related_name='ospf_instances', + on_delete=models.CASCADE, + blank=True, + null=True + ) clone_fields = ('device',) prerequisite_models = ( diff --git a/netbox_routing/tables/ospf.py b/netbox_routing/tables/ospf.py index 83aa82d..c9d6b3f 100644 --- a/netbox_routing/tables/ospf.py +++ b/netbox_routing/tables/ospf.py @@ -15,8 +15,8 @@ class OSPFInstanceTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = OSPFInstance - fields = ('pk', 'id', 'name', 'router_id', 'process_id', 'device') - default_columns = ('pk', 'id', 'name', 'router_id', 'process_id', 'device') + fields = ('pk', 'id', 'name', 'router_id', 'process_id', 'device', 'vrf', ) + default_columns = ('pk', 'id', 'name', 'router_id', 'process_id', 'device', ) class OSPFAreaTable(NetBoxTable): diff --git a/netbox_routing/templates/netbox_routing/ospfinstance.html b/netbox_routing/templates/netbox_routing/ospfinstance.html index 9d679b2..8ce4e86 100644 --- a/netbox_routing/templates/netbox_routing/ospfinstance.html +++ b/netbox_routing/templates/netbox_routing/ospfinstance.html @@ -34,6 +34,12 @@
OSPF Details
{{ object.device|linkify }} + + VRF + + {{ object.vrf|linkify }} + + diff --git a/netbox_routing/tests/ospf/test_api.py b/netbox_routing/tests/ospf/test_api.py index 92c83ef..8a7a847 100644 --- a/netbox_routing/tests/ospf/test_api.py +++ b/netbox_routing/tests/ospf/test_api.py @@ -1,4 +1,5 @@ from dcim.models import Interface +from ipam.models import VRF from utilities.testing import APIViewTestCases, create_test_device from netbox_routing.models import OSPFInstance, OSPFArea, OSPFInterface @@ -14,7 +15,7 @@ class OSPFInstanceTestCase(IPAddressFieldMixin, APIViewTestCases.APIViewTestCase): model = OSPFInstance view_namespace = 'plugins-api:netbox_routing' - brief_fields = ['device', 'display', 'id', 'name', 'process_id', 'router_id', 'url', ] + brief_fields = ['device', 'display', 'id', 'name', 'process_id', 'router_id', 'url', 'vrf', ] user_permissions = ('dcim.view_device', ) @@ -24,13 +25,13 @@ class OSPFInstanceTestCase(IPAddressFieldMixin, APIViewTestCases.APIViewTestCase @classmethod def setUpTestData(cls): - + vrf = VRF.objects.create(name='Test VRF') device = create_test_device(name='Test Device') data = ( - cls.model(name='Instance 1', device=device, router_id='1.1.1.1', process_id=1), - cls.model(name='Instance 2', device=device, router_id='2.2.2.2', process_id=2), - cls.model(name='Instance 3', device=device, router_id='3.3.3.3', process_id=3), + cls.model(name='Instance 1', device=device, router_id='1.1.1.1', process_id=1, vrf=None), + cls.model(name='Instance 2', device=device, router_id='2.2.2.2', process_id=2, vrf=vrf), + cls.model(name='Instance 3', device=device, router_id='3.3.3.3', process_id=3, vrf=None), ) cls.model.objects.bulk_create(data) @@ -39,7 +40,8 @@ def setUpTestData(cls): 'name': 'Instance X', 'device': device.pk, 'router_id': '4.4.4.4', - 'process_id': 4 + 'process_id': 4, + 'vrf': vrf.pk, }, ] diff --git a/netbox_routing/tests/ospf/test_filtersets.py b/netbox_routing/tests/ospf/test_filtersets.py index 46da1d6..3f8501e 100644 --- a/netbox_routing/tests/ospf/test_filtersets.py +++ b/netbox_routing/tests/ospf/test_filtersets.py @@ -21,6 +21,13 @@ class OSPFInstanceTestCase(TestCase): @classmethod def setUpTestData(cls): + vrfs = ( + VRF(name='VRF 1'), + VRF(name='VRF 2'), + VRF(name='VRF 3'), + ) + VRF.objects.bulk_create(vrfs) + devices = [ create_test_device(name='Device 1'), create_test_device(name='Device 2'), @@ -29,10 +36,10 @@ def setUpTestData(cls): ] data = ( - OSPFInstance(name='Instance 1', device=devices[0], router_id='0.0.0.0', process_id=0), - OSPFInstance(name='Instance 2', device=devices[1], router_id='1.1.1.1', process_id=1), - OSPFInstance(name='Instance 3', device=devices[2], router_id='2.2.2.2', process_id=2), - OSPFInstance(name='Instance 3', device=devices[3], router_id='3.3.3.3', process_id=3), + OSPFInstance(name='Instance 1', device=devices[0], router_id='0.0.0.0', process_id=0, vrf=vrfs[0]), + OSPFInstance(name='Instance 2', device=devices[1], router_id='1.1.1.1', process_id=1, vrf=vrfs[1]), + OSPFInstance(name='Instance 3', device=devices[2], router_id='2.2.2.2', process_id=2, vrf=None), + OSPFInstance(name='Instance 3', device=devices[3], router_id='3.3.3.3', process_id=3, vrf=vrfs[2]), ) OSPFInstance.objects.bulk_create(data) @@ -45,6 +52,15 @@ def test_name(self): params = {'name': ['Instance 1', 'Instance 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + def test_vrf(self): + data = VRF.objects.all()[0:2] + + params = {'vrf_id': [data[0].pk, data[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + params = {'vrf': [data[0].name, data[1].name]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + def test_device(self): data = Device.objects.all()[0:2] @@ -95,6 +111,13 @@ class OSPFInterfaceTestCase(TestCase): @classmethod def setUpTestData(cls): + vrfs = ( + VRF(name='VRF 1'), + VRF(name='VRF 2'), + VRF(name='VRF 3'), + ) + VRF.objects.bulk_create(vrfs) + devices = ( create_test_device(name='Device 1'), create_test_device(name='Device 2'), @@ -118,10 +141,10 @@ def setUpTestData(cls): OSPFArea.objects.bulk_create(areas) instances = ( - OSPFInstance(name='Instance 0', device=devices[0], router_id='0.0.0.0', process_id=0), - OSPFInstance(name='Instance 1', device=devices[1], router_id='1.1.1.1', process_id=1), - OSPFInstance(name='Instance 2', device=devices[2], router_id='2.2.2.2', process_id=2), - OSPFInstance(name='Instance 3', device=devices[3], router_id='3.3.3.3', process_id=3), + OSPFInstance(name='Instance 0', device=devices[0], router_id='0.0.0.0', process_id=0, vrf=vrfs[0]), + OSPFInstance(name='Instance 1', device=devices[1], router_id='1.1.1.1', process_id=1, vrf=vrfs[1]), + OSPFInstance(name='Instance 2', device=devices[2], router_id='2.2.2.2', process_id=2, vrf=None), + OSPFInstance(name='Instance 3', device=devices[3], router_id='3.3.3.3', process_id=3, vrf=vrfs[2]), ) OSPFInstance.objects.bulk_create(instances) @@ -158,6 +181,15 @@ def test_area(self): params = {'area': [data[0].area_id, data[1].area_id]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + def test_vrfs(self): + data = VRF.objects.all()[0:2] + + params = {'vrf_id': [data[0].pk, data[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + params = {'vrf': [data[0].name, data[1].name]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + def test_devices(self): data = Device.objects.all()[0:2] diff --git a/netbox_routing/tests/ospf/test_forms.py b/netbox_routing/tests/ospf/test_forms.py index 1f1cc3a..54a205a 100644 --- a/netbox_routing/tests/ospf/test_forms.py +++ b/netbox_routing/tests/ospf/test_forms.py @@ -1,6 +1,7 @@ from django.test import TestCase from dcim.models import Device, Interface +from ipam.models import VRF from utilities.testing import create_test_device from netbox_routing.forms import * @@ -17,6 +18,7 @@ class OSPFInstanceTestCase(TestCase): @classmethod def setUpTestData(cls): + vrf = VRF.objects.create(name='Test VRF') device = create_test_device(name='Device 1') def test_instance(self): @@ -31,6 +33,19 @@ def test_instance(self): self.assertTrue(form.is_valid()) self.assertTrue(form.save()) + def test_instance_with_vrf(self): + form = OSPFInstanceForm(data={ + 'name': 'Instance 2', + 'process_id': '1', + 'router_id': '20.20.20.1', + 'device': Device.objects.first().pk, + 'vrf': VRF.objects.first().pk, + }) + if not form.is_valid(): + print(form.errors) + self.assertTrue(form.is_valid()) + self.assertTrue(form.save()) + class OSPFAreaTestCase(TestCase): diff --git a/netbox_routing/tests/ospf/test_views.py b/netbox_routing/tests/ospf/test_views.py index c80bfd1..0c0bbd7 100644 --- a/netbox_routing/tests/ospf/test_views.py +++ b/netbox_routing/tests/ospf/test_views.py @@ -30,24 +30,27 @@ class OSPFInstanceTestCase( @classmethod def setUpTestData(cls): + vrf = VRF.objects.create(name='Test') devices = [create_test_device(name='Device 1'), create_test_device(name='Device 2')] - routes = ( - cls.model(name="Instance 0", device=devices[0], router_id='0.0.0.0', process_id='0'), - cls.model(name="Instance 1", device=devices[0], router_id='1.1.1.1', process_id='1'), - cls.model(name="Instance 2", device=devices[0], router_id='2.2.2.2', process_id='2'), + instances = ( + cls.model(name="Instance 0", device=devices[0], router_id='0.0.0.0', process_id='0', vrf=None), + cls.model(name="Instance 1", device=devices[0], router_id='1.1.1.1', process_id='1', vrf=None), + cls.model(name="Instance 2", device=devices[0], router_id='2.2.2.2', process_id='2', vrf=vrf), ) - cls.model.objects.bulk_create(routes) + cls.model.objects.bulk_create(instances) cls.form_data = { 'name': 'Instance X', 'device': devices[1].pk, 'router_id': '4.4.4.4', 'process_id': 4, + 'vrf': vrf.pk, } cls.bulk_edit_data = { - 'description': 'A test Instance description' + 'description': 'A test Instance description', + 'vrf': vrf.pk } def _get_base_url(self):