simhall 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- simhall/__init__.py +32 -0
- simhall/__main__.py +7 -0
- simhall/actions.py +103 -0
- simhall/cli.py +291 -0
- simhall/emulators/__init__.py +3 -0
- simhall/emulators/dcgm.py +99 -0
- simhall/emulators/lldp.py +188 -0
- simhall/emulators/netbox.py +709 -0
- simhall/emulators/redfish.py +395 -0
- simhall/faults.py +169 -0
- simhall/golden.py +92 -0
- simhall/hall.py +1066 -0
- simhall/models.py +269 -0
- simhall/naming.py +93 -0
- simhall/py.typed +0 -0
- simhall/seed_netbox.py +343 -0
- simhall/server.py +427 -0
- simhall/specfile.py +80 -0
- simhall/technician.py +567 -0
- simhall/views.py +123 -0
- simhall-0.1.0.dist-info/METADATA +133 -0
- simhall-0.1.0.dist-info/RECORD +26 -0
- simhall-0.1.0.dist-info/WHEEL +5 -0
- simhall-0.1.0.dist-info/entry_points.txt +2 -0
- simhall-0.1.0.dist-info/licenses/LICENSE +201 -0
- simhall-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Copyright 2026 Hasanat Kazmi
|
|
3
|
+
"""Switch-side LLDP and port state in the two public JSON transports of CONTRACT.md section 3.2.
|
|
4
|
+
|
|
5
|
+
Arista eAPI (`runCmds` JSON-RPC: `show lldp neighbors detail`, `show interfaces status`) and NVIDIA
|
|
6
|
+
NVUE REST (`GET /nvue_v1/interface?rev=applied`). Both render from the same `LeafPortView` list.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
12
|
+
|
|
13
|
+
from simhall import naming
|
|
14
|
+
from simhall.models import LeafIntent
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from simhall.hall import LeafPortView
|
|
18
|
+
|
|
19
|
+
SYSTEM_DESCRIPTION = "Linux 6.8.0 x86_64, lldpd 1.0.18"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def render_eapi(lf: LeafIntent, views: list[LeafPortView]) -> dict[str, Any]:
|
|
23
|
+
"""`{"lldpNeighbors": result[0] body, "interfaceStatuses": result[1] body}` for an Arista leaf."""
|
|
24
|
+
neighbors: dict[str, Any] = {}
|
|
25
|
+
statuses: dict[str, Any] = {}
|
|
26
|
+
for v in views:
|
|
27
|
+
statuses[v.port] = {
|
|
28
|
+
"linkStatus": "connected" if v.link_up else "notconnect",
|
|
29
|
+
"bandwidth": v.speed_gbps * 10**9 if (v.link_up and v.speed_gbps) else 0,
|
|
30
|
+
"description": "",
|
|
31
|
+
"interfaceType": "400GBASE-DR4" if v.label else "Not Present",
|
|
32
|
+
"autoNegotiateActive": False,
|
|
33
|
+
"duplex": "duplexFull",
|
|
34
|
+
"lineProtocolStatus": "up" if v.link_up else "notPresent",
|
|
35
|
+
"vlanInformation": {"interfaceMode": "routed", "interfaceForwardingModel": "routed"},
|
|
36
|
+
}
|
|
37
|
+
if v.neighbor_system is not None:
|
|
38
|
+
neighbors[v.port] = {
|
|
39
|
+
"lldpNeighborInfo": [
|
|
40
|
+
{
|
|
41
|
+
"chassisIdType": "macAddress",
|
|
42
|
+
"chassisId": v.neighbor_chassis,
|
|
43
|
+
"systemName": v.neighbor_system,
|
|
44
|
+
"systemDescription": SYSTEM_DESCRIPTION,
|
|
45
|
+
"neighborInterfaceInfo": {
|
|
46
|
+
"interfaceIdType": "macAddress" if v.neighbor_port_is_mac else "interfaceName",
|
|
47
|
+
"interfaceId": v.neighbor_port_id,
|
|
48
|
+
"interfaceDescription": v.neighbor_iface or "",
|
|
49
|
+
},
|
|
50
|
+
"ttl": 120,
|
|
51
|
+
"lastChangeTime": 1760630400.0,
|
|
52
|
+
"lastContactTime": 1760630430.0,
|
|
53
|
+
"systemCapabilities": {"bridge": False, "router": False, "stationOnly": True},
|
|
54
|
+
"managementAddresses": [],
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
return {"lldpNeighbors": neighbors, "interfaceStatuses": statuses}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def eapi_response(body: Any, lf: LeafIntent, table: dict[str, Any]) -> dict[str, Any]:
|
|
62
|
+
"""Answer one eAPI JSON-RPC request from the rendered table; unknown commands get an eAPI-style error."""
|
|
63
|
+
if not isinstance(body, dict):
|
|
64
|
+
return {"jsonrpc": "2.0", "id": None, "error": {"code": -32700, "message": "Parse error"}}
|
|
65
|
+
req_id = body.get("id")
|
|
66
|
+
if body.get("method") != "runCmds":
|
|
67
|
+
return {"jsonrpc": "2.0", "id": req_id, "error": {"code": -32601, "message": "Method not found"}}
|
|
68
|
+
params = body.get("params") or {}
|
|
69
|
+
cmds = params.get("cmds") or []
|
|
70
|
+
results: list[dict[str, Any]] = []
|
|
71
|
+
neighbor_count = len(table["lldpNeighbors"])
|
|
72
|
+
for i, raw in enumerate(cmds):
|
|
73
|
+
cmd = raw.get("cmd", "") if isinstance(raw, dict) else str(raw)
|
|
74
|
+
norm = " ".join(cmd.split()).lower()
|
|
75
|
+
if norm == "show lldp neighbors detail":
|
|
76
|
+
results.append(
|
|
77
|
+
{
|
|
78
|
+
"tablesLastChangeTime": 1760630400.0,
|
|
79
|
+
"tablesAgeOuts": 0,
|
|
80
|
+
"tablesInserts": neighbor_count,
|
|
81
|
+
"lldpNeighbors": table["lldpNeighbors"],
|
|
82
|
+
"tablesDeletes": 0,
|
|
83
|
+
"tablesDrops": 0,
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
elif norm == "show interfaces status":
|
|
87
|
+
results.append({"interfaceStatuses": table["interfaceStatuses"]})
|
|
88
|
+
elif norm == "show lldp neighbors":
|
|
89
|
+
brief = [
|
|
90
|
+
{
|
|
91
|
+
"port": port,
|
|
92
|
+
"neighborDevice": info["lldpNeighborInfo"][0]["systemName"],
|
|
93
|
+
"neighborPort": info["lldpNeighborInfo"][0]["neighborInterfaceInfo"]["interfaceId"],
|
|
94
|
+
"ttl": 120,
|
|
95
|
+
}
|
|
96
|
+
for port, info in table["lldpNeighbors"].items()
|
|
97
|
+
]
|
|
98
|
+
results.append(
|
|
99
|
+
{
|
|
100
|
+
"tablesLastChangeTime": 1760630400.0,
|
|
101
|
+
"tablesAgeOuts": 0,
|
|
102
|
+
"tablesInserts": neighbor_count,
|
|
103
|
+
"lldpNeighbors": brief,
|
|
104
|
+
"tablesDeletes": 0,
|
|
105
|
+
"tablesDrops": 0,
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
elif norm == "show version":
|
|
109
|
+
results.append(
|
|
110
|
+
{
|
|
111
|
+
"modelName": f"DCS-{lf.model}",
|
|
112
|
+
"internalVersion": "4.33.2F",
|
|
113
|
+
"version": "4.33.2F",
|
|
114
|
+
"serialNumber": lf.serial,
|
|
115
|
+
"systemMacAddress": lf.chassis_mac,
|
|
116
|
+
"architecture": "x86_64",
|
|
117
|
+
"hardwareRevision": "11.00",
|
|
118
|
+
"mfgName": "Arista",
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
elif norm == "show hostname":
|
|
122
|
+
results.append({"hostname": lf.name, "fqdn": lf.name})
|
|
123
|
+
elif norm in ("enable", "terminal length 0"):
|
|
124
|
+
results.append({})
|
|
125
|
+
else:
|
|
126
|
+
return {
|
|
127
|
+
"jsonrpc": "2.0",
|
|
128
|
+
"id": req_id,
|
|
129
|
+
"error": {
|
|
130
|
+
"code": 1002,
|
|
131
|
+
"message": f"CLI command {i + 1} of {len(cmds)} '{cmd}' failed: invalid command",
|
|
132
|
+
"data": [*results, {"errors": ["Invalid input"]}],
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
return {"jsonrpc": "2.0", "id": req_id, "result": results}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def render_nvue(lf: LeafIntent, views: list[LeafPortView], nominal_gbps: int = 400) -> dict[str, Any]:
|
|
139
|
+
"""NVUE `GET /nvue_v1/interface?rev=applied` body for an NVIDIA leaf: `{"swp21": {...}, ...}`."""
|
|
140
|
+
out: dict[str, Any] = {}
|
|
141
|
+
for v in views:
|
|
142
|
+
speed = v.speed_gbps if (v.link_up and v.speed_gbps) else nominal_gbps
|
|
143
|
+
entry: dict[str, Any] = {
|
|
144
|
+
"type": "swp",
|
|
145
|
+
"link": {
|
|
146
|
+
"state": {"up": {}} if v.link_up else {"down": {}},
|
|
147
|
+
"speed": f"{speed}G",
|
|
148
|
+
"mtu": 9216,
|
|
149
|
+
"auto-negotiate": "off",
|
|
150
|
+
"duplex": "full",
|
|
151
|
+
"fec": "rs",
|
|
152
|
+
"mac": naming.leaf_port_mac(lf.number, v.number),
|
|
153
|
+
},
|
|
154
|
+
"ifindex": v.number + 2,
|
|
155
|
+
}
|
|
156
|
+
if v.neighbor_system is not None:
|
|
157
|
+
entry["lldp"] = {
|
|
158
|
+
"neighbor": {
|
|
159
|
+
v.neighbor_system: {
|
|
160
|
+
"chassis": {
|
|
161
|
+
"chassis-id": v.neighbor_chassis,
|
|
162
|
+
"chassis-id-type": "mac",
|
|
163
|
+
"system-name": v.neighbor_system,
|
|
164
|
+
"system-description": SYSTEM_DESCRIPTION,
|
|
165
|
+
},
|
|
166
|
+
"port": {
|
|
167
|
+
"id": v.neighbor_port_id,
|
|
168
|
+
"id-type": "mac" if v.neighbor_port_is_mac else "if_name",
|
|
169
|
+
"description": v.neighbor_iface or "",
|
|
170
|
+
"ttl": 120,
|
|
171
|
+
},
|
|
172
|
+
"age": "0:00:30",
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
out[v.port] = entry
|
|
177
|
+
return out
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def nvue_system(lf: LeafIntent) -> dict[str, Any]:
|
|
181
|
+
"""`GET /nvue_v1/system` body."""
|
|
182
|
+
return {
|
|
183
|
+
"hostname": lf.name,
|
|
184
|
+
"build": "Cumulus Linux 5.12.0",
|
|
185
|
+
"platform": {"manufacturer": lf.manufacturer, "model": lf.model, "serial-number": lf.serial},
|
|
186
|
+
"uptime": "2 days, 4:11:09",
|
|
187
|
+
"timezone": "Etc/UTC",
|
|
188
|
+
}
|