views.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856
  1. from __future__ import unicode_literals
  2. from django_tables2 import RequestConfig
  3. import netaddr
  4. from django.conf import settings
  5. from django.contrib.auth.mixins import PermissionRequiredMixin
  6. from django.db.models import Count, Q
  7. from django.shortcuts import get_object_or_404, render
  8. from django.urls import reverse
  9. from django.views.generic import View
  10. from dcim.models import Device, Interface
  11. from utilities.paginator import EnhancedPaginator
  12. from utilities.views import (
  13. BulkCreateView, BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
  14. )
  15. from virtualization.models import VirtualMachine
  16. from . import filters, forms, tables
  17. from .constants import IPADDRESS_ROLE_ANYCAST
  18. from .models import (
  19. Aggregate, IPAddress, PREFIX_STATUS_ACTIVE, PREFIX_STATUS_DEPRECATED, PREFIX_STATUS_RESERVED, Prefix, RIR, Role,
  20. Service, VLAN, VLANGroup, VRF,
  21. )
  22. def add_available_prefixes(parent, prefix_list):
  23. """
  24. Create fake Prefix objects for all unallocated space within a prefix.
  25. """
  26. # Find all unallocated space
  27. available_prefixes = netaddr.IPSet(parent) ^ netaddr.IPSet([p.prefix for p in prefix_list])
  28. available_prefixes = [Prefix(prefix=p) for p in available_prefixes.iter_cidrs()]
  29. # Concatenate and sort complete list of children
  30. prefix_list = list(prefix_list) + available_prefixes
  31. prefix_list.sort(key=lambda p: p.prefix)
  32. return prefix_list
  33. def add_available_ipaddresses(prefix, ipaddress_list, is_pool=False):
  34. """
  35. Annotate ranges of available IP addresses within a given prefix. If is_pool is True, the first and last IP will be
  36. considered usable (regardless of mask length).
  37. """
  38. output = []
  39. prev_ip = None
  40. # Ignore the network and broadcast addresses for non-pool IPv4 prefixes larger than /31.
  41. if prefix.version == 4 and prefix.prefixlen < 31 and not is_pool:
  42. first_ip_in_prefix = netaddr.IPAddress(prefix.first + 1)
  43. last_ip_in_prefix = netaddr.IPAddress(prefix.last - 1)
  44. else:
  45. first_ip_in_prefix = netaddr.IPAddress(prefix.first)
  46. last_ip_in_prefix = netaddr.IPAddress(prefix.last)
  47. if not ipaddress_list:
  48. return [(
  49. int(last_ip_in_prefix - first_ip_in_prefix + 1),
  50. '{}/{}'.format(first_ip_in_prefix, prefix.prefixlen)
  51. )]
  52. # Account for any available IPs before the first real IP
  53. if ipaddress_list[0].address.ip > first_ip_in_prefix:
  54. skipped_count = int(ipaddress_list[0].address.ip - first_ip_in_prefix)
  55. first_skipped = '{}/{}'.format(first_ip_in_prefix, prefix.prefixlen)
  56. output.append((skipped_count, first_skipped))
  57. # Iterate through existing IPs and annotate free ranges
  58. for ip in ipaddress_list:
  59. if prev_ip:
  60. diff = int(ip.address.ip - prev_ip.address.ip)
  61. if diff > 1:
  62. first_skipped = '{}/{}'.format(prev_ip.address.ip + 1, prefix.prefixlen)
  63. output.append((diff - 1, first_skipped))
  64. output.append(ip)
  65. prev_ip = ip
  66. # Include any remaining available IPs
  67. if prev_ip.address.ip < last_ip_in_prefix:
  68. skipped_count = int(last_ip_in_prefix - prev_ip.address.ip)
  69. first_skipped = '{}/{}'.format(prev_ip.address.ip + 1, prefix.prefixlen)
  70. output.append((skipped_count, first_skipped))
  71. return output
  72. #
  73. # VRFs
  74. #
  75. class VRFListView(ObjectListView):
  76. queryset = VRF.objects.select_related('tenant')
  77. filter = filters.VRFFilter
  78. filter_form = forms.VRFFilterForm
  79. table = tables.VRFTable
  80. template_name = 'ipam/vrf_list.html'
  81. class VRFView(View):
  82. def get(self, request, pk):
  83. vrf = get_object_or_404(VRF.objects.all(), pk=pk)
  84. prefix_table = tables.PrefixTable(
  85. list(Prefix.objects.filter(vrf=vrf).select_related('site', 'role')), orderable=False
  86. )
  87. prefix_table.exclude = ('vrf',)
  88. return render(request, 'ipam/vrf.html', {
  89. 'vrf': vrf,
  90. 'prefix_table': prefix_table,
  91. })
  92. class VRFCreateView(PermissionRequiredMixin, ObjectEditView):
  93. permission_required = 'ipam.add_vrf'
  94. model = VRF
  95. form_class = forms.VRFForm
  96. template_name = 'ipam/vrf_edit.html'
  97. default_return_url = 'ipam:vrf_list'
  98. class VRFEditView(VRFCreateView):
  99. permission_required = 'ipam.change_vrf'
  100. class VRFDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  101. permission_required = 'ipam.delete_vrf'
  102. model = VRF
  103. default_return_url = 'ipam:vrf_list'
  104. class VRFBulkImportView(PermissionRequiredMixin, BulkImportView):
  105. permission_required = 'ipam.add_vrf'
  106. model_form = forms.VRFCSVForm
  107. table = tables.VRFTable
  108. default_return_url = 'ipam:vrf_list'
  109. class VRFBulkEditView(PermissionRequiredMixin, BulkEditView):
  110. permission_required = 'ipam.change_vrf'
  111. cls = VRF
  112. queryset = VRF.objects.select_related('tenant')
  113. filter = filters.VRFFilter
  114. table = tables.VRFTable
  115. form = forms.VRFBulkEditForm
  116. default_return_url = 'ipam:vrf_list'
  117. class VRFBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  118. permission_required = 'ipam.delete_vrf'
  119. cls = VRF
  120. queryset = VRF.objects.select_related('tenant')
  121. filter = filters.VRFFilter
  122. table = tables.VRFTable
  123. default_return_url = 'ipam:vrf_list'
  124. #
  125. # RIRs
  126. #
  127. class RIRListView(ObjectListView):
  128. queryset = RIR.objects.annotate(aggregate_count=Count('aggregates'))
  129. filter = filters.RIRFilter
  130. filter_form = forms.RIRFilterForm
  131. table = tables.RIRDetailTable
  132. template_name = 'ipam/rir_list.html'
  133. def alter_queryset(self, request):
  134. if request.GET.get('family') == '6':
  135. family = 6
  136. denominator = 2 ** 64 # Count /64s for IPv6 rather than individual IPs
  137. else:
  138. family = 4
  139. denominator = 1
  140. rirs = []
  141. for rir in self.queryset:
  142. stats = {
  143. 'total': 0,
  144. 'active': 0,
  145. 'reserved': 0,
  146. 'deprecated': 0,
  147. 'available': 0,
  148. }
  149. aggregate_list = Aggregate.objects.filter(family=family, rir=rir)
  150. for aggregate in aggregate_list:
  151. queryset = Prefix.objects.filter(prefix__net_contained_or_equal=str(aggregate.prefix))
  152. # Find all consumed space for each prefix status (we ignore containers for this purpose).
  153. active_prefixes = netaddr.cidr_merge([p.prefix for p in queryset.filter(status=PREFIX_STATUS_ACTIVE)])
  154. reserved_prefixes = netaddr.cidr_merge([p.prefix for p in queryset.filter(status=PREFIX_STATUS_RESERVED)])
  155. deprecated_prefixes = netaddr.cidr_merge([p.prefix for p in queryset.filter(status=PREFIX_STATUS_DEPRECATED)])
  156. # Find all available prefixes by subtracting each of the existing prefix sets from the aggregate prefix.
  157. available_prefixes = (
  158. netaddr.IPSet([aggregate.prefix]) -
  159. netaddr.IPSet(active_prefixes) -
  160. netaddr.IPSet(reserved_prefixes) -
  161. netaddr.IPSet(deprecated_prefixes)
  162. )
  163. # Add the size of each metric to the RIR total.
  164. stats['total'] += aggregate.prefix.size / denominator
  165. stats['active'] += netaddr.IPSet(active_prefixes).size / denominator
  166. stats['reserved'] += netaddr.IPSet(reserved_prefixes).size / denominator
  167. stats['deprecated'] += netaddr.IPSet(deprecated_prefixes).size / denominator
  168. stats['available'] += available_prefixes.size / denominator
  169. # Calculate the percentage of total space for each prefix status.
  170. total = float(stats['total'])
  171. stats['percentages'] = {
  172. 'active': float('{:.2f}'.format(stats['active'] / total * 100)) if total else 0,
  173. 'reserved': float('{:.2f}'.format(stats['reserved'] / total * 100)) if total else 0,
  174. 'deprecated': float('{:.2f}'.format(stats['deprecated'] / total * 100)) if total else 0,
  175. }
  176. stats['percentages']['available'] = (
  177. 100 -
  178. stats['percentages']['active'] -
  179. stats['percentages']['reserved'] -
  180. stats['percentages']['deprecated']
  181. )
  182. rir.stats = stats
  183. rirs.append(rir)
  184. return rirs
  185. def extra_context(self):
  186. totals = {
  187. 'total': sum([rir.stats['total'] for rir in self.queryset]),
  188. 'active': sum([rir.stats['active'] for rir in self.queryset]),
  189. 'reserved': sum([rir.stats['reserved'] for rir in self.queryset]),
  190. 'deprecated': sum([rir.stats['deprecated'] for rir in self.queryset]),
  191. 'available': sum([rir.stats['available'] for rir in self.queryset]),
  192. }
  193. return {
  194. 'totals': totals,
  195. }
  196. class RIRCreateView(PermissionRequiredMixin, ObjectEditView):
  197. permission_required = 'ipam.add_rir'
  198. model = RIR
  199. form_class = forms.RIRForm
  200. def get_return_url(self, request, obj):
  201. return reverse('ipam:rir_list')
  202. class RIREditView(RIRCreateView):
  203. permission_required = 'ipam.change_rir'
  204. class RIRBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  205. permission_required = 'ipam.delete_rir'
  206. cls = RIR
  207. queryset = RIR.objects.annotate(aggregate_count=Count('aggregates'))
  208. filter = filters.RIRFilter
  209. table = tables.RIRTable
  210. default_return_url = 'ipam:rir_list'
  211. #
  212. # Aggregates
  213. #
  214. class AggregateListView(ObjectListView):
  215. queryset = Aggregate.objects.select_related('rir').extra(select={
  216. 'child_count': 'SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix',
  217. })
  218. filter = filters.AggregateFilter
  219. filter_form = forms.AggregateFilterForm
  220. table = tables.AggregateDetailTable
  221. template_name = 'ipam/aggregate_list.html'
  222. def extra_context(self):
  223. ipv4_total = 0
  224. ipv6_total = 0
  225. for a in self.queryset:
  226. if a.prefix.version == 4:
  227. ipv4_total += a.prefix.size
  228. elif a.prefix.version == 6:
  229. ipv6_total += a.prefix.size / 2 ** 64
  230. return {
  231. 'ipv4_total': ipv4_total,
  232. 'ipv6_total': ipv6_total,
  233. }
  234. class AggregateView(View):
  235. def get(self, request, pk):
  236. aggregate = get_object_or_404(Aggregate, pk=pk)
  237. # Find all child prefixes contained by this aggregate
  238. child_prefixes = Prefix.objects.filter(
  239. prefix__net_contained_or_equal=str(aggregate.prefix)
  240. ).select_related(
  241. 'site', 'role'
  242. ).annotate_depth(
  243. limit=0
  244. )
  245. child_prefixes = add_available_prefixes(aggregate.prefix, child_prefixes)
  246. prefix_table = tables.PrefixTable(child_prefixes)
  247. if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
  248. prefix_table.base_columns['pk'].visible = True
  249. paginate = {
  250. 'klass': EnhancedPaginator,
  251. 'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT)
  252. }
  253. RequestConfig(request, paginate).configure(prefix_table)
  254. # Compile permissions list for rendering the object table
  255. permissions = {
  256. 'add': request.user.has_perm('ipam.add_prefix'),
  257. 'change': request.user.has_perm('ipam.change_prefix'),
  258. 'delete': request.user.has_perm('ipam.delete_prefix'),
  259. }
  260. return render(request, 'ipam/aggregate.html', {
  261. 'aggregate': aggregate,
  262. 'prefix_table': prefix_table,
  263. 'permissions': permissions,
  264. })
  265. class AggregateCreateView(PermissionRequiredMixin, ObjectEditView):
  266. permission_required = 'ipam.add_aggregate'
  267. model = Aggregate
  268. form_class = forms.AggregateForm
  269. template_name = 'ipam/aggregate_edit.html'
  270. default_return_url = 'ipam:aggregate_list'
  271. class AggregateEditView(AggregateCreateView):
  272. permission_required = 'ipam.change_aggregate'
  273. class AggregateDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  274. permission_required = 'ipam.delete_aggregate'
  275. model = Aggregate
  276. default_return_url = 'ipam:aggregate_list'
  277. class AggregateBulkImportView(PermissionRequiredMixin, BulkImportView):
  278. permission_required = 'ipam.add_aggregate'
  279. model_form = forms.AggregateCSVForm
  280. table = tables.AggregateTable
  281. default_return_url = 'ipam:aggregate_list'
  282. class AggregateBulkEditView(PermissionRequiredMixin, BulkEditView):
  283. permission_required = 'ipam.change_aggregate'
  284. cls = Aggregate
  285. queryset = Aggregate.objects.select_related('rir')
  286. filter = filters.AggregateFilter
  287. table = tables.AggregateTable
  288. form = forms.AggregateBulkEditForm
  289. default_return_url = 'ipam:aggregate_list'
  290. class AggregateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  291. permission_required = 'ipam.delete_aggregate'
  292. cls = Aggregate
  293. queryset = Aggregate.objects.select_related('rir')
  294. filter = filters.AggregateFilter
  295. table = tables.AggregateTable
  296. default_return_url = 'ipam:aggregate_list'
  297. #
  298. # Prefix/VLAN roles
  299. #
  300. class RoleListView(ObjectListView):
  301. queryset = Role.objects.all()
  302. table = tables.RoleTable
  303. template_name = 'ipam/role_list.html'
  304. class RoleCreateView(PermissionRequiredMixin, ObjectEditView):
  305. permission_required = 'ipam.add_role'
  306. model = Role
  307. form_class = forms.RoleForm
  308. def get_return_url(self, request, obj):
  309. return reverse('ipam:role_list')
  310. class RoleEditView(RoleCreateView):
  311. permission_required = 'ipam.change_role'
  312. class RoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  313. permission_required = 'ipam.delete_role'
  314. cls = Role
  315. table = tables.RoleTable
  316. default_return_url = 'ipam:role_list'
  317. #
  318. # Prefixes
  319. #
  320. class PrefixListView(ObjectListView):
  321. queryset = Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
  322. filter = filters.PrefixFilter
  323. filter_form = forms.PrefixFilterForm
  324. table = tables.PrefixDetailTable
  325. template_name = 'ipam/prefix_list.html'
  326. def alter_queryset(self, request):
  327. # Show only top-level prefixes by default (unless searching)
  328. limit = None if request.GET.get('expand') or request.GET.get('q') else 0
  329. return self.queryset.annotate_depth(limit=limit)
  330. class PrefixView(View):
  331. def get(self, request, pk):
  332. prefix = get_object_or_404(Prefix.objects.select_related(
  333. 'vrf', 'site__region', 'tenant__group', 'vlan__group', 'role'
  334. ), pk=pk)
  335. try:
  336. aggregate = Aggregate.objects.get(prefix__net_contains_or_equals=str(prefix.prefix))
  337. except Aggregate.DoesNotExist:
  338. aggregate = None
  339. # Count child IP addresses
  340. ipaddress_count = IPAddress.objects.filter(
  341. vrf=prefix.vrf, address__net_host_contained=str(prefix.prefix)
  342. ).count()
  343. # Parent prefixes table
  344. parent_prefixes = Prefix.objects.filter(
  345. Q(vrf=prefix.vrf) | Q(vrf__isnull=True)
  346. ).filter(
  347. prefix__net_contains=str(prefix.prefix)
  348. ).select_related(
  349. 'site', 'role'
  350. ).annotate_depth()
  351. parent_prefix_table = tables.PrefixTable(list(parent_prefixes), orderable=False)
  352. parent_prefix_table.exclude = ('vrf',)
  353. # Duplicate prefixes table
  354. duplicate_prefixes = Prefix.objects.filter(
  355. vrf=prefix.vrf, prefix=str(prefix.prefix)
  356. ).exclude(
  357. pk=prefix.pk
  358. ).select_related(
  359. 'site', 'role'
  360. )
  361. duplicate_prefix_table = tables.PrefixTable(list(duplicate_prefixes), orderable=False)
  362. duplicate_prefix_table.exclude = ('vrf',)
  363. # Child prefixes table
  364. child_prefixes = Prefix.objects.filter(
  365. vrf=prefix.vrf, prefix__net_contained=str(prefix.prefix)
  366. ).select_related(
  367. 'site', 'role'
  368. ).annotate_depth(limit=0)
  369. if child_prefixes:
  370. child_prefixes = add_available_prefixes(prefix.prefix, child_prefixes)
  371. child_prefix_table = tables.PrefixTable(child_prefixes)
  372. if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
  373. child_prefix_table.base_columns['pk'].visible = True
  374. paginate = {
  375. 'klass': EnhancedPaginator,
  376. 'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT)
  377. }
  378. RequestConfig(request, paginate).configure(child_prefix_table)
  379. # Compile permissions list for rendering the object table
  380. permissions = {
  381. 'add': request.user.has_perm('ipam.add_prefix'),
  382. 'change': request.user.has_perm('ipam.change_prefix'),
  383. 'delete': request.user.has_perm('ipam.delete_prefix'),
  384. }
  385. return render(request, 'ipam/prefix.html', {
  386. 'prefix': prefix,
  387. 'aggregate': aggregate,
  388. 'ipaddress_count': ipaddress_count,
  389. 'parent_prefix_table': parent_prefix_table,
  390. 'child_prefix_table': child_prefix_table,
  391. 'duplicate_prefix_table': duplicate_prefix_table,
  392. 'permissions': permissions,
  393. 'return_url': prefix.get_absolute_url(),
  394. })
  395. class PrefixIPAddressesView(View):
  396. def get(self, request, pk):
  397. prefix = get_object_or_404(Prefix.objects.all(), pk=pk)
  398. # Find all IPAddresses belonging to this Prefix
  399. ipaddresses = IPAddress.objects.filter(
  400. vrf=prefix.vrf, address__net_host_contained=str(prefix.prefix)
  401. ).select_related(
  402. 'vrf', 'interface__device', 'primary_ip4_for', 'primary_ip6_for'
  403. )
  404. ipaddresses = add_available_ipaddresses(prefix.prefix, ipaddresses, prefix.is_pool)
  405. ip_table = tables.IPAddressTable(ipaddresses)
  406. if request.user.has_perm('ipam.change_ipaddress') or request.user.has_perm('ipam.delete_ipaddress'):
  407. ip_table.base_columns['pk'].visible = True
  408. paginate = {
  409. 'klass': EnhancedPaginator,
  410. 'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT)
  411. }
  412. RequestConfig(request, paginate).configure(ip_table)
  413. # Compile permissions list for rendering the object table
  414. permissions = {
  415. 'add': request.user.has_perm('ipam.add_ipaddress'),
  416. 'change': request.user.has_perm('ipam.change_ipaddress'),
  417. 'delete': request.user.has_perm('ipam.delete_ipaddress'),
  418. }
  419. return render(request, 'ipam/prefix_ipaddresses.html', {
  420. 'prefix': prefix,
  421. 'ip_table': ip_table,
  422. 'permissions': permissions,
  423. 'bulk_querystring': 'vrf_id={}&parent={}'.format(prefix.vrf or '0', prefix.prefix),
  424. })
  425. class PrefixCreateView(PermissionRequiredMixin, ObjectEditView):
  426. permission_required = 'ipam.add_prefix'
  427. model = Prefix
  428. form_class = forms.PrefixForm
  429. template_name = 'ipam/prefix_edit.html'
  430. default_return_url = 'ipam:prefix_list'
  431. class PrefixEditView(PrefixCreateView):
  432. permission_required = 'ipam.change_prefix'
  433. class PrefixDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  434. permission_required = 'ipam.delete_prefix'
  435. model = Prefix
  436. template_name = 'ipam/prefix_delete.html'
  437. default_return_url = 'ipam:prefix_list'
  438. class PrefixBulkImportView(PermissionRequiredMixin, BulkImportView):
  439. permission_required = 'ipam.add_prefix'
  440. model_form = forms.PrefixCSVForm
  441. table = tables.PrefixTable
  442. default_return_url = 'ipam:prefix_list'
  443. class PrefixBulkEditView(PermissionRequiredMixin, BulkEditView):
  444. permission_required = 'ipam.change_prefix'
  445. cls = Prefix
  446. queryset = Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
  447. filter = filters.PrefixFilter
  448. table = tables.PrefixTable
  449. form = forms.PrefixBulkEditForm
  450. default_return_url = 'ipam:prefix_list'
  451. class PrefixBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  452. permission_required = 'ipam.delete_prefix'
  453. cls = Prefix
  454. queryset = Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
  455. filter = filters.PrefixFilter
  456. table = tables.PrefixTable
  457. default_return_url = 'ipam:prefix_list'
  458. #
  459. # IP addresses
  460. #
  461. class IPAddressListView(ObjectListView):
  462. queryset = IPAddress.objects.select_related(
  463. 'vrf__tenant', 'tenant', 'nat_inside'
  464. ).prefetch_related(
  465. 'interface__device', 'interface__virtual_machine'
  466. )
  467. filter = filters.IPAddressFilter
  468. filter_form = forms.IPAddressFilterForm
  469. table = tables.IPAddressDetailTable
  470. template_name = 'ipam/ipaddress_list.html'
  471. class IPAddressView(View):
  472. def get(self, request, pk):
  473. ipaddress = get_object_or_404(IPAddress.objects.select_related('vrf__tenant', 'tenant'), pk=pk)
  474. # Parent prefixes table
  475. parent_prefixes = Prefix.objects.filter(
  476. vrf=ipaddress.vrf, prefix__net_contains=str(ipaddress.address.ip)
  477. ).select_related(
  478. 'site', 'role'
  479. )
  480. parent_prefixes_table = tables.PrefixTable(list(parent_prefixes), orderable=False)
  481. parent_prefixes_table.exclude = ('vrf',)
  482. # Duplicate IPs table
  483. duplicate_ips = IPAddress.objects.filter(
  484. vrf=ipaddress.vrf, address=str(ipaddress.address)
  485. ).exclude(
  486. pk=ipaddress.pk
  487. ).select_related(
  488. 'nat_inside'
  489. ).prefetch_related(
  490. 'interface__device'
  491. )
  492. # Exclude anycast IPs if this IP is anycast
  493. if ipaddress.role == IPADDRESS_ROLE_ANYCAST:
  494. duplicate_ips = duplicate_ips.exclude(role=IPADDRESS_ROLE_ANYCAST)
  495. duplicate_ips_table = tables.IPAddressTable(list(duplicate_ips), orderable=False)
  496. # Related IP table
  497. related_ips = IPAddress.objects.prefetch_related(
  498. 'interface__device'
  499. ).exclude(
  500. address=str(ipaddress.address)
  501. ).filter(
  502. vrf=ipaddress.vrf, address__net_contained_or_equal=str(ipaddress.address)
  503. )
  504. related_ips_table = tables.IPAddressTable(list(related_ips), orderable=False)
  505. return render(request, 'ipam/ipaddress.html', {
  506. 'ipaddress': ipaddress,
  507. 'parent_prefixes_table': parent_prefixes_table,
  508. 'duplicate_ips_table': duplicate_ips_table,
  509. 'related_ips_table': related_ips_table,
  510. })
  511. class IPAddressCreateView(PermissionRequiredMixin, ObjectEditView):
  512. permission_required = 'ipam.add_ipaddress'
  513. model = IPAddress
  514. form_class = forms.IPAddressForm
  515. template_name = 'ipam/ipaddress_edit.html'
  516. default_return_url = 'ipam:ipaddress_list'
  517. def alter_obj(self, obj, request, url_args, url_kwargs):
  518. interface_id = request.GET.get('interface')
  519. if interface_id:
  520. try:
  521. obj.interface = Interface.objects.get(pk=interface_id)
  522. except (ValueError, Interface.DoesNotExist):
  523. pass
  524. return obj
  525. class IPAddressEditView(IPAddressCreateView):
  526. permission_required = 'ipam.change_ipaddress'
  527. class IPAddressDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  528. permission_required = 'ipam.delete_ipaddress'
  529. model = IPAddress
  530. default_return_url = 'ipam:ipaddress_list'
  531. class IPAddressBulkCreateView(PermissionRequiredMixin, BulkCreateView):
  532. permission_required = 'ipam.add_ipaddress'
  533. pattern_form = forms.IPAddressPatternForm
  534. model_form = forms.IPAddressBulkAddForm
  535. pattern_target = 'address'
  536. template_name = 'ipam/ipaddress_bulk_add.html'
  537. default_return_url = 'ipam:ipaddress_list'
  538. class IPAddressBulkImportView(PermissionRequiredMixin, BulkImportView):
  539. permission_required = 'ipam.add_ipaddress'
  540. model_form = forms.IPAddressCSVForm
  541. table = tables.IPAddressTable
  542. default_return_url = 'ipam:ipaddress_list'
  543. class IPAddressBulkEditView(PermissionRequiredMixin, BulkEditView):
  544. permission_required = 'ipam.change_ipaddress'
  545. cls = IPAddress
  546. queryset = IPAddress.objects.select_related('vrf__tenant', 'tenant').prefetch_related('interface__device')
  547. filter = filters.IPAddressFilter
  548. table = tables.IPAddressTable
  549. form = forms.IPAddressBulkEditForm
  550. default_return_url = 'ipam:ipaddress_list'
  551. class IPAddressBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  552. permission_required = 'ipam.delete_ipaddress'
  553. cls = IPAddress
  554. queryset = IPAddress.objects.select_related('vrf__tenant', 'tenant').prefetch_related('interface__device')
  555. filter = filters.IPAddressFilter
  556. table = tables.IPAddressTable
  557. default_return_url = 'ipam:ipaddress_list'
  558. #
  559. # VLAN groups
  560. #
  561. class VLANGroupListView(ObjectListView):
  562. queryset = VLANGroup.objects.select_related('site').annotate(vlan_count=Count('vlans'))
  563. filter = filters.VLANGroupFilter
  564. filter_form = forms.VLANGroupFilterForm
  565. table = tables.VLANGroupTable
  566. template_name = 'ipam/vlangroup_list.html'
  567. class VLANGroupCreateView(PermissionRequiredMixin, ObjectEditView):
  568. permission_required = 'ipam.add_vlangroup'
  569. model = VLANGroup
  570. form_class = forms.VLANGroupForm
  571. def get_return_url(self, request, obj):
  572. return reverse('ipam:vlangroup_list')
  573. class VLANGroupEditView(VLANGroupCreateView):
  574. permission_required = 'ipam.change_vlangroup'
  575. class VLANGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  576. permission_required = 'ipam.delete_vlangroup'
  577. cls = VLANGroup
  578. queryset = VLANGroup.objects.select_related('site').annotate(vlan_count=Count('vlans'))
  579. filter = filters.VLANGroupFilter
  580. table = tables.VLANGroupTable
  581. default_return_url = 'ipam:vlangroup_list'
  582. #
  583. # VLANs
  584. #
  585. class VLANListView(ObjectListView):
  586. queryset = VLAN.objects.select_related('site', 'group', 'tenant', 'role').prefetch_related('prefixes')
  587. filter = filters.VLANFilter
  588. filter_form = forms.VLANFilterForm
  589. table = tables.VLANDetailTable
  590. template_name = 'ipam/vlan_list.html'
  591. class VLANView(View):
  592. def get(self, request, pk):
  593. vlan = get_object_or_404(VLAN.objects.select_related(
  594. 'site__region', 'tenant__group', 'role'
  595. ), pk=pk)
  596. prefixes = Prefix.objects.filter(vlan=vlan).select_related('vrf', 'site', 'role')
  597. prefix_table = tables.PrefixTable(list(prefixes), orderable=False)
  598. prefix_table.exclude = ('vlan',)
  599. return render(request, 'ipam/vlan.html', {
  600. 'vlan': vlan,
  601. 'prefix_table': prefix_table,
  602. })
  603. class VLANCreateView(PermissionRequiredMixin, ObjectEditView):
  604. permission_required = 'ipam.add_vlan'
  605. model = VLAN
  606. form_class = forms.VLANForm
  607. template_name = 'ipam/vlan_edit.html'
  608. default_return_url = 'ipam:vlan_list'
  609. class VLANEditView(VLANCreateView):
  610. permission_required = 'ipam.change_vlan'
  611. class VLANDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  612. permission_required = 'ipam.delete_vlan'
  613. model = VLAN
  614. default_return_url = 'ipam:vlan_list'
  615. class VLANBulkImportView(PermissionRequiredMixin, BulkImportView):
  616. permission_required = 'ipam.add_vlan'
  617. model_form = forms.VLANCSVForm
  618. table = tables.VLANTable
  619. default_return_url = 'ipam:vlan_list'
  620. class VLANBulkEditView(PermissionRequiredMixin, BulkEditView):
  621. permission_required = 'ipam.change_vlan'
  622. cls = VLAN
  623. queryset = VLAN.objects.select_related('site', 'group', 'tenant', 'role')
  624. filter = filters.VLANFilter
  625. table = tables.VLANTable
  626. form = forms.VLANBulkEditForm
  627. default_return_url = 'ipam:vlan_list'
  628. class VLANBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  629. permission_required = 'ipam.delete_vlan'
  630. cls = VLAN
  631. queryset = VLAN.objects.select_related('site', 'group', 'tenant', 'role')
  632. filter = filters.VLANFilter
  633. table = tables.VLANTable
  634. default_return_url = 'ipam:vlan_list'
  635. #
  636. # Services
  637. #
  638. class ServiceCreateView(PermissionRequiredMixin, ObjectEditView):
  639. permission_required = 'ipam.add_service'
  640. model = Service
  641. form_class = forms.ServiceForm
  642. template_name = 'ipam/service_edit.html'
  643. def alter_obj(self, obj, request, url_args, url_kwargs):
  644. if 'device' in url_kwargs:
  645. obj.device = get_object_or_404(Device, pk=url_kwargs['device'])
  646. elif 'virtualmachine' in url_kwargs:
  647. obj.virtual_machine = get_object_or_404(VirtualMachine, pk=url_kwargs['virtualmachine'])
  648. return obj
  649. def get_return_url(self, request, obj):
  650. return obj.parent.get_absolute_url()
  651. class ServiceEditView(ServiceCreateView):
  652. permission_required = 'ipam.change_service'
  653. class ServiceDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  654. permission_required = 'ipam.delete_service'
  655. model = Service