diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 7d1cab9e..451e3a11 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -669,12 +669,12 @@ class EmaneManager(ModelManager): # multicast route is needed for OTA data args = [constants.IP_BIN, "route", "add", otagroup, "dev", otadev] - node.check_cmd(args) + node.network_cmd(args) # multicast route is also needed for event data if on control network if eventservicenetidx >= 0 and eventgroup != otagroup: args = [constants.IP_BIN, "route", "add", eventgroup, "dev", eventdev] - node.check_cmd(args) + node.network_cmd(args) # start emane args = emanecmd + ["-f", os.path.join(path, "emane%d.log" % n), os.path.join(path, "platform%d.xml" % n)] diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 555eda78..1ce21aca 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -493,11 +493,11 @@ class CoreNode(CoreNodeBase): # bring up the loopback interface logging.debug("bringing up loopback interface") - self.check_cmd([constants.IP_BIN, "link", "set", "lo", "up"]) + self.network_cmd([constants.IP_BIN, "link", "set", "lo", "up"]) # set hostname for node logging.debug("setting hostname: %s", self.name) - self.check_cmd(["hostname", self.name]) + self.network_cmd(["hostname", self.name]) # mark node as up self.up = True @@ -572,6 +572,17 @@ class CoreNode(CoreNodeBase): """ return self.client.cmd_output(args) + def network_cmd(self, args): + """ + Runs a command for a node that is used to configure and setup network interfaces. + + :param list[str]|str args: command to run + :return: combined stdout and stderr + :rtype: str + :raises CoreCommandError: when a non-zero exit status occurs + """ + return self.check_cmd(args) + def check_cmd(self, args): """ Runs shell command on node. @@ -667,15 +678,15 @@ class CoreNode(CoreNodeBase): if self.up: utils.check_cmd([constants.IP_BIN, "link", "set", veth.name, "netns", str(self.pid)]) - self.check_cmd([constants.IP_BIN, "link", "set", veth.name, "name", ifname]) - self.check_cmd([constants.ETHTOOL_BIN, "-K", ifname, "rx", "off", "tx", "off"]) + self.network_cmd([constants.IP_BIN, "link", "set", veth.name, "name", ifname]) + self.network_cmd([constants.ETHTOOL_BIN, "-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.check_cmd(["ip", "link", "show", veth.name]) + output = self.network_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 @@ -736,7 +747,7 @@ class CoreNode(CoreNodeBase): self._netif[ifindex].sethwaddr(addr) if self.up: args = [constants.IP_BIN, "link", "set", "dev", self.ifname(ifindex), "address", str(addr)] - self.check_cmd(args) + self.network_cmd(args) def addaddr(self, ifindex, addr): """ @@ -750,10 +761,10 @@ class CoreNode(CoreNodeBase): # check if addr is ipv6 if ":" in str(addr): args = [constants.IP_BIN, "addr", "add", str(addr), "dev", self.ifname(ifindex)] - self.check_cmd(args) + self.network_cmd(args) else: args = [constants.IP_BIN, "addr", "add", str(addr), "broadcast", "+", "dev", self.ifname(ifindex)] - self.check_cmd(args) + self.network_cmd(args) self._netif[ifindex].addaddr(addr) @@ -772,7 +783,7 @@ class CoreNode(CoreNodeBase): logging.exception("trying to delete unknown address: %s" % addr) if self.up: - self.check_cmd([constants.IP_BIN, "addr", "del", str(addr), "dev", self.ifname(ifindex)]) + self.network_cmd([constants.IP_BIN, "addr", "del", str(addr), "dev", self.ifname(ifindex)]) def delalladdr(self, ifindex, address_types=None): """ @@ -806,7 +817,7 @@ class CoreNode(CoreNodeBase): :return: nothing """ if self.up: - self.check_cmd([constants.IP_BIN, "link", "set", self.ifname(ifindex), "up"]) + self.network_cmd([constants.IP_BIN, "link", "set", self.ifname(ifindex), "up"]) def newnetif(self, net=None, addrlist=None, hwaddr=None, ifindex=None, ifname=None): """ @@ -867,12 +878,12 @@ class CoreNode(CoreNodeBase): 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([constants.IP_BIN, "link", "set", tmp1, "name", ifname]) + self.network_cmd([constants.IP_BIN, "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]) + othernode.network_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()) diff --git a/daemon/core/nodes/docker.py b/daemon/core/nodes/docker.py index 909909f9..f14ceaa9 100644 --- a/daemon/core/nodes/docker.py +++ b/daemon/core/nodes/docker.py @@ -1,6 +1,6 @@ +import json import logging import os -import threading from core import utils, CoreCommandError from core.emulator.enumerations import NodeTypes @@ -15,53 +15,66 @@ class DockerClient(object): self._addr = {} def create_container(self): - utils.check_cmd("docker run -td --net=none --hostname {name} --name {name} {image} /bin/bash".format( - name=self.name, - image=self.image - )) + utils.check_cmd( + "docker run -td --init --net=none --hostname {name} --name {name} " + "--sysctl net.ipv6.conf.all.disable_ipv6=0 " + "{image} /bin/bash".format( + name=self.name, + image=self.image + )) self.pid = self.get_pid() return self.pid + def get_info(self): + args = "docker inspect {name}".format(name=self.name) + status, output = utils.cmd_output(args) + if status: + raise CoreCommandError(status, args, output) + data = json.loads(output) + if not data: + raise CoreCommandError(status, args, "docker({name}) not present".format(name=self.name)) + return data[0] + def is_alive(self): - status, output = utils.cmd_output("docker containers ls -f name={name}".format( - name=self.name - )) - return not status and len(output.split("\n")) == 2 + try: + data = self.get_info() + return data["State"]["Running"] + except CoreCommandError: + return False def stop_container(self): utils.check_cmd("docker rm -f {name}".format( name=self.name )) - def run_cmd(self, cmd): + def cmd(self, cmd, wait=True): if isinstance(cmd, list): cmd = " ".join(cmd) - logging.info("docker cmd: %s", cmd) - return utils.cmd_output("docker exec -it {name} {cmd}".format( + logging.info("docker cmd wait(%s): %s", wait, cmd) + return utils.cmd("docker exec {name} {cmd}".format( + name=self.name, + cmd=cmd + ), wait) + + def cmd_output(self, cmd): + if isinstance(cmd, list): + cmd = " ".join(cmd) + logging.info("docker cmd output: %s", cmd) + return utils.cmd_output("docker exec {name} {cmd}".format( name=self.name, cmd=cmd )) - def _ns_args(self, cmd): - return "nsenter -t {pid} -m -u -i -p -n {cmd}".format( + def ns_cmd(self, cmd): + if isinstance(cmd, list): + cmd = " ".join(cmd) + args = "nsenter -t {pid} -u -i -p -n {cmd}".format( pid=self.pid, cmd=cmd ) - - def ns_cmd_output(self, cmd): - if isinstance(cmd, list): - cmd = " ".join(cmd) - args = self._ns_args(cmd) logging.info("ns cmd: %s", args) return utils.cmd_output(args) - def ns_cmd(self, cmd, wait=True): - if isinstance(cmd, list): - cmd = " ".join(cmd) - args = self._ns_args(cmd) - logging.info("ns cmd: %s", args) - return utils.cmd(args, wait) - def get_pid(self): args = "docker inspect -f '{{{{.State.Pid}}}}' {name}".format(name=self.name) status, output = utils.cmd_output(args) @@ -95,7 +108,7 @@ class DockerClient(object): interface = {"ether": [], "inet": [], "inet6": [], "inet6link": []} args = ["ip", "addr", "show", "dev", ifname] - status, output = self.ns_cmd_output(args) + status, output = self.ns_cmd(args) for line in output: line = line.strip().split() if line[0] == "link/ether": @@ -118,11 +131,10 @@ class DockerClient(object): class DockerNode(CoreNode): apitype = NodeTypes.DOCKER.value - valid_address_types = {"inet", "inet6", "inet6link"} def __init__(self, session, _id=None, name=None, nodedir=None, bootsh="boot.sh", start=True, image=None): """ - Create a CoreNode instance. + Create a DockerNode instance. :param core.emulator.session.Session session: core session instance :param int _id: object id @@ -130,21 +142,12 @@ class DockerNode(CoreNode): :param str nodedir: node directory :param str bootsh: boot shell to use :param bool start: start flag + :param str image: image to start container with """ - 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)) if image is None: image = "ubuntu" - self.client = DockerClient(self.name, image) - self.pid = None - self.up = False - self.lock = threading.RLock() - self._mounts = [] - self.bootsh = bootsh - logging.debug("docker services: %s", self.services) - if start: - self.startup() + self.image = image + super(DockerNode, self).__init__(session, _id, name, nodedir, bootsh, start) def alive(self): """ @@ -167,6 +170,7 @@ class DockerNode(CoreNode): if self.up: raise ValueError("starting a node that is already up") self.makenodedir() + self.client = DockerClient(self.name, self.image) self.pid = self.client.create_container() self.up = True @@ -194,7 +198,7 @@ class DockerNode(CoreNode): :return: exit status for command :rtype: int """ - return self.client.ns_cmd(args, wait) + return self.client.cmd(args, wait) def cmd_output(self, args): """ @@ -204,7 +208,7 @@ class DockerNode(CoreNode): :return: exit status and combined stdout and stderr :rtype: tuple[int, str] """ - return self.client.ns_cmd_output(args) + return self.client.cmd_output(args) def check_cmd(self, args): """ @@ -215,7 +219,17 @@ class DockerNode(CoreNode): :rtype: str :raises CoreCommandError: when a non-zero exit status occurs """ - status, output = self.client.ns_cmd_output(args) + status, output = self.client.cmd_output(args) + if status: + raise CoreCommandError(status, args, output) + return output + + def network_cmd(self, args): + if not self.up: + logging.debug("node down, not running network command: %s", args) + return 0 + + status, output = self.client.ns_cmd(args) if status: raise CoreCommandError(status, args, output) return output @@ -227,7 +241,7 @@ class DockerNode(CoreNode): :param str sh: shell to execute command in :return: str """ - return "" + return "docker exec -it {name} bash".format(name=self.name) def privatedir(self, path): """ @@ -238,9 +252,7 @@ class DockerNode(CoreNode): """ logging.info("creating node dir: %s", path) args = "mkdir -p {path}".format(path=path) - status, output = self.client.run_cmd(args) - if status: - raise CoreCommandError(status, args, output) + self.check_cmd(args) def mount(self, source, target): """ @@ -252,7 +264,7 @@ class DockerNode(CoreNode): :raises CoreCommandError: when a non-zero exit status occurs """ logging.info("mounting source(%s) target(%s)", source, target) - raise Exception("you found a docker node") + raise Exception("not supported") def nodefile(self, filename, contents, mode=0o644): """ @@ -282,4 +294,4 @@ class DockerNode(CoreNode): :return: nothing """ logging.info("node file copy file(%s) source(%s) mode(%s)", filename, srcfilename, mode) - raise Exception("you found a docker node") + raise Exception("not supported") diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index 3d8b6dc5..e9e4af6f 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -237,7 +237,7 @@ class Veth(CoreInterface): if self.node: try: - self.node.check_cmd([constants.IP_BIN, "-6", "addr", "flush", "dev", self.name]) + self.node.network_cmd([constants.IP_BIN, "-6", "addr", "flush", "dev", self.name]) except CoreCommandError: logging.exception("error shutting down interface") @@ -245,7 +245,7 @@ class Veth(CoreInterface): try: utils.check_cmd([constants.IP_BIN, "link", "delete", self.localname]) except CoreCommandError: - logging.exception("error deleting link") + logging.info("link already removed: %s", self.localname) self.up = False @@ -298,7 +298,7 @@ class TunTap(CoreInterface): return try: - self.node.check_cmd([constants.IP_BIN, "-6", "addr", "flush", "dev", self.name]) + self.node.network_cmd([constants.IP_BIN, "-6", "addr", "flush", "dev", self.name]) except CoreCommandError: logging.exception("error shutting down tunnel tap") @@ -361,7 +361,11 @@ class TunTap(CoreInterface): def nodedevexists(): args = [constants.IP_BIN, "link", "show", self.name] - return self.node.cmd(args) + try: + self.node.network_cmd(args) + return 0 + except CoreCommandError: + return 1 count = 0 while True: @@ -393,8 +397,8 @@ class TunTap(CoreInterface): self.waitfordevicelocal() netns = str(self.node.pid) utils.check_cmd([constants.IP_BIN, "link", "set", self.localname, "netns", netns]) - self.node.check_cmd([constants.IP_BIN, "link", "set", self.localname, "name", self.name]) - self.node.check_cmd([constants.IP_BIN, "link", "set", self.name, "up"]) + self.node.network_cmd([constants.IP_BIN, "link", "set", self.localname, "name", self.name]) + self.node.network_cmd([constants.IP_BIN, "link", "set", self.name, "up"]) def setaddrs(self): """ @@ -404,7 +408,7 @@ class TunTap(CoreInterface): """ self.waitfordevicenode() for addr in self.addrlist: - self.node.check_cmd([constants.IP_BIN, "addr", "add", str(addr), "dev", self.name]) + self.node.network_cmd([constants.IP_BIN, "addr", "add", str(addr), "dev", self.name]) class GreTap(CoreInterface): diff --git a/daemon/core/nodes/lxd.py b/daemon/core/nodes/lxd.py index f8ca377e..1437cac1 100644 --- a/daemon/core/nodes/lxd.py +++ b/daemon/core/nodes/lxd.py @@ -1,7 +1,6 @@ import json import logging import os -import threading import time from core import utils, CoreCommandError @@ -21,43 +20,46 @@ class LxdClient(object): name=self.name, image=self.image )) - data = self._get_data()[0] + data = self.get_info() self.pid = data["state"]["pid"] return self.pid - def _get_data(self): + def get_info(self): args = "lxc list {name} --format json".format(name=self.name) status, output = utils.cmd_output(args) if status: raise CoreCommandError(status, args, output) - return json.loads(output) - - def _cmd_args(self, cmd): - return "lxc exec {name} -- {cmd}".format( - name=self.name, - cmd=cmd - ) + data = json.loads(output) + if not data: + raise CoreCommandError(status, args, "LXC({name}) not present".format(name=self.name)) + return data[0] def is_alive(self): - data = self._get_data() - if not data: + try: + data = self.get_info() + return data["state"]["status"] == "Running" + except CoreCommandError: return False - data = data[0] - return data["state"]["status"] == "Running" def stop_container(self): utils.check_cmd("lxc delete --force {name}".format( name=self.name )) - def run_cmd_output(self, cmd): + def _cmd_args(self, cmd): + return "lxc exec -nT {name} -- {cmd}".format( + name=self.name, + cmd=cmd + ) + + def cmd_output(self, cmd): if isinstance(cmd, list): cmd = " ".join(cmd) args = self._cmd_args(cmd) logging.info("lxc cmd output: %s", args) return utils.cmd_output(args) - def run_cmd(self, cmd, wait=True): + def cmd(self, cmd, wait=True): if isinstance(cmd, list): cmd = " ".join(cmd) args = self._cmd_args(cmd) @@ -134,11 +136,10 @@ class LxdClient(object): class LxcNode(CoreNode): apitype = NodeTypes.LXC.value - valid_address_types = {"inet", "inet6", "inet6link"} def __init__(self, session, _id=None, name=None, nodedir=None, bootsh="boot.sh", start=True, image=None): """ - Create a CoreNode instance. + Create a LxcNode instance. :param core.emulator.session.Session session: core session instance :param int _id: object id @@ -146,20 +147,12 @@ class LxcNode(CoreNode): :param str nodedir: node directory :param str bootsh: boot shell to use :param bool start: start flag + :param str image: image to start container with """ - 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)) if image is None: image = "ubuntu" - self.client = LxdClient(self.name, image) - self.pid = None - self.up = False - self.lock = threading.RLock() - self._mounts = [] - self.bootsh = bootsh - if start: - self.startup() + self.image = image + super(LxcNode, self).__init__(session, _id, name, nodedir, bootsh, start) def alive(self): """ @@ -180,6 +173,7 @@ class LxcNode(CoreNode): if self.up: raise ValueError("starting a node that is already up") self.makenodedir() + self.client = LxdClient(self.name, self.image) self.pid = self.client.create_container() self.up = True @@ -207,7 +201,7 @@ class LxcNode(CoreNode): :return: exit status for command :rtype: int """ - return self.client.run_cmd(args, wait) + return self.client.cmd(args, wait) def cmd_output(self, args): """ @@ -217,7 +211,7 @@ class LxcNode(CoreNode): :return: exit status and combined stdout and stderr :rtype: tuple[int, str] """ - return self.client.run_cmd_output(args) + return self.client.cmd_output(args) def check_cmd(self, args): """ @@ -228,11 +222,17 @@ class LxcNode(CoreNode): :rtype: str :raises CoreCommandError: when a non-zero exit status occurs """ - status, output = self.client.run_cmd_output(args) + status, output = self.client.cmd_output(args) if status: raise CoreCommandError(status, args, output) return output + def network_cmd(self, args): + if not self.up: + logging.debug("node down, not running network command: %s", args) + return 0 + return self.check_cmd(args) + def termcmdstring(self, sh="/bin/sh"): """ Create a terminal command string. @@ -240,7 +240,7 @@ class LxcNode(CoreNode): :param str sh: shell to execute command in :return: str """ - return "" + return "lxc exec {name} -- bash".format(name=self.name) def privatedir(self, path): """ @@ -251,9 +251,7 @@ class LxcNode(CoreNode): """ logging.info("creating node dir: %s", path) args = "mkdir -p {path}".format(path=path) - status, output = self.client.run_cmd_output(args) - if status: - raise CoreCommandError(status, args, output) + self.check_cmd(args) def mount(self, source, target): """ @@ -265,7 +263,7 @@ class LxcNode(CoreNode): :raises CoreCommandError: when a non-zero exit status occurs """ logging.info("mounting source(%s) target(%s)", source, target) - raise Exception("you found a lxc node") + raise Exception("not supported") def nodefile(self, filename, contents, mode=0o644): """ @@ -295,37 +293,9 @@ class LxcNode(CoreNode): :return: nothing """ logging.info("node file copy file(%s) source(%s) mode(%s)", filename, srcfilename, mode) - raise Exception("you found a lxc node") + raise Exception("not supported") - 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) - - # delay required for lxc nodes - time.sleep(0.5) - - for address in utils.make_tuple(addrlist): - self.addaddr(ifindex, address) - - self.ifup(ifindex) - return ifindex + def addnetif(self, netif, ifindex): + super(LxcNode, self).addnetif(netif, ifindex) + # adding small delay to allow time for adding addresses to work correctly + time.sleep(0.5) diff --git a/daemon/examples/docker/docker2core.py b/daemon/examples/docker/docker2core.py index e7a626ec..15d1bbe7 100644 --- a/daemon/examples/docker/docker2core.py +++ b/daemon/examples/docker/docker2core.py @@ -13,7 +13,7 @@ if __name__ == "__main__": try: prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16") - options = NodeOptions(image="ubuntu:ifconfig") + options = NodeOptions(model=None, image="ubuntu") # create node one node_one = session.add_node(_type=NodeTypes.DOCKER, node_options=options) diff --git a/daemon/examples/docker/docker2docker.py b/daemon/examples/docker/docker2docker.py index 52bca1ce..32fcc6d6 100644 --- a/daemon/examples/docker/docker2docker.py +++ b/daemon/examples/docker/docker2docker.py @@ -15,7 +15,7 @@ if __name__ == "__main__": # create nodes and interfaces try: prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16") - options = NodeOptions(image="ubuntu:ifconfig") + options = NodeOptions(model=None, image="ubuntu") # create node one node_one = session.add_node(_type=NodeTypes.DOCKER, node_options=options) diff --git a/daemon/examples/docker/switch.py b/daemon/examples/docker/switch.py index 6204a4cb..a4615d4a 100644 --- a/daemon/examples/docker/switch.py +++ b/daemon/examples/docker/switch.py @@ -14,7 +14,7 @@ if __name__ == "__main__": try: prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16") - options = NodeOptions(image="ubuntu:ifconfig") + options = NodeOptions(model=None, image="ubuntu") # create switch switch = session.add_node(_type=NodeTypes.SWITCH)