utils.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. import netaddr
  2. from .constants import *
  3. from .models import Prefix, VLAN
  4. __all__ = (
  5. 'add_available_ipaddresses',
  6. 'add_available_vlans',
  7. 'add_requested_prefixes',
  8. 'get_next_available_prefix',
  9. 'rebuild_prefixes',
  10. )
  11. def add_requested_prefixes(parent, prefix_list, show_available=True, show_assigned=True):
  12. """
  13. Return a list of requested prefixes using show_available, show_assigned filters. If available prefixes are
  14. requested, create fake Prefix objects for all unallocated space within a prefix.
  15. :param parent: Parent Prefix instance
  16. :param prefix_list: Child prefixes list
  17. :param show_available: Include available prefixes.
  18. :param show_assigned: Show assigned prefixes.
  19. """
  20. child_prefixes = []
  21. # Add available prefixes to the table if requested
  22. if prefix_list and show_available:
  23. # Find all unallocated space, add fake Prefix objects to child_prefixes.
  24. available_prefixes = netaddr.IPSet(parent) ^ netaddr.IPSet([p.prefix for p in prefix_list])
  25. available_prefixes = [Prefix(prefix=p, status=None) for p in available_prefixes.iter_cidrs()]
  26. child_prefixes = child_prefixes + available_prefixes
  27. # Add assigned prefixes to the table if requested
  28. if prefix_list and show_assigned:
  29. child_prefixes = child_prefixes + list(prefix_list)
  30. # Sort child prefixes after additions
  31. child_prefixes.sort(key=lambda p: p.prefix)
  32. return child_prefixes
  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. def add_available_vlans(vlans, vlan_group=None):
  73. """
  74. Create fake records for all gaps between used VLANs
  75. """
  76. min_vid = vlan_group.min_vid if vlan_group else VLAN_VID_MIN
  77. max_vid = vlan_group.max_vid if vlan_group else VLAN_VID_MAX
  78. if not vlans:
  79. return [{
  80. 'vid': min_vid,
  81. 'vlan_group': vlan_group,
  82. 'available': max_vid - min_vid + 1
  83. }]
  84. prev_vid = max_vid
  85. new_vlans = []
  86. for vlan in vlans:
  87. if vlan.vid - prev_vid > 1:
  88. new_vlans.append({
  89. 'vid': prev_vid + 1,
  90. 'vlan_group': vlan_group,
  91. 'available': vlan.vid - prev_vid - 1,
  92. })
  93. prev_vid = vlan.vid
  94. if vlans[0].vid > min_vid:
  95. new_vlans.append({
  96. 'vid': min_vid,
  97. 'vlan_group': vlan_group,
  98. 'available': vlans[0].vid - min_vid,
  99. })
  100. if prev_vid < max_vid:
  101. new_vlans.append({
  102. 'vid': prev_vid + 1,
  103. 'vlan_group': vlan_group,
  104. 'available': max_vid - prev_vid,
  105. })
  106. vlans = list(vlans) + new_vlans
  107. vlans.sort(key=lambda v: v.vid if type(v) is VLAN else v['vid'])
  108. return vlans
  109. def rebuild_prefixes(vrf):
  110. """
  111. Rebuild the prefix hierarchy for all prefixes in the specified VRF (or global table).
  112. """
  113. def contains(parent, child):
  114. return child in parent and child != parent
  115. def push_to_stack(prefix):
  116. # Increment child count on parent nodes
  117. for n in stack:
  118. n['children'] += 1
  119. stack.append({
  120. 'pk': [prefix['pk']],
  121. 'prefix': prefix['prefix'],
  122. 'children': 0,
  123. })
  124. stack = []
  125. update_queue = []
  126. prefixes = Prefix.objects.filter(vrf=vrf).values('pk', 'prefix')
  127. # Iterate through all Prefixes in the VRF, growing and shrinking the stack as we go
  128. for i, p in enumerate(prefixes):
  129. # Grow the stack if this is a child of the most recent prefix
  130. if not stack or contains(stack[-1]['prefix'], p['prefix']):
  131. push_to_stack(p)
  132. # Handle duplicate prefixes
  133. elif stack[-1]['prefix'] == p['prefix']:
  134. stack[-1]['pk'].append(p['pk'])
  135. # If this is a sibling or parent of the most recent prefix, pop nodes from the
  136. # stack until we reach a parent prefix (or the root)
  137. else:
  138. while stack and not contains(stack[-1]['prefix'], p['prefix']):
  139. node = stack.pop()
  140. for pk in node['pk']:
  141. update_queue.append(
  142. Prefix(pk=pk, _depth=len(stack), _children=node['children'])
  143. )
  144. push_to_stack(p)
  145. # Flush the update queue once it reaches 100 Prefixes
  146. if len(update_queue) >= 100:
  147. Prefix.objects.bulk_update(update_queue, ['_depth', '_children'])
  148. update_queue = []
  149. # Clear out any prefixes remaining in the stack
  150. while stack:
  151. node = stack.pop()
  152. for pk in node['pk']:
  153. update_queue.append(
  154. Prefix(pk=pk, _depth=len(stack), _children=node['children'])
  155. )
  156. # Final flush of any remaining Prefixes
  157. Prefix.objects.bulk_update(update_queue, ['_depth', '_children'])
  158. def get_next_available_prefix(ipset, prefix_size):
  159. """
  160. Given a prefix length, allocate the next available prefix from an IPSet.
  161. """
  162. for available_prefix in ipset.iter_cidrs():
  163. if prefix_size >= available_prefix.prefixlen:
  164. allocated_prefix = f"{available_prefix.network}/{prefix_size}"
  165. ipset.remove(allocated_prefix)
  166. return allocated_prefix
  167. return None