Skip to content

Commit 06d2a22

Browse files
authored
Network support (#76)
* ignore * extended link support and network support * update pkgs
1 parent d487336 commit 06d2a22

File tree

11 files changed

+810
-627
lines changed

11 files changed

+810
-627
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ lab-examples/*
66
!lab-examples/*.yml
77
!lab-examples/*.yaml
88
lab-examples/*flow_panel.yaml
9-
9+
patch.sh
1010
# Created by https://www.toptal.com/developers/gitignore/api/python
1111
# Edit at https://www.toptal.com/developers/gitignore?templates=python
1212

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
name: special-endpoints-test
2+
3+
topology:
4+
kinds:
5+
nokia_srlinux:
6+
type: ixrd1
7+
image: ghcr.io/nokia/srlinux:latest
8+
nodes:
9+
srl1:
10+
kind: nokia_srlinux
11+
srl2:
12+
kind: nokia_srlinux
13+
br0:
14+
kind: bridge
15+
br1:
16+
kind: ovs-bridge
17+
18+
links:
19+
# Regular link between nodes but extended
20+
- endpoints:
21+
- node: srl1
22+
interface: e1-1
23+
mac: 02:42:ac:11:00:02
24+
- node: srl2
25+
interface: e1-1
26+
mac: 02:42:ac:11:00:03
27+
type: veth
28+
mtu: 1400
29+
vars:
30+
test: test
31+
labels:
32+
test: test
33+
34+
# Management network link
35+
- endpoints: [ srl2:e1-2, mgmt-net:srl2-e1-2 ]
36+
- endpoints: [ srl2:e1-3, macvlan:enp0s3 ]
37+
- endpoints: [ srl1:e1-3, srl2:e1-4 ]
38+
- endpoints: [ srl1:e1-4, macvlan:enp0s3 ]
39+
- type: host
40+
endpoint:
41+
node: srl1
42+
interface: e1-5
43+
mac: 02:42:ac:11:00:04
44+
host-interface: eth1
45+
mtu: 1400
46+
vars:
47+
test: test
48+
labels:
49+
test: test
50+
- type: vxlan
51+
endpoint:
52+
node: srl1
53+
interface: e1-6
54+
remote: 10.0.0.1
55+
vni: 100
56+
udp-port: 4789
57+
mtu: 1500
58+
- type: vxlan-stitch
59+
endpoint:
60+
node: srl1
61+
interface: e1-7
62+
remote: 10.0.0.2
63+
vni: 100
64+
udp-port: 4786
65+
mtu: 1500
66+
- type: dummy
67+
endpoint:
68+
node: srl1
69+
interface: e1-2
70+
- endpoints: [ srl1:e1-8, br0:eth1 ]
71+
- endpoints: [ srl1:e1-9, br1:eth1 ]

pyproject.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
[project]
22
name = "clab-io-draw"
3-
version = "0.6.3"
3+
version = "0.7.0"
44
description = "Convert between draw.io and containerlab topology files"
55
readme = "README.md"
66
requires-python = ">=3.11"
77
dependencies = [
88
"n2g==0.3.3",
9-
"prompt-toolkit==3.0.51",
9+
"prompt-toolkit==3.0.52",
1010
"PyYAML==6.0.2",
1111
"six==1.17.0",
1212
"wcwidth==0.2.13",
1313
"ruamel-yaml==0.18.15",
1414
"textual-dev==1.7.0",
15-
"textual==5.3.0",
15+
"textual==6.1.0",
1616
"networkx>=3.5.0",
1717
"defusedxml>=0.7.1",
18-
"typer>=0.16.1",
19-
"pytest>=8.4.1",
18+
"typer>=0.17.4",
19+
"pytest>=8.4.2",
2020
]
2121

2222
[project.scripts]

src/clab_io_draw/clab2drawio.py

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -172,58 +172,48 @@ def main(
172172
f"User chose theme '{chosen_theme}' but no file found. Keeping old theme."
173173
)
174174

175-
# Check if any nodes have predefined positions from the YAML
176-
has_predefined_positions = any(
177-
node.pos_x is not None
175+
# Determine which nodes have predefined positions
176+
predefined = {
177+
name: node
178+
for name, node in nodes.items()
179+
if node.pos_x is not None
178180
and node.pos_y is not None
179181
and str(node.pos_x).strip() != ""
180182
and str(node.pos_y).strip() != ""
181-
for node in nodes.values()
182-
)
183+
}
184+
185+
has_any_fixed = bool(predefined)
186+
all_fixed = len(predefined) == len(nodes)
183187

184-
if has_predefined_positions:
188+
fixed_positions = {}
189+
if has_any_fixed:
185190
logger.debug("Using predefined positions from YAML file with scaling...")
186191

187-
# Scale factor to ensure adequate spacing between nodes
188192
x_scale = (styles.get("padding_x", 150) / 100) * 1.5
189193
y_scale = (styles.get("padding_y", 150) / 100) * 1.5
190194

191-
# Convert string positions to float and apply scaling
192-
for node in nodes.values():
195+
for name, node in predefined.items():
193196
try:
194-
if (
195-
node.pos_x is not None
196-
and node.pos_y is not None
197-
and str(node.pos_x).strip() != ""
198-
and str(node.pos_y).strip() != ""
199-
):
200-
node.pos_x = int(node.pos_x) * x_scale
201-
node.pos_y = int(node.pos_y) * y_scale
197+
node.pos_x = int(node.pos_x) * x_scale
198+
node.pos_y = int(node.pos_y) * y_scale
199+
fixed_positions[name] = (node.pos_x, node.pos_y)
202200
except (ValueError, TypeError):
203201
logger.debug(
204202
f"Could not convert position for node {node.name}, will use layout position"
205203
)
206204
node.pos_x = None
207205
node.pos_y = None
208206

209-
# When fixed positions are available, we still assign graph levels for connectivity purposes
210-
# but instruct it to skip warnings and not override positions
211-
logger.debug(
212-
"Using predefined positions - graph levels will only be used for connectivity"
213-
)
214207
graph_manager = GraphLevelManager()
215208
graph_manager.assign_graphlevels(
216-
diagram, verbose=False, skip_warnings=True, respect_fixed_positions=True
209+
diagram, verbose=False, skip_warnings=True, respect_fixed_positions=False
217210
)
218211
else:
219-
# No fixed positions, proceed with normal graph level assignment
220212
logger.debug("No predefined positions found - assigning graph levels normally")
221213
graph_manager = GraphLevelManager()
222214
graph_manager.assign_graphlevels(diagram, verbose=False)
223215

224-
# Only apply layout manager if we don't have predefined positions
225-
if not has_predefined_positions:
226-
# Choose layout based on layout argument
216+
if not all_fixed:
227217
if layout == "vertical":
228218
layout_manager = VerticalLayout()
229219
else:
@@ -232,24 +222,34 @@ def main(
232222
logger.debug(f"Applying {layout} layout...")
233223
layout_manager.apply(diagram, verbose=log_level == LogLevel.DEBUG)
234224

225+
# Restore positions for nodes that had predefined coordinates
226+
for name, (x, y) in fixed_positions.items():
227+
node = nodes.get(name)
228+
if node:
229+
node.pos_x = x
230+
node.pos_y = y
231+
235232
# Calculate the diagram size based on the positions of the nodes
236-
min_x = min(node.pos_x for node in nodes.values())
237-
min_y = min(node.pos_y for node in nodes.values())
238-
max_x = max(node.pos_x for node in nodes.values())
239-
max_y = max(node.pos_y for node in nodes.values())
233+
positioned_nodes = [
234+
n for n in nodes.values() if n.pos_x is not None and n.pos_y is not None
235+
]
236+
min_x = min(node.pos_x for node in positioned_nodes)
237+
min_y = min(node.pos_y for node in positioned_nodes)
238+
max_x = max(node.pos_x for node in positioned_nodes)
239+
max_y = max(node.pos_y for node in positioned_nodes)
240240

241241
# Determine the necessary adjustments
242242
adjust_x = -min_x + 100 # Adjust so the minimum x is at least 100
243243
adjust_y = -min_y + 100 # Adjust so the minimum y is at least 100
244244

245245
# Apply adjustments to each node's position
246-
for node in nodes.values():
246+
for node in positioned_nodes:
247247
node.pos_x += adjust_x
248248
node.pos_y += adjust_y
249249

250250
# Recalculate diagram size if necessary, after adjustment
251-
max_x = max(node.pos_x for node in nodes.values())
252-
max_y = max(node.pos_y for node in nodes.values())
251+
max_x = max(node.pos_x for node in positioned_nodes)
252+
max_y = max(node.pos_y for node in positioned_nodes)
253253

254254
max_size_x = max_x + 100 # Adding a margin to the right side
255255
max_size_y = max_y + 100 # Adding a margin to the bottom

0 commit comments

Comments
 (0)