views.py 12 KB

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