From 5f282bb6950e575c8d6c9f1c920c0072dbf376a1 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 14 Oct 2019 14:28:18 -0700 Subject: [PATCH] updates to lxd/docker to work with net_cmd/node_net_cmd --- daemon/core/nodes/base.py | 20 ++++++-- daemon/core/nodes/client.py | 11 +---- daemon/core/nodes/docker.py | 89 ++++++++++++++++++++++++---------- daemon/core/nodes/lxd.py | 70 +++++++++++++++----------- daemon/examples/lxd/lxd2lxd.py | 4 +- 5 files changed, 122 insertions(+), 72 deletions(-) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 85e25074..f263d0f3 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -452,14 +452,24 @@ class CoreNode(CoreNodeBase): self._mounts = [] self.bootsh = bootsh - if session.options.get_config("ovs") == "True": - self.node_net_client = OvsNetClient(self.node_net_cmd) - else: - self.node_net_client = LinuxNetClient(self.node_net_cmd) + use_ovs = session.options.get_config("ovs") == "True" + self.node_net_client = self.create_node_net_client(use_ovs) if start: self.startup() + def create_node_net_client(self, use_ovs): + """ + Create a client for running network orchestration commands. + + :param bool use_ovs: True to use OVS bridges, False for Linux bridge + :return: network client + """ + if use_ovs: + return OvsNetClient(self.node_net_cmd) + else: + return LinuxNetClient(self.node_net_cmd) + def alive(self): """ Check if the node is alive. @@ -584,7 +594,7 @@ class CoreNode(CoreNodeBase): :param str sh: shell to execute command in :return: str """ - return self.client.termcmdstring(sh) + return self.client.create_cmd(sh) def privatedir(self, path): """ diff --git a/daemon/core/nodes/client.py b/daemon/core/nodes/client.py index e09c72fa..632e12bc 100644 --- a/daemon/core/nodes/client.py +++ b/daemon/core/nodes/client.py @@ -4,7 +4,7 @@ over a control channel to the vnoded process running in a network namespace. The control channel can be accessed via calls using the vcmd shell. """ -from core import constants, utils +from core import utils from core.constants import VCMD_BIN @@ -66,12 +66,3 @@ class VnodeClient(object): self._verify_connection() args = self.create_cmd(args) return utils.check_cmd(args, wait=wait) - - def termcmdstring(self, sh="/bin/sh"): - """ - Create a terminal command string. - - :param str sh: shell to execute command in - :return: str - """ - return "%s -c %s -- %s" % (constants.VCMD_BIN, self.ctrlchnlname, sh) diff --git a/daemon/core/nodes/docker.py b/daemon/core/nodes/docker.py index 416b31d1..e465a768 100644 --- a/daemon/core/nodes/docker.py +++ b/daemon/core/nodes/docker.py @@ -1,21 +1,25 @@ import json import logging import os +from tempfile import NamedTemporaryFile from core import utils +from core.emulator import distributed from core.emulator.enumerations import NodeTypes from core.errors import CoreCommandError from core.nodes.base import CoreNode +from core.nodes.netclient import LinuxNetClient, OvsNetClient class DockerClient(object): - def __init__(self, name, image): + def __init__(self, name, image, run): self.name = name self.image = image + self.run = run self.pid = None def create_container(self): - utils.check_cmd( + self.run( "docker run -td --init --net=none --hostname {name} --name {name} " "--sysctl net.ipv6.conf.all.disable_ipv6=0 " "{image} /bin/bash".format( @@ -27,7 +31,7 @@ class DockerClient(object): def get_info(self): args = "docker inspect {name}".format(name=self.name) - output = utils.check_cmd(args) + output = self.run(args) data = json.loads(output) if not data: raise CoreCommandError( @@ -43,22 +47,24 @@ class DockerClient(object): return False def stop_container(self): - utils.check_cmd("docker rm -f {name}".format( + self.run("docker rm -f {name}".format( name=self.name )) def check_cmd(self, cmd): - if isinstance(cmd, list): - cmd = " ".join(cmd) logging.info("docker cmd output: %s", cmd) return utils.check_cmd("docker exec {name} {cmd}".format( name=self.name, cmd=cmd )) + def create_ns_cmd(self, cmd): + return "nsenter -t {pid} -u -i -p -n {cmd}".format( + pid=self.pid, + cmd=cmd + ) + def ns_cmd(self, cmd, wait): - if isinstance(cmd, list): - cmd = " ".join(cmd) args = "nsenter -t {pid} -u -i -p -n {cmd}".format( pid=self.pid, cmd=cmd @@ -67,7 +73,7 @@ class DockerClient(object): def get_pid(self): args = "docker inspect -f '{{{{.State.Pid}}}}' {name}".format(name=self.name) - output = utils.check_cmd(args) + output = self.run(args) self.pid = output logging.debug("node(%s) pid: %s", self.name, self.pid) return output @@ -78,7 +84,7 @@ class DockerClient(object): name=self.name, destination=destination ) - return utils.check_cmd(args) + return self.run(args) class DockerNode(CoreNode): @@ -101,6 +107,12 @@ class DockerNode(CoreNode): self.image = image super(DockerNode, self).__init__(session, _id, name, nodedir, bootsh, start) + def create_node_net_client(self, use_ovs): + if use_ovs: + return OvsNetClient(self.nsenter_cmd) + else: + return LinuxNetClient(self.nsenter_cmd) + def alive(self): """ Check if the node is alive. @@ -122,7 +134,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.client = DockerClient(self.name, self.image, self.net_cmd) self.pid = self.client.create_container() self.up = True @@ -141,12 +153,13 @@ class DockerNode(CoreNode): self.client.stop_container() self.up = False - def node_net_cmd(self, args, wait=True): - if not self.up: - logging.debug("node down, not running network command: %s", args) - return "" - - return self.client.ns_cmd(args, wait) + def nsenter_cmd(self, args, wait=True): + if self.server is None: + args = self.client.create_ns_cmd(args) + return utils.check_cmd(args, wait=wait) + else: + args = self.client.create_ns_cmd(args) + return distributed.remote_cmd(self.server, args, wait=wait) def termcmdstring(self, sh="/bin/sh"): """ @@ -166,7 +179,7 @@ class DockerNode(CoreNode): """ logging.debug("creating node dir: %s", path) args = "mkdir -p {path}".format(path=path) - self.client.check_cmd(args) + self.node_net_cmd(args) def mount(self, source, target): """ @@ -189,13 +202,24 @@ class DockerNode(CoreNode): :param int mode: mode for file :return: nothing """ - logging.debug("node dir(%s) ctrlchannel(%s)", self.nodedir, self.ctrlchnlname) logging.debug("nodefile filename(%s) mode(%s)", filename, mode) - file_path = os.path.join(self.nodedir, filename) - with open(file_path, "w") as f: - os.chmod(f.name, mode) - f.write(contents) - self.client.copy_file(file_path, filename) + directory = os.path.dirname(filename) + temp = NamedTemporaryFile(delete=False) + temp.write(contents.encode("utf-8")) + temp.close() + + if directory: + self.node_net_cmd("mkdir -m %o -p %s" % (0o755, directory)) + if self.server is not None: + distributed.remote_put(self.server, temp.name, temp.name) + self.client.copy_file(temp.name, filename) + self.node_net_cmd("chmod %o %s" % (mode, filename)) + if self.server is not None: + self.net_cmd("rm -f %s" % temp.name) + os.unlink(temp.name) + logging.debug( + "node(%s) added file: %s; mode: 0%o", self.name, filename, mode + ) def nodefilecopy(self, filename, srcfilename, mode=None): """ @@ -207,5 +231,18 @@ class DockerNode(CoreNode): :param int mode: mode to copy to :return: nothing """ - logging.info("node file copy file(%s) source(%s) mode(%s)", filename, srcfilename, mode) - raise Exception("not supported") + logging.info( + "node file copy file(%s) source(%s) mode(%s)", filename, srcfilename, mode + ) + directory = os.path.dirname(filename) + self.node_net_cmd("mkdir -p %s" % directory) + + if self.server is None: + source = srcfilename + else: + temp = NamedTemporaryFile(delete=False) + source = temp.name + distributed.remote_put(self.server, source, temp.name) + + self.client.copy_file(source, filename) + self.node_net_cmd("chmod %o %s" % (mode, filename)) diff --git a/daemon/core/nodes/lxd.py b/daemon/core/nodes/lxd.py index 96b107f8..afd36db2 100644 --- a/daemon/core/nodes/lxd.py +++ b/daemon/core/nodes/lxd.py @@ -2,30 +2,31 @@ import json import logging import os import time +from tempfile import NamedTemporaryFile from core import utils +from core.emulator import distributed from core.emulator.enumerations import NodeTypes from core.errors import CoreCommandError from core.nodes.base import CoreNode class LxdClient(object): - def __init__(self, name, image): + def __init__(self, name, image, run): self.name = name self.image = image + self.run = run self.pid = None def create_container(self): - utils.check_cmd( - "lxc launch {image} {name}".format(name=self.name, image=self.image) - ) + self.run("lxc launch {image} {name}".format(name=self.name, image=self.image)) data = self.get_info() self.pid = data["state"]["pid"] return self.pid def get_info(self): args = "lxc list {name} --format json".format(name=self.name) - output = utils.check_cmd(args) + output = self.run(args) data = json.loads(output) if not data: raise CoreCommandError( @@ -41,20 +42,16 @@ class LxdClient(object): return False def stop_container(self): - utils.check_cmd("lxc delete --force {name}".format(name=self.name)) + self.run("lxc delete --force {name}".format(name=self.name)) def create_cmd(self, cmd): return "lxc exec -nT {name} -- {cmd}".format(name=self.name, cmd=cmd) - def check_cmd(self, cmd, wait=True): - args = self.create_cmd(cmd) - return utils.check_cmd(args, wait=wait) - def create_ns_cmd(self, cmd): return "nsenter -t {pid} -m -u -i -p -n {cmd}".format(pid=self.pid, cmd=cmd) - def ns_check_cmd(self, cmd, wait=True): - args = self.create_ns_cmd(cmd) + def check_cmd(self, cmd, wait=True): + args = self.create_cmd(cmd) return utils.check_cmd(args, wait=wait) def copy_file(self, source, destination): @@ -64,7 +61,7 @@ class LxdClient(object): args = "lxc file push {source} {name}/{destination}".format( source=source, name=self.name, destination=destination ) - utils.check_cmd(args) + self.run(args) class LxcNode(CoreNode): @@ -115,7 +112,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.client = LxdClient(self.name, self.image, self.net_cmd) self.pid = self.client.create_container() self.up = True @@ -134,12 +131,6 @@ class LxcNode(CoreNode): self.client.stop_container() self.up = False - def node_net_cmd(self, args, wait=True): - if not self.up: - logging.debug("node down, not running network command: %s", args) - return "" - return self.client.check_cmd(args, wait) - def termcmdstring(self, sh="/bin/sh"): """ Create a terminal command string. @@ -147,7 +138,7 @@ class LxcNode(CoreNode): :param str sh: shell to execute command in :return: str """ - return "lxc exec {name} -- bash".format(name=self.name) + return "lxc exec {name} -- {sh}".format(name=self.name, sh=sh) def privatedir(self, path): """ @@ -158,7 +149,7 @@ class LxcNode(CoreNode): """ logging.info("creating node dir: %s", path) args = "mkdir -p {path}".format(path=path) - return self.client.check_cmd(args) + return self.node_net_cmd(args) def mount(self, source, target): """ @@ -181,13 +172,23 @@ class LxcNode(CoreNode): :param int mode: mode for file :return: nothing """ - logging.debug("node dir(%s) ctrlchannel(%s)", self.nodedir, self.ctrlchnlname) logging.debug("nodefile filename(%s) mode(%s)", filename, mode) - file_path = os.path.join(self.nodedir, filename) - with open(file_path, "w") as f: - os.chmod(f.name, mode) - f.write(contents) - self.client.copy_file(file_path, filename) + + directory = os.path.dirname(filename) + temp = NamedTemporaryFile(delete=False) + temp.write(contents.encode("utf-8")) + temp.close() + + if directory: + self.node_net_cmd("mkdir -m %o -p %s" % (0o755, directory)) + if self.server is not None: + distributed.remote_put(self.server, temp.name, temp.name) + self.client.copy_file(temp.name, filename) + self.node_net_cmd("chmod %o %s" % (mode, filename)) + if self.server is not None: + self.net_cmd("rm -f %s" % temp.name) + os.unlink(temp.name) + logging.debug("node(%s) added file: %s; mode: 0%o", self.name, filename, mode) def nodefilecopy(self, filename, srcfilename, mode=None): """ @@ -202,7 +203,18 @@ class LxcNode(CoreNode): logging.info( "node file copy file(%s) source(%s) mode(%s)", filename, srcfilename, mode ) - raise Exception("not supported") + directory = os.path.dirname(filename) + self.node_net_cmd("mkdir -p %s" % directory) + + if self.server is None: + source = srcfilename + else: + temp = NamedTemporaryFile(delete=False) + source = temp.name + distributed.remote_put(self.server, source, temp.name) + + self.client.copy_file(source, filename) + self.node_net_cmd("chmod %o %s" % (mode, filename)) def addnetif(self, netif, ifindex): super(LxcNode, self).addnetif(netif, ifindex) diff --git a/daemon/examples/lxd/lxd2lxd.py b/daemon/examples/lxd/lxd2lxd.py index e0ff13a3..4f27de95 100644 --- a/daemon/examples/lxd/lxd2lxd.py +++ b/daemon/examples/lxd/lxd2lxd.py @@ -5,7 +5,7 @@ from core.emulator.emudata import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes, NodeTypes if __name__ == "__main__": - logging.basicConfig(level=logging.DEBUG) + logging.basicConfig(level=logging.INFO) coreemu = CoreEmu() session = coreemu.create_session() @@ -14,7 +14,7 @@ if __name__ == "__main__": # create nodes and interfaces try: prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16") - options = NodeOptions(image="ubuntu") + options = NodeOptions(image="ubuntu:18.04") # create node one node_one = session.add_node(_type=NodeTypes.LXC, node_options=options)