blob: fe039e6042b6200efd6af5c949175f74a084a9da [file] [log] [blame]
awlane1cec2332025-04-24 17:24:47 -05001from mininet.net import Mininet
2from mininet.log import info, error
3from mininet.link import Link
4
5class TopoExecutor:
6 def __init__(self, net: Mininet):
7 self.net = net
8
9 async def get_topo(self):
10 """UI Function: Get topology"""
11 nodes = []
12 node_ids = set()
13 links = []
14
15 for host in self.net.hosts:
16 nodes.append(self._node_dict(host))
17 node_ids.add(host.name)
18
19 for switch in self.net.switches:
20 nodes.append(self._node_dict(switch, switch=True))
21
22 for station in getattr(self.net, "stations", []):
23 if station.name not in node_ids:
24 nodes.append(self._node_dict(station))
25
26 for link in self.net.links:
27 if obj := self._link_dict(link):
28 links.append(obj)
29
30 return {
31 "nodes": nodes,
32 "links": links,
33 }
34
35 async def add_link(self, a, b, link_id, opts):
36 """UI Function: Add link"""
37 link = self.net.addLink(self.net[a], self.net[b], **self._conv_link_opts(opts))
38 info(f"Added link {link}\n")
39 return {
40 "id": link_id,
41 "mnId": str(link),
42 **opts,
43 }
44
45 async def del_link(self, a, b, mn_id):
46 """UI Function: Delete link"""
47 link = self._get_link(a, b, mn_id)
48 if link:
49 self.net.delLink(link)
50 return True
51
52 error(f"No link found to remove for {mn_id}\n")
53 return False
54
55 async def upd_link(self, a, b, mn_id, opts):
56 """UI Function: Update link"""
57 link = self._get_link(a, b, mn_id)
58 if link:
59 params = self._conv_link_opts(opts)
60 link.intf1.config(**params)
61 link.intf2.config(**params)
62 for p in params:
63 link.intf1.params[p] = params[p]
64 link.intf2.params[p] = params[p]
65 return True
66
67 info(f"No link to configure for {mn_id}\n")
68 return False
69
70 async def add_node(self, node_id, label):
71 """UI Function: Add node (host is added)"""
72 self.net.addHost(label)
73 return {
74 "id": node_id,
75 "label": label,
76 }
77
78 async def del_node(self, node_id):
79 """UI Function: Delete node"""
80 self.net.delNode(self.net[node_id])
81 info(f"Removed node {node_id}\n")
82 return True
83
84 async def set_node_pos(self, node_id, x, y):
85 """UI Function: Set node position"""
86 node = self.net[node_id]
87 if not node or not hasattr(node, "position"):
88 return False
89 x, y = x / 10, y / 10
90 z = node.position[2] if len(node.position) > 2 else 0
91 node.setPosition(f"{int(x)},{int(y)},{int(z)}")
92 info(f"Set position of {node_id} to {x},{y}\n")
93 return True
94
95 def _node_dict(self, node, switch=False):
96 val = {
97 "id": node.name,
98 "label": node.name,
99 }
100
101 if switch:
102 val["isSwitch"] = True
103
104 if hasattr(node, "position"):
105 # Mininet positions are in m, NDN-Play are much smaller
106 val["x"] = node.position[0] * 10
107 val["y"] = node.position[1] * 10
108
109 if hasattr(node, "params") and "params" in node.params:
110 p = node.params["params"]
111 if "color" in p:
112 val["color"] = p["color"]
113
114 return val
115
116 def _link_dict(self, link):
117 if isinstance(link.intf2, str):
118 if link.intf2 == "wifiAdhoc":
119 # TODO: visualize adhoc links
120 pass
121 return None
122
123 obj = {
124 "mnId": str(link),
125 "from": link.intf1.node.name,
126 "to": link.intf2.node.name,
127 }
128
129 if "delay" in link.intf1.params:
130 d1 = int(link.intf1.params["delay"][:-len("ms")])
131 d2 = int(link.intf2.params["delay"][:-len("ms")])
132 obj["latency"] = (d1 + d2) / 2
133
134 if "loss" in link.intf1.params:
135 l1 = link.intf1.params["loss"]
136 l2 = link.intf2.params["loss"]
137 obj["loss"] = (l1 + l2) / 2
138
139 return obj
140
141 def _get_link(self, a, b, mn_id) -> Link | None:
142 """Helper: get link between two nodes by name"""
143 for link in self.net.linksBetween(self.net[a], self.net[b]):
144 if str(link) == mn_id:
145 return link
146
147 return None
148
149 def _conv_link_opts(self, opts: dict):
150 """Helper: convert link options"""
151 params = {}
152 if "latency" in opts and opts["latency"] is not None and int(opts["latency"]) >= 0:
153 params["delay"] = str(int(opts["latency"])) + "ms"
154 if "loss" in opts and opts["loss"] is not None and float(opts["loss"]) >= 0:
155 params["loss"] = float(opts["loss"])
156 return params