Add initial support for showing and querying nested containers. This is done through a new --nesting argument to lxc-ls and uses lxc-attach to go look for sub-containers.
Known limitations include the dependency on setns support for the PID and NETWORK namespaces and the assumption that LXCPATH for the sub-containers matches that of the host. Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- doc/lxc-ls.sgml.in | 12 +++++++ src/lxc/lxc-ls | 75 ++++++++++++++++++++++++++++++++++----- src/python-lxc/lxc/__init__.py.in | 15 ++------ 3 files changed, 81 insertions(+), 21 deletions(-) diff --git a/doc/lxc-ls.sgml.in b/doc/lxc-ls.sgml.in index 83618e5..877404d 100644 --- a/doc/lxc-ls.sgml.in +++ b/doc/lxc-ls.sgml.in @@ -56,6 +56,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA <arg choice="opt">--stopped</arg> <arg choice="opt">--fancy</arg> <arg choice="opt">--fancy-format</arg> + <arg choice="opt">--nesting</arg> <arg choice="opt">filter</arg> </cmdsynopsis> </refsynopsisdiv> @@ -152,6 +153,17 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA <varlistentry> <term> + <option><optional>--nesting</optional></option> + </term> + <listitem> + <para> + Show nested containers. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> <option><optional>filter</optional></option> </term> <listitem> diff --git a/src/lxc/lxc-ls b/src/lxc/lxc-ls index 92a4e53..6468890 100644 --- a/src/lxc/lxc-ls +++ b/src/lxc/lxc-ls @@ -31,9 +31,11 @@ warnings.filterwarnings("ignore", "The python-lxc API isn't yet stable") import argparse import gettext +import json import lxc import os import re +import subprocess import sys _ = gettext.gettext @@ -85,6 +87,23 @@ def getTerminalSize(): return int(cr[1]), int(cr[0]) + +def getSubContainers(container, lxcpath): + attach = ['lxc-attach', '-R', '-s', 'NETWORK|PID', '-n', container, + '--', sys.argv[0], "--nesting"] + + with open(os.devnull, "w") as fd: + newenv = dict(os.environ) + newenv['NESTED'] = "/proc/1/root/%s" % lxcpath + sp = subprocess.Popen(attach, stderr=fd, stdout=subprocess.PIPE, + env=newenv, universal_newlines=True) + sp.wait() + out = sp.stdout.read() + if out: + return json.loads(out) + return None + + # Begin parsing the command line parser = argparse.ArgumentParser(description=_("LXC: List containers"), formatter_class=argparse.RawTextHelpFormatter) @@ -93,7 +112,8 @@ parser.add_argument("-1", dest="one", action="store_true", help=_("list one container per line (default when piped)")) parser.add_argument("-P", "--lxcpath", dest="lxcpath", metavar="PATH", - help=_("Use specified container path"), default=None) + help=_("Use specified container path"), + default=lxc.default_config_path) parser.add_argument("--active", action="store_true", help=_("list only active containers " @@ -114,6 +134,9 @@ parser.add_argument("--fancy", action="store_true", parser.add_argument("--fancy-format", type=str, default="name,state,ipv4,ipv6", help=_("comma separated list of fields to show")) +parser.add_argument("--nesting", dest="nesting", action="store_true", + help=_("show nested containers")) + parser.add_argument("filter", metavar='FILTER', type=str, nargs="?", help=_("regexp to be applied on the container list")) @@ -129,6 +152,9 @@ if args.active: if not sys.stdout.isatty(): args.one = True +# Set the lookup path for the containers +lxcpath = os.environ.get('NESTED', args.lxcpath) + # Turn args.fancy_format into a list args.fancy_format = args.fancy_format.strip().split(",") @@ -141,7 +167,7 @@ if not os.geteuid() == 0 and (args.fancy or args.state): # List of containers, stored as dictionaries containers = [] -for container_name in lxc.list_containers(config_path=args.lxcpath): +for container_name in lxc.list_containers(config_path=lxcpath): entry = {} entry['name'] = container_name @@ -150,7 +176,7 @@ for container_name in lxc.list_containers(config_path=args.lxcpath): continue # Return before grabbing the object (non-root) - if not args.state and not args.fancy: + if not args.state and not args.fancy and not args.nesting: containers.append(entry) continue @@ -161,28 +187,47 @@ for container_name in lxc.list_containers(config_path=args.lxcpath): continue # Nothing more is needed if we're not printing some fancy output - if not args.fancy: + if not args.fancy and not args.nesting: containers.append(entry) continue # Some extra field we may want - if 'state' in args.fancy_format: + if 'state' in args.fancy_format or args.nesting: entry['state'] = container.state - if 'pid' in args.fancy_format: + + if 'pid' in args.fancy_format or args.nesting: entry['pid'] = "-" if container.init_pid != -1: entry['pid'] = str(container.init_pid) # Get the IPs for protocol in ('ipv4', 'ipv6'): - if protocol in args.fancy_format: + if protocol in args.fancy_format or args.nesting: entry[protocol] = "-" ips = container.get_ips(protocol=protocol, timeout=1) if ips: entry[protocol] = ", ".join(ips) + # Append the container containers.append(entry) + # Nested containers + if args.nesting: + sub = getSubContainers(container_name, args.lxcpath) + if sub: + for entry in sub: + if 'nesting_parent' not in entry: + entry['nesting_parent'] = [] + entry['nesting_parent'].insert(0, container_name) + entry['nesting_real_name'] = entry.get('nesting_real_name', + entry['name']) + entry['name'] = "%s -> %s" % (container_name, entry['name']) + containers += sub + +# Deal with json output: +if 'NESTED' in os.environ: + print(json.dumps(containers)) + sys.exit(0) # Print the list ## Standard list with one entry per line @@ -226,7 +271,12 @@ if args.fancy: for container in containers: for field in args.fancy_format: - if len(container[field]) > field_maxlength[field]: + if field == 'name' and 'nesting_real_name' in container: + fieldlen = len(" " * ((len(container['nesting_parent']) - 1) + * 4) + " \_ " + container['nesting_real_name']) + if fieldlen > field_maxlength[field]: + field_maxlength[field] = fieldlen + elif len(container[field]) > field_maxlength[field]: field_maxlength[field] = len(container[field]) # Generate the line format string based on the maximum length and @@ -250,5 +300,12 @@ if args.fancy: # Print the entries for container in sorted(containers, key=lambda container: container['name']): - fields = [container[field] for field in args.fancy_format] + fields = [] + for field in args.fancy_format: + if field == 'name' and 'nesting_real_name' in container: + prefix = " " * ((len(container['nesting_parent']) - 1) * 4) + fields.append(prefix + " \_ " + container['nesting_real_name']) + else: + fields.append(container[field]) + print(line_format.format(fields=fields)) diff --git a/src/python-lxc/lxc/__init__.py.in b/src/python-lxc/lxc/__init__.py.in index e262c23..f1848f2 100644 --- a/src/python-lxc/lxc/__init__.py.in +++ b/src/python-lxc/lxc/__init__.py.in @@ -337,18 +337,9 @@ class Container(_lxc.Container): Returns the list of IP addresses for the container. """ - if not self.defined or not self.running: + if not self.running: return False - try: - os.makedirs("/run/netns") - except: - pass - - path = tempfile.mktemp(dir="/run/netns") - - os.symlink("/proc/%s/ns/net" % self.init_pid, path) - ips = [] count = 0 @@ -356,7 +347,8 @@ class Container(_lxc.Container): if count != 0: time.sleep(1) - base_cmd = ["ip", "netns", "exec", path.split("/")[-1], "ip"] + base_cmd = ["lxc-attach", "-s", "NETWORK", "-n", self.name, "--", + "ip"] # Get IPv6 if protocol in ("ipv6", None): @@ -397,7 +389,6 @@ class Container(_lxc.Container): count += 1 - os.remove(path) return ips def get_keys(self, key=None): -- 1.8.1.2 ------------------------------------------------------------------------------ Everyone hates slow websites. So do we. Make your web apps faster with AppDynamics Download AppDynamics Lite for free today: http://p.sf.net/sfu/appdyn_d2d_feb _______________________________________________ Lxc-devel mailing list Lxc-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/lxc-devel