Skip to content

Commit

Permalink
#1246: Initial work on an API endpoint to retrieve available IPs for …
Browse files Browse the repository at this point in the history
…a prefix
  • Loading branch information
jeremystretch committed Jun 28, 2017
1 parent 5940feb commit d5bb37b
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 6 deletions.
15 changes: 15 additions & 0 deletions netbox/ipam/api/serializers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from __future__ import unicode_literals
from collections import OrderedDict

from rest_framework import serializers
from rest_framework.validators import UniqueTogetherValidator
Expand Down Expand Up @@ -268,6 +269,20 @@ class Meta:
]


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
#
Expand Down
32 changes: 32 additions & 0 deletions netbox/ipam/api/views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
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 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 import filters
from extras.api.views import CustomFieldModelViewSet
Expand Down Expand Up @@ -61,6 +66,33 @@ class PrefixViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
write_serializer_class = serializers.WritablePrefixSerializer
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
Expand Down
34 changes: 28 additions & 6 deletions netbox/ipam/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from __future__ import unicode_literals

from netaddr import IPNetwork, cidr_merge
import netaddr

from django.conf import settings
from django.contrib.contenttypes.fields import GenericRelation
Expand Down Expand Up @@ -161,7 +160,7 @@ def get_utilization(self):
"""
child_prefixes = Prefix.objects.filter(prefix__net_contained_or_equal=str(self.prefix))
# 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)
for p in networks:
children_size += p.size
Expand Down Expand Up @@ -321,11 +320,34 @@ def get_status_class(self):
def get_duplicates(self):
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):
"""
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
if self.family == 4 and self.prefix.prefixlen < 31 and not self.is_pool:
prefix_size -= 2
Expand All @@ -335,11 +357,11 @@ def get_utilization(self):
def new_subnet(self):
if self.family == 4:
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
if self.family == 6:
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


Expand Down

0 comments on commit d5bb37b

Please sign in to comment.