[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