Skip to content

Commit

Permalink
ASN Range models
Browse files Browse the repository at this point in the history
  • Loading branch information
mzbroch committed Apr 4, 2024
1 parent eada30b commit c0b2b04
Show file tree
Hide file tree
Showing 21 changed files with 583 additions and 2 deletions.
1 change: 1 addition & 0 deletions changes/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
!.gitignore
1 change: 1 addition & 0 deletions changes/117.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added ASN Range model.
15 changes: 15 additions & 0 deletions nautobot_bgp_models/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@ class Meta:
fields = "__all__"


class AutonomousSystemRangeSerializer(
NautobotModelSerializer,
TaggedModelSerializerMixin,
):
"""REST API serializer for AutonomousSystemRange records."""

url = serializers.HyperlinkedIdentityField(
view_name="plugins-api:nautobot_bgp_models-api:autonomoussystemrange-detail"
)

class Meta:
model = models.AutonomousSystemRange
fields = "__all__"


class InheritableFieldsSerializerMixin:
"""Common mixin for Serializers that support an additional `include_inherited` query parameter."""

Expand Down
1 change: 1 addition & 0 deletions nautobot_bgp_models/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
router = OrderedDefaultRouter()

router.register("autonomous-systems", views.AutonomousSystemViewSet)
router.register("autonomous-system-ranges", views.AutonomousSystemRangeViewSet)
router.register("peer-groups", views.PeerGroupViewSet)
router.register("peer-group-templates", views.PeerGroupTemplateViewSet)
router.register("peer-endpoints", views.PeerEndpointViewSet)
Expand Down
8 changes: 8 additions & 0 deletions nautobot_bgp_models/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ class AutonomousSystemViewSet(NautobotModelViewSet):
filterset_class = filters.AutonomousSystemFilterSet


class AutonomousSystemRangeViewSet(NautobotModelViewSet):
"""REST API viewset for AutonomousSystemRange records."""

queryset = models.AutonomousSystemRange.objects.all()
serializer_class = serializers.AutonomousSystemRangeSerializer
filterset_class = filters.AutonomousSystemRangeFilterSet


include_inherited = OpenApiParameter(
name="include_inherited",
required=False,
Expand Down
26 changes: 26 additions & 0 deletions nautobot_bgp_models/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,32 @@ class Meta:
fields = ["id", "asn", "status", "tags"]


class AutonomousSystemRangeFilterSet(
BaseFilterSet,
CreatedUpdatedModelFilterSetMixin,
CustomFieldModelFilterSetMixin,
):
"""Filtering of AutonomousSystemRange records."""

q = django_filters.CharFilter(
method="search",
label="Search",
)

def search(self, queryset, name, value): # pylint: disable=unused-argument
"""Free-text search method implementation."""
if not value.strip():
return queryset

return queryset.filter(
Q(name=value) | Q(asn_max__icontains=value) | Q(asn_min__icontains=value) | Q(description__icontains=value)
).distinct()

class Meta:
model = models.AutonomousSystemRange
fields = ["id", "name", "asn_min", "asn_max", "tags"]


class BGPRoutingInstanceFilterSet(
BaseFilterSet, CreatedUpdatedModelFilterSetMixin, CustomFieldModelFilterSetMixin, StatusModelFilterSetMixin
):
Expand Down
34 changes: 34 additions & 0 deletions nautobot_bgp_models/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from nautobot.extras.forms import NautobotFilterForm, RoleModelFilterFormMixin
from nautobot.extras.models import Tag, Secret, Role
from nautobot.ipam.models import VRF, IPAddress
from nautobot.tenancy.models import Tenant

from . import choices, models

Expand Down Expand Up @@ -53,6 +54,39 @@ class Meta:
]


class AutonomousSystemRangeForm(NautobotModelForm):
"""Form for creating/updating AutonomousSystem records."""

tags = DynamicModelMultipleChoiceField(queryset=Tag.objects.all(), required=False)
tenant = DynamicModelChoiceField(queryset=Tenant.objects.all(), required=False)

class Meta:
model = models.AutonomousSystemRange
fields = ("name", "asn_min", "asn_max", "description", "tenant", "tags")


class AutonomousSystemRangeFilterForm(NautobotFilterForm):
"""Form for filtering AutonomousSystem records in combination with AutonomousSystemFilterSet."""

model = models.AutonomousSystemRange
field_order = ["name", "asn_min", "asn_max", "tenant", "tags"]
tag = TagFilterField(model)


class AutonomousSystemRangeBulkEditForm(TagsBulkEditFormMixin, NautobotBulkEditForm):
"""Form for bulk-editing multiple AutonomousSystem records."""

pk = forms.ModelMultipleChoiceField(
queryset=models.AutonomousSystemRange.objects.all(), widget=forms.MultipleHiddenInput()
)
description = forms.CharField(max_length=200, required=False)

class Meta:
nullable_fields = [
"description",
]


class BGPRoutingInstanceForm(NautobotModelForm):
"""Form for creating/updating BGPRoutingInstance records."""

Expand Down
30 changes: 30 additions & 0 deletions nautobot_bgp_models/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""BGP helper functions."""


def add_available_asns(instance, asns):
"""Create fake records for all gaps between used Autonomous Systems."""
new_list = []
last_asn = None
for asn_val in asns:
if asn_val.asn == instance.asn_min:
new_list.append(asn_val)
last_asn = asn_val.asn
elif not last_asn:
new_list.append({"asn": instance.asn_min, "available": asn_val.asn - instance.asn_min})
new_list.append(asn_val)
last_asn = asn_val.asn
elif instance.asn_min <= asn_val.asn <= instance.asn_max:
if asn_val.asn - last_asn > 1:
new_list.append({"asn": last_asn + 1, "available": asn_val.asn - last_asn - 1})
new_list.append(asn_val)
last_asn = asn_val.asn
elif asn_val.asn == instance.asn_max:
new_list.append(asn_val)
last_asn = asn_val.asn

if not asns:
new_list.append({"asn": instance.asn_min, "available": instance.asn_max - instance.asn_min + 1})
elif last_asn < instance.asn_max:
new_list.append({"asn": last_asn + 1, "available": instance.asn_max - last_asn})

return new_list
58 changes: 58 additions & 0 deletions nautobot_bgp_models/migrations/0009_autonomoussystemrange.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# pylint: disable=missing-module-docstring,missing-function-docstring,missing-class-docstring,invalid-name

import uuid
import django.core.serializers.json
from django.db import migrations, models
import django.db.models.deletion
import nautobot.core.models.fields
import nautobot.dcim.fields
import nautobot.extras.models.mixins


class Migration(migrations.Migration):

dependencies = [
("tenancy", "0008_tagsfield"),
("extras", "0098_rename_data_jobresult_result"),
("nautobot_bgp_models", "0008_nautobotv2_updates"),
]

operations = [
migrations.CreateModel(
name="AutonomousSystemRange",
fields=[
(
"id",
models.UUIDField(
default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True
),
),
("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=255, unique=True)),
("asn_min", nautobot.dcim.fields.ASNField()),
("asn_max", nautobot.dcim.fields.ASNField()),
("description", models.CharField(blank=True, max_length=255)),
("tags", nautobot.core.models.fields.TagsField(through="extras.TaggedItem", to="extras.Tag")),
(
"tenant",
models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to="tenancy.tenant"
),
),
],
options={
"verbose_name": "Autonomous System Range",
"ordering": ["asn_min"],
},
bases=(
models.Model,
nautobot.extras.models.mixins.DynamicGroupMixin,
nautobot.extras.models.mixins.NotesMixin,
),
),
]
44 changes: 44 additions & 0 deletions nautobot_bgp_models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from nautobot.apps.models import extras_features
from nautobot.ipam.models import IPAddress, IPAddressToInterface
from nautobot.core.utils.data import deepmerge
from nautobot.tenancy.models import Tenant

from nautobot_bgp_models.choices import AFISAFIChoices

Expand Down Expand Up @@ -143,6 +144,49 @@ def __str__(self):
return f"AS {self.asn}"


@extras_features(
"custom_fields",
"custom_links",
"custom_validators",
"export_templates",
"graphql",
"relationships",
"webhooks",
)
class AutonomousSystemRange(PrimaryModel):
"""Autonomous System Range information."""

name = models.CharField(max_length=255, unique=True, blank=False)
asn_min = ASNField(verbose_name="Start", help_text="Min value for 32-bit autonomous system number")
asn_max = ASNField(verbose_name="End", help_text="Max value for 32-bit autonomous system number")
description = models.CharField(max_length=255, blank=True)
tenant = models.ForeignKey(to=Tenant, on_delete=models.PROTECT, blank=True, null=True)

class Meta:
ordering = ["asn_min"]
verbose_name = "Autonomous System Range"

def __str__(self):
"""String representation of an AutonomousSystemRange."""
return f"ASN Range {self.asn_min}-{self.asn_max}"

def clean(self):
"""Clean."""
if self.asn_min >= self.asn_max:
raise ValidationError("asn_min value must be lower than asn_max value.")

def get_next_available_asn(self):
"""Return the first available ASN number in the range, or None if none are available."""
asn_nums = AutonomousSystem.objects.filter(asn__gte=self.asn_min, asn__lte=self.asn_max).values_list(
"asn", flat=True
)
for i in range(self.asn_min, self.asn_max + 1):
if i not in asn_nums:
return i

return None


@extras_features(
"custom_fields",
"custom_links",
Expand Down
15 changes: 15 additions & 0 deletions nautobot_bgp_models/navigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,21 @@
),
),
),
NavMenuItem(
link="plugins:nautobot_bgp_models:autonomoussystemrange_list",
name="Autonomous System Ranges",
permissions=["nautobot_bgp_models.view_autonomoussystemrange"],
buttons=(
NavMenuAddButton(
link="plugins:nautobot_bgp_models:autonomoussystemrange_add",
permissions=["nautobot_bgp_models.add_autonomoussystemrange"],
),
NavMenuImportButton(
link="plugins:nautobot_bgp_models:autonomoussystemrange_import",
permissions=["nautobot_bgp_models.add_autonomoussystemrange"],
),
),
),
NavMenuItem(
link="plugins:nautobot_bgp_models:peergrouptemplate_list",
name="Peer Group Templates",
Expand Down
31 changes: 30 additions & 1 deletion nautobot_bgp_models/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,25 @@

from . import models

ASN_LINK = """
{% if record.present_in_database %}
<a href="{{ record.get_absolute_url }}">{{ record.asn }}</a>
{% elif perms.nautobot_bgp_models.autonomoussystem_add %}
<a href="\
{% url 'plugins:nautobot_bgp_models:autonomoussystem_add' %}\
?asn={{ record.asn }}\
" class="btn btn-xs btn-success">{{ record.available }} ASN{{ record.available|pluralize }} available</a>\
{% else %}
{{ record.available }} ASN{{ record.available|pluralize }} available
{% endif %}
"""


class AutonomousSystemTable(StatusTableMixin, BaseTable):
"""Table representation of AutonomousSystem records."""

pk = ToggleColumn()
asn = tables.LinkColumn()
asn = tables.TemplateColumn(template_code=ASN_LINK, verbose_name="ASN")
provider = tables.LinkColumn()
tags = TagColumn(url_name="plugins:nautobot_bgp_models:autonomoussystem_list")
actions = ButtonsColumn(model=models.AutonomousSystem)
Expand All @@ -30,6 +43,22 @@ class Meta(BaseTable.Meta):
fields = ("pk", "asn", "status", "provider", "description", "tags")


class AutonomousSystemRangeTable(StatusTableMixin, BaseTable):
"""Table representation of AutonomousSystem records."""

pk = ToggleColumn()
name = tables.LinkColumn()
asn_min = tables.LinkColumn()
asn_max = tables.LinkColumn()
tenant = tables.LinkColumn()
tags = TagColumn(url_name="plugins:nautobot_bgp_models:autonomoussystemrange_list")
actions = ButtonsColumn(model=models.AutonomousSystemRange)

class Meta(BaseTable.Meta):
model = models.AutonomousSystemRange
fields = ("pk", "name", "asn_min", "asn_max", "tenant", "description", "tags")


class BGPRoutingInstanceTable(StatusTableMixin, BaseTable):
"""Table representation of BGPRoutingInstance records."""

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{% extends 'generic/object_detail.html' %}
{% load helpers %}

{% block content_left_page %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>BGP Autonomous System Range</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Start ASN</td>
<td>{{ object.asn_min }}</td>
</tr>
<tr>
<td>End ASN</td>
<td>{{ object.asn_max }}</td>
</tr>
<tr>
<td>Tenant</td>
<td>{{ object.tenant | hyperlinked_object }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ object.description }}</td>
</tr>
</table>
</div>
{% endblock content_left_page %}

{% block content_right_page %}
{% include 'utilities/obj_table.html' with table=asn_range_table table_template='panel_table.html' heading='ASNs' bulk_edit_url='plugins:nautobot_bgp_models:autonomoussystem_bulk_edit' bulk_delete_url='plugins:nautobot_bgp_models:autonomoussystem_bulk_delete' %}
{% endblock content_right_page %}
Loading

0 comments on commit c0b2b04

Please sign in to comment.