]> git.proxmox.com Git - mirror_frr.git/blob - tests/topotests/lib/micronet_compat.py
*: auto-convert to SPDX License IDs
[mirror_frr.git] / tests / topotests / lib / micronet_compat.py
1 # -*- coding: utf-8 eval: (blacken-mode 1) -*-
2 # SPDX-License-Identifier: GPL-2.0-or-later
3 #
4 # July 11 2021, Christian Hopps <chopps@labn.net>
5 #
6 # Copyright (c) 2021, LabN Consulting, L.L.C
7 #
8
9 import glob
10 import logging
11 import os
12 import signal
13 import time
14
15 from lib.micronet import LinuxNamespace, Micronet
16 from lib.micronet_cli import cli
17
18
19 def get_pids_with_env(has_var, has_val=None):
20 result = {}
21 for pidenv in glob.iglob("/proc/*/environ"):
22 pid = pidenv.split("/")[2]
23 try:
24 with open(pidenv, "rb") as rfb:
25 envlist = [
26 x.decode("utf-8").split("=", 1) for x in rfb.read().split(b"\0")
27 ]
28 envlist = [[x[0], ""] if len(x) == 1 else x for x in envlist]
29 envdict = dict(envlist)
30 if has_var not in envdict:
31 continue
32 if has_val is None:
33 result[pid] = envdict
34 elif envdict[has_var] == str(has_val):
35 result[pid] = envdict
36 except Exception:
37 # E.g., process exited and files are gone
38 pass
39 return result
40
41
42 def _kill_piddict(pids_by_upid, sig):
43 for upid, pids in pids_by_upid:
44 logging.info(
45 "Sending %s to (%s) of micronet pid %s", sig, ", ".join(pids), upid
46 )
47 for pid in pids:
48 try:
49 os.kill(int(pid), sig)
50 except Exception:
51 pass
52
53
54 def _get_our_pids():
55 ourpid = str(os.getpid())
56 piddict = get_pids_with_env("MICRONET_PID", ourpid)
57 pids = [x for x in piddict if x != ourpid]
58 if pids:
59 return {ourpid: pids}
60 return {}
61
62
63 def _get_other_pids():
64 piddict = get_pids_with_env("MICRONET_PID")
65 unet_pids = {d["MICRONET_PID"] for d in piddict.values()}
66 pids_by_upid = {p: set() for p in unet_pids}
67 for pid, envdict in piddict.items():
68 pids_by_upid[envdict["MICRONET_PID"]].add(pid)
69 # Filter out any child pid sets whos micronet pid is still running
70 return {x: y for x, y in pids_by_upid.items() if x not in y}
71
72
73 def _get_pids_by_upid(ours):
74 if ours:
75 return _get_our_pids()
76 return _get_other_pids()
77
78
79 def _cleanup_pids(ours):
80 pids_by_upid = _get_pids_by_upid(ours).items()
81 if not pids_by_upid:
82 return
83
84 _kill_piddict(pids_by_upid, signal.SIGTERM)
85
86 # Give them 5 second to exit cleanly
87 logging.info("Waiting up to 5s to allow for clean exit of abandon'd pids")
88 for _ in range(0, 5):
89 pids_by_upid = _get_pids_by_upid(ours).items()
90 if not pids_by_upid:
91 return
92 time.sleep(1)
93
94 pids_by_upid = _get_pids_by_upid(ours).items()
95 _kill_piddict(pids_by_upid, signal.SIGKILL)
96
97
98 def cleanup_current():
99 """Attempt to cleanup preview runs.
100
101 Currently this only scans for old processes.
102 """
103 logging.info("reaping current micronet processes")
104 _cleanup_pids(True)
105
106
107 def cleanup_previous():
108 """Attempt to cleanup preview runs.
109
110 Currently this only scans for old processes.
111 """
112 logging.info("reaping past micronet processes")
113 _cleanup_pids(False)
114
115
116 class Node(LinuxNamespace):
117 """Node (mininet compat)."""
118
119 def __init__(self, name, **kwargs):
120 """
121 Create a Node.
122 """
123 self.params = kwargs
124
125 if "private_mounts" in kwargs:
126 private_mounts = kwargs["private_mounts"]
127 else:
128 private_mounts = kwargs.get("privateDirs", [])
129
130 logger = kwargs.get("logger")
131
132 super(Node, self).__init__(name, logger=logger, private_mounts=private_mounts)
133
134 def cmd(self, cmd, **kwargs):
135 """Execute a command, joins stdout, stderr, ignores exit status."""
136
137 return super(Node, self).cmd_legacy(cmd, **kwargs)
138
139 def config(self, lo="up", **params):
140 """Called by Micronet when topology is built (but not started)."""
141 # mininet brings up loopback here.
142 del params
143 del lo
144
145 def intfNames(self):
146 return self.intfs
147
148 def terminate(self):
149 return
150
151
152 class Topo(object): # pylint: disable=R0205
153 def __init__(self, *args, **kwargs):
154 raise Exception("Remove Me")
155
156
157 class Mininet(Micronet):
158 """
159 Mininet using Micronet.
160 """
161
162 g_mnet_inst = None
163
164 def __init__(self, controller=None):
165 """
166 Create a Micronet.
167 """
168 assert not controller
169
170 if Mininet.g_mnet_inst is not None:
171 Mininet.g_mnet_inst.stop()
172 Mininet.g_mnet_inst = self
173
174 self.configured_hosts = set()
175 self.host_params = {}
176 self.prefix_len = 8
177
178 # SNMPd used to require this, which was set int he mininet shell
179 # that all commands executed from. This is goofy default so let's not
180 # do it if we don't have to. The snmpd.conf files have been updated
181 # to set permissions to root:frr 770 to make this unneeded in that case
182 # os.umask(0)
183
184 super(Mininet, self).__init__()
185
186 self.logger.debug("%s: Creating", self)
187
188 def __str__(self):
189 return "Mininet()"
190
191 def configure_hosts(self):
192 """
193 Configure hosts once the topology has been built.
194
195 This function can be called multiple times if routers are added to the topology
196 later.
197 """
198 if not self.hosts:
199 return
200
201 self.logger.debug("Configuring hosts: %s", self.hosts.keys())
202
203 for name in sorted(self.hosts.keys()):
204 if name in self.configured_hosts:
205 continue
206
207 host = self.hosts[name]
208 first_intf = host.intfs[0] if host.intfs else None
209 params = self.host_params[name]
210
211 if first_intf and "ip" in params:
212 ip = params["ip"]
213 i = ip.find("/")
214 if i == -1:
215 plen = self.prefix_len
216 else:
217 plen = int(ip[i + 1 :])
218 ip = ip[:i]
219
220 host.cmd_raises("ip addr add {}/{} dev {}".format(ip, plen, first_intf))
221
222 if "defaultRoute" in params:
223 host.cmd_raises(
224 "ip route add default {}".format(params["defaultRoute"])
225 )
226
227 host.config()
228
229 self.configured_hosts.add(name)
230
231 def add_host(self, name, cls=Node, **kwargs):
232 """Add a host to micronet."""
233
234 self.host_params[name] = kwargs
235 super(Mininet, self).add_host(name, cls=cls, **kwargs)
236
237 def start(self):
238 """Start the micronet topology."""
239 self.logger.debug("%s: Starting (no-op).", self)
240
241 def stop(self):
242 """Stop the mininet topology (deletes)."""
243 self.logger.debug("%s: Stopping (deleting).", self)
244
245 self.delete()
246
247 self.logger.debug("%s: Stopped (deleted).", self)
248
249 if Mininet.g_mnet_inst == self:
250 Mininet.g_mnet_inst = None
251
252 def cli(self):
253 cli(self)