[lxc-devel] [PATCH] add lua binding for the lxc API

Dwight Engen dwight.engen at oracle.com
Tue Jan 22 16:03:12 UTC 2013


The lua binding is based closely on the python binding. Also included are
a test program for excercising the binding, and an lxc-top utility for
showing statistics on running containers.

Signed-off-by: Dwight Engen <dwight.engen at oracle.com>
---
 Makefile.am                  |   6 +-
 configure.ac                 |  20 +++
 doc/Makefile.am              |   4 +
 doc/lxc-top.sgml.in          | 164 +++++++++++++++++
 lxc.spec.in                  |  13 +-
 src/Makefile.am              |   2 +-
 src/lua-lxc/Makefile.am      |  30 ++++
 src/lua-lxc/core.c           | 382 +++++++++++++++++++++++++++++++++++++++
 src/lua-lxc/lxc-top          | 242 +++++++++++++++++++++++++
 src/lua-lxc/lxc.lua          | 412 +++++++++++++++++++++++++++++++++++++++++++
 src/lua-lxc/test/apitest.lua | 302 +++++++++++++++++++++++++++++++
 11 files changed, 1574 insertions(+), 3 deletions(-)
 create mode 100644 doc/lxc-top.sgml.in
 create mode 100644 src/lua-lxc/Makefile.am
 create mode 100644 src/lua-lxc/core.c
 create mode 100644 src/lua-lxc/lxc-top
 create mode 100644 src/lua-lxc/lxc.lua
 create mode 100644 src/lua-lxc/test/apitest.lua

diff --git a/Makefile.am b/Makefile.am
index 7b32326..89260e5 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -6,6 +6,10 @@ SUBDIRS = config src templates doc
 DIST_SUBDIRS = config src templates doc
 EXTRA_DIST = autogen.sh lxc.spec CONTRIBUTING MAINTAINERS ChangeLog
 
+if ENABLE_LUA
+RPMARGS = --with lua
+endif
+
 pcdatadir = $(libdir)/pkgconfig
 pcdata_DATA = lxc.pc
 
@@ -17,4 +21,4 @@ ChangeLog::
 	@touch ChangeLog
 
 rpm: dist
-	rpmbuild --clean -ta ${distdir}.tar.gz
+	rpmbuild --clean -ta ${distdir}.tar.gz $(RPMARGS)
diff --git a/configure.ac b/configure.ac
index d1f5ad9..02c75a1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -137,6 +137,23 @@ AM_COND_IF([ENABLE_PYTHON],
 	PKG_CHECK_MODULES([PYTHONDEV], [python3 >= 3.2],[],[AC_MSG_ERROR([You must install python3-dev])])
 	AC_DEFINE_UNQUOTED([ENABLE_PYTHON], 1, [Python3 is available])])
 
+# Lua module and scripts
+if test x"$with_distro" = "xdebian" -o x"$with_distro" = "xubuntu" ; then
+    LUAPKGCONFIG=lua5.1
+else
+    LUAPKGCONFIG=lua
+fi
+
+AC_ARG_ENABLE([lua],
+	[AC_HELP_STRING([--enable-lua], [enable lua binding])],
+	[enable_lua=yes], [enable_lua=no])
+
+AM_CONDITIONAL([ENABLE_LUA], [test "x$enable_lua" = "xyes"])
+
+AM_COND_IF([ENABLE_LUA],
+	[PKG_CHECK_MODULES([LUA], [$LUAPKGCONFIG >= 5.1],[],[AC_MSG_ERROR([You must install lua-devel for lua 5.1])])
+	AC_DEFINE_UNQUOTED([ENABLE_LUA], 1, [Lua is available])])
+
 # Optional test binaries
 AC_ARG_ENABLE([tests],
 	[AC_HELP_STRING([--enable-tests], [build test/example binaries])],
@@ -268,6 +285,7 @@ AC_CONFIG_FILES([
 	doc/lxc-wait.sgml
 	doc/lxc-ls.sgml
 	doc/lxc-ps.sgml
+	doc/lxc-top.sgml
 	doc/lxc-cgroup.sgml
 	doc/lxc-kill.sgml
 	doc/lxc-attach.sgml
@@ -321,6 +339,8 @@ AC_CONFIG_FILES([
 	src/python-lxc/lxc/__init__.py
 	src/python-lxc/examples/api_test.py
 
+	src/lua-lxc/Makefile
+
 	src/tests/Makefile
 ])
 AC_CONFIG_COMMANDS([default],[[]],[[]])
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 86de2fe..e539254 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -34,6 +34,10 @@ else
     man_MANS += legacy/lxc-ls.1
 endif
 
+if ENABLE_LUA
+    man_MANS += lxc-top.1
+endif
+
 %.1 : %.sgml
 	$(db2xman) $<
 	test "$(shell basename $@)" != "$@" && mv $(shell basename $@) $@ || true
diff --git a/doc/lxc-top.sgml.in b/doc/lxc-top.sgml.in
new file mode 100644
index 0000000..2a4f835
--- /dev/null
+++ b/doc/lxc-top.sgml.in
@@ -0,0 +1,164 @@
+<!--
+
+Copyright © 2012 Oracle.
+
+Authors:
+Dwight Engen <dwight.engen at oracle.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
+
+-->
+
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
+
+<!ENTITY seealso SYSTEM "@builddir@/see_also.sgml">
+]>
+
+<refentry>
+
+  <docinfo><date>@LXC_GENERATE_DATE@</date></docinfo>
+
+  <refmeta>
+    <refentrytitle>lxc-top</refentrytitle>
+    <manvolnum>1</manvolnum>
+  </refmeta>
+
+  <refnamediv>
+    <refname>lxc-top</refname>
+
+    <refpurpose>
+      monitor container statistics
+    </refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>lxc-top</command>
+      <arg choice="opt">--help</arg>
+      <arg choice="opt">--max <replaceable>count</replaceable></arg>
+      <arg choice="opt">--delay <replaceable>delay</replaceable></arg>
+      <arg choice="opt">--sort <replaceable>sortby</replaceable></arg>
+      <arg choice="opt">--reverse</arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>Description</title>
+    <para>
+      <command>lxc-top</command> displays container statistics. The output
+        is updated every <replaceable>delay</replaceable> seconds, and is
+        ordered according to the <replaceable>sortby</replaceable> value
+        given. Specifying <replaceable>count</replaceable> will limit the
+        number of containers displayed, otherwise <command>lxc-top</command>
+        will display as many containers as can fit in your terminal.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>Options</title>
+    <variablelist>
+
+      <varlistentry>
+        <term>
+          <option><optional>-m, --max <replaceable>count</replaceable></optional></option>
+        </term>
+        <listitem>
+          <para>
+            Limit the number of containers displayed to
+            <replaceable>count</replaceable>.
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>
+          <option><optional>-d, --delay <replaceable>delay</replaceable></optional></option>
+        </term>
+        <listitem>
+          <para>
+            Amount of time in seconds to delay between screen updates.
+            This can be specified as less than a second by giving a
+            rational number, for example 0.5 for a half second delay. The
+            default is 3 seconds.
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <option><optional>-s, --sort <replaceable>sortby</replaceable></optional></option>
+        </term>
+        <listitem>
+          <para>
+            Sort the containers by name, cpu use, or memory use. The
+            <replaceable>sortby</replaceable> argument should be one of
+            the letters n,c,d,m to sort by name, cpu use, disk I/O, or
+            memory use respectively. The default is 'n'.
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <option><optional>-r, --reverse</optional></option>
+        </term>
+        <listitem>
+          <para>
+            Reverse the default sort order. By default, names sort in
+            ascending alphabetical order and values sort in descending
+            amounts (ie. largest value first).
+          </para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>Example</title>
+    <variablelist>
+      <varlistentry>
+        <term>lxc-top --delay 1 --sort m</term>
+        <listitem>
+        <para>
+          Display containers, updating every second, sorted by memory use.
+        </para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  &seealso;
+
+  <refsect1>
+    <title>Author</title>
+    <para>Dwight Engen <email>dwight.engen at oracle.com</email></para>
+  </refsect1>
+
+</refentry>
+
+<!-- Keep this comment at the end of the file
+Local variables:
+mode: sgml
+sgml-omittag:t
+sgml-shorttag:t
+sgml-minimize-attributes:nil
+sgml-always-quote-attributes:t
+sgml-indent-step:2
+sgml-indent-data:t
+sgml-parent-document:nil
+sgml-default-dtd-file:nil
+sgml-exposed-tags:nil
+sgml-local-catalogs:nil
+sgml-local-ecat-files:nil
+End:
+-->
diff --git a/lxc.spec.in b/lxc.spec.in
index c7470b8..e77cbc3 100644
--- a/lxc.spec.in
+++ b/lxc.spec.in
@@ -32,6 +32,13 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-build
 Requires: libcap openssl rsync
 BuildRequires: libcap libcap-devel docbook2X
 
+%define with_lua %{?_with_lua: 1} %{?!_with_lua: 0}
+%if %{with_lua}
+Requires: lua-filesystem
+BuildRequires: lua-devel
+%define enable_lua --enable-lua
+%endif
+
 %description
 
 The package "%{name}" provides the command lines to create and manage
@@ -62,7 +69,7 @@ development of the linux containers.
 %prep
 %setup
 %build
-PATH=$PATH:/usr/sbin:/sbin %configure $args --disable-rpath
+PATH=$PATH:/usr/sbin:/sbin %configure $args --disable-rpath %{?enable_lua}
 make %{?_smp_mflags}
 
 %install
@@ -98,6 +105,10 @@ rm -rf %{buildroot}
 %{_libdir}/*.so.*
 %{_libdir}/%{name}
 %{_localstatedir}/*
+%if %{with_lua}
+%{_datadir}/lua
+%{_libdir}/lua
+%endif
 %attr(4555,root,root) %{_libexecdir}/%{name}/lxc-init
 
 %files devel
diff --git a/src/Makefile.am b/src/Makefile.am
index 4e4d66b..c96cbe7 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1 +1 @@
-SUBDIRS = lxc tests python-lxc
+SUBDIRS = lxc tests python-lxc lua-lxc
diff --git a/src/lua-lxc/Makefile.am b/src/lua-lxc/Makefile.am
new file mode 100644
index 0000000..3db3b7c
--- /dev/null
+++ b/src/lua-lxc/Makefile.am
@@ -0,0 +1,30 @@
+if ENABLE_LUA
+
+luadir=$(datadir)/lua/5.1
+sodir=$(libdir)/lua/5.1/lxc
+
+lua_SCRIPTS=lxc.lua
+EXTRA_DIST=lxc.lua lxc-top
+
+so_PROGRAMS = core.so
+
+core_so_SOURCES = core.c
+
+bin_SCRIPTS = lxc-top
+
+AM_CFLAGS=-I$(top_srcdir)/src $(LUA_CFLAGS) -DVERSION=\"$(VERSION)\" -DLXCPATH=\"$(LXCPATH)\"
+
+core_so_CFLAGS = -fPIC -DPIC $(AM_CFLAGS)
+
+core_so_LDFLAGS = \
+	-shared \
+	-L$(top_srcdir)/src/lxc \
+	-Wl,-soname,core.so.$(firstword $(subst ., ,$(VERSION)))
+
+core_so_LDADD = -llxc $(LUA_LIBS)
+
+lxc.lua:
+
+lxc-top:
+
+endif
diff --git a/src/lua-lxc/core.c b/src/lua-lxc/core.c
new file mode 100644
index 0000000..5c47aed
--- /dev/null
+++ b/src/lua-lxc/core.c
@@ -0,0 +1,382 @@
+/*
+ * lua-lxc: lua bindings for lxc
+ *
+ * Copyright © 2012 Oracle.
+ *
+ * Authors:
+ * Dwight Engen <dwight.engen at oracle.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2, as
+ * published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#define LUA_LIB
+#define _GNU_SOURCE
+#include <lua.h>
+#include <lauxlib.h>
+#include <string.h>
+#include <lxc/lxccontainer.h>
+
+#ifdef NO_CHECK_UDATA
+#define checkudata(L,i,tname)	lua_touserdata(L, i)
+#else
+#define checkudata(L,i,tname)	luaL_checkudata(L, i, tname)
+#endif
+
+#define lua_boxpointer(L,u) \
+    (*(void **) (lua_newuserdata(L, sizeof(void *))) = (u))
+
+#define lua_unboxpointer(L,i,tname) \
+    (*(void **) (checkudata(L, i, tname)))
+
+#define CONTAINER_TYPENAME	"lxc.container"
+
+static int container_new(lua_State *L)
+{
+    const char *name = luaL_checkstring(L, 1);
+    struct lxc_container *c = lxc_container_new(name);
+
+    if (c) {
+	lua_boxpointer(L, c);
+	luaL_getmetatable(L, CONTAINER_TYPENAME);
+	lua_setmetatable(L, -2);
+    } else {
+	lua_pushnil(L);
+    }
+    return 1;
+}
+
+static int container_gc(lua_State *L)
+{
+    struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
+
+    /* XXX what to do if this fails? */
+    lxc_container_put(c);
+    return 0;
+}
+
+static int container_config_file_name(lua_State *L)
+{
+    struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
+    char *config_file_name;
+
+    config_file_name = c->config_file_name(c);
+    lua_pushstring(L, config_file_name);
+    free(config_file_name);
+    return 1;
+}
+
+static int container_defined(lua_State *L)
+{
+    struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
+
+    lua_pushboolean(L, !!c->is_defined(c));
+    return 1;
+}
+
+static int container_name(lua_State *L)
+{
+    struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
+
+    lua_pushstring(L, c->name);
+    return 1;
+}
+
+static int container_create(lua_State *L)
+{
+    struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
+    char *template_name = strdupa(luaL_checkstring(L, 2));
+    int argc = lua_gettop(L);
+    char **argv;
+    int i;
+
+    argv = alloca((argc+1) * sizeof(char *));
+    for (i = 0; i < argc-2; i++)
+	argv[i] = strdupa(luaL_checkstring(L, i+3));
+    argv[i] = NULL;
+
+    lua_pushboolean(L, !!c->create(c, template_name, argv));
+    return 1;
+}
+
+static int container_destroy(lua_State *L)
+{
+    struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
+
+    lua_pushboolean(L, !!c->destroy(c));
+    return 1;
+}
+
+/* container state */
+static int container_start(lua_State *L)
+{
+    struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
+    int argc = lua_gettop(L);
+    char **argv = NULL;
+    int i,j;
+    int useinit = 0;
+
+    if (argc > 1) {
+	argv = alloca((argc+1) * sizeof(char *));
+	for (i = 0, j = 0; i < argc-1; i++) {
+	    const char *arg = luaL_checkstring(L, i+2);
+
+	    if (!strcmp(arg, "useinit"))
+		useinit = 1;
+	    else
+		argv[j++] = strdupa(arg);
+	}
+	argv[j] = NULL;
+    }
+
+    c->want_daemonize(c);
+    lua_pushboolean(L, !!c->start(c, useinit, argv));
+    return 1;
+}
+
+static int container_stop(lua_State *L)
+{
+    struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
+
+    lua_pushboolean(L, !!c->stop(c));
+    return 1;
+}
+
+static int container_shutdown(lua_State *L)
+{
+    struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
+    int timeout = luaL_checkinteger(L, 2);
+
+    lua_pushboolean(L, !!c->shutdown(c, timeout));
+    return 1;
+}
+
+static int container_wait(lua_State *L)
+{
+    struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
+    const char *state = luaL_checkstring(L, 2);
+    int timeout = luaL_checkinteger(L, 3);
+
+    lua_pushboolean(L, !!c->wait(c, state, timeout));
+    return 1;
+}
+
+static int container_freeze(lua_State *L)
+{
+    struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
+
+    lua_pushboolean(L, !!c->freeze(c));
+    return 1;
+}
+
+static int container_unfreeze(lua_State *L)
+{
+    struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
+
+    lua_pushboolean(L, !!c->unfreeze(c));
+    return 1;
+}
+
+static int container_running(lua_State *L)
+{
+    struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
+
+    lua_pushboolean(L, !!c->is_running(c));
+    return 1;
+}
+
+static int container_state(lua_State *L)
+{
+    struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
+
+    lua_pushstring(L, c->state(c));
+    return 1;
+}
+
+static int container_init_pid(lua_State *L)
+{
+    struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
+
+    lua_pushinteger(L, c->init_pid(c));
+    return 1;
+}
+
+/* configuration file methods */
+static int container_load_config(lua_State *L)
+{
+    struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
+    int arg_cnt = lua_gettop(L);
+    const char *alt_path = NULL;
+
+    if (arg_cnt > 1)
+	alt_path = luaL_checkstring(L, 2);
+
+    lua_pushboolean(L, !!c->load_config(c, alt_path));
+    return 1;
+}
+
+static int container_save_config(lua_State *L)
+{
+    struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
+    int arg_cnt = lua_gettop(L);
+    const char *alt_path = NULL;
+
+    if (arg_cnt > 1)
+	alt_path = luaL_checkstring(L, 2);
+
+    lua_pushboolean(L, !!c->save_config(c, alt_path));
+    return 1;
+}
+
+static int container_clear_config_item(lua_State *L)
+{
+    struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
+    const char *key = luaL_checkstring(L, 2);
+
+    lua_pushboolean(L, !!c->clear_config_item(c, key));
+    return 1;
+}
+
+static int container_get_config_item(lua_State *L)
+{
+    struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
+    const char *key = luaL_checkstring(L, 2);
+    int len;
+    char *value;
+
+    len = c->get_config_item(c, key, NULL, 0);
+    if (len <= 0)
+	goto not_found;
+
+    value = alloca(sizeof(char)*len + 1);
+    if (c->get_config_item(c, key, value, len + 1) != len)
+	goto not_found;
+
+    lua_pushstring(L, value);
+    return 1;
+
+not_found:
+    lua_pushnil(L);
+    return 1;
+}
+
+static int container_set_config_item(lua_State *L)
+{
+    struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
+    const char *key = luaL_checkstring(L, 2);
+    const char *value = luaL_checkstring(L, 3);
+
+    lua_pushboolean(L, !!c->set_config_item(c, key, value));
+    return 1;
+}
+
+static int container_get_keys(lua_State *L)
+{
+    struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
+    const char *key = NULL;
+    int len;
+    char *value;
+    int arg_cnt = lua_gettop(L);
+
+    if (arg_cnt > 1)
+	key = luaL_checkstring(L, 2);
+
+    len = c->get_keys(c, key, NULL, 0);
+    if (len <= 0)
+	goto not_found;
+
+    value = alloca(sizeof(char)*len + 1);
+    if (c->get_keys(c, key, value, len + 1) != len)
+	goto not_found;
+
+    lua_pushstring(L, value);
+    return 1;
+
+not_found:
+    lua_pushnil(L);
+    return 1;
+}
+
+static luaL_Reg lxc_container_methods[] =
+{
+    {"create",			container_create},
+    {"defined",			container_defined},
+    {"destroy",			container_destroy},
+    {"init_pid",		container_init_pid},
+    {"name",			container_name},
+    {"running",			container_running},
+    {"state",			container_state},
+    {"freeze",			container_freeze},
+    {"unfreeze",		container_unfreeze},
+    {"start",			container_start},
+    {"stop",			container_stop},
+    {"shutdown",		container_shutdown},
+    {"wait",			container_wait},
+
+    {"config_file_name",	container_config_file_name},
+    {"load_config",		container_load_config},
+    {"save_config",		container_save_config},
+    {"get_config_item",		container_get_config_item},
+    {"set_config_item",		container_set_config_item},
+    {"clear_config_item",	container_clear_config_item},
+    {"get_keys",		container_get_keys},
+    {NULL, NULL}
+};
+
+static int lxc_version_get(lua_State *L) {
+    lua_pushstring(L, VERSION);
+    return 1;
+}
+
+static int lxc_path_get(lua_State *L) {
+    lua_pushstring(L, LXCPATH);
+    return 1;
+}
+
+static luaL_Reg lxc_lib_methods[] = {
+    {"version_get",		lxc_version_get},
+    {"path_get",		lxc_path_get},
+    {"container_new",		container_new},
+    {NULL, NULL}
+};
+
+static int lxc_lib_uninit(lua_State *L) {
+    (void) L;
+    /* this is where we would fini liblxc.so if we needed to */
+    return 0;
+}
+
+LUALIB_API int luaopen_lxc_core(lua_State *L) {
+    /* this is where we would initialize liblxc.so if we needed to */
+
+    luaL_register(L, "lxc", lxc_lib_methods);
+
+    lua_newuserdata(L, 0);
+    lua_newtable(L);  /* metatable */
+    lua_pushvalue(L, -1);
+    lua_pushliteral(L, "__gc");
+    lua_pushcfunction(L, lxc_lib_uninit);
+    lua_rawset(L, -3);
+    lua_setmetatable(L, -3);
+    lua_rawset(L, -3);
+
+    luaL_newmetatable(L, CONTAINER_TYPENAME);
+    lua_pushvalue(L, -1);  /* push metatable */
+    lua_pushstring(L, "__gc");
+    lua_pushcfunction(L, container_gc);
+    lua_settable(L, -3);
+    lua_setfield(L, -2, "__index");  /* metatable.__index = metatable */
+    luaL_register(L, NULL, lxc_container_methods);
+    lua_pop(L, 1);
+    return 1;
+}
diff --git a/src/lua-lxc/lxc-top b/src/lua-lxc/lxc-top
new file mode 100644
index 0000000..31aaecf
--- /dev/null
+++ b/src/lua-lxc/lxc-top
@@ -0,0 +1,242 @@
+#!/usr/bin/env lua
+--
+-- top(1) like monitor for lxc containers
+--
+-- Copyright © 2012 Oracle.
+--
+-- Authors:
+-- Dwight Engen <dwight.engen at oracle.com>
+--
+-- This library is free software; you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License version 2, as
+-- published by the Free Software Foundation.
+--
+-- This program 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 General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along
+-- with this program; if not, write to the Free Software Foundation, Inc.,
+-- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+--
+
+local lxc    = require("lxc")
+local getopt = require("alt_getopt")
+local lfs    = require("lfs")
+
+local USER_HZ   = 100
+local ESC       = string.format("%c", 27)
+local TERMCLEAR = ESC.."[H"..ESC.."[J"
+local TERMNORM  = ESC.."[0m"
+local TERMBOLD  = ESC.."[1m"
+local TERMRVRS  = ESC.."[7m"
+
+local containers = {}
+local stats = {}
+local stats_total = {}
+local max_containers
+
+function printf(...)
+    local function wrapper(...) io.write(string.format(...)) end
+    local status, result = pcall(wrapper, ...)
+    if not status then
+	error(result, 2)
+    end
+end
+
+function string:split(delim, max_cols)
+    local cols = {}
+    local start = 1
+    local nextc
+    repeat
+	nextc = string.find(self, delim, start)
+	if (nextc and #cols ~= max_cols - 1) then
+	    table.insert(cols, string.sub(self, start, nextc-1))
+	    start = nextc + #delim
+	else
+	    table.insert(cols, string.sub(self, start, string.len(self)))
+	    nextc = nil
+	end
+    until nextc == nil or start > #self
+    return cols
+end
+
+function strsisize(size, width)
+    local KiB = 1024
+    local MiB = 1048576
+    local GiB = 1073741824
+    local TiB = 1099511627776
+    local PiB = 1125899906842624
+    local EiB = 1152921504606846976
+    local ZiB = 1180591620717411303424
+
+    if (size >= ZiB) then
+	return string.format("%d.%2.2d ZB", size / ZiB, (math.floor(size % ZiB) * 100) / ZiB)
+    end
+    if (size >= EiB) then
+	return string.format("%d.%2.2d EB", size / EiB, (math.floor(size % EiB) * 100) / EiB)
+    end
+    if (size >= PiB) then
+	return string.format("%d.%2.2d PB", size / PiB, (math.floor(size % PiB) * 100) / PiB)
+    end
+    if (size >= TiB) then
+	return string.format("%d.%2.2d TB", size / TiB, (math.floor(size % TiB) * 100) / TiB)
+    end
+    if (size >= GiB) then
+	return string.format("%d.%2.2d GB", size / GiB, (math.floor(size % GiB) * 100) / GiB)
+    end
+    if (size >= MiB) then
+	return string.format("%d.%2.2d MB", size / MiB, (math.floor(size % MiB) * 1000) / (MiB * 10))
+    end
+    if (size >= KiB) then
+	return string.format("%d.%2.2d KB", size / KiB, (math.floor(size % KiB) * 1000) / (KiB * 10))
+    end
+    return string.format("%3d.00   ", size)
+end
+
+function usleep(n)
+    if (n ~= 0) then
+	ret = os.execute("usleep " .. tonumber(n))
+	if (ret ~= 0) then
+	    os.exit(0)
+	end
+    end
+end
+
+function tty_lines()
+    local rows = 25
+    local f = assert(io.popen("stty -a | head -n 1"))
+    for line in f:lines() do
+	local stty_rows
+	_,_,stty_rows = string.find(line, "rows (%d+)")
+	if (stty_rows ~= nil) then
+	    rows = stty_rows
+	    break
+	end
+    end
+    f:close()
+    return rows
+end
+
+function container_sort(a, b)
+    if (optarg["r"]) then
+	if     (optarg["s"] == "n") then return (a > b)
+	elseif (optarg["s"] == "c") then return (stats[a].cpu_use_nanos < stats[b].cpu_use_nanos)
+	elseif (optarg["s"] == "d") then return (stats[a].blkio < stats[b].blkio)
+	elseif (optarg["s"] == "m") then return (stats[a].mem_used < stats[b].mem_used)
+	end
+    else
+	if     (optarg["s"] == "n") then return (a < b)
+	elseif (optarg["s"] == "c") then return (stats[a].cpu_use_nanos > stats[b].cpu_use_nanos)
+	elseif (optarg["s"] == "d") then return (stats[a].blkio > stats[b].blkio)
+	elseif (optarg["s"] == "m") then return (stats[a].mem_used > stats[b].mem_used)
+	end
+    end
+end
+
+function container_list_update()
+    local now_running
+
+    lxc.stats_clear(stats_total)
+    now_running = lxc.containers_running(true)
+
+    -- check for newly started containers
+    for _,v in ipairs(now_running) do
+	if (containers[v] == nil) then
+	    local ct = lxc.container:new(v)
+	    -- note, this is a "mixed" table, ie both dictionary and list
+	    containers[v] = ct
+	    table.insert(containers, v)
+	end
+    end
+
+    -- check for newly stopped containers
+    local indx = 1
+    while (indx <= #containers) do
+	local ctname = containers[indx]
+	if (now_running[ctname] == nil) then
+	    containers[ctname] = nil
+	    stats[ctname] = nil
+	    table.remove(containers, indx)
+	else
+	    indx = indx + 1
+	end
+    end
+
+    -- get stats for all current containers and resort the list
+    lxc.stats_clear(stats_total)
+    for _,ctname in ipairs(containers) do
+	stats[ctname] = containers[ctname]:stats_get(stats_total)
+    end
+    table.sort(containers, container_sort)
+end
+
+function stats_print_header()
+    printf(TERMRVRS .. TERMBOLD)
+    printf("%-15s %8s %8s %8s %10s %10s\n", "Container", "CPU",  "CPU",  "CPU",  "BlkIO", "Mem")
+    printf("%-15s %8s %8s %8s %10s %10s\n", "Name",      "Used", "Sys",  "User", "Total", "Used")
+    printf(TERMNORM)
+end
+
+function stats_print(name, stats)
+    printf("%-15s %8.2f %8.2f %8.2f %10s %10s",
+	   name,
+	   stats.cpu_use_nanos / 1000000000,
+	   stats.cpu_use_sys  / USER_HZ,
+	   stats.cpu_use_user / USER_HZ,
+	   strsisize(stats.blkio),
+	   strsisize(stats.mem_used))
+end
+
+function usage()
+    printf("Usage: lxc-top [options]\n" ..
+	"  -h|--help      print this help message\n" ..
+	"  -m|--max       display maximum number of containers\n" ..
+	"  -d|--delay     delay in seconds between refreshes (default: 3.0)\n" ..
+	"  -s|--sort      sort by [n,c,d,m] (default: n) where\n" ..
+	"                 n = Name\n" ..
+	"                 c = CPU use\n" ..
+	"                 d = Disk I/O use\n" ..
+	"                 m = Memory use\n" ..
+	"  -r|--reverse   sort in reverse (descending) order\n"
+    )
+    os.exit(1)
+end
+
+local long_opts = {
+    help      = "h",
+    delay     = "d",
+    max       = "m",
+    reverse   = "r",
+    sort      = "s",
+}
+
+optarg,optind = alt_getopt.get_opts (arg, "hd:m:rs:", long_opts)
+optarg["d"] = tonumber(optarg["d"]) or 3.0
+optarg["m"] = tonumber(optarg["m"]) or tonumber(tty_lines() - 3)
+optarg["r"] = optarg["r"] or false
+optarg["s"] = optarg["s"] or "n"
+if (optarg["h"] ~= nil) then
+    usage()
+end
+
+while true
+do
+    container_list_update()
+    -- if some terminal we care about doesn't support the simple escapes, we
+    -- may fall back to this, or ncurses. ug.
+    --os.execute("tput clear")
+    printf(TERMCLEAR)
+    stats_print_header()
+    for index,ctname in ipairs(containers) do
+	stats_print(ctname, stats[ctname])
+	printf("\n")
+	if (index >= optarg["m"]) then
+	    break
+	end
+    end
+    stats_print(string.format("TOTAL (%-2d)", #containers), stats_total)
+    io.flush()
+    usleep(optarg["d"] * 1000000)
+end
diff --git a/src/lua-lxc/lxc.lua b/src/lua-lxc/lxc.lua
new file mode 100644
index 0000000..c71de48
--- /dev/null
+++ b/src/lua-lxc/lxc.lua
@@ -0,0 +1,412 @@
+--
+-- lua lxc module
+--
+-- Copyright © 2012 Oracle.
+--
+-- Authors:
+-- Dwight Engen <dwight.engen at oracle.com>
+--
+-- This library is free software; you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License version 2, as
+-- published by the Free Software Foundation.
+--
+-- This program 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 General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along
+-- with this program; if not, write to the Free Software Foundation, Inc.,
+-- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+--
+
+local core   = require("lxc.core")
+local lfs    = require("lfs")
+local table  = require("table")
+local string = require("string")
+local io     = require("io")
+module("lxc", package.seeall)
+
+local lxc_path
+local cgroup_path
+local log_level = 3
+
+-- the following two functions can be useful for debugging
+function printf(...)
+    local function wrapper(...) io.write(string.format(...)) end
+    local status, result = pcall(wrapper, ...)
+    if not status then
+	error(result, 2)
+    end
+end
+
+function log(level, ...)
+    if (log_level >= level) then
+	printf(os.date("%Y-%m-%d %T "))
+	printf(...)
+    end
+end
+
+function string:split(delim, max_cols)
+    local cols = {}
+    local start = 1
+    local nextc
+    repeat
+	nextc = string.find(self, delim, start)
+	if (nextc and #cols ~= max_cols - 1) then
+	    table.insert(cols, string.sub(self, start, nextc-1))
+	    start = nextc + #delim
+	else
+	    table.insert(cols, string.sub(self, start, string.len(self)))
+	    nextc = nil
+	end
+    until nextc == nil or start > #self
+    return cols
+end
+
+function dirname(path)
+    local f,output
+    f = io.popen("dirname " .. path)
+    output = f:read('*all')
+    f:close()
+    return output:sub(1,-2)
+end
+
+function basename(path, suffix)
+    local f,output
+    f = io.popen("basename " .. path .. " " .. (suffix or ""))
+    output = f:read('*all')
+    f:close()
+    return output:sub(1,-2)
+end
+
+function cgroup_path_get()
+    local f,line,cgroup_path
+
+    f = io.open("/proc/mounts", "r")
+    if (f) then
+	while true do
+	    local c
+	    line = f:read()
+	    c = line:split(" ", 6)
+	    if (c[1] == "cgroup") then
+		cgroup_path = dirname(c[2])
+		break
+	    end
+	end
+	f:close()
+    end
+    if (not cgroup_path) then
+	cgroup_path = "/sys/fs/cgroup"
+    end
+    return cgroup_path
+end
+
+-- container class
+container = {}
+container_mt = {}
+container_mt.__index = container
+
+function container:new(lname)
+    local lcore
+    local lnetcfg = {}
+    local lstats = {}
+
+    if lname then
+	lcore = core.container_new(lname)
+    end
+
+    return setmetatable({ctname = lname, core = lcore, netcfg = lnetcfg, stats = lstats}, container_mt)
+end
+
+-- methods interfacing to core functionality
+function container:config_file_name()
+    return self.core:config_file_name()
+end
+
+function container:defined()
+    return self.core:defined()
+end
+
+function container:init_pid()
+    return self.core:init_pid()
+end
+
+function container:name()
+    return self.core:name()
+end
+
+function container:start()
+    return self.core:start()
+end
+
+function container:stop()
+    return self.core:stop()
+end
+
+function container:shutdown(timeout)
+    return self.core:shutdown(timeout)
+end
+
+function container:wait(state, timeout)
+    return self.core:wait(state, timeout)
+end
+
+function container:freeze()
+    return self.core:freeze()
+end
+
+function container:unfreeze()
+    return self.core:unfreeze()
+end
+
+function container:running()
+    return self.core:running()
+end
+
+function container:state()
+    return self.core:state()
+end
+
+function container:create(template, ...)
+    return self.core:create(template, ...)
+end
+
+function container:destroy()
+    return self.core:destroy()
+end
+
+function container:append_config_item(key, value)
+    return self.core:set_config_item(key, value)
+end
+
+function container:clear_config_item(key)
+    return self.core:clear_config_item(key)
+end
+
+function container:get_config_item(key)
+    local value
+    local vals = {}
+
+    value = self.core:get_config_item(key)
+
+    -- check if it is a single item
+    if (not value or not string.find(value, "\n")) then
+	return value
+    end
+
+    -- it must be a list type item, make a table of it
+    vals = value:split("\n", 1000)
+    -- make it a "mixed" table, ie both dictionary and list for ease of use
+    for _,v in ipairs(vals) do
+	vals[v] = true
+    end
+    return vals
+end
+
+function container:set_config_item(key, value)
+    return self.core:set_config_item(key, value)
+end
+
+function container:get_keys(base)
+    local ktab = {}
+    local keys
+
+    if (base) then
+	keys = self.core:get_keys(base)
+	base = base .. "."
+    else
+	keys = self.core:get_keys()
+	base = ""
+    end
+    if (keys == nil) then
+	return nil
+    end
+    keys = keys:split("\n", 1000)
+    for _,v in ipairs(keys) do
+	local config_item = base .. v
+	ktab[v] = self.core:get_config_item(config_item)
+    end
+    return ktab
+end
+
+function container:load_config(alt_path)
+    if (alt_path) then
+	return self.core:load_config(alt_path)
+    else
+	return self.core:load_config()
+    end
+end
+
+function container:save_config(alt_path)
+    if (alt_path) then
+	return self.core:save_config(alt_path)
+    else
+	return self.core:save_config()
+    end
+end
+
+-- methods for stats collection from various cgroup files
+-- read integers at given coordinates from a cgroup file
+function container:stat_get_ints(controller, item, coords)
+    local f = io.open(cgroup_path.."/"..controller.."/lxc/"..self.ctname.."/"..item, "r")
+    local lines = {}
+    local result = {}
+
+    if (not f) then
+	for k,c in ipairs(coords) do
+	    table.insert(result, 0)
+	end
+    else
+	for line in f:lines() do
+	    table.insert(lines, line)
+	end
+	f:close()
+	for k,c in ipairs(coords) do
+	    local col
+
+	    col = lines[c[1]]:split(" ", 80)
+	    local val = tonumber(col[c[2]])
+	    table.insert(result, val)
+	end
+    end
+    return unpack(result)
+end
+
+-- read an integer from a cgroup file
+function container:stat_get_int(controller, item)
+    local f = io.open(cgroup_path.."/"..controller.."/lxc/"..self.ctname.."/"..item, "r")
+    if (not f) then
+	return 0
+    end
+
+    local line = f:read()
+    f:close()
+    -- if line is nil (on an error like Operation not supported because
+    -- CONFIG_MEMCG_SWAP_ENABLED isn't enabled) return 0
+    return tonumber(line) or 0
+end
+
+function container:stat_match_get_int(controller, item, match, column)
+    local val
+    local f = io.open(cgroup_path.."/"..controller.."/lxc/"..self.ctname.."/"..item, "r")
+    if (not f) then
+	return 0
+    end
+
+    for line in f:lines() do
+	printf("matching line:%s with match:%s\n", line, match)
+	if (string.find(line, match)) then
+	    local col
+
+	    col = line:split(" ", 80)
+	    val = tonumber(col[column]) or 0
+	    printf("found line!! val:%d\n", val)
+	end
+    end
+    f:close()
+    return val
+end
+
+function stats_clear(stat)
+    stat.mem_used      = 0
+    stat.mem_limit     = 0
+    stat.memsw_used    = 0
+    stat.memsw_limit   = 0
+    stat.cpu_use_nanos = 0
+    stat.cpu_use_user  = 0
+    stat.cpu_use_sys   = 0
+    stat.blkio         = 0
+end
+
+function container:stats_get(total)
+    local stat = {}
+    stat.mem_used      = self:stat_get_int("memory",  "memory.usage_in_bytes")
+    stat.mem_limit     = self:stat_get_int("memory",  "memory.limit_in_bytes")
+    stat.memsw_used    = self:stat_get_int("memory",  "memory.memsw.usage_in_bytes")
+    stat.memsw_limit   = self:stat_get_int("memory",  "memory.memsw.limit_in_bytes")
+    stat.cpu_use_nanos = self:stat_get_int("cpuacct", "cpuacct.usage")
+    stat.cpu_use_user,
+    stat.cpu_use_sys   = self:stat_get_ints("cpuacct", "cpuacct.stat", {{1, 2}, {2, 2}})
+    stat.blkio         = self:stat_match_get_int("blkio", "blkio.throttle.io_service_bytes", "Total", 2)
+
+    if (total) then
+	total.mem_used      = total.mem_used      + stat.mem_used
+	total.mem_limit     = total.mem_limit     + stat.mem_limit
+	total.memsw_used    = total.memsw_used    + stat.memsw_used
+	total.memsw_limit   = total.memsw_limit   + stat.memsw_limit
+	total.cpu_use_nanos = total.cpu_use_nanos + stat.cpu_use_nanos
+	total.cpu_use_user  = total.cpu_use_user  + stat.cpu_use_user
+	total.cpu_use_sys   = total.cpu_use_sys   + stat.cpu_use_sys
+	total.blkio         = total.blkio         + stat.blkio
+    end
+    return stat
+end
+
+
+
+-- return configured containers found in LXC_PATH directory
+function containers_configured(names_only)
+    local containers = {}
+
+    for dir in lfs.dir(lxc_path) do
+	if (dir ~= "." and dir ~= "..")
+	then
+	    local cfgfile = lxc_path .. "/" .. dir .. "/config"
+	    local cfgattr = lfs.attributes(cfgfile)
+
+	    if (cfgattr and cfgattr.mode == "file") then
+		if (names_only) then
+		    -- note, this is a "mixed" table, ie both dictionary and list
+		    containers[dir] = true
+		    table.insert(containers, dir)
+		else
+		    local ct = container:new(dir)
+		    -- note, this is a "mixed" table, ie both dictionary and list
+		    containers[dir] = ct
+		    table.insert(containers, dir)
+		end
+	    end
+	end
+    end
+    table.sort(containers, function (a,b) return (a < b) end)
+    return containers
+end
+
+-- return running containers found in cgroup fs
+function containers_running(names_only)
+    local containers = {}
+    local attr
+
+    -- the lxc directory won't exist if no containers has ever been started
+    attr = lfs.attributes(cgroup_path .. "/cpu/lxc")
+    if (not attr) then
+	return containers
+    end
+
+    for file in lfs.dir(cgroup_path .. "/cpu/lxc") do
+	if (file ~= "." and file ~= "..")
+	then
+	    local pathfile = cgroup_path .. "/cpu/lxc/" .. file
+	    local attr = lfs.attributes(pathfile)
+
+	    if (attr.mode == "directory") then
+		if (names_only) then
+		    -- note, this is a "mixed" table, ie both dictionary and list
+		    containers[file] = true
+		    table.insert(containers, file)
+		else
+		    local ct = container:new(file)
+		    -- note, this is a "mixed" table, ie both dictionary and list
+		    containers[file] = ct
+		    table.insert(containers, file)
+		end
+	    end
+	end
+    end
+    table.sort(containers, function (a,b) return (a < b) end)
+    return containers
+end
+
+lxc_path = core.path_get()
+cgroup_path = cgroup_path_get()
diff --git a/src/lua-lxc/test/apitest.lua b/src/lua-lxc/test/apitest.lua
new file mode 100644
index 0000000..14d2a9d
--- /dev/null
+++ b/src/lua-lxc/test/apitest.lua
@@ -0,0 +1,302 @@
+#!/usr/bin/env lua
+--
+-- test the lxc lua api
+--
+-- Copyright © 2012 Oracle.
+--
+-- Authors:
+-- Dwight Engen <dwight.engen at oracle.com>
+--
+-- This library is free software; you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License version 2, as
+-- published by the Free Software Foundation.
+--
+-- This program 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 General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along
+-- with this program; if not, write to the Free Software Foundation, Inc.,
+-- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+--
+
+local lxc     = require("lxc")
+local getopt  = require("alt_getopt")
+
+local LXC_PATH		= lxc.path_get()
+
+local container
+local cfg_containers	= {}
+local optarg		= {}
+local optind		= {}
+
+function printf(...)
+    local function wrapper(...) io.write(string.format(...)) end
+    local status, result = pcall(wrapper, ...)
+    if not status then
+	error(result, 2)
+    end
+end
+
+function log(level, ...)
+    if (optarg["v"] >= level) then
+	printf(os.date("%Y-%m-%d %T "))
+	printf(...)
+	printf("\n")
+    end
+end
+
+function die(...)
+    printf(...)
+    os.exit(1)
+end
+
+function test_global_info()
+    local cfg_containers
+    local run_containers
+
+    log(0, "%-20s %s", "LXC version:", lxc.version_get())
+    log(0, "%-20s %s", "Container name:", optarg["n"])
+    if (optarg["c"]) then
+	log(0, "%-20s %s", "Creating container:", "yes")
+	log(0, "%-20s %s", "With template:", optarg["t"])
+    end
+    log(0, "%-20s %s", "Containers path:", LXC_PATH)
+
+    cfg_containers = lxc.containers_configured()
+    log(0, "%-20s", "Containers configured:")
+    for _,v in ipairs(cfg_containers) do
+	log(0, "  %s", v)
+    end
+
+    run_containers = lxc.containers_running(true)
+    log(0, "%-20s", "Containers running:")
+    for _,v in ipairs(run_containers) do
+	log(0, "  %s", v)
+    end
+end
+
+function test_container_new()
+    container = lxc.container:new(optarg["n"])
+    assert(container ~= nil)
+    assert(container:config_file_name() == string.format("%s/%s/config", LXC_PATH, optarg["n"]))
+end
+
+function test_container_create()
+    if (optarg["c"]) then
+	log(0, "%-20s %s", "Destroy existing container:", optarg["n"])
+	container:destroy()
+	assert(container:defined() == false)
+    else
+	local cfg_containers = lxc.containers_configured()
+	if (cfg_containers[optarg["n"]]) then
+	    log(0, "%-20s %s", "Use existing container:", optarg["n"])
+	    return
+	end
+    end
+    log(0, "%-20s %s", "Creating rootfs using:", optarg["t"])
+    container:create(optarg["t"])
+    assert(container:defined() == true)
+    assert(container:name() == optarg["n"])
+end
+
+function test_container_started()
+    local now_running
+    log(2, "state:%s pid:%d\n", container:state(), container:init_pid())
+    assert(container:init_pid() > 1)
+    assert(container:running() == true)
+    assert(container:state() == "RUNNING")
+    now_running = lxc.containers_running(true)
+    assert(now_running[optarg["n"]] ~= nil)
+    log(1, "%-20s %s", "Running, init pid:", container:init_pid())
+end
+
+function test_container_stopped()
+    local now_running
+    assert(container:init_pid() == -1)
+    assert(container:running() == false)
+    assert(container:state() == "STOPPED")
+    now_running = lxc.containers_running(true)
+    assert(now_running[optarg["n"]] == nil)
+end
+
+function test_container_frozen()
+    local now_running
+    assert(container:init_pid() > 1)
+    assert(container:running() == true)
+    assert(container:state() == "FROZEN")
+    now_running = lxc.containers_running(true)
+    assert(now_running[optarg["n"]] ~= nil)
+end
+
+function test_container_start()
+    log(0, "Starting...")
+    if (not container:start()) then
+	log(1, "Start returned failure, waiting another 10 seconds...")
+	container:wait("RUNNING", 10)
+    end
+    container:wait("RUNNING", 1)
+end
+
+function test_container_stop()
+    log(0, "Stopping...")
+    if (not container:stop()) then
+	log(1, "Stop returned failure, waiting another 10 seconds...")
+	container:wait("STOPPED", 10)
+    end
+    container:wait("STOPPED", 1)
+end
+
+function test_container_freeze()
+    log(0, "Freezing...")
+    if (not container:freeze()) then
+	log(1, "Freeze returned failure, waiting another 10 seconds...")
+	container:wait("FROZEN", 10)
+    end
+end
+
+function test_container_unfreeze()
+    log(0, "Unfreezing...")
+    if (not container:unfreeze()) then
+	log(1, "Unfreeze returned failure, waiting another 10 seconds...")
+	container:wait("RUNNING", 10)
+    end
+end
+
+function test_container_shutdown()
+    log(0, "Shutting down...")
+    container:shutdown(5)
+
+    if (container:running()) then
+	test_container_stop()
+    end
+end
+
+function test_container_in_cfglist(should_find)
+    local cfg_containers = lxc.containers_configured()
+
+    if (should_find) then
+	assert(cfg_containers[container:name()] ~= nil)
+    else
+	assert(cfg_containers[container:name()] == nil)
+    end
+end
+
+function test_config_items()
+    log(0, "Test set/clear configuration items...")
+
+    -- test setting a 'single type' item
+    assert(container:get_config_item("lxc.utsname") == optarg["n"])
+    container:set_config_item("lxc.utsname", "foobar")
+    assert(container:get_config_item("lxc.utsname") == "foobar")
+    container:set_config_item("lxc.utsname", optarg["n"])
+    assert(container:get_config_item("lxc.utsname") == optarg["n"])
+
+    -- test clearing/setting a 'list type' item
+    container:clear_config_item("lxc.cap.drop")
+    container:set_config_item("lxc.cap.drop", "new_cap1")
+    container:set_config_item("lxc.cap.drop", "new_cap2")
+    local cap_drop = container:get_config_item("lxc.cap.drop")
+    assert(cap_drop["new_cap1"] ~= nil)
+    assert(cap_drop["new_cap2"] ~= nil)
+    -- note: clear_config_item only works on list type items
+    container:clear_config_item("lxc.cap.drop")
+    assert(container:get_config_item("lxc.cap.drop") == nil)
+
+    local altname = "/tmp/" .. optarg["n"] .. ".altconfig"
+    log(0, "Test saving to an alternate (%s) config file...", altname)
+    assert(container:save_config(altname))
+    assert(os.remove(altname))
+end
+
+function test_config_mount_entries()
+    local mntents
+
+    -- mount entries are a list type item
+    mntents = container:get_config_item("lxc.mount.entry")
+    log(0, "Mount entries:")
+    for _,v in ipairs(mntents) do
+	log(0, "  %s", v)
+    end
+end
+
+function test_config_keys()
+    local keys
+
+    keys = container:get_keys()
+    log(0, "Top level keys:")
+    for k,v in pairs(keys) do
+	log(0, "  %s = %s", k, v or "")
+    end
+end
+
+function test_config_network(net_nr)
+    log(0, "Test network %d config...", net_nr)
+    local netcfg
+
+    netcfg = container:get_keys("lxc.network." .. net_nr)
+    if (netcfg == nil) then
+	return
+    end
+    for k,v in pairs(netcfg) do
+	log(0, "  %s = %s", k, v or "")
+    end
+    assert(netcfg["flags"] == "up")
+    assert(container:get_config_item("lxc.network."..net_nr..".type") == "veth")
+end
+
+
+function usage()
+    die("Usage: apitest <options>\n" ..
+	"  -v|--verbose        increase verbosity with each -v\n" ..
+	"  -h|--help           print help message\n" ..
+	"  -n|--name           name of container to use for testing\n" ..
+	"  -c|--create         create the test container anew\n" ..
+	"  -l|--login          do interactive login test\n" ..
+	"  -t|--template       template to use when creating test container\n"
+    )
+end
+
+local long_opts = {
+    verbose       = "v",
+    help          = "h",
+    name          = "n",
+    create        = "c",
+    template      = "t",
+}
+
+optarg,optind = alt_getopt.get_opts (arg, "hvn:ct:", long_opts)
+optarg["v"] = tonumber(optarg["v"]) or 0
+optarg["n"] = optarg["n"] or "lua-apitest"
+optarg["c"] = optarg["c"] or nil
+optarg["t"] = optarg["t"] or "busybox"
+if (optarg["h"] ~= nil) then
+    usage()
+end
+
+test_global_info()
+test_container_new()
+test_container_create()
+test_container_stopped()
+test_container_in_cfglist(true)
+
+test_config_items()
+test_config_keys()
+test_config_mount_entries()
+test_config_network(0)
+
+test_container_start()
+test_container_started()
+
+test_container_freeze()
+test_container_frozen()
+test_container_unfreeze()
+test_container_started()
+
+test_container_shutdown()
+test_container_stopped()
+container:destroy()
+test_container_in_cfglist(false)
+
+log(0, "All tests passed")
-- 
1.7.12.3





More information about the lxc-devel mailing list