some refactor

This commit is contained in:
2026-02-16 01:35:03 +03:00
parent 612c2ad6b2
commit 76597cc7f1
25 changed files with 421 additions and 770 deletions
+2
View File
@@ -1,3 +1,5 @@
tmp
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]
View File
-3
View File
@@ -1,3 +0,0 @@
, prop1 , prop2 , prop3
ob1 , val1 , , val2
obj2 , , val3 , val4
1 prop1 prop2 prop3
2 ob1 val1 val2
3 obj2 val3 val4
-160
View File
@@ -1,160 +0,0 @@
# yaml-language-server: $schema=./topology-schema.json
schema: asvk.topology/1.0
meta:
id: "string"
title: "string"
description: "string"
provider:
name: virtualbox # virtualbox | kvm | container
defaults:
cpu: 1
ram_mb: 512
disk_gb: 8
os_image: "debian-12-cloud.qcow2"
nic_model: virtio
defaults:
routing:
stack: bird # bird | frr | linux
protocols_enabled: []
switch:
impl: linux-bridge # linux-bridge | ovs
firewall:
impl: nftables # nftables | none
mgmt:
ssh_user: "lab"
ssh_key: "~/.ssh/id_rsa.pub"
variables: # for templates
asn: 65000
domain: "lab.local"
networks: # L2 / L3 / VLAN trunks
- id: lan1
type: l2 # l2 | l3
mtu: 1500
- id: lan13
type: l2
- id: trunk1
type: l2
vlan:
mode: trunk
allowed: [4, 8]
- id: mgmt
type: l3
cidr: 192.168.100.0/24
dhcp: false
nodes: # nodes (VM/containers)
- name: R1
role: router # router | switch | host
resources:
cpu: 1
ram_mb: 512
mgmt:
ip: 192.168.100.11/24
gw: 192.168.100.1
net: mgmt
interfaces: # order matters: eth1 ↔ adapter2 and so on
- name: eth1
network: lan13
addresses: ["10.0.13.1/24"]
- name: eth0 # MGMT
network: mgmt
routing:
stack: bird # inherited from defaults, can be omitted
protocols:
static:
routes:
- to: 0.0.0.0/0
via: 10.0.13.3
ospf: # if needed
enabled: false
rip:
enabled: false
services: # servers/daemons of the host
- name: sshd
enabled: true
switching: # L2 config for switch nodes
- node: S1
impl: linux-bridge # linux-bridge | ovs
bridges:
- name: br0
ports:
- if: eth1 # to trunk1
vlan:
mode: trunk
allowed: [4, 8]
- if: eth2 # access → VLAN 4
vlan:
mode: access
vid: 4
- if: eth3 # access → VLAN 8
vlan:
mode: access
vid: 8
svis: # VLAN management interfaces (SVI)
- vid: 4
ifname: br0.4
addresses: ["10.0.4.254/24"]
firewall: # High-level ACL-> to nftables
- node: R2
rules:
- id: allow-ospf
match:
in_if: eth1
ip_proto: ospf
action: accept
- id: telnet-from-R1-to-S1
match:
src: 10.0.13.1/32
dst: 10.0.4.254/32
l4: { proto: tcp, dport: 23 }
action: accept
- id: ftp-from-R3-to-S2
match:
src: 10.0.13.3/32
dst: 10.0.6.254/32
l4: { proto: tcp, dports: [20, 21] }
action: accept
- id: drop-rest
action: drop
profiles: # overlays - patches on top of the base description
- name: "all-frr"
description: "Switch all control-plane to FRR"
patches:
- op: replace
path: /defaults/routing/stack
value: frr
- name: "mix-bird-frr-ospf"
description: "BIRD by default, but OSPF on R2 via FRR"
patches:
- op: add
path: /nodes
where:
name: R2
patch:
routing:
protocols:
ospf:
enabled: true
impl: frr
- name: "switch-ovs"
patches:
- op: replace
path: /defaults/switch/impl
value: ovs
tests: # tests (expected result)
- name: ping-R1-to-R3
from: R1
to: 10.0.13.3
expect: success
- name: ping-VLAN-4-R1-to-R3
from: R1
to: 10.0.4.1
expect: success
-607
View File
@@ -1,607 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://github.com/asvk-topology/schema/1.0",
"title": "ASVK Topology Configuration Schema",
"description": "JSON Schema for ASVK topology configuration files",
"type": "object",
"required": ["schema", "meta", "provider", "nodes"],
"properties": {
"schema": {
"type": "string",
"description": "Schema identifier",
"pattern": "^asvk\\.topology/[0-9]+\\.[0-9]+$",
"examples": ["asvk.topology/1.0"]
},
"meta": {
"type": "object",
"description": "Metadata for the topology configuration",
"required": ["id", "title"],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the topology",
"pattern": "^[a-zA-Z0-9_-]+$"
},
"title": {
"type": "string",
"description": "Human-readable name for the topology"
},
"description": {
"type": "string",
"description": "Detailed description of the topology"
}
}
},
"provider": {
"type": "object",
"description": "Provider configuration for VMs/containers",
"required": ["name"],
"properties": {
"name": {
"type": "string",
"description": "Provider name",
"enum": ["virtualbox", "kvm", "container"]
},
"defaults": {
"type": "object",
"description": "Default resource allocation",
"properties": {
"cpu": {
"type": "integer",
"description": "Default CPU cores",
"minimum": 1,
"default": 1
},
"ram_mb": {
"type": "integer",
"description": "Default RAM in MB",
"minimum": 128,
"default": 512
},
"disk_gb": {
"type": "integer",
"description": "Default disk size in GB",
"minimum": 1,
"default": 8
},
"os_image": {
"type": "string",
"description": "Default OS image"
},
"nic_model": {
"type": "string",
"description": "Default NIC model",
"enum": ["virtio", "e1000", "rtl8139"]
},
"promiscuous": {
"type": "string",
"description": "Promiscuous mode setting",
"enum": ["deny", "allow-vms", "allow-all"]
}
}
},
"images": {
"type": "object",
"description": "Custom images for different node types",
"properties": {
"router": { "type": "string" },
"switch": { "type": "string" },
"host": { "type": "string" }
}
}
}
},
"defaults": {
"type": "object",
"description": "Default behavior and software configuration",
"properties": {
"routing": {
"type": "object",
"properties": {
"stack": {
"type": "string",
"enum": ["bird", "frr", "linux"],
"default": "bird"
},
"protocols_enabled": {
"type": "array",
"items": {
"type": "string",
"enum": ["ospf", "rip", "bgp", "static"]
},
"default": []
}
}
},
"switch": {
"type": "object",
"properties": {
"impl": {
"type": "string",
"enum": ["linux-bridge", "ovs"],
"default": "linux-bridge"
}
}
},
"firewall": {
"type": "object",
"properties": {
"impl": {
"type": "string",
"enum": ["nftables", "none"],
"default": "nftables"
}
}
},
"mgmt": {
"type": "object",
"properties": {
"ssh_user": {
"type": "string",
"default": "lab"
},
"ssh_key": {
"type": "string",
"description": "Path to SSH public key"
}
}
}
}
},
"variables": {
"type": "object",
"description": "Global variables that can be referenced in templates",
"additionalProperties": true
},
"networks": {
"type": "array",
"description": "Network definitions (L2 domains, L3 networks, VLAN trunks)",
"items": {
"type": "object",
"required": ["id", "type"],
"properties": {
"id": {
"type": "string",
"description": "Unique network identifier",
"pattern": "^[a-zA-Z0-9_-]+$"
},
"type": {
"type": "string",
"enum": ["l2", "l3"],
"description": "Network type"
},
"mtu": {
"type": "integer",
"description": "MTU for the network",
"minimum": 68,
"maximum": 9000,
"default": 1500
},
"cidr": {
"type": "string",
"description": "CIDR notation for L3 networks",
"pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$"
},
"dhcp": {
"type": "boolean",
"description": "Enable DHCP on this network",
"default": false
},
"vlan": {
"type": "object",
"description": "VLAN configuration for trunk ports",
"properties": {
"mode": {
"type": "string",
"enum": ["access", "trunk"]
},
"allowed": {
"type": "array",
"items": {
"type": "integer",
"minimum": 1,
"maximum": 4094
},
"description": "Allowed VLAN IDs on trunk"
},
"vid": {
"type": "integer",
"description": "Native VLAN ID for access ports",
"minimum": 1,
"maximum": 4094
}
}
}
}
}
},
"nodes": {
"type": "array",
"description": "Node definitions (VMs/containers)",
"items": {
"type": "object",
"required": ["name", "role"],
"properties": {
"name": {
"type": "string",
"description": "Node name",
"pattern": "^[a-zA-Z0-9_-]+$"
},
"role": {
"type": "string",
"enum": ["router", "switch", "host"],
"description": "Node role/type"
},
"image": {
"type": "string",
"description": "OS image for the node (can reference provider.images)"
},
"resources": {
"type": "object",
"properties": {
"cpu": {
"type": "integer",
"minimum": 1
},
"ram_mb": {
"type": "integer",
"minimum": 128
},
"disk_gb": {
"type": "integer",
"minimum": 1
}
}
},
"mgmt": {
"type": "object",
"description": "Management network configuration",
"properties": {
"ip": {
"type": "string",
"pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$"
},
"gw": {
"type": "string",
"pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}$"
},
"net": {
"type": "string"
}
}
},
"interfaces": {
"type": "array",
"description": "Network interfaces (order matters)",
"items": {
"type": "object",
"required": ["name"],
"properties": {
"name": {
"type": "string",
"pattern": "^(eth[0-9]+|lo)$"
},
"network": {
"type": "string"
},
"loopback": {
"type": "boolean",
"description": "Indicates if this is a loopback interface"
},
"addresses": {
"type": "array",
"items": {
"type": "string",
"pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$"
}
}
},
"anyOf": [
{
"required": ["network"]
},
{
"required": ["loopback"],
"properties": {
"loopback": { "const": true }
}
}
]
}
},
"routing": {
"type": "object",
"properties": {
"stack": {
"type": "string",
"enum": ["bird", "frr", "linux"]
},
"protocols": {
"type": "object",
"properties": {
"static": {
"type": "object",
"properties": {
"routes": {
"type": "array",
"items": {
"type": "object",
"required": ["to", "via"],
"properties": {
"to": {
"type": "string",
"pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])|0\\.0\\.0\\.0/0$"
},
"via": {
"type": "string",
"pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}$"
}
}
}
}
}
},
"ospf": {
"type": "object",
"properties": {
"enabled": { "type": "boolean", "default": false },
"impl": {
"type": "string",
"enum": ["bird", "frr"]
}
}
},
"rip": {
"type": "object",
"properties": {
"enabled": { "type": "boolean", "default": false }
}
},
"bgp": {
"type": "object",
"properties": {
"enabled": { "type": "boolean", "default": false },
"asn": { "type": "integer" }
}
}
}
}
}
},
"services": {
"type": "array",
"items": {
"type": "object",
"required": ["name"],
"properties": {
"name": { "type": "string" },
"enabled": { "type": "boolean", "default": true }
}
}
}
}
}
},
"switching": {
"type": "array",
"description": "L2 switching configuration",
"items": {
"type": "object",
"required": ["node", "impl"],
"properties": {
"node": {
"type": "string",
"description": "Switch node name"
},
"impl": {
"type": "string",
"enum": ["linux-bridge", "ovs"]
},
"bridges": {
"type": "array",
"items": {
"type": "object",
"required": ["name"],
"properties": {
"name": { "type": "string" },
"ports": {
"type": "array",
"items": {
"type": "object",
"required": ["if"],
"properties": {
"if": {
"type": "string",
"pattern": "^eth[0-9]+$"
},
"vlan": {
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": ["access", "trunk"]
},
"allowed": {
"type": "array",
"items": {
"type": "integer",
"minimum": 1,
"maximum": 4094
}
},
"vid": {
"type": "integer",
"minimum": 1,
"maximum": 4094
}
}
}
}
}
},
"svis": {
"type": "array",
"description": "Switched Virtual Interfaces (SVI)",
"items": {
"type": "object",
"required": ["vid", "ifname"],
"properties": {
"vid": {
"type": "integer",
"minimum": 1,
"maximum": 4094
},
"ifname": {
"type": "string",
"pattern": "^[a-zA-Z0-9._-]+$"
},
"addresses": {
"type": "array",
"items": {
"type": "string",
"pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$"
}
}
}
}
}
}
}
}
}
}
},
"firewall": {
"type": "array",
"description": "Firewall configuration",
"items": {
"type": "object",
"required": ["node", "rules"],
"properties": {
"node": { "type": "string" },
"rules": {
"type": "array",
"items": {
"type": "object",
"required": ["id", "action"],
"properties": {
"id": {
"type": "string",
"pattern": "^[a-zA-Z0-9_-]+$"
},
"match": {
"type": "object",
"properties": {
"in_if": { "type": "string" },
"out_if": { "type": "string" },
"src": {
"type": "string",
"pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$"
},
"dst": {
"type": "string",
"pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$"
},
"ip_proto": {
"type": "string",
"enum": ["tcp", "udp", "icmp", "ospf", "all"]
},
"l4": {
"type": "object",
"properties": {
"proto": {
"type": "string",
"enum": ["tcp", "udp"]
},
"sport": {
"type": ["integer", "array"],
"items": { "type": "integer" },
"minimum": 1,
"maximum": 65535
},
"dport": {
"type": ["integer", "array"],
"items": { "type": "integer" },
"minimum": 1,
"maximum": 65535
},
"dports": {
"type": ["integer", "array"],
"items": { "type": "integer" },
"minimum": 1,
"maximum": 65535
}
}
}
}
},
"action": {
"type": "string",
"enum": ["accept", "drop", "reject"]
}
}
}
}
}
}
},
"profiles": {
"type": "array",
"description": "Configuration profiles/overlays",
"items": {
"type": "object",
"required": ["name", "patches"],
"properties": {
"name": { "type": "string" },
"description": { "type": "string" },
"patches": {
"type": "array",
"items": {
"type": "object",
"required": ["op", "path"],
"properties": {
"op": {
"type": "string",
"enum": ["add", "replace", "remove"]
},
"path": { "type": "string" },
"value": true,
"where": {
"type": "object",
"description": "Conditional matching for patches"
},
"patch": true
}
}
}
}
}
},
"tests": {
"type": "array",
"description": "Validation tests",
"items": {
"type": "object",
"required": ["name", "from", "to", "expect"],
"properties": {
"name": { "type": "string" },
"from": { "type": "string" },
"to": {
"type": ["string", "object"],
"properties": {
"ip": {
"type": "string",
"pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}$"
},
"node": { "type": "string" }
}
},
"expect": {
"type": "string",
"pattern": "^(success|failure|timeout|via .*)$"
}
}
}
}
}
}
+9
View File
@@ -0,0 +1,9 @@
[project]
name = "netdiag"
version = "0.2.0"
[tool.setuptools]
package-dir = {"" = "src"}
[tool.setuptools.packages.find]
where = ["src"]
View File
+3
View File
@@ -0,0 +1,3 @@
from .base import run
raise SystemExit(run())
+30
View File
@@ -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()
View File
+194
View File
@@ -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})"
View File
+32
View File
@@ -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
+151
View File
@@ -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
View File