some refactor
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
from .base import run
|
||||
|
||||
raise SystemExit(run())
|
||||
@@ -0,0 +1,30 @@
|
||||
from .parse import parse_csv
|
||||
from .domain.models import Topology
|
||||
from .parse.convert_raw import (
|
||||
convert_raw_topology,
|
||||
)
|
||||
import logging
|
||||
|
||||
# from .make_res import make_result
|
||||
# from .structures import Result
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
|
||||
|
||||
def run():
|
||||
raw_devices = parse_csv("data/input/table.csv")
|
||||
# print(raw_devices)
|
||||
topology = convert_raw_topology(raw_devices)
|
||||
print(topology)
|
||||
|
||||
# 2) make structured result
|
||||
# result = make_result(devices_raw, network_agg, network_interfaces)
|
||||
|
||||
# # 3) output (for debug)
|
||||
# print(result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
@@ -0,0 +1,194 @@
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
|
||||
# ===========================
|
||||
|
||||
|
||||
class Interface:
|
||||
name: str
|
||||
ip_address: Optional[str]
|
||||
network: Optional[str]
|
||||
default_gateway: Optional[str]
|
||||
# ---
|
||||
device: Optional[
|
||||
"Device"
|
||||
] # set by Device.add_interface() when the interface is added to a device
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
ip_address: Optional[str] = None,
|
||||
network: Optional[str] = None,
|
||||
default_gateway: Optional[str] = None,
|
||||
):
|
||||
if not isinstance(name, str):
|
||||
raise ValueError("Interface 'name' must be a string")
|
||||
if ip_address is not None and not isinstance(ip_address, str):
|
||||
raise ValueError("Interface 'ip_address' must be a string or None")
|
||||
if network is not None and not isinstance(network, str):
|
||||
raise ValueError("Interface 'network' must be a string or None")
|
||||
if default_gateway is not None and not isinstance(default_gateway, str):
|
||||
raise ValueError("Interface 'default_gateway' must be a string or None")
|
||||
|
||||
self.name = name
|
||||
self.ip_address = ip_address
|
||||
self.network = network
|
||||
self.default_gateway = default_gateway
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Interface(name={self.name}, ip_address={self.ip_address}, network={self.network}, default_gateway={self.default_gateway})"
|
||||
|
||||
|
||||
class VirtualInterface(Interface):
|
||||
pass
|
||||
|
||||
|
||||
# ===========================
|
||||
|
||||
|
||||
class Device:
|
||||
name: str
|
||||
interfaces: Dict[str, Interface]
|
||||
|
||||
def __init__(self, name: str):
|
||||
if not isinstance(name, str):
|
||||
raise ValueError("Device 'name' must be a string")
|
||||
|
||||
self.name = name
|
||||
self.interfaces = dict()
|
||||
|
||||
def add_interface(self, interface: Interface):
|
||||
interface.device = (
|
||||
self # set the device attribute of the interface to this device
|
||||
)
|
||||
self.interfaces[interface.name] = interface
|
||||
|
||||
def rm_interface(self, interface: Interface):
|
||||
if interface.name in self.interfaces:
|
||||
del self.interfaces[interface.name]
|
||||
interface.device = None # clear the device attribute of the interface
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Interface '{interface.name}' not found in device '{self.name}'"
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Device(name={self.name}, interfaces={self.interfaces})"
|
||||
|
||||
|
||||
class Host(Device):
|
||||
pass
|
||||
|
||||
|
||||
class Router(Device):
|
||||
pass
|
||||
|
||||
|
||||
class Switch(Device):
|
||||
pass
|
||||
|
||||
|
||||
# ===========================
|
||||
|
||||
|
||||
class Network:
|
||||
name: str
|
||||
interfaces: List[Interface]
|
||||
vlan: Optional[str] # need a proper parse
|
||||
network_ip: Optional[str]
|
||||
subnet_mask: Optional[str]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
vlan: Optional[str] = None,
|
||||
network_ip: Optional[str] = None,
|
||||
subnet_mask: Optional[str] = None,
|
||||
):
|
||||
if not isinstance(name, str):
|
||||
raise ValueError("Network 'name' must be a string")
|
||||
if vlan is not None and not isinstance(vlan, str):
|
||||
raise ValueError("Network 'vlan' must be a string or None")
|
||||
if network_ip is not None and not isinstance(network_ip, str):
|
||||
raise ValueError("Network 'network_ip' must be a string or None")
|
||||
if subnet_mask is not None and not isinstance(subnet_mask, str):
|
||||
raise ValueError("Network 'subnet_mask' must be a string or None")
|
||||
|
||||
self.name = name
|
||||
self.interfaces = []
|
||||
self.vlan = vlan
|
||||
self.network_ip = network_ip
|
||||
self.subnet_mask = subnet_mask
|
||||
|
||||
def add_interface(self, interface: Interface):
|
||||
if not isinstance(interface, Interface):
|
||||
raise ValueError("Argument must be an instance of Interface")
|
||||
if interface in self.interfaces:
|
||||
raise ValueError(
|
||||
f"Interface '{interface.name}' already exists in network '{self.name}'"
|
||||
)
|
||||
|
||||
interface.network = (
|
||||
self.name # set the network attribute of the interface to this network
|
||||
)
|
||||
self.interfaces.append(interface)
|
||||
|
||||
def rm_interface(self, interface: Interface):
|
||||
if interface in self.interfaces:
|
||||
self.interfaces.remove(interface)
|
||||
interface.network = None # clear the network attribute of the interface
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Interface '{interface.name}' not found in network '{self.name}'"
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Network(name={self.name}, interfaces={self.interfaces}), vlan={self.vlan}, network_ip={self.network_ip}, subnet_mask={self.subnet_mask})"
|
||||
|
||||
|
||||
class Topology:
|
||||
devices: Dict[str, Device]
|
||||
networks: Dict[str, Network]
|
||||
|
||||
def __init__(self):
|
||||
self.devices = dict()
|
||||
self.networks = dict()
|
||||
|
||||
def add_device(self, device: Device):
|
||||
if not isinstance(device, Device):
|
||||
raise ValueError("Argument must be an instance of Device")
|
||||
|
||||
if device.name in self.devices:
|
||||
raise ValueError(
|
||||
f"Device with name '{device.name}' already exists in topology"
|
||||
)
|
||||
|
||||
self.devices[device.name] = device
|
||||
|
||||
def rm_device(self, device: Device):
|
||||
if device.name in self.devices:
|
||||
del self.devices[device.name]
|
||||
else:
|
||||
raise ValueError(f"Device with name '{device.name}' not found in topology")
|
||||
|
||||
def add_network(self, network: Network):
|
||||
if not isinstance(network, Network):
|
||||
raise ValueError("Argument must be an instance of Network")
|
||||
|
||||
if network.name in self.networks:
|
||||
raise ValueError(
|
||||
f"Network with name '{network.name}' already exists in topology"
|
||||
)
|
||||
|
||||
self.networks[network.name] = network
|
||||
|
||||
def rm_network(self, network: Network):
|
||||
if network.name in self.networks:
|
||||
del self.networks[network.name]
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Network with name '{network.name}' not found in topology"
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Topology(devices={self.devices}, networks={self.networks})"
|
||||
@@ -0,0 +1,32 @@
|
||||
from typing import Dict, Any, List
|
||||
import csv
|
||||
import logging
|
||||
|
||||
|
||||
class RawDevices:
|
||||
id: int
|
||||
fields: Dict[str, Any]
|
||||
|
||||
def __init__(self, id: int, fields: Dict[str, Any]):
|
||||
if not isinstance(id, int):
|
||||
raise ValueError("RawDevices 'id' must be an integer")
|
||||
if not isinstance(fields, dict):
|
||||
raise ValueError("RawDevices 'fields' must be a dictionary")
|
||||
if any(not isinstance(k, str) for k in fields.keys()):
|
||||
raise ValueError("RawDevices 'fields' keys must be strings")
|
||||
|
||||
self.id = id
|
||||
self.fields = fields
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"RawDevices(id={self.id}, fields={self.fields})"
|
||||
|
||||
|
||||
def parse_csv(file_path: str, delimiter: str = ",") -> List[RawDevices]:
|
||||
logging.info(f"Parsing CSV file: {file_path}")
|
||||
|
||||
with open(file_path, mode="r", encoding="utf-8") as csvfile:
|
||||
reader = csv.DictReader(csvfile, delimiter=delimiter)
|
||||
devices = [RawDevices(idx + 1, row) for idx, row in enumerate(reader)]
|
||||
|
||||
return devices
|
||||
@@ -0,0 +1,151 @@
|
||||
from ..domain.models import (
|
||||
Interface,
|
||||
VirtualInterface,
|
||||
Device,
|
||||
Host,
|
||||
Router,
|
||||
Switch,
|
||||
Network,
|
||||
Topology,
|
||||
)
|
||||
|
||||
from . import RawDevices
|
||||
|
||||
name_matching = {
|
||||
"DEVICE_TYPE": "Role",
|
||||
"DEVICE_NAME": "Name",
|
||||
"INTERFACE_NAME": "Interface",
|
||||
"NETWORK_NAME": "Network",
|
||||
"VLAN": "VLAN",
|
||||
"NETWORK_IP": "Network IP",
|
||||
"SUBNET_MASK": "Mask",
|
||||
"IP_ADDRESS": "Device IP",
|
||||
"DEFAULT_GATEWAY": "Default Gateway",
|
||||
# --- In-cell fields ---
|
||||
"TRUNK": "trunk",
|
||||
"HOST": "Host",
|
||||
"ROUTER": "Router",
|
||||
"SWITCH": "Switch",
|
||||
}
|
||||
|
||||
|
||||
def parse_devices(raw_devices: list[RawDevices]) -> list[Device]:
|
||||
devices = []
|
||||
|
||||
for raw_device in raw_devices:
|
||||
device_type = raw_device.fields.get(name_matching["DEVICE_TYPE"], "").strip()
|
||||
device_name = raw_device.fields.get(name_matching["DEVICE_NAME"], "").strip()
|
||||
|
||||
if not device_type or not device_name:
|
||||
raise ValueError(
|
||||
f"Device with ID {raw_device.id} is missing required fields 'DEVICE_TYPE' and 'DEVICE_NAME'"
|
||||
)
|
||||
|
||||
if device_name in [d.name for d in devices]:
|
||||
continue
|
||||
|
||||
if device_type == name_matching["HOST"]:
|
||||
device = Host(name=device_name)
|
||||
elif device_type == name_matching["ROUTER"]:
|
||||
device = Router(name=device_name)
|
||||
elif device_type == name_matching["SWITCH"]:
|
||||
device = Switch(name=device_name)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Device with ID {raw_device.id} has unrecognized DEVICE_TYPE '{device_type}'"
|
||||
)
|
||||
|
||||
devices.append(device)
|
||||
|
||||
return devices
|
||||
|
||||
|
||||
def add_interfaces(devices: list[Device], raw_devices: list[RawDevices]) -> None:
|
||||
for raw_device in raw_devices:
|
||||
device_name = raw_device.fields.get(name_matching["DEVICE_NAME"], "").strip()
|
||||
device = next(
|
||||
(d for d in devices if d.name == device_name), None
|
||||
) # some other way?
|
||||
|
||||
if not device:
|
||||
raise ValueError(
|
||||
f"Device with name '{device_name}' not found for interface parsing"
|
||||
)
|
||||
|
||||
interface = Interface(
|
||||
name=raw_device.fields.get(name_matching["INTERFACE_NAME"], "").strip(),
|
||||
ip_address=raw_device.fields.get(name_matching["IP_ADDRESS"], "").strip(),
|
||||
network=raw_device.fields.get(name_matching["NETWORK_NAME"], "").strip(),
|
||||
default_gateway=raw_device.fields.get(
|
||||
name_matching["DEFAULT_GATEWAY"], ""
|
||||
).strip(),
|
||||
)
|
||||
device.add_interface(interface)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def parse_networks(
|
||||
devices: list[Device], raw_devices: list[RawDevices]
|
||||
) -> list[Network]:
|
||||
networks = []
|
||||
|
||||
for raw_device in raw_devices:
|
||||
network_name = raw_device.fields.get(name_matching["NETWORK_NAME"], "").strip()
|
||||
if not network_name:
|
||||
continue
|
||||
|
||||
vlan = (raw_device.fields.get(name_matching["VLAN"], "").strip(),)
|
||||
network_ip = (raw_device.fields.get(name_matching["NETWORK_IP"], "").strip(),)
|
||||
subnet_mask = (raw_device.fields.get(name_matching["SUBNET_MASK"], "").strip(),)
|
||||
|
||||
if network_name in [n.name for n in networks]: # update if already exists
|
||||
network = next(n for n in networks if n.name == network_name)
|
||||
|
||||
if not network.vlan and vlan:
|
||||
network.vlan = vlan
|
||||
if not network.network_ip and network_ip:
|
||||
network.network_ip = network_ip
|
||||
if not network.subnet_mask and subnet_mask:
|
||||
network.subnet_mask = subnet_mask
|
||||
continue
|
||||
|
||||
network = Network(
|
||||
name=network_name,
|
||||
vlan=raw_device.fields.get(name_matching["VLAN"], "").strip(),
|
||||
network_ip=raw_device.fields.get(name_matching["NETWORK_IP"], "").strip(),
|
||||
subnet_mask=raw_device.fields.get(name_matching["SUBNET_MASK"], "").strip(),
|
||||
)
|
||||
networks.append(network)
|
||||
|
||||
return networks
|
||||
|
||||
|
||||
def assign_interfaces_to_networks(
|
||||
networks: list[Network], devices: list[Device]
|
||||
) -> None:
|
||||
for network in networks:
|
||||
for device in devices:
|
||||
for _, interface in device.interfaces.items():
|
||||
if interface.network == network.name:
|
||||
network.add_interface(interface)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def convert_raw_topology(raw_devices: list[RawDevices]) -> Topology:
|
||||
topology = Topology()
|
||||
|
||||
devices = parse_devices(raw_devices)
|
||||
add_interfaces(devices, raw_devices)
|
||||
|
||||
for device in devices:
|
||||
topology.add_device(device)
|
||||
|
||||
networks = parse_networks(devices, raw_devices)
|
||||
assign_interfaces_to_networks(networks, devices)
|
||||
|
||||
for network in networks:
|
||||
topology.networks[network.name] = network
|
||||
|
||||
return topology
|
||||
Reference in New Issue
Block a user