views.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
  2. from django.db import transaction
  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.response import Response
  8. from rest_framework.routers import APIRootView
  9. from rest_framework.views import APIView
  10. from dcim.models import Site
  11. from ipam import filtersets
  12. from ipam.models import *
  13. from netbox.api.viewsets import NetBoxModelViewSet
  14. from netbox.api.viewsets.mixins import ObjectValidationMixin
  15. from netbox.config import get_config
  16. from utilities.constants import ADVISORY_LOCK_KEYS
  17. from utilities.utils import count_related
  18. from . import serializers
  19. class IPAMRootView(APIRootView):
  20. """
  21. IPAM API root view
  22. """
  23. def get_view_name(self):
  24. return 'IPAM'
  25. #
  26. # Viewsets
  27. #
  28. class ASNViewSet(NetBoxModelViewSet):
  29. queryset = ASN.objects.prefetch_related('tenant', 'rir').annotate(site_count=count_related(Site, 'asns'))
  30. serializer_class = serializers.ASNSerializer
  31. filterset_class = filtersets.ASNFilterSet
  32. class VRFViewSet(NetBoxModelViewSet):
  33. queryset = VRF.objects.prefetch_related('tenant').prefetch_related(
  34. 'import_targets', 'export_targets', 'tags'
  35. ).annotate(
  36. ipaddress_count=count_related(IPAddress, 'vrf'),
  37. prefix_count=count_related(Prefix, 'vrf')
  38. )
  39. serializer_class = serializers.VRFSerializer
  40. filterset_class = filtersets.VRFFilterSet
  41. class RouteTargetViewSet(NetBoxModelViewSet):
  42. queryset = RouteTarget.objects.prefetch_related('tenant').prefetch_related('tags')
  43. serializer_class = serializers.RouteTargetSerializer
  44. filterset_class = filtersets.RouteTargetFilterSet
  45. class RIRViewSet(NetBoxModelViewSet):
  46. queryset = RIR.objects.annotate(
  47. aggregate_count=count_related(Aggregate, 'rir')
  48. ).prefetch_related('tags')
  49. serializer_class = serializers.RIRSerializer
  50. filterset_class = filtersets.RIRFilterSet
  51. class AggregateViewSet(NetBoxModelViewSet):
  52. queryset = Aggregate.objects.prefetch_related('rir').prefetch_related('tags')
  53. serializer_class = serializers.AggregateSerializer
  54. filterset_class = filtersets.AggregateFilterSet
  55. class RoleViewSet(NetBoxModelViewSet):
  56. queryset = Role.objects.annotate(
  57. prefix_count=count_related(Prefix, 'role'),
  58. vlan_count=count_related(VLAN, 'role')
  59. ).prefetch_related('tags')
  60. serializer_class = serializers.RoleSerializer
  61. filterset_class = filtersets.RoleFilterSet
  62. class PrefixViewSet(NetBoxModelViewSet):
  63. queryset = Prefix.objects.prefetch_related(
  64. 'site', 'vrf__tenant', 'tenant', 'vlan', 'role', 'tags'
  65. )
  66. serializer_class = serializers.PrefixSerializer
  67. filterset_class = filtersets.PrefixFilterSet
  68. parent_model = Prefix # AvailableIPsMixin
  69. def get_serializer_class(self):
  70. if self.action == "available_prefixes" and self.request.method == "POST":
  71. return serializers.PrefixLengthSerializer
  72. return super().get_serializer_class()
  73. class IPRangeViewSet(NetBoxModelViewSet):
  74. queryset = IPRange.objects.prefetch_related('vrf', 'role', 'tenant', 'tags')
  75. serializer_class = serializers.IPRangeSerializer
  76. filterset_class = filtersets.IPRangeFilterSet
  77. parent_model = IPRange # AvailableIPsMixin
  78. class IPAddressViewSet(NetBoxModelViewSet):
  79. queryset = IPAddress.objects.prefetch_related(
  80. 'vrf__tenant', 'tenant', 'nat_inside', 'nat_outside', 'tags', 'assigned_object'
  81. )
  82. serializer_class = serializers.IPAddressSerializer
  83. filterset_class = filtersets.IPAddressFilterSet
  84. class FHRPGroupViewSet(NetBoxModelViewSet):
  85. queryset = FHRPGroup.objects.prefetch_related('ip_addresses', 'tags')
  86. serializer_class = serializers.FHRPGroupSerializer
  87. filterset_class = filtersets.FHRPGroupFilterSet
  88. brief_prefetch_fields = ('ip_addresses',)
  89. class FHRPGroupAssignmentViewSet(NetBoxModelViewSet):
  90. queryset = FHRPGroupAssignment.objects.prefetch_related('group', 'interface')
  91. serializer_class = serializers.FHRPGroupAssignmentSerializer
  92. filterset_class = filtersets.FHRPGroupAssignmentFilterSet
  93. class VLANGroupViewSet(NetBoxModelViewSet):
  94. queryset = VLANGroup.objects.annotate(
  95. vlan_count=count_related(VLAN, 'group')
  96. ).prefetch_related('tags')
  97. serializer_class = serializers.VLANGroupSerializer
  98. filterset_class = filtersets.VLANGroupFilterSet
  99. class VLANViewSet(NetBoxModelViewSet):
  100. queryset = VLAN.objects.prefetch_related(
  101. 'site', 'group', 'tenant', 'role', 'tags'
  102. ).annotate(
  103. prefix_count=count_related(Prefix, 'vlan')
  104. )
  105. serializer_class = serializers.VLANSerializer
  106. filterset_class = filtersets.VLANFilterSet
  107. class ServiceTemplateViewSet(NetBoxModelViewSet):
  108. queryset = ServiceTemplate.objects.prefetch_related('tags')
  109. serializer_class = serializers.ServiceTemplateSerializer
  110. filterset_class = filtersets.ServiceTemplateFilterSet
  111. class ServiceViewSet(NetBoxModelViewSet):
  112. queryset = Service.objects.prefetch_related(
  113. 'device', 'virtual_machine', 'tags', 'ipaddresses'
  114. )
  115. serializer_class = serializers.ServiceSerializer
  116. filterset_class = filtersets.ServiceFilterSet
  117. #
  118. # Views
  119. #
  120. class AvailablePrefixesView(ObjectValidationMixin, APIView):
  121. queryset = Prefix.objects.all()
  122. @swagger_auto_schema(responses={200: serializers.AvailablePrefixSerializer(many=True)})
  123. def get(self, request, pk):
  124. prefix = get_object_or_404(Prefix.objects.restrict(request.user), pk=pk)
  125. available_prefixes = prefix.get_available_prefixes()
  126. serializer = serializers.AvailablePrefixSerializer(available_prefixes.iter_cidrs(), many=True, context={
  127. 'request': request,
  128. 'vrf': prefix.vrf,
  129. })
  130. return Response(serializer.data)
  131. @swagger_auto_schema(
  132. request_body=serializers.PrefixLengthSerializer,
  133. responses={201: serializers.PrefixSerializer(many=True)}
  134. )
  135. @advisory_lock(ADVISORY_LOCK_KEYS['available-prefixes'])
  136. def post(self, request, pk):
  137. self.queryset = self.queryset.restrict(request.user, 'add')
  138. prefix = get_object_or_404(Prefix.objects.restrict(request.user), pk=pk)
  139. available_prefixes = prefix.get_available_prefixes()
  140. # Validate Requested Prefixes' length
  141. serializer = serializers.PrefixLengthSerializer(
  142. data=request.data if isinstance(request.data, list) else [request.data],
  143. many=True,
  144. context={
  145. 'request': request,
  146. 'prefix': prefix,
  147. }
  148. )
  149. if not serializer.is_valid():
  150. return Response(
  151. serializer.errors,
  152. status=status.HTTP_400_BAD_REQUEST
  153. )
  154. requested_prefixes = serializer.validated_data
  155. # Allocate prefixes to the requested objects based on availability within the parent
  156. for i, requested_prefix in enumerate(requested_prefixes):
  157. # Find the first available prefix equal to or larger than the requested size
  158. for available_prefix in available_prefixes.iter_cidrs():
  159. if requested_prefix['prefix_length'] >= available_prefix.prefixlen:
  160. allocated_prefix = '{}/{}'.format(available_prefix.network, requested_prefix['prefix_length'])
  161. requested_prefix['prefix'] = allocated_prefix
  162. requested_prefix['vrf'] = prefix.vrf.pk if prefix.vrf else None
  163. break
  164. else:
  165. return Response(
  166. {
  167. "detail": "Insufficient space is available to accommodate the requested prefix size(s)"
  168. },
  169. status=status.HTTP_409_CONFLICT
  170. )
  171. # Remove the allocated prefix from the list of available prefixes
  172. available_prefixes.remove(allocated_prefix)
  173. # Initialize the serializer with a list or a single object depending on what was requested
  174. context = {'request': request}
  175. if isinstance(request.data, list):
  176. serializer = serializers.PrefixSerializer(data=requested_prefixes, many=True, context=context)
  177. else:
  178. serializer = serializers.PrefixSerializer(data=requested_prefixes[0], context=context)
  179. # Create the new Prefix(es)
  180. if serializer.is_valid():
  181. try:
  182. with transaction.atomic():
  183. created = serializer.save()
  184. self._validate_objects(created)
  185. except ObjectDoesNotExist:
  186. raise PermissionDenied()
  187. return Response(serializer.data, status=status.HTTP_201_CREATED)
  188. return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  189. class AvailableIPAddressesView(ObjectValidationMixin, APIView):
  190. queryset = IPAddress.objects.all()
  191. def get_parent(self, request, pk):
  192. raise NotImplemented()
  193. @swagger_auto_schema(responses={200: serializers.AvailableIPSerializer(many=True)})
  194. def get(self, request, pk):
  195. parent = self.get_parent(request, pk)
  196. config = get_config()
  197. PAGINATE_COUNT = config.PAGINATE_COUNT
  198. MAX_PAGE_SIZE = config.MAX_PAGE_SIZE
  199. try:
  200. limit = int(request.query_params.get('limit', PAGINATE_COUNT))
  201. except ValueError:
  202. limit = PAGINATE_COUNT
  203. if MAX_PAGE_SIZE:
  204. limit = min(limit, MAX_PAGE_SIZE)
  205. # Calculate available IPs within the parent
  206. ip_list = []
  207. for index, ip in enumerate(parent.get_available_ips(), start=1):
  208. ip_list.append(ip)
  209. if index == limit:
  210. break
  211. serializer = serializers.AvailableIPSerializer(ip_list, many=True, context={
  212. 'request': request,
  213. 'parent': parent,
  214. 'vrf': parent.vrf,
  215. })
  216. return Response(serializer.data)
  217. @swagger_auto_schema(
  218. request_body=serializers.AvailableIPSerializer,
  219. responses={201: serializers.IPAddressSerializer(many=True)}
  220. )
  221. @advisory_lock(ADVISORY_LOCK_KEYS['available-ips'])
  222. def post(self, request, pk):
  223. self.queryset = self.queryset.restrict(request.user, 'add')
  224. parent = self.get_parent(request, pk)
  225. # Normalize to a list of objects
  226. requested_ips = request.data if isinstance(request.data, list) else [request.data]
  227. # Determine if the requested number of IPs is available
  228. available_ips = parent.get_available_ips()
  229. if available_ips.size < len(requested_ips):
  230. return Response(
  231. {
  232. "detail": f"An insufficient number of IP addresses are available within {parent} "
  233. f"({len(requested_ips)} requested, {len(available_ips)} available)"
  234. },
  235. status=status.HTTP_409_CONFLICT
  236. )
  237. # Assign addresses from the list of available IPs and copy VRF assignment from the parent
  238. available_ips = iter(available_ips)
  239. for requested_ip in requested_ips:
  240. requested_ip['address'] = f'{next(available_ips)}/{parent.mask_length}'
  241. requested_ip['vrf'] = parent.vrf.pk if parent.vrf else None
  242. # Initialize the serializer with a list or a single object depending on what was requested
  243. context = {'request': request}
  244. if isinstance(request.data, list):
  245. serializer = serializers.IPAddressSerializer(data=requested_ips, many=True, context=context)
  246. else:
  247. serializer = serializers.IPAddressSerializer(data=requested_ips[0], context=context)
  248. # Create the new IP address(es)
  249. if serializer.is_valid():
  250. try:
  251. with transaction.atomic():
  252. created = serializer.save()
  253. self._validate_objects(created)
  254. except ObjectDoesNotExist:
  255. raise PermissionDenied()
  256. return Response(serializer.data, status=status.HTTP_201_CREATED)
  257. return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  258. class PrefixAvailableIPAddressesView(AvailableIPAddressesView):
  259. def get_parent(self, request, pk):
  260. return get_object_or_404(Prefix.objects.restrict(request.user), pk=pk)
  261. class IPRangeAvailableIPAddressesView(AvailableIPAddressesView):
  262. def get_parent(self, request, pk):
  263. return get_object_or_404(IPRange.objects.restrict(request.user), pk=pk)
  264. class AvailableVLANsView(ObjectValidationMixin, APIView):
  265. queryset = VLAN.objects.all()
  266. @swagger_auto_schema(responses={200: serializers.AvailableVLANSerializer(many=True)})
  267. def get(self, request, pk):
  268. vlangroup = get_object_or_404(VLANGroup.objects.restrict(request.user), pk=pk)
  269. available_vlans = vlangroup.get_available_vids()
  270. serializer = serializers.AvailableVLANSerializer(available_vlans, many=True, context={
  271. 'request': request,
  272. 'group': vlangroup,
  273. })
  274. return Response(serializer.data)
  275. @swagger_auto_schema(
  276. request_body=serializers.CreateAvailableVLANSerializer,
  277. responses={201: serializers.VLANSerializer(many=True)}
  278. )
  279. @advisory_lock(ADVISORY_LOCK_KEYS['available-vlans'])
  280. def post(self, request, pk):
  281. self.queryset = self.queryset.restrict(request.user, 'add')
  282. vlangroup = get_object_or_404(VLANGroup.objects.restrict(request.user), pk=pk)
  283. available_vlans = vlangroup.get_available_vids()
  284. many = isinstance(request.data, list)
  285. # Validate requested VLANs
  286. serializer = serializers.CreateAvailableVLANSerializer(
  287. data=request.data if many else [request.data],
  288. many=True,
  289. context={
  290. 'request': request,
  291. 'group': vlangroup,
  292. }
  293. )
  294. if not serializer.is_valid():
  295. return Response(
  296. serializer.errors,
  297. status=status.HTTP_400_BAD_REQUEST
  298. )
  299. requested_vlans = serializer.validated_data
  300. for i, requested_vlan in enumerate(requested_vlans):
  301. try:
  302. requested_vlan['vid'] = available_vlans.pop(0)
  303. requested_vlan['group'] = vlangroup.pk
  304. except IndexError:
  305. return Response({
  306. "detail": "The requested number of VLANs is not available"
  307. }, status=status.HTTP_409_CONFLICT)
  308. # Initialize the serializer with a list or a single object depending on what was requested
  309. context = {'request': request}
  310. if many:
  311. serializer = serializers.VLANSerializer(data=requested_vlans, many=True, context=context)
  312. else:
  313. serializer = serializers.VLANSerializer(data=requested_vlans[0], context=context)
  314. # Create the new VLAN(s)
  315. if serializer.is_valid():
  316. try:
  317. with transaction.atomic():
  318. created = serializer.save()
  319. self._validate_objects(created)
  320. except ObjectDoesNotExist:
  321. raise PermissionDenied()
  322. return Response(serializer.data, status=status.HTTP_201_CREATED)
  323. return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)