Quoting Christian Seiler (christ...@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 <christ...@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.hal...@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@lists.sourceforge.net > https://lists.sourceforge.net/lists/listinfo/lxc-devel ------------------------------------------------------------------------------ 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@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/lxc-devel