Refactor nbhelper
Change-Id: I69d10d164fac3eb319e072447a520905880c31dd
diff --git a/scripts/nbhelper/container.py b/scripts/nbhelper/container.py
new file mode 100644
index 0000000..6e0cfcf
--- /dev/null
+++ b/scripts/nbhelper/container.py
@@ -0,0 +1,291 @@
+import netaddr
+
+
+class Singleton(type):
+ _instances = {}
+
+ def __call__(cls, *args, **kwargs):
+ if cls not in cls._instances:
+ cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
+ return cls._instances[cls]
+
+
+class Container(object):
+ def __init__(self):
+ self.instances = dict()
+
+ def add(self, instance_id, instance):
+ if instance_id in self.instances:
+ raise Exception()
+ self.instances[instance_id] = instance
+
+ def get(self, instance_id):
+ if instance_id not in self.instances:
+ return instance_id
+ return self.instances[instance_id]
+
+
+class AssignedObjectContainer(Container):
+ # AssignedObjectContainer is the parent class
+ # which is with the shared function of Devices/VMs Container
+
+ def all(self):
+ return self.instances.values()
+
+ def getDNSServer(self):
+ for instance in self.instances.values():
+ if "dns" in list(map(str, instance.services)):
+ return instance
+
+ def getDHCPServer(self):
+ for instance in self.instances.values():
+ if "tftp" in list(map(str, instance.services)):
+ return instance
+
+ def getNTPServer(self):
+ for instance in self.instances.values():
+ if "ntp" in list(map(str, instance.services)):
+ return instance
+
+ def getRouters(self):
+ """ Get a list of Devices/VMs which type is Router """
+
+ ret = list()
+ for instance in self.instances.values():
+ if instance.role == "router":
+ ret.append(instance)
+
+ return ret
+
+ def getRouterIPforPrefix(self, prefix):
+ """ Get the first found IP address of exist routers as string """
+
+ for router in self.getRouters():
+ for interface in router.interfaces.values():
+
+ # The mgmt-only interface will not act as gateway
+ if interface["mgmtOnly"]:
+ continue
+
+ for address in interface["addresses"]:
+ if netaddr.IPNetwork(address).ip in netaddr.IPNetwork(
+ prefix.subnet
+ ):
+ return str(netaddr.IPNetwork(address).ip)
+
+
+class DeviceContainer(AssignedObjectContainer, metaclass=Singleton):
+ # DeviceContainer holds all devices fetch from Netbox, device_id as key
+ pass
+
+
+class VirtualMachineContainer(AssignedObjectContainer, metaclass=Singleton):
+ # DeviceContainer holds all devices fetch from Netbox, vm_id as key
+ pass
+
+
+class PrefixContainer(Container, metaclass=Singleton):
+ # PrefixContainer holds all prefixes fetch from Netbox, prefix(str) as key
+
+ def get(self, instance_id, name_segments=1):
+ return super().get(instance_id)
+
+ def all(self):
+ return self.instances.values()
+
+ def all_reserved_ips(self, ip_addr=""):
+ ret = list()
+
+ for prefix in self.instances.values():
+ if ip_addr and netaddr.IPNetwork(ip_addr).ip in netaddr.IPNetwork(
+ prefix.subnet
+ ):
+ if prefix.reserved_ips:
+ return list(prefix.reserved_ips.values())
+ else:
+ if prefix.reserved_ips:
+ ret.extend(list(prefix.reserved_ips.values()))
+
+ return ret
+
+
+class ServiceInfoContainer(Container, metaclass=Singleton):
+ # ServiceInfoContainer holds hosts DNS/DHCP information in Tenant networks
+
+ def __init__(self):
+ super().__init__()
+ self.initialized = False
+
+ def all(self):
+ return self.instances.items()
+
+ def initialize(self):
+ if self.initialized:
+ return
+
+ deviceContainer = DeviceContainer()
+ vmContainer = VirtualMachineContainer()
+ prefixContainer = PrefixContainer()
+
+ for prefix in prefixContainer.all():
+
+ subnet = netaddr.IPNetwork(prefix.subnet)
+ domain = prefix.data.description or ""
+
+ if not domain:
+ continue
+
+ # If prefix has set the Router IP, use this value as defualt,
+ # otherwise find router in this subnet and get its IP address
+ routes = (
+ prefix.routes
+ or deviceContainer.getRouterIPforPrefix(prefix)
+ or vmContainer.getRouterIPforPrefix(prefix)
+ or ""
+ )
+
+ self.instances[prefix.data.description] = {
+ "domain": domain,
+ "subnet": prefix.subnet,
+ "router": routes,
+ "dhcprange": prefix.dhcp_range or "",
+ "hosts": dict(),
+ "dnsServer": None,
+ "ntpServer": None,
+ "dhcpServer": None,
+ }
+
+ # Find the service IP address for this network
+ serviceMap = {
+ "dnsServer": deviceContainer.getDNSServer()
+ or vmContainer.getDNSServer(),
+ "ntpServer": deviceContainer.getNTPServer()
+ or vmContainer.getNTPServer(),
+ "dhcpServer": deviceContainer.getDHCPServer()
+ or vmContainer.getDHCPServer(),
+ }
+
+ # Loop through the device's IP, and set IP to dataset
+ for service, device in serviceMap.items():
+ for interface in device.interfaces.values():
+ for address in interface["addresses"]:
+ address = netaddr.IPNetwork(address).ip
+ if address in subnet:
+ self.instances[domain][service] = {
+ "name": device.name,
+ "address": str(address),
+ }
+ else:
+ for neighbor in prefix.neighbor:
+ neighborSubnet = netaddr.IPNetwork(neighbor.subnet)
+ if address in neighborSubnet:
+ self.instances[domain][service] = {
+ "name": device.name,
+ "address": str(address),
+ }
+ break
+
+ # A dict to check if the device exists in this domain (prefix)
+ deviceInDomain = dict()
+
+ # Gather all Devices/VMs in this tenant, build IP/Hostname map
+ for device in list(deviceContainer.all()) + list(vmContainer.all()):
+
+ # Iterate the interface owned by device
+ for intfName, interface in device.interfaces.items():
+
+ # Iterate the address with each interface
+ for address in interface["addresses"]:
+
+ # Extract the IP address from interface's IP (w/o netmask)
+ address = netaddr.IPNetwork(address).ip
+
+ # Only record the IP address in current subnet,
+ # Skip if the mac_address is blank
+ if address in subnet:
+
+ # deviceInDomain store the interface information like:
+ # {"mgmtserver1": {
+ # "non-mgmt-counter": 1,
+ # "interface": {
+ # "bmc": {
+ # "mgmtOnly": True,
+ # "macaddr": "ca:fe:ba:be:00:00",
+ # "ipaddr": [IPAddress("10.32.4.1")]
+ # }
+ # "eno1": {
+ # "mgmtOnly": False,
+ # "macaddr": "ca:fe:ba:be:11:11",
+ # "ipaddr": [IPAddress("10.32.4.129"), IPAddress("10.32.4.130")]
+ # }
+ # }
+ # "mgmtswitch1": ...
+ # }
+
+ deviceInDomain.setdefault(device.name, dict())
+ deviceInDomain[device.name].setdefault(
+ "non-mgmt-counter", 0
+ )
+ deviceInDomain[device.name].setdefault("interfaces", dict())
+
+ # Set up a interface structure in deviceInDomain[device.name]
+ deviceInDomain[device.name]["interfaces"].setdefault(
+ intfName, dict()
+ )
+ interfaceDict = deviceInDomain[device.name]["interfaces"][
+ intfName
+ ]
+ interfaceDict.setdefault("mgmtOnly", False)
+
+ # Use interface["mac_address"] as the default value, but if the mac_address
+ # is None, that means we are dealing with a virtual interfaces
+ # so we can get the linked interface's mac_address instead
+
+ interfaceDict.setdefault(
+ "mac_address", interface["mac_address"] or
+ device.interfaces[interface["instance"].label]["mac_address"]
+ )
+ interfaceDict.setdefault("ip_addresses", list())
+ interfaceDict["ip_addresses"].append(address)
+
+ # If the interface is mgmtOnly, set the attribute to True
+ # Otherwise, increase the non-mgmt-counter, the counter uses to
+ # find out how many interfaces of this device has IPs on subnet
+ if interface["mgmtOnly"]:
+ interfaceDict["mgmtOnly"] = True
+ else:
+ deviceInDomain[device.name]["non-mgmt-counter"] += 1
+
+ for deviceName, data in deviceInDomain.items():
+
+ nonMgmtCounter = data["non-mgmt-counter"]
+ for intfName, interface in data["interfaces"].items():
+
+ # If current interface doesn't have mac address set, skip
+ if not interface["mac_address"]:
+ continue
+
+ # In the default situation, hostname is deviceName
+ hostname_list = [deviceName]
+
+ # If the condition is -
+ # 1. multiple interfaces show on this subnet
+ # 2. the interface is a management-only interface
+ # then add interface name into hostname
+ if nonMgmtCounter > 1 or interface["mgmtOnly"]:
+ hostname_list.append(intfName)
+
+ # Iterate the IP address owns by current interface,
+ # if the interface has multiple IP addresses,
+ # add last digit to hostname for identifiability
+ for address in interface["ip_addresses"]:
+ hostname = hostname_list.copy()
+ if len(interface["ip_addresses"]) > 1:
+ hostname.append(str(address.words[-1]))
+
+ self.instances[domain]["hosts"][str(address)] = {
+ "hostname": "-".join(hostname),
+ "macaddr": interface["mac_address"].lower(),
+ }
+
+ self.initialized = True