mirror of
https://git.proxmox.com/git/mirror_lxc
synced 2025-08-05 11:44:48 +00:00
add lua binding for the lxc API
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@oracle.com> Acked-by: Stéphane Graber <stgraber@ubuntu.com>
This commit is contained in:
parent
68c152ef7a
commit
f080ffd7d6
@ -5,9 +5,14 @@ ACLOCAL_AMFLAGS = -I config
|
||||
SUBDIRS = config src templates doc
|
||||
DIST_SUBDIRS = config src templates doc
|
||||
EXTRA_DIST = autogen.sh lxc.spec CONTRIBUTING MAINTAINERS ChangeLog
|
||||
RPMARGS =
|
||||
|
||||
if ENABLE_LUA
|
||||
RPMARGS += --with lua
|
||||
endif
|
||||
|
||||
if ENABLE_PYTHON
|
||||
RPMARGS = --with python
|
||||
RPMARGS += --with python
|
||||
endif
|
||||
|
||||
pcdatadir = $(libdir)/pkgconfig
|
||||
|
20
configure.ac
20
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])],
|
||||
@ -289,6 +306,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
|
||||
@ -342,6 +360,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],[[]],[[]])
|
||||
|
@ -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
|
||||
|
164
doc/lxc-top.sgml.in
Normal file
164
doc/lxc-top.sgml.in
Normal file
@ -0,0 +1,164 @@
|
||||
<!--
|
||||
|
||||
Copyright © 2012 Oracle.
|
||||
|
||||
Authors:
|
||||
Dwight Engen <dwight.engen@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@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:
|
||||
-->
|
13
lxc.spec.in
13
lxc.spec.in
@ -38,6 +38,12 @@ Requires: python3
|
||||
BuildRequires: python3-devel
|
||||
%endif
|
||||
|
||||
%define with_lua %{?_with_lua: 1} %{?!_with_lua: 0}
|
||||
%if %{with_lua}
|
||||
Requires: lua-filesystem
|
||||
BuildRequires: lua-devel
|
||||
%endif
|
||||
|
||||
%description
|
||||
|
||||
The package "%{name}" provides the command lines to create and manage
|
||||
@ -69,6 +75,9 @@ development of the linux containers.
|
||||
%setup
|
||||
%build
|
||||
PATH=$PATH:/usr/sbin:/sbin %configure $args \
|
||||
%if %{with_lua}
|
||||
--enable-lua \
|
||||
%endif
|
||||
%if %{with_python}
|
||||
--enable-python \
|
||||
%endif
|
||||
@ -107,6 +116,10 @@ rm -rf %{buildroot}
|
||||
%defattr(-,root,root)
|
||||
%{_libdir}/*.so.*
|
||||
%{_libdir}/%{name}
|
||||
%if %{with_lua}
|
||||
%{_datadir}/lua
|
||||
%{_libdir}/lua
|
||||
%endif
|
||||
%if %{with_python}
|
||||
%{_libdir}/python*
|
||||
%endif
|
||||
|
@ -1 +1 @@
|
||||
SUBDIRS = lxc tests python-lxc
|
||||
SUBDIRS = lxc tests python-lxc lua-lxc
|
||||
|
26
src/lua-lxc/Makefile.am
Normal file
26
src/lua-lxc/Makefile.am
Normal file
@ -0,0 +1,26 @@
|
||||
if ENABLE_LUA
|
||||
|
||||
luadir=$(datadir)/lua/5.1
|
||||
sodir=$(libdir)/lua/5.1/lxc
|
||||
|
||||
lua_SCRIPTS=lxc.lua
|
||||
EXTRA_DIST=lxc.lua
|
||||
|
||||
so_PROGRAMS = core.so
|
||||
|
||||
core_so_SOURCES = core.c
|
||||
|
||||
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:
|
||||
|
||||
endif
|
382
src/lua-lxc/core.c
Normal file
382
src/lua-lxc/core.c
Normal file
@ -0,0 +1,382 @@
|
||||
/*
|
||||
* lua-lxc: lua bindings for lxc
|
||||
*
|
||||
* Copyright © 2012 Oracle.
|
||||
*
|
||||
* Authors:
|
||||
* Dwight Engen <dwight.engen@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;
|
||||
}
|
412
src/lua-lxc/lxc.lua
Executable file
412
src/lua-lxc/lxc.lua
Executable file
@ -0,0 +1,412 @@
|
||||
--
|
||||
-- lua lxc module
|
||||
--
|
||||
-- Copyright © 2012 Oracle.
|
||||
--
|
||||
-- Authors:
|
||||
-- Dwight Engen <dwight.engen@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()
|
302
src/lua-lxc/test/apitest.lua
Executable file
302
src/lua-lxc/test/apitest.lua
Executable file
@ -0,0 +1,302 @@
|
||||
#!/usr/bin/env lua
|
||||
--
|
||||
-- test the lxc lua api
|
||||
--
|
||||
-- Copyright © 2012 Oracle.
|
||||
--
|
||||
-- Authors:
|
||||
-- Dwight Engen <dwight.engen@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")
|
@ -129,8 +129,9 @@ bin_SCRIPTS = \
|
||||
lxc-shutdown \
|
||||
lxc-destroy
|
||||
|
||||
EXTRA_DIST=
|
||||
if ENABLE_PYTHON
|
||||
EXTRA_DIST = lxc-device lxc-ls
|
||||
EXTRA_DIST += lxc-device lxc-ls
|
||||
bin_SCRIPTS += lxc-device
|
||||
bin_SCRIPTS += lxc-ls
|
||||
bin_SCRIPTS += lxc-start-ephemeral
|
||||
@ -138,6 +139,11 @@ else
|
||||
bin_SCRIPTS += legacy/lxc-ls
|
||||
endif
|
||||
|
||||
if ENABLE_LUA
|
||||
EXTRA_DIST += lxc-top
|
||||
bin_SCRIPTS += lxc-top
|
||||
endif
|
||||
|
||||
bin_PROGRAMS = \
|
||||
lxc-attach \
|
||||
lxc-unshare \
|
||||
|
242
src/lxc/lxc-top
Executable file
242
src/lxc/lxc-top
Executable file
@ -0,0 +1,242 @@
|
||||
#!/usr/bin/env lua
|
||||
--
|
||||
-- top(1) like monitor for lxc containers
|
||||
--
|
||||
-- Copyright © 2012 Oracle.
|
||||
--
|
||||
-- Authors:
|
||||
-- Dwight Engen <dwight.engen@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
|
Loading…
Reference in New Issue
Block a user