mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-17 20:46:30 -06:00
Merge pull request #686 from digitalocean/rir_stats
#667: Add utilization statistics to RIR list view
This commit is contained in:
commit
f5b2420b4b
@ -22,23 +22,33 @@ AF_CHOICES = (
|
|||||||
(6, 'IPv6'),
|
(6, 'IPv6'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
PREFIX_STATUS_CONTAINER = 0
|
||||||
|
PREFIX_STATUS_ACTIVE = 1
|
||||||
|
PREFIX_STATUS_RESERVED = 2
|
||||||
|
PREFIX_STATUS_DEPRECATED = 3
|
||||||
PREFIX_STATUS_CHOICES = (
|
PREFIX_STATUS_CHOICES = (
|
||||||
(0, 'Container'),
|
(PREFIX_STATUS_CONTAINER, 'Container'),
|
||||||
(1, 'Active'),
|
(PREFIX_STATUS_ACTIVE, 'Active'),
|
||||||
(2, 'Reserved'),
|
(PREFIX_STATUS_RESERVED, 'Reserved'),
|
||||||
(3, 'Deprecated')
|
(PREFIX_STATUS_DEPRECATED, 'Deprecated')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
IPADDRESS_STATUS_ACTIVE = 1
|
||||||
|
IPADDRESS_STATUS_RESERVED = 2
|
||||||
|
IPADDRESS_STATUS_DHCP = 5
|
||||||
IPADDRESS_STATUS_CHOICES = (
|
IPADDRESS_STATUS_CHOICES = (
|
||||||
(1, 'Active'),
|
(IPADDRESS_STATUS_ACTIVE, 'Active'),
|
||||||
(2, 'Reserved'),
|
(IPADDRESS_STATUS_RESERVED, 'Reserved'),
|
||||||
(5, 'DHCP')
|
(IPADDRESS_STATUS_DHCP, 'DHCP')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
VLAN_STATUS_ACTIVE = 1
|
||||||
|
VLAN_STATUS_RESERVED = 2
|
||||||
|
VLAN_STATUS_DEPRECATED = 3
|
||||||
VLAN_STATUS_CHOICES = (
|
VLAN_STATUS_CHOICES = (
|
||||||
(1, 'Active'),
|
(VLAN_STATUS_ACTIVE, 'Active'),
|
||||||
(2, 'Reserved'),
|
(VLAN_STATUS_RESERVED, 'Reserved'),
|
||||||
(3, 'Deprecated')
|
(VLAN_STATUS_DEPRECATED, 'Deprecated')
|
||||||
)
|
)
|
||||||
|
|
||||||
STATUS_CHOICE_CLASSES = {
|
STATUS_CHOICE_CLASSES = {
|
||||||
|
@ -6,6 +6,25 @@ from utilities.tables import BaseTable, ToggleColumn
|
|||||||
from .models import Aggregate, IPAddress, Prefix, RIR, Role, VLAN, VLANGroup, VRF
|
from .models import Aggregate, IPAddress, Prefix, RIR, Role, VLAN, VLANGroup, VRF
|
||||||
|
|
||||||
|
|
||||||
|
RIR_UTILIZATION = """
|
||||||
|
<div class="progress">
|
||||||
|
{% if record.stats.total %}
|
||||||
|
<div class="progress-bar" role="progressbar" style="width: {{ record.stats.percentages.active }}%;">
|
||||||
|
<span class="sr-only">{{ record.stats.percentages.active }}%</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar progress-bar-info" role="progressbar" style="width: {{ record.stats.percentages.reserved }}%;">
|
||||||
|
<span class="sr-only">{{ record.stats.percentages.reserved }}%</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar progress-bar-danger" role="progressbar" style="width: {{ record.stats.percentages.deprecated }}%;">
|
||||||
|
<span class="sr-only">{{ record.stats.percentages.deprecated }}%</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar progress-bar-success" role="progressbar" style="width: {{ record.stats.percentages.available }}%;">
|
||||||
|
<span class="sr-only">{{ record.stats.percentages.available }}%</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
RIR_ACTIONS = """
|
RIR_ACTIONS = """
|
||||||
{% if perms.ipam.change_rir %}
|
{% if perms.ipam.change_rir %}
|
||||||
<a href="{% url 'ipam:rir_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
<a href="{% url 'ipam:rir_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||||
@ -108,12 +127,22 @@ class RIRTable(BaseTable):
|
|||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
name = tables.LinkColumn(verbose_name='Name')
|
name = tables.LinkColumn(verbose_name='Name')
|
||||||
aggregate_count = tables.Column(verbose_name='Aggregates')
|
aggregate_count = tables.Column(verbose_name='Aggregates')
|
||||||
slug = tables.Column(verbose_name='Slug')
|
stats_total = tables.Column(accessor='stats.total', verbose_name='Total',
|
||||||
|
footer=lambda table: sum(r.stats['total'] for r in table.data))
|
||||||
|
stats_active = tables.Column(accessor='stats.active', verbose_name='Active',
|
||||||
|
footer=lambda table: sum(r.stats['active'] for r in table.data))
|
||||||
|
stats_reserved = tables.Column(accessor='stats.reserved', verbose_name='Reserved',
|
||||||
|
footer=lambda table: sum(r.stats['reserved'] for r in table.data))
|
||||||
|
stats_deprecated = tables.Column(accessor='stats.deprecated', verbose_name='Deprecated',
|
||||||
|
footer=lambda table: sum(r.stats['deprecated'] for r in table.data))
|
||||||
|
stats_available = tables.Column(accessor='stats.available', verbose_name='Available',
|
||||||
|
footer=lambda table: sum(r.stats['available'] for r in table.data))
|
||||||
|
utilization = tables.TemplateColumn(template_code=RIR_UTILIZATION, verbose_name='Utilization')
|
||||||
actions = tables.TemplateColumn(template_code=RIR_ACTIONS, attrs={'td': {'class': 'text-right'}}, verbose_name='')
|
actions = tables.TemplateColumn(template_code=RIR_ACTIONS, attrs={'td': {'class': 'text-right'}}, verbose_name='')
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = RIR
|
model = RIR
|
||||||
fields = ('pk', 'name', 'aggregate_count', 'slug', 'actions')
|
fields = ('pk', 'name', 'aggregate_count', 'stats_total', 'stats_active', 'stats_reserved', 'stats_deprecated', 'stats_available', 'utilization', 'actions')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import netaddr
|
from collections import OrderedDict
|
||||||
from django_tables2 import RequestConfig
|
from django_tables2 import RequestConfig
|
||||||
|
import netaddr
|
||||||
|
|
||||||
from django.contrib.auth.decorators import permission_required
|
from django.contrib.auth.decorators import permission_required
|
||||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||||
@ -16,7 +17,7 @@ from utilities.views import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from . import filters, forms, tables
|
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):
|
def add_available_prefixes(parent, prefix_list):
|
||||||
@ -157,6 +158,82 @@ class RIRListView(ObjectListView):
|
|||||||
edit_permissions = ['ipam.change_rir', 'ipam.delete_rir']
|
edit_permissions = ['ipam.change_rir', 'ipam.delete_rir']
|
||||||
template_name = 'ipam/rir_list.html'
|
template_name = 'ipam/rir_list.html'
|
||||||
|
|
||||||
|
def alter_queryset(self, request):
|
||||||
|
|
||||||
|
if request.GET.get('family') == '6':
|
||||||
|
family = 6
|
||||||
|
denominator = 2 ** 64 # Count /64s for IPv6 rather than individual IPs
|
||||||
|
else:
|
||||||
|
family = 4
|
||||||
|
denominator = 1
|
||||||
|
|
||||||
|
rirs = []
|
||||||
|
for rir in self.queryset:
|
||||||
|
|
||||||
|
stats = {
|
||||||
|
'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['total'] += aggregate.prefix.size / denominator
|
||||||
|
stats['active'] += netaddr.IPSet(active_prefixes).size / denominator
|
||||||
|
stats['reserved'] += netaddr.IPSet(reserved_prefixes).size / denominator
|
||||||
|
stats['deprecated'] += netaddr.IPSet(deprecated_prefixes).size / denominator
|
||||||
|
stats['available'] += available_prefixes.size / denominator
|
||||||
|
|
||||||
|
# Calculate the percentage of total space for each prefix status.
|
||||||
|
total = float(stats['total'])
|
||||||
|
stats['percentages'] = {
|
||||||
|
'active': float('{:.2f}'.format(stats['active'] / total * 100)) if total else 0,
|
||||||
|
'reserved': float('{:.2f}'.format(stats['reserved'] / total * 100)) if total else 0,
|
||||||
|
'deprecated': float('{:.2f}'.format(stats['deprecated'] / total * 100)) if total else 0,
|
||||||
|
}
|
||||||
|
stats['percentages']['available'] = (
|
||||||
|
100 -
|
||||||
|
stats['percentages']['active'] -
|
||||||
|
stats['percentages']['reserved'] -
|
||||||
|
stats['percentages']['deprecated']
|
||||||
|
)
|
||||||
|
rir.stats = stats
|
||||||
|
rirs.append(rir)
|
||||||
|
|
||||||
|
return rirs
|
||||||
|
|
||||||
|
def extra_context(self):
|
||||||
|
|
||||||
|
totals = {
|
||||||
|
'total': sum([rir.stats['total'] for rir in self.queryset]),
|
||||||
|
'active': sum([rir.stats['active'] for rir in self.queryset]),
|
||||||
|
'reserved': sum([rir.stats['reserved'] for rir in self.queryset]),
|
||||||
|
'deprecated': sum([rir.stats['deprecated'] for rir in self.queryset]),
|
||||||
|
'available': sum([rir.stats['available'] for rir in self.queryset]),
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'totals': totals,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class RIREditView(PermissionRequiredMixin, ObjectEditView):
|
class RIREditView(PermissionRequiredMixin, ObjectEditView):
|
||||||
permission_required = 'ipam.change_rir'
|
permission_required = 'ipam.change_rir'
|
||||||
|
@ -85,6 +85,9 @@ label.required {
|
|||||||
th.pk, td.pk {
|
th.pk, td.pk {
|
||||||
width: 30px;
|
width: 30px;
|
||||||
}
|
}
|
||||||
|
tfoot td {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
/* Paginator */
|
/* Paginator */
|
||||||
nav ul.pagination {
|
nav ul.pagination {
|
||||||
|
@ -1,10 +1,22 @@
|
|||||||
{% extends '_base.html' %}
|
{% extends '_base.html' %}
|
||||||
|
{% load humanize %}
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
|
||||||
{% block title %}RIRs{% endblock %}
|
{% block title %}RIRs{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
|
{% if request.GET.family == '6' %}
|
||||||
|
<a href="{% url 'ipam:rir_list' %}" class="btn btn-default">
|
||||||
|
<span class="fa fa-table" aria-hidden="true"></span>
|
||||||
|
IPv4 Stats
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'ipam:rir_list' %}?family=6" class="btn btn-default">
|
||||||
|
<span class="fa fa-table" aria-hidden="true"></span>
|
||||||
|
IPv6 Stats
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
{% if perms.ipam.add_rir %}
|
{% if perms.ipam.add_rir %}
|
||||||
<a href="{% url 'ipam:rir_add' %}" class="btn btn-primary">
|
<a href="{% url 'ipam:rir_add' %}" class="btn btn-primary">
|
||||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||||
@ -18,4 +30,7 @@
|
|||||||
{% include 'utilities/obj_table.html' with bulk_delete_url='ipam:rir_bulk_delete' %}
|
{% include 'utilities/obj_table.html' with bulk_delete_url='ipam:rir_bulk_delete' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% if request.GET.family == '6' %}
|
||||||
|
<div class="pull-right text-muted"><strong>Note:</strong> Numbers shown indicate /64 prefixes.</div>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
Loading…
Reference in New Issue
Block a user