views.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. from django.conf import settings
  2. from django.db.models import Count, Prefetch
  3. from django.shortcuts import get_object_or_404
  4. from django_pglocks import advisory_lock
  5. from drf_yasg.utils import swagger_auto_schema
  6. from rest_framework import status
  7. from rest_framework.decorators import action
  8. from rest_framework.response import Response
  9. from extras.api.views import CustomFieldModelViewSet
  10. from ipam import filters
  11. from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
  12. from utilities.api import ModelViewSet
  13. from utilities.constants import ADVISORY_LOCK_KEYS
  14. from utilities.utils import get_subquery
  15. from . import serializers
  16. #
  17. # VRFs
  18. #
  19. class VRFViewSet(CustomFieldModelViewSet):
  20. queryset = VRF.objects.prefetch_related('tenant').prefetch_related('tags').annotate(
  21. ipaddress_count=get_subquery(IPAddress, 'vrf'),
  22. prefix_count=get_subquery(Prefix, 'vrf')
  23. ).order_by(*VRF._meta.ordering)
  24. serializer_class = serializers.VRFSerializer
  25. filterset_class = filters.VRFFilterSet
  26. #
  27. # RIRs
  28. #
  29. class RIRViewSet(ModelViewSet):
  30. queryset = RIR.objects.annotate(
  31. aggregate_count=Count('aggregates')
  32. ).order_by(*RIR._meta.ordering)
  33. serializer_class = serializers.RIRSerializer
  34. filterset_class = filters.RIRFilterSet
  35. #
  36. # Aggregates
  37. #
  38. class AggregateViewSet(CustomFieldModelViewSet):
  39. queryset = Aggregate.objects.prefetch_related('rir').prefetch_related('tags')
  40. serializer_class = serializers.AggregateSerializer
  41. filterset_class = filters.AggregateFilterSet
  42. #
  43. # Roles
  44. #
  45. class RoleViewSet(ModelViewSet):
  46. queryset = Role.objects.annotate(
  47. prefix_count=get_subquery(Prefix, 'role'),
  48. vlan_count=get_subquery(VLAN, 'role')
  49. ).order_by(*Role._meta.ordering)
  50. serializer_class = serializers.RoleSerializer
  51. filterset_class = filters.RoleFilterSet
  52. #
  53. # Prefixes
  54. #
  55. class PrefixViewSet(CustomFieldModelViewSet):
  56. queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role', 'tags')
  57. serializer_class = serializers.PrefixSerializer
  58. filterset_class = filters.PrefixFilterSet
  59. def get_serializer_class(self):
  60. if self.action == "available_prefixes" and self.request.method == "POST":
  61. return serializers.PrefixLengthSerializer
  62. return super().get_serializer_class()
  63. @swagger_auto_schema(method='get', responses={200: serializers.AvailablePrefixSerializer(many=True)})
  64. @swagger_auto_schema(method='post', responses={201: serializers.AvailablePrefixSerializer(many=True)})
  65. @action(detail=True, url_path='available-prefixes', methods=['get', 'post'])
  66. @advisory_lock(ADVISORY_LOCK_KEYS['available-prefixes'])
  67. def available_prefixes(self, request, pk=None):
  68. """
  69. A convenience method for returning available child prefixes within a parent.
  70. The advisory lock decorator uses a PostgreSQL advisory lock to prevent this API from being
  71. invoked in parallel, which results in a race condition where multiple insertions can occur.
  72. """
  73. prefix = get_object_or_404(self.queryset, pk=pk)
  74. available_prefixes = prefix.get_available_prefixes()
  75. if request.method == 'POST':
  76. # Validate Requested Prefixes' length
  77. serializer = serializers.PrefixLengthSerializer(
  78. data=request.data if isinstance(request.data, list) else [request.data],
  79. many=True,
  80. context={
  81. 'request': request,
  82. 'prefix': prefix,
  83. }
  84. )
  85. if not serializer.is_valid():
  86. return Response(
  87. serializer.errors,
  88. status=status.HTTP_400_BAD_REQUEST
  89. )
  90. requested_prefixes = serializer.validated_data
  91. # Allocate prefixes to the requested objects based on availability within the parent
  92. for i, requested_prefix in enumerate(requested_prefixes):
  93. # Find the first available prefix equal to or larger than the requested size
  94. for available_prefix in available_prefixes.iter_cidrs():
  95. if requested_prefix['prefix_length'] >= available_prefix.prefixlen:
  96. allocated_prefix = '{}/{}'.format(available_prefix.network, requested_prefix['prefix_length'])
  97. requested_prefix['prefix'] = allocated_prefix
  98. requested_prefix['vrf'] = prefix.vrf.pk if prefix.vrf else None
  99. break
  100. else:
  101. return Response(
  102. {
  103. "detail": "Insufficient space is available to accommodate the requested prefix size(s)"
  104. },
  105. status=status.HTTP_204_NO_CONTENT
  106. )
  107. # Remove the allocated prefix from the list of available prefixes
  108. available_prefixes.remove(allocated_prefix)
  109. # Initialize the serializer with a list or a single object depending on what was requested
  110. context = {'request': request}
  111. if isinstance(request.data, list):
  112. serializer = serializers.PrefixSerializer(data=requested_prefixes, many=True, context=context)
  113. else:
  114. serializer = serializers.PrefixSerializer(data=requested_prefixes[0], context=context)
  115. # Create the new Prefix(es)
  116. if serializer.is_valid():
  117. serializer.save()
  118. return Response(serializer.data, status=status.HTTP_201_CREATED)
  119. return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  120. else:
  121. serializer = serializers.AvailablePrefixSerializer(available_prefixes.iter_cidrs(), many=True, context={
  122. 'request': request,
  123. 'vrf': prefix.vrf,
  124. })
  125. return Response(serializer.data)
  126. @swagger_auto_schema(method='get', responses={200: serializers.AvailableIPSerializer(many=True)})
  127. @swagger_auto_schema(method='post', responses={201: serializers.AvailableIPSerializer(many=True)},
  128. request_body=serializers.AvailableIPSerializer(many=False))
  129. @action(detail=True, url_path='available-ips', methods=['get', 'post'], queryset=IPAddress.objects.all())
  130. @advisory_lock(ADVISORY_LOCK_KEYS['available-ips'])
  131. def available_ips(self, request, pk=None):
  132. """
  133. A convenience method for returning available IP addresses within a prefix. By default, the number of IPs
  134. returned will be equivalent to PAGINATE_COUNT. An arbitrary limit (up to MAX_PAGE_SIZE, if set) may be passed,
  135. however results will not be paginated.
  136. The advisory lock decorator uses a PostgreSQL advisory lock to prevent this API from being
  137. invoked in parallel, which results in a race condition where multiple insertions can occur.
  138. """
  139. prefix = get_object_or_404(Prefix.objects.restrict(request.user), pk=pk)
  140. # Create the next available IP within the prefix
  141. if request.method == 'POST':
  142. # Normalize to a list of objects
  143. requested_ips = request.data if isinstance(request.data, list) else [request.data]
  144. # Determine if the requested number of IPs is available
  145. available_ips = prefix.get_available_ips()
  146. if available_ips.size < len(requested_ips):
  147. return Response(
  148. {
  149. "detail": "An insufficient number of IP addresses are available within the prefix {} ({} "
  150. "requested, {} available)".format(prefix, len(requested_ips), len(available_ips))
  151. },
  152. status=status.HTTP_204_NO_CONTENT
  153. )
  154. # Assign addresses from the list of available IPs and copy VRF assignment from the parent prefix
  155. available_ips = iter(available_ips)
  156. prefix_length = prefix.prefix.prefixlen
  157. for requested_ip in requested_ips:
  158. requested_ip['address'] = '{}/{}'.format(next(available_ips), prefix_length)
  159. requested_ip['vrf'] = prefix.vrf.pk if prefix.vrf else None
  160. # Initialize the serializer with a list or a single object depending on what was requested
  161. context = {'request': request}
  162. if isinstance(request.data, list):
  163. serializer = serializers.IPAddressSerializer(data=requested_ips, many=True, context=context)
  164. else:
  165. serializer = serializers.IPAddressSerializer(data=requested_ips[0], context=context)
  166. # Create the new IP address(es)
  167. if serializer.is_valid():
  168. serializer.save()
  169. return Response(serializer.data, status=status.HTTP_201_CREATED)
  170. return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  171. # Determine the maximum number of IPs to return
  172. else:
  173. try:
  174. limit = int(request.query_params.get('limit', settings.PAGINATE_COUNT))
  175. except ValueError:
  176. limit = settings.PAGINATE_COUNT
  177. if settings.MAX_PAGE_SIZE:
  178. limit = min(limit, settings.MAX_PAGE_SIZE)
  179. # Calculate available IPs within the prefix
  180. ip_list = []
  181. for index, ip in enumerate(prefix.get_available_ips(), start=1):
  182. ip_list.append(ip)
  183. if index == limit:
  184. break
  185. serializer = serializers.AvailableIPSerializer(ip_list, many=True, context={
  186. 'request': request,
  187. 'prefix': prefix.prefix,
  188. 'vrf': prefix.vrf,
  189. })
  190. return Response(serializer.data)
  191. #
  192. # IP addresses
  193. #
  194. class IPAddressViewSet(CustomFieldModelViewSet):
  195. queryset = IPAddress.objects.prefetch_related(
  196. 'vrf__tenant', 'tenant', 'nat_inside', 'nat_outside', 'tags',
  197. )
  198. serializer_class = serializers.IPAddressSerializer
  199. filterset_class = filters.IPAddressFilterSet
  200. #
  201. # VLAN groups
  202. #
  203. class VLANGroupViewSet(ModelViewSet):
  204. queryset = VLANGroup.objects.prefetch_related('site').annotate(
  205. vlan_count=Count('vlans')
  206. ).order_by(*VLANGroup._meta.ordering)
  207. serializer_class = serializers.VLANGroupSerializer
  208. filterset_class = filters.VLANGroupFilterSet
  209. #
  210. # VLANs
  211. #
  212. class VLANViewSet(CustomFieldModelViewSet):
  213. queryset = VLAN.objects.prefetch_related(
  214. 'site', 'group', 'tenant', 'role', 'tags'
  215. ).annotate(
  216. prefix_count=get_subquery(Prefix, 'vlan')
  217. ).order_by(*VLAN._meta.ordering)
  218. serializer_class = serializers.VLANSerializer
  219. filterset_class = filters.VLANFilterSet
  220. #
  221. # Services
  222. #
  223. class ServiceViewSet(ModelViewSet):
  224. queryset = Service.objects.prefetch_related(
  225. Prefetch('ipaddresses', queryset=IPAddress.objects.unrestricted()),
  226. 'device', 'virtual_machine', 'tags'
  227. )
  228. serializer_class = serializers.ServiceSerializer
  229. filterset_class = filters.ServiceFilterSet