views.py 12 KB

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