diff --git a/Makefile b/Makefile index 71cd4d0..0b22891 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ PYTHON_VER?=3.8 -NETBOX_VER?=v3.2.0 +NETBOX_VER?=v3.2.4 NAME=netbox-bgp diff --git a/README.md b/README.md index a3d0e60..70e4e69 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,14 @@ # NetBox BGP Plugin [Netbox](https://github.com/netbox-community/netbox) plugin for BGP related objects documentation. +## Features +This plugin provide following Models: +* AS Numbers (will be removed in 0.8.0) +* BGP Communities +* BGP Sessions +* Routing Policy +* Prefix Lists (new in 0.7.0) + ## Compatibility | | | diff --git a/netbox_bgp/api/serializers.py b/netbox_bgp/api/serializers.py index 495c678..9cc7a0a 100644 --- a/netbox_bgp/api/serializers.py +++ b/netbox_bgp/api/serializers.py @@ -11,7 +11,7 @@ from netbox_bgp.models import ( ASN, ASNStatusChoices, BGPSession, SessionStatusChoices, RoutingPolicy, BGPPeerGroup, - Community, RoutingPolicyRule + Community, RoutingPolicyRule, PrefixList, PrefixListRule ) @@ -161,6 +161,7 @@ def to_representation(self, instance): ) return ret + class NestedBGPSessionSerializer(WritableNestedSerializer): url = HyperlinkedIdentityField(view_name='plugins:netbox_bgp:bgpsession') @@ -169,6 +170,7 @@ class Meta: fields = ['id', 'url', 'name', 'description'] validators = [] + class CommunitySerializer(NetBoxModelSerializer): status = ChoiceField(choices=ASNStatusChoices, required=False) tenant = NestedTenantSerializer(required=False, allow_null=True) @@ -183,3 +185,15 @@ class RoutingPolicyRuleSerializer(NetBoxModelSerializer): class Meta: model = RoutingPolicyRule fields = '__all__' + + +class PrefixListSerializer(NetBoxModelSerializer): + class Meta: + model = PrefixList + fields = '__all__' + + +class PrefixListRuleSerializer(NetBoxModelSerializer): + class Meta: + model = PrefixListRule + fields = '__all__' diff --git a/netbox_bgp/api/urls.py b/netbox_bgp/api/urls.py index 4aea08a..0861652 100644 --- a/netbox_bgp/api/urls.py +++ b/netbox_bgp/api/urls.py @@ -2,7 +2,7 @@ from .views import ( ASNViewSet, BGPSessionViewSet, RoutingPolicyViewSet, BGPPeerGroupViewSet, - CommunityViewSet + CommunityViewSet, PrefixListViewSet ) router = routers.DefaultRouter() @@ -13,6 +13,7 @@ router.register('peer-group', BGPPeerGroupViewSet, 'peergroup') router.register('bgppeergroup', BGPPeerGroupViewSet, 'bgppeergroup') router.register('community', CommunityViewSet) +router.register('prefix-list', PrefixListViewSet) urlpatterns = router.urls diff --git a/netbox_bgp/api/views.py b/netbox_bgp/api/views.py index 7619f2a..58ff3dc 100644 --- a/netbox_bgp/api/views.py +++ b/netbox_bgp/api/views.py @@ -2,12 +2,12 @@ from .serializers import ( ASNSerializer, BGPSessionSerializer, RoutingPolicySerializer, BGPPeerGroupSerializer, - CommunitySerializer + CommunitySerializer, PrefixListSerializer ) -from netbox_bgp.models import ASN, BGPSession, RoutingPolicy, BGPPeerGroup, Community +from netbox_bgp.models import ASN, BGPSession, RoutingPolicy, BGPPeerGroup, Community, PrefixList from netbox_bgp.filters import ( ASNFilterSet, BGPSessionFilterSet, RoutingPolicyFilterSet, BGPPeerGroupFilterSet, - CommunityFilterSet + CommunityFilterSet, PrefixListFilterSet ) @@ -39,3 +39,9 @@ class CommunityViewSet(ModelViewSet): queryset = Community.objects.all() serializer_class = CommunitySerializer filterset_class = CommunityFilterSet + + +class PrefixListViewSet(ModelViewSet): + queryset = PrefixList.objects.all() + serializer_class = PrefixListSerializer + filterset_class = PrefixListFilterSet diff --git a/netbox_bgp/choices.py b/netbox_bgp/choices.py new file mode 100644 index 0000000..fcafab4 --- /dev/null +++ b/netbox_bgp/choices.py @@ -0,0 +1,73 @@ +from utilities.choices import ChoiceSet + + +class ASNStatusChoices(ChoiceSet): + + STATUS_ACTIVE = 'active' + STATUS_RESERVED = 'reserved' + STATUS_DEPRECATED = 'deprecated' + + CHOICES = ( + (STATUS_ACTIVE, 'Active', 'blue'), + (STATUS_RESERVED, 'Reserved', 'cyan'), + (STATUS_DEPRECATED, 'Deprecated', 'red'), + ) + + +class SessionStatusChoices(ChoiceSet): + + STATUS_OFFLINE = 'offline' + STATUS_ACTIVE = 'active' + STATUS_PLANNED = 'planned' + STATUS_FAILED = 'failed' + + CHOICES = ( + (STATUS_OFFLINE, 'Offline', 'orange'), + (STATUS_ACTIVE, 'Active', 'green'), + (STATUS_PLANNED, 'Planned', 'cyan'), + (STATUS_FAILED, 'Failed', 'red'), + ) + + +class ActionChoices(ChoiceSet): + + CHOICES = [ + ('permit', 'Permit', 'green'), + ('deny', 'Deny', 'red'), + ] + + +class AFISAFIChoices(ChoiceSet): + AFISAFI_IPV4_UNICAST = 'ipv4-unicast' + AFISAFI_IPV4_MULTICAST = 'ipv4-multicast' + AFISAFI_IPV4_FLOWSPEC = 'ipv4-flowspec' + + AFISAFI_IPV6_UNICAST = 'ipv6-unicast' + AFISAFI_IPV6_MULTICAST = 'ipv6-multicast' + AFISAFI_IPV6_FLOWSPEC = 'ipv6-flowspec' + + AFISAFI_L2VPN_VPLS = 'l2vpn-vpls' + AFISAFI_L2VPN_EVPN = 'l2vpn-evpn' + + AFISAFI_VPNV4_UNICAST = 'vpnv4-unicast' + AFISAFI_VPNV4_MULTICAST = 'vpnv4-multicast' + AFISAFI_VPNV4_FLOWSPEC = 'vpnv4-flowspec' + + AFISAFI_VPNV6_UNICAST = 'vpnv6-unicast' + AFISAFI_VPNV6_MULTICAST = 'vpnv6-multicast' + AFISAFI_VPNV6_FLOWSPEC = 'vpnv6-flowspec' + + CHOICES = ( + + ) + + +class IPAddressFamilyChoices(ChoiceSet): + + FAMILY_4 = 4 + FAMILY_6 = 6 + + CHOICES = ( + (FAMILY_4, 'IPv4'), + (FAMILY_6, 'IPv6'), + ) diff --git a/netbox_bgp/filters.py b/netbox_bgp/filters.py index f58ff43..3a71238 100644 --- a/netbox_bgp/filters.py +++ b/netbox_bgp/filters.py @@ -4,7 +4,7 @@ from netaddr.core import AddrFormatError from extras.filters import TagFilter -from .models import ASN, Community, BGPSession, RoutingPolicy, BGPPeerGroup +from .models import ASN, Community, BGPSession, RoutingPolicy, BGPPeerGroup, PrefixList from ipam.models import IPAddress from dcim.models import Device @@ -217,3 +217,25 @@ def search(self, queryset, name, value): | Q(description__icontains=value) ) return queryset.filter(qs_filter) + + +class PrefixListFilterSet(django_filters.FilterSet): + q = django_filters.CharFilter( + method='search', + label='Search', + ) + tag = TagFilter() + + class Meta: + model = PrefixList + fields = ['name', 'description'] + + def search(self, queryset, name, value): + """Perform the filtered search.""" + if not value.strip(): + return queryset + qs_filter = ( + Q(name__icontains=value) + | Q(description__icontains=value) + ) + return queryset.filter(qs_filter) diff --git a/netbox_bgp/forms.py b/netbox_bgp/forms.py index 9f8ca94..ea55c84 100644 --- a/netbox_bgp/forms.py +++ b/netbox_bgp/forms.py @@ -19,7 +19,9 @@ from .models import ( ASN, ASNStatusChoices, Community, BGPSession, - SessionStatusChoices, RoutingPolicy, BGPPeerGroup, RoutingPolicyRule + SessionStatusChoices, RoutingPolicy, BGPPeerGroup, + RoutingPolicyRule, PrefixList, PrefixListRule + ) @@ -421,7 +423,7 @@ class RoutingPolicyForm(NetBoxModelForm): class Meta: model = RoutingPolicy - fields = ['name', 'description'] + fields = ['name', 'description', 'tags'] class BGPPeerGroupFilterForm(NetBoxModelFilterSetForm): @@ -460,19 +462,24 @@ class Meta: class RoutingPolicyRuleForm(NetBoxModelForm): + continue_entry = forms.IntegerField( + required=False, + label='Continue', + help_text='Null for disable, 0 to next entry, or any sequence number' + ) match_community = DynamicModelMultipleChoiceField( queryset=Community.objects.all(), required=False, ) - match_ip = DynamicModelMultipleChoiceField( - queryset=Prefix.objects.all(), + match_ip_address = DynamicModelMultipleChoiceField( + queryset=PrefixList.objects.all(), required=False, - label='Match Prefix', + label='Match IP address Prefix lists', ) - match_ip_cond = forms.JSONField( - label='Match filtered prefixes', - help_text='Filter for Prefixes, e.g., {"site__name": "site1", "tenant__name": "tenant1"}', + match_ipv6_address = DynamicModelMultipleChoiceField( + queryset=PrefixList.objects.all(), required=False, + label='Match IPv6 address Prefix lists', ) match_custom = forms.JSONField( label='Custom Match', @@ -488,7 +495,57 @@ class RoutingPolicyRuleForm(NetBoxModelForm): class Meta: model = RoutingPolicyRule fields = [ - 'routing_policy', 'index', 'action', 'match_community', - 'match_ip', 'match_ip_cond', 'match_custom', + 'routing_policy', 'index', 'action', 'continue_entry', 'match_community', + 'match_ip_address', 'match_ipv6_address', 'match_custom', 'set_actions', 'description', ] + + +class PrefixListFilterForm(NetBoxModelFilterSetForm): + model = PrefixList + q = forms.CharField( + required=False, + label='Search' + ) + + tag = TagFilterField(model) + + +class PrefixListForm(NetBoxModelForm): + tags = DynamicModelMultipleChoiceField( + queryset=Tag.objects.all(), + required=False + ) + + class Meta: + model = PrefixList + fields = ['name', 'description', 'tags'] + + +class PrefixListRuleForm(NetBoxModelForm): + prefix = DynamicModelChoiceField( + queryset=Prefix.objects.all(), + required=False, + help_text='NetBox Prefix Object', + ) + prefix_custom = IPNetworkFormField( + required=False, + label='Prefix', + help_text='Just IP field for define special prefix like 0.0.0.0/0', + ) + ge = forms.IntegerField( + label='Greater than or equal to', + required=False, + ) + le = forms.IntegerField( + label='Less than or equal to', + required=False, + ) + + class Meta: + model = PrefixListRule + fields = [ + 'prefix_list', 'index', + 'action', 'prefix', 'prefix_custom', + 'ge', 'le' + ] diff --git a/netbox_bgp/migrations/0023_netbox_bgp.py b/netbox_bgp/migrations/0023_netbox_bgp.py new file mode 100644 index 0000000..ff9e063 --- /dev/null +++ b/netbox_bgp/migrations/0023_netbox_bgp.py @@ -0,0 +1,58 @@ +# Generated by Django 4.0.4 on 2022-08-18 13:47 + +import django.core.serializers.json +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion +import ipam.fields +import taggit.managers + + +class Migration(migrations.Migration): + + dependencies = [ + ('extras', '0073_journalentry_tags_custom_fields'), + ('ipam', '0057_created_datetimefield'), + ('netbox_bgp', '0022_netbox_bgp'), + ] + + operations = [ + migrations.CreateModel( + name='PrefixList', + 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=django.core.serializers.json.DjangoJSONEncoder)), + ('name', models.CharField(max_length=100)), + ('description', models.CharField(blank=True, max_length=200)), + ('family', models.CharField(max_length=10)), + ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')), + ], + options={ + 'verbose_name_plural': 'Prefix Lists', + 'unique_together': {('name', 'description', 'family')}, + }, + ), + migrations.CreateModel( + name='PrefixListRule', + 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=django.core.serializers.json.DjangoJSONEncoder)), + ('index', models.PositiveIntegerField()), + ('action', models.CharField(max_length=30)), + ('prefix_custom', ipam.fields.IPNetworkField(blank=True, null=True)), + ('ge', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(128)])), + ('le', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(128)])), + ('prefix', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='ipam.prefix')), + ('prefix_list', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='prefrules', to='netbox_bgp.prefixlist')), + ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')), + ], + options={ + 'ordering': ('prefix_list', 'index'), + 'unique_together': {('prefix_list', 'index')}, + }, + ), + ] diff --git a/netbox_bgp/migrations/0024_netbox_bgp.py b/netbox_bgp/migrations/0024_netbox_bgp.py new file mode 100644 index 0000000..42ef68e --- /dev/null +++ b/netbox_bgp/migrations/0024_netbox_bgp.py @@ -0,0 +1,36 @@ +# Generated by Django 4.0.4 on 2022-08-19 06:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('netbox_bgp', '0023_netbox_bgp'), + ] + + operations = [ + migrations.RemoveField( + model_name='routingpolicyrule', + name='match_ip', + ), + migrations.RemoveField( + model_name='routingpolicyrule', + name='match_ip_cond', + ), + migrations.AddField( + model_name='routingpolicyrule', + name='continue_entry', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='routingpolicyrule', + name='match_ip_address', + field=models.ManyToManyField(blank=True, related_name='+', to='netbox_bgp.prefixlist'), + ), + migrations.AddField( + model_name='routingpolicyrule', + name='match_ipv6_address', + field=models.ManyToManyField(blank=True, related_name='+', to='netbox_bgp.prefixlist'), + ), + ] diff --git a/netbox_bgp/migrations/0025_netbox_bgp.py b/netbox_bgp/migrations/0025_netbox_bgp.py new file mode 100644 index 0000000..9f07a15 --- /dev/null +++ b/netbox_bgp/migrations/0025_netbox_bgp.py @@ -0,0 +1,23 @@ +# Generated by Django 4.0.4 on 2022-08-19 11:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('netbox_bgp', '0024_netbox_bgp'), + ] + + operations = [ + migrations.AlterField( + model_name='routingpolicyrule', + name='match_ip_address', + field=models.ManyToManyField(blank=True, related_name='plrules', to='netbox_bgp.prefixlist'), + ), + migrations.AlterField( + model_name='routingpolicyrule', + name='match_ipv6_address', + field=models.ManyToManyField(blank=True, related_name='plrules6', to='netbox_bgp.prefixlist'), + ), + ] diff --git a/netbox_bgp/models.py b/netbox_bgp/models.py index cc592e4..a512502 100644 --- a/netbox_bgp/models.py +++ b/netbox_bgp/models.py @@ -1,51 +1,17 @@ from django.urls import reverse from django.db import models from django.core.validators import MaxValueValidator, MinValueValidator, RegexValidator -from django.core.exceptions import FieldError +from django.core.exceptions import FieldError, ValidationError from django.conf import settings from taggit.managers import TaggableManager -from utilities.choices import ChoiceSet from netbox.models import NetBoxModel from netbox.models.features import ChangeLoggingMixin +from ipam.fields import IPNetworkField from ipam.models import Prefix - -class ASNStatusChoices(ChoiceSet): - - STATUS_ACTIVE = 'active' - STATUS_RESERVED = 'reserved' - STATUS_DEPRECATED = 'deprecated' - - CHOICES = ( - (STATUS_ACTIVE, 'Active', 'blue'), - (STATUS_RESERVED, 'Reserved', 'cyan'), - (STATUS_DEPRECATED, 'Deprecated', 'red'), - ) - - -class SessionStatusChoices(ChoiceSet): - - STATUS_OFFLINE = 'offline' - STATUS_ACTIVE = 'active' - STATUS_PLANNED = 'planned' - STATUS_FAILED = 'failed' - - CHOICES = ( - (STATUS_OFFLINE, 'Offline', 'orange'), - (STATUS_ACTIVE, 'Active', 'green'), - (STATUS_PLANNED, 'Planned', 'cyan'), - (STATUS_FAILED, 'Failed', 'red'), - ) - - -class ActionChoices(ChoiceSet): - - CHOICES = [ - ('permit', 'Permit', 'green'), - ('deny', 'Deny', 'red'), - ] +from .choices import IPAddressFamilyChoices, ASNStatusChoices, SessionStatusChoices, ActionChoices class ASNGroup(ChangeLoggingMixin, models.Model): @@ -318,6 +284,98 @@ def get_absolute_url(self): return reverse('plugins:netbox_bgp:bgpsession', args=[self.pk]) +class PrefixList(NetBoxModel): + """ + """ + name = models.CharField( + max_length=100 + ) + description = models.CharField( + max_length=200, + blank=True + ) + family = models.CharField( + max_length=10, + choices=IPAddressFamilyChoices + ) + + class Meta: + verbose_name_plural = 'Prefix Lists' + unique_together = ['name', 'description', 'family'] + + def __str__(self): + return self.name + + def get_absolute_url(self): + return reverse('plugins:netbox_bgp:prefixlist', args=[self.pk]) + + +class PrefixListRule(NetBoxModel): + """ + """ + prefix_list = models.ForeignKey( + to=PrefixList, + on_delete=models.CASCADE, + related_name='prefrules' + ) + index = models.PositiveIntegerField() + action = models.CharField( + max_length=30, + choices=ActionChoices + ) + prefix = models.ForeignKey( + to='ipam.Prefix', + blank=True, + null=True, + related_name='+', + on_delete=models.CASCADE, + ) + prefix_custom = IPNetworkField( + blank=True, + null=True, + ) + ge = models.PositiveSmallIntegerField( + blank=True, + null=True, + validators=[MinValueValidator(0), MaxValueValidator(128)] + ) + le = models.PositiveSmallIntegerField( + blank=True, + null=True, + validators=[MinValueValidator(0), MaxValueValidator(128)] + ) + + class Meta: + ordering = ('prefix_list', 'index') + unique_together = ('prefix_list', 'index') + + @property + def network(self): + return self.prefix_custom or self.prefix + + def __str__(self): + return f'{self.prefix_list}: Rule {self.index}' + + def get_absolute_url(self): + return reverse('plugins:netbox_bgp:prefixlistrule', args=[self.pk]) + + def get_action_color(self): + return ActionChoices.colors.get(self.action) + + def clean(self): + super().clean() + # make sure that only one field is setted + if self.prefix and self.prefix_custom: + raise ValidationError( + {'prefix': 'Cannot set both fields'} + ) + # at least one fields must be setted + if self.prefix is None and self.prefix_custom is None: + raise ValidationError( + {'prefix': 'Cannot set both fields to Null'} + ) + + class RoutingPolicyRule(NetBoxModel): routing_policy = models.ForeignKey( to=RoutingPolicy, @@ -333,19 +391,24 @@ class RoutingPolicyRule(NetBoxModel): max_length=500, blank=True ) + continue_entry = models.PositiveIntegerField( + blank=True, + null=True + ) match_community = models.ManyToManyField( to=Community, blank=True, related_name='+' ) - match_ip = models.ManyToManyField( - to='ipam.Prefix', + match_ip_address = models.ManyToManyField( + to=PrefixList, blank=True, - related_name='+', + related_name='plrules', ) - match_ip_cond = models.JSONField( + match_ipv6_address = models.ManyToManyField( + to=PrefixList, blank=True, - null=True, + related_name='plrules6', ) match_custom = models.JSONField( blank=True, @@ -367,16 +430,7 @@ def get_absolute_url(self): return reverse('plugins:netbox_bgp:routingpolicyrule', args=[self.pk]) def get_action_color(self): - return ActionChoices.colors.get(self.action) - - def get_ip_conditions(self): - queryset = Prefix.objects.none() - if self.match_ip_cond and self.match_ip_cond != {}: - try: - queryset = Prefix.objects.filter(**self.match_ip_cond) - except FieldError: - pass - return queryset + return ActionChoices.colors.get(self.action) def get_match_custom(self): # some kind of ckeck? @@ -393,14 +447,17 @@ def match_statements(self): {'community': list(self.match_community.all().values_list('value', flat=True))} ) result.update( - {'ip address': [str(prefix) for prefix in self.match_ip.all().values_list('prefix', flat=True)]} + {'ip address': [str(prefix_list) for prefix_list in self.match_ip_address.all().values_list('name', flat=True)]} + ) + result.update( + {'ipv6 address': [str(prefix_list) for prefix_list in self.match_ipv6_address.all().values_list('name', flat=True)]} ) - matched_ip = self.get_ip_conditions() - result['ip address'].extend([str(prefix) for prefix in matched_ip.values_list('prefix', flat=True)]) + custom_match = self.get_match_custom() # update community from custom result['community'].extend(custom_match.get('community', [])) result['ip address'].extend(custom_match.get('ip address', [])) + result['ipv6 address'].extend(custom_match.get('ipv6 address', [])) # remove empty matches result = {k: v for k, v in result.items() if v} return result diff --git a/netbox_bgp/navigation.py b/netbox_bgp/navigation.py index 712e2bf..f1839bd 100644 --- a/netbox_bgp/navigation.py +++ b/netbox_bgp/navigation.py @@ -58,6 +58,20 @@ ), ), ), + PluginMenuItem( + link='plugins:netbox_bgp:prefixlist_list', + link_text='Prefix Lists', + permissions=['netbox_bgp.view_prefixlist'], + buttons=( + PluginMenuButton( + link='plugins:netbox_bgp:prefixlist_add', + title='Prefix Lists', + icon_class='mdi mdi-plus-thick', + color=ButtonColorChoices.GREEN, + permissions=['netbox_bgp.add_prefixlist'], + ), + ), + ), PluginMenuItem( link='plugins:netbox_bgp:bgppeergroup_list', link_text='Peer Groups', diff --git a/netbox_bgp/tables.py b/netbox_bgp/tables.py index 509c4ee..6fe3c93 100644 --- a/netbox_bgp/tables.py +++ b/netbox_bgp/tables.py @@ -5,7 +5,7 @@ from netbox.tables import NetBoxTable from netbox.tables.columns import ChoiceFieldColumn, TagColumn -from .models import ASN, Community, BGPSession, RoutingPolicy, BGPPeerGroup, RoutingPolicyRule +from .models import ASN, Community, BGPSession, RoutingPolicy, BGPPeerGroup, RoutingPolicyRule, PrefixList, PrefixListRule AVAILABLE_LABEL = mark_safe('Available') COL_TENANT = """ @@ -138,3 +138,32 @@ class Meta(NetBoxTable.Meta): 'pk', 'routing_policy', 'index', 'match_statements', 'set_statements', 'action', 'description' ) + + +class PrefixListTable(NetBoxTable): + name = tables.LinkColumn() + + class Meta(NetBoxTable.Meta): + model = PrefixList + fields = ('pk', 'name', 'description') + + +class PrefixListRuleTable(NetBoxTable): + prefix_list = tables.Column( + linkify=True + ) + index = tables.Column( + linkify=True + ) + action = ChoiceFieldColumn() + network = tables.Column( + verbose_name='Prefix', + linkify=True, + ) + + class Meta(NetBoxTable.Meta): + model = PrefixListRule + fields = ( + 'pk', 'prefix_list', 'index', + 'action', 'network', 'ge', 'le' + ) diff --git a/netbox_bgp/templates/netbox_bgp/asn_list.html b/netbox_bgp/templates/netbox_bgp/asn_list.html new file mode 100644 index 0000000..98154c2 --- /dev/null +++ b/netbox_bgp/templates/netbox_bgp/asn_list.html @@ -0,0 +1,17 @@ +{% extends 'generic/object_list.html' %} + +{% block subtitle %} + +{% endblock %} +{% block extra_controls %} +
+ {% csrf_token %} + + +
+{% endblock %} \ No newline at end of file diff --git a/netbox_bgp/templates/netbox_bgp/asn_migrate.html b/netbox_bgp/templates/netbox_bgp/asn_migrate.html new file mode 100644 index 0000000..2c511d4 --- /dev/null +++ b/netbox_bgp/templates/netbox_bgp/asn_migrate.html @@ -0,0 +1,35 @@ +{% extends 'base/layout.html' %} +{% load helpers %} +{% load render_table from django_tables2 %} + + +{% block title %}Migrate {{ table.rows|length }} ASNs?{% endblock %} + +{% block content %} +
+ +
+
+
+ {% render_table table 'inc/table.html' %} +
+
+
+ {% csrf_token %} + {% for field in form.hidden_fields %} + {{ field }} + {% endfor %} +
+ + Cancel +
+
+
+
+{% endblock content %} \ No newline at end of file diff --git a/netbox_bgp/templates/netbox_bgp/bgpsession_list.html b/netbox_bgp/templates/netbox_bgp/bgpsession_list.html new file mode 100644 index 0000000..98154c2 --- /dev/null +++ b/netbox_bgp/templates/netbox_bgp/bgpsession_list.html @@ -0,0 +1,17 @@ +{% extends 'generic/object_list.html' %} + +{% block subtitle %} + +{% endblock %} +{% block extra_controls %} +
+ {% csrf_token %} + + +
+{% endblock %} \ No newline at end of file diff --git a/netbox_bgp/templates/netbox_bgp/community.html b/netbox_bgp/templates/netbox_bgp/community.html index 38806a7..57eb450 100644 --- a/netbox_bgp/templates/netbox_bgp/community.html +++ b/netbox_bgp/templates/netbox_bgp/community.html @@ -5,7 +5,7 @@ {% load plugins %} {% block breadcrumbs %} - + {% endblock %} {% block controls %} diff --git a/netbox_bgp/templates/netbox_bgp/prefixlist.html b/netbox_bgp/templates/netbox_bgp/prefixlist.html new file mode 100644 index 0000000..03598d5 --- /dev/null +++ b/netbox_bgp/templates/netbox_bgp/prefixlist.html @@ -0,0 +1,94 @@ +{% extends 'generic/object.html' %} +{% load buttons %} +{% load custom_links %} +{% load helpers %} +{% load plugins %} +{% load render_table from django_tables2 %} + +{% block breadcrumbs %} + +{% endblock %} +{% block controls %} +
+ {% if perms.netbox_bgp.change_prefixlist %} + + Rule + + {% endif %} + {% if perms.netbox_bgp.change_policy %} + + Edit + + {% endif %} + {% if perms.netbox_bgp.delete_policy %} + + Delete + + {% endif %} +
+{% endblock controls %} +{% block tabs %} + +{% endblock tabs %} + +{% block content %} +
+
+
+
+ Prefix List +
+
+ + + + + + + + + +
name{{ object.name }}
Description{{ object.description|placeholder }}
+
+
+ {% include 'inc/panels/custom_fields.html' %} + {% include 'inc/panels/tags.html' %} + {% plugin_left_page object %} +
+
+
+
+ Related Routing Policy Rules +
+
+ {% render_table rprules_table 'inc/table.html' %} +
+ {% plugin_right_page object %} +
+
+
+
+
+
+
+ Rules +
+
+ {% render_table rules_table 'inc/table.html' %} +
+ {% plugin_full_width_page object %} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/netbox_bgp/templates/netbox_bgp/prefixlistrule.html b/netbox_bgp/templates/netbox_bgp/prefixlistrule.html new file mode 100644 index 0000000..ca47b67 --- /dev/null +++ b/netbox_bgp/templates/netbox_bgp/prefixlistrule.html @@ -0,0 +1,55 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load static %} + +{% block content %} +
+
+
+
Prefix List Rule
+
+ + + + + + + + + + + + + + + + {% if object.network.get_absolute_url %} + + {% else %} + + {% endif %} + + {% if object.ge %} + + + + + {% endif %} + {% if object.le %} + + + + + {% endif %} +
Prefix List + {{ object.prefix_list }} +
Index{{ object.index }}
Action{% badge object.get_action_display bg_color=object.get_action_color %}
Prefix + {{ object.network }} + {{ object.network|placeholder }}
Greater than or equal to{{ object.ge|placeholder }}
Less than or equal to{{ object.le|placeholder }}
+
+
+ {% include 'inc/panels/custom_fields.html' %} + {% include 'inc/panels/tags.html' %} +
+
+{% endblock content %} \ No newline at end of file diff --git a/netbox_bgp/urls.py b/netbox_bgp/urls.py index b32ac79..d05e2ec 100644 --- a/netbox_bgp/urls.py +++ b/netbox_bgp/urls.py @@ -1,68 +1,72 @@ from django.urls import path from netbox.views.generic import ObjectChangeLogView -from .models import ASN, BGPSession, Community, RoutingPolicy, BGPPeerGroup, RoutingPolicyRule +from .models import ASN, BGPSession, Community, RoutingPolicy, BGPPeerGroup, RoutingPolicyRule, PrefixList, PrefixListRule -from .views import ( - ASNListView, ASNView, ASNBulkDeleteView, ASNEditView, ASNBulkEditView, - ASNDeleteView, CommunityListView, CommunityEditView, CommunityView, - CommunityBulkEditView, CommunityBulkDeleteView, CommunityDeleteView, - BGPSessionListView, BGPSessionEditView, BGPSessionBulkDeleteView, - BGPSessionView, BGPSessionDeleteView, BGPSessionAddView, - RoutingPolicyListView, RoutingPolicyEditView, RoutingPolicyBulkDeleteView, - RoutingPolicyView, RoutingPolicyDeleteView, BGPPeerGroupListView, - BGPPeerGroupEditView, BGPPeerGroupBulkDeleteView, BGPPeerGroupView, - BGPPeerGroupDeleteView, RoutingPolicyRuleEditView, RoutingPolicyRuleDeleteView, - RoutingPolicyRuleView, RoutingPolicyRuleListView -) +from . import views urlpatterns = [ - path('asn/', ASNListView.as_view(), name='asn_list'), - path('asn/add/', ASNEditView.as_view(), name='asn_add'), - path('asn/edit/', ASNBulkEditView.as_view(), name='asn_bulk_edit'), - path('asn/delete/', ASNBulkDeleteView.as_view(), name='asn_bulk_delete'), - path('asn//', ASNView.as_view(), name='asn'), - path('asn//edit/', ASNEditView.as_view(), name='asn_edit'), - path('asn//delete/', ASNDeleteView.as_view(), name='asn_delete'), + path('asn/', views.ASNListView.as_view(), name='asn_list'), + path('asn/add/', views.ASNEditView.as_view(), name='asn_add'), + path('asn/edit/', views.ASNBulkEditView.as_view(), name='asn_bulk_edit'), + path('asn/delete/', views.ASNBulkDeleteView.as_view(), name='asn_bulk_delete'), + path('asn//', views.ASNView.as_view(), name='asn'), + path('asn//edit/', views.ASNEditView.as_view(), name='asn_edit'), + path('asn//delete/', views.ASNDeleteView.as_view(), name='asn_delete'), path('asn//changelog/', ObjectChangeLogView.as_view(), name='asn_changelog', kwargs={'model': ASN}), + path('asn/migrate/', views.ASNMigrateView.as_view(), name='asn_migrate'), # Community - path('community/', CommunityListView.as_view(), name='community_list'), - path('community/add/', CommunityEditView.as_view(), name='community_add'), - path('community/edit/', CommunityBulkEditView.as_view(), name='community_bulk_edit'), - path('community/delete/', CommunityBulkDeleteView.as_view(), name='community_bulk_delete'), - path('community//', CommunityView.as_view(), name='community'), - path('community//edit/', CommunityEditView.as_view(), name='community_edit'), - path('community//delete/', CommunityDeleteView.as_view(), name='community_delete'), + path('community/', views.CommunityListView.as_view(), name='community_list'), + path('community/add/', views.CommunityEditView.as_view(), name='community_add'), + path('community/edit/', views.CommunityBulkEditView.as_view(), name='community_bulk_edit'), + path('community/delete/', views.CommunityBulkDeleteView.as_view(), name='community_bulk_delete'), + path('community//', views.CommunityView.as_view(), name='community'), + path('community//edit/', views.CommunityEditView.as_view(), name='community_edit'), + path('community//delete/', views.CommunityDeleteView.as_view(), name='community_delete'), path('community//changelog/', ObjectChangeLogView.as_view(), name='community_changelog', kwargs={'model': Community}), # Sessions - path('session/', BGPSessionListView.as_view(), name='bgpsession_list'), - path('session/add/', BGPSessionAddView.as_view(), name='bgpsession_add'), - path('session/delete/', BGPSessionBulkDeleteView.as_view(), name='bgpsession_bulk_delete'), - path('session//', BGPSessionView.as_view(), name='bgpsession'), - path('session//edit/', BGPSessionEditView.as_view(), name='bgpsession_edit'), - path('session//delete/', BGPSessionDeleteView.as_view(), name='bgpsession_delete'), + path('session/', views.BGPSessionListView.as_view(), name='bgpsession_list'), + path('session/add/', views.BGPSessionAddView.as_view(), name='bgpsession_add'), + path('session/delete/', views.BGPSessionBulkDeleteView.as_view(), name='bgpsession_bulk_delete'), + path('session//', views.BGPSessionView.as_view(), name='bgpsession'), + path('session//edit/', views.BGPSessionEditView.as_view(), name='bgpsession_edit'), + path('session//delete/', views.BGPSessionDeleteView.as_view(), name='bgpsession_delete'), path('session//changelog/', ObjectChangeLogView.as_view(), name='bgpsession_changelog', kwargs={'model': BGPSession}), # Routing Policies - - path('routing-policy/', RoutingPolicyListView.as_view(), name='routingpolicy_list'), - path('routing-policy/add/', RoutingPolicyEditView.as_view(), name='routingpolicy_add'), - path('routing-policy/delete/', RoutingPolicyBulkDeleteView.as_view(), name='routingpolicy_bulk_delete'), - path('routing-policy//', RoutingPolicyView.as_view(), name='routingpolicy'), - path('routing-policy//edit/', RoutingPolicyEditView.as_view(), name='routingpolicy_edit'), - path('routing-policy//delete/', RoutingPolicyDeleteView.as_view(), name='routingpolicy_delete'), + path('routing-policy/', views.RoutingPolicyListView.as_view(), name='routingpolicy_list'), + path('routing-policy/add/', views.RoutingPolicyEditView.as_view(), name='routingpolicy_add'), + path('routing-policy/delete/', views.RoutingPolicyBulkDeleteView.as_view(), name='routingpolicy_bulk_delete'), + path('routing-policy//', views.RoutingPolicyView.as_view(), name='routingpolicy'), + path('routing-policy//edit/', views.RoutingPolicyEditView.as_view(), name='routingpolicy_edit'), + path('routing-policy//delete/', views.RoutingPolicyDeleteView.as_view(), name='routingpolicy_delete'), path('routing-policy//changelog/', ObjectChangeLogView.as_view(), name='routingpolicy_changelog', kwargs={'model': RoutingPolicy}), # Peer Groups - path('peer-group/', BGPPeerGroupListView.as_view(), name='bgppeergroup_list'), - path('peer-group/add/', BGPPeerGroupEditView.as_view(), name='bgppeergroup_add'), - path('peer-group/delete/', BGPPeerGroupBulkDeleteView.as_view(), name='bgppeergroup_bulk_delete'), - path('peer-group//', BGPPeerGroupView.as_view(), name='bgppeergroup'), - path('peer-group//edit/', BGPPeerGroupEditView.as_view(), name='bgppeergroup_edit'), - path('peer-group//delete/', BGPPeerGroupDeleteView.as_view(), name='bgppeergroup_delete'), + path('peer-group/', views.BGPPeerGroupListView.as_view(), name='bgppeergroup_list'), + path('peer-group/add/', views.BGPPeerGroupEditView.as_view(), name='bgppeergroup_add'), + path('peer-group/delete/', views.BGPPeerGroupBulkDeleteView.as_view(), name='bgppeergroup_bulk_delete'), + path('peer-group//', views.BGPPeerGroupView.as_view(), name='bgppeergroup'), + path('peer-group//edit/', views.BGPPeerGroupEditView.as_view(), name='bgppeergroup_edit'), + path('peer-group//delete/', views.BGPPeerGroupDeleteView.as_view(), name='bgppeergroup_delete'), path('peer-group//changelog/', ObjectChangeLogView.as_view(), name='bgppeergroup_changelog', kwargs={'model': BGPPeerGroup}), # Routing Policy Rules - path('routing-policy-rule/', RoutingPolicyRuleListView.as_view(), name='routingpolicyrule_list'), - path('routing-policy-rule/add/', RoutingPolicyRuleEditView.as_view(), name='routingpolicyrule_add'), - path('routing-policy-rule//', RoutingPolicyRuleView.as_view(), name='routingpolicyrule'), - path('routing-policy-rule//edit/', RoutingPolicyRuleEditView.as_view(), name='routingpolicyrule_edit'), - path('routing-policy-rule//delete/', RoutingPolicyRuleDeleteView.as_view(), name='routingpolicyrule_delete'), + path('routing-policy-rule/', views.RoutingPolicyRuleListView.as_view(), name='routingpolicyrule_list'), + path('routing-policy-rule/add/', views.RoutingPolicyRuleEditView.as_view(), name='routingpolicyrule_add'), + path('routing-policy-rule//', views.RoutingPolicyRuleView.as_view(), name='routingpolicyrule'), + path('routing-policy-rule//edit/', views.RoutingPolicyRuleEditView.as_view(), name='routingpolicyrule_edit'), + path('routing-policy-rule//delete/', views.RoutingPolicyRuleDeleteView.as_view(), name='routingpolicyrule_delete'), path('routing-policy-rule//changelog/', ObjectChangeLogView.as_view(), name='routingpolicyrule_changelog', kwargs={'model': RoutingPolicyRule}), + # Prefix Lists + path('prefix-list/', views.PrefixListListView.as_view(), name='prefixlist_list'), + path('prefix-list/add/', views.PrefixListEditView.as_view(), name='prefixlist_add'), + path('prefix-list/delete/', views.PrefixListBulkDeleteView.as_view(), name='prefixlist_bulk_delete'), + path('prefix-list//', views.PrefixListView.as_view(), name='prefixlist'), + path('prefix-list//edit/', views.PrefixListEditView.as_view(), name='prefixlist_edit'), + path('prefix-list//delete/', views.PrefixListDeleteView.as_view(), name='prefixlist_delete'), + path('prefix-list//changelog/', ObjectChangeLogView.as_view(), name='prefixlist_changelog', kwargs={'model': PrefixList}), + # Prefix List Rules + path('prefix-list-rule/', views.PrefixListRuleListView.as_view(), name='prefixlistrule_list'), + path('prefix-list-rule/add/', views.PrefixListRuleEditView.as_view(), name='prefixlistrule_add'), + path('prefix-list-rule//', views.PrefixListRuleView.as_view(), name='prefixlistrule'), + path('prefix-list-rule//edit/', views.PrefixListRuleEditView.as_view(), name='prefixlistrule_edit'), + path('prefix-list-rule//delete/', views.PrefixListRuleDeleteView.as_view(), name='prefixlistrule_delete'), + path('prefix-list-rule//changelog/', ObjectChangeLogView.as_view(), name='prefixlistrule_changelog', kwargs={'model': PrefixListRule}), ] diff --git a/netbox_bgp/version.py b/netbox_bgp/version.py index 22049ab..49e0fc1 100644 --- a/netbox_bgp/version.py +++ b/netbox_bgp/version.py @@ -1 +1 @@ -__version__ = "0.6.2" +__version__ = "0.7.0" diff --git a/netbox_bgp/views.py b/netbox_bgp/views.py index d7abb73..379219e 100644 --- a/netbox_bgp/views.py +++ b/netbox_bgp/views.py @@ -1,29 +1,32 @@ + from django.db.models import Q +from django.contrib import messages +from django.shortcuts import get_object_or_404, redirect, render, reverse +from django.utils.text import slugify from netbox.views import generic +from ipam.models import RIR +from ipam.models import ASN as CoreASN -from .filters import ( - ASNFilterSet, CommunityFilterSet, BGPSessionFilterSet, - RoutingPolicyFilterSet, BGPPeerGroupFilterSet -) -from .models import ASN, Community, BGPSession, RoutingPolicy, BGPPeerGroup, RoutingPolicyRule -from .tables import ( - ASNTable, CommunityTable, BGPSessionTable, RoutingPolicyTable, BGPPeerGroupTable, RoutingPolicyRuleTable -) -from .forms import ( - ASNFilterForm, ASNBulkEditForm, ASNForm, CommunityForm, - CommunityFilterForm, CommunityBulkEditForm, BGPSessionForm, - BGPSessionFilterForm, BGPSessionAddForm, RoutingPolicyFilterForm, - RoutingPolicyForm, BGPPeerGroupFilterForm, BGPPeerGroupForm, RoutingPolicyRuleForm +from .models import ( + ASN, Community, BGPSession, RoutingPolicy, + BGPPeerGroup, RoutingPolicyRule, PrefixList, + PrefixListRule ) +from . import forms, tables, filters + + +# ASN + class ASNListView(generic.ObjectListView): queryset = ASN.objects.all() - filterset = ASNFilterSet - filterset_form = ASNFilterForm - table = ASNTable + filterset = filters.ASNFilterSet + filterset_form = forms.ASNFilterForm + table = tables.ASNTable action_buttons = ('add',) + template_name = 'netbox_bgp/asn_list.html' class ASNView(generic.ObjectView): @@ -32,7 +35,7 @@ class ASNView(generic.ObjectView): def get_extra_context(self, request, instance): sess = BGPSession.objects.filter(remote_as=instance) | BGPSession.objects.filter(local_as=instance) - sess_table = BGPSessionTable(sess) + sess_table = tables.BGPSessionTable(sess) return { 'related_session_table': sess_table } @@ -40,30 +43,112 @@ def get_extra_context(self, request, instance): class ASNEditView(generic.ObjectEditView): queryset = ASN.objects.all() - form = ASNForm + form = forms.ASNForm class ASNBulkDeleteView(generic.BulkDeleteView): queryset = ASN.objects.all() - table = ASNTable + table = tables.ASNTable class ASNBulkEditView(generic.BulkEditView): queryset = ASN.objects.all() - filterset = ASNFilterSet - table = ASNTable - form = ASNBulkEditForm + filterset = filters.ASNFilterSet + table = tables.ASNTable + form = forms.ASNBulkEditForm class ASNDeleteView(generic.ObjectDeleteView): queryset = ASN.objects.all() +class ASNMigrateView(generic.BulkDeleteView): + asn_to_rir = { + (64496, 64511): 'RFC5398', + (64512, 65534): 'RFC6996', + (65536, 65551): 'RFC5398', + (4200000000, 4294967294): 'RFC6996', + } + queryset = ASN.objects.all() + table = tables.ASNTable + template_name = 'netbox_bgp/asn_migrate.html' + + def post(self, request, **kwargs): + model = self.queryset.model + + if request.POST.get('_all'): + qs = model.objects.all() + if self.filterset is not None: + qs = self.filterset(request.GET, qs).qs + pk_list = qs.only('pk').values_list('pk', flat=True) + else: + pk_list = [int(pk) for pk in request.POST.getlist('pk')] + + form_cls = self.get_form() + + if '_confirm' in request.POST: + form = form_cls(request.POST) + if form.is_valid(): + + # Delete objects + queryset = self.queryset.filter(pk__in=pk_list) + deleted_count = queryset.count() + for asn in queryset: + rir_name = 'Default' + # get rir name + for k, v in self.asn_to_rir.items(): + if asn.number >= k[0] and asn.number <= k[1]: + rir_name = v + break + rir, _ = RIR.objects.get_or_create(name=rir_name, slug=slugify(rir_name)) + try: + new_asn, new_asn_created = CoreASN.objects.get_or_create( + asn=asn.number, + description=asn.description, + tenant=asn.tenant, + rir=rir, + custom_field_data=asn.custom_field_data + ) + if new_asn_created: + # update tags + new_asn.tags.set(asn.tags.all()) + new_asn.save() + else: + deleted_count -= 1 + except Exception: + deleted_count -= 1 + + msg = f"Migrated {deleted_count} {model._meta.verbose_name_plural}" + messages.success(request, msg) + return redirect(reverse('ipam:asn_list')) + + else: + form = form_cls(initial={ + 'pk': pk_list, + 'return_url': self.get_return_url(request), + }) + + # Retrieve objects being deleted + table = self.table(self.queryset.filter(pk__in=pk_list), orderable=False) + if not table.rows: + messages.warning(request, "No {} were selected for migration.".format(model._meta.verbose_name_plural)) + return redirect(self.get_return_url(request)) + + return render(request, self.template_name, { + 'form': form, + 'obj_type_plural': model._meta.verbose_name_plural, + 'table': table, + 'return_url': self.get_return_url(request), + }) + +# Community + + class CommunityListView(generic.ObjectListView): queryset = Community.objects.all() - filterset = CommunityFilterSet - filterset_form = CommunityFilterForm - table = CommunityTable + filterset = filters.CommunityFilterSet + filterset_form = forms.CommunityFilterForm + table = tables.CommunityTable action_buttons = ('add',) @@ -74,46 +159,51 @@ class CommunityView(generic.ObjectView): class CommunityEditView(generic.ObjectEditView): queryset = Community.objects.all() - form = CommunityForm + form = forms.CommunityForm class CommunityBulkDeleteView(generic.BulkDeleteView): queryset = Community.objects.all() - table = CommunityTable + table = tables.CommunityTable class CommunityBulkEditView(generic.BulkEditView): queryset = Community.objects.all() - filterset = CommunityFilterSet - table = CommunityTable - form = CommunityBulkEditForm + filterset = filters.CommunityFilterSet + table = tables.CommunityTable + form = forms.CommunityBulkEditForm class CommunityDeleteView(generic.ObjectDeleteView): queryset = Community.objects.all() + default_return_url = 'plugins:netbox_bgp:community_list' + + +# Session class BGPSessionListView(generic.ObjectListView): queryset = BGPSession.objects.all() - filterset = BGPSessionFilterSet - filterset_form = BGPSessionFilterForm - table = BGPSessionTable + filterset = filters.BGPSessionFilterSet + filterset_form = forms.BGPSessionFilterForm + table = tables.BGPSessionTable action_buttons = ('add',) + template_name = 'netbox_bgp/bgpsession_list.html' class BGPSessionEditView(generic.ObjectEditView): queryset = BGPSession.objects.all() - form = BGPSessionForm + form = forms.BGPSessionForm class BGPSessionAddView(generic.ObjectEditView): queryset = BGPSession.objects.all() - form = BGPSessionAddForm + form = forms.BGPSessionAddForm class BGPSessionBulkDeleteView(generic.BulkDeleteView): queryset = BGPSession.objects.all() - table = BGPSessionTable + table = tables.BGPSessionTable class BGPSessionView(generic.ObjectView): @@ -128,11 +218,11 @@ def get_extra_context(self, request, instance): import_policies_qs = instance.import_policies.all() export_policies_qs = instance.export_policies.all() - import_policies_table = RoutingPolicyTable( + import_policies_table = tables.RoutingPolicyTable( import_policies_qs, orderable=False ) - export_policies_table = RoutingPolicyTable( + export_policies_table = tables.RoutingPolicyTable( export_policies_qs, orderable=False ) @@ -145,24 +235,27 @@ def get_extra_context(self, request, instance): class BGPSessionDeleteView(generic.ObjectDeleteView): queryset = BGPSession.objects.all() + default_return_url = 'plugins:netbox_bgp:bgpsession_list' + +# Routing Policy class RoutingPolicyListView(generic.ObjectListView): queryset = RoutingPolicy.objects.all() - filterset = RoutingPolicyFilterSet - filterset_form = RoutingPolicyFilterForm - table = RoutingPolicyTable + filterset = filters.RoutingPolicyFilterSet + filterset_form = forms.RoutingPolicyFilterForm + table = tables.RoutingPolicyTable action_buttons = ('add',) class RoutingPolicyEditView(generic.ObjectEditView): queryset = RoutingPolicy.objects.all() - form = RoutingPolicyForm + form = forms.RoutingPolicyForm class RoutingPolicyBulkDeleteView(generic.BulkDeleteView): queryset = RoutingPolicy.objects.all() - table = RoutingPolicyTable + table = tables.RoutingPolicyTable class RoutingPolicyView(generic.ObjectView): @@ -177,9 +270,9 @@ def get_extra_context(self, request, instance): | Q(peer_group__in=instance.group_export_policies.all()) ) sess = sess.distinct() - sess_table = BGPSessionTable(sess) + sess_table = tables.BGPSessionTable(sess) rules = instance.rules.all() - rules_table = RoutingPolicyRuleTable(rules) + rules_table = tables.RoutingPolicyRuleTable(rules) return { 'rules_table': rules_table, 'related_session_table': sess_table @@ -188,24 +281,28 @@ def get_extra_context(self, request, instance): class RoutingPolicyDeleteView(generic.ObjectDeleteView): queryset = RoutingPolicy.objects.all() + default_return_url = 'plugins:netbox_bgp:routingpolicy_list' + + +# Peer Group class BGPPeerGroupListView(generic.ObjectListView): queryset = BGPPeerGroup.objects.all() - filterset = BGPPeerGroupFilterSet - filterset_form = BGPPeerGroupFilterForm - table = BGPPeerGroupTable + filterset = filters.BGPPeerGroupFilterSet + filterset_form = forms.BGPPeerGroupFilterForm + table = tables.BGPPeerGroupTable action_buttons = ('add',) class BGPPeerGroupEditView(generic.ObjectEditView): queryset = BGPPeerGroup.objects.all() - form = BGPPeerGroupForm + form = forms.BGPPeerGroupForm class BGPPeerGroupBulkDeleteView(generic.BulkDeleteView): queryset = BGPPeerGroup.objects.all() - table = BGPPeerGroupTable + table = tables.BGPPeerGroupTable class BGPPeerGroupView(generic.ObjectView): @@ -213,18 +310,18 @@ class BGPPeerGroupView(generic.ObjectView): template_name = 'netbox_bgp/bgppeergroup.html' def get_extra_context(self, request, instance): - import_policies_table = RoutingPolicyTable( + import_policies_table = tables.RoutingPolicyTable( instance.import_policies.all(), orderable=False ) - export_policies_table = RoutingPolicyTable( + export_policies_table = tables.RoutingPolicyTable( instance.export_policies.all(), orderable=False ) sess = BGPSession.objects.filter(peer_group=instance) sess = sess.distinct() - sess_table = BGPSessionTable(sess) + sess_table = tables.BGPSessionTable(sess) return { 'import_policies_table': import_policies_table, 'export_policies_table': export_policies_table, @@ -234,15 +331,20 @@ def get_extra_context(self, request, instance): class BGPPeerGroupDeleteView(generic.ObjectDeleteView): queryset = BGPPeerGroup.objects.all() + default_return_url = 'plugins:netbox_bgp:bgppeergroup_list' + + +# Routing Policy Rule class RoutingPolicyRuleEditView(generic.ObjectEditView): queryset = RoutingPolicyRule.objects.all() - form = RoutingPolicyRuleForm + form = forms.RoutingPolicyRuleForm class RoutingPolicyRuleDeleteView(generic.ObjectDeleteView): queryset = RoutingPolicyRule.objects.all() + default_return_url = 'plugins:netbox_bgp:routingpolicyrule_list' class RoutingPolicyRuleView(generic.ObjectView): @@ -266,7 +368,79 @@ def get_extra_context(self, request, instance): class RoutingPolicyRuleListView(generic.ObjectListView): queryset = RoutingPolicyRule.objects.all() - #filterset = RoutingPolicyRuleFilterSet - #filterset_form = RoutingPolicyRuleFilterForm - table = RoutingPolicyRuleTable + # filterset = RoutingPolicyRuleFilterSet + # filterset_form = RoutingPolicyRuleFilterForm + table = tables.RoutingPolicyRuleTable action_buttons = ('add',) + + +# Prefix List + + +class PrefixListListView(generic.ObjectListView): + queryset = PrefixList.objects.all() + filterset = filters.PrefixListFilterSet + filterset_form = forms.PrefixListFilterForm + table = tables.PrefixListTable + action_buttons = ('add',) + + +class PrefixListEditView(generic.ObjectEditView): + queryset = PrefixList.objects.all() + form = forms.PrefixListForm + + +class PrefixListBulkDeleteView(generic.BulkDeleteView): + queryset = PrefixList.objects.all() + table = tables.PrefixListTable + + +class PrefixListView(generic.ObjectView): + queryset = PrefixList.objects.all() + template_name = 'netbox_bgp/prefixlist.html' + + def get_extra_context(self, request, instance): + rprules = instance.plrules.all() + rprules_table = tables.RoutingPolicyRuleTable(rprules) + rules = instance.prefrules.all() + rules_table = tables.PrefixListRuleTable(rules) + return { + 'rules_table': rules_table, + 'rprules_table': rprules_table + } + + +class PrefixListDeleteView(generic.ObjectDeleteView): + queryset = PrefixList.objects.all() + default_return_url = 'plugins:netbox_bgp:prefixlist_list' + + +# Prefix List Rule + + +class PrefixListRuleListView(generic.ObjectListView): + queryset = PrefixListRule.objects.all() + # filterset = RoutingPolicyRuleFilterSet + # filterset_form = RoutingPolicyRuleFilterForm + table = tables.PrefixListRuleTable + action_buttons = ('add',) + + +class PrefixListRuleEditView(generic.ObjectEditView): + queryset = PrefixListRule.objects.all() + form = forms.PrefixListRuleForm + + +class PrefixListRuleBulkDeleteView(generic.BulkDeleteView): + queryset = PrefixListRule.objects.all() + table = tables.PrefixListRuleTable + + +class PrefixListRuleDeleteView(generic.ObjectDeleteView): + queryset = PrefixListRule.objects.all() + default_return_url = 'plugins:netbox_bgp:prefixlistrule_list' + + +class PrefixListRuleView(generic.ObjectView): + queryset = PrefixListRule.objects.all() + template_name = 'netbox_bgp/prefixlistrule.html'