views.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  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. class FHRPGroupViewSet(NetBoxModelViewSet):
  90. queryset = FHRPGroup.objects.prefetch_related('ip_addresses', 'tags')
  91. serializer_class = serializers.FHRPGroupSerializer
  92. filterset_class = filtersets.FHRPGroupFilterSet
  93. brief_prefetch_fields = ('ip_addresses',)
  94. class FHRPGroupAssignmentViewSet(NetBoxModelViewSet):
  95. queryset = FHRPGroupAssignment.objects.prefetch_related('group', 'interface')
  96. serializer_class = serializers.FHRPGroupAssignmentSerializer
  97. filterset_class = filtersets.FHRPGroupAssignmentFilterSet
  98. class VLANGroupViewSet(NetBoxModelViewSet):
  99. queryset = VLANGroup.objects.annotate(
  100. vlan_count=count_related(VLAN, 'group')
  101. ).prefetch_related('tags')
  102. serializer_class = serializers.VLANGroupSerializer
  103. filterset_class = filtersets.VLANGroupFilterSet
  104. class VLANViewSet(NetBoxModelViewSet):
  105. queryset = VLAN.objects.prefetch_related(
  106. 'site', 'group', 'tenant', 'role', 'tags'
  107. ).annotate(
  108. prefix_count=count_related(Prefix, 'vlan')
  109. )
  110. serializer_class = serializers.VLANSerializer
  111. filterset_class = filtersets.VLANFilterSet
  112. class ServiceTemplateViewSet(NetBoxModelViewSet):
  113. queryset = ServiceTemplate.objects.prefetch_related('tags')
  114. serializer_class = serializers.ServiceTemplateSerializer
  115. filterset_class = filtersets.ServiceTemplateFilterSet
  116. class ServiceViewSet(NetBoxModelViewSet):
  117. queryset = Service.objects.prefetch_related(
  118. 'device', 'virtual_machine', 'tags', 'ipaddresses'
  119. )
  120. serializer_class = serializers.ServiceSerializer
  121. filterset_class = filtersets.ServiceFilterSet
  122. class L2VPNViewSet(NetBoxModelViewSet):
  123. queryset = L2VPN.objects.prefetch_related('import_targets', 'export_targets', 'tenant', 'tags')
  124. serializer_class = serializers.L2VPNSerializer
  125. filterset_class = filtersets.L2VPNFilterSet
  126. class L2VPNTerminationViewSet(NetBoxModelViewSet):
  127. queryset = L2VPNTermination.objects.prefetch_related('assigned_object')
  128. serializer_class = serializers.L2VPNTerminationSerializer
  129. filterset_class = filtersets.L2VPNTerminationFilterSet
  130. #
  131. # Views
  132. #
  133. def get_results_limit(request):
  134. """
  135. Return the lesser of the specified limit (if any) and the configured MAX_PAGE_SIZE.
  136. """
  137. config = get_config()
  138. try:
  139. limit = int(request.query_params.get('limit', config.PAGINATE_COUNT)) or config.MAX_PAGE_SIZE
  140. except ValueError:
  141. limit = config.PAGINATE_COUNT
  142. if config.MAX_PAGE_SIZE:
  143. limit = min(limit, config.MAX_PAGE_SIZE)
  144. return limit
  145. class AvailablePrefixesView(ObjectValidationMixin, APIView):
  146. queryset = Prefix.objects.all()
  147. @swagger_auto_schema(responses={200: serializers.AvailablePrefixSerializer(many=True)})
  148. def get(self, request, pk):
  149. prefix = get_object_or_404(Prefix.objects.restrict(request.user), pk=pk)
  150. available_prefixes = prefix.get_available_prefixes()
  151. serializer = serializers.AvailablePrefixSerializer(available_prefixes.iter_cidrs(), many=True, context={
  152. 'request': request,
  153. 'vrf': prefix.vrf,
  154. })
  155. return Response(serializer.data)
  156. @swagger_auto_schema(
  157. request_body=serializers.PrefixLengthSerializer,
  158. responses={201: serializers.PrefixSerializer(many=True)}
  159. )
  160. @advisory_lock(ADVISORY_LOCK_KEYS['available-prefixes'])
  161. def post(self, request, pk):
  162. self.queryset = self.queryset.restrict(request.user, 'add')
  163. prefix = get_object_or_404(Prefix.objects.restrict(request.user), pk=pk)
  164. available_prefixes = prefix.get_available_prefixes()
  165. # Validate Requested Prefixes' length
  166. serializer = serializers.PrefixLengthSerializer(
  167. data=request.data if isinstance(request.data, list) else [request.data],
  168. many=True,
  169. context={
  170. 'request': request,
  171. 'prefix': prefix,
  172. }
  173. )
  174. if not serializer.is_valid():
  175. return Response(
  176. serializer.errors,
  177. status=status.HTTP_400_BAD_REQUEST
  178. )
  179. requested_prefixes = serializer.validated_data
  180. # Allocate prefixes to the requested objects based on availability within the parent
  181. for i, requested_prefix in enumerate(requested_prefixes):
  182. # Find the first available prefix equal to or larger than the requested size
  183. for available_prefix in available_prefixes.iter_cidrs():
  184. if requested_prefix['prefix_length'] >= available_prefix.prefixlen:
  185. allocated_prefix = '{}/{}'.format(available_prefix.network, requested_prefix['prefix_length'])
  186. requested_prefix['prefix'] = allocated_prefix
  187. requested_prefix['vrf'] = prefix.vrf.pk if prefix.vrf else None
  188. break
  189. else:
  190. return Response(
  191. {
  192. "detail": "Insufficient space is available to accommodate the requested prefix size(s)"
  193. },
  194. status=status.HTTP_409_CONFLICT
  195. )
  196. # Remove the allocated prefix from the list of available prefixes
  197. available_prefixes.remove(allocated_prefix)
  198. # Initialize the serializer with a list or a single object depending on what was requested
  199. context = {'request': request}
  200. if isinstance(request.data, list):
  201. serializer = serializers.PrefixSerializer(data=requested_prefixes, many=True, context=context)
  202. else:
  203. serializer = serializers.PrefixSerializer(data=requested_prefixes[0], context=context)
  204. # Create the new Prefix(es)
  205. if serializer.is_valid():
  206. try:
  207. with transaction.atomic():
  208. created = serializer.save()
  209. self._validate_objects(created)
  210. except ObjectDoesNotExist:
  211. raise PermissionDenied()
  212. return Response(serializer.data, status=status.HTTP_201_CREATED)
  213. return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  214. class AvailableIPAddressesView(ObjectValidationMixin, APIView):
  215. queryset = IPAddress.objects.all()
  216. def get_parent(self, request, pk):
  217. raise NotImplemented()
  218. @swagger_auto_schema(responses={200: serializers.AvailableIPSerializer(many=True)})
  219. def get(self, request, pk):
  220. parent = self.get_parent(request, pk)
  221. limit = get_results_limit(request)
  222. # Calculate available IPs within the parent
  223. ip_list = []
  224. for index, ip in enumerate(parent.get_available_ips(), start=1):
  225. ip_list.append(ip)
  226. if index == limit:
  227. break
  228. serializer = serializers.AvailableIPSerializer(ip_list, many=True, context={
  229. 'request': request,
  230. 'parent': parent,
  231. 'vrf': parent.vrf,
  232. })
  233. return Response(serializer.data)
  234. @swagger_auto_schema(
  235. request_body=serializers.AvailableIPSerializer,
  236. responses={201: serializers.IPAddressSerializer(many=True)}
  237. )
  238. @advisory_lock(ADVISORY_LOCK_KEYS['available-ips'])
  239. def post(self, request, pk):
  240. self.queryset = self.queryset.restrict(request.user, 'add')
  241. parent = self.get_parent(request, pk)
  242. # Normalize to a list of objects
  243. requested_ips = request.data if isinstance(request.data, list) else [request.data]
  244. # Determine if the requested number of IPs is available
  245. available_ips = parent.get_available_ips()
  246. if available_ips.size < len(requested_ips):
  247. return Response(
  248. {
  249. "detail": f"An insufficient number of IP addresses are available within {parent} "
  250. f"({len(requested_ips)} requested, {len(available_ips)} available)"
  251. },
  252. status=status.HTTP_409_CONFLICT
  253. )
  254. # Assign addresses from the list of available IPs and copy VRF assignment from the parent
  255. available_ips = iter(available_ips)
  256. for requested_ip in requested_ips:
  257. requested_ip['address'] = f'{next(available_ips)}/{parent.mask_length}'
  258. requested_ip['vrf'] = parent.vrf.pk if parent.vrf else None
  259. # Initialize the serializer with a list or a single object depending on what was requested
  260. context = {'request': request}
  261. if isinstance(request.data, list):
  262. serializer = serializers.IPAddressSerializer(data=requested_ips, many=True, context=context)
  263. else:
  264. serializer = serializers.IPAddressSerializer(data=requested_ips[0], context=context)
  265. # Create the new IP address(es)
  266. if serializer.is_valid():
  267. try:
  268. with transaction.atomic():
  269. created = serializer.save()
  270. self._validate_objects(created)
  271. except ObjectDoesNotExist:
  272. raise PermissionDenied()
  273. return Response(serializer.data, status=status.HTTP_201_CREATED)
  274. return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  275. class PrefixAvailableIPAddressesView(AvailableIPAddressesView):
  276. def get_parent(self, request, pk):
  277. return get_object_or_404(Prefix.objects.restrict(request.user), pk=pk)
  278. class IPRangeAvailableIPAddressesView(AvailableIPAddressesView):
  279. def get_parent(self, request, pk):
  280. return get_object_or_404(IPRange.objects.restrict(request.user), pk=pk)
  281. class AvailableVLANsView(ObjectValidationMixin, APIView):
  282. queryset = VLAN.objects.all()
  283. @swagger_auto_schema(responses={200: serializers.AvailableVLANSerializer(many=True)})
  284. def get(self, request, pk):
  285. vlangroup = get_object_or_404(VLANGroup.objects.restrict(request.user), pk=pk)
  286. limit = get_results_limit(request)
  287. available_vlans = vlangroup.get_available_vids()[:limit]
  288. serializer = serializers.AvailableVLANSerializer(available_vlans, many=True, context={
  289. 'request': request,
  290. 'group': vlangroup,
  291. })
  292. return Response(serializer.data)
  293. @swagger_auto_schema(
  294. request_body=serializers.CreateAvailableVLANSerializer,
  295. responses={201: serializers.VLANSerializer(many=True)}
  296. )
  297. @advisory_lock(ADVISORY_LOCK_KEYS['available-vlans'])
  298. def post(self, request, pk):
  299. self.queryset = self.queryset.restrict(request.user, 'add')
  300. vlangroup = get_object_or_404(VLANGroup.objects.restrict(request.user), pk=pk)
  301. available_vlans = vlangroup.get_available_vids()
  302. many = isinstance(request.data, list)
  303. # Validate requested VLANs
  304. serializer = serializers.CreateAvailableVLANSerializer(
  305. data=request.data if many else [request.data],
  306. many=True,
  307. context={
  308. 'request': request,
  309. 'group': vlangroup,
  310. }
  311. )
  312. if not serializer.is_valid():
  313. return Response(
  314. serializer.errors,
  315. status=status.HTTP_400_BAD_REQUEST
  316. )
  317. requested_vlans = serializer.validated_data
  318. for i, requested_vlan in enumerate(requested_vlans):
  319. try:
  320. requested_vlan['vid'] = available_vlans.pop(0)
  321. requested_vlan['group'] = vlangroup.pk
  322. except IndexError:
  323. return Response({
  324. "detail": "The requested number of VLANs is not available"
  325. }, status=status.HTTP_409_CONFLICT)
  326. # Initialize the serializer with a list or a single object depending on what was requested
  327. context = {'request': request}
  328. if many:
  329. serializer = serializers.VLANSerializer(data=requested_vlans, many=True, context=context)
  330. else:
  331. serializer = serializers.VLANSerializer(data=requested_vlans[0], context=context)
  332. # Create the new VLAN(s)
  333. if serializer.is_valid():
  334. try:
  335. with transaction.atomic():
  336. created = serializer.save()
  337. self._validate_objects(created)
  338. except ObjectDoesNotExist:
  339. raise PermissionDenied()
  340. return Response(serializer.data, status=status.HTTP_201_CREATED)
  341. return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)