utils.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import netaddr
  2. from .constants import *
  3. from .models import Prefix, VLAN
  4. def add_requested_prefixes(parent, prefix_list, show_available=True, show_assigned=True):
  5. """
  6. Return a list of requested prefixes using show_available, show_assigned filters. If available prefixes are
  7. requested, create fake Prefix objects for all unallocated space within a prefix.
  8. :param parent: Parent Prefix instance
  9. :param prefix_list: Child prefixes list
  10. :param show_available: Include available prefixes.
  11. :param show_assigned: Show assigned prefixes.
  12. """
  13. child_prefixes = []
  14. # Add available prefixes to the table if requested
  15. if prefix_list and show_available:
  16. # Find all unallocated space, add fake Prefix objects to child_prefixes.
  17. available_prefixes = netaddr.IPSet(parent) ^ netaddr.IPSet([p.prefix for p in prefix_list])
  18. available_prefixes = [Prefix(prefix=p, status=None) for p in available_prefixes.iter_cidrs()]
  19. child_prefixes = child_prefixes + available_prefixes
  20. # Add assigned prefixes to the table if requested
  21. if prefix_list and show_assigned:
  22. child_prefixes = child_prefixes + list(prefix_list)
  23. # Sort child prefixes after additions
  24. child_prefixes.sort(key=lambda p: p.prefix)
  25. return child_prefixes
  26. def add_available_ipaddresses(prefix, ipaddress_list, is_pool=False):
  27. """
  28. Annotate ranges of available IP addresses within a given prefix. If is_pool is True, the first and last IP will be
  29. considered usable (regardless of mask length).
  30. """
  31. output = []
  32. prev_ip = None
  33. # Ignore the network and broadcast addresses for non-pool IPv4 prefixes larger than /31.
  34. if prefix.version == 4 and prefix.prefixlen < 31 and not is_pool:
  35. first_ip_in_prefix = netaddr.IPAddress(prefix.first + 1)
  36. last_ip_in_prefix = netaddr.IPAddress(prefix.last - 1)
  37. else:
  38. first_ip_in_prefix = netaddr.IPAddress(prefix.first)
  39. last_ip_in_prefix = netaddr.IPAddress(prefix.last)
  40. if not ipaddress_list:
  41. return [(
  42. int(last_ip_in_prefix - first_ip_in_prefix + 1),
  43. '{}/{}'.format(first_ip_in_prefix, prefix.prefixlen)
  44. )]
  45. # Account for any available IPs before the first real IP
  46. if ipaddress_list[0].address.ip > first_ip_in_prefix:
  47. skipped_count = int(ipaddress_list[0].address.ip - first_ip_in_prefix)
  48. first_skipped = '{}/{}'.format(first_ip_in_prefix, prefix.prefixlen)
  49. output.append((skipped_count, first_skipped))
  50. # Iterate through existing IPs and annotate free ranges
  51. for ip in ipaddress_list:
  52. if prev_ip:
  53. diff = int(ip.address.ip - prev_ip.address.ip)
  54. if diff > 1:
  55. first_skipped = '{}/{}'.format(prev_ip.address.ip + 1, prefix.prefixlen)
  56. output.append((diff - 1, first_skipped))
  57. output.append(ip)
  58. prev_ip = ip
  59. # Include any remaining available IPs
  60. if prev_ip.address.ip < last_ip_in_prefix:
  61. skipped_count = int(last_ip_in_prefix - prev_ip.address.ip)
  62. first_skipped = '{}/{}'.format(prev_ip.address.ip + 1, prefix.prefixlen)
  63. output.append((skipped_count, first_skipped))
  64. return output
  65. def add_available_vlans(vlans, vlan_group=None):
  66. """
  67. Create fake records for all gaps between used VLANs
  68. """
  69. min_vid = vlan_group.min_vid if vlan_group else VLAN_VID_MIN
  70. max_vid = vlan_group.max_vid if vlan_group else VLAN_VID_MAX
  71. if not vlans:
  72. return [{
  73. 'vid': min_vid,
  74. 'vlan_group': vlan_group,
  75. 'available': max_vid - min_vid + 1
  76. }]
  77. prev_vid = max_vid
  78. new_vlans = []
  79. for vlan in vlans:
  80. if vlan.vid - prev_vid > 1:
  81. new_vlans.append({
  82. 'vid': prev_vid + 1,
  83. 'vlan_group': vlan_group,
  84. 'available': vlan.vid - prev_vid - 1,
  85. })
  86. prev_vid = vlan.vid
  87. if vlans[0].vid > min_vid:
  88. new_vlans.append({
  89. 'vid': min_vid,
  90. 'vlan_group': vlan_group,
  91. 'available': vlans[0].vid - min_vid,
  92. })
  93. if prev_vid < max_vid:
  94. new_vlans.append({
  95. 'vid': prev_vid + 1,
  96. 'vlan_group': vlan_group,
  97. 'available': max_vid - prev_vid,
  98. })
  99. vlans = list(vlans) + new_vlans
  100. vlans.sort(key=lambda v: v.vid if type(v) == VLAN else v['vid'])
  101. return vlans
  102. def rebuild_prefixes(vrf):
  103. """
  104. Rebuild the prefix hierarchy for all prefixes in the specified VRF (or global table).
  105. """
  106. def contains(parent, child):
  107. return child in parent and child != parent
  108. def push_to_stack(prefix):
  109. # Increment child count on parent nodes
  110. for n in stack:
  111. n['children'] += 1
  112. stack.append({
  113. 'pk': [prefix['pk']],
  114. 'prefix': prefix['prefix'],
  115. 'children': 0,
  116. })
  117. stack = []
  118. update_queue = []
  119. prefixes = Prefix.objects.filter(vrf=vrf).values('pk', 'prefix')
  120. # Iterate through all Prefixes in the VRF, growing and shrinking the stack as we go
  121. for i, p in enumerate(prefixes):
  122. # Grow the stack if this is a child of the most recent prefix
  123. if not stack or contains(stack[-1]['prefix'], p['prefix']):
  124. push_to_stack(p)
  125. # Handle duplicate prefixes
  126. elif stack[-1]['prefix'] == p['prefix']:
  127. stack[-1]['pk'].append(p['pk'])
  128. # If this is a sibling or parent of the most recent prefix, pop nodes from the
  129. # stack until we reach a parent prefix (or the root)
  130. else:
  131. while stack and not contains(stack[-1]['prefix'], p['prefix']):
  132. node = stack.pop()
  133. for pk in node['pk']:
  134. update_queue.append(
  135. Prefix(pk=pk, _depth=len(stack), _children=node['children'])
  136. )
  137. push_to_stack(p)
  138. # Flush the update queue once it reaches 100 Prefixes
  139. if len(update_queue) >= 100:
  140. Prefix.objects.bulk_update(update_queue, ['_depth', '_children'])
  141. update_queue = []
  142. # Clear out any prefixes remaining in the stack
  143. while stack:
  144. node = stack.pop()
  145. for pk in node['pk']:
  146. update_queue.append(
  147. Prefix(pk=pk, _depth=len(stack), _children=node['children'])
  148. )
  149. # Final flush of any remaining Prefixes
  150. Prefix.objects.bulk_update(update_queue, ['_depth', '_children'])