views.py 30 KB

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