update_dhcp.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. #!/usr/bin/env python3
  2. #
  3. # Copyright (c) 2017-2019 Joe Clarke <jclarke@cisco.com>
  4. # All rights reserved.
  5. #
  6. # Redistribution and use in source and binary forms, with or without
  7. # modification, are permitted provided that the following conditions
  8. # are met:
  9. # 1. Redistributions of source code must retain the above copyright
  10. # notice, this list of conditions and the following disclaimer.
  11. # 2. Redistributions in binary form must reproduce the above copyright
  12. # notice, this list of conditions and the following disclaimer in the
  13. # documentation and/or other materials provided with the distribution.
  14. #
  15. # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  16. # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  17. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  18. # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  19. # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  20. # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  21. # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  22. # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  23. # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  24. # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  25. # SUCH DAMAGE.
  26. import json
  27. import requests
  28. from requests.packages.urllib3.exceptions import InsecureRequestWarning
  29. requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
  30. import sys
  31. import re
  32. from netaddr import IPAddress
  33. import CLEUCreds
  34. from cleu.config import Config as C
  35. IDF_CNT = 99
  36. ADDITIONAL_IDFS = (252, 253, 254)
  37. FIRST_IP = 31
  38. LAST_IP = 253
  39. IDF_OVERRIDES = {
  40. 252: {
  41. 'first_ip': 160,
  42. 'last_ip': '250'
  43. },
  44. 253: {
  45. 'first_ip': 160,
  46. 'last_ip': '250'
  47. },
  48. 254: {
  49. 'first_ip': 160,
  50. 'last_ip': '250'
  51. }
  52. }
  53. SCOPE_BASE = C.DHCP_BASE + 'Scope'
  54. DHCP_TEMPLATE = {
  55. "optionList": {
  56. "OptionItem": []
  57. }
  58. }
  59. HEADERS = {
  60. 'authorization': CLEUCreds.JCLARKE_BASIC,
  61. 'accept': 'application/json',
  62. 'content-type': 'application/json'
  63. }
  64. def mtoc(mask):
  65. return IPAddress(mask).netmask_bits()
  66. if __name__ == '__main__':
  67. if len(sys.argv) != 2:
  68. sys.stderr.write("usage: {} INPUT_FILE\n".format(sys.argv[0]))
  69. sys.exit(1)
  70. contents = None
  71. try:
  72. fd = open(sys.argv[1], 'r')
  73. contents = fd.read()
  74. fd.close()
  75. except Exception as e:
  76. sys.stderr.write("Failed to open {}: {}\n".format(sys.argv[1], str(e)))
  77. sys.exit(1)
  78. for row in contents.split('\n'):
  79. row = row.strip()
  80. if re.search(r'^#', row):
  81. continue
  82. if row == '':
  83. continue
  84. [vlan, mask, name, policy] = row.split(',')
  85. if vlan == '' or mask == '' or name == '' or policy == '':
  86. sys.stderr.write("Skipping malformed row '{}'\n".format(row))
  87. continue
  88. start = 1
  89. cnt = IDF_CNT
  90. idf_set = ()
  91. if mask == '255.255.0.0':
  92. start = 0
  93. cnt = 0
  94. for i in range(start, cnt + 1):
  95. idf_set += (i,)
  96. if mask != '255.255.0.0':
  97. idf_set += ADDITIONAL_IDFS
  98. if mask.startswith('10.'):
  99. octets = mask.split('.')
  100. idf_set = (octets[2],)
  101. mask = '255.255.255.0'
  102. for i in idf_set:
  103. prefix = 'IDF-{}'.format(str(i).zfill(3))
  104. if i == 0:
  105. prefix = 'CORE'
  106. scope = ('{}-{}'.format(prefix, name)).upper()
  107. ip = '10.{}.{}.0'.format(vlan, i)
  108. octets = ['10', vlan, str(i), '0']
  109. roctets = list(octets)
  110. roctets[3] = '254'
  111. url = '{}/{}'.format(SCOPE_BASE, scope)
  112. response = requests.request(
  113. 'GET', url, headers=HEADERS, verify=False)
  114. if response.status_code != 404:
  115. sys.stderr.write("Scope {} already exists: {}\n".format(
  116. scope, response.status_code))
  117. continue
  118. template = {'optionList': {'OptionItem': []}}
  119. if mask == '255.255.0.0':
  120. roctets[2] = '255'
  121. template['optionList']['OptionItem'].append(
  122. {'number': '3', 'value': '.'.join(roctets)})
  123. first_ip = FIRST_IP
  124. last_ip = LAST_IP
  125. if i in IDF_OVERRIDES:
  126. first_ip = IDF_OVERRIDES[i]['first_ip']
  127. last_ip = IDF_OVERRIDES[i]['last_ip']
  128. sipa = list(octets)
  129. sipa[3] = str(first_ip)
  130. eipa = list(octets)
  131. eipa[3] = str(last_ip)
  132. if mask == '255.255.0.0':
  133. eipa[2] = '255'
  134. sip = '.'.join(sipa)
  135. eip = '.'.join(eipa)
  136. rlist = {'RangeItem': [{'end': eip, 'start': sip}]}
  137. cidr = mtoc(mask)
  138. payload = {'embeddedPolicy': template, 'name': scope, 'policy': policy,
  139. 'rangeList': rlist, 'subnet': '{}/{}'.format(ip, cidr), 'tenantId': '0', 'vpnId': '0'}
  140. try:
  141. response = requests.request('PUT', url, data=json.dumps(
  142. payload), headers=HEADERS, verify=False)
  143. response.raise_for_status()
  144. except Exception as e:
  145. sys.stderr.write("Error adding scope {} ({}/{}) with range sip:{} eip:{}: {} ({})\n".format(
  146. scope, ip, cidr, sip, eip, response.text, str(e)))
  147. sys.stderr.write("Request: {}\n".format(
  148. json.dumps(payload, indent=4)))
  149. continue