mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-22 03:56:53 -06:00
#1246: Initial work on an API endpoint to retrieve available IPs for a prefix
This commit is contained in:
parent
5940feb64b
commit
d5bb37b552
@ -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
|
||||||
#
|
#
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user