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