views.py 16 KB

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