views.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  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. class AvailablePrefixesView(ObjectValidationMixin, APIView):
  134. queryset = Prefix.objects.all()
  135. @swagger_auto_schema(responses={200: serializers.AvailablePrefixSerializer(many=True)})
  136. def get(self, request, pk):
  137. prefix = get_object_or_404(Prefix.objects.restrict(request.user), pk=pk)
  138. available_prefixes = prefix.get_available_prefixes()
  139. serializer = serializers.AvailablePrefixSerializer(available_prefixes.iter_cidrs(), many=True, context={
  140. 'request': request,
  141. 'vrf': prefix.vrf,
  142. })
  143. return Response(serializer.data)
  144. @swagger_auto_schema(
  145. request_body=serializers.PrefixLengthSerializer,
  146. responses={201: serializers.PrefixSerializer(many=True)}
  147. )
  148. @advisory_lock(ADVISORY_LOCK_KEYS['available-prefixes'])
  149. def post(self, request, pk):
  150. self.queryset = self.queryset.restrict(request.user, 'add')
  151. prefix = get_object_or_404(Prefix.objects.restrict(request.user), pk=pk)
  152. available_prefixes = prefix.get_available_prefixes()
  153. # Validate Requested Prefixes' length
  154. serializer = serializers.PrefixLengthSerializer(
  155. data=request.data if isinstance(request.data, list) else [request.data],
  156. many=True,
  157. context={
  158. 'request': request,
  159. 'prefix': prefix,
  160. }
  161. )
  162. if not serializer.is_valid():
  163. return Response(
  164. serializer.errors,
  165. status=status.HTTP_400_BAD_REQUEST
  166. )
  167. requested_prefixes = serializer.validated_data
  168. # Allocate prefixes to the requested objects based on availability within the parent
  169. for i, requested_prefix in enumerate(requested_prefixes):
  170. # Find the first available prefix equal to or larger than the requested size
  171. for available_prefix in available_prefixes.iter_cidrs():
  172. if requested_prefix['prefix_length'] >= available_prefix.prefixlen:
  173. allocated_prefix = '{}/{}'.format(available_prefix.network, requested_prefix['prefix_length'])
  174. requested_prefix['prefix'] = allocated_prefix
  175. requested_prefix['vrf'] = prefix.vrf.pk if prefix.vrf else None
  176. break
  177. else:
  178. return Response(
  179. {
  180. "detail": "Insufficient space is available to accommodate the requested prefix size(s)"
  181. },
  182. status=status.HTTP_409_CONFLICT
  183. )
  184. # Remove the allocated prefix from the list of available prefixes
  185. available_prefixes.remove(allocated_prefix)
  186. # Initialize the serializer with a list or a single object depending on what was requested
  187. context = {'request': request}
  188. if isinstance(request.data, list):
  189. serializer = serializers.PrefixSerializer(data=requested_prefixes, many=True, context=context)
  190. else:
  191. serializer = serializers.PrefixSerializer(data=requested_prefixes[0], context=context)
  192. # Create the new Prefix(es)
  193. if serializer.is_valid():
  194. try:
  195. with transaction.atomic():
  196. created = serializer.save()
  197. self._validate_objects(created)
  198. except ObjectDoesNotExist:
  199. raise PermissionDenied()
  200. return Response(serializer.data, status=status.HTTP_201_CREATED)
  201. return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  202. class AvailableIPAddressesView(ObjectValidationMixin, APIView):
  203. queryset = IPAddress.objects.all()
  204. def get_parent(self, request, pk):
  205. raise NotImplemented()
  206. @swagger_auto_schema(responses={200: serializers.AvailableIPSerializer(many=True)})
  207. def get(self, request, pk):
  208. parent = self.get_parent(request, pk)
  209. config = get_config()
  210. PAGINATE_COUNT = config.PAGINATE_COUNT
  211. MAX_PAGE_SIZE = config.MAX_PAGE_SIZE
  212. try:
  213. limit = int(request.query_params.get('limit', PAGINATE_COUNT))
  214. except ValueError:
  215. limit = PAGINATE_COUNT
  216. if MAX_PAGE_SIZE:
  217. limit = min(limit, MAX_PAGE_SIZE)
  218. # Calculate available IPs within the parent
  219. ip_list = []
  220. for index, ip in enumerate(parent.get_available_ips(), start=1):
  221. ip_list.append(ip)
  222. if index == limit:
  223. break
  224. serializer = serializers.AvailableIPSerializer(ip_list, many=True, context={
  225. 'request': request,
  226. 'parent': parent,
  227. 'vrf': parent.vrf,
  228. })
  229. return Response(serializer.data)
  230. @swagger_auto_schema(
  231. request_body=serializers.AvailableIPSerializer,
  232. responses={201: serializers.IPAddressSerializer(many=True)}
  233. )
  234. @advisory_lock(ADVISORY_LOCK_KEYS['available-ips'])
  235. def post(self, request, pk):
  236. self.queryset = self.queryset.restrict(request.user, 'add')
  237. parent = self.get_parent(request, pk)
  238. # Normalize to a list of objects
  239. requested_ips = request.data if isinstance(request.data, list) else [request.data]
  240. # Determine if the requested number of IPs is available
  241. available_ips = parent.get_available_ips()
  242. if available_ips.size < len(requested_ips):
  243. return Response(
  244. {
  245. "detail": f"An insufficient number of IP addresses are available within {parent} "
  246. f"({len(requested_ips)} requested, {len(available_ips)} available)"
  247. },
  248. status=status.HTTP_409_CONFLICT
  249. )
  250. # Assign addresses from the list of available IPs and copy VRF assignment from the parent
  251. available_ips = iter(available_ips)
  252. for requested_ip in requested_ips:
  253. requested_ip['address'] = f'{next(available_ips)}/{parent.mask_length}'
  254. requested_ip['vrf'] = parent.vrf.pk if parent.vrf else None
  255. # Initialize the serializer with a list or a single object depending on what was requested
  256. context = {'request': request}
  257. if isinstance(request.data, list):
  258. serializer = serializers.IPAddressSerializer(data=requested_ips, many=True, context=context)
  259. else:
  260. serializer = serializers.IPAddressSerializer(data=requested_ips[0], context=context)
  261. # Create the new IP address(es)
  262. if serializer.is_valid():
  263. try:
  264. with transaction.atomic():
  265. created = serializer.save()
  266. self._validate_objects(created)
  267. except ObjectDoesNotExist:
  268. raise PermissionDenied()
  269. return Response(serializer.data, status=status.HTTP_201_CREATED)
  270. return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  271. class PrefixAvailableIPAddressesView(AvailableIPAddressesView):
  272. def get_parent(self, request, pk):
  273. return get_object_or_404(Prefix.objects.restrict(request.user), pk=pk)
  274. class IPRangeAvailableIPAddressesView(AvailableIPAddressesView):
  275. def get_parent(self, request, pk):
  276. return get_object_or_404(IPRange.objects.restrict(request.user), pk=pk)
  277. class AvailableVLANsView(ObjectValidationMixin, APIView):
  278. queryset = VLAN.objects.all()
  279. @swagger_auto_schema(responses={200: serializers.AvailableVLANSerializer(many=True)})
  280. def get(self, request, pk):
  281. vlangroup = get_object_or_404(VLANGroup.objects.restrict(request.user), pk=pk)
  282. available_vlans = vlangroup.get_available_vids()
  283. serializer = serializers.AvailableVLANSerializer(available_vlans, many=True, context={
  284. 'request': request,
  285. 'group': vlangroup,
  286. })
  287. return Response(serializer.data)
  288. @swagger_auto_schema(
  289. request_body=serializers.CreateAvailableVLANSerializer,
  290. responses={201: serializers.VLANSerializer(many=True)}
  291. )
  292. @advisory_lock(ADVISORY_LOCK_KEYS['available-vlans'])
  293. def post(self, request, pk):
  294. self.queryset = self.queryset.restrict(request.user, 'add')
  295. vlangroup = get_object_or_404(VLANGroup.objects.restrict(request.user), pk=pk)
  296. available_vlans = vlangroup.get_available_vids()
  297. many = isinstance(request.data, list)
  298. # Validate requested VLANs
  299. serializer = serializers.CreateAvailableVLANSerializer(
  300. data=request.data if many else [request.data],
  301. many=True,
  302. context={
  303. 'request': request,
  304. 'group': vlangroup,
  305. }
  306. )
  307. if not serializer.is_valid():
  308. return Response(
  309. serializer.errors,
  310. status=status.HTTP_400_BAD_REQUEST
  311. )
  312. requested_vlans = serializer.validated_data
  313. for i, requested_vlan in enumerate(requested_vlans):
  314. try:
  315. requested_vlan['vid'] = available_vlans.pop(0)
  316. requested_vlan['group'] = vlangroup.pk
  317. except IndexError:
  318. return Response({
  319. "detail": "The requested number of VLANs is not available"
  320. }, status=status.HTTP_409_CONFLICT)
  321. # Initialize the serializer with a list or a single object depending on what was requested
  322. context = {'request': request}
  323. if many:
  324. serializer = serializers.VLANSerializer(data=requested_vlans, many=True, context=context)
  325. else:
  326. serializer = serializers.VLANSerializer(data=requested_vlans[0], context=context)
  327. # Create the new VLAN(s)
  328. if serializer.is_valid():
  329. try:
  330. with transaction.atomic():
  331. created = serializer.save()
  332. self._validate_objects(created)
  333. except ObjectDoesNotExist:
  334. raise PermissionDenied()
  335. return Response(serializer.data, status=status.HTTP_201_CREATED)
  336. return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)