views.py 11 KB

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