diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index 163712d1e..1538251cf 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -22,23 +22,33 @@ AF_CHOICES = ( (6, 'IPv6'), ) +PREFIX_STATUS_CONTAINER = 0 +PREFIX_STATUS_ACTIVE = 1 +PREFIX_STATUS_RESERVED = 2 +PREFIX_STATUS_DEPRECATED = 3 PREFIX_STATUS_CHOICES = ( - (0, 'Container'), - (1, 'Active'), - (2, 'Reserved'), - (3, 'Deprecated') + (PREFIX_STATUS_CONTAINER, 'Container'), + (PREFIX_STATUS_ACTIVE, 'Active'), + (PREFIX_STATUS_RESERVED, 'Reserved'), + (PREFIX_STATUS_DEPRECATED, 'Deprecated') ) +IPADDRESS_STATUS_ACTIVE = 1 +IPADDRESS_STATUS_RESERVED = 2 +IPADDRESS_STATUS_DHCP = 5 IPADDRESS_STATUS_CHOICES = ( - (1, 'Active'), - (2, 'Reserved'), - (5, 'DHCP') + (IPADDRESS_STATUS_ACTIVE, 'Active'), + (IPADDRESS_STATUS_RESERVED, 'Reserved'), + (IPADDRESS_STATUS_DHCP, 'DHCP') ) +VLAN_STATUS_ACTIVE = 1 +VLAN_STATUS_RESERVED = 2 +VLAN_STATUS_DEPRECATED = 3 VLAN_STATUS_CHOICES = ( - (1, 'Active'), - (2, 'Reserved'), - (3, 'Deprecated') + (VLAN_STATUS_ACTIVE, 'Active'), + (VLAN_STATUS_RESERVED, 'Reserved'), + (VLAN_STATUS_DEPRECATED, 'Deprecated') ) STATUS_CHOICE_CLASSES = { diff --git a/netbox/ipam/urls.py b/netbox/ipam/urls.py index dc5fcc964..71017be26 100644 --- a/netbox/ipam/urls.py +++ b/netbox/ipam/urls.py @@ -19,6 +19,8 @@ urlpatterns = [ url(r'^rirs/$', views.RIRListView.as_view(), name='rir_list'), url(r'^rirs/add/$', views.RIREditView.as_view(), name='rir_add'), url(r'^rirs/delete/$', views.RIRBulkDeleteView.as_view(), name='rir_bulk_delete'), + url(r'^rirs/stats/$', views.rir_stats, name='rir_stats'), + url(r'^rirs/stats/ipv6/$', views.rir_stats, kwargs={'family': 6}, name='rir_stats_ipv6'), url(r'^rirs/(?P[\w-]+)/edit/$', views.RIREditView.as_view(), name='rir_edit'), # Aggregates diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 3262bbeb5..6beb22f3e 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -1,5 +1,6 @@ -import netaddr +from collections import OrderedDict from django_tables2 import RequestConfig +import netaddr from django.contrib.auth.decorators import permission_required from django.contrib.auth.mixins import PermissionRequiredMixin @@ -16,7 +17,7 @@ from utilities.views import ( ) from . import filters, forms, tables -from .models import Aggregate, IPAddress, Prefix, RIR, Role, VLAN, VLANGroup, VRF +from .models import Aggregate, IPAddress, PREFIX_STATUS_ACTIVE, PREFIX_STATUS_DEPRECATED, PREFIX_STATUS_RESERVED, Prefix, RIR, Role, VLAN, VLANGroup, VRF def add_available_prefixes(parent, prefix_list): @@ -655,3 +656,76 @@ class VLANBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'ipam.delete_vlan' cls = VLAN default_redirect_url = 'ipam:vlan_list' + + +# +# Miscellaneous +# + +def rir_stats(request, family=4): + + denominator = 2 ** 64 if family == 6 else 1 + + stats = OrderedDict() + for rir in RIR.objects.all(): + + stats[rir] = { + 'total': 0, + 'active': 0, + 'reserved': 0, + 'deprecated': 0, + 'available': 0, + } + aggregate_list = Aggregate.objects.filter(family=family, rir=rir) + for aggregate in aggregate_list: + + queryset = Prefix.objects.filter(prefix__net_contained_or_equal=str(aggregate.prefix)) + + # Find all consumed space for each prefix status (we ignore containers for this purpose). + active_prefixes = netaddr.cidr_merge([p.prefix for p in queryset.filter(status=PREFIX_STATUS_ACTIVE)]) + reserved_prefixes = netaddr.cidr_merge([p.prefix for p in queryset.filter(status=PREFIX_STATUS_RESERVED)]) + deprecated_prefixes = netaddr.cidr_merge([p.prefix for p in queryset.filter(status=PREFIX_STATUS_DEPRECATED)]) + + # Find all available prefixes by subtracting each of the existing prefix sets from the aggregate prefix. + available_prefixes = ( + netaddr.IPSet([aggregate.prefix]) + - netaddr.IPSet(active_prefixes) + - netaddr.IPSet(reserved_prefixes) + - netaddr.IPSet(deprecated_prefixes) + ) + + # Add the size of each metric to the RIR total. + stats[rir]['total'] += aggregate.prefix.size / denominator + stats[rir]['active'] += netaddr.IPSet(active_prefixes).size / denominator + stats[rir]['reserved'] += netaddr.IPSet(reserved_prefixes).size / denominator + stats[rir]['deprecated'] += netaddr.IPSet(deprecated_prefixes).size / denominator + stats[rir]['available'] += available_prefixes.size / denominator + + # Calculate the percentage of total space for each prefix status. + total = float(stats[rir]['total']) + stats[rir]['percentages'] = { + 'active': float('{:.2f}'.format(stats[rir]['active'] / total * 100)) if total else 0, + 'reserved': float('{:.2f}'.format(stats[rir]['reserved'] / total * 100)) if total else 0, + 'deprecated': float('{:.2f}'.format(stats[rir]['deprecated'] / total * 100)) if total else 0, + 'available': float('{:.2f}'.format(stats[rir]['available'] / total * 100)) if total else 0, + } + stats[rir]['percentages']['available'] = ( + 100 + - stats[rir]['percentages']['active'] + - stats[rir]['percentages']['reserved'] + - stats[rir]['percentages']['deprecated'] + ) + + totals = { + 'total': sum([counts['total'] for rir, counts in stats.items()]), + 'active': sum([counts['active'] for rir, counts in stats.items()]), + 'reserved': sum([counts['reserved'] for rir, counts in stats.items()]), + 'deprecated': sum([counts['deprecated'] for rir, counts in stats.items()]), + 'available': sum([counts['available'] for rir, counts in stats.items()]), + } + + return render(request, 'ipam/stats.html', { + 'stats': stats, + 'totals': totals, + 'family': family, + }) diff --git a/netbox/templates/ipam/stats.html b/netbox/templates/ipam/stats.html new file mode 100644 index 000000000..e4056e224 --- /dev/null +++ b/netbox/templates/ipam/stats.html @@ -0,0 +1,90 @@ +{% extends '_base.html' %} +{% load humanize %} +{% load render_table from django_tables2 %} + +{% block title %}RIR Statistics{% endblock %} + +{% block content %} +

RIR Statistics

+
+
+ + {% if family == 6 %} + + {% endif %} + {% for rir, counts in stats.items %} +

{{ rir }}

+
+ {% if counts.total %} +
+ {{ counts.percentages.active }}% +
+
+ {{ counts.percentages.reserved }}% +
+
+ {{ counts.percentages.deprecated }}% +
+
+ {{ counts.percentages.available }}% +
+ {% endif %} +
+
+
+

{{ counts.total|intcomma }}

+ Total +
+
+

{{ counts.active|intcomma }}

+ Active +
+
+

{{ counts.reserved|intcomma }}

+ Reserved +
+
+

{{ counts.deprecated|intcomma }}

+ Deprecated +
+
+

{{ counts.available|intcomma }}

+ Available +
+
+ {% endfor %} +
+
+
+

Totals

+
+
+

{{ totals.total|intcomma }}

+ All IPv{{ family }} space +
+
+

{{ totals.active|intcomma }}

+ Active +
+
+

{{ totals.reserved|intcomma }}

+ Reserved +
+
+

{{ totals.deprecated|intcomma }}

+ Deprecated +
+
+

{{ totals.available|intcomma }}

+ Available +
+
+
+
+{% endblock %}