First commit
This commit is contained in:
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
1
.idea/.name
generated
Normal file
1
.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
|||||||
|
parseur_logs
|
||||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
6
.idea/misc.xml
generated
Normal file
6
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="Python 3.13 (PythonProject)" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/PythonProject.iml" filepath="$PROJECT_DIR$/.idea/PythonProject.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
10
.idea/parseur_logs.iml
generated
Normal file
10
.idea/parseur_logs.iml
generated
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="Python 3.13 virtualenv at C:\Users\rveyssey\PycharmProjects\parseur_logs\.venv" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
58497
data/example-a.log
Normal file
58497
data/example-a.log
Normal file
File diff suppressed because it is too large
Load Diff
1121
data/switch_parsed.json
Normal file
1121
data/switch_parsed.json
Normal file
File diff suppressed because it is too large
Load Diff
37
main.py
Normal file
37
main.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# main.py
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from utils.texttools import split_display_sections
|
||||||
|
from models.hpe5130 import SwitchHPE5130
|
||||||
|
|
||||||
|
|
||||||
|
def read_file_with_fallback(path: Path) -> str:
|
||||||
|
try:
|
||||||
|
return path.read_text(encoding="utf-8")
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
print("[warn] UTF-8 échoué, tentative avec cp1252...")
|
||||||
|
return path.read_text(encoding="cp1252")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
log_path = Path("data/example-a.log")
|
||||||
|
if not log_path.exists():
|
||||||
|
print(f"Fichier non trouvé : {log_path}")
|
||||||
|
return
|
||||||
|
|
||||||
|
content = read_file_with_fallback(log_path)
|
||||||
|
sections = split_display_sections(content)
|
||||||
|
|
||||||
|
switch = SwitchHPE5130()
|
||||||
|
for section in sections:
|
||||||
|
switch.parse_block(section["section"], section["content"])
|
||||||
|
|
||||||
|
output_path = Path("data/switch_parsed.json")
|
||||||
|
with open(output_path, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(switch.to_dict(), f, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
|
print(f"Switch exporté dans {output_path}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
125
models/base_switch.py
Normal file
125
models/base_switch.py
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
# models/base_switch.py
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
class PowerSupply:
|
||||||
|
def __init__(self, id: int, state: str, mode: Optional[str] = None):
|
||||||
|
self.id = id
|
||||||
|
self.state = state
|
||||||
|
self.mode = mode
|
||||||
|
self.summary = f"Alimentation {id} en état {state}"
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
"object": "power_supply",
|
||||||
|
"id": self.id,
|
||||||
|
"state": self.state,
|
||||||
|
"mode": self.mode,
|
||||||
|
"summary": self.summary,
|
||||||
|
}
|
||||||
|
|
||||||
|
class Fan:
|
||||||
|
def __init__(self, id: int, state: str):
|
||||||
|
self.id = id
|
||||||
|
self.state = state
|
||||||
|
self.summary = f"Ventilateur {id} en état {state}"
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
"object": "fan",
|
||||||
|
"id": self.id,
|
||||||
|
"state": self.state,
|
||||||
|
"summary": self.summary,
|
||||||
|
}
|
||||||
|
|
||||||
|
class MACEntry:
|
||||||
|
def __init__(self, mac: str, vlan: int, port: str, type_: str):
|
||||||
|
self.mac = mac
|
||||||
|
self.vlan = vlan
|
||||||
|
self.port = port
|
||||||
|
self.type = type_
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
"object": "mac_entry",
|
||||||
|
"mac": self.mac,
|
||||||
|
"vlan": self.vlan,
|
||||||
|
"port": self.port,
|
||||||
|
"type": self.type,
|
||||||
|
}
|
||||||
|
|
||||||
|
class Port:
|
||||||
|
def __init__(self, name: str):
|
||||||
|
self.object = "port"
|
||||||
|
self.name = name
|
||||||
|
self.status = None
|
||||||
|
self.speed = None
|
||||||
|
self.vlan = None
|
||||||
|
self.link_type = None
|
||||||
|
self.trunk_vlans = []
|
||||||
|
self.bpdu_protection = False
|
||||||
|
self.portfast = False
|
||||||
|
self.loopback_detection = None
|
||||||
|
self.mac_addresses = []
|
||||||
|
self.summary = ""
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
"object": "port",
|
||||||
|
"name": self.name,
|
||||||
|
"status": self.status,
|
||||||
|
"speed": self.speed,
|
||||||
|
"vlan": self.vlan,
|
||||||
|
"link_type": self.link_type,
|
||||||
|
"trunk_vlans": self.trunk_vlans if self.link_type == "trunk" else None,
|
||||||
|
"bpdu_protection": self.bpdu_protection,
|
||||||
|
"portfast": self.portfast,
|
||||||
|
"loopback_detection": self.loopback_detection,
|
||||||
|
"mac_addresses": [m.to_dict() for m in self.mac_addresses],
|
||||||
|
"summary": self.summary,
|
||||||
|
}
|
||||||
|
|
||||||
|
class VLAN:
|
||||||
|
def __init__(self, vlan_id: int, name: Optional[str] = None, description: Optional[str] = None):
|
||||||
|
self.vlan_id = vlan_id
|
||||||
|
self.name = name
|
||||||
|
self.description = description
|
||||||
|
self.summary = f"VLAN {vlan_id} : {name or 'non nommé'}"
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
"object": "vlan",
|
||||||
|
"vlan_id": self.vlan_id,
|
||||||
|
"name": self.name,
|
||||||
|
"description": self.description,
|
||||||
|
"summary": self.summary,
|
||||||
|
}
|
||||||
|
|
||||||
|
class Switch:
|
||||||
|
def __init__(self):
|
||||||
|
self.hostname = None
|
||||||
|
self.model = None
|
||||||
|
self.firmware_version = None
|
||||||
|
self.boot_image = None
|
||||||
|
self.uptime = None
|
||||||
|
self.ports: List[Port] = []
|
||||||
|
self.vlans: List[VLAN] = []
|
||||||
|
self.fans: List[Fan] = []
|
||||||
|
self.power_supplies: List[PowerSupply] = []
|
||||||
|
self.mac_table: List[MACEntry] = []
|
||||||
|
self.summary = ""
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
"object": "switch",
|
||||||
|
"hostname": self.hostname,
|
||||||
|
"model": self.model,
|
||||||
|
"firmware_version": self.firmware_version,
|
||||||
|
"boot_image": self.boot_image,
|
||||||
|
"uptime": self.uptime,
|
||||||
|
"summary": self.summary,
|
||||||
|
"fans": [f.to_dict() for f in self.fans],
|
||||||
|
"power_supplies": [p.to_dict() for p in self.power_supplies],
|
||||||
|
"ports": [p.to_dict() for p in self.ports],
|
||||||
|
"vlans": [v.to_dict() for v in self.vlans],
|
||||||
|
"mac_table": [m.to_dict() for m in self.mac_table],
|
||||||
|
}
|
||||||
223
models/hpe5130.py
Normal file
223
models/hpe5130.py
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
# models/hpe5130.py
|
||||||
|
from models.base_switch import Switch
|
||||||
|
from models.base_switch import Fan, PowerSupply, MACEntry, Port, VLAN
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
class SwitchHPE5130(Switch):
|
||||||
|
def parse_block(self, section: str, content: str):
|
||||||
|
section = section.strip().lower()
|
||||||
|
if section == "version":
|
||||||
|
self._parse_display_version(content)
|
||||||
|
elif section == "fan":
|
||||||
|
self._parse_display_fan(content)
|
||||||
|
elif section == "power":
|
||||||
|
self._parse_display_power(content)
|
||||||
|
elif section == "mac-address":
|
||||||
|
self._parse_display_mac_address(content)
|
||||||
|
elif section == "interface":
|
||||||
|
self._parse_display_interface(content)
|
||||||
|
elif section == "current-configuration":
|
||||||
|
self._parse_display_current_config(content)
|
||||||
|
|
||||||
|
def _parse_display_version(self, content: str):
|
||||||
|
lines = content.splitlines()
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
if "Comware Software" in line:
|
||||||
|
match = re.search(r"Version ([^,]+, Release [^\s]+)", line)
|
||||||
|
if match:
|
||||||
|
self.firmware_version = match.group(1).strip()
|
||||||
|
elif "uptime is" in line:
|
||||||
|
match = re.search(r"uptime is (.+)$", line)
|
||||||
|
if match:
|
||||||
|
self.uptime = match.group(1).strip()
|
||||||
|
elif "Boot image:" in line:
|
||||||
|
match = re.search(r"(flash:.+\.bin)", line)
|
||||||
|
if match:
|
||||||
|
self.boot_image = match.group(1)
|
||||||
|
elif "Release Version:" in line:
|
||||||
|
self.model = line.replace("Release Version:", "").strip()
|
||||||
|
|
||||||
|
parts = []
|
||||||
|
if self.model:
|
||||||
|
parts.append(self.model)
|
||||||
|
if self.firmware_version:
|
||||||
|
parts.append(self.firmware_version)
|
||||||
|
if self.uptime:
|
||||||
|
parts.append(f"uptime: {self.uptime}")
|
||||||
|
|
||||||
|
self.summary = ", ".join(parts)
|
||||||
|
|
||||||
|
def _parse_display_fan(self, content: str):
|
||||||
|
fan_id = None
|
||||||
|
for line in content.splitlines():
|
||||||
|
match_id = re.search(r"Fan\s+(\d+):", line)
|
||||||
|
match_state = re.search(r"State\s*:\s*(\w+)", line)
|
||||||
|
if match_id:
|
||||||
|
fan_id = int(match_id.group(1))
|
||||||
|
elif match_state and fan_id is not None:
|
||||||
|
state = match_state.group(1)
|
||||||
|
self.fans.append(Fan(fan_id, state))
|
||||||
|
fan_id = None
|
||||||
|
|
||||||
|
def _parse_display_power(self, content: str):
|
||||||
|
lines = content.splitlines()
|
||||||
|
for line in lines:
|
||||||
|
match = re.match(r"\s*(\d+)\s+(\w+)\s+(AC|DC)\s+", line)
|
||||||
|
if match:
|
||||||
|
psu_id = int(match.group(1))
|
||||||
|
state = match.group(2)
|
||||||
|
mode = match.group(3)
|
||||||
|
self.power_supplies.append(PowerSupply(psu_id, state, mode))
|
||||||
|
|
||||||
|
def _parse_display_mac_address(self, content: str):
|
||||||
|
for line in content.splitlines():
|
||||||
|
match = re.match(r"([0-9a-fA-F\-]{14})\s+(\d+)\s+\w+\s+(\S+)", line)
|
||||||
|
if match:
|
||||||
|
mac = match.group(1).replace("-", ":").lower()
|
||||||
|
vlan = int(match.group(2))
|
||||||
|
port = match.group(3)
|
||||||
|
self.mac_table.append(MACEntry(mac, vlan, port, type_="dynamic"))
|
||||||
|
|
||||||
|
def _parse_display_interface(self, content: str):
|
||||||
|
pattern = re.compile(r"^(Bridge-Aggregation\d+|\S*Ethernet\d+/\d+/\d+|InLoopBack\d+|NULL\d+)$", re.MULTILINE)
|
||||||
|
matches = list(pattern.finditer(content))
|
||||||
|
|
||||||
|
for idx, match in enumerate(matches):
|
||||||
|
start = match.start()
|
||||||
|
end = matches[idx + 1].start() if idx + 1 < len(matches) else len(content)
|
||||||
|
block = content[start:end].strip()
|
||||||
|
lines = block.splitlines()
|
||||||
|
if not lines:
|
||||||
|
continue
|
||||||
|
name = lines[0].strip()
|
||||||
|
port = next((p for p in self.ports if p.name == name), None)
|
||||||
|
if not port:
|
||||||
|
port = Port(name)
|
||||||
|
self.ports.append(port)
|
||||||
|
for line in lines:
|
||||||
|
if "Current state:" in line:
|
||||||
|
port.status = line.split(":")[-1].strip().lower()
|
||||||
|
elif "Bandwidth:" in line:
|
||||||
|
match_speed = re.search(r"Bandwidth:\s+(\d+) kbps", line)
|
||||||
|
if match_speed:
|
||||||
|
kbps = int(match_speed.group(1))
|
||||||
|
if kbps >= 1_000_000:
|
||||||
|
port.speed = f"{kbps / 1_000_000:.1f} Gbps"
|
||||||
|
elif kbps >= 1_000:
|
||||||
|
port.speed = f"{kbps / 1_000:.1f} Mbps"
|
||||||
|
else:
|
||||||
|
port.speed = f"{kbps} kbps"
|
||||||
|
elif "PVID:" in line:
|
||||||
|
match = re.search(r"PVID:\s+(\d+)", line)
|
||||||
|
if match:
|
||||||
|
port.vlan = int(match.group(1))
|
||||||
|
|
||||||
|
port.summary = f"Port {port.name} est {port.status.upper()} à {port.speed or 'vitesse inconnue'}, VLAN {port.vlan or '?'}"
|
||||||
|
|
||||||
|
def _apply_interface_config(self, ifname: str, lines: list):
|
||||||
|
ifname = ifname.strip()
|
||||||
|
port = next((p for p in self.ports if p.name == ifname), None)
|
||||||
|
if not port:
|
||||||
|
port = Port(ifname)
|
||||||
|
self.ports.append(port)
|
||||||
|
|
||||||
|
is_trunk = False
|
||||||
|
permit_vlans = []
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
line = line.strip()
|
||||||
|
match line:
|
||||||
|
case l if l.startswith("port access vlan"):
|
||||||
|
if m := re.search(r"port access vlan (\d+)", l):
|
||||||
|
port.vlan = int(m.group(1))
|
||||||
|
port.link_type = "access"
|
||||||
|
|
||||||
|
case l if l.startswith("pvid"):
|
||||||
|
if m := re.search(r"pvid (\d+)", l):
|
||||||
|
port.vlan = int(m.group(1))
|
||||||
|
|
||||||
|
case l if l.startswith("port link-type trunk"):
|
||||||
|
is_trunk = True
|
||||||
|
port.link_type = "trunk"
|
||||||
|
|
||||||
|
case l if l.startswith("undo port trunk permit vlan"):
|
||||||
|
continue # ignorer
|
||||||
|
|
||||||
|
case l if l.startswith("port trunk permit vlan"):
|
||||||
|
vlan_groups = re.findall(r"\d+(?:\s+to\s+\d+)?", l)
|
||||||
|
for item in vlan_groups:
|
||||||
|
parts = item.strip().split()
|
||||||
|
if len(parts) == 3 and parts[1] == "to":
|
||||||
|
start, end = int(parts[0]), int(parts[2])
|
||||||
|
permit_vlans.extend(range(start, end + 1))
|
||||||
|
elif len(parts) == 1:
|
||||||
|
permit_vlans.append(int(parts[0]))
|
||||||
|
|
||||||
|
case l if "stp edged-port" in l:
|
||||||
|
port.portfast = True
|
||||||
|
|
||||||
|
case l if "bpdu" in l:
|
||||||
|
port.bpdu_protection = True
|
||||||
|
|
||||||
|
case l if "loopback-detection enable" in l:
|
||||||
|
port.loopback_detection = True
|
||||||
|
|
||||||
|
if is_trunk and permit_vlans:
|
||||||
|
port.trunk_vlans = sorted(set(permit_vlans))
|
||||||
|
|
||||||
|
if port.link_type == "trunk" and hasattr(port, "trunk_vlans"):
|
||||||
|
vlan_info = f"TRUNK ({len(port.trunk_vlans)} VLANs)"
|
||||||
|
else:
|
||||||
|
vlan_info = f"VLAN {port.vlan or '?'}"
|
||||||
|
|
||||||
|
status = port.status.upper() if port.status else "INCONNU"
|
||||||
|
speed = port.speed or "vitesse inconnue"
|
||||||
|
port.summary = f"Port {port.name} est {status} à {speed}, {vlan_info}"
|
||||||
|
|
||||||
|
def _parse_display_current_config(self, content: str):
|
||||||
|
current_interface = None
|
||||||
|
block_lines = []
|
||||||
|
vlan_id = None
|
||||||
|
for line in content.splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
match line:
|
||||||
|
case l if l.startswith("sysname"):
|
||||||
|
parts = l.split()
|
||||||
|
if len(parts) >= 2:
|
||||||
|
self.hostname = parts[1]
|
||||||
|
|
||||||
|
case l if l.startswith("vlan") and re.match(r"vlan \d+", l):
|
||||||
|
vlan_id = int(l.split()[1])
|
||||||
|
|
||||||
|
case l if l.startswith("name") and vlan_id is not None:
|
||||||
|
name = l.split("name")[-1].strip()
|
||||||
|
self.vlans.append(VLAN(vlan_id, name))
|
||||||
|
vlan_id = None
|
||||||
|
|
||||||
|
case l if l.startswith("irf-port"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
case l if "port group interface" in l:
|
||||||
|
match = re.search(r"interface (\S+)", l)
|
||||||
|
if match:
|
||||||
|
portname = match.group(1)
|
||||||
|
port = next((p for p in self.ports if p.name == portname), None)
|
||||||
|
if not port:
|
||||||
|
port = Port(portname)
|
||||||
|
self.ports.append(port)
|
||||||
|
port.link_type = "irf"
|
||||||
|
port.summary = f"Port {port.name} est utilisé pour un lien IRF"
|
||||||
|
|
||||||
|
case l if l.startswith("interface"):
|
||||||
|
if current_interface and block_lines:
|
||||||
|
self._apply_interface_config(current_interface, block_lines)
|
||||||
|
current_interface = l.split()[1]
|
||||||
|
block_lines = []
|
||||||
|
|
||||||
|
case _ if current_interface:
|
||||||
|
block_lines.append(line)
|
||||||
|
|
||||||
|
if current_interface and block_lines:
|
||||||
|
self._apply_interface_config(current_interface, block_lines)
|
||||||
0
output/export_json.py
Normal file
0
output/export_json.py
Normal file
0
parsers/dispatcher.py
Normal file
0
parsers/dispatcher.py
Normal file
0
parsers/display_current_config.py
Normal file
0
parsers/display_current_config.py
Normal file
34
utils/texttools.py
Normal file
34
utils/texttools.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# utils/texttools.py
|
||||||
|
import re
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
def split_display_sections(text: str) -> List[dict]:
|
||||||
|
pattern = re.compile(r"^=+\s*display ([a-zA-Z0-9\-\_ ]+)\s*=+$")
|
||||||
|
sections = []
|
||||||
|
current_section = None
|
||||||
|
current_content = []
|
||||||
|
|
||||||
|
for line in text.splitlines():
|
||||||
|
match = pattern.match(line.strip())
|
||||||
|
if match:
|
||||||
|
if current_section:
|
||||||
|
sections.append({
|
||||||
|
"object": "raw_section",
|
||||||
|
"section": current_section.strip(),
|
||||||
|
"summary": f"Bloc issu de la commande display {current_section.strip()}",
|
||||||
|
"content": "\n".join(current_content).strip()
|
||||||
|
})
|
||||||
|
current_section = match.group(1)
|
||||||
|
current_content = []
|
||||||
|
elif current_section:
|
||||||
|
current_content.append(line)
|
||||||
|
|
||||||
|
if current_section:
|
||||||
|
sections.append({
|
||||||
|
"object": "raw_section",
|
||||||
|
"section": current_section.strip(),
|
||||||
|
"summary": f"Bloc issu de la commande display {current_section.strip()}",
|
||||||
|
"content": "\n".join(current_content).strip()
|
||||||
|
})
|
||||||
|
|
||||||
|
return sections
|
||||||
Reference in New Issue
Block a user