views.py 32 KB

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