[lxc-devel] [PATCH 7/8] python: add attach support

Serge Hallyn serge.hallyn at ubuntu.com
Wed Aug 14 20:49:45 UTC 2013


Quoting Christian Seiler (christian at iwakd.de):
> Add methods attach() and attach_wait() to the Python API that give
> access to the attach functionality of LXC. Both accept two main
> arguments:
> 
> 1. run: A python function that is executed inside the container
> 2. payload: (optional) A parameter that will be passed to the python
>             function
> 
> Additionally, the following keyword arguments are supported:
> 
> attach_flags: How attach should operate, i.e. whether to attach to
>               cgroups, whether to drop capabilities, etc. The following
>               constants are defined as part of the lxc module that may
>               be OR'd together for this option:
>                 LXC_ATTACH_MOVE_TO_CGROUP
>                 LXC_ATTACH_DROP_CAPABILITIES
>                 LXC_ATTACH_SET_PERSONALITY
>                 LXC_ATTACH_APPARMOR
>                 LXC_ATTACH_REMOUNT_PROC_SYS
>                 LXC_ATTACH_DEFAULT
> namespaces: Which namespaces to attach to, as defined as the flags that
>             may be passed to the clone(2) system call. Note: maybe we
>             should export these flags too.
> personality: The personality of the process, it will be passed to the
>              personality(2) syscall. Note: maybe we should provide
>              access to the function that converts arch into
>              personality.
> initial_cwd: The initial working directory after attaching.
> uid: The user id after attaching.
> gid: The group id after attaching.
> env_policy: The environment policy, may be one of:
>               LXC_ATTACH_KEEP_ENV
>               LXC_ATTACH_CLEAR_ENV
> extra_env_vars: A list (or tuple) of environment variables (in the form
>                 KEY=VALUE) that should be set once attach has
>                 succeeded.
> extra_keep_env: A list (or tuple) of names of environment variables
>                 that should be kept regardless of policy.
> stdin: A file/socket/... object that should be used as stdin for the
>        attached process. (If not a standard Python object, it has to
>        implemented the fileno() method and provide a fd as the result.)
> stdout, stderr: See stdin.
> 
> attach() returns the PID of the attached process, or -1 on failure.
> 
> attach_wait() returns the return code of the attached process after
> that has finished executing, or -1 on failure. Note that if the exit
> status of the process is 255, -1 will also be returned, since attach
> failures result in an exit code of 255.
> 
> Two default run functions are also provided in the lxc module:
> 
> attach_run_command: Runs the specified command
> attach_run_shell: Runs a shell in the container
> 
> Examples (assumeing c is a Container object):
> 
> c.attach_wait(lxc.attach_run_command, 'id')
> c.attach_wait(lxc.attach_run_shell)
> def foo():
>   print("Hello World")
>   # the following line is important, otherwise the exit code of
>   # the attached program will be -1
>   # sys.exit(0) will also work
>   return 0
> c.attach_wait(foo)
> c.attach_wait(lxc.attach_run_command, ['cat', '/proc/self/cgroup'])
> c.attach_wait(lxc.attach_run_command, ['cat', '/proc/self/cgroup'],
>               attach_flags=(lxc.LXC_ATTACH_DEFAULT &
>               ~lxc.LXC_ATTACH_MOVE_TO_CGROUP))
> 
> Note that while it is possible to execute Python code inside the
> container by passing a function (see example), it is unwise to import
> modules, since there is no guarantee that the Python installation
> inside the container is in any way compatible with that outside of it.
> If you want to run Python code directly, please import all modules
> before attaching and only use them within the container.
> 
> Signed-off-by: Christian Seiler <christian at iwakd.de>

I don't see any problems, but that doesn't mean much...  I'd like to
wait until Stéphane reviews it before pushing to git.  I'll push the
rest of the set though.

Acked-by: Serge E. Hallyn <serge.hallyn at ubuntu.com>


> ---
>  src/python-lxc/lxc.c           |  241 ++++++++++++++++++++++++++++++++++++++++
>  src/python-lxc/lxc/__init__.py |   57 ++++++----
>  2 files changed, 276 insertions(+), 22 deletions(-)
> 
> diff --git a/src/python-lxc/lxc.c b/src/python-lxc/lxc.c
> index ec81bbf..ddf3fa0 100644
> --- a/src/python-lxc/lxc.c
> +++ b/src/python-lxc/lxc.c
> @@ -24,6 +24,7 @@
>  #include <Python.h>
>  #include "structmember.h"
>  #include <lxc/lxccontainer.h>
> +#include <lxc/utils.h>
>  #include <stdio.h>
>  #include <sys/wait.h>
>  
> @@ -697,6 +698,214 @@ Container_wait(Container *self, PyObject *args, PyObject *kwds)
>      Py_RETURN_FALSE;
>  }
>  
> +struct lxc_attach_python_payload {
> +    PyObject *fn;
> +    PyObject *arg;
> +};
> +
> +static int lxc_attach_python_exec(void* _payload)
> +{
> +    struct lxc_attach_python_payload *payload = (struct lxc_attach_python_payload *)_payload;
> +    PyObject *result = PyObject_CallFunctionObjArgs(payload->fn, payload->arg, NULL);
> +    
> +    if (!result) {
> +        PyErr_Print();
> +        return -1;
> +    }
> +    if (PyLong_Check(result))
> +        return (int)PyLong_AsLong(result);
> +    else
> +        return -1;
> +}
> +
> +static lxc_attach_options_t *lxc_attach_parse_options(PyObject *kwds)
> +{
> +    static char *kwlist[] = {"attach_flags", "namespaces", "personality", "initial_cwd", "uid", "gid", "env_policy", "extra_env_vars", "extra_keep_env", "stdin", "stdout", "stderr", NULL};
> +    long temp_uid, temp_gid;
> +    int temp_env_policy;
> +    PyObject *extra_env_vars_obj = NULL;
> +    PyObject *extra_keep_env_obj = NULL;
> +    PyObject *stdin_obj = NULL;
> +    PyObject *stdout_obj = NULL;
> +    PyObject *stderr_obj = NULL;
> +    PyObject *initial_cwd_obj = NULL;
> +    PyObject *dummy;
> +    bool parse_result;
> +
> +    lxc_attach_options_t default_options = LXC_ATTACH_OPTIONS_DEFAULT;
> +    lxc_attach_options_t *options = malloc(sizeof(*options));
> +
> +    if (!options) {
> +        PyErr_SetNone(PyExc_MemoryError);
> +        return NULL;
> +    }
> +    memcpy(options, &default_options, sizeof(*options));
> +
> +    /* we need some dummy variables because we can't be sure 
> +     * the data types match completely */
> +    temp_uid = -1;
> +    temp_gid = -1;
> +    temp_env_policy = options->env_policy;
> +
> +    /* we need a dummy tuple */
> +    dummy = PyTuple_New(0);
> +
> +    parse_result = PyArg_ParseTupleAndKeywords(dummy, kwds, "|iilO&lliOOOOO", kwlist,
> +                                               &options->attach_flags, &options->namespaces, &options->personality,
> +                                               PyUnicode_FSConverter, &initial_cwd_obj, &temp_uid, &temp_gid,
> +                                               &temp_env_policy, &extra_env_vars_obj, &extra_keep_env_obj,
> +                                               &stdin_obj, &stdout_obj, &stderr_obj);
> +
> +    /* immediately get rid of the dummy tuple */
> +    Py_DECREF(dummy);
> +
> +    if (!parse_result)
> +        return NULL;
> +
> +    /* duplicate the string, so we don't depend on some random Python object */
> +    if (initial_cwd_obj != NULL) {
> +        options->initial_cwd = strndup(PyBytes_AsString(initial_cwd_obj), PyBytes_Size(initial_cwd_obj));
> +        Py_DECREF(initial_cwd_obj);
> +    }
> +
> +    /* do the type conversion from the types that match the parse string */
> +    if (temp_uid != -1) options->uid = (uid_t)temp_uid;
> +    if (temp_gid != -1) options->gid = (gid_t)temp_gid;
> +    options->env_policy = (lxc_attach_env_policy_t)temp_env_policy;
> +
> +    if (extra_env_vars_obj)
> +        options->extra_env_vars = convert_tuple_to_char_pointer_array(extra_env_vars_obj);
> +    if (extra_keep_env_obj)
> +        options->extra_keep_env = convert_tuple_to_char_pointer_array(extra_keep_env_obj);
> +    if (stdin_obj) {
> +        options->stdin_fd = PyObject_AsFileDescriptor(stdin_obj);
> +        if (options->stdin_fd < 0)
> +            return NULL;
> +    }
> +    if (stdout_obj) {
> +        options->stdout_fd = PyObject_AsFileDescriptor(stdout_obj);
> +        if (options->stdout_fd < 0)
> +            return NULL;
> +    }
> +    if (stderr_obj) {
> +        options->stderr_fd = PyObject_AsFileDescriptor(stderr_obj);
> +        if (options->stderr_fd < 0)
> +            return NULL;
> +    }
> +
> +    return options;
> +}
> +
> +void lxc_attach_free_options(lxc_attach_options_t *options)
> +{
> +    int i;
> +    if (!options)
> +        return;
> +    if (options->initial_cwd)
> +        free(options->initial_cwd);
> +    if (options->extra_env_vars) {
> +        for (i = 0; options->extra_env_vars[i]; i++)
> +            free(options->extra_env_vars[i]);
> +        free(options->extra_env_vars);
> +    }
> +    if (options->extra_keep_env) {
> +        for (i = 0; options->extra_keep_env[i]; i++)
> +            free(options->extra_keep_env[i]);
> +        free(options->extra_keep_env);
> +    }
> +    free(options);
> +}
> +
> +static PyObject *
> +Container_attach_and_possibly_wait(Container *self, PyObject *args, PyObject *kwds, int wait)
> +{
> +    struct lxc_attach_python_payload payload = { NULL, NULL };
> +    lxc_attach_options_t *options = NULL;
> +    long ret;
> +    pid_t pid;
> +
> +    if (!PyArg_ParseTuple(args, "O|O", &payload.fn, &payload.arg))
> +        return NULL;
> +    if (!PyCallable_Check(payload.fn)) {
> +        PyErr_Format(PyExc_TypeError, "attach: object not callable");
> +        return NULL;
> +    }
> +
> +    options = lxc_attach_parse_options(kwds);
> +    if (!options)
> +        return NULL;
> +
> +    ret = self->container->attach(self->container, lxc_attach_python_exec, &payload, options, &pid);
> +    if (ret < 0)
> +        goto out;
> +
> +    if (wait) {
> +        ret = lxc_wait_for_pid_status(pid);
> +        /* handle case where attach fails */
> +        if (WIFEXITED(ret) && WEXITSTATUS(ret) == 255)
> +            ret = -1;
> +    } else {
> +        ret = (long)pid;
> +    }
> +
> +out:
> +    lxc_attach_free_options(options);
> +    return PyLong_FromLong(ret);
> +}
> +
> +static PyObject *
> +Container_attach(Container *self, PyObject *args, PyObject *kwds)
> +{
> +    return Container_attach_and_possibly_wait(self, args, kwds, 0);
> +}
> +
> +static PyObject *
> +Container_attach_wait(Container *self, PyObject *args, PyObject *kwds)
> +{
> +    return Container_attach_and_possibly_wait(self, args, kwds, 1);
> +}
> +
> +static PyObject *
> +LXC_attach_run_shell(PyObject *self, PyObject *arg)
> +{
> +    int rv;
> +
> +    rv = lxc_attach_run_shell(NULL);
> +
> +    return PyLong_FromLong(rv);
> +}
> +
> +static PyObject *
> +LXC_attach_run_command(PyObject *self, PyObject *arg)
> +{
> +    PyObject *args_obj = NULL;
> +    int i, rv;
> +    lxc_attach_command_t cmd = {
> +        NULL,         /* program */
> +        NULL          /* argv[] */
> +    };
> +
> +    if (!PyArg_ParseTuple(arg, "sO", (const char**)&cmd.program, &args_obj))
> +        return NULL;
> +    if (args_obj && PyList_Check(args_obj)) {
> +        cmd.argv = convert_tuple_to_char_pointer_array(args_obj);
> +    } else {
> +        PyErr_Format(PyExc_TypeError, "Second part of tuple passed to attach_run_command must be a list.");
> +        return NULL;
> +    }
> +
> +    if (!cmd.argv)
> +        return NULL;
> +
> +    rv = lxc_attach_run_command(&cmd);
> +
> +    for (i = 0; cmd.argv[i]; i++)
> +        free(cmd.argv[i]);
> +    free(cmd.argv);
> +
> +    return PyLong_FromLong(rv);
> +}
> +
>  static PyGetSetDef Container_getseters[] = {
>      {"config_file_name",
>       (getter)Container_config_file_name, NULL,
> @@ -858,6 +1067,18 @@ static PyMethodDef Container_methods[] = {
>       "\n"
>       "Attach to container's console."
>      },
> +    {"attach", (PyCFunction)Container_attach,
> +     METH_VARARGS|METH_KEYWORDS,
> +     "attach(run, payload) -> int\n"
> +     "\n"
> +     "Attach to the container. Returns the pid of the attached process."
> +    },
> +    {"attach_wait", (PyCFunction)Container_attach_wait,
> +     METH_VARARGS|METH_KEYWORDS,
> +     "attach(run, payload) -> int\n"
> +     "\n"
> +     "Attach to the container. Returns the exit code of the process."
> +    },
>      {NULL, NULL, 0, NULL}
>  };
>  
> @@ -904,6 +1125,10 @@ PyVarObject_HEAD_INIT(NULL, 0)
>  };
>  
>  static PyMethodDef LXC_methods[] = {
> +    {"attach_run_shell", (PyCFunction)LXC_attach_run_shell, METH_O,
> +     "Starts up a shell when attaching, to use as the run parameter for attach or attach_wait"},
> +    {"attach_run_command", (PyCFunction)LXC_attach_run_command, METH_O,
> +     "Runs a command when attaching, to use as the run parameter for attach or attach_wait"},
>      {"get_default_config_path", (PyCFunction)LXC_get_default_config_path, METH_NOARGS,
>       "Returns the current LXC config path"},
>      {"get_version", (PyCFunction)LXC_get_version, METH_NOARGS,
> @@ -923,6 +1148,7 @@ PyMODINIT_FUNC
>  PyInit__lxc(void)
>  {
>      PyObject* m;
> +    PyObject* d;
>  
>      if (PyType_Ready(&_lxc_ContainerType) < 0)
>          return NULL;
> @@ -933,5 +1159,20 @@ PyInit__lxc(void)
>  
>      Py_INCREF(&_lxc_ContainerType);
>      PyModule_AddObject(m, "Container", (PyObject *)&_lxc_ContainerType);
> +
> +    /* add constants */
> +    d = PyModule_GetDict(m);
> +    PyDict_SetItemString(d, "LXC_ATTACH_KEEP_ENV", PyLong_FromLong(LXC_ATTACH_KEEP_ENV));
> +    PyDict_SetItemString(d, "LXC_ATTACH_CLEAR_ENV", PyLong_FromLong(LXC_ATTACH_CLEAR_ENV));
> +    PyDict_SetItemString(d, "LXC_ATTACH_MOVE_TO_CGROUP", PyLong_FromLong(LXC_ATTACH_MOVE_TO_CGROUP));
> +    PyDict_SetItemString(d, "LXC_ATTACH_DROP_CAPABILITIES", PyLong_FromLong(LXC_ATTACH_DROP_CAPABILITIES));
> +    PyDict_SetItemString(d, "LXC_ATTACH_SET_PERSONALITY", PyLong_FromLong(LXC_ATTACH_SET_PERSONALITY));
> +    PyDict_SetItemString(d, "LXC_ATTACH_APPARMOR", PyLong_FromLong(LXC_ATTACH_APPARMOR));
> +    PyDict_SetItemString(d, "LXC_ATTACH_REMOUNT_PROC_SYS", PyLong_FromLong(LXC_ATTACH_REMOUNT_PROC_SYS));
> +    PyDict_SetItemString(d, "LXC_ATTACH_DEFAULT", PyLong_FromLong(LXC_ATTACH_DEFAULT));
>      return m;
>  }
> +
> +/*
> + * kate: space-indent on; indent-width 4; mixedindent off; indent-mode cstyle;
> + */
> diff --git a/src/python-lxc/lxc/__init__.py b/src/python-lxc/lxc/__init__.py
> index 4891cd1..1748bac 100644
> --- a/src/python-lxc/lxc/__init__.py
> +++ b/src/python-lxc/lxc/__init__.py
> @@ -230,28 +230,6 @@ class Container(_lxc.Container):
>  
>          return _lxc.Container.set_config_item(self, key, value)
>  
> -    def attach(self, namespace="ALL", *cmd):
> -        """
> -            Attach to a running container.
> -        """
> -
> -        if not self.running:
> -            return False
> -
> -        attach = ["lxc-attach", "-n", self.name,
> -                  "-P", self.get_config_path()]
> -        if namespace != "ALL":
> -            attach += ["-s", namespace]
> -
> -        if cmd:
> -            attach += ["--"] + list(cmd)
> -
> -        if subprocess.call(
> -                attach,
> -                universal_newlines=True) != 0:
> -            return False
> -        return True
> -
>      def create(self, template, args={}):
>          """
>              Create a new rootfs for the container.
> @@ -446,3 +424,38 @@ def list_containers(as_object=False, config_path=None):
>          else:
>              containers.append(entry.split("/")[-2])
>      return containers
> +
> +def attach_run_command(cmd):
> +    """
> +        Run a command when attaching
> +        
> +        Please do not call directly, this will execvp the command.
> +        This is to be used in conjunction with the attach method
> +        of a container.
> +    """
> +    if isinstance(cmd, tuple):
> +        return _lxc.attach_run_command(cmd)
> +    elif isinstance(cmd, list):
> +        return _lxc.attach_run_command((cmd[0], cmd))
> +    else:
> +        return _lxc.attach_run_command((cmd, [cmd]))
> +
> +def attach_run_shell():
> +    """
> +        Run a shell when attaching
> +        
> +        Please do not call directly, this will execvp the shell.
> +        This is to be used in conjunction with the attach method
> +        of a container.
> +    """
> +    return _lxc.attach_run_shell(None)
> +
> +# Some constants for attach
> +LXC_ATTACH_KEEP_ENV = _lxc.LXC_ATTACH_KEEP_ENV
> +LXC_ATTACH_CLEAR_ENV = _lxc.LXC_ATTACH_CLEAR_ENV
> +LXC_ATTACH_MOVE_TO_CGROUP = _lxc.LXC_ATTACH_MOVE_TO_CGROUP
> +LXC_ATTACH_DROP_CAPABILITIES = _lxc.LXC_ATTACH_DROP_CAPABILITIES
> +LXC_ATTACH_SET_PERSONALITY = _lxc.LXC_ATTACH_SET_PERSONALITY
> +LXC_ATTACH_APPARMOR = _lxc.LXC_ATTACH_APPARMOR
> +LXC_ATTACH_REMOUNT_PROC_SYS = _lxc.LXC_ATTACH_REMOUNT_PROC_SYS
> +LXC_ATTACH_DEFAULT = _lxc.LXC_ATTACH_DEFAULT
> -- 
> 1.7.10.4
> 
> 
> ------------------------------------------------------------------------------
> Get 100% visibility into Java/.NET code with AppDynamics Lite!
> It's a free troubleshooting tool designed for production.
> Get down to code-level detail for bottlenecks, with <2% overhead. 
> Download for free and get started troubleshooting in minutes. 
> http://pubads.g.doubleclick.net/gampad/clk?id=48897031&iu=/4140/ostg.clktrk
> _______________________________________________
> Lxc-devel mailing list
> Lxc-devel at lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/lxc-devel




More information about the lxc-devel mailing list