Refactor nbhelper
Change-Id: I69d10d164fac3eb319e072447a520905880c31dd
diff --git a/scripts/nbhelper/service.py b/scripts/nbhelper/service.py
new file mode 100644
index 0000000..d84f0cb
--- /dev/null
+++ b/scripts/nbhelper/service.py
@@ -0,0 +1,182 @@
+#!/usr/bin/env python3
+
+# SPDX-FileCopyrightText: © 2021 Open Networking Foundation <support@opennetworking.org>
+# SPDX-License-Identifier: Apache-2.0
+
+# service.py
+#
+
+import re
+import netaddr
+
+from .utils import logger, AttrDict
+from .container import ServiceInfoContainer
+
+
+def getIPaddress(addressWithMask):
+ return str(netaddr.IPNetwork(addressWithMask).ip)
+
+
+def dhcpSubnetConfigGenerator():
+ dhcpSubnetConfigs = list()
+
+ serviceInfoContainer = ServiceInfoContainer()
+ serviceInfoContainer.initialize()
+
+ for domainName, domain in serviceInfoContainer.all():
+ subnetConfig = {
+ "dns_search": [domainName],
+ "dns_servers": [domain["dnsServer"]["address"]],
+ "ntp_servers": [domain["ntpServer"]["address"]],
+ "tftp_servers": domain["dhcpServer"]["address"],
+ "range": domain["dhcprange"],
+ "subnet": domain["subnet"],
+ "routers": [{"ip": domain["router"]}],
+ "hosts": list(),
+ }
+
+ for address, host in domain["hosts"].items():
+ subnetConfig["hosts"].append(
+ {
+ "ip_addr": getIPaddress(address),
+ "mac_addr": host["macaddr"],
+ "name": host["hostname"],
+ }
+ )
+
+ subnetConfig["hosts"] = sorted(
+ subnetConfig["hosts"], key=lambda x: int(x["ip_addr"].split(".")[-1])
+ )
+ dhcpSubnetConfigs.append(subnetConfig)
+
+ return dhcpSubnetConfigs
+
+
+def dnsFowardZoneConfigGenerator():
+ def getDomainNameByIP(ip_address):
+ """
+ getDomainNameByIP will return the corresponding domain name of an IP address
+ In the ntpServer, dhcpServer, dnsServer we only have the IP addresses of them,
+ But we can use the dhcp subnet configuration to find the FQDN
+ """
+ dhcpSubnetConfigs = dhcpSubnetConfigGenerator()
+
+ for domain in dhcpSubnetConfigs:
+ domainName = domain["dns_search"][0]
+ for host in domain["hosts"]:
+ if ip_address == host["ip_addr"]:
+ return f"{host['name']}.{domainName}."
+
+
+ dnsForwardZoneConfigs = dict()
+
+ serviceInfoContainer = ServiceInfoContainer()
+ serviceInfoContainer.initialize()
+
+ for domainName, domain in serviceInfoContainer.all():
+ forwardZoneConfig = {
+ "cname": dict(),
+ "a": dict(),
+ "ns": list(),
+ "srv": dict(),
+ "txt": dict(),
+ }
+
+ # Get the services set to this Tenant network
+ ntpServer = domain["ntpServer"] or None
+ dhcpServer = domain["dhcpServer"] or None
+ dnsServer = domain["dnsServer"] or None
+
+ # If service exists, set the FQDN to CNAME records
+ if ntpServer:
+ forwardZoneConfig["cname"]["dns"] = getDomainNameByIP(ntpServer["address"])
+ if dhcpServer:
+ forwardZoneConfig["cname"]["tftp"] = getDomainNameByIP(dhcpServer["address"])
+ if dnsServer:
+ forwardZoneConfig["cname"]["dns"] = getDomainNameByIP(dnsServer["address"])
+ forwardZoneConfig["ns"].append(getDomainNameByIP(dnsServer["address"]))
+
+ for address, host in domain["hosts"].items():
+ # Add exist IP address into dnsReverseZoneConfigs,
+ hostname = host["hostname"]
+ forwardZoneConfig["a"][hostname] = address
+
+ for address in netaddr.IPSet(domain["dhcprange"]):
+ # If address exists in ServiceInfoContainer's host dictionary,
+ # Use the pre-generated hostname as A record's name
+ hostname = "dhcp%03d" % address.words[-1]
+ forwardZoneConfig["a"][hostname] = str(address)
+
+ dnsForwardZoneConfigs[domainName] = forwardZoneConfig
+
+ return dnsForwardZoneConfigs
+
+
+def dnsReverseZoneConfigGenerator():
+ def canonicalize_rfc1918_prefix(prefix):
+ """
+ RFC1918 prefixes need to be expanded to their widest canonical range to
+ group all reverse lookup domains together for reverse DNS with NSD/Unbound.
+ """
+
+ pnet = netaddr.IPNetwork(prefix)
+ (o1, o2, o3, o4) = pnet.network.words # Split ipv4 octets
+ cidr_plen = pnet.prefixlen
+
+ if o1 == 10:
+ o2 = o3 = o4 = 0
+ cidr_plen = 8
+ elif (o1 == 172 and o2 >= 16 and o2 <= 31) or (o1 == 192 and o2 == 168):
+ o3 = o4 = 0
+ cidr_plen = 16
+
+ return "%s/%d" % (".".join(map(str, [o1, o2, o3, o4])), cidr_plen)
+
+ serviceInfoContainer = ServiceInfoContainer()
+ serviceInfoContainer.initialize()
+
+ # dnsReverseZoneConfigs contains all reverse zone records.
+ dnsReverseZoneConfigs = dict()
+ widedomain = None
+
+ for domainName, domain in serviceInfoContainer.all():
+
+ # Expand network range to group all tenant domains
+ widedomain = widedomain or canonicalize_rfc1918_prefix(domain["subnet"])
+
+ # Create the basic structure of reverse zone config
+ # {"10.0.0.0/8": {"ns": list(), "ptr": dict()}}
+ dnsReverseZoneConfigs.setdefault(widedomain, dict())
+ dnsReverseZoneConfigs[widedomain].setdefault("ns", list())
+ dnsReverseZoneConfigs[widedomain].setdefault("ptr", dict())
+
+ # Get the DNS services set to this Tenant network
+ dnsServer = domain["dnsServer"]["name"] if domain["dnsServer"] else None
+
+ # If service exists, set the FQDN to CNAME records
+ if dnsServer:
+ dnsReverseZoneConfigs[widedomain]["ns"].append(f"{domainName}.{dnsServer}.")
+
+ for address, host in domain["hosts"].items():
+ # Add exist IP address into dnsReverseZoneConfigs,
+ hostname = host["hostname"]
+ dnsReverseZoneConfigs[widedomain]["ptr"][
+ address
+ ] = f"{hostname}.{domainName}."
+
+ for address in netaddr.IPSet(domain["dhcprange"]):
+ # Add DHCP range IP address into dnsReverseZoneConfigs,
+ # Use the pre-generated hostname as A record's name
+ hostname = "dhcp%03d" % address.words[3]
+ dnsReverseZoneConfigs[widedomain]["ptr"][
+ str(address)
+ ] = f"{hostname}.{domainName}."
+
+ dnsReverseZoneConfigs[widedomain]["ptr"] = dict(
+ sorted(
+ dnsReverseZoneConfigs[widedomain]["ptr"].items(),
+ key=lambda x: (int(x[0].split(".")[-2]), int(x[0].split(".")[-1])),
+ )
+ )
+
+ return dnsReverseZoneConfigs