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:
Dwight Engen 2013-01-24 11:42:22 -05:00 committed by Stéphane Graber
parent 68c152ef7a
commit f080ffd7d6
12 changed files with 1579 additions and 3 deletions

View File

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

View File

@ -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],[[]],[[]])

View File

@ -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
View 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:
-->

View File

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

View File

@ -1 +1 @@
SUBDIRS = lxc tests python-lxc
SUBDIRS = lxc tests python-lxc lua-lxc

26
src/lua-lxc/Makefile.am Normal file
View 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
View 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
View 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
View 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")

View File

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