#1246: Initial work on an API endpoint to retrieve available IPs for a prefix

This commit is contained in:
Jeremy Stretch 2017-06-28 16:23:17 -04:00
parent 5940feb64b
commit d5bb37b552
3 changed files with 75 additions and 6 deletions

View File

@ -1,4 +1,5 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from collections import OrderedDict
from rest_framework import serializers from rest_framework import serializers
from rest_framework.validators import UniqueTogetherValidator from rest_framework.validators import UniqueTogetherValidator
@ -268,6 +269,20 @@ class WritableIPAddressSerializer(CustomFieldModelSerializer):
] ]
class AvailableIPSerializer(serializers.Serializer):
def to_representation(self, instance):
if self.context.get('vrf'):
vrf = NestedVRFSerializer(self.context['vrf'], context={'request': self.context['request']}).data
else:
vrf = None
return OrderedDict([
('family', self.context['prefix'].version),
('address', '{}/{}'.format(instance, self.context['prefix'].prefixlen)),
('vrf', vrf),
])
# #
# Services # Services
# #

View File

@ -1,7 +1,12 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from rest_framework.decorators import detail_route
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from django.conf import settings
from django.shortcuts import get_object_or_404
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
from ipam import filters from ipam import filters
from extras.api.views import CustomFieldModelViewSet from extras.api.views import CustomFieldModelViewSet
@ -61,6 +66,33 @@ class PrefixViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
write_serializer_class = serializers.WritablePrefixSerializer write_serializer_class = serializers.WritablePrefixSerializer
filter_class = filters.PrefixFilter filter_class = filters.PrefixFilter
@detail_route(url_path='available-ips')
def available_ips(self, request, pk=None):
"""
A convenience method for returning available IP addresses within a prefix. By default, the number of IPs
returned will be equivalent to PAGINATE_COUNT. An arbitrary limit (up to MAX_PAGE_SIZE, if set) may be passed,
however results will not be paginated.
"""
prefix = get_object_or_404(Prefix, pk=pk)
# Determine the maximum amount of IPs to return
try:
limit = int(request.query_params.get('limit', settings.PAGINATE_COUNT))
except ValueError:
limit = settings.PAGINATE_COUNT
if settings.MAX_PAGE_SIZE:
limit = min(limit, settings.MAX_PAGE_SIZE)
# Calculate available IPs within the prefix
ip_list = list(prefix.get_available_ips())[:limit]
serializer = serializers.AvailableIPSerializer(ip_list, many=True, context={
'request': request,
'prefix': prefix.prefix,
'vrf': prefix.vrf,
})
return Response(serializer.data)
# #
# IP addresses # IP addresses

View File

@ -1,6 +1,5 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import netaddr
from netaddr import IPNetwork, cidr_merge
from django.conf import settings from django.conf import settings
from django.contrib.contenttypes.fields import GenericRelation from django.contrib.contenttypes.fields import GenericRelation
@ -161,7 +160,7 @@ class Aggregate(CreatedUpdatedModel, CustomFieldModel):
""" """
child_prefixes = Prefix.objects.filter(prefix__net_contained_or_equal=str(self.prefix)) child_prefixes = Prefix.objects.filter(prefix__net_contained_or_equal=str(self.prefix))
# Remove overlapping prefixes from list of children # Remove overlapping prefixes from list of children
networks = cidr_merge([c.prefix for c in child_prefixes]) networks = netaddr.cidr_merge([c.prefix for c in child_prefixes])
children_size = float(0) children_size = float(0)
for p in networks: for p in networks:
children_size += p.size children_size += p.size
@ -321,11 +320,34 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel):
def get_duplicates(self): def get_duplicates(self):
return Prefix.objects.filter(vrf=self.vrf, prefix=str(self.prefix)).exclude(pk=self.pk) return Prefix.objects.filter(vrf=self.vrf, prefix=str(self.prefix)).exclude(pk=self.pk)
def get_child_ips(self):
"""
Return all IPAddresses within this Prefix.
"""
return IPAddress.objects.filter(address__net_contained_or_equal=str(self.prefix), vrf=self.vrf)
def get_available_ips(self):
"""
Return all available IPs within this prefix as an IPSet.
"""
prefix = netaddr.IPSet(self.prefix)
child_ips = netaddr.IPSet([ip.address for ip in self.get_child_ips()])
available_ips = prefix - child_ips
# Remove unusable IPs from non-pool prefixes
if not self.is_pool:
available_ips -= netaddr.IPSet([
netaddr.IPAddress(self.prefix.first),
netaddr.IPAddress(self.prefix.last),
])
return available_ips
def get_utilization(self): def get_utilization(self):
""" """
Determine the utilization of the prefix and return it as a percentage. Determine the utilization of the prefix and return it as a percentage.
""" """
child_count = IPAddress.objects.filter(address__net_contained_or_equal=str(self.prefix), vrf=self.vrf).count() child_count = self.get_child_ips().count()
prefix_size = self.prefix.size prefix_size = self.prefix.size
if self.family == 4 and self.prefix.prefixlen < 31 and not self.is_pool: if self.family == 4 and self.prefix.prefixlen < 31 and not self.is_pool:
prefix_size -= 2 prefix_size -= 2
@ -335,11 +357,11 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel):
def new_subnet(self): def new_subnet(self):
if self.family == 4: if self.family == 4:
if self.prefix.prefixlen <= 30: if self.prefix.prefixlen <= 30:
return IPNetwork('{}/{}'.format(self.prefix.network, self.prefix.prefixlen + 1)) return netaddr.IPNetwork('{}/{}'.format(self.prefix.network, self.prefix.prefixlen + 1))
return None return None
if self.family == 6: if self.family == 6:
if self.prefix.prefixlen <= 126: if self.prefix.prefixlen <= 126:
return IPNetwork('{}/{}'.format(self.prefix.network, self.prefix.prefixlen + 1)) return netaddr.IPNetwork('{}/{}'.format(self.prefix.network, self.prefix.prefixlen + 1))
return None return None