From 597195052371d67f009321dfddcc5de8577fff3a Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Fri, 28 Jun 2019 08:17:11 -0700 Subject: [PATCH] changes to have DockerNode based off a CoreNode instead, elminating the need for a lot of boiler plate --- daemon/core/nodes/docker.py | 354 +----------------------- daemon/examples/docker/docker2core.py | 1 + daemon/examples/docker/docker2docker.py | 1 + daemon/examples/docker/switch.py | 1 + 4 files changed, 17 insertions(+), 340 deletions(-) diff --git a/daemon/core/nodes/docker.py b/daemon/core/nodes/docker.py index a01db2e9..74b9ea01 100644 --- a/daemon/core/nodes/docker.py +++ b/daemon/core/nodes/docker.py @@ -1,16 +1,10 @@ import logging import os -import random -import shutil -import string import threading -from core import utils, CoreCommandError, constants +from core import utils, CoreCommandError from core.emulator.enumerations import NodeTypes -from core.nodes.base import CoreNodeBase -from core.nodes.interface import Veth, TunTap, CoreInterface - -_DEFAULT_MTU = 1500 +from core.nodes.base import CoreNode class DockerClient(object): @@ -24,8 +18,10 @@ class DockerClient(object): name=self.name, image=image )) + self.pid = self.get_pid() + return self.pid - def is_container_alive(self): + def is_alive(self): status, output = utils.cmd_output("docker containers ls -f name={name}".format( name=self.name )) @@ -47,13 +43,10 @@ class DockerClient(object): def ns_cmd(self, cmd): if isinstance(cmd, list): cmd = " ".join(cmd) - status, output = utils.cmd_output("nsenter -t {pid} -m -u -i -p -n {cmd}".format( + return utils.cmd_output("nsenter -t {pid} -m -u -i -p -n {cmd}".format( pid=self.pid, cmd=cmd )) - if status: - raise CoreCommandError(status, output) - return output def get_pid(self): status, output = utils.cmd_output("docker inspect -f '{{{{.State.Pid}}}}' {name}".format(name=self.name)) @@ -98,7 +91,7 @@ class DockerClient(object): return interface -class DockerNode(CoreNodeBase): +class DockerNode(CoreNode): apitype = NodeTypes.DOCKER.value valid_address_types = {"inet", "inet6", "inet6link"} @@ -113,7 +106,7 @@ class DockerNode(CoreNodeBase): :param str bootsh: boot shell to use :param bool start: start flag """ - super(CoreNodeBase, self).__init__(session, _id, name, start=start) + super(CoreNode, self).__init__(session, _id, name, start=start) self.nodedir = nodedir self.ctrlchnlname = os.path.abspath(os.path.join(self.session.session_dir, self.name)) self.client = DockerClient(self.name) @@ -122,6 +115,7 @@ class DockerNode(CoreNodeBase): self.lock = threading.RLock() self._mounts = [] self.bootsh = bootsh + logging.debug("docker services: %s", self.services) if start: self.startup() @@ -132,7 +126,7 @@ class DockerNode(CoreNodeBase): :return: True if node is alive, False otherwise :rtype: bool """ - return self.client.is_container_alive() + return self.client.is_alive() def startup(self): """ @@ -145,8 +139,7 @@ class DockerNode(CoreNodeBase): with self.lock: if self.up: raise ValueError("starting a node that is already up") - self.client.create_container("ubuntu:ifconfig") - self.pid = self.client.get_pid() + self.pid = self.client.create_container("ubuntu:ifconfig") self.up = True def shutdown(self): @@ -173,7 +166,7 @@ class DockerNode(CoreNodeBase): :return: exit status for command :rtype: int """ - status, _ = self.client.run_cmd(args) + status, _ = self.client.ns_cmd(args) return status def cmd_output(self, args): @@ -184,7 +177,7 @@ class DockerNode(CoreNodeBase): :return: exit status and combined stdout and stderr :rtype: tuple[int, str] """ - return self.client.run_cmd(args) + return self.client.ns_cmd(args) def check_cmd(self, args): """ @@ -195,7 +188,7 @@ class DockerNode(CoreNodeBase): :rtype: str :raises CoreCommandError: when a non-zero exit status occurs """ - status, output = self.client.run_cmd(args) + status, output = self.client.ns_cmd(args) if status: raise CoreCommandError(status, output) return output @@ -228,322 +221,3 @@ class DockerNode(CoreNodeBase): :raises CoreCommandError: when a non-zero exit status occurs """ pass - - def newifindex(self): - """ - Retrieve a new interface index. - - :return: new interface index - :rtype: int - """ - with self.lock: - return super(DockerNode, self).newifindex() - - def newveth(self, ifindex=None, ifname=None, net=None): - """ - Create a new interface. - - :param int ifindex: index for the new interface - :param str ifname: name for the new interface - :param core.nodes.base.CoreNetworkBase net: network to associate interface with - :return: nothing - """ - with self.lock: - if ifindex is None: - ifindex = self.newifindex() - - if ifname is None: - ifname = "eth%d" % ifindex - - sessionid = self.session.short_session_id() - - try: - suffix = "%x.%s.%s" % (self.id, ifindex, sessionid) - except TypeError: - suffix = "%s.%s.%s" % (self.id, ifindex, sessionid) - - localname = "veth" + suffix - if len(localname) >= 16: - raise ValueError("interface local name (%s) too long" % localname) - - name = localname + "p" - if len(name) >= 16: - raise ValueError("interface name (%s) too long" % name) - - veth = Veth(node=self, name=name, localname=localname, net=net, start=self.up) - - if self.up: - utils.check_cmd([constants.IP_BIN, "link", "set", veth.name, "netns", str(self.pid)]) - self.client.ns_cmd(["ip", "link", "set", veth.name, "name", ifname]) - self.client.ns_cmd(["ethtool", "-K", ifname, "rx", "off", "tx", "off"]) - - veth.name = ifname - - if self.up: - # TODO: potentially find better way to query interface ID - # retrieve interface information - output = self.client.ns_cmd([constants.IP_BIN, "link", "show", veth.name]) - logging.debug("interface command output: %s", output) - output = output.split("\n") - veth.flow_id = int(output[0].strip().split(":")[0]) + 1 - logging.debug("interface flow index: %s - %s", veth.name, veth.flow_id) - # TODO: mimic packed hwaddr - # veth.hwaddr = MacAddress.from_string(output[1].strip().split()[1]) - logging.debug("interface mac: %s - %s", veth.name, veth.hwaddr) - - try: - self.addnetif(veth, ifindex) - except ValueError as e: - veth.shutdown() - del veth - raise e - - return ifindex - - def newtuntap(self, ifindex=None, ifname=None, net=None): - """ - Create a new tunnel tap. - - :param int ifindex: interface index - :param str ifname: interface name - :param net: network to associate with - :return: interface index - :rtype: int - """ - with self.lock: - if ifindex is None: - ifindex = self.newifindex() - - if ifname is None: - ifname = "eth%d" % ifindex - - sessionid = self.session.short_session_id() - localname = "tap%s.%s.%s" % (self.id, ifindex, sessionid) - name = ifname - tuntap = TunTap(node=self, name=name, localname=localname, net=net, start=self.up) - - try: - self.addnetif(tuntap, ifindex) - except ValueError as e: - tuntap.shutdown() - del tuntap - raise e - - return ifindex - - def sethwaddr(self, ifindex, addr): - """ - Set hardware addres for an interface. - - :param int ifindex: index of interface to set hardware address for - :param core.nodes.ipaddress.MacAddress addr: hardware address to set - :return: nothing - :raises CoreCommandError: when a non-zero exit status occurs - """ - self._netif[ifindex].sethwaddr(addr) - if self.up: - args = ["ip", "link", "set", "dev", self.ifname(ifindex), "address", str(addr)] - self.client.ns_cmd(args) - - def addaddr(self, ifindex, addr): - """ - Add interface address. - - :param int ifindex: index of interface to add address to - :param str addr: address to add to interface - :return: nothing - """ - if self.up: - # check if addr is ipv6 - if ":" in str(addr): - args = ["ip", "addr", "add", str(addr), "dev", self.ifname(ifindex)] - self.client.ns_cmd(args) - else: - args = ["ip", "addr", "add", str(addr), "broadcast", "+", "dev", self.ifname(ifindex)] - self.client.ns_cmd(args) - - self._netif[ifindex].addaddr(addr) - - def deladdr(self, ifindex, addr): - """ - Delete address from an interface. - - :param int ifindex: index of interface to delete address from - :param str addr: address to delete from interface - :return: nothing - :raises CoreCommandError: when a non-zero exit status occurs - """ - try: - self._netif[ifindex].deladdr(addr) - except ValueError: - logging.exception("trying to delete unknown address: %s" % addr) - - if self.up: - self.check_cmd(["ip", "addr", "del", str(addr), "dev", self.ifname(ifindex)]) - - def delalladdr(self, ifindex, address_types=None): - """ - Delete all addresses from an interface. - - :param int ifindex: index of interface to delete address types from - :param tuple[str] address_types: address types to delete - :return: nothing - :raises CoreCommandError: when a non-zero exit status occurs - """ - if not address_types: - address_types = self.valid_address_types - - interface_name = self.ifname(ifindex) - addresses = self.client.getaddr(interface_name, rescan=True) - - for address_type in address_types: - if address_type not in self.valid_address_types: - raise ValueError("addr type must be in: %s" % " ".join(self.valid_address_types)) - for address in addresses[address_type]: - self.deladdr(ifindex, address) - - # update cached information - self.client.getaddr(interface_name, rescan=True) - - def ifup(self, ifindex): - """ - Bring an interface up. - - :param int ifindex: index of interface to bring up - :return: nothing - """ - if self.up: - # self.check_cmd(["ip", "link", "set", self.ifname(ifindex), "up"]) - self.client.ns_cmd(["ip", "link", "set", self.ifname(ifindex), "up"]) - - def newnetif(self, net=None, addrlist=None, hwaddr=None, ifindex=None, ifname=None): - """ - Create a new network interface. - - :param core.nodes.base.CoreNetworkBase net: network to associate with - :param list addrlist: addresses to add on the interface - :param core.nodes.ipaddress.MacAddress hwaddr: hardware address to set for interface - :param int ifindex: index of interface to create - :param str ifname: name for interface - :return: interface index - :rtype: int - """ - if not addrlist: - addrlist = [] - - with self.lock: - ifindex = self.newveth(ifindex=ifindex, ifname=ifname, net=net) - - if net is not None: - self.attachnet(ifindex, net) - - if hwaddr: - self.sethwaddr(ifindex, hwaddr) - - for address in utils.make_tuple(addrlist): - self.addaddr(ifindex, address) - - self.ifup(ifindex) - return ifindex - - def connectnode(self, ifname, othernode, otherifname): - """ - Connect a node. - - :param str ifname: name of interface to connect - :param core.nodes.CoreNodeBase othernode: node to connect to - :param str otherifname: interface name to connect to - :return: nothing - """ - tmplen = 8 - tmp1 = "tmp." + "".join([random.choice(string.ascii_lowercase) for _ in range(tmplen)]) - tmp2 = "tmp." + "".join([random.choice(string.ascii_lowercase) for _ in range(tmplen)]) - utils.check_cmd([constants.IP_BIN, "link", "add", "name", tmp1, "type", "veth", "peer", "name", tmp2]) - - utils.check_cmd([constants.IP_BIN, "link", "set", tmp1, "netns", str(self.pid)]) - self.check_cmd(["ip", "link", "set", tmp1, "name", ifname]) - interface = CoreInterface(node=self, name=ifname, mtu=_DEFAULT_MTU) - self.addnetif(interface, self.newifindex()) - - utils.check_cmd([constants.IP_BIN, "link", "set", tmp2, "netns", str(othernode.pid)]) - othernode.check_cmd([constants.IP_BIN, "link", "set", tmp2, "name", otherifname]) - other_interface = CoreInterface(node=othernode, name=otherifname, mtu=_DEFAULT_MTU) - othernode.addnetif(other_interface, othernode.newifindex()) - - def addfile(self, srcname, filename): - """ - Add a file. - - :param str srcname: source file name - :param str filename: file name to add - :return: nothing - :raises CoreCommandError: when a non-zero exit status occurs - """ - logging.info("adding file from %s to %s", srcname, filename) - directory = os.path.dirname(filename) - - cmd = 'mkdir -p "%s" && mv "%s" "%s" && sync' % (directory, srcname, filename) - status, output = self.client.run_cmd(cmd) - if status: - raise CoreCommandError(status, cmd, output) - - def hostfilename(self, filename): - """ - Return the name of a node"s file on the host filesystem. - - :param str filename: host file name - :return: path to file - """ - dirname, basename = os.path.split(filename) - if not basename: - raise ValueError("no basename for filename: %s" % filename) - if dirname and dirname[0] == "/": - dirname = dirname[1:] - dirname = dirname.replace("/", ".") - dirname = os.path.join(self.nodedir, dirname) - return os.path.join(dirname, basename) - - def opennodefile(self, filename, mode="w"): - """ - Open a node file, within it"s directory. - - :param str filename: file name to open - :param str mode: mode to open file in - :return: open file - :rtype: file - """ - hostfilename = self.hostfilename(filename) - dirname, _basename = os.path.split(hostfilename) - if not os.path.isdir(dirname): - os.makedirs(dirname, mode=0o755) - return open(hostfilename, mode) - - def nodefile(self, filename, contents, mode=0o644): - """ - Create a node file with a given mode. - - :param str filename: name of file to create - :param contents: contents of file - :param int mode: mode for file - :return: nothing - """ - with self.opennodefile(filename, "w") as open_file: - open_file.write(contents) - os.chmod(open_file.name, mode) - logging.info("node(%s) added file: %s; mode: 0%o", self.name, open_file.name, mode) - - def nodefilecopy(self, filename, srcfilename, mode=None): - """ - Copy a file to a node, following symlinks and preserving metadata. - Change file mode if specified. - - :param str filename: file name to copy file to - :param str srcfilename: file to copy - :param int mode: mode to copy to - :return: nothing - """ - hostfilename = self.hostfilename(filename) - shutil.copy2(srcfilename, hostfilename) - if mode is not None: - os.chmod(hostfilename, mode) - logging.info("node(%s) copied file: %s; mode: %s", self.name, hostfilename, mode) diff --git a/daemon/examples/docker/docker2core.py b/daemon/examples/docker/docker2core.py index ba328c0f..1b27039b 100644 --- a/daemon/examples/docker/docker2core.py +++ b/daemon/examples/docker/docker2core.py @@ -26,6 +26,7 @@ if __name__ == "__main__": print(node_one.cmd_output("ifconfig")) print(node_two.cmd_output("ifconfig")) input("press key to continue") + session.instantiate() finally: input("continue to shutdown") coreemu.shutdown() diff --git a/daemon/examples/docker/docker2docker.py b/daemon/examples/docker/docker2docker.py index e1001da9..60c63c53 100644 --- a/daemon/examples/docker/docker2docker.py +++ b/daemon/examples/docker/docker2docker.py @@ -26,6 +26,7 @@ if __name__ == "__main__": print(node_one.cmd_output("ifconfig")) print(node_two.cmd_output("ifconfig")) input("press key to continue") + session.instantiate() finally: input("continue to shutdown") coreemu.shutdown() diff --git a/daemon/examples/docker/switch.py b/daemon/examples/docker/switch.py index dbcc8466..70ba634f 100644 --- a/daemon/examples/docker/switch.py +++ b/daemon/examples/docker/switch.py @@ -31,6 +31,7 @@ if __name__ == "__main__": print(node_one.cmd_output("ifconfig")) print(node_two.cmd_output("ifconfig")) input("press key to continue") + session.instantiate() finally: input("continue to shutdown") coreemu.shutdown()