]>
git.proxmox.com Git - mirror_frr.git/blob - tests/topotests/lib/micronet.py
1 # -*- coding: utf-8 eval: (blacken-mode 1) -*-
3 # July 9 2021, Christian Hopps <chopps@labn.net>
5 # Copyright (c) 2021, LabN Consulting, L.L.C.
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License
9 # as published by the Free Software Foundation; either version 2
10 # of the License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License along
18 # with this program; see the file COPYING; if not, write to the Free Software
19 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
29 import time
as time_mod
32 root_hostname
= subprocess
.check_output("hostname")
34 # This allows us to cleanup any leftovers later on
35 os
.environ
["MICRONET_PID"] = str(os
.getpid())
38 class Timeout(object):
39 def __init__(self
, delta
):
40 self
.started_on
= datetime
.datetime
.now()
41 self
.expires_on
= self
.started_on
+ datetime
.timedelta(seconds
=delta
)
44 elapsed
= datetime
.datetime
.now() - self
.started_on
45 return elapsed
.total_seconds()
48 return datetime
.datetime
.now() > self
.expires_on
52 """Return True if value is a string."""
54 return isinstance(value
, basestring
) # type: ignore
56 return isinstance(value
, str)
59 def shell_quote(command
):
60 """Return command wrapped in single quotes."""
61 if sys
.version_info
[0] >= 3:
62 return shlex
.quote(command
)
63 return "'{}'".format(command
.replace("'", "'\"'\"'")) # type: ignore
66 def cmd_error(rc
, o
, e
):
67 s
= "rc {}".format(rc
)
68 o
= "\n\tstdout: " + o
.strip() if o
and o
.strip() else ""
69 e
= "\n\tstderr: " + e
.strip() if e
and e
.strip() else ""
73 def proc_error(p
, o
, e
):
74 args
= p
.args
if is_string(p
.args
) else " ".join(p
.args
)
75 s
= "rc {} pid {}\n\targs: {}".format(p
.returncode
, p
.pid
, args
)
76 o
= "\n\tstdout: " + o
.strip() if o
and o
.strip() else ""
77 e
= "\n\tstderr: " + e
.strip() if e
and e
.strip() else ""
84 if not hasattr(p
, "saved_output"):
85 p
.saved_output
= p
.communicate()
86 return proc_error(p
, *p
.saved_output
)
89 class Commander(object): # pylint: disable=R0205
93 An object that can execute commands.
98 def __init__(self
, name
, logger
=None):
99 """Create a Commander."""
104 self
.pre_cmd_str
= ""
107 self
.logger
= logging
.getLogger(__name__
+ ".commander." + name
)
111 self
.cwd
= self
.cmd_raises("pwd").strip()
113 def set_logger(self
, logfile
):
114 self
.logger
= logging
.getLogger(__name__
+ ".commander." + self
.name
)
115 if is_string(logfile
):
116 handler
= logging
.FileHandler(logfile
, mode
="w")
118 handler
= logging
.StreamHandler(logfile
)
120 fmtstr
= "%(asctime)s.%(msecs)03d %(levelname)s: {}({}): %(message)s".format(
121 self
.__class
__.__name
__, self
.name
123 handler
.setFormatter(logging
.Formatter(fmt
=fmtstr
))
124 self
.logger
.addHandler(handler
)
126 def set_pre_cmd(self
, pre_cmd
=None):
129 self
.pre_cmd_str
= ""
131 self
.pre_cmd
= pre_cmd
132 self
.pre_cmd_str
= " ".join(self
.pre_cmd
) + " "
135 return "Commander({})".format(self
.name
)
137 def get_exec_path(self
, binary
):
138 """Return the full path to the binary executable.
140 `binary` :: binary name or list of binary names
142 if is_string(binary
):
147 if b
in self
.exec_paths
:
148 return self
.exec_paths
[b
]
150 rc
, output
, _
= self
.cmd_status("which " + b
, warn
=False)
152 return os
.path
.abspath(output
.strip())
155 def get_tmp_dir(self
, uniq
):
156 return os
.path
.join(tempfile
.mkdtemp(), uniq
)
158 def test(self
, flags
, arg
):
159 """Run test binary, with flags and arg"""
160 test_path
= self
.get_exec_path(["test"])
161 rc
, output
, _
= self
.cmd_status([test_path
, flags
, arg
], warn
=False)
164 def path_exists(self
, path
):
165 """Check if path exists."""
166 return self
.test("-e", path
)
168 def _get_cmd_str(self
, cmd
):
170 return self
.pre_cmd_str
+ cmd
171 cmd
= self
.pre_cmd
+ cmd
174 def _get_sub_args(self
, cmd
, defaults
, **kwargs
):
176 defaults
["shell"] = True
177 pre_cmd
= self
.pre_cmd_str
179 defaults
["shell"] = False
180 pre_cmd
= self
.pre_cmd
181 cmd
= [str(x
) for x
in cmd
]
182 defaults
.update(kwargs
)
183 return pre_cmd
, cmd
, defaults
185 def _popen(self
, method
, cmd
, skip_pre_cmd
=False, **kwargs
):
186 if sys
.version_info
[0] >= 3:
189 "stdout": subprocess
.PIPE
,
190 "stderr": subprocess
.PIPE
,
194 "stdout": subprocess
.PIPE
,
195 "stderr": subprocess
.PIPE
,
197 pre_cmd
, cmd
, defaults
= self
._get
_sub
_args
(cmd
, defaults
, **kwargs
)
199 self
.logger
.debug('%s: %s("%s", kwargs: %s)', self
, method
, cmd
, defaults
)
201 actual_cmd
= cmd
if skip_pre_cmd
else pre_cmd
+ cmd
202 p
= subprocess
.Popen(actual_cmd
, **defaults
)
203 if not hasattr(p
, "args"):
207 def set_cwd(self
, cwd
):
208 self
.logger
.warning("%s: 'cd' (%s) does not work outside namespaces", self
, cwd
)
211 def popen(self
, cmd
, **kwargs
):
213 Creates a pipe with the given `command`.
216 command: `str` or `list` of command to open a pipe with.
217 **kwargs: kwargs is eventually passed on to Popen. If `command` is a string
218 then will be invoked with shell=True, otherwise `command` is a list and
219 will be invoked with shell=False.
222 a subprocess.Popen object.
224 p
, _
= self
._popen
("popen", cmd
, **kwargs
)
227 def cmd_status(self
, cmd
, raises
=False, warn
=True, stdin
=None, **kwargs
):
228 """Execute a command."""
230 # We are not a shell like mininet, so we need to intercept this
232 if not is_string(cmd
):
235 # XXX we can drop this when the code stops assuming it works
236 m
= re
.match(r
"cd(\s*|\s+(\S+))$", cmd
)
239 "Bad call to 'cd' (chdir) emulating, use self.set_cwd():\n%s",
240 "".join(traceback
.format_stack(limit
=12)),
242 assert is_string(cmd
)
246 # If we are going to run under bash then we don't need shell=True!
247 cmds
= ["/bin/bash", "-c", cmd
]
251 if is_string(stdin
) or isinstance(stdin
, bytes
):
253 stdin
= subprocess
.PIPE
255 p
, actual_cmd
= self
._popen
("cmd_status", cmds
, stdin
=stdin
, **kwargs
)
256 stdout
, stderr
= p
.communicate(input=pinput
)
259 # For debugging purposes.
260 self
.last
= (rc
, actual_cmd
, cmd
, stdout
, stderr
)
265 "%s: proc failed: %s:", self
, proc_error(p
, stdout
, stderr
)
268 # error = Exception("stderr: {}".format(stderr))
269 # This annoyingly doesnt' show stderr when printed normally
270 error
= subprocess
.CalledProcessError(rc
, actual_cmd
)
271 error
.stdout
, error
.stderr
= stdout
, stderr
274 self
.set_cwd(stdout
.strip())
276 return rc
, stdout
, stderr
278 def cmd_legacy(self
, cmd
, **kwargs
):
279 """Execute a command with stdout and stderr joined, *IGNORES ERROR*."""
281 defaults
= {"stderr": subprocess
.STDOUT
}
282 defaults
.update(kwargs
)
283 _
, stdout
, _
= self
.cmd_status(cmd
, raises
=False, **defaults
)
286 def cmd_raises(self
, cmd
, **kwargs
):
287 """Execute a command. Raise an exception on errors"""
289 rc
, stdout
, _
= self
.cmd_status(cmd
, raises
=True, **kwargs
)
293 # Run a command in a new window (gnome-terminal, screen, tmux, xterm)
306 Run a command in a new window (TMUX, Screen or XTerm).
309 wait_for: True to wait for exit from command or `str` as channel neme to signal on exit, otherwise False
310 background: Do not change focus to new window.
311 title: Title for new pane (tmux) or window (xterm).
312 name: Name of the new window (tmux)
313 forcex: Force use of X11.
314 new_window: Open new window (instead of pane) in TMUX
315 tmux_target: Target for tmux pane.
318 the pane/window identifier from TMUX (depends on `new_window`)
322 if is_string(wait_for
):
324 elif wait_for
is True:
325 channel
= "{}-wait-{}".format(os
.getpid(), Commander
.tmux_wait_gen
)
326 Commander
.tmux_wait_gen
+= 1
328 sudo_path
= self
.get_exec_path(["sudo"])
329 nscmd
= sudo_path
+ " " + self
.pre_cmd_str
+ cmd
330 if "TMUX" in os
.environ
and not forcex
:
331 cmd
= [self
.get_exec_path("tmux")]
333 cmd
.append("new-window")
340 cmd
.append(tmux_target
)
342 cmd
.append("split-window")
346 tmux_target
= os
.getenv("TMUX_PANE", "")
351 cmd
.append(tmux_target
)
353 nscmd
= "printf '\033]2;{}\033\\'; {}".format(title
, nscmd
)
355 nscmd
= 'trap "tmux wait -S {}; exit 0" EXIT; {}'.format(channel
, nscmd
)
357 elif "STY" in os
.environ
and not forcex
:
358 # wait for not supported in screen for now
360 cmd
= [self
.get_exec_path("screen")]
361 if not os
.path
.exists(
362 "/run/screen/S-{}/{}".format(os
.environ
["USER"], os
.environ
["STY"])
364 cmd
= ["sudo", "-u", os
.environ
["SUDO_USER"]] + cmd
366 elif "DISPLAY" in os
.environ
:
367 # We need it broken up for xterm
369 cmd
= [self
.get_exec_path("xterm")]
370 if "SUDO_USER" in os
.environ
:
371 cmd
= [self
.get_exec_path("sudo"), "-u", os
.environ
["SUDO_USER"]] + cmd
376 cmd
.append(sudo_path
)
377 cmd
.extend(self
.pre_cmd
)
378 cmd
.extend(["bash", "-c", user_cmd
])
380 # return self.cmd_raises(cmd, skip_pre_cmd=True)
389 if p
.poll() is not None:
390 self
.logger
.error("%s: Failed to launch xterm: %s", self
, comm_error(p
))
394 "DISPLAY, STY, and TMUX not in environment, can't open window"
396 raise Exception("Window requestd but TMUX, Screen and X11 not available")
398 pane_info
= self
.cmd_raises(cmd
, skip_pre_cmd
=True).strip()
400 # Re-adjust the layout
401 if "TMUX" in os
.environ
:
403 "tmux select-layout -t {} tiled".format(
404 pane_info
if not tmux_target
else tmux_target
409 # Wait here if we weren't handed the channel to wait for
410 if channel
and wait_for
is True:
411 cmd
= [self
.get_exec_path("tmux"), "wait", channel
]
412 self
.cmd_status(cmd
, skip_pre_cmd
=True)
420 class LinuxNamespace(Commander
):
424 An object that creates and executes commands in a linux namespace
443 Create a new linux namespace.
446 name: Internal name for the namespace.
447 net: Create network namespace.
448 mount: Create network namespace.
449 uts: Create UTS (hostname) namespace.
450 cgroup: Create cgroup namespace.
451 ipc: Create IPC namespace.
452 pid: Create PID namespace, also mounts new /proc.
453 time: Create time namespace.
454 user: Create user namespace, also keeps capabilities.
455 set_hostname: Set the hostname to `name`, uts must also be True.
456 private_mounts: List of strings of the form
457 "[/external/path:]/internal/path. If no external path is specified a
458 tmpfs is mounted on the internal path. Any paths specified are first
459 passed to `mkdir -p`.
460 logger: Passed to superclass.
462 super(LinuxNamespace
, self
).__init
__(name
, logger
)
464 self
.logger
.debug("%s: Creating", self
)
469 cmd
= ["/usr/bin/unshare"]
474 nslist
.append("cgroup")
488 cmd
.append("--mount-proc")
490 # XXX this filename is probably wrong
491 nslist
.append("time")
494 nslist
.append("user")
496 cmd
.append("--keep-caps")
502 cmd
.append("/bin/cat")
504 # Using cat and a stdin PIPE is nice as it will exit when we do. However, we
505 # also detach it from the pgid so that signals do not propagate to it. This is
506 # b/c it would exit early (e.g., ^C) then, at least the main micronet proc which
507 # has no other processes like frr daemons running, will take the main network
508 # namespace with it, which will remove the bridges and the veth pair (because
509 # the bridge side veth is deleted).
510 self
.logger
.debug("%s: creating namespace process: %s", self
, cmd
)
511 p
= subprocess
.Popen(
513 stdin
=subprocess
.PIPE
,
514 stdout
=open("/dev/null", "w"),
515 stderr
=open("/dev/null", "w"),
516 preexec_fn
=os
.setsid
, # detach from pgid so signals don't propogate
522 self
.logger
.debug("%s: namespace pid: %d", self
, self
.pid
)
524 # -----------------------------------------------
525 # Now let's wait until unshare completes it's job
526 # -----------------------------------------------
527 timeout
= Timeout(30)
528 while p
.poll() is None and not timeout
.is_expired():
529 for fname
in tuple(nslist
):
530 ours
= os
.readlink("/proc/self/ns/{}".format(fname
))
531 theirs
= os
.readlink("/proc/{}/ns/{}".format(self
.pid
, fname
))
532 # See if their namespace is different
537 elapsed
= int(timeout
.elapsed())
541 self
.logger
.warning("%s: unshare taking more than %ss", self
, elapsed
)
544 self
.logger
.info("%s: unshare taking more than %ss", self
, elapsed
)
546 assert p
.poll() is None, "unshare unexpectedly exited!"
547 assert not nslist
, "unshare never unshared!"
549 # Set pre-command based on our namespace proc
550 self
.base_pre_cmd
= ["/usr/bin/nsenter", "-a", "-t", str(self
.pid
)]
552 self
.base_pre_cmd
.append("-F")
553 self
.set_pre_cmd(self
.base_pre_cmd
+ ["--wd=" + self
.cwd
])
555 # Remount /sys to pickup any changes
556 self
.cmd_raises("mount -t sysfs sysfs /sys")
558 # Set the hostname to the namespace name
559 if uts
and set_hostname
:
560 # Debugging get the root hostname
561 self
.cmd_raises("hostname " + self
.name
)
562 nroot
= subprocess
.check_output("hostname")
563 if root_hostname
!= nroot
:
564 result
= self
.p
.poll()
565 assert root_hostname
== nroot
, "STATE of namespace process {}".format(
570 if is_string(private_mounts
):
571 private_mounts
= [private_mounts
]
572 for m
in private_mounts
:
575 self
.tmpfs_mount(s
[0])
577 self
.bind_mount(s
[0], s
[1])
579 o
= self
.cmd_legacy("ls -l /proc/{}/ns".format(self
.pid
))
580 self
.logger
.debug("namespaces:\n %s", o
)
582 # Doing this here messes up all_protocols ipv6 check
583 self
.cmd_raises("ip link set lo up")
586 return "LinuxNamespace({})".format(self
.name
)
588 def tmpfs_mount(self
, inner
):
589 self
.cmd_raises("mkdir -p " + inner
)
590 self
.cmd_raises("mount -n -t tmpfs tmpfs " + inner
)
592 def bind_mount(self
, outer
, inner
):
593 self
.cmd_raises("mkdir -p " + inner
)
594 self
.cmd_raises("mount --rbind {} {} ".format(outer
, inner
))
596 def add_netns(self
, ns
):
597 self
.logger
.debug("Adding network namespace %s", ns
)
599 ip_path
= self
.get_exec_path("ip")
600 assert ip_path
, "XXX missing ip command!"
601 if os
.path
.exists("/run/netns/{}".format(ns
)):
602 self
.logger
.warning("%s: Removing existing nsspace %s", self
, ns
)
604 self
.delete_netns(ns
)
605 except Exception as ex
:
607 "%s: Couldn't remove existing nsspace %s: %s",
613 self
.cmd_raises([ip_path
, "netns", "add", ns
])
615 def delete_netns(self
, ns
):
616 self
.logger
.debug("Deleting network namespace %s", ns
)
618 ip_path
= self
.get_exec_path("ip")
619 assert ip_path
, "XXX missing ip command!"
620 self
.cmd_raises([ip_path
, "netns", "delete", ns
])
622 def set_intf_netns(self
, intf
, ns
, up
=False):
623 # In case a user hard-codes 1 thinking it "resets"
628 self
.logger
.debug("Moving interface %s to namespace %s", intf
, ns
)
630 cmd
= "ip link set {} netns " + ns
633 self
.intf_ip_cmd(intf
, cmd
)
634 if ns
== str(self
.pid
):
635 # If we are returning then remove from dict
636 if intf
in self
.ifnetns
:
637 del self
.ifnetns
[intf
]
639 self
.ifnetns
[intf
] = ns
641 def reset_intf_netns(self
, intf
):
642 self
.logger
.debug("Moving interface %s to default namespace", intf
)
643 self
.set_intf_netns(intf
, str(self
.pid
))
645 def intf_ip_cmd(self
, intf
, cmd
):
646 """Run an ip command for considering an interfaces possible namespace.
648 `cmd` - format is run using the interface name on the command
650 if intf
in self
.ifnetns
:
651 assert cmd
.startswith("ip ")
652 cmd
= "ip -n " + self
.ifnetns
[intf
] + cmd
[2:]
653 self
.cmd_raises(cmd
.format(intf
))
655 def set_cwd(self
, cwd
):
656 # Set pre-command based on our namespace proc
657 self
.logger
.debug("%s: new CWD %s", self
, cwd
)
658 self
.set_pre_cmd(self
.base_pre_cmd
+ ["--wd=" + cwd
])
660 def register_interface(self
, ifname
):
661 if ifname
not in self
.intfs
:
662 self
.intfs
.append(ifname
)
665 if self
.p
and self
.p
.poll() is None:
666 if sys
.version_info
[0] >= 3:
669 self
.p
.communicate(timeout
=10)
670 except subprocess
.TimeoutExpired
:
672 self
.p
.communicate(timeout
=2)
676 self
.set_pre_cmd(["/bin/false"])
679 class SharedNamespace(Commander
):
681 Share another namespace.
683 An object that executes commands in an existing pid's linux namespace
686 def __init__(self
, name
, pid
, logger
=None):
688 Share a linux namespace.
691 name: Internal name for the namespace.
692 pid: PID of the process to share with.
694 super(SharedNamespace
, self
).__init
__(name
, logger
)
696 self
.logger
.debug("%s: Creating", self
)
701 # Set pre-command based on our namespace proc
703 ["/usr/bin/nsenter", "-a", "-t", str(self
.pid
), "--wd=" + self
.cwd
]
707 return "SharedNamespace({})".format(self
.name
)
709 def set_cwd(self
, cwd
):
710 # Set pre-command based on our namespace proc
711 self
.logger
.debug("%s: new CWD %s", self
, cwd
)
712 self
.set_pre_cmd(["/usr/bin/nsenter", "-a", "-t", str(self
.pid
), "--wd=" + cwd
])
714 def register_interface(self
, ifname
):
715 if ifname
not in self
.intfs
:
716 self
.intfs
.append(ifname
)
719 class Bridge(SharedNamespace
):
727 def _get_next_brid(cls
):
728 brid_ord
= cls
.next_brid_ord
729 cls
.next_brid_ord
+= 1
732 def __init__(self
, name
=None, unet
=None, logger
=None):
733 """Create a linux Bridge."""
736 self
.brid_ord
= self
._get
_next
_brid
()
740 self
.brid
= "br{}".format(self
.brid_ord
)
743 super(Bridge
, self
).__init
__(name
, unet
.pid
, logger
)
745 self
.logger
.debug("Bridge: Creating")
747 assert len(self
.brid
) <= 16 # Make sure fits in IFNAMSIZE
748 self
.cmd_raises("ip link delete {} || true".format(self
.brid
))
749 self
.cmd_raises("ip link add {} type bridge".format(self
.brid
))
750 self
.cmd_raises("ip link set {} up".format(self
.brid
))
752 self
.logger
.debug("%s: Created, Running", self
)
755 return "Bridge({})".format(self
.brid
)
758 """Stop the bridge (i.e., delete the linux resources)."""
760 rc
, o
, e
= self
.cmd_status("ip link show {}".format(self
.brid
), warn
=False)
762 rc
, o
, e
= self
.cmd_status(
763 "ip link delete {}".format(self
.brid
), warn
=False
767 "%s: error deleting bridge %s: %s",
773 self
.logger
.debug("%s: Deleted.", self
)
776 class Micronet(LinuxNamespace
): # pylint: disable=R0205
782 """Create a Micronet."""
790 super(Micronet
, self
).__init
__("micronet", mount
=True, net
=True, uts
=True)
792 self
.logger
.debug("%s: Creating", self
)
797 def __getitem__(self
, key
):
798 if key
in self
.switches
:
799 return self
.switches
[key
]
800 return self
.hosts
[key
]
802 def add_host(self
, name
, cls
=LinuxNamespace
, **kwargs
):
803 """Add a host to micronet."""
805 self
.logger
.debug("%s: add_host %s", self
, name
)
807 self
.hosts
[name
] = cls(name
, **kwargs
)
808 # Create a new mounted FS for tracking nested network namespaces creatd by the
809 # user with `ip netns add`
810 self
.hosts
[name
].tmpfs_mount("/run/netns")
812 def add_link(self
, name1
, name2
, if1
, if2
):
813 """Add a link between switch and host to micronet."""
815 if name1
in self
.switches
:
816 assert name2
in self
.hosts
817 elif name2
in self
.switches
:
818 assert name1
in self
.hosts
819 name1
, name2
= name2
, name1
823 assert name1
in self
.hosts
824 assert name2
in self
.hosts
827 lname
= "{}:{}-{}:{}".format(name1
, if1
, name2
, if2
)
828 self
.logger
.debug("%s: add_link %s%s", self
, lname
, " p2p" if isp2p
else "")
829 self
.links
[lname
] = (name1
, if1
, name2
, if2
)
831 # And create the veth now.
833 lhost
, rhost
= self
.hosts
[name1
], self
.hosts
[name2
]
834 lifname
= "i1{:x}".format(lhost
.pid
)
835 rifname
= "i2{:x}".format(rhost
.pid
)
837 "ip link add {} type veth peer name {}".format(lifname
, rifname
)
840 self
.cmd_raises("ip link set {} netns {}".format(lifname
, lhost
.pid
))
841 lhost
.cmd_raises("ip link set {} name {}".format(lifname
, if1
))
842 lhost
.cmd_raises("ip link set {} up".format(if1
))
843 lhost
.register_interface(if1
)
845 self
.cmd_raises("ip link set {} netns {}".format(rifname
, rhost
.pid
))
846 rhost
.cmd_raises("ip link set {} name {}".format(rifname
, if2
))
847 rhost
.cmd_raises("ip link set {} up".format(if2
))
848 rhost
.register_interface(if2
)
850 switch
= self
.switches
[name1
]
851 host
= self
.hosts
[name2
]
853 assert len(if1
) <= 16 and len(if2
) <= 16 # Make sure fits in IFNAMSIZE
855 self
.logger
.debug("%s: Creating veth pair for link %s", self
, lname
)
857 "ip link add {} type veth peer name {} netns {}".format(
861 self
.cmd_raises("ip link set {} netns {}".format(if1
, switch
.pid
))
862 switch
.register_interface(if1
)
863 host
.register_interface(if2
)
864 self
.cmd_raises("ip link set {} master {}".format(if1
, switch
.brid
))
865 self
.cmd_raises("ip link set {} up".format(if1
))
866 host
.cmd_raises("ip link set {} up".format(if2
))
868 # Cache the MAC values, and reverse mapping
869 self
.get_mac(name1
, if1
)
870 self
.get_mac(name2
, if2
)
872 def add_switch(self
, name
):
873 """Add a switch to micronet."""
875 self
.logger
.debug("%s: add_switch %s", self
, name
)
876 self
.switches
[name
] = Bridge(name
, self
)
878 def get_mac(self
, name
, ifname
):
879 if name
in self
.hosts
:
880 dev
= self
.hosts
[name
]
882 dev
= self
.switches
[name
]
884 if (name
, ifname
) not in self
.macs
:
885 _
, output
, _
= dev
.cmd_status("ip -o link show " + ifname
)
886 m
= re
.match(".*link/(loopback|ether) ([0-9a-fA-F:]+) .*", output
)
888 self
.macs
[(name
, ifname
)] = mac
889 self
.rmacs
[mac
] = (name
, ifname
)
891 return self
.macs
[(name
, ifname
)]
894 """Delete the micronet topology."""
896 self
.logger
.debug("%s: Deleting.", self
)
898 for lname
, (_
, _
, rname
, rif
) in self
.links
.items():
899 host
= self
.hosts
[rname
]
901 self
.logger
.debug("%s: Deleting veth pair for link %s", self
, lname
)
903 rc
, o
, e
= host
.cmd_status("ip link delete {}".format(rif
), warn
=False)
906 "Error deleting veth pair %s: %s", lname
, cmd_error(rc
, o
, e
)
911 for host
in self
.hosts
.values():
914 except Exception as error
:
916 "%s: error while deleting host %s: %s", self
, host
, error
921 for switch
in self
.switches
.values():
924 except Exception as error
:
926 "%s: error while deleting switch %s: %s", self
, switch
, error
930 self
.logger
.debug("%s: Deleted.", self
)
932 super(Micronet
, self
).delete()
935 # ---------------------------
936 # Root level utility function
937 # ---------------------------
940 def get_exec_path(binary
):
941 base
= Commander("base")
942 return base
.get_exec_path(binary
)
945 commander
= Commander("micronet")