]> git.proxmox.com Git - mirror_lxc.git/commitdiff
Add python-lxc based on the new liblxc API.
authorStéphane Graber <stgraber@ubuntu.com>
Mon, 27 Aug 2012 23:04:43 +0000 (19:04 -0400)
committerStéphane Graber <stgraber@ubuntu.com>
Mon, 12 Nov 2012 18:16:16 +0000 (13:16 -0500)
This adds a basic python binding done in C and a python overlay to
extend some features and provide a user-friendlier API.

This python API only supports python 3.x and was tested with >= 3.2.

It's disabled by default in configure and can be turned on by using
--enable-python.

A basic example of the API can be found in src/python-lxc/test.py.
More documentation and examples will be added soon.

configure.ac
src/Makefile.am
src/python-lxc/Makefile.am [new file with mode: 0644]
src/python-lxc/lxc.c [new file with mode: 0644]
src/python-lxc/lxc/__init__.py [new file with mode: 0644]
src/python-lxc/setup.py [new file with mode: 0644]
src/python-lxc/test.py [new file with mode: 0644]

index dbaf48ba9591f7fd58aa0702ae86bc3ee6fbb4ca..7b98306b22903c2065c7b08403fcb2f3d7ef76cb 100644 (file)
@@ -12,6 +12,11 @@ AM_PROG_CC_C_O
 AC_GNU_SOURCE
 AC_CHECK_PROG(SETCAP, setcap, yes, no, $PATH$PATH_SEPARATOR/sbin)
 
+if test -f /etc/debian_version; then
+    osname="debian"
+fi
+AM_CONDITIONAL([HAVE_DEBIAN], [test x"$osname" == xdebian])
+
 AC_ARG_ENABLE([rpath],
        [AC_HELP_STRING([--disable-rpath], [do not set rpath in executables])],
        [], [enable_rpath=yes])
@@ -67,6 +72,17 @@ AC_ARG_ENABLE([examples],
 
 AM_CONDITIONAL([ENABLE_EXAMPLES], [test "x$enable_examples" = "xyes"])
 
+AC_ARG_ENABLE([python],
+       [AC_HELP_STRING([--enable-python], [enable python binding])],
+       [enable_python=yes], [enable_python=no])
+
+AM_CONDITIONAL([ENABLE_PYTHON], [test "x$enable_python" = "xyes"])
+
+AM_COND_IF([ENABLE_PYTHON],
+    [AM_PATH_PYTHON([3.2], [], [AC_MSG_ERROR([You must install python3])])
+     AC_CHECK_HEADER([python$PYTHON_VERSION/Python.h],[],[AC_MSG_ERROR([You must install python3-dev])])
+     AC_DEFINE_UNQUOTED([ENABLE_PYTHON], 1, [Python3 is available])])
+
 AS_AC_EXPAND(PREFIX, $prefix)
 AS_AC_EXPAND(LIBDIR, $libdir)
 AS_AC_EXPAND(BINDIR, $bindir)
@@ -192,6 +208,8 @@ AC_CONFIG_FILES([
        src/lxc/lxc-shutdown
        src/lxc/lxc-destroy
 
+       src/python-lxc/Makefile
+
        src/tests/Makefile
 
 ])
index ca3b092033fa616a9544dd10be1ed9c2ae24f0f4..4e4d66b5ec04a998e753a70e7445307e2f175a12 100644 (file)
@@ -1 +1 @@
-SUBDIRS = lxc tests
+SUBDIRS = lxc tests python-lxc
diff --git a/src/python-lxc/Makefile.am b/src/python-lxc/Makefile.am
new file mode 100644 (file)
index 0000000..15c61ea
--- /dev/null
@@ -0,0 +1,18 @@
+if ENABLE_PYTHON
+
+if HAVE_DEBIAN
+    DISTSETUPOPTS=--install-layout=deb
+else
+    DISTSETUPOPTS=
+endif
+
+all:
+       CFLAGS="$(CFLAGS) -I ../../src -L../../src/lxc/" $(PYTHON) setup.py build
+
+install:
+       python3 setup.py install --root=$(DESTDIR) --prefix=$(PREFIX) --no-compile $(DISTSETUPOPTS)
+
+clean:
+       rm -rf build
+
+endif
diff --git a/src/python-lxc/lxc.c b/src/python-lxc/lxc.c
new file mode 100644 (file)
index 0000000..f58a954
--- /dev/null
@@ -0,0 +1,576 @@
+/*
+ * python-lxc: Python bindings for LXC
+ *
+ * (C) Copyright Canonical Ltd. 2012
+ *
+ * Authors:
+ * Stéphane Graber <stgraber@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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <Python.h>
+#include "structmember.h"
+#include <lxc/lxccontainer.h>
+#include <stdio.h>
+#include <sys/wait.h>
+
+typedef struct {
+    PyObject_HEAD
+    struct lxc_container *container;
+} Container;
+
+char**
+convert_tuple_to_char_pointer_array(PyObject *argv) {
+    int argc = PyTuple_Size(argv);
+    int i;
+
+    char **result = (char**) malloc(sizeof(char*)*argc + 1);
+
+    for (i = 0; i < argc; i++) {
+        PyObject *pyobj = PyTuple_GetItem(argv, i);
+
+        char *str = NULL;
+        PyObject *pystr;
+        if (!PyUnicode_Check(pyobj)) {
+            PyErr_SetString(PyExc_ValueError, "Expected a string");
+            return NULL;
+        }
+
+        pystr = PyUnicode_AsUTF8String(pyobj);
+        str = PyBytes_AsString(pystr);
+        memcpy((char *) &result[i], (char *) &str, sizeof(str));
+    }
+
+    result[argc] = NULL;
+
+    return result;
+}
+
+void zombie_handler(int sig)
+{
+    signal(SIGCHLD,zombie_handler);
+    int status;
+
+    waitpid(-1, &status, WNOHANG);
+}
+
+static void
+Container_dealloc(Container* self)
+{
+    Py_TYPE(self)->tp_free((PyObject*)self);
+}
+
+static PyObject *
+Container_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+    Container *self;
+
+    self = (Container *)type->tp_alloc(type, 0);
+
+    return (PyObject *)self;
+}
+
+static int
+Container_init(Container *self, PyObject *args, PyObject *kwds)
+{
+    static char *kwlist[] = {"name", NULL};
+    char *name = NULL;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|", kwlist,
+                                      &name))
+        return -1;
+
+    self->container = lxc_container_new(name);
+    if (!self->container) {
+        fprintf(stderr, "%d: error creating lxc_container %s\n", __LINE__, name);
+        return -1;
+    }
+
+    return 0;
+}
+
+// Container properties
+static PyObject *
+Container_config_file_name(Container *self, PyObject *args, PyObject *kwds)
+{
+    return PyUnicode_FromString(self->container->config_file_name(self->container));
+}
+
+static PyObject *
+Container_defined(Container *self, PyObject *args, PyObject *kwds)
+{
+    if (self->container->is_defined(self->container)) {
+        Py_RETURN_TRUE;
+    }
+
+    Py_RETURN_FALSE;
+}
+
+static PyObject *
+Container_init_pid(Container *self, PyObject *args, PyObject *kwds)
+{
+    return Py_BuildValue("i", self->container->init_pid(self->container));
+}
+
+static PyObject *
+Container_name(Container *self, PyObject *args, PyObject *kwds)
+{
+    return PyUnicode_FromString(self->container->name);
+}
+
+static PyObject *
+Container_running(Container *self, PyObject *args, PyObject *kwds)
+{
+    if (self->container->is_running(self->container)) {
+        Py_RETURN_TRUE;
+    }
+
+    Py_RETURN_FALSE;
+}
+
+static PyObject *
+Container_state(Container *self, PyObject *args, PyObject *kwds)
+{
+    return PyUnicode_FromString(self->container->state(self->container));
+}
+
+// Container Functions
+static PyObject *
+Container_clear_config_item(Container *self, PyObject *args, PyObject *kwds)
+{
+    static char *kwlist[] = {"key", NULL};
+    char *key = NULL;
+
+    if (! PyArg_ParseTupleAndKeywords(args, kwds, "s|", kwlist,
+                                      &key))
+        Py_RETURN_FALSE;
+
+    if (self->container->clear_config_item(self->container, key)) {
+        Py_RETURN_TRUE;
+    }
+
+    Py_RETURN_FALSE;
+}
+
+static PyObject *
+Container_create(Container *self, PyObject *args, PyObject *kwds)
+{
+    char* template_name = NULL;
+    char** create_args = {NULL};
+    PyObject *vargs = NULL;
+    static char *kwlist[] = {"template", "args", NULL};
+
+    if (! PyArg_ParseTupleAndKeywords(args, kwds, "s|O", kwlist,
+                                      &template_name, &vargs))
+        Py_RETURN_FALSE;
+
+    if (vargs && PyTuple_Check(vargs)) {
+        create_args = convert_tuple_to_char_pointer_array(vargs);
+        if (!create_args) {
+            return NULL;
+        }
+    }
+
+    if (self->container->create(self->container, template_name, create_args)) {
+        Py_RETURN_TRUE;
+    }
+
+    Py_RETURN_FALSE;
+}
+
+static PyObject *
+Container_destroy(Container *self, PyObject *args, PyObject *kwds)
+{
+    if (self->container->destroy(self->container)) {
+        Py_RETURN_TRUE;
+    }
+
+    Py_RETURN_FALSE;
+}
+
+static PyObject *
+Container_freeze(Container *self, PyObject *args, PyObject *kwds)
+{
+    if (self->container->freeze(self->container)) {
+        Py_RETURN_TRUE;
+    }
+
+    Py_RETURN_FALSE;
+}
+
+static PyObject *
+Container_get_config_item(Container *self, PyObject *args, PyObject *kwds)
+{
+    static char *kwlist[] = {"key", NULL};
+    char* key = NULL;
+    int len = 0;
+
+    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|s", kwlist,
+                                      &key))
+        Py_RETURN_FALSE;
+
+    len = self->container->get_config_item(self->container, key, NULL, 0);
+
+    if (len <= 0) {
+        Py_RETURN_FALSE;
+    }
+
+    char* value = (char*) malloc(sizeof(char)*len + 1);
+    if (self->container->get_config_item(self->container, key, value, len + 1) != len) {
+        Py_RETURN_FALSE;
+    }
+
+    return PyUnicode_FromString(value);
+}
+
+static PyObject *
+Container_get_keys(Container *self, PyObject *args, PyObject *kwds)
+{
+    static char *kwlist[] = {"key", NULL};
+    char* key = NULL;
+    int len = 0;
+
+    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|s", kwlist,
+                                      &key))
+        Py_RETURN_FALSE;
+
+    len = self->container->get_keys(self->container, key, NULL, 0);
+
+    if (len <= 0) {
+        Py_RETURN_FALSE;
+    }
+
+    char* value = (char*) malloc(sizeof(char)*len + 1);
+    if (self->container->get_keys(self->container, key, value, len + 1) != len) {
+        Py_RETURN_FALSE;
+    }
+
+    return PyUnicode_FromString(value);
+}
+
+static PyObject *
+Container_load_config(Container *self, PyObject *args, PyObject *kwds)
+{
+    static char *kwlist[] = {"path", NULL};
+    char* path = NULL;
+
+    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|s", kwlist,
+                                      &path))
+        Py_RETURN_FALSE;
+
+    if (self->container->load_config(self->container, path)) {
+        Py_RETURN_TRUE;
+    }
+
+    Py_RETURN_FALSE;
+}
+
+static PyObject *
+Container_save_config(Container *self, PyObject *args, PyObject *kwds)
+{
+    static char *kwlist[] = {"path", NULL};
+    char* path = NULL;
+
+    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|s", kwlist,
+                                      &path))
+        Py_RETURN_FALSE;
+
+    if (self->container->save_config(self->container, path)) {
+        Py_RETURN_TRUE;
+    }
+
+    Py_RETURN_FALSE;
+}
+
+static PyObject *
+Container_set_config_item(Container *self, PyObject *args, PyObject *kwds)
+{
+    static char *kwlist[] = {"key", "value", NULL};
+    char *key = NULL;
+    char *value = NULL;
+
+    if (! PyArg_ParseTupleAndKeywords(args, kwds, "ss|", kwlist,
+                                      &key, &value))
+        Py_RETURN_FALSE;
+
+    if (self->container->set_config_item(self->container, key, value)) {
+        Py_RETURN_TRUE;
+    }
+
+    Py_RETURN_FALSE;
+}
+
+static PyObject *
+Container_shutdown(Container *self, PyObject *args, PyObject *kwds)
+{
+    static char *kwlist[] = {"timeout", NULL};
+    int timeout = -1;
+
+    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|i", kwlist,
+                                      &timeout))
+        Py_RETURN_FALSE;
+
+    if (self->container->shutdown(self->container, timeout)) {
+        Py_RETURN_TRUE;
+    }
+
+    Py_RETURN_FALSE;
+}
+
+static PyObject *
+Container_start(Container *self, PyObject *args, PyObject *kwds)
+{
+    char** init_args = {NULL};
+    PyObject *useinit = NULL, *vargs = NULL;
+    int init_useinit = 0;
+    static char *kwlist[] = {"useinit", "cmd", NULL};
+
+    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OO", kwlist,
+                                      &useinit, &vargs))
+        Py_RETURN_FALSE;
+
+    if (useinit && useinit == Py_True) {
+        init_useinit = 1;
+    }
+
+    if (vargs && PyTuple_Check(vargs)) {
+        init_args = convert_tuple_to_char_pointer_array(vargs);
+        if (!init_args) {
+            return NULL;
+        }
+    }
+
+    signal(SIGCHLD, zombie_handler);
+    self->container->want_daemonize(self->container);
+
+    if (self->container->start(self->container, init_useinit, init_args)) {
+        Py_RETURN_TRUE;
+    }
+
+    Py_RETURN_FALSE;
+}
+
+static PyObject *
+Container_stop(Container *self, PyObject *args, PyObject *kwds)
+{
+    if (self->container->stop(self->container)) {
+        Py_RETURN_TRUE;
+    }
+
+    Py_RETURN_FALSE;
+}
+
+static PyObject *
+Container_unfreeze(Container *self, PyObject *args, PyObject *kwds)
+{
+    if (self->container->unfreeze(self->container)) {
+        Py_RETURN_TRUE;
+    }
+
+    Py_RETURN_FALSE;
+}
+
+static PyObject *
+Container_wait(Container *self, PyObject *args, PyObject *kwds)
+{
+    static char *kwlist[] = {"state", "timeout", NULL};
+    char *state = NULL;
+    int timeout = -1;
+
+    if (! PyArg_ParseTupleAndKeywords(args, kwds, "s|i", kwlist,
+                                      &state, &timeout))
+        Py_RETURN_FALSE;
+
+    if (self->container->wait(self->container, state, timeout)) {
+        Py_RETURN_TRUE;
+    }
+
+    Py_RETURN_FALSE;
+}
+
+static PyGetSetDef Container_getseters[] = {
+    {"config_file_name",
+     (getter)Container_config_file_name, 0,
+     "Path to the container configuration",
+     NULL},
+    {"defined",
+     (getter)Container_defined, 0,
+     "Boolean indicating whether the container configuration exists",
+     NULL},
+    {"init_pid",
+     (getter)Container_init_pid, 0,
+     "PID of the container's init process in the host's PID namespace",
+     NULL},
+    {"name",
+     (getter)Container_name, 0,
+     "Container name",
+     NULL},
+    {"running",
+     (getter)Container_running, 0,
+     "Boolean indicating whether the container is running or not",
+     NULL},
+    {"state",
+     (getter)Container_state, 0,
+     "Container state",
+     NULL},
+};
+
+static PyMethodDef Container_methods[] = {
+    {"clear_config_item", (PyCFunction)Container_clear_config_item, METH_VARARGS | METH_KEYWORDS,
+     "clear_config_item(key) -> boolean\n"
+     "\n"
+     "Clear the current value of a config key."
+    },
+    {"create", (PyCFunction)Container_create, METH_VARARGS | METH_KEYWORDS,
+     "create(template, args = (,)) -> boolean\n"
+     "\n"
+     "Create a new rootfs for the container, using the given template "
+     "and passing some optional arguments to it."
+    },
+    {"destroy", (PyCFunction)Container_destroy, METH_NOARGS,
+     "destroy() -> boolean\n"
+     "\n"
+     "Destroys the container."
+    },
+    {"freeze", (PyCFunction)Container_freeze, METH_NOARGS,
+     "freeze() -> boolean\n"
+     "\n"
+     "Freezes the container and returns its return code."
+    },
+    {"get_config_item", (PyCFunction)Container_get_config_item, METH_VARARGS | METH_KEYWORDS,
+     "get_config_item(key) -> string\n"
+     "\n"
+     "Get the current value of a config key."
+    },
+    {"get_keys", (PyCFunction)Container_get_keys, METH_VARARGS | METH_KEYWORDS,
+     "get_keys(key) -> string\n"
+     "\n"
+     "Get a list of valid sub-keys for a key."
+    },
+    {"load_config", (PyCFunction)Container_load_config, METH_VARARGS | METH_KEYWORDS,
+     "load_config(path = DEFAULT) -> boolean\n"
+     "\n"
+     "Read the container configuration from its default "
+     "location or from an alternative location if provided."
+    },
+    {"save_config", (PyCFunction)Container_save_config, METH_VARARGS | METH_KEYWORDS,
+     "save_config(path = DEFAULT) -> boolean\n"
+     "\n"
+     "Save the container configuration to its default "
+     "location or to an alternative location if provided."
+    },
+    {"set_config_item", (PyCFunction)Container_set_config_item, METH_VARARGS | METH_KEYWORDS,
+     "set_config_item(key, value) -> boolean\n"
+     "\n"
+     "Set a config key to the provided value."
+    },
+    {"shutdown", (PyCFunction)Container_shutdown, METH_VARARGS | METH_KEYWORDS,
+     "shutdown(timeout = -1) -> boolean\n"
+     "\n"
+     "Sends SIGPWR to the container and wait for it to shutdown "
+     "unless timeout is set to a positive value, in which case "
+     "the container will be killed when the timeout is reached."
+    },
+    {"start", (PyCFunction)Container_start, METH_VARARGS | METH_KEYWORDS,
+     "start(useinit = False, cmd = (,)) -> boolean\n"
+     "\n"
+     "Start the container, optionally using lxc-init and"
+     "an alternate init command, then returns its return code."
+    },
+    {"stop", (PyCFunction)Container_stop, METH_NOARGS,
+     "stop() -> boolean\n"
+     "\n"
+     "Stop the container and returns its return code."
+    },
+    {"unfreeze", (PyCFunction)Container_unfreeze, METH_NOARGS,
+     "unfreeze() -> boolean\n"
+     "\n"
+     "Unfreezes the container and returns its return code."
+    },
+    {"wait", (PyCFunction)Container_wait, METH_VARARGS | METH_KEYWORDS,
+     "wait(state, timeout = -1) -> boolean\n"
+     "\n"
+     "Wait for the container to reach a given state or timeout."
+    },
+    {NULL}  /* Sentinel */
+};
+
+static PyTypeObject _lxc_ContainerType = {
+PyVarObject_HEAD_INIT(NULL, 0)
+    "lxc.Container",                /* tp_name */
+    sizeof(Container),              /* tp_basicsize */
+    0,                              /* tp_itemsize */
+    (destructor)Container_dealloc,  /* tp_dealloc */
+    0,                              /* tp_print */
+    0,                              /* tp_getattr */
+    0,                              /* tp_setattr */
+    0,                              /* tp_reserved */
+    0,                              /* tp_repr */
+    0,                              /* tp_as_number */
+    0,                              /* tp_as_sequence */
+    0,                              /* tp_as_mapping */
+    0,                              /* tp_hash  */
+    0,                              /* tp_call */
+    0,                              /* tp_str */
+    0,                              /* tp_getattro */
+    0,                              /* tp_setattro */
+    0,                              /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT |
+        Py_TPFLAGS_BASETYPE,        /* tp_flags */
+    "Container objects",            /* tp_doc */
+    0,                              /* tp_traverse */
+    0,                              /* tp_clear */
+    0,                              /* tp_richcompare */
+    0,                              /* tp_weaklistoffset */
+    0,                              /* tp_iter */
+    0,                              /* tp_iternext */
+    Container_methods,              /* tp_methods */
+    0,                              /* tp_members */
+    Container_getseters,            /* tp_getset */
+    0,                              /* tp_base */
+    0,                              /* tp_dict */
+    0,                              /* tp_descr_get */
+    0,                              /* tp_descr_set */
+    0,                              /* tp_dictoffset */
+    (initproc)Container_init,       /* tp_init */
+    0,                              /* tp_alloc */
+    Container_new,                  /* tp_new */
+};
+
+static PyModuleDef _lxcmodule = {
+    PyModuleDef_HEAD_INIT,
+    "_lxc",
+    "Binding for liblxc in python",
+    -1,
+    NULL, NULL, NULL, NULL, NULL
+};
+
+PyMODINIT_FUNC
+PyInit__lxc(void)
+{
+    PyObject* m;
+
+    if (PyType_Ready(&_lxc_ContainerType) < 0)
+        return NULL;
+
+    m = PyModule_Create(&_lxcmodule);
+    if (m == NULL)
+        return NULL;
+
+    Py_INCREF(&_lxc_ContainerType);
+    PyModule_AddObject(m, "Container", (PyObject *)&_lxc_ContainerType);
+    return m;
+}
diff --git a/src/python-lxc/lxc/__init__.py b/src/python-lxc/lxc/__init__.py
new file mode 100644 (file)
index 0000000..94616f5
--- /dev/null
@@ -0,0 +1,372 @@
+#
+# python-lxc: Python bindings for LXC
+#
+# (C) Copyright Canonical Ltd. 2012
+#
+# Authors:
+# Stéphane Graber <stgraber@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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+import _lxc
+import glob
+import os
+import subprocess
+import tempfile
+import time
+import warnings
+
+warnings.warn("The python-lxc API isn't yet stable "
+              "and may change at any point in the future.", Warning, 2)
+
+class ContainerNetwork():
+    props = {}
+
+    def __init__(self, container, index):
+        self.container = container
+        self.index = index
+
+        for key in self.container.get_keys("lxc.network.%s" % self.index):
+            if "." in key:
+                self.props[key.replace(".", "_")] = key
+            else:
+                self.props[key] = key
+
+        if not self.props:
+            return False
+
+    def __delattr__(self, key):
+        if key in ["container", "index", "props"]:
+            return object.__delattr__(self, key)
+
+        if key not in self.props:
+            raise AttributeError("'%s' network has no attribute '%s'" % (
+                    self.__get_network_item("type"), key))
+
+        return self.__clear_network_item(self.props[key])
+
+    def __dir__(self):
+        return sorted(self.props.keys())
+
+    def __getattr__(self, key):
+        if key in ["container", "index", "props"]:
+            return object.__getattribute__(self, key)
+
+        if key not in self.props:
+            raise AttributeError("'%s' network has no attribute '%s'" % (
+                    self.__get_network_item("type"), key))
+
+        return self.__get_network_item(self.props[key])
+
+    def __hasattr__(self, key):
+        if key in ["container", "index", "props"]:
+            return object.__hasattr__(self, key)
+
+        if key not in self.props:
+            raise AttributeError("'%s' network has no attribute '%s'" % (
+                    self.__get_network_item("type"), key))
+
+        return True
+
+    def __repr__(self):
+        return "'%s' network at index '%s'" % (
+            self.__get_network_item("type"), self.index)
+
+    def __setattr__(self, key, value):
+        if key in ["container", "index", "props"]:
+            return object.__setattr__(self, key, value)
+
+        if key not in self.props:
+            raise AttributeError("'%s' network has no attribute '%s'" % (
+                    self.__get_network_item("type"), key))
+
+        return self.__set_network_item(self.props[key], value)
+
+    def __clear_network_item(self, key):
+        return self.container.clear_config_item("lxc.network.%s.%s" % (
+                    self.index, key))
+
+    def __get_network_item(self, key):
+        return self.container.get_config_item("lxc.network.%s.%s" % (
+                    self.index, key))
+
+    def __set_network_item(self, key, value):
+        return self.container.set_config_item("lxc.network.%s.%s" % (
+                    self.index, key), value)
+
+
+class ContainerNetworkList():
+    def __init__(self, container):
+        self.container = container
+
+    def __getitem__(self, index):
+        count = len(self.container.get_config_item("lxc.network"))
+        if index >= count:
+            raise IndexError("list index out of range")
+
+        return ContainerNetwork(self.container, index)
+
+    def __len__(self):
+        return len(self.container.get_config_item("lxc.network"))
+
+    def add(self, network_type):
+        index = len(self.container.get_config_item("lxc.network"))
+
+        return self.container.set_config_item("lxc.network.%s.type" % index,
+                    network_type)
+
+    def remove(self, index):
+        count = len(self.container.get_config_item("lxc.network"))
+        if index >= count:
+            raise IndexError("list index out of range")
+
+        return self.container.clear_config_item("lxc.network.%s" % index)
+
+
+class Container(_lxc.Container):
+    def __init__(self, name):
+        """
+            Creates a new Container instance.
+        """
+
+        _lxc.Container.__init__(self, name)
+        self.network = ContainerNetworkList(self)
+
+    def append_config_item(self, key, value):
+        """
+            Append 'value' to 'key', assuming 'key' is a list.
+            If 'key' isn't a list, 'value' will be set as the value of 'key'.
+        """
+
+        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]
+        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.
+
+            "template" must be a valid template name.
+
+            "args" (optional) is a dictionary of parameters and values to pass
+            to the template.
+        """
+
+        template_args = []
+        for item in args.items():
+            template_args.append("--%s" % item[0])
+            template_args.append("%s" % item[1])
+
+        return _lxc.Container.create(self, template, tuple(template_args))
+
+    def clone(self, container):
+        """
+            Clone an existing container into a new one.
+        """
+
+        if self.defined:
+            return False
+
+        if isinstance(container, Container):
+            source = container
+        else:
+            source = Container(container)
+
+        if not source.defined:
+            return False
+
+        if subprocess.call(
+                    ["lxc-clone", "-o", source.name, "-n", self.name],
+                    universal_newlines=True) != 0:
+            return False
+
+        self.load_config()
+        return True
+
+    def console(self, tty="1"):
+        """
+            Access the console of a container.
+        """
+
+        if not self.running:
+            return False
+
+        if subprocess.call(
+                    ["lxc-console", "-n", self.name, "-t", "%s" % tty],
+                    universal_newlines=True) != 0:
+            return False
+        return True
+
+    def get_config_item(self, key):
+        """
+            Returns the value for a given config key.
+            A list is returned when multiple values are set.
+        """
+        value = _lxc.Container.get_config_item(self, key)
+
+        if value is False:
+            return False
+        elif value.endswith("\n"):
+            return value.rstrip("\n").split("\n")
+        else:
+            return value
+
+    def get_ips(self, timeout=60, interface=None, protocol=None):
+        """
+            Returns the list of IP addresses for the container.
+        """
+
+        if not self.defined or not self.running:
+            return False
+
+        try:
+            os.makedirs("/run/netns")
+        except:
+            pass
+
+        path = tempfile.mktemp(dir="/run/netns")
+
+        os.symlink("/proc/%s/ns/net" % self.init_pid, path)
+
+        ips = []
+
+        count = 0
+        while count < timeout:
+            if count != 0:
+                time.sleep(1)
+
+            base_cmd = ["ip", "netns", "exec", path.split("/")[-1], "ip"]
+
+            # Get IPv6
+            if protocol in ("ipv6", None):
+                ip6_cmd = base_cmd + ["-6", "addr", "show", "scope", "global"]
+                if interface:
+                    ip = subprocess.Popen(ip6_cmd + ["dev", interface],
+                            stdout=subprocess.PIPE, universal_newlines=True)
+                else:
+                    ip = subprocess.Popen(ip6_cmd, stdout=subprocess.PIPE,
+                            universal_newlines=True)
+
+                ip.wait()
+                for line in ip.stdout.read().split("\n"):
+                    fields = line.split()
+                    if len(fields) > 2 and fields[0] == "inet6":
+                        ips.append(fields[1].split('/')[0])
+
+            # Get IPv4
+            if protocol in ("ipv4", None):
+                ip4_cmd = base_cmd + ["-4", "addr", "show", "scope", "global"]
+                if interface:
+                    ip = subprocess.Popen(ip4_cmd + ["dev", interface],
+                            stdout=subprocess.PIPE, universal_newlines=True)
+                else:
+                    ip = subprocess.Popen(ip4_cmd, stdout=subprocess.PIPE,
+                            universal_newlines=True)
+
+                ip.wait()
+                for line in ip.stdout.read().split("\n"):
+                    fields = line.split()
+                    if len(fields) > 2 and fields[0] == "inet":
+                        ips.append(fields[1].split('/')[0])
+
+            if ips:
+                break
+
+            count += 1
+
+        os.remove(path)
+        return ips
+
+    def get_keys(self, key):
+        """
+            Returns a list of valid sub-keys.
+        """
+        value = _lxc.Container.get_keys(self, key)
+
+        if value is False:
+            return False
+        elif value.endswith("\n"):
+            return value.rstrip("\n").split("\n")
+        else:
+            return value
+
+    def set_config_item(self, key, value):
+        """
+            Set a config key to a provided value.
+            The value can be a list for the keys supporting multiple values.
+        """
+        old_value = self.get_config_item(key)
+
+        # Check if it's a list
+        def set_key(key, value):
+            self.clear_config_item(key)
+            if isinstance(value, list):
+                for entry in value:
+                    if not _lxc.Container.set_config_item(self, key, entry):
+                        return False
+            else:
+                _lxc.Container.set_config_item(self, key, value)
+
+        set_key(key, value)
+        new_value = self.get_config_item(key)
+
+        if isinstance(value, str) and isinstance(new_value, str) and \
+           value == new_value:
+            return True
+        elif isinstance(value, list) and isinstance(new_value, list) and \
+           set(value) == set(new_value):
+            return True
+        elif isinstance(value, str) and isinstance(new_value, list) and \
+           set([value]) == set(new_value):
+            return True
+        elif old_value:
+            set_key(key, old_value)
+            return False
+        else:
+            self.clear_config_item(key)
+            return False
+
+
+def list_containers(as_object=False):
+    """
+        List the containers on the system.
+    """
+    containers = []
+    for entry in glob.glob("/var/lib/lxc/*/config"):
+        if as_object:
+            containers.append(Container(entry.split("/")[-2]))
+        else:
+            containers.append(entry.split("/")[-2])
+    return containers
diff --git a/src/python-lxc/setup.py b/src/python-lxc/setup.py
new file mode 100644 (file)
index 0000000..bf635ea
--- /dev/null
@@ -0,0 +1,10 @@
+from distutils.core import setup, Extension
+
+module = Extension('_lxc', sources = ['lxc.c'], libraries = ['lxc'])
+
+setup (name = '_lxc',
+        version = '0.1',
+        description = 'LXC',
+        packages = ['lxc'],
+        package_dir = {'lxc':'lxc'},
+        ext_modules = [module])
diff --git a/src/python-lxc/test.py b/src/python-lxc/test.py
new file mode 100644 (file)
index 0000000..5552fe1
--- /dev/null
@@ -0,0 +1,28 @@
+import lxc
+
+t1 = lxc.Container("test")
+print("Name set properly: %s" % (t1.name == "test"))
+print("Test config loaded properly: %s" % t1.load_config("/etc/lxc/lxc.conf"))
+print("Real config loaded properly: %s" % t1.load_config())
+print("Test config path: %s" % (t1.config_file_name == "/var/lib/lxc/test/config"))
+print("Set config item: %s" % t1.set_config_item("lxc.utsname", "blabla"))
+print("Container defined: %s" % (t1.defined))
+print("Started properly: %s" % t1.start())
+print("Container running: %s" % t1.wait("RUNNING"))
+print("Container state: %s" % t1.state)
+print("Container running: %s" % t1.running)
+print("Container init process: %s" % t1.init_pid)
+print("Freezing: %s" % t1.freeze())
+print("Container frozen: %s" % t1.wait("FROZEN"))
+print("Container state: %s" % t1.state)
+print("Unfreezing: %s" % t1.unfreeze())
+print("Container running: %s" % t1.wait("RUNNING"))
+print("Container state: %s" % t1.state)
+print("Stopped properly: %s" % t1.stop())
+print("Container state: %s" % t1.state)
+
+#print("Started properly: %s" % t1.start(useinit=True))
+#print("Container running: %s" % t1.wait("RUNNING"))
+#print("Container state: %s" % t1.state)
+#print("Stopped properly: %s" % t1.stop())
+#print("Container state: %s" % t1.state)