views.py 11 KB

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