views.py 11 KB

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