update_dhcp.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. #!/usr/bin/env python
  2. #
  3. # Copyright (c) 2017-2022 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. from builtins import str
  27. from builtins import range
  28. import json
  29. from elemental_utils import ElementalNetbox
  30. import ipaddress
  31. import requests
  32. from requests.packages.urllib3.exceptions import InsecureRequestWarning # type: ignore
  33. requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
  34. import sys
  35. import os
  36. import CLEUCreds # type: ignore
  37. from cleu.config import Config as C # type: ignore
  38. IDF_CNT = 99
  39. ADDITIONAL_IDFS = (252, 253, 254)
  40. FIRST_IP = 31
  41. LAST_IP = 253
  42. NB_TENANT = "Attendees"
  43. IDF_OVERRIDES = {
  44. 252: {"first_ip": 160, "last_ip": "250"},
  45. 253: {"first_ip": 160, "last_ip": "250"},
  46. 254: {"first_ip": 160, "last_ip": "250"},
  47. }
  48. SCOPE_BASE = C.DHCP_BASE + "Scope"
  49. DHCP_TEMPLATE = {"optionList": {"OptionItem": []}}
  50. HEADERS = {"accept": "application/json", "content-type": "application/json"}
  51. if __name__ == "__main__":
  52. os.environ["NETBOX_ADDRESS"] = C.NETBOX_SERVER
  53. os.environ["NETBOX_API_TOKEN"] = CLEUCreds.NETBOX_API_TOKEN
  54. enb = ElementalNetbox()
  55. tenant = list(enb.tenancy.tenants.filter(tenant_name=NB_TENANT))[0]
  56. prefixes = list(enb.ipam.prefixes.filter(tenant_id=tenant.id))
  57. for prefix in prefixes:
  58. prefix_obj = ipaddress.ip_network(prefix.prefix)
  59. start = 1
  60. cnt = IDF_CNT
  61. idf_set = ()
  62. if str(prefix_obj.netmask) == "255.255.0.0":
  63. start = 0
  64. cnt = 0
  65. for i in range(start, cnt + 1):
  66. idf_set += (i,)
  67. if str(prefix_obj.netmask) != "255.255.0.0":
  68. idf_set += ADDITIONAL_IDFS
  69. for i in idf_set:
  70. scope_prefix = f"IDF-{str(i).zfill(3)}"
  71. if i == 0:
  72. scope_prefix = "CORE"
  73. scope = (f"{scope_prefix}-{prefix.vlan.name.replace(' ', '-')}").upper()
  74. ip = f"10.{prefix.vlan.vid}.{i}.0"
  75. octets = ["10", str(prefix.vlan.vid), str(i), "0"]
  76. roctets = list(octets)
  77. roctets[3] = "254"
  78. url = f"{SCOPE_BASE}/{scope}"
  79. response = requests.request("GET", url, auth=(CLEUCreds.CPNR_USERNAME, CLEUCreds.CPNR_PASSWORD), headers=HEADERS, verify=False)
  80. if response.status_code != 404:
  81. sys.stderr.write(f"Scope {scope} already exists: {response.status_code}\n")
  82. continue
  83. template = {"optionList": {"OptionItem": []}}
  84. if str(prefix_obj.netmask) == "255.255.0.0":
  85. roctets[2] = "255"
  86. template["optionList"]["OptionItem"].append({"number": "3", "value": ".".join(roctets)})
  87. first_ip = FIRST_IP
  88. last_ip = LAST_IP
  89. if i in IDF_OVERRIDES:
  90. first_ip = IDF_OVERRIDES[i]["first_ip"]
  91. last_ip = IDF_OVERRIDES[i]["last_ip"]
  92. sipa = list(octets)
  93. sipa[3] = str(first_ip)
  94. eipa = list(octets)
  95. eipa[3] = str(last_ip)
  96. if str(prefix_obj.netmask) == "255.255.0.0":
  97. eipa[2] = "255"
  98. sip = ".".join(sipa)
  99. eip = ".".join(eipa)
  100. rlist = {"RangeItem": [{"end": eip, "start": sip}]}
  101. cidr = prefix_obj.prefixlen
  102. if not prefix.role:
  103. sys.stderr.write(f"WARNING: Unable to add scope {scope} as the prefix does not have a role.")
  104. continue
  105. payload = {
  106. "embeddedPolicy": template,
  107. "name": scope,
  108. "policy": prefix.role.name,
  109. "rangeList": rlist,
  110. "subnet": f"{ip}/{cidr}",
  111. "tenantId": "0",
  112. "vpnId": "0",
  113. }
  114. response = requests.request(
  115. "PUT", url, json=payload, auth=(CLEUCreds.CPNR_USERNAME, CLEUCreds.CPNR_PASSWORD), headers=HEADERS, verify=False
  116. )
  117. try:
  118. response.raise_for_status()
  119. except Exception as e:
  120. sys.stderr.write(f"Error adding scope {scope} ({ip}/{cidr}) with range sip:{sip} eip:{eip}: {response.text} ({e})\n")
  121. sys.stderr.write(f"Request: {json.dumps(payload, indent=4)}\n")
  122. continue