[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