mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-23 04:22:01 -06:00
Extended prefix 'available-ips' endpoint to accept multiple objects (related to #1553)
This commit is contained in:
parent
c3e5106b04
commit
4f2dc50b5c
@ -96,28 +96,41 @@ class PrefixViewSet(CustomFieldModelViewSet):
|
|||||||
if not request.user.has_perm('ipam.add_ipaddress'):
|
if not request.user.has_perm('ipam.add_ipaddress'):
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
|
|
||||||
# Find the first available IP address in the prefix
|
# Determine if the requested number of IPs is available
|
||||||
try:
|
requested_count = len(request.data) if isinstance(request.data, list) else 1
|
||||||
ipaddress = list(prefix.get_available_ips())[0]
|
available_ips = list(prefix.get_available_ips())
|
||||||
except IndexError:
|
if len(available_ips) < requested_count:
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
"detail": "There are no available IPs within this prefix ({})".format(prefix)
|
"detail": "An insufficient number of IP addresses are available within the prefix {} ({} "
|
||||||
|
"requested, {} available)".format(prefix, requested_count, len(available_ips))
|
||||||
},
|
},
|
||||||
status=status.HTTP_400_BAD_REQUEST
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create the new IP address
|
# Deserializing multiple IP addresses
|
||||||
data = request.data.copy()
|
if isinstance(request.data, list):
|
||||||
data['address'] = '{}/{}'.format(ipaddress, prefix.prefix.prefixlen)
|
request_data = list(request.data) # Need a mutable copy
|
||||||
data['vrf'] = prefix.vrf.pk if prefix.vrf else None
|
for obj in request_data:
|
||||||
serializer = serializers.WritableIPAddressSerializer(data=data)
|
obj['address'] = available_ips.pop(0)
|
||||||
|
obj['vrf'] = prefix.vrf.pk if prefix.vrf else None
|
||||||
|
serializer = serializers.WritableIPAddressSerializer(data=request_data, many=True)
|
||||||
|
|
||||||
|
# Deserializing a single IP address
|
||||||
|
else:
|
||||||
|
request_data = request.data.copy() # Need a mutable copy
|
||||||
|
request_data['address'] = available_ips.pop(0)
|
||||||
|
request_data['vrf'] = prefix.vrf.pk if prefix.vrf else None
|
||||||
|
serializer = serializers.WritableIPAddressSerializer(data=request_data)
|
||||||
|
|
||||||
|
# Create the new IP address(es)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer.save()
|
serializer.save()
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
# Determine the maximum amount of IPs to return
|
# Determine the maximum number of IPs to return
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
limit = int(request.query_params.get('limit', settings.PAGINATE_COUNT))
|
limit = int(request.query_params.get('limit', settings.PAGINATE_COUNT))
|
||||||
|
@ -365,7 +365,7 @@ class PrefixTest(HttpStatusMixin, APITestCase):
|
|||||||
self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
|
self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
|
||||||
self.assertEqual(Prefix.objects.count(), 2)
|
self.assertEqual(Prefix.objects.count(), 2)
|
||||||
|
|
||||||
def test_available_ips(self):
|
def test_list_available_ips(self):
|
||||||
|
|
||||||
prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/29'), is_pool=True)
|
prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/29'), is_pool=True)
|
||||||
url = reverse('ipam-api:prefix-available-ips', kwargs={'pk': prefix.pk})
|
url = reverse('ipam-api:prefix-available-ips', kwargs={'pk': prefix.pk})
|
||||||
@ -380,12 +380,19 @@ class PrefixTest(HttpStatusMixin, APITestCase):
|
|||||||
response = self.client.get(url, **self.header)
|
response = self.client.get(url, **self.header)
|
||||||
self.assertEqual(len(response.data), 6) # 8 - 2 because prefix.is_pool = False
|
self.assertEqual(len(response.data), 6) # 8 - 2 because prefix.is_pool = False
|
||||||
|
|
||||||
# Create all six available IPs
|
def test_create_single_available_ip(self):
|
||||||
for i in range(6):
|
|
||||||
|
prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/30'), is_pool=True)
|
||||||
|
url = reverse('ipam-api:prefix-available-ips', kwargs={'pk': prefix.pk})
|
||||||
|
|
||||||
|
# Create all four available IPs with individual requests
|
||||||
|
for i in range(1, 5):
|
||||||
data = {
|
data = {
|
||||||
'description': 'Test IP {}'.format(i)
|
'description': 'Test IP {}'.format(i)
|
||||||
}
|
}
|
||||||
response = self.client.post(url, data, **self.header)
|
response = self.client.post(url, data, **self.header)
|
||||||
|
if response.status_code != status.HTTP_201_CREATED:
|
||||||
|
assert False, response.content
|
||||||
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
||||||
self.assertEqual(response.data['description'], data['description'])
|
self.assertEqual(response.data['description'], data['description'])
|
||||||
|
|
||||||
@ -394,6 +401,27 @@ class PrefixTest(HttpStatusMixin, APITestCase):
|
|||||||
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertIn('detail', response.data)
|
self.assertIn('detail', response.data)
|
||||||
|
|
||||||
|
def test_create_multiple_available_ips(self):
|
||||||
|
|
||||||
|
prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/29'), is_pool=True)
|
||||||
|
url = reverse('ipam-api:prefix-available-ips', kwargs={'pk': prefix.pk})
|
||||||
|
|
||||||
|
# Try to create nine IPs (only eight are available)
|
||||||
|
data = [{'description': 'Test IP {}'.format(i)} for i in range(1, 10)] # 9 IPs
|
||||||
|
response = self.client.post(url, data, format='json', **self.header)
|
||||||
|
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
||||||
|
self.assertIn('detail', response.data)
|
||||||
|
|
||||||
|
# Verify that no IPs were created (eight are still available)
|
||||||
|
response = self.client.get(url, **self.header)
|
||||||
|
self.assertEqual(len(response.data), 8)
|
||||||
|
|
||||||
|
# Create all eight available IPs in a single request
|
||||||
|
data = [{'description': 'Test IP {}'.format(i)} for i in range(1, 9)] # 8 IPs
|
||||||
|
response = self.client.post(url, data, format='json', **self.header)
|
||||||
|
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
||||||
|
self.assertEqual(len(response.data), 8)
|
||||||
|
|
||||||
|
|
||||||
class IPAddressTest(HttpStatusMixin, APITestCase):
|
class IPAddressTest(HttpStatusMixin, APITestCase):
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user