Skip to content

Plugin API

Konstantin Osipov edited this page Oct 3, 2013 · 6 revisions

Tarantool Plugin API

In Tarantool, in addition to Lua stored procedurs, it's possible to write C plugins. These plugins have full access to server core and add the features not present in the s tock version. For example, they make it possible to implement a custom client/server protocol or turn Tarantool into HTTP server, or connect to a database.

The first two existing plugins are connectors to PostgreSQL and MySQL.

They make it possible to run queries against these databases from within stored procedures. For example, this is how a procedure pulling data from MySQL to a Tarantool space may look like:

local dbh = box.net.sql.connect('mysql', 'host', port, user, password, database)

local cards = dbh:select('SELECT * FROM cards WHERE time > ?', box.time())

for i, card in pairs(cards) do
	box.replace(space, card.id, card.name, card.title)
end

Let's consider the steps necessary to write your own plugin using an example. Imagine Tarantool is used as a statistics store, and it's necessary to publish various counters in a monitoring application once in a while. We must also assume that the protocol to work with this system is an external library (since otherwise it would be simpler to just open a box.socket instance and push a packet into it). Perhaps it's a proprietary system and the protocol is closed.

To sum up, imagine we have

  • an external library, libmonitor
  • a library header file, monitor.h

To use the library in a C++ program, one typically writes a code like this:

#include <monitor.h>

struct monitor *mon = mon_connect("host", "auth");

mon_push(mon, value);

mon_close(mon);

Both methods, mon_connect and mon_push are "blocking", i.e. block the current thread while they execute, so can't be used directly in Lua, e.g. via FFI. Let's see how the library can be wrapped into a plugin, and its methods used in a non-blocking manner. For simplicity, exceptions and error handling is omitted.

1. Clone the source tree

git clone --recursive git@github.com:mailru/tarantool.git

2. Add plugin directory src/plugin/monitor

3. Add the plugin directory to the build list в src/plugin/CMakeLists.txt:

	add_subdirectory(monitor)

4. Add cmake-директивы, которые будут собирать наш плагин

(файл src/plugins/monitor/CMakeLists.txt):

add_library(monitor SHARED monitor.cc)
install(TARGETS monitor LIBRARY DESTINATION ${PLUGIN_DIR})

Первая директива говорит что весь наш плагин будет упакован в 'so'-файл с именем libmonitor.so и создан на базе файла monitor.cc (который мы создадим ниже). Вторая директива говорит о том что make install будет устанавливать эту библиотеку в тот же каталог куда устанавливаются все плагины.

5. собственно пишем плагин.

Каждый плагин для тарантула может использовать произвольные инструменты внутри тарантула (см. заголовочные файлы из директории include, а так же C/C++-файлы из директории src), для корректной работы плагин должен минимально задекларировать следующие вещи:

  • Имя плагина
  • Версия плагина (целое число)
  • Инициализационная функция
  • Возможно несколько дополнительных параметров.

для декларации этих параметров используется макрос DECLARE_PLUGIN. Итак, напишем наш плагин:

extern "C" {
        #include <lua.h>
        #include <lauxlib.h>
        #include <lualib.h>
}

#include <plugin.h>
#include <monitor.h>
#include <coeio.h>
#include <tarantool_ev.h>

static ssize_t
my_mon_connect(va_list ap)
{
	struct monitor **mon = va_arg(ap, typeof(mon));
	const char *host = va_arg(ap, typeof(host));
	const char *auth = va_arg(ap, typeof(auth));
	*mon = mon_connect(host, auth);
	return 0;
}

static ssize_t
my_mon_push(va_list ap)
{
	struct monitor **mon = va_arg(ap, typeof(mon));
	int value = va_arg(ap, typeof(value));
	mon_push(mon, value);
	return 0;
}


static int
lua_mon_send(struct lua_State *L)
{
	struct monitor *mon = *(struct monitor **)lua_touserdata(L, 1);
	int value = lua_tointeger(L, 2);

	coeio_custom(my_mon_push, TIMEOUT_INFINITY, mon, value);
	return 0;

}

static int
lua_mon_close(struct lua_State *L)
{
	struct monitor *mon = *(struct monitor **)lua_touserdata(L, 1);
	mon_close(mon);
}

static int
lua_mon_connect(struct lua_State *L)
{
	const char *host = lua_tostring(L, 1);
	const char *auth = lua_tostring(L, 2);

	struct monitor *mon = NULL;
	coeio_custom(my_mon_connect, TIMEOUT_INFINITY, &mon, host, auth);

	struct monitor **ptr = (struct monitor **)lua_newuserdata(sizeof(mon));
	ptr[0] = mon;

	lua_newtable(L);
	
	/* "деструктор" */
	lua_pushstring(L, "__gc");
	lua_pushcfunction(L, lua_mon_close);
	lua_rawset(L, -3);

	/* метод отправки данных */
	lua_pushstring(L, "__call");
	lua_pushcfunction(L, lua_mon_send);
	lua_rawset(L, -3);

	/* метатаблица для установленного соединения */
	lua_setmetatable(L, -2);

	return 1;
}

static void
init (struct lua_State *L)
{
	lua_pushcfunction(L, lua_mon_connect);
	lua_setfield(L, LUA_GLOBALSINDEX, "mon_connect");

}

DECLARE_PLUGIN("monitor", 1, init, NULL);

Итак, что мы видим в данном плагине:

  1. Заголовочные файлы lua*.h позволяют оперировать контекстом Lua из нашего плагина (подробнее см. документацию на lua и luajit)
  2. plugin.h определяем макрос DECLARE_PLUGIN
  3. monitor.h описан выше
  4. coeio.h - интерфейс доступа к блокирующим операциям из событийной
  5. tarantool_ev.h - константа TIMEOUT_INFINITY машины тарантула.

Работа плагина начинается с функции init. Эта функция вызывается в момент инициализации плагина. Ей передается lua-контекст тарантула. В этой функции мы регистрируем глобальную функцию mon_connect, которую в дальнейшем можно использовать в наших хранимых процедурах:

	local mon = mon_connect(host, auth) -- приконнектиться к монитору
	mon(123)	-- отправить число 123 в монитор
	mon(234)	-- отправить число 234 в монитор

При вызове функции mon_connect в lua, будет неявно вызвана функция lua_mon_connect. Которая при помощи coeio_custom сделает блокирующий вызов mon_connect и результаты преобразует в lua-объект с двумя метаметодами:

  • __gc - деструктор, вызывает mon_close
  • __call - будет вызывать mon_push

6. Компилируем плагин и тарантул:

	cmake .
	make

В каталоге src/plugins/monitor будет собран плагин libmonitor.so.

7. Замечания и примечания.

  • Тарантул загружает плагины из директории, определяемой стандартным префиксом cmake (обычно /usr{/local}/lib/tarantool), а так же по путям, определенным в переменной окружения TARANTOOL_PLUGIN_DIR
  • По факту загрузки каждого плагина в логе тарантула имеется соответствующая запись.
  • Мониторить загруженные плагины можно используя команду show plugins командного интерфейса.

Clone this wiki locally