diff --git a/README.md b/README.md index a3d0e60..a27403f 100644 --- a/README.md +++ b/README.md @@ -39,12 +39,6 @@ BGP Session BGP Sessions ![BGP Session Table](docs/img/bgp_sess_list.png) -ASN -![ASN](docs/img/asn.png) - -ASNs -![ASN Table](docs/img/asn_list.png) - Community ![Community](docs/img/commun.png) diff --git a/develop/Dockerfile b/develop/Dockerfile index 1635006..9481362 100644 --- a/develop/Dockerfile +++ b/develop/Dockerfile @@ -1,4 +1,4 @@ -ARG python_ver=3.7 +ARG python_ver=3.8 FROM python:${python_ver} ARG netbox_ver=master diff --git a/docs/img/asn.png b/docs/img/asn.png deleted file mode 100644 index d5cc2c8..0000000 Binary files a/docs/img/asn.png and /dev/null differ diff --git a/docs/img/asn_list.png b/docs/img/asn_list.png deleted file mode 100644 index 8657ab0..0000000 Binary files a/docs/img/asn_list.png and /dev/null differ diff --git a/netbox_bgp/admin.py b/netbox_bgp/admin.py index 91382f2..f78f40d 100644 --- a/netbox_bgp/admin.py +++ b/netbox_bgp/admin.py @@ -1,10 +1,5 @@ from django.contrib import admin -from .models import ASN, Community, BGPSession, BGPPeerGroup, RoutingPolicy - - -@admin.register(ASN) -class ASNAdmin(admin.ModelAdmin): - fields = ('number', 'status', 'description') +from .models import Community, BGPSession, BGPPeerGroup, RoutingPolicy @admin.register(Community) diff --git a/netbox_bgp/api/serializers.py b/netbox_bgp/api/serializers.py index 495c678..3e11d68 100644 --- a/netbox_bgp/api/serializers.py +++ b/netbox_bgp/api/serializers.py @@ -6,11 +6,11 @@ from dcim.api.nested_serializers import NestedSiteSerializer, NestedDeviceSerializer from tenancy.api.nested_serializers import NestedTenantSerializer from extras.api.nested_serializers import NestedTagSerializer -from ipam.api.nested_serializers import NestedIPAddressSerializer +from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedASNSerializer from netbox_bgp.models import ( - ASN, ASNStatusChoices, BGPSession, SessionStatusChoices, RoutingPolicy, BGPPeerGroup, + CommunityStatusChoices, BGPSession, SessionStatusChoices, RoutingPolicy, BGPPeerGroup, Community, RoutingPolicyRule ) @@ -25,39 +25,6 @@ def to_representation(self, value): return self.serializer(value, context={'request': self.context['request']}).data -class ASNSerializer(NetBoxModelSerializer): - status = ChoiceField(choices=ASNStatusChoices, required=False) - site = NestedSiteSerializer(required=False, allow_null=True) - tenant = NestedTenantSerializer(required=False, allow_null=True) - - def validate(self, attrs): - try: - number = attrs['number'] - tenant = attrs.get('tenant') - except KeyError: - # this is patch - return attrs - if ASN.objects.filter(number=number, tenant=tenant).exists(): - raise ValidationError( - {'error': 'Asn with this Number and Tenant already exists.'} - ) - return attrs - - class Meta: - ref_name = 'BGP_ASN' - model = ASN - fields = ['number', 'id', 'display', 'status', 'description', 'custom_fields', 'site', 'tenant', 'tags'] - - -class NestedASNSerializer(WritableNestedSerializer): - url = HyperlinkedIdentityField(view_name='plugins:netbox_bgp:asn') - - class Meta: - ref_name = 'BGP_ASN_Nested' - model = ASN - fields = ['id', 'url', 'number', 'description'] - - class RoutingPolicySerializer(NetBoxModelSerializer): class Meta: model = RoutingPolicy @@ -170,7 +137,7 @@ class Meta: validators = [] class CommunitySerializer(NetBoxModelSerializer): - status = ChoiceField(choices=ASNStatusChoices, required=False) + status = ChoiceField(choices=CommunityStatusChoices, required=False) tenant = NestedTenantSerializer(required=False, allow_null=True) class Meta: diff --git a/netbox_bgp/api/urls.py b/netbox_bgp/api/urls.py index 4aea08a..82164b0 100644 --- a/netbox_bgp/api/urls.py +++ b/netbox_bgp/api/urls.py @@ -1,12 +1,8 @@ from rest_framework import routers -from .views import ( - ASNViewSet, BGPSessionViewSet, RoutingPolicyViewSet, BGPPeerGroupViewSet, - CommunityViewSet -) +from .views import BGPSessionViewSet, RoutingPolicyViewSet, BGPPeerGroupViewSet, CommunityViewSet router = routers.DefaultRouter() -router.register('asn', ASNViewSet) router.register('session', BGPSessionViewSet, 'session') router.register('bgpsession', BGPSessionViewSet, 'bgpsession') router.register('routing-policy', RoutingPolicyViewSet) diff --git a/netbox_bgp/api/views.py b/netbox_bgp/api/views.py index 7619f2a..9184740 100644 --- a/netbox_bgp/api/views.py +++ b/netbox_bgp/api/views.py @@ -1,22 +1,16 @@ from rest_framework.viewsets import ModelViewSet from .serializers import ( - ASNSerializer, BGPSessionSerializer, RoutingPolicySerializer, BGPPeerGroupSerializer, + BGPSessionSerializer, RoutingPolicySerializer, BGPPeerGroupSerializer, CommunitySerializer ) -from netbox_bgp.models import ASN, BGPSession, RoutingPolicy, BGPPeerGroup, Community +from netbox_bgp.models import BGPSession, RoutingPolicy, BGPPeerGroup, Community from netbox_bgp.filters import ( - ASNFilterSet, BGPSessionFilterSet, RoutingPolicyFilterSet, BGPPeerGroupFilterSet, + BGPSessionFilterSet, RoutingPolicyFilterSet, BGPPeerGroupFilterSet, CommunityFilterSet ) -class ASNViewSet(ModelViewSet): - queryset = ASN.objects.all() - serializer_class = ASNSerializer - filterset_class = ASNFilterSet - - class BGPSessionViewSet(ModelViewSet): queryset = BGPSession.objects.all() serializer_class = BGPSessionSerializer diff --git a/netbox_bgp/filters.py b/netbox_bgp/filters.py index f58ff43..9c2093a 100644 --- a/netbox_bgp/filters.py +++ b/netbox_bgp/filters.py @@ -4,34 +4,11 @@ from netaddr.core import AddrFormatError from extras.filters import TagFilter -from .models import ASN, Community, BGPSession, RoutingPolicy, BGPPeerGroup -from ipam.models import IPAddress +from .models import Community, BGPSession, RoutingPolicy, BGPPeerGroup +from ipam.models import IPAddress, ASN from dcim.models import Device -class ASNFilterSet(django_filters.FilterSet): - q = django_filters.CharFilter( - method='search', - label='Search', - ) - tag = TagFilter() - - class Meta: - model = ASN - fields = ['number', 'description', 'status', 'tenant', 'site'] - - def search(self, queryset, name, value): - """Perform the filtered search.""" - if not value.strip(): - return queryset - qs_filter = ( - Q(id__icontains=value) - | Q(number__icontains=value) - | Q(description__icontains=value) - ) - return queryset.filter(qs_filter) - - class CommunityFilterSet(django_filters.FilterSet): q = django_filters.CharFilter( method='search', @@ -65,7 +42,7 @@ class BGPSessionFilterSet(django_filters.FilterSet): remote_as = django_filters.ModelMultipleChoiceFilter( field_name='remote_as__number', queryset=ASN.objects.all(), - to_field_name='number', + to_field_name='asn', label='Remote AS (Number)', ) remote_as_id = django_filters.ModelMultipleChoiceFilter( @@ -77,7 +54,7 @@ class BGPSessionFilterSet(django_filters.FilterSet): local_as = django_filters.ModelMultipleChoiceFilter( field_name='local_as__number', queryset=ASN.objects.all(), - to_field_name='number', + to_field_name='asn', label='Local AS (Number)', ) local_as_id = django_filters.ModelMultipleChoiceFilter( diff --git a/netbox_bgp/forms.py b/netbox_bgp/forms.py index 9f8ca94..0abf567 100644 --- a/netbox_bgp/forms.py +++ b/netbox_bgp/forms.py @@ -8,7 +8,7 @@ from extras.models import Tag from tenancy.models import Tenant from dcim.models import Device, Site -from ipam.models import IPAddress, Prefix +from ipam.models import IPAddress, Prefix, ASN from ipam.formfields import IPNetworkFormField from utilities.forms import ( DynamicModelChoiceField, @@ -18,7 +18,7 @@ from netbox.forms import NetBoxModelForm, NetBoxModelBulkEditForm, NetBoxModelFilterSetForm from .models import ( - ASN, ASNStatusChoices, Community, BGPSession, + CommunityStatusChoices, Community, BGPSession, SessionStatusChoices, RoutingPolicy, BGPPeerGroup, RoutingPolicyRule ) @@ -26,137 +26,6 @@ from django.forms.widgets import TextInput -class ASNField(forms.CharField): - ''' - Return int value, but allows to input dotted digit text - ''' - - def to_python(self, value): - if not re.match(r'^\d+(\.\d+)?$', value): - raise ValidationError('Invalid AS Number: {}'.format(value)) - if '.' in value: - if int(value.split('.')[0]) > 65535 or int(value.split('.')[1]) > 65535: - raise ValidationError('Invalid AS Number: {}'.format(value)) - try: - return int(value.split('.')[0]) * 65536 + int(value.split('.')[1]) - except ValueError: - raise ValidationError('Invalid AS Number: {}'.format(value)) - try: - return int(value) - except ValueError: - raise ValidationError('Invalid AS Number: {}'.format(value)) - - -class ASdotInput(TextInput): - def _format_value(self, value): - if not value: - return 0 - if type(value) is str: - return value - if int(value) > 65535: - return '{}.{}'.format(value // 65536, value % 65536) - else: - return value - - def render(self, name, value, attrs=None, renderer=None): - nb_settings = settings.PLUGINS_CONFIG.get('netbox_bgp', {}) - asdot = nb_settings.get('asdot', False) - if asdot: - value = self._format_value(value) - return super().render(name, value, attrs, renderer) - - -class ASNFilterForm(NetBoxModelFilterSetForm): - model = ASN - q = forms.CharField( - required=False, - label='Search' - ) - tenant = DynamicModelChoiceField( - queryset=Tenant.objects.all(), - required=False - ) - status = forms.MultipleChoiceField( - choices=ASNStatusChoices, - required=False, - widget=StaticSelectMultiple() - ) - site = DynamicModelChoiceField( - queryset=Site.objects.all(), - required=False - ) - - tag = TagFilterField(model) - - -class ASNForm(NetBoxModelForm): - number = ASNField( - widget=ASdotInput - ) - tags = DynamicModelMultipleChoiceField( - queryset=Tag.objects.all(), - required=False - ) - site = DynamicModelChoiceField( - queryset=Site.objects.all(), - required=False - ) - status = forms.ChoiceField( - required=False, - choices=ASNStatusChoices, - widget=StaticSelect() - ) - tenant = DynamicModelChoiceField( - queryset=Tenant.objects.all(), - required=False - ) - - def clean(self): - cleaned_data = super().clean() - if self.errors.get('number'): - return cleaned_data - number = cleaned_data.get('number') - tenant = cleaned_data.get('tenant') - if 'number' in self.changed_data or 'tenant' in self.changed_data: - if ASN.objects.filter(number=number, tenant=tenant).exists(): - raise forms.ValidationError('AS number with this number and tenant is already exists.') - return cleaned_data - - class Meta: - model = ASN - fields = [ - 'number', 'description', 'status', 'site', 'tenant', 'tags', - ] - - -class ASNBulkEditForm(NetBoxModelBulkEditForm): - pk = forms.ModelMultipleChoiceField( - queryset=ASN.objects.all(), - widget=forms.MultipleHiddenInput - ) - tenant = DynamicModelChoiceField( - queryset=Tenant.objects.all(), - required=False - ) - description = forms.CharField( - max_length=200, - required=False - ) - status = forms.ChoiceField( - required=False, - choices=ASNStatusChoices, - widget=StaticSelect() - ) - - model = ASN - fieldsets = ( - (None, ('tenant', 'description', 'status')), - ) - nullable_fields = ( - 'tenant', 'description', - ) - - class CommunityForm(NetBoxModelForm): tags = DynamicModelMultipleChoiceField( queryset=Tag.objects.all(), @@ -164,7 +33,7 @@ class CommunityForm(NetBoxModelForm): ) status = forms.ChoiceField( required=False, - choices=ASNStatusChoices, + choices=CommunityStatusChoices, widget=StaticSelect() ) tenant = DynamicModelChoiceField( @@ -189,7 +58,7 @@ class CommunityFilterForm(NetBoxModelFilterSetForm): required=False ) status = forms.MultipleChoiceField( - choices=ASNStatusChoices, + choices=CommunityStatusChoices, required=False, widget=StaticSelectMultiple() ) @@ -218,7 +87,7 @@ class CommunityBulkEditForm(NetBoxModelBulkEditForm): ) status = forms.ChoiceField( required=False, - choices=ASNStatusChoices, + choices=CommunityStatusChoices, widget=StaticSelect() ) @@ -257,19 +126,11 @@ class BGPSessionForm(NetBoxModelForm): query_params={ 'site_id': '$site' }, - widget=APISelect( - api_url='/api/plugins/bgp/asn/', - ) - + label=_('Local AS') ) remote_as = DynamicModelChoiceField( queryset=ASN.objects.all(), - query_params={ - 'site_id': '$site' - }, - widget=APISelect( - api_url='/api/plugins/bgp/asn/', - ) + label=_('Remote AS') ) local_address = DynamicModelChoiceField( queryset=IPAddress.objects.all(), @@ -343,18 +204,12 @@ class BGPSessionFilterForm(NetBoxModelFilterSetForm): remote_as_id = DynamicModelMultipleChoiceField( queryset=ASN.objects.all(), required=False, - label=_('Remote AS'), - widget=APISelectMultiple( - api_url='/api/plugins/bgp/asn/', - ) + label=_('Remote AS') ) local_as_id = DynamicModelMultipleChoiceField( queryset=ASN.objects.all(), required=False, - label=_('Local AS'), - widget=APISelectMultiple( - api_url='/api/plugins/bgp/asn/', - ) + label=_('Local AS') ) by_local_address = forms.CharField( required=False, diff --git a/netbox_bgp/graphql.py b/netbox_bgp/graphql.py index 3145d6e..8022cc2 100755 --- a/netbox_bgp/graphql.py +++ b/netbox_bgp/graphql.py @@ -14,15 +14,6 @@ class Meta: filterset_class = filters.CommunityFilterSet -class AsnType(NetBoxObjectType): - number = Field(BigInt) - - class Meta: - model = models.ASN - fields = '__all__' - filterset_class = filters.ASNFilterSet - - class BgpSessionType(NetBoxObjectType): class Meta: model = models.BGPSession @@ -48,9 +39,6 @@ class BGPQuery(ObjectType): community = ObjectField(CommunityType) community_list = ObjectListField(CommunityType) - bgp_asn = ObjectField(AsnType) - bgp_asn_list = ObjectListField(AsnType) - bgp_session = ObjectField(BgpSessionType) bgp_session_list = ObjectListField(BgpSessionType) diff --git a/netbox_bgp/migrations/0023_netbox_bgp.py b/netbox_bgp/migrations/0023_netbox_bgp.py new file mode 100644 index 0000000..836bbfa --- /dev/null +++ b/netbox_bgp/migrations/0023_netbox_bgp.py @@ -0,0 +1,35 @@ +# Generated by Django 4.0.6 on 2022-07-29 10:29 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('ipam', '0057_created_datetimefield'), + ('netbox_bgp', '0022_netbox_bgp'), + ] + + operations = [ + migrations.RemoveField( + model_name='asngroup', + name='site', + ), + migrations.AlterField( + model_name='bgpsession', + name='local_as', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='local_as', to='ipam.asn'), + ), + migrations.AlterField( + model_name='bgpsession', + name='remote_as', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='remote_as', to='ipam.asn'), + ), + migrations.DeleteModel( + name='ASN', + ), + migrations.DeleteModel( + name='ASNGroup', + ), + ] diff --git a/netbox_bgp/models.py b/netbox_bgp/models.py index cc592e4..4b99e86 100644 --- a/netbox_bgp/models.py +++ b/netbox_bgp/models.py @@ -12,7 +12,7 @@ from ipam.models import Prefix -class ASNStatusChoices(ChoiceSet): +class CommunityStatusChoices(ChoiceSet): STATUS_ACTIVE = 'active' STATUS_RESERVED = 'reserved' @@ -48,27 +48,6 @@ class ActionChoices(ChoiceSet): ] -class ASNGroup(ChangeLoggingMixin, models.Model): - """ - """ - name = models.CharField( - max_length=100 - ) - site = models.ForeignKey( - to='dcim.Site', - on_delete=models.SET_NULL, - blank=True, - null=True - ) - description = models.CharField( - max_length=200, - blank=True - ) - - def __str__(self): - return self.name - - class RoutingPolicy(NetBoxModel): """ """ @@ -141,8 +120,8 @@ class BGPBase(NetBoxModel): ) status = models.CharField( max_length=50, - choices=ASNStatusChoices, - default=ASNStatusChoices.STATUS_ACTIVE + choices=CommunityStatusChoices, + default=CommunityStatusChoices.STATUS_ACTIVE ) role = models.ForeignKey( to='ipam.Role', @@ -159,59 +138,6 @@ class Meta: abstract = True -class ASN(BGPBase): - - number = models.PositiveBigIntegerField( - validators=[MinValueValidator(1), MaxValueValidator(4294967295)] - ) - - group = models.ForeignKey( - ASNGroup, - on_delete=models.PROTECT, - blank=True, - null=True - ) - - tags = TaggableManager(through='extras.TaggedItem', related_name='asn_tags') - - clone_fields = ['description', 'status', 'tenant'] - - class Meta: - verbose_name = 'AS Number' - verbose_name_plural = 'AS Numbers' - constraints = [ - models.UniqueConstraint( - fields=['number', 'tenant'], - name='uniqie_number_tenant' - ), - models.UniqueConstraint( - fields=['number'], - condition=models.Q(tenant=None), - name='uniqie_number' - ), - ] - # unique_together = ['number', 'site', 'tenant'] - - def get_status_color(self): - return ASNStatusChoices.colors.get(self.status) - - def get_absolute_url(self): - return reverse('plugins:netbox_bgp:asn', args=[self.pk]) - - def get_asdot(self): - if self.number > 65535: - return '{}.{}'.format(self.number // 65536, self.number % 65536) - else: - return str(self.number) - - def __str__(self): - nb_settings = settings.PLUGINS_CONFIG.get('netbox_bgp', {}) - asdot = nb_settings.get('asdot', False) - if asdot: - return self.get_asdot() - return str(self.number) - - class Community(BGPBase): """ """ @@ -227,7 +153,7 @@ def __str__(self): return self.value def get_status_color(self): - return ASNStatusChoices.colors.get(self.status) + return CommunityStatusChoices.colors.get(self.status) def get_absolute_url(self): return reverse('plugins:netbox_bgp:community', args=[self.pk]) @@ -267,12 +193,12 @@ class BGPSession(NetBoxModel): related_name='remote_address' ) local_as = models.ForeignKey( - ASN, + to='ipam.ASN', on_delete=models.PROTECT, related_name='local_as' ) remote_as = models.ForeignKey( - ASN, + to='ipam.ASN', on_delete=models.PROTECT, related_name='remote_as' ) diff --git a/netbox_bgp/navigation.py b/netbox_bgp/navigation.py index 712e2bf..0f3d3b5 100644 --- a/netbox_bgp/navigation.py +++ b/netbox_bgp/navigation.py @@ -2,20 +2,6 @@ from utilities.choices import ButtonColorChoices menu_items = ( - PluginMenuItem( - link='plugins:netbox_bgp:asn_list', - link_text='AS Numbers', - permissions=['netbox_bgp.view_asn'], - buttons=( - PluginMenuButton( - link='plugins:netbox_bgp:asn_add', - title='AS Numbers', - icon_class='mdi mdi-plus-thick', - color=ButtonColorChoices.GREEN, - permissions=['netbox_bgp.add_asn'], - ), - ), - ), PluginMenuItem( link='plugins:netbox_bgp:community_list', link_text='Communities', diff --git a/netbox_bgp/tables.py b/netbox_bgp/tables.py index 509c4ee..3b2fcd1 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 Community, BGPSession, RoutingPolicy, BGPPeerGroup, RoutingPolicyRule AVAILABLE_LABEL = mark_safe('Available') COL_TENANT = """ @@ -25,21 +25,6 @@ """ -class ASNTable(NetBoxTable): - number = tables.LinkColumn(text=lambda record: record.__str__(), args=[A('pk')]) - status = ChoiceFieldColumn( - default=AVAILABLE_LABEL - ) - site = tables.LinkColumn() - tenant = tables.TemplateColumn( - template_code=COL_TENANT - ) - - class Meta(NetBoxTable.Meta): - model = ASN - fields = ('pk', 'number', 'description', 'status', 'tenant') - - class CommunityTable(NetBoxTable): value = tables.LinkColumn() status = ChoiceFieldColumn( diff --git a/netbox_bgp/templates/netbox_bgp/asn.html b/netbox_bgp/templates/netbox_bgp/asn.html deleted file mode 100644 index d5bec68..0000000 --- a/netbox_bgp/templates/netbox_bgp/asn.html +++ /dev/null @@ -1,106 +0,0 @@ -{% 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_asn %} - - Edit - - {% endif %} - {% if perms.netbox_bgp.delete_asn %} - - Delete - - {% endif %} -
-{% endblock controls %} -{% block tabs %} - -{% endblock tabs %} -{% block content %} -
-
-
-
- ASN -
-
- - - - - - - - - - - - - - - - - - - - - -
Site - {% if object.site %} - {{ object.site }} - {% else %} - None - {% endif %} -
Number{{ object }}
Tenant - {% if object.tenant %} - {{ object.tenant }} - {% else %} - None - {% endif %} -
Status - {{ object.get_status_display }} -
Description{{ object.description|placeholder }}
-
-
- {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/tags.html' %} - {% plugin_left_page object %} -
-
-
-
- Related BGP Sessions -
-
- {% render_table related_session_table 'inc/table.html' %} -
- {% plugin_right_page object %} -
-
-
-
- {% plugin_full_width_page object %} -
-
- {% endblock %} \ No newline at end of file diff --git a/netbox_bgp/tests/test_api.py b/netbox_bgp/tests/test_api.py index c408bc3..449a990 100644 --- a/netbox_bgp/tests/test_api.py +++ b/netbox_bgp/tests/test_api.py @@ -12,9 +12,9 @@ from tenancy.models import Tenant from dcim.models import Site, DeviceRole, DeviceType, Manufacturer, Device, Interface -from ipam.models import IPAddress +from ipam.models import IPAddress, ASN, RIR -from netbox_bgp.models import ASN, Community, BGPPeerGroup, BGPSession +from netbox_bgp.models import Community, BGPPeerGroup, BGPSession class BaseTestCase(TestCase): @@ -27,98 +27,6 @@ def setUp(self): self.gql_client = Client(HTTP_AUTHORIZATION=f'Token {self.token.key}') -class ASNTestCase(BaseTestCase): - def setUp(self): - super().setUp() - self.base_url_lookup = 'plugins-api:netbox_bgp-api:asn' - self.asn1 = ASN.objects.create(number=65000, description='test_asn1') - self.asn2 = ASN.objects.create(number=65001, description='test_asn2') - self.tenant = Tenant.objects.create(name='tenant') - - def test_list_asn(self): - url = reverse(f'{self.base_url_lookup}-list') - response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data['count'], 2) - - def test_get_asn(self): - url = reverse(f'{self.base_url_lookup}-detail', kwargs={'pk': self.asn1.pk}) - response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data['number'], self.asn1.number) - self.assertEqual(response.data['description'], self.asn1.description) - - def test_create_asn(self): - url = reverse(f'{self.base_url_lookup}-list') - data = {'number': 65002, 'description': 'test_asn3'} - - response = self.client.post(url, data, format='json') - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - for key, value in data.items(): - self.assertEqual(response.data[key], value) - - asn = ASN.objects.get(pk=response.data['id']) - self.assertEqual(asn.number, data['number']) - self.assertEqual(asn.description, data['description']) - - def test_update_asn(self): - url = reverse(f'{self.base_url_lookup}-detail', kwargs={'pk': self.asn1.pk}) - - response = self.client.patch(url, {'number': 65004}, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) - asn = ASN.objects.get(pk=self.asn1.pk) - self.assertEqual(asn.number, 65004) - - response = self.client.patch(url, {'number': 65005}, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) - asn = ASN.objects.get(pk=self.asn1.pk) - self.assertEqual(asn.number, 65005) - - response = self.client.patch(url, {'description': 'changed'}, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) - asn = ASN.objects.get(pk=self.asn1.pk) - self.assertEqual(asn.description, 'changed') - - def test_delete_task(self): - url = reverse(f'{self.base_url_lookup}-detail', kwargs={'pk': self.asn1.pk}) - - response = self.client.delete(url) - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - with self.assertRaises(ASN.DoesNotExist): - ASN.objects.get(pk=self.asn1.pk) - - def test_uniqueconstraint_asn(self): - url = reverse(f'{self.base_url_lookup}-list') - data = {'number': 65001, 'description': 'test_asn3'} - response = self.client.post(url, data, format='json') - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - data = {'number': 65001, 'description': 'test_asn3', 'tenant': self.tenant.pk} - response = self.client.post(url, data, format='json') - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - - def test_graphql(self): - url = reverse('graphql') - query = 'query bgp_asn($id: Int!){bgp_asn(id: $id){number}}' - response = self.gql_client.post( - url, - json.dumps({'query': query, 'variables': {'id': self.asn1.pk}}), - content_type='application/json' - ) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(json.loads(response.content)['data']['bgp_asn']['number'], self.asn1.number) - - def test_graphql_list(self): - url = reverse('graphql') - query = '{bgp_asn_list{number}}' - response = self.gql_client.post( - url, - json.dumps({'query': query}), - content_type='application/json' - ) - self.assertEqual(response.status_code, status.HTTP_200_OK) - - class CommunityTestCase(BaseTestCase): def setUp(self): super().setUp() @@ -247,10 +155,11 @@ def setUp(self): self.remote_ip = IPAddress.objects.create(address='4.4.4.4/32') intf.ip_addresses.add(local_ip) self.device.save() - self.local_as = ASN.objects.create(number=65000, description='local_as') - self.remote_as = ASN.objects.create(number=65001, description='remote_as') - local_as = ASN.objects.create(number=65002, description='local_as') - remote_as = ASN.objects.create(number=65003, description='remote_as') + self.rir = RIR.objects.create(name="rir") + self.local_as = ASN.objects.create(asn=65000, rir=self.rir, description='local_as') + self.remote_as = ASN.objects.create(asn=65001, rir=self.rir, description='remote_as') + local_as = ASN.objects.create(asn=65002, rir=self.rir, description='local_as') + remote_as = ASN.objects.create(asn=65003, rir=self.rir, description='remote_as') self.peer_group = BGPPeerGroup.objects.create(name='peer_group', description='peer_group_description') self.session = BGPSession.objects.create( name='session', @@ -275,8 +184,8 @@ def test_get_session(self): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['name'], self.session.name) self.assertEqual(response.data['description'], self.session.description) - self.assertEqual(response.data['local_as']['number'], self.session.local_as.number) - self.assertEqual(response.data['remote_as']['number'], self.session.remote_as.number) + self.assertEqual(response.data['local_as']['asn'], self.session.local_as.asn) + self.assertEqual(response.data['remote_as']['asn'], self.session.remote_as.asn) self.assertEqual(response.data['local_address']['address'], self.session.local_address.address) self.assertEqual(response.data['remote_address']['address'], self.session.remote_address.address) self.assertEqual(response.data['status']['value'], self.session.status) diff --git a/netbox_bgp/tests/test_forms.py b/netbox_bgp/tests/test_forms.py index 5e15afc..8fe38d2 100644 --- a/netbox_bgp/tests/test_forms.py +++ b/netbox_bgp/tests/test_forms.py @@ -2,39 +2,28 @@ from django.test import TestCase -from netbox_bgp.forms import ASNForm +from netbox_bgp.forms import CommunityForm -class TestASNFormCase(TestCase): +class TestCommunityFormCase(TestCase): def test_asn_invalid_letters(self): - form = ASNForm( + form = CommunityForm( data={ - 'number': 'dsad', + 'value': 'dsad', 'status': 'active' } ) self.assertEqual( - form.errors['number'], ['Invalid AS Number: dsad'] + form.errors['value'], ['Enter a valid value.'] ) - def test_asn_asdot_valid(self): - form = ASNForm( + def test_community_valid(self): + form = CommunityForm( data={ - 'number': '1.1', + 'value': '1234:5678', 'status': 'active' } ) self.assertEqual( - form.errors.get('number'), None - ) - - def test_asn_invalid_range(self): - form = ASNForm( - data={ - 'number': '65536.1', - 'status': 'active' - } - ) - self.assertEqual( - form.errors['number'], ['Invalid AS Number: 65536.1'] + form.errors.get('value'), None ) diff --git a/netbox_bgp/tests/test_models.py b/netbox_bgp/tests/test_models.py index 2a54e77..f7a0752 100644 --- a/netbox_bgp/tests/test_models.py +++ b/netbox_bgp/tests/test_models.py @@ -4,40 +4,9 @@ from tenancy.models import Tenant from dcim.models import Site, Device, Manufacturer, DeviceRole, DeviceType -from ipam.models import IPAddress +from ipam.models import IPAddress, ASN, RIR -from netbox_bgp.models import ASN, BGPSession, Community, RoutingPolicy, BGPPeerGroup - - -class ASNTestCase(TestCase): - def setUp(self): - self.tenant = Tenant.objects.create(name='tenant') - self.asn = ASN.objects.create( - number=65001, - description='test_asn' - ) - - def test_create_asn(self): - self.assertTrue(isinstance(self.asn, ASN)) - self.assertEqual(self.asn.__str__(), str(self.asn.number)) - - def test_invalid_asn0(self): - asn = ASN(number=0) - self.assertRaises(ValidationError, asn.full_clean) - - def test_invalid_asndohuya(self): - asn = ASN(number=4294967296) - self.assertRaises(ValidationError, asn.full_clean) - - def test_uniqueconstraint_asn(self): - asn = ASN(number=65001) - with self.assertRaises(IntegrityError): - asn.save() - - def test_uniqueconstraint_asn2(self): - asn = ASN.objects.create(number=65001, tenant=self.tenant) - self.assertEqual(str(asn), '65001') - # todo cre another 65001 tenant=self.tenant +from netbox_bgp.models import BGPSession, Community, RoutingPolicy, BGPPeerGroup class RoutingPolicyTestCase(TestCase): @@ -170,11 +139,16 @@ def setUp(self): device_role=device_role, device_type=device_type ) + self.rir = RIR.objects.create( + name="rir" + ) self.local_as = ASN.objects.create( - number=65001, + asn=65001, + rir=self.rir ) self.remote_as = ASN.objects.create( - number=65002, + asn=65002, + rir=self.rir ) self.peer_group = BGPPeerGroup.objects.create( name='peer_group' diff --git a/netbox_bgp/urls.py b/netbox_bgp/urls.py index b32ac79..b5802a2 100644 --- a/netbox_bgp/urls.py +++ b/netbox_bgp/urls.py @@ -1,10 +1,9 @@ from django.urls import path from netbox.views.generic import ObjectChangeLogView -from .models import ASN, BGPSession, Community, RoutingPolicy, BGPPeerGroup, RoutingPolicyRule +from .models import BGPSession, Community, RoutingPolicy, BGPPeerGroup, RoutingPolicyRule from .views import ( - ASNListView, ASNView, ASNBulkDeleteView, ASNEditView, ASNBulkEditView, - ASNDeleteView, CommunityListView, CommunityEditView, CommunityView, + CommunityListView, CommunityEditView, CommunityView, CommunityBulkEditView, CommunityBulkDeleteView, CommunityDeleteView, BGPSessionListView, BGPSessionEditView, BGPSessionBulkDeleteView, BGPSessionView, BGPSessionDeleteView, BGPSessionAddView, @@ -16,14 +15,6 @@ ) 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//changelog/', ObjectChangeLogView.as_view(), name='asn_changelog', kwargs={'model': ASN}), # Community path('community/', CommunityListView.as_view(), name='community_list'), path('community/add/', CommunityEditView.as_view(), name='community_add'), @@ -42,7 +33,6 @@ path('session//delete/', 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'), diff --git a/netbox_bgp/views.py b/netbox_bgp/views.py index d7abb73..6a5d2a9 100644 --- a/netbox_bgp/views.py +++ b/netbox_bgp/views.py @@ -3,62 +3,20 @@ from netbox.views import generic from .filters import ( - ASNFilterSet, CommunityFilterSet, BGPSessionFilterSet, + CommunityFilterSet, BGPSessionFilterSet, RoutingPolicyFilterSet, BGPPeerGroupFilterSet ) -from .models import ASN, Community, BGPSession, RoutingPolicy, BGPPeerGroup, RoutingPolicyRule +from .models import Community, BGPSession, RoutingPolicy, BGPPeerGroup, RoutingPolicyRule from .tables import ( - ASNTable, CommunityTable, BGPSessionTable, RoutingPolicyTable, BGPPeerGroupTable, RoutingPolicyRuleTable + CommunityTable, BGPSessionTable, RoutingPolicyTable, BGPPeerGroupTable, RoutingPolicyRuleTable ) from .forms import ( - ASNFilterForm, ASNBulkEditForm, ASNForm, CommunityForm, - CommunityFilterForm, CommunityBulkEditForm, BGPSessionForm, + CommunityForm, CommunityFilterForm, CommunityBulkEditForm, BGPSessionForm, BGPSessionFilterForm, BGPSessionAddForm, RoutingPolicyFilterForm, RoutingPolicyForm, BGPPeerGroupFilterForm, BGPPeerGroupForm, RoutingPolicyRuleForm ) -class ASNListView(generic.ObjectListView): - queryset = ASN.objects.all() - filterset = ASNFilterSet - filterset_form = ASNFilterForm - table = ASNTable - action_buttons = ('add',) - - -class ASNView(generic.ObjectView): - queryset = ASN.objects.all() - template_name = 'netbox_bgp/asn.html' - - def get_extra_context(self, request, instance): - sess = BGPSession.objects.filter(remote_as=instance) | BGPSession.objects.filter(local_as=instance) - sess_table = BGPSessionTable(sess) - return { - 'related_session_table': sess_table - } - - -class ASNEditView(generic.ObjectEditView): - queryset = ASN.objects.all() - form = ASNForm - - -class ASNBulkDeleteView(generic.BulkDeleteView): - queryset = ASN.objects.all() - table = ASNTable - - -class ASNBulkEditView(generic.BulkEditView): - queryset = ASN.objects.all() - filterset = ASNFilterSet - table = ASNTable - form = ASNBulkEditForm - - -class ASNDeleteView(generic.ObjectDeleteView): - queryset = ASN.objects.all() - - class CommunityListView(generic.ObjectListView): queryset = Community.objects.all() filterset = CommunityFilterSet