[lxc-devel] [PATCH] lxc-ls: Fix support of --nesting for unpriv

Stéphane Graber stgraber at ubuntu.com
Tue Mar 4 21:43:58 UTC 2014


On Tue, Mar 04, 2014 at 04:41:14PM -0500, Stéphane Graber wrote:
> This reworks the way lxc-ls works in nesting mode. In the past it'd use
> attach_wait's subprocess function to call itself in the container's
> namespace, carefully only attaching to the namespaces it needed.
> 
> This works great for system containers but not so much as soon as you
> also need to attach to userns. Instead this fix moves all of the
> container listing code into a get_containers function (hence the massive
> diff, sorry), this function is then called recursively.
> 
> For running containers, the function is called through attach_wait
> inside the container's namespace, for stopped container, the function is
> simply called recursively with a base path (container's rootfs) in an
> attempt to find containers that way.
> Communication between the parent lxc-ls and the child lxc-ls is done
> through a temporary fd and serialized state using json (similar to what
> was done using stdout in the previous implementation).
> 
> As get_global_config_item unfortunately caches the values, there's no
> easy way to figure out what the lxcpath should be for a root container
> when running as non-root, so just use @LXCPATH@ for now and have
> python do the parsing itself.
> 
> As a result, the following things now work as expected:
>  - listing nested unprivileged containers (root containers inside unpriv)
>  - listing nested containers when they're not running
>  - filtering containers in nesting mode (only the first level is filtered)
>  - copy with invalid config (used to traceback)
> 

I tested the above in the following scenarios:
 nested as root without running containers on 3.13 => works
 nested as root with running containers on 3.13 => works
 nested as root without running containers on 3.2 => fails with clear error
 nested as root with running containers on 3.2 => fails with clear error
 nested as root without running containers using custom lxcpath => works
 nested as root with running containers using custom lxcpath => works

 nested as user without running containers => works
 nested as user with running containers => works
 nested as user without running containers using custom lxcpath => works
 nested as user with running containers using custom lxcpath => works

Again, sorry for the massive diff, between shifting most of the code in
a function and renaming the file to lxc-ls.in, that was enough to
confuse git enough to have it think it's a whole new piece of code...

> Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
> ---
>  configure.ac      |   1 +
>  src/lxc/lxc-ls    | 411 ------------------------------------------------
>  src/lxc/lxc-ls.in | 463 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 464 insertions(+), 411 deletions(-)
>  delete mode 100755 src/lxc/lxc-ls
>  create mode 100755 src/lxc/lxc-ls.in
> 
> diff --git a/configure.ac b/configure.ac
> index 47e3869..e352840 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -696,6 +696,7 @@ AC_CONFIG_FILES([
>  	src/Makefile
>  	src/lxc/Makefile
>  	src/lxc/lxc-checkconfig
> +	src/lxc/lxc-ls
>  	src/lxc/lxc-start-ephemeral
>  	src/lxc/legacy/lxc-ls
>  	src/lxc/lxc.functions
> diff --git a/src/lxc/lxc-ls b/src/lxc/lxc-ls
> deleted file mode 100755
> index 065b3e8..0000000
> --- a/src/lxc/lxc-ls
> +++ /dev/null
> @@ -1,411 +0,0 @@
> -#!/usr/bin/python3
> -#
> -# lxc-ls: List containers
> -#
> -# This python implementation is based on the work done in the original
> -# shell implementation done by Serge Hallyn in Ubuntu (and other contributors)
> -#
> -# (C) Copyright Canonical Ltd. 2012
> -#
> -# Authors:
> -# Stéphane Graber <stgraber at ubuntu.com>
> -#
> -# This library is free software; you can redistribute it and/or
> -# modify it under the terms of the GNU Lesser General Public
> -# License as published by the Free Software Foundation; either
> -# version 2.1 of the License, or (at your option) any later version.
> -#
> -# This library is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> -# Lesser General Public License for more details.
> -#
> -# You should have received a copy of the GNU Lesser General Public
> -# License along with this library; if not, write to the Free Software
> -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> -#
> -
> -import argparse
> -import gettext
> -import json
> -import lxc
> -import os
> -import re
> -import tempfile
> -import sys
> -
> -_ = gettext.gettext
> -gettext.textdomain("lxc-ls")
> -
> -
> -# Functions used later on
> -def batch(iterable, cols=1):
> -    import math
> -
> -    length = len(iterable)
> -    lines = math.ceil(length / cols)
> -
> -    for line in range(lines):
> -        fields = []
> -        for col in range(cols):
> -            index = line + (col * lines)
> -            if index < length:
> -                fields.append(iterable[index])
> -        yield fields
> -
> -
> -def getTerminalSize():
> -    import os
> -    env = os.environ
> -
> -    def ioctl_GWINSZ(fd):
> -        try:
> -            import fcntl
> -            import termios
> -            import struct
> -            cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ,
> -                               '1234'))
> -            return cr
> -        except:
> -            return
> -
> -    cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
> -    if not cr:
> -        try:
> -            fd = os.open(os.ctermid(), os.O_RDONLY)
> -            cr = ioctl_GWINSZ(fd)
> -            os.close(fd)
> -        except:
> -            pass
> -
> -    if not cr:
> -        cr = (env.get('LINES', 25), env.get('COLUMNS', 80))
> -
> -    return int(cr[1]), int(cr[0])
> -
> -
> -def getSubContainers(container):
> -    with open(os.devnull, "w") as fd:
> -        fdnum, path = tempfile.mkstemp()
> -        os.remove(path)
> -
> -        fd = os.fdopen(fdnum)
> -
> -        container.attach_wait(
> -            lxc.attach_run_command, [sys.argv[0], "--nesting"],
> -            attach_flags=(lxc.LXC_ATTACH_REMOUNT_PROC_SYS),
> -            namespaces=(lxc.CLONE_NEWNET + lxc.CLONE_NEWPID),
> -            extra_env_vars=["NESTED=/proc/1/root/%s" %
> -                            lxc.default_config_path],
> -            stdout=fd)
> -
> -        fd.seek(0)
> -        out = fd.read()
> -        fd.close()
> -        if out:
> -            return json.loads(out)
> -    return None
> -
> -# Constants
> -FIELDS = ("name", "state", "ipv4", "ipv6", "autostart", "pid",
> -          "memory", "ram", "swap")
> -
> -# Begin parsing the command line
> -parser = argparse.ArgumentParser(description=_("LXC: List containers"),
> -                                 formatter_class=argparse.RawTextHelpFormatter)
> -
> -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=lxc.default_config_path)
> -
> -parser.add_argument("--active", action="store_true",
> -                    help=_("list only active containers"))
> -
> -parser.add_argument("--frozen", dest="state", action="append_const",
> -                    const="FROZEN", help=_("list only frozen containers"))
> -
> -parser.add_argument("--running", dest="state", action="append_const",
> -                    const="RUNNING", help=_("list only running containers"))
> -
> -parser.add_argument("--stopped", dest="state", action="append_const",
> -                    const="STOPPED", help=_("list only stopped containers"))
> -
> -parser.add_argument("-f", "--fancy", action="store_true",
> -                    help=_("use fancy output"))
> -
> -parser.add_argument("-F", "--fancy-format", type=str,
> -                    default="name,state,ipv4,ipv6,autostart",
> -                    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"))
> -
> -args = parser.parse_args()
> -
> -# --active is the same as --running --frozen
> -if args.active:
> -    if not args.state:
> -        args.state = []
> -    args.state += ["RUNNING", "FROZEN", "UNKNOWN"]
> -
> -# If the output is piped, default to --one
> -if not sys.stdout.isatty():
> -    args.one = True
> -
> -# Set the lookup path for the containers
> -# This value will contain the full path for a nested containers
> -# use args.lxcpath if you need the value relative to the container
> -nest_lxcpath = os.environ.get('NESTED', args.lxcpath)
> -
> -# Turn args.fancy_format into a list
> -args.fancy_format = args.fancy_format.strip().split(",")
> -
> -if set(args.fancy_format) - set(FIELDS):
> -    parser.error(_("Invalid field(s): %s" %
> -                 ", ".join(list(set(args.fancy_format) - set(FIELDS)))))
> -
> -# Basic checks
> -## Check for setns
> -SUPPORT_SETNS = os.path.exists("/proc/self/ns")
> -SUPPORT_SETNS_NET = False
> -SUPPORT_SETNS_PID = False
> -if SUPPORT_SETNS:
> -    SUPPORT_SETNS_NET = os.path.exists("/proc/self/ns/net")
> -    SUPPORT_SETNS_PID = os.path.exists("/proc/self/ns/pid")
> -
> -## Nesting requires setns to pid and net ns
> -if args.nesting:
> -    if not SUPPORT_SETNS:
> -        parser.error(_("Showing nested containers requires setns support "
> -                       "which your kernel doesn't support."))
> -
> -    if not SUPPORT_SETNS_NET:
> -        parser.error(_("Showing nested containers requires setns to the "
> -                       "network namespace which your kernel doesn't support."))
> -
> -    if not SUPPORT_SETNS_PID:
> -        parser.error(_("Showing nested containers requires setns to the "
> -                       "PID namespace which your kernel doesn't support."))
> -
> -# List of containers, stored as dictionaries
> -containers = []
> -for container_name in lxc.list_containers(config_path=nest_lxcpath):
> -    entry = {}
> -    entry['name'] = container_name
> -
> -    # Apply filter
> -    if args.filter and not re.match(args.filter, container_name):
> -        continue
> -
> -    # Return before grabbing the object (non-root)
> -    if not args.state and not args.fancy and not args.nesting:
> -        containers.append(entry)
> -        continue
> -
> -    container = lxc.Container(container_name, args.lxcpath)
> -
> -    if 'NESTED' in os.environ:
> -        container.load_config(os.path.join(nest_lxcpath, container_name,
> -                                           "config"))
> -
> -    if container.controllable:
> -        state = container.state
> -    else:
> -        state = 'UNKNOWN'
> -
> -    # Filter by status
> -    if args.state and state not in args.state:
> -        continue
> -
> -    # Nothing more is needed if we're not printing some fancy output
> -    if not args.fancy and not args.nesting:
> -        containers.append(entry)
> -        continue
> -
> -    # Some extra field we may want
> -    if 'state' in args.fancy_format or args.nesting:
> -        entry['state'] = state
> -
> -    if 'pid' in args.fancy_format or args.nesting:
> -        entry['pid'] = "-"
> -        if state == 'UNKNOWN':
> -            entry['pid'] = state
> -        elif container.init_pid != -1:
> -            entry['pid'] = str(container.init_pid)
> -
> -    if 'autostart' in args.fancy_format or args.nesting:
> -        entry['autostart'] = "NO"
> -        try:
> -            if container.get_config_item("lxc.start.auto") == "1":
> -                entry['autostart'] = "YES"
> -
> -                groups = container.get_config_item("lxc.group")
> -                if len(groups) > 0:
> -                    entry['autostart'] = "YES (%s)" % ", ".join(groups)
> -        except KeyError:
> -            pass
> -
> -    if 'memory' in args.fancy_format or \
> -       'ram' in args.fancy_format or \
> -       'swap' in args.fancy_format:
> -
> -        if container.running:
> -            try:
> -                memory_total = int(container.get_cgroup_item(
> -                    "memory.usage_in_bytes"))
> -            except:
> -                memory_total = 0
> -
> -            try:
> -                memory_swap = int(container.get_cgroup_item(
> -                    "memory.memsw.usage_in_bytes"))
> -            except:
> -                memory_swap = 0
> -        else:
> -            memory_total = 0
> -            memory_swap = 0
> -
> -    if 'memory' in args.fancy_format:
> -        if container.running:
> -            entry['memory'] = "%sMB" % round(memory_total / 1048576, 2)
> -        else:
> -            entry['memory'] = "-"
> -
> -    if 'ram' in args.fancy_format:
> -        if container.running:
> -            entry['ram'] = "%sMB" % round(
> -                (memory_total - memory_swap) / 1048576, 2)
> -        else:
> -            entry['ram'] = "-"
> -
> -    if 'swap' in args.fancy_format:
> -        if container.running:
> -            entry['swap'] = "%sMB" % round(memory_swap / 1048576, 2)
> -        else:
> -            entry['swap'] = "-"
> -
> -    # Get the IPs
> -    for family, protocol in {'inet': 'ipv4', 'inet6': 'ipv6'}.items():
> -        if protocol in args.fancy_format or args.nesting:
> -            entry[protocol] = "-"
> -
> -            if state == 'UNKNOWN':
> -                entry[protocol] = state
> -                continue
> -
> -            if container.running:
> -                if not SUPPORT_SETNS_NET:
> -                    entry[protocol] = 'UNKNOWN'
> -                    continue
> -
> -                ips = container.get_ips(family=family)
> -                if ips:
> -                    entry[protocol] = ", ".join(ips)
> -
> -    # Append the container
> -    containers.append(entry)
> -
> -    # Nested containers
> -    if args.nesting and container.state == "RUNNING":
> -        sub = getSubContainers(container)
> -        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
> -if not args.fancy and args.one:
> -    for container in sorted(containers,
> -                            key=lambda container: container['name']):
> -        print(container['name'])
> -    sys.exit(0)
> -
> -## Standard list with multiple entries per line
> -if not args.fancy and not args.one:
> -    # Get the longest name and extra simple list
> -    field_maxlength = 0
> -    container_names = []
> -    for container in containers:
> -        if len(container['name']) > field_maxlength:
> -            field_maxlength = len(container['name'])
> -        container_names.append(container['name'])
> -
> -    # Figure out how many we can put per line
> -    width = getTerminalSize()[0]
> -
> -    entries = int(width / (field_maxlength + 2))
> -    if entries == 0:
> -        entries = 1
> -
> -    for line in batch(sorted(container_names), entries):
> -        line_format = ""
> -        for index in range(len(line)):
> -            line_format += "{line[%s]:%s}" % (index, field_maxlength + 2)
> -
> -        print(line_format.format(line=line))
> -
> -## Fancy listing
> -if args.fancy:
> -    field_maxlength = {}
> -
> -    # Get the maximum length per field
> -    for field in args.fancy_format:
> -        field_maxlength[field] = len(field)
> -
> -    for container in containers:
> -        for field in args.fancy_format:
> -            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
> -    # a 2 character padding
> -    line_format = ""
> -    index = 0
> -    for field in args.fancy_format:
> -        line_format += "{fields[%s]:%s}" % (index, field_maxlength[field] + 2)
> -        index += 1
> -
> -    # Get the line length minus the padding of the last field
> -    line_length = -2
> -    for field in field_maxlength:
> -        line_length += field_maxlength[field] + 2
> -
> -    # Print header
> -    print(line_format.format(fields=[header.upper()
> -                                     for header in args.fancy_format]))
> -    print("-" * line_length)
> -
> -    # Print the entries
> -    for container in sorted(containers,
> -                            key=lambda container: container['name']):
> -        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/lxc/lxc-ls.in b/src/lxc/lxc-ls.in
> new file mode 100755
> index 0000000..8c33f50
> --- /dev/null
> +++ b/src/lxc/lxc-ls.in
> @@ -0,0 +1,463 @@
> +#!/usr/bin/python3
> +#
> +# lxc-ls: List containers
> +#
> +# This python implementation is based on the work done in the original
> +# shell implementation done by Serge Hallyn in Ubuntu (and other contributors)
> +#
> +# (C) Copyright Canonical Ltd. 2012
> +#
> +# Authors:
> +# Stéphane Graber <stgraber at ubuntu.com>
> +#
> +# This library is free software; you can redistribute it and/or
> +# modify it under the terms of the GNU Lesser General Public
> +# License as published by the Free Software Foundation; either
> +# version 2.1 of the License, or (at your option) any later version.
> +#
> +# This library is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +# Lesser General Public License for more details.
> +#
> +# You should have received a copy of the GNU Lesser General Public
> +# License along with this library; if not, write to the Free Software
> +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> +#
> +
> +import argparse
> +import gettext
> +import json
> +import lxc
> +import os
> +import re
> +import shutil
> +import tempfile
> +import sys
> +
> +_ = gettext.gettext
> +gettext.textdomain("lxc-ls")
> +
> +# Constants
> +LXCPATH = "@LXCPATH@"
> +RUNTIME_PATH = "@RUNTIME_PATH@"
> +
> +
> +# Functions used later on
> +def batch(iterable, cols=1):
> +    import math
> +
> +    length = len(iterable)
> +    lines = math.ceil(length / cols)
> +
> +    for line in range(lines):
> +        fields = []
> +        for col in range(cols):
> +            index = line + (col * lines)
> +            if index < length:
> +                fields.append(iterable[index])
> +        yield fields
> +
> +
> +def get_terminal_size():
> +    import os
> +    env = os.environ
> +
> +    def ioctl_GWINSZ(fd):
> +        try:
> +            import fcntl
> +            import termios
> +            import struct
> +            cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ,
> +                               '1234'))
> +            return cr
> +        except:
> +            return
> +
> +    cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
> +    if not cr:
> +        try:
> +            fd = os.open(os.ctermid(), os.O_RDONLY)
> +            cr = ioctl_GWINSZ(fd)
> +            os.close(fd)
> +        except:
> +            pass
> +
> +    if not cr:
> +        cr = (env.get('LINES', 25), env.get('COLUMNS', 80))
> +
> +    return int(cr[1]), int(cr[0])
> +
> +
> +def get_root_path(path):
> +    lxc_path = LXCPATH
> +    global_conf = "%s/etc/lxc/lxc.conf" % path
> +    if os.path.exists(global_conf):
> +        with open(global_conf, "r") as fd:
> +            for line in fd:
> +                if line.startswith("lxc.lxcpath"):
> +                    lxc_path = line.split("=")[-1].strip()
> +                    break
> +    return lxc_path
> +
> +
> +# Constants
> +FIELDS = ("name", "state", "ipv4", "ipv6", "autostart", "pid",
> +          "memory", "ram", "swap")
> +
> +# Begin parsing the command line
> +parser = argparse.ArgumentParser(description=_("LXC: List containers"),
> +                                 formatter_class=argparse.RawTextHelpFormatter)
> +
> +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=lxc.default_config_path)
> +
> +parser.add_argument("--active", action="store_true",
> +                    help=_("list only active containers"))
> +
> +parser.add_argument("--frozen", dest="state", action="append_const",
> +                    const="FROZEN", help=_("list only frozen containers"))
> +
> +parser.add_argument("--running", dest="state", action="append_const",
> +                    const="RUNNING", help=_("list only running containers"))
> +
> +parser.add_argument("--stopped", dest="state", action="append_const",
> +                    const="STOPPED", help=_("list only stopped containers"))
> +
> +parser.add_argument("-f", "--fancy", action="store_true",
> +                    help=_("use fancy output"))
> +
> +parser.add_argument("-F", "--fancy-format", type=str,
> +                    default="name,state,ipv4,ipv6,autostart",
> +                    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"))
> +
> +args = parser.parse_args()
> +
> +# --active is the same as --running --frozen
> +if args.active:
> +    if not args.state:
> +        args.state = []
> +    args.state += ["RUNNING", "FROZEN", "UNKNOWN"]
> +
> +# If the output is piped, default to --one
> +if not sys.stdout.isatty():
> +    args.one = True
> +
> +# Turn args.fancy_format into a list
> +args.fancy_format = args.fancy_format.strip().split(",")
> +
> +if set(args.fancy_format) - set(FIELDS):
> +    parser.error(_("Invalid field(s): %s" %
> +                 ", ".join(list(set(args.fancy_format) - set(FIELDS)))))
> +
> +# Basic checks
> +## Check for setns
> +SUPPORT_SETNS = os.path.exists("/proc/self/ns")
> +SUPPORT_SETNS_NET = False
> +SUPPORT_SETNS_PID = False
> +if SUPPORT_SETNS:
> +    SUPPORT_SETNS_NET = os.path.exists("/proc/self/ns/net")
> +    SUPPORT_SETNS_PID = os.path.exists("/proc/self/ns/pid")
> +
> +## Nesting requires setns to pid and net ns
> +if args.nesting:
> +    if not SUPPORT_SETNS:
> +        parser.error(_("Showing nested containers requires setns support "
> +                       "which your kernel doesn't support."))
> +
> +    if not SUPPORT_SETNS_NET:
> +        parser.error(_("Showing nested containers requires setns to the "
> +                       "network namespace which your kernel doesn't support."))
> +
> +    if not SUPPORT_SETNS_PID:
> +        parser.error(_("Showing nested containers requires setns to the "
> +                       "PID namespace which your kernel doesn't support."))
> +
> +# Set the actual lxcpath value
> +if not args.lxcpath:
> +    args.lxcpath = lxc.default_config_path
> +
> +
> +# List of containers, stored as dictionaries
> +def get_containers(fd=None, base="/", root=False):
> +    containers = []
> +
> +    paths = [args.lxcpath]
> +
> +    if not root:
> +        paths.append(get_root_path(base))
> +
> +    # Generate a unique list of valid paths
> +    paths = set([os.path.normpath("%s/%s" % (base, path)) for path in paths])
> +
> +    for path in paths:
> +        if not os.access(path, os.R_OK):
> +            continue
> +
> +        for container_name in lxc.list_containers(config_path=path):
> +            entry = {}
> +            entry['name'] = container_name
> +
> +            # Apply filter
> +            if root and args.filter and \
> +               not re.match(args.filter, container_name):
> +                continue
> +
> +            # Return before grabbing the object (non-root)
> +            if not args.state and not args.fancy and not args.nesting:
> +                containers.append(entry)
> +                continue
> +
> +            try:
> +                container = lxc.Container(container_name, path)
> +            except:
> +                print("Invalid container: %s" % conainer_name, file=sys.stderr)
> +                pass
> +
> +            if container.controllable:
> +                state = container.state
> +            else:
> +                state = 'UNKNOWN'
> +
> +            # Filter by status
> +            if args.state and state not in args.state:
> +                continue
> +
> +            # Nothing more is needed if we're not printing some fancy output
> +            if not args.fancy and not args.nesting:
> +                containers.append(entry)
> +                continue
> +
> +            # Some extra field we may want
> +            if 'state' in args.fancy_format or args.nesting:
> +                entry['state'] = state
> +
> +            if 'pid' in args.fancy_format or args.nesting:
> +                entry['pid'] = "-"
> +                if state == 'UNKNOWN':
> +                    entry['pid'] = state
> +                elif container.init_pid != -1:
> +                    entry['pid'] = str(container.init_pid)
> +
> +            if 'autostart' in args.fancy_format or args.nesting:
> +                entry['autostart'] = "NO"
> +                try:
> +                    if container.get_config_item("lxc.start.auto") == "1":
> +                        entry['autostart'] = "YES"
> +
> +                        groups = container.get_config_item("lxc.group")
> +                        if len(groups) > 0:
> +                            entry['autostart'] = "YES (%s)" % ", ".join(groups)
> +                except KeyError:
> +                    pass
> +
> +            if 'memory' in args.fancy_format or \
> +               'ram' in args.fancy_format or \
> +               'swap' in args.fancy_format:
> +
> +                if container.running:
> +                    try:
> +                        memory_total = int(container.get_cgroup_item(
> +                            "memory.usage_in_bytes"))
> +                    except:
> +                        memory_total = 0
> +
> +                    try:
> +                        memory_swap = int(container.get_cgroup_item(
> +                            "memory.memsw.usage_in_bytes"))
> +                    except:
> +                        memory_swap = 0
> +                else:
> +                    memory_total = 0
> +                    memory_swap = 0
> +
> +            if 'memory' in args.fancy_format:
> +                if container.running:
> +                    entry['memory'] = "%sMB" % round(memory_total / 1048576, 2)
> +                else:
> +                    entry['memory'] = "-"
> +
> +            if 'ram' in args.fancy_format:
> +                if container.running:
> +                    entry['ram'] = "%sMB" % round(
> +                        (memory_total - memory_swap) / 1048576, 2)
> +                else:
> +                    entry['ram'] = "-"
> +
> +            if 'swap' in args.fancy_format:
> +                if container.running:
> +                    entry['swap'] = "%sMB" % round(memory_swap / 1048576, 2)
> +                else:
> +                    entry['swap'] = "-"
> +
> +            # Get the IPs
> +            for family, protocol in {'inet': 'ipv4', 'inet6': 'ipv6'}.items():
> +                if protocol in args.fancy_format or args.nesting:
> +                    entry[protocol] = "-"
> +
> +                    if state == 'UNKNOWN':
> +                        entry[protocol] = state
> +                        continue
> +
> +                    if container.running:
> +                        if not SUPPORT_SETNS_NET:
> +                            entry[protocol] = 'UNKNOWN'
> +                            continue
> +
> +                        ips = container.get_ips(family=family)
> +                        if ips:
> +                            entry[protocol] = ", ".join(ips)
> +
> +            # Nested containers
> +            if args.nesting:
> +                if container.running:
> +                    # Recursive call in container namespace
> +                    temp_fd, temp_file = tempfile.mkstemp()
> +                    os.remove(temp_file)
> +
> +                    container.attach_wait(get_containers, temp_fd,
> +                                          attach_flags=0)
> +
> +                    json_file = os.fdopen(temp_fd, "r")
> +                    json_file.seek(0)
> +
> +                    try:
> +                        sub_containers = json.loads(json_file.read())
> +                    except:
> +                        sub_containers = []
> +
> +                    json_file.close()
> +                else:
> +                    def clear_lock():
> +                        try:
> +                            lock_path = "%s/lock/lxc/%s/%s" % (RUNTIME_PATH,
> +                                                               path,
> +                                                               entry['name'])
> +                            if os.path.exists(lock_path):
> +                                if os.path.isdir(lock_path):
> +                                    shutil.rmtree(lock_path)
> +                                else:
> +                                    os.remove(lock_path)
> +                        except:
> +                            pass
> +
> +                    clear_lock()
> +
> +                    # Recursive call using container rootfs
> +                    sub_containers = get_containers(
> +                        base="%s/%s" % (
> +                            base, container.get_config_item("lxc.rootfs")))
> +
> +                    clear_lock()
> +
> +                for sub in sub_containers:
> +                    if 'nesting_parent' not in sub:
> +                        sub['nesting_parent'] = []
> +                    sub['nesting_parent'].insert(0, entry['name'])
> +                    sub['nesting_real_name'] = sub.get('nesting_real_name',
> +                                                       sub['name'])
> +                    sub['name'] = "%s/%s" % (entry['name'], sub['name'])
> +                    containers.append(sub)
> +
> +            # Append the container
> +            containers.append(entry)
> +
> +    if fd:
> +        json_file = os.fdopen(fd, "w+")
> +        json_file.write(json.dumps(containers))
> +        return
> +
> +    return containers
> +
> +containers = get_containers(root=True)
> +
> +# Print the list
> +## Standard list with one entry per line
> +if not args.fancy and args.one:
> +    for container in sorted(containers,
> +                            key=lambda container: container['name']):
> +        print(container['name'])
> +    sys.exit(0)
> +
> +## Standard list with multiple entries per line
> +if not args.fancy and not args.one:
> +    # Get the longest name and extra simple list
> +    field_maxlength = 0
> +    container_names = []
> +    for container in containers:
> +        if len(container['name']) > field_maxlength:
> +            field_maxlength = len(container['name'])
> +        container_names.append(container['name'])
> +
> +    # Figure out how many we can put per line
> +    width = get_terminal_size()[0]
> +
> +    entries = int(width / (field_maxlength + 2))
> +    if entries == 0:
> +        entries = 1
> +
> +    for line in batch(sorted(container_names), entries):
> +        line_format = ""
> +        for index in range(len(line)):
> +            line_format += "{line[%s]:%s}" % (index, field_maxlength + 2)
> +
> +        print(line_format.format(line=line))
> +
> +## Fancy listing
> +if args.fancy:
> +    field_maxlength = {}
> +
> +    # Get the maximum length per field
> +    for field in args.fancy_format:
> +        field_maxlength[field] = len(field)
> +
> +    for container in containers:
> +        for field in args.fancy_format:
> +            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
> +    # a 2 character padding
> +    line_format = ""
> +    index = 0
> +    for field in args.fancy_format:
> +        line_format += "{fields[%s]:%s}" % (index, field_maxlength[field] + 2)
> +        index += 1
> +
> +    # Get the line length minus the padding of the last field
> +    line_length = -2
> +    for field in field_maxlength:
> +        line_length += field_maxlength[field] + 2
> +
> +    # Print header
> +    print(line_format.format(fields=[header.upper()
> +                                     for header in args.fancy_format]))
> +    print("-" * line_length)
> +
> +    # Print the entries
> +    for container in sorted(containers,
> +                            key=lambda container: container['name']):
> +        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))
> -- 
> 1.9.0
> 

-- 
Stéphane Graber
Ubuntu developer
http://www.ubuntu.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: Digital signature
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20140304/e7740ae5/attachment.pgp>


More information about the lxc-devel mailing list