diff --git a/netbox_routing/api/_serializers/bgp.py b/netbox_routing/api/_serializers/bgp.py index ed8b54c..10e9363 100644 --- a/netbox_routing/api/_serializers/bgp.py +++ b/netbox_routing/api/_serializers/bgp.py @@ -3,11 +3,14 @@ from dcim.api.serializers_.devices import DeviceSerializer from ipam.api.serializers_.asns import ASNSerializer +from ipam.api.serializers_.ip import IPAddressSerializer from ipam.api.serializers_.vrfs import VRFSerializer +from netbox.api.fields import ContentTypeField from netbox.api.serializers import NetBoxModelSerializer +from tenancy.api.serializers_.tenants import TenantSerializer from utilities.api import get_serializer_for_model -from netbox_routing.models import BGPRouter, BGPSetting, BGPScope, BGPAddressFamily +from netbox_routing.models.bgp import * __all__ = ( 'BGPRouterSerializer', @@ -62,8 +65,8 @@ class BGPRouterSerializer(NetBoxModelSerializer): device = DeviceSerializer(nested=True) asn = ASNSerializer(nested=True) - settings = BGPSettingSerializer(many=True) + tenant = TenantSerializer(nested=True) class Meta: model = BGPRouter @@ -74,6 +77,7 @@ class Meta: 'device', 'asn', 'settings', + 'tenant', 'description', 'comments', ) @@ -93,8 +97,8 @@ class BGPScopeSerializer(NetBoxModelSerializer): router = BGPRouterSerializer(nested=True) vrf = VRFSerializer(nested=True) - settings = BGPSettingSerializer(many=True) + tenant = TenantSerializer(nested=True) class Meta: model = BGPScope @@ -105,6 +109,7 @@ class Meta: 'router', 'vrf', 'settings', + 'tenant', 'description', 'comments', ) @@ -124,6 +129,7 @@ class BGPAddressFamilySerializer(NetBoxModelSerializer): scope = BGPScopeSerializer(nested=True) settings = BGPSettingSerializer(many=True) + tenant = TenantSerializer(nested=True) class Meta: model = BGPAddressFamily @@ -134,6 +140,7 @@ class Meta: 'scope', 'address_family', 'settings', + 'tenant', 'description', 'comments', ) @@ -144,3 +151,169 @@ class Meta: 'scope', 'address_family', ) + + +class BGPSessionTemplateSerializer(NetBoxModelSerializer): + url = serializers.HyperlinkedIdentityField( + view_name='plugins-api:netbox_routing-api:bgpscope-detail' + ) + + router = BGPRouterSerializer(nested=True) + asn = ASNSerializer(nested=True) + tenant = TenantSerializer(nested=True) + + class Meta: + model = BGPSessionTemplate + fields = ( + 'url', + 'id', + 'display', + 'name', + 'router', + 'parent', + 'enabled', + 'asn', + 'bfd', + 'password', + 'tenant', + 'description', + 'comments', + ) + brief_fields = ( + 'url', + 'id', + 'display', + 'name', + 'router', + 'parent', + ) + + +class BGPPolicyTemplateSerializer(NetBoxModelSerializer): + url = serializers.HyperlinkedIdentityField( + view_name='plugins-api:netbox_routing-api:bgpscope-detail' + ) + + router = BGPRouterSerializer(nested=True) + tenant = TenantSerializer(nested=True) + + class Meta: + model = BGPPolicyTemplate + fields = ( + 'url', + 'id', + 'display', + 'name', + 'router', + 'enabled', + 'prefixlist_in', + 'prefixlist_out', + 'routemap_in', + 'routemap_out', + 'tenant', + 'description', + 'comments', + ) + brief_fields = ( + 'url', + 'id', + 'display', + 'name', + 'router', + ) + + +class BGPPeerTemplateSerializer(NetBoxModelSerializer): + url = serializers.HyperlinkedIdentityField( + view_name='plugins-api:netbox_routing-api:bgpaddressfamily-detail' + ) + + remote_as = ASNSerializer(nested=True) + tenant = TenantSerializer(nested=True) + + class Meta: + model = BGPPeerTemplate + fields = ( + 'url', + 'id', + 'display', + 'name', + 'remote_as', + 'enabled', + 'tenant', + 'description', + 'comments', + ) + brief_fields = ( + 'url', + 'id', + 'display', + 'name', + 'remote_as', + ) + + +class BGPPeerSerializer(NetBoxModelSerializer): + url = serializers.HyperlinkedIdentityField( + view_name='plugins-api:netbox_routing-api:bgpaddressfamily-detail' + ) + + scope = BGPScopeSerializer(nested=True) + peer = IPAddressSerializer(nested=True) + remote_as = ASNSerializer(nested=True) + local_as = ASNSerializer(nested=True) + tenant = TenantSerializer(nested=True) + + class Meta: + model = BGPPeer + fields = ( + 'url', + 'id', + 'display', + 'scope', + 'peer', + 'remote_as', + 'local_as', + 'enabled', + 'bfd', + 'password', + 'tenant', + 'description', + 'comments', + ) + brief_fields = ('url', 'id', 'display', 'scope', 'peer', 'remote_as', 'enabled') + + +class BGPPeerAddressFamilySerializer(NetBoxModelSerializer): + url = serializers.HyperlinkedIdentityField( + view_name='plugins-api:netbox_routing-api:bgpaddressfamily-detail' + ) + assigned_object_type = ContentTypeField( + queryset=ContentType.objects.filter(BGPPEERAF_ASSIGNMENT_MODELS), + required=False, + allow_null=True, + ) + assigned_object = serializers.SerializerMethodField(read_only=True) + tenant = TenantSerializer(nested=True) + + class Meta: + model = BGPPeer + fields = ( + 'url', + 'id', + 'display', + 'assigned_object_type', + 'assigned_object_id', + 'assigned_object', + 'address_family', + 'enabled', + 'peer_policy', + 'prefixlist_in', + 'prefixlist_out', + 'routemap_in', + 'routemap_out', + 'tenant', + 'description', + 'comments', + ) + brief_fields = ('url', 'id', 'display', 'scope', 'peer', 'remote_as', 'enabled') diff --git a/netbox_routing/api/_serializers/communities.py b/netbox_routing/api/_serializers/communities.py new file mode 100644 index 0000000..226dc6d --- /dev/null +++ b/netbox_routing/api/_serializers/communities.py @@ -0,0 +1,53 @@ +from rest_framework import serializers + +from netbox.api.serializers import NetBoxModelSerializer +from tenancy.api.serializers_.tenants import TenantSerializer + +from netbox_routing.models.communities import * + + +__all__ = ( + 'CommunityListSerializer', + 'CommunitySerializer', +) + + +class CommunityListSerializer(NetBoxModelSerializer): + url = serializers.HyperlinkedIdentityField( + view_name='plugins-api:netbox_routing-api:staticroute-detail' + ) + tenant = TenantSerializer(read_only=True) + + class Meta: + model = CommunityList + fields = ( + 'url', + 'id', + 'display', + 'name', + 'communities', + 'tenant', + 'description', + 'comments', + ) + brief_fields = ('url', 'id', 'display', 'name', 'description') + + +class CommunitySerializer(NetBoxModelSerializer): + url = serializers.HyperlinkedIdentityField( + view_name='plugins-api:netbox_routing-api:staticroute-detail' + ) + tenant = TenantSerializer(read_only=True) + + class Meta: + model = Community + fields = ( + 'url', + 'id', + 'display', + 'community', + 'tenant', + 'description', + 'comments', + ) + brief_fields = ('url', 'id', 'display', 'community', 'description') diff --git a/netbox_routing/api/_serializers/objects.py b/netbox_routing/api/_serializers/objects.py index 4f2a9b5..f95a257 100644 --- a/netbox_routing/api/_serializers/objects.py +++ b/netbox_routing/api/_serializers/objects.py @@ -4,7 +4,12 @@ from netbox_routing.models import PrefixList, PrefixListEntry, RouteMap, RouteMapEntry -__all__ = 'StaticRouteSerializer' +__all__ = ( + 'PrefixListSerializer', + 'PrefixListEntrySerializer', + 'RouteMapSerializer', + 'RouteMapEntrySerializer', +) class PrefixListSerializer(NetBoxModelSerializer): @@ -44,7 +49,7 @@ class Meta: 'display', 'prefix_list', 'sequence', - 'type', + 'action', 'prefix', 'le', 'ge', @@ -57,7 +62,7 @@ class Meta: 'display', 'prefix_list', 'sequence', - 'type', + 'action', 'prefix', 'le', 'ge', @@ -96,8 +101,8 @@ class Meta: 'display', 'route_map', 'sequence', - 'type', + 'action', 'description', 'comments', ) - brief_fields = ('url', 'id', 'display', 'route_map', 'sequence', 'type') + brief_fields = ('url', 'id', 'display', 'route_map', 'sequence', 'action') diff --git a/netbox_routing/filtersets/objects.py b/netbox_routing/filtersets/objects.py index 5a59732..10d4ddf 100644 --- a/netbox_routing/filtersets/objects.py +++ b/netbox_routing/filtersets/objects.py @@ -27,7 +27,7 @@ class PrefixListEntryFilterSet(NetBoxModelFilterSet): class Meta: model = PrefixListEntry - fields = ('prefix_list', 'prefix', 'sequence', 'type', 'le', 'ge') + fields = ('prefix_list', 'prefix', 'sequence', 'action', 'le', 'ge') def search(self, queryset, name, value): if not value.strip(): @@ -66,7 +66,7 @@ class RouteMapEntryFilterSet(NetBoxModelFilterSet): class Meta: model = RouteMapEntry - fields = ('route_map', 'sequence', 'type') + fields = ('route_map', 'sequence', 'action') def search(self, queryset, name, value): if not value.strip(): diff --git a/netbox_routing/forms/objects.py b/netbox_routing/forms/objects.py index c3727b2..c681650 100644 --- a/netbox_routing/forms/objects.py +++ b/netbox_routing/forms/objects.py @@ -20,7 +20,7 @@ class Meta: fields = ( 'prefix_list', 'sequence', - 'type', + 'action', 'prefix', 'le', 'ge', @@ -47,7 +47,7 @@ class Meta: fields = ( 'route_map', 'sequence', - 'type', + 'action', 'description', 'comments', ) diff --git a/netbox_routing/migrations/0005_ospf.py b/netbox_routing/migrations/0005_ospf.py index 4a98f5b..f286b8f 100644 --- a/netbox_routing/migrations/0005_ospf.py +++ b/netbox_routing/migrations/0005_ospf.py @@ -10,8 +10,6 @@ class Migration(migrations.Migration): dependencies = [ - ('dcim', '0172_larger_power_draw_values'), - ('extras', '0092_delete_jobresult'), ('netbox_routing', '0004_alter_prefixlistentry_ge_alter_prefixlistentry_le'), ] diff --git a/netbox_routing/migrations/0006_bgp.py b/netbox_routing/migrations/0006_bgp.py index 882ff62..54d2262 100644 --- a/netbox_routing/migrations/0006_bgp.py +++ b/netbox_routing/migrations/0006_bgp.py @@ -10,10 +10,6 @@ class Migration(migrations.Migration): dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), - ('ipam', '0067_ipaddress_index_host'), - ('dcim', '0181_rename_device_role_device_role'), - ('extras', '0098_webhook_custom_field_data_webhook_tags'), ('netbox_routing', '0005_ospf'), ] diff --git a/netbox_routing/migrations/0007_bgpsessiontemplate_asn_bgpsessiontemplate_bfd_and_more.py b/netbox_routing/migrations/0007_bgpsessiontemplate_asn_bgpsessiontemplate_bfd_and_more.py index 5701d08..62bc50e 100644 --- a/netbox_routing/migrations/0007_bgpsessiontemplate_asn_bgpsessiontemplate_bfd_and_more.py +++ b/netbox_routing/migrations/0007_bgpsessiontemplate_asn_bgpsessiontemplate_bfd_and_more.py @@ -7,7 +7,6 @@ class Migration(migrations.Migration): dependencies = [ - ('ipam', '0069_gfk_indexes'), ('netbox_routing', '0006_bgp'), ] diff --git a/netbox_routing/migrations/0010_eigrp.py b/netbox_routing/migrations/0010_eigrp.py index 8b2a214..c65fd3a 100644 --- a/netbox_routing/migrations/0010_eigrp.py +++ b/netbox_routing/migrations/0010_eigrp.py @@ -10,9 +10,6 @@ class Migration(migrations.Migration): dependencies = [ - ('dcim', '0190_nested_modules'), - ('extras', '0121_customfield_related_object_filter'), - ('ipam', '0070_vlangroup_vlan_id_ranges'), ('netbox_routing', '0009_alter_staticroute_metric_alter_staticroute_permanent'), ] diff --git a/netbox_routing/migrations/0012_osfp_instance_vrf.py b/netbox_routing/migrations/0012_osfp_instance_vrf.py index 90d4590..73ae086 100644 --- a/netbox_routing/migrations/0012_osfp_instance_vrf.py +++ b/netbox_routing/migrations/0012_osfp_instance_vrf.py @@ -7,7 +7,6 @@ class Migration(migrations.Migration): dependencies = [ - ('ipam', '0070_vlangroup_vlan_id_ranges'), ('netbox_routing', '0011_osfp_passive_interface'), ] diff --git a/netbox_routing/migrations/0017_rename_bgppolicytemplate.py b/netbox_routing/migrations/0017_rename_bgppolicytemplate.py new file mode 100644 index 0000000..b1c1794 --- /dev/null +++ b/netbox_routing/migrations/0017_rename_bgppolicytemplate.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2b1 on 2025-07-29 13:56 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('netbox_routing', '0016_ospfinterface_interface_onetoonefield'), + ] + + operations = [ + migrations.RenameModel( + old_name='BGPPoliyTemplate', + new_name='BGPPolicyTemplate', + ), + ] diff --git a/netbox_routing/migrations/0018_rename_type_to_action.py b/netbox_routing/migrations/0018_rename_type_to_action.py new file mode 100644 index 0000000..fddaec7 --- /dev/null +++ b/netbox_routing/migrations/0018_rename_type_to_action.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2b1 on 2025-07-29 17:41 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('netbox_routing', '0017_rename_bgppolicytemplate'), + ] + + operations = [ + migrations.RenameField( + model_name='prefixlistentry', + old_name='type', + new_name='action', + ), + migrations.RenameField( + model_name='routemapentry', + old_name='type', + new_name='action', + ), + ] diff --git a/netbox_routing/migrations/0019_fix_peer_policy_and_session_position.py b/netbox_routing/migrations/0019_fix_peer_policy_and_session_position.py new file mode 100644 index 0000000..91e5f4b --- /dev/null +++ b/netbox_routing/migrations/0019_fix_peer_policy_and_session_position.py @@ -0,0 +1,44 @@ +# Generated by Django 5.2b1 on 2025-07-29 13:57 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('netbox_routing', '0018_rename_type_to_action'), + ] + + operations = [ + migrations.RemoveField( + model_name='bgppeer', + name='peer_policy', + ), + migrations.RemoveField( + model_name='bgppeeraddressfamily', + name='peer_session', + ), + migrations.AddField( + model_name='bgppeer', + name='peer_session', + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name='peers', + to='netbox_routing.bgpsessiontemplate', + ), + ), + migrations.AddField( + model_name='bgppeeraddressfamily', + name='peer_policy', + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name='peer_afs', + to='netbox_routing.bgppolicytemplate', + ), + ), + ] diff --git a/netbox_routing/migrations/0020_bgp_configuration.py b/netbox_routing/migrations/0020_bgp_configuration.py new file mode 100644 index 0000000..5241d7d --- /dev/null +++ b/netbox_routing/migrations/0020_bgp_configuration.py @@ -0,0 +1,189 @@ +# Generated by Django 5.2b1 on 2025-07-29 17:18 + +import django.db.models.deletion +from django.db import migrations, models + + +def set_peer(apps, schema_editor): + BGPPeer = apps.get_model('netbox_routing', 'BGPPeer') + IPAddress = apps.get_model('ipam', 'IPAddress') + for peer in BGPPeer.objects.all(): + try: + ip = IPAddress.objects.get(address=peer.peer_ip) + except IPAddress.DoesNotExist: + ip = IPAddress.objects.create(address=peer.peer_ip) + peer.peer = ip + peer.save() + + +def unset_peer(apps, schema_editor): + BGPPeer = apps.get_model('netbox_routing', 'BGPPeer') + for peer in BGPPeer.objects.all(): + if peer.peer is not None: + peer.peer_ip = peer.peer.address + peer.peer = None + peer.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('netbox_routing', '0019_fix_peer_policy_and_session_position'), + ] + + operations = [ + migrations.RemoveField( + model_name='bgppeer', + name='assigned_object_id', + ), + migrations.RemoveField( + model_name='bgppeer', + name='assigned_object_type', + ), + migrations.AddField( + model_name='bgpaddressfamily', + name='tenant', + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name='bgp_address_families', + to='tenancy.tenant', + ), + ), + migrations.AddField( + model_name='bgppeer', + name='bfd', + field=models.BooleanField(blank=True, null=True), + ), + migrations.AddField( + model_name='bgppeer', + name='local_as', + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name='+', + to='ipam.asn', + ), + ), + migrations.AddField( + model_name='bgppeer', + name='password', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='bgppeer', + name='scope', + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name='peers', + to='netbox_routing.bgpscope', + ), + ), + migrations.AddField( + model_name='bgppeer', + name='tenant', + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name='bgp_peers', + to='tenancy.tenant', + ), + ), + migrations.AddField( + model_name='bgppeeraddressfamily', + name='tenant', + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name='bgp_peer_afs', + to='tenancy.tenant', + ), + ), + migrations.AddField( + model_name='bgppeertemplate', + name='tenant', + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name='bgp_peer_templates', + to='tenancy.tenant', + ), + ), + migrations.AddField( + model_name='bgppolicytemplate', + name='parents', + field=models.ManyToManyField( + related_name='children', to='netbox_routing.bgppolicytemplate' + ), + ), + migrations.AddField( + model_name='bgppolicytemplate', + name='tenant', + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name='bgp_peer_policy_templates', + to='tenancy.tenant', + ), + ), + migrations.AddField( + model_name='bgprouter', + name='tenant', + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name='bgp_routers', + to='tenancy.tenant', + ), + ), + migrations.AddField( + model_name='bgpscope', + name='tenant', + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name='bgp_scopes', + to='tenancy.tenant', + ), + ), + migrations.AddField( + model_name='bgpsessiontemplate', + name='tenant', + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name='bgp_peer_session_templates', + to='tenancy.tenant', + ), + ), + migrations.RenameField( + model_name='bgppeer', + old_name='peer', + new_name='peer_ip', + ), + migrations.AddField( + model_name='bgppeer', + name='peer', + field=models.OneToOneField( + on_delete=django.db.models.deletion.PROTECT, + related_name='peers', + to='ipam.ipaddress', + ), + ), + migrations.RunPython(set_peer, unset_peer), + migrations.RemoveField( + model_name='bgppeer', + name='peer_ip', + ), + ] diff --git a/netbox_routing/migrations/0021_community_communitylist.py b/netbox_routing/migrations/0021_community_communitylist.py new file mode 100644 index 0000000..1b81807 --- /dev/null +++ b/netbox_routing/migrations/0021_community_communitylist.py @@ -0,0 +1,111 @@ +# Generated by Django 5.2b1 on 2025-07-29 17:38 + +import django.db.models.deletion +import netbox.models.deletion +import taggit.managers +import utilities.json +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('netbox_routing', '0020_bgp_configuration'), + ] + + operations = [ + migrations.CreateModel( + name='Community', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False + ), + ), + ('created', models.DateTimeField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ( + 'custom_field_data', + models.JSONField( + blank=True, + default=dict, + encoder=utilities.json.CustomFieldJSONEncoder, + ), + ), + ('description', models.CharField(blank=True, max_length=200)), + ('comments', models.TextField(blank=True)), + ('community', models.CharField(max_length=255)), + ( + 'tags', + taggit.managers.TaggableManager( + through='extras.TaggedItem', to='extras.Tag' + ), + ), + ( + 'tenant', + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name='communities', + to='tenancy.tenant', + ), + ), + ], + options={ + 'verbose_name': 'Community', + }, + bases=(netbox.models.deletion.DeleteMixin, models.Model), + ), + migrations.CreateModel( + name='CommunityList', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False + ), + ), + ('created', models.DateTimeField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ( + 'custom_field_data', + models.JSONField( + blank=True, + default=dict, + encoder=utilities.json.CustomFieldJSONEncoder, + ), + ), + ('description', models.CharField(blank=True, max_length=200)), + ('comments', models.TextField(blank=True)), + ('name', models.CharField(max_length=255)), + ( + 'communities', + models.ManyToManyField( + related_name='community_lists', to='netbox_routing.community' + ), + ), + ( + 'tags', + taggit.managers.TaggableManager( + through='extras.TaggedItem', to='extras.Tag' + ), + ), + ( + 'tenant', + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name='community_lists', + to='tenancy.tenant', + ), + ), + ], + options={ + 'verbose_name': 'Community List', + }, + bases=(netbox.models.deletion.DeleteMixin, models.Model), + ), + ] diff --git a/netbox_routing/models/__init__.py b/netbox_routing/models/__init__.py index 716af3e..05a8315 100644 --- a/netbox_routing/models/__init__.py +++ b/netbox_routing/models/__init__.py @@ -1,25 +1,39 @@ -from .static import StaticRoute -from .ospf import OSPFArea, OSPFInstance, OSPFInterface -from .objects import PrefixList, PrefixListEntry, RouteMap, RouteMapEntry -from .bgp import BGPRouter, BGPScope, BGPAddressFamily, BGPSetting +from .bgp import * +from .communities import * from .eigrp import * +from .objects import PrefixList, PrefixListEntry, RouteMap, RouteMapEntry +from .ospf import OSPFArea, OSPFInstance, OSPFInterface +from .static import StaticRoute __all__ = ( - 'StaticRoute', - 'OSPFArea', - 'OSPFInstance', - 'OSPFInterface', + # BGP + 'BGPRouter', + 'BGPScope', + 'BGPAddressFamily', + 'BGPPeerTemplate', + 'BGPPolicyTemplate', + 'BGPSessionTemplate', + 'BGPSetting', + 'BGPPeer', + 'BGPPeerAddressFamily', + # Communities + 'Community', + 'CommunityList', + # EIGRP 'EIGRPRouter', 'EIGRPAddressFamily', 'EIGRPNetwork', 'EIGRPInterface', + # OSPF + 'OSPFArea', + 'OSPFInstance', + 'OSPFInterface', + # Objects -> Prefix Lists 'PrefixList', 'PrefixListEntry', + # Objects -> Route Maps 'RouteMap', 'RouteMapEntry', - # Not fully implemented - 'BGPRouter', - 'BGPScope', - 'BGPAddressFamily', - 'BGPSetting', + # Static Routing + 'StaticRoute', ) diff --git a/netbox_routing/models/bgp.py b/netbox_routing/models/bgp.py index 358038b..8ba1b3e 100644 --- a/netbox_routing/models/bgp.py +++ b/netbox_routing/models/bgp.py @@ -1,24 +1,17 @@ from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType from django.db import models +from django.urls import reverse from django.utils.translation import gettext as _ from netbox.models import PrimaryModel -from netbox_routing.choices.bgp import ( - BGPSettingChoices, - BGPAddressFamilyChoices, - BFDChoices, -) -from netbox_routing.constants.bgp import ( - BGPSETTING_ASSIGNMENT_MODELS, - BGPPEER_ASSIGNMENT_MODELS, - BGPPEERAF_ASSIGNMENT_MODELS, -) -from netbox_routing.fields.ip import IPAddressField +from netbox_routing.choices.bgp import * +from netbox_routing.constants.bgp import * class BGPSetting(PrimaryModel): assigned_object_type = models.ForeignKey( + verbose_name=_('Assigned Object Type'), to=ContentType, limit_choices_to=BGPSETTING_ASSIGNMENT_MODELS, on_delete=models.PROTECT, @@ -26,14 +19,19 @@ class BGPSetting(PrimaryModel): blank=True, null=True, ) - assigned_object_id = models.PositiveBigIntegerField(blank=True, null=True) + assigned_object_id = models.PositiveBigIntegerField( + verbose_name=_('Assigned Object ID'), blank=True, null=True + ) assigned_object = GenericForeignKey( ct_field='assigned_object_type', fk_field='assigned_object_id' ) key = models.CharField( + verbose_name=_('Setting Name'), choices=BGPSettingChoices, ) - value = models.CharField() + value = models.CharField( + verbose_name=_('Setting Value'), + ) class Meta: verbose_name = 'BGP Setting' @@ -42,31 +40,47 @@ def __str__(self): return f'{self.assigned_object}: {self.key}' def get_absolute_url(self): - from django.urls import reverse - return reverse('plugins:netbox_routing:bgpsetting', args=[self.pk]) class BGPRouter(PrimaryModel): device = models.ForeignKey( + verbose_name=_('Device'), to='dcim.Device', on_delete=models.PROTECT, related_name='router', - verbose_name='Device', ) asn = models.ForeignKey( + verbose_name=_('ASN'), to='ipam.ASN', on_delete=models.PROTECT, related_name='router', - verbose_name='ASN', ) settings = GenericRelation( + verbose_name=_('Settings'), to='netbox_routing.BGPSetting', related_name='router', related_query_name='router', content_type_field='assigned_object_type', object_id_field='assigned_object_id', ) + tenant = models.ForeignKey( + verbose_name=_('Tenant'), + to='tenancy.Tenant', + on_delete=models.PROTECT, + related_name='bgp_routers', + blank=True, + null=True, + ) + + clone_fields = ( + 'device', + 'asn', + ) + prerequisite_models = ( + 'dcim.Device', + 'ipam.ASN', + ) class Meta: verbose_name = 'BGP Router' @@ -75,13 +89,12 @@ def __str__(self): return f'{self.device} ({self.asn})' def get_absolute_url(self): - from django.urls import reverse - return reverse('plugins:netbox_routing:bgprouter', args=[self.pk]) class BGPScope(PrimaryModel): router = models.ForeignKey( + verbose_name=_('Router'), to=BGPRouter, on_delete=models.PROTECT, related_name='scopes', @@ -89,14 +102,24 @@ class BGPScope(PrimaryModel): null=False, ) vrf = models.ForeignKey( + verbose_name=_('VRF'), to='ipam.VRF', on_delete=models.PROTECT, related_name='scopes', blank=True, null=True, ) + tenant = models.ForeignKey( + verbose_name=_('Tenant'), + to='tenancy.Tenant', + on_delete=models.PROTECT, + related_name='bgp_scopes', + blank=True, + null=True, + ) settings = GenericRelation( + verbose_name=_('Settings'), to='netbox_routing.BGPSetting', related_name='scope', related_query_name='scope', @@ -104,6 +127,12 @@ class BGPScope(PrimaryModel): object_id_field='assigned_object_id', ) + clone_fields = ( + 'router', + 'vrf', + ) + prerequisite_models = ('netbox_routing.BGPRouter',) + class Meta: verbose_name = 'BGP Scope' @@ -111,22 +140,29 @@ def __str__(self): return f'{self.router}: {self.vrf or "Global VRF"}' def get_absolute_url(self): - from django.urls import reverse - return reverse('plugins:netbox_routing:bgpscope', args=[self.pk]) class BGPAddressFamily(PrimaryModel): scope = models.ForeignKey( + verbose_name=_('Scope'), to=BGPScope, on_delete=models.PROTECT, related_name='address_families', - verbose_name=_('BGP Scope'), ) address_family = models.CharField( - max_length=50, choices=BGPAddressFamilyChoices, verbose_name=_('PoE type') + verbose_name=_('Address Family'), max_length=50, choices=BGPAddressFamilyChoices + ) + tenant = models.ForeignKey( + verbose_name=_('Tenant'), + to='tenancy.Tenant', + on_delete=models.PROTECT, + related_name='bgp_address_families', + blank=True, + null=True, ) settings = GenericRelation( + verbose_name=_('Settings'), to='netbox_routing.BGPSetting', related_name='address_family', related_query_name='address_family', @@ -134,6 +170,12 @@ class BGPAddressFamily(PrimaryModel): object_id_field='assigned_object_id', ) + clone_fields = ( + 'scope', + 'address_family', + ) + prerequisite_models = ('netbox_routing.BGPScope',) + class Meta: verbose_name = 'BGP Address Family' @@ -141,27 +183,28 @@ def __str__(self): return f'{self.scope} ({self.get_address_family_display()})' def get_absolute_url(self): - from django.urls import reverse - return reverse('plugins:netbox_routing:bgpaddressfamily', args=[self.pk]) class BGPSessionTemplate(PrimaryModel): - name = models.CharField(verbose_name='Name', max_length=255) + name = models.CharField(verbose_name=_('Name'), max_length=255) router = models.ForeignKey( + verbose_name=_('Router'), to=BGPRouter, on_delete=models.PROTECT, related_name='session_templates', ) parent = models.ForeignKey( + verbose_name=_('Parent'), to='netbox_routing.BGPSessionTemplate', on_delete=models.PROTECT, related_name='children', blank=True, null=True, ) - enabled = models.BooleanField(blank=True, null=True) + enabled = models.BooleanField(verbose_name=_('Enabled'), blank=True, null=True) asn = models.ForeignKey( + verbose_name=_('ASN'), to='ipam.ASN', on_delete=models.PROTECT, related_name='session_templates', @@ -169,20 +212,44 @@ class BGPSessionTemplate(PrimaryModel): null=True, ) bfd = models.CharField( + verbose_name=_('BFD'), max_length=50, choices=BFDChoices, blank=True, null=True, ) password = models.CharField( + verbose_name=_('Password'), max_length=255, blank=True, null=True, ) + tenant = models.ForeignKey( + verbose_name=_('Tenant'), + to='tenancy.Tenant', + on_delete=models.PROTECT, + related_name='bgp_peer_session_templates', + blank=True, + null=True, + ) + + clone_fields = ( + 'name', + 'router', + 'parent', + 'enabled', + 'asn', + 'bfd', + 'password', + ) + prerequisite_models = ('netbox_routing.BGPRouter',) + + class Meta: + verbose_name = 'BGP Session Template' -class BGPPoliyTemplate(PrimaryModel): - name = models.CharField(verbose_name='Name', max_length=255) +class BGPPolicyTemplate(PrimaryModel): + name = models.CharField(verbose_name=_('Name'), max_length=255) router = models.ForeignKey( to=BGPRouter, on_delete=models.PROTECT, @@ -190,8 +257,14 @@ class BGPPoliyTemplate(PrimaryModel): blank=False, null=False, ) - enabled = models.BooleanField(blank=True, null=True) + parents = models.ManyToManyField( + verbose_name=_('Parent Policies'), + to='netbox_routing.BGPPolicyTemplate', + related_name='children', + ) + enabled = models.BooleanField(verbose_name=_('Enabled'), blank=True, null=True) prefixlist_out = models.ForeignKey( + verbose_name=_('Outbound Prefix List'), to='netbox_routing.PrefixList', on_delete=models.PROTECT, related_name='template_afs_out', @@ -199,6 +272,7 @@ class BGPPoliyTemplate(PrimaryModel): null=True, ) prefixlist_in = models.ForeignKey( + verbose_name=_('Inbound Prefix List'), to='netbox_routing.PrefixList', on_delete=models.PROTECT, related_name='template_afs_in', @@ -206,6 +280,7 @@ class BGPPoliyTemplate(PrimaryModel): null=True, ) routemap_out = models.ForeignKey( + verbose_name=_('Outbound Route Map'), to='netbox_routing.RouteMap', on_delete=models.PROTECT, related_name='template_afs_out', @@ -213,58 +288,169 @@ class BGPPoliyTemplate(PrimaryModel): null=True, ) routemap_in = models.ForeignKey( + verbose_name=_('Inbound Route Map'), to='netbox_routing.RouteMap', on_delete=models.PROTECT, related_name='template_afs_in', blank=True, null=True, ) + tenant = models.ForeignKey( + verbose_name=_('Tenant'), + to='tenancy.Tenant', + on_delete=models.PROTECT, + related_name='bgp_peer_policy_templates', + blank=True, + null=True, + ) + + clone_fields = ( + 'name', + 'router', + 'parents', + 'enabled', + 'prefixlist_out', + 'prefixlist_in', + 'routemap_out', + 'routemap_in', + ) + prerequisite_models = ('netbox_routing.BGPRouter',) + + class Meta: + verbose_name = 'BGP Policy Template' class BGPPeerTemplate(PrimaryModel): - name = models.CharField(verbose_name='Name', max_length=255) + name = models.CharField(verbose_name=_('Name'), max_length=255) remote_as = models.ForeignKey( - to='ipam.ASN', on_delete=models.PROTECT, related_name='+', blank=True, null=True + verbose_name=_('Remote AS'), + to='ipam.ASN', + on_delete=models.PROTECT, + related_name='+', + blank=True, + null=True, ) - enabled = models.BooleanField(blank=True, null=True) + enabled = models.BooleanField(verbose_name=_('Enabled'), blank=True, null=True) + tenant = models.ForeignKey( + verbose_name=_('Tenant'), + to='tenancy.Tenant', + on_delete=models.PROTECT, + related_name='bgp_peer_templates', + blank=True, + null=True, + ) + address_families = GenericRelation( + verbose_name=_('Address Families'), + to='netbox_routing.BGPPeerAddressFamily', + content_type_field='assigned_object_type', + object_id_field='assigned_object_id', + related_query_name='peertemplate', + ) + + clone_fields = ('name', 'remote_as', 'enabled') + + class Meta: + verbose_name = 'BGP Peer Template' class BGPPeer(PrimaryModel): - assigned_object_type = models.ForeignKey( - to=ContentType, - limit_choices_to=BGPPEER_ASSIGNMENT_MODELS, + scope = models.ForeignKey( + verbose_name=_('Scope'), + to=BGPScope, on_delete=models.PROTECT, - related_name='+', + related_name='peers', blank=True, null=True, ) - assigned_object_id = models.PositiveBigIntegerField(blank=True, null=True) - assigned_object = GenericForeignKey( - ct_field='assigned_object_type', fk_field='assigned_object_id' + peer = models.OneToOneField( + verbose_name=_('Peer Address'), + to='ipam.IPAddress', + on_delete=models.PROTECT, + related_name='peers', + blank=False, + null=False, ) - peer = IPAddressField() peer_group = models.ForeignKey( + verbose_name=_('Peer Group'), to=BGPPeerTemplate, on_delete=models.PROTECT, related_name='peers', blank=True, null=True, ) - peer_policy = models.ForeignKey( - to=BGPPoliyTemplate, + peer_session = models.ForeignKey( + verbose_name=_('Peer Session'), + to=BGPSessionTemplate, on_delete=models.PROTECT, related_name='peers', blank=True, null=True, ) remote_as = models.ForeignKey( - to='ipam.ASN', on_delete=models.PROTECT, related_name='+', blank=True, null=True + verbose_name=_('Remote AS'), + to='ipam.ASN', + on_delete=models.PROTECT, + related_name='+', + blank=True, + null=True, + ) + enabled = models.BooleanField(verbose_name=_('Enabled'), blank=True, null=True) + local_as = models.ForeignKey( + verbose_name=_('Local AS'), + to='ipam.ASN', + on_delete=models.PROTECT, + related_name='+', + blank=True, + null=True, + ) + bfd = models.BooleanField(verbose_name=_('BFD'), blank=True, null=True) + password = models.CharField( + verbose_name=_('Password'), max_length=255, blank=True, null=True + ) + tenant = models.ForeignKey( + verbose_name=_('Tenant'), + to='tenancy.Tenant', + on_delete=models.PROTECT, + related_name='bgp_peers', + blank=True, + null=True, + ) + address_families = GenericRelation( + verbose_name=_('Address Families'), + to='netbox_routing.BGPPeerAddressFamily', + content_type_field='assigned_object_type', + object_id_field='assigned_object_id', + related_query_name='peer', ) - enabled = models.BooleanField(blank=True, null=True) + + clone_fields = ( + 'scope', + 'remote_as', + 'local_as', + 'enabled', + 'bfd', + 'password', + ) + prerequisite_models = ( + 'netbox_routing.BGPRouter', + 'netbox_routing.BGPScope', + ) + + class Meta: + verbose_name = 'BGP Peer' + + def __str__(self): + return f'{self.peer} ({self.remote_as})' + + def get_absolute_url(self): + from django.urls import reverse + + return reverse('plugins:netbox_routing:bgppeer', args=[self.pk]) class BGPPeerAddressFamily(PrimaryModel): assigned_object_type = models.ForeignKey( + verbose_name=_('Assigned Object Type'), to=ContentType, limit_choices_to=BGPPEERAF_ASSIGNMENT_MODELS, on_delete=models.PROTECT, @@ -272,24 +458,30 @@ class BGPPeerAddressFamily(PrimaryModel): blank=True, null=True, ) - assigned_object_id = models.PositiveBigIntegerField(blank=True, null=True) + assigned_object_id = models.PositiveBigIntegerField( + verbose_name=_('Assigned Object ID'), blank=True, null=True + ) assigned_object = GenericForeignKey( ct_field='assigned_object_type', fk_field='assigned_object_id' ) address_family = models.ForeignKey( - to=BGPAddressFamily, on_delete=models.PROTECT, related_name='address_families' + verbose_name=_('Address Family'), + to=BGPAddressFamily, + on_delete=models.PROTECT, + related_name='address_families', ) - - peer_session = models.ForeignKey( - to=BGPSessionTemplate, + peer_policy = models.ForeignKey( + verbose_name=_('Peer Policy'), + to=BGPPolicyTemplate, on_delete=models.PROTECT, related_name='peer_afs', blank=True, null=True, ) - enabled = models.BooleanField(blank=True, null=True) + enabled = models.BooleanField(verbose_name=_('Enabled'), blank=True, null=True) prefixlist_out = models.ForeignKey( + verbose_name=_('Outbound Prefix List'), to='netbox_routing.PrefixList', on_delete=models.PROTECT, related_name='peer_afs_out', @@ -297,6 +489,7 @@ class BGPPeerAddressFamily(PrimaryModel): null=True, ) prefixlist_in = models.ForeignKey( + verbose_name=_('Inbound Prefix List'), to='netbox_routing.PrefixList', on_delete=models.PROTECT, related_name='peer_afs_in', @@ -304,6 +497,7 @@ class BGPPeerAddressFamily(PrimaryModel): null=True, ) routemap_out = models.ForeignKey( + verbose_name=_('Outbound Route Map'), to='netbox_routing.RouteMap', on_delete=models.PROTECT, related_name='peer_afs_out', @@ -311,9 +505,35 @@ class BGPPeerAddressFamily(PrimaryModel): null=True, ) routemap_in = models.ForeignKey( + verbose_name=_('Inbound Route Map'), to='netbox_routing.RouteMap', on_delete=models.PROTECT, related_name='peer_afs_in', blank=True, null=True, ) + tenant = models.ForeignKey( + verbose_name=_('Tenant'), + to='tenancy.Tenant', + on_delete=models.PROTECT, + related_name='bgp_peer_afs', + blank=True, + null=True, + ) + + clone_fields = ( + 'address_family', + 'peer_session', + 'enabled', + 'prefixlist_out', + 'prefixlist_in', + 'routemap_out', + 'routemap_in', + ) + prerequisite_models = ( + 'netbox_routing.BGPRouter', + 'netbox_routing.BGPScope', + ) + + class Meta: + verbose_name = 'BGP Peer Address Family' diff --git a/netbox_routing/models/communities.py b/netbox_routing/models/communities.py new file mode 100644 index 0000000..1192767 --- /dev/null +++ b/netbox_routing/models/communities.py @@ -0,0 +1,67 @@ +from django.db import models +from django.utils.translation import gettext as _ + +from netbox.models import PrimaryModel + + +__all__ = ( + 'CommunityList', + 'Community', +) + + +class CommunityList(PrimaryModel): + name = models.CharField( + verbose_name=_('List'), + max_length=255, + ) + communities = models.ManyToManyField( + verbose_name=_('Communities'), + to='netbox_routing.Community', + related_name='community_lists', + ) + tenant = models.ForeignKey( + verbose_name=_('Tenant'), + to='tenancy.Tenant', + on_delete=models.PROTECT, + related_name='community_lists', + null=True, + blank=True, + ) + + class Meta: + verbose_name = _('Community List') + + def __str__(self): + return f'{self.name}' + + def get_absolute_url(self): + from django.urls import reverse + + return reverse('plugins:netbox_routing:communitylist', args=[self.pk]) + + +class Community(PrimaryModel): + community = models.CharField( + verbose_name=_('Community'), + max_length=255, + ) + tenant = models.ForeignKey( + verbose_name=_('Tenant'), + to='tenancy.Tenant', + on_delete=models.PROTECT, + related_name='communities', + null=True, + blank=True, + ) + + class Meta: + verbose_name = _('Community') + + def __str__(self): + return f'{self.community}' + + def get_absolute_url(self): + from django.urls import reverse + + return reverse('plugins:netbox_routing:community', args=[self.pk]) diff --git a/netbox_routing/models/objects.py b/netbox_routing/models/objects.py index 8f25ca1..dce6e6e 100644 --- a/netbox_routing/models/objects.py +++ b/netbox_routing/models/objects.py @@ -50,7 +50,7 @@ class RouteMapEntry(PermitDenyChoiceMixin, PrimaryModel): related_name='entries', verbose_name='Route Map', ) - type = models.CharField(max_length=6, choices=PermitDenyChoices) + action = models.CharField(max_length=6, choices=PermitDenyChoices) sequence = models.PositiveSmallIntegerField() match = models.JSONField( blank=True, @@ -122,7 +122,7 @@ class PrefixListEntry(PermitDenyChoiceMixin, PrimaryModel): verbose_name='Prefix List', ) sequence = models.PositiveSmallIntegerField() - type = models.CharField(max_length=6, choices=PermitDenyChoices) + action = models.CharField(max_length=6, choices=PermitDenyChoices) prefix = IPNetworkField(help_text='IPv4 or IPv6 network with mask') ge = models.PositiveSmallIntegerField( verbose_name='GE', diff --git a/netbox_routing/tables/objects.py b/netbox_routing/tables/objects.py index 4520c4b..faaa1b4 100644 --- a/netbox_routing/tables/objects.py +++ b/netbox_routing/tables/objects.py @@ -18,13 +18,13 @@ class PrefixListEntryTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = PrefixListEntry - fields = ('pk', 'id', 'prefix_list', 'sequence', 'type', 'prefix', 'le', 'ge') + fields = ('pk', 'id', 'prefix_list', 'sequence', 'action', 'prefix', 'le', 'ge') default_columns = ( 'pk', 'id', 'prefix_list', 'sequence', - 'type', + 'action', 'prefix', 'le', 'ge', @@ -44,5 +44,5 @@ class RouteMapEntryTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = RouteMapEntry - fields = ('pk', 'id', 'route_map', 'sequence', 'type') - default_columns = ('pk', 'id', 'route_map', 'sequence', 'type') + fields = ('pk', 'id', 'route_map', 'sequence', 'action') + default_columns = ('pk', 'id', 'route_map', 'sequence', 'action') diff --git a/pyproject.toml b/pyproject.toml index e2a54c4..02a7ad4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,5 +36,5 @@ exclude=["netbox_routing.tests"] [tool.black] skip-string-normalization = 1 -check = 1 +check = 0 diff = 0 \ No newline at end of file