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> --- 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