From cb33c5c0de316895e1597c333f1e8f27b66c27ab Mon Sep 17 00:00:00 2001 From: Noah Watkins Date: Mon, 18 Jan 2016 18:20:04 -0800 Subject: [PATCH] cls-lua: write object classes in Lua Introduces cls_lua that allows object classes to be created dynamically using the Lua language. Each request is processed in an empty Lua VM instance, and scripts can be submitted using bufferlist or JSON encoding. Signed-off-by: Noah Watkins --- src/cls/CMakeLists.txt | 14 + src/cls/lua/cls_lua.cc | 1053 ++++++++++++++++++++++++++++ src/cls/lua/cls_lua.h | 14 + src/cls/lua/cls_lua_client.cc | 37 + src/cls/lua/cls_lua_client.h | 13 + src/cls/lua/cls_lua_ops.h | 27 + src/cls/lua/lua_bufferlist.cc | 180 +++++ src/test/CMakeLists.txt | 1 + src/test/cls_lua/CMakeLists.txt | 18 + src/test/cls_lua/test_cls_lua.cc | 1106 ++++++++++++++++++++++++++++++ 10 files changed, 2463 insertions(+) create mode 100644 src/cls/lua/cls_lua.cc create mode 100644 src/cls/lua/cls_lua.h create mode 100644 src/cls/lua/cls_lua_client.cc create mode 100644 src/cls/lua/cls_lua_client.h create mode 100644 src/cls/lua/cls_lua_ops.h create mode 100644 src/cls/lua/lua_bufferlist.cc create mode 100644 src/test/cls_lua/CMakeLists.txt create mode 100644 src/test/cls_lua/test_cls_lua.cc diff --git a/src/cls/CMakeLists.txt b/src/cls/CMakeLists.txt index 185da04f7f8c5..9741f3697391b 100644 --- a/src/cls/CMakeLists.txt +++ b/src/cls/CMakeLists.txt @@ -136,3 +136,17 @@ endif (WITH_CEPHFS) add_library(cls_numops_client STATIC numops/cls_numops_client.cc) +add_library(cls_lua SHARED + lua/cls_lua.cc + lua/lua_bufferlist.cc +) +set_target_properties(cls_lua PROPERTIES VERSION "1.0.0" SOVERSION "1") +install(TARGETS cls_lua DESTINATION ${cls_dir}) +target_link_libraries(cls_lua + liblua + json_spirit +) + +add_library(cls_lua_client STATIC + lua/cls_lua_client.cc +) diff --git a/src/cls/lua/cls_lua.cc b/src/cls/lua/cls_lua.cc new file mode 100644 index 0000000000000..39506fa9d33bb --- /dev/null +++ b/src/cls/lua/cls_lua.cc @@ -0,0 +1,1053 @@ +/* + * Lua Bindings for RADOS Object Class + */ +#include +#include +#include +#include +#include +#include "include/types.h" +#include "objclass/objclass.h" +#include "json_spirit/json_spirit.h" +#include "cls_lua.h" +#include "cls_lua_ops.h" + +CLS_VER(1,0) +CLS_NAME(lua) + +cls_handle_t h_class; +cls_method_handle_t h_eval_json; +cls_method_handle_t h_eval_bufferlist; + +/* + * Jump point for recovering from Lua panic. + */ +static jmp_buf cls_lua_panic_jump; + +/* + * Handle Lua panic. + */ +static int cls_lua_atpanic(lua_State *lua) +{ + CLS_ERR("error: Lua panic: %s", lua_tostring(lua, -1)); + longjmp(cls_lua_panic_jump, 1); + return 0; +} + +struct clslua_err { + bool error; + int ret; +}; + +/* + * Input parameter encoding. + */ +enum InputEncoding { + JSON_ENC, + BUFFERLIST_ENC, +}; + +struct clslua_hctx { + struct clslua_err error; + InputEncoding in_enc; + int ret; + + cls_method_context_t *hctx; + bufferlist *inbl; // raw cls input + bufferlist *outbl; // raw cls output + + string script; // lua script + string handler; // lua handler + bufferlist input; // lua handler input +}; + +/* Lua registry key for method context */ +static char clslua_hctx_reg_key; + +/* + * Grabs the full method handler context + */ +static clslua_hctx *__clslua_get_hctx(lua_State *L) +{ + /* lookup registry value */ + lua_pushlightuserdata(L, &clslua_hctx_reg_key); + lua_gettable(L, LUA_REGISTRYINDEX); + + /* check cls_lua assumptions */ + assert(!lua_isnil(L, -1)); + assert(lua_type(L, -1) == LUA_TLIGHTUSERDATA); + + /* cast and cleanup stack */ + clslua_hctx *hctx = (struct clslua_hctx *)lua_touserdata(L, -1); + lua_pop(L, 1); + + return hctx; +} + +/* + * Get the method context out of the registry. This is called at the beginning + * of each clx_cxx_* wrapper, and must be set before there is any chance a Lua + * script calling a 'cls' module function that requires it. + */ +static cls_method_context_t clslua_get_hctx(lua_State *L) +{ + struct clslua_hctx *hctx = __clslua_get_hctx(L); + return *hctx->hctx; +} + +/* + * Returns a reference to cls_lua error state from registry. + */ +struct clslua_err *clslua_checkerr(lua_State *L) +{ + struct clslua_hctx *hctx = __clslua_get_hctx(L); + struct clslua_err *err = &hctx->error; + return err; +} + + +/* Registry key for real `pcall` function */ +static char clslua_pcall_reg_key; + +/* + * Wrap Lua pcall to check for errors thrown by cls_lua (e.g. I/O errors or + * bufferlist decoding errors). The global error is cleared before returning + * to the caller. + */ +static int clslua_pcall(lua_State *L) +{ + int nargs = lua_gettop(L); + lua_pushlightuserdata(L, &clslua_pcall_reg_key); + lua_gettable(L, LUA_REGISTRYINDEX); + lua_insert(L, 1); + lua_call(L, nargs, LUA_MULTRET); + struct clslua_err *err = clslua_checkerr(L); + assert(err); + if (err->error) { + err->error = false; + lua_pushinteger(L, err->ret); + lua_insert(L, -2); + } + return lua_gettop(L); +} + + +/* + * cls_log + */ +static int clslua_log(lua_State *L) +{ + int nargs = lua_gettop(L); + + if (!nargs) + return 0; + + int loglevel = LOG_LEVEL_DEFAULT; + bool custom_ll = false; + + /* check if first arg can be a log level */ + if (nargs > 1 && lua_isnumber(L, 1)) { + int ll = (int)lua_tonumber(L, 1); + if (ll >= 0) { + loglevel = ll; + custom_ll = true; + } + } + + /* check space for args and seperators (" ") */ + int nelems = ((nargs - (custom_ll ? 1 : 0)) * 2) - 1; + luaL_checkstack(L, nelems, "rados.log(..)"); + + for (int i = custom_ll ? 2 : 1; i <= nargs; i++) { + const char *part = lua_tostring(L, i); + if (!part) { + if (lua_type(L, i) == LUA_TBOOLEAN) + part = lua_toboolean(L, i) ? "true" : "false"; + else + part = luaL_typename(L, i); + } + lua_pushstring(L, part); + if ((i+1) <= nargs) + lua_pushstring(L, " "); + } + + /* join string parts and send to Ceph/reply log */ + lua_concat(L, nelems); + CLS_LOG(loglevel, "%s", lua_tostring(L, -1)); + + /* concat leaves result at top of stack */ + return 1; +} + +static char clslua_registered_handle_reg_key; + +/* + * Register a function to be used as a handler target + */ +static int clslua_register(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TFUNCTION); + + /* get table of registered handlers */ + lua_pushlightuserdata(L, &clslua_registered_handle_reg_key); + lua_gettable(L, LUA_REGISTRYINDEX); + assert(lua_type(L, -1) == LUA_TTABLE); + + /* lookup function argument */ + lua_pushvalue(L, 1); + lua_gettable(L, -2); + + if (lua_isnil(L, -1)) { + lua_pushvalue(L, 1); + lua_pushvalue(L, 1); + lua_settable(L, -4); + } else { + lua_pushstring(L, "Cannot register handler more than once"); + return lua_error(L); + } + + return 0; +} + +/* + * Check if a function is registered as a handler + */ +static void clslua_check_registered_handler(lua_State *L) +{ + luaL_checktype(L, -1, LUA_TFUNCTION); + + /* get table of registered handlers */ + lua_pushlightuserdata(L, &clslua_registered_handle_reg_key); + lua_gettable(L, LUA_REGISTRYINDEX); + assert(lua_type(L, -1) == LUA_TTABLE); + + /* lookup function argument */ + lua_pushvalue(L, -2); + lua_gettable(L, -2); + + if (!lua_rawequal(L, -1, -3)) { + lua_pushstring(L, "Handler is not registered"); + lua_error(L); + } + + lua_pop(L, 2); +} + +/* + * Handle result of a cls_cxx_* call. If @ok is non-zero then we return with + * the number of Lua return arguments on the stack. Otherwise we save error + * information in the registry and throw a Lua error. + */ +static int clslua_opresult(lua_State *L, int ok, int ret, int nargs, + bool error_on_stack = false) +{ + struct clslua_err *err = clslua_checkerr(L); + + assert(err); + if (err->error) { + CLS_ERR("error: cls_lua state machine: unexpected error"); + assert(0); + } + + /* everything is cherry */ + if (ok) + return nargs; + + /* set error in registry */ + err->error = true; + err->ret = ret; + + /* push error message */ + if (!error_on_stack) + lua_pushfstring(L, "%s", strerror(-ret)); + + return lua_error(L); +} + +/* + * cls_cxx_create + */ +static int clslua_create(lua_State *lua) +{ + cls_method_context_t hctx = clslua_get_hctx(lua); + int exclusive = lua_toboolean(lua, 1); + + int ret = cls_cxx_create(hctx, exclusive); + return clslua_opresult(lua, (ret == 0), ret, 0); +} + +/* + * cls_cxx_remove + */ +static int clslua_remove(lua_State *lua) +{ + cls_method_context_t hctx = clslua_get_hctx(lua); + + int ret = cls_cxx_remove(hctx); + return clslua_opresult(lua, (ret == 0), ret, 0); +} + +/* + * cls_cxx_stat + */ +static int clslua_stat(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + + uint64_t size; + time_t mtime; + int ret = cls_cxx_stat(hctx, &size, &mtime); + if (!ret) { + lua_pushinteger(L, size); + lua_pushinteger(L, mtime); + } + return clslua_opresult(L, (ret == 0), ret, 2); +} + +/* + * cls_cxx_read + */ +static int clslua_read(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + int offset = luaL_checkinteger(L, 1); + int length = luaL_checkinteger(L, 2); + bufferlist *bl = clslua_pushbufferlist(L, NULL); + int ret = cls_cxx_read(hctx, offset, length, bl); + return clslua_opresult(L, (ret >= 0), ret, 1); +} + +/* + * cls_cxx_write + */ +static int clslua_write(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + int offset = luaL_checkinteger(L, 1); + int length = luaL_checkinteger(L, 2); + bufferlist *bl = clslua_checkbufferlist(L, 3); + int ret = cls_cxx_write(hctx, offset, length, bl); + return clslua_opresult(L, (ret == 0), ret, 0); +} + +/* + * cls_cxx_write_full + */ +static int clslua_write_full(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + bufferlist *bl = clslua_checkbufferlist(L, 1); + int ret = cls_cxx_write_full(hctx, bl); + return clslua_opresult(L, (ret == 0), ret, 0); +} + +/* + * cls_cxx_getxattr + */ +static int clslua_getxattr(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + const char *name = luaL_checkstring(L, 1); + bufferlist *bl = clslua_pushbufferlist(L, NULL); + int ret = cls_cxx_getxattr(hctx, name, bl); + return clslua_opresult(L, (ret >= 0), ret, 1); +} + +/* + * cls_cxx_getxattrs + */ +static int clslua_getxattrs(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + + map attrs; + int ret = cls_cxx_getxattrs(hctx, &attrs); + if (ret < 0) + return clslua_opresult(L, 0, ret, 0); + + lua_createtable(L, 0, attrs.size()); + + for (auto it = attrs.cbegin(); it != attrs.cend(); it++) { + lua_pushstring(L, it->first.c_str()); + bufferlist *bl = clslua_pushbufferlist(L, NULL); + *bl = it->second; // xfer ownership... will be GC'd + lua_settable(L, -3); + } + + return clslua_opresult(L, 1, ret, 1); +} + +/* + * cls_cxx_setxattr + */ +static int clslua_setxattr(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + const char *name = luaL_checkstring(L, 1); + bufferlist *bl = clslua_checkbufferlist(L, 2); + int ret = cls_cxx_setxattr(hctx, name, bl); + return clslua_opresult(L, (ret == 0), ret, 1); +} + +/* + * cls_cxx_map_get_val + */ +static int clslua_map_get_val(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + const char *key = luaL_checkstring(L, 1); + bufferlist *bl = clslua_pushbufferlist(L, NULL); + int ret = cls_cxx_map_get_val(hctx, key, bl); + return clslua_opresult(L, (ret == 0), ret, 1); +} + +/* + * cls_cxx_map_set_val + */ +static int clslua_map_set_val(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + const char *key = luaL_checkstring(L, 1); + bufferlist *val = clslua_checkbufferlist(L, 2); + int ret = cls_cxx_map_set_val(hctx, key, val); + return clslua_opresult(L, (ret == 0), ret, 0); +} + +/* + * cls_cxx_map_clear + */ +static int clslua_map_clear(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + int ret = cls_cxx_map_clear(hctx); + return clslua_opresult(L, (ret == 0), ret, 0); +} + +/* + * cls_cxx_map_get_keys + */ +static int clslua_map_get_keys(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + const char *start_after = luaL_checkstring(L, 1); + int max_to_get = luaL_checkinteger(L, 2); + + std::set keys; + int ret = cls_cxx_map_get_keys(hctx, start_after, max_to_get, &keys); + if (ret < 0) + return clslua_opresult(L, 0, ret, 0); + + lua_createtable(L, 0, keys.size()); + + for (auto it = keys.cbegin(); it != keys.cend(); it++) { + const std::string& key = *it; + lua_pushstring(L, key.c_str()); + lua_pushboolean(L, 1); + lua_settable(L, -3); + } + + return clslua_opresult(L, 1, ret, 1); +} + +/* + * cls_cxx_map_get_vals + */ +static int clslua_map_get_vals(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + const char *start_after = luaL_checkstring(L, 1); + const char *filter_prefix= luaL_checkstring(L, 2); + int max_to_get = luaL_checkinteger(L, 3); + + map kvpairs; + int ret = cls_cxx_map_get_vals(hctx, start_after, filter_prefix, + max_to_get, &kvpairs); + if (ret < 0) + return clslua_opresult(L, 0, ret, 0); + + lua_createtable(L, 0, kvpairs.size()); + + for (auto it = kvpairs.cbegin(); it != kvpairs.cend(); it++) { + lua_pushstring(L, it->first.c_str()); + bufferlist *bl = clslua_pushbufferlist(L, NULL); + *bl = it->second; // xfer ownership... will be GC'd + lua_settable(L, -3); + } + + return clslua_opresult(L, 1, ret, 1); +} + +/* + * cls_cxx_map_read_header + */ +static int clslua_map_read_header(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + bufferlist *bl = clslua_pushbufferlist(L, NULL); + int ret = cls_cxx_map_read_header(hctx, bl); + return clslua_opresult(L, (ret >= 0), ret, 1); +} + +/* + * cls_cxx_map_write_header + */ +static int clslua_map_write_header(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + bufferlist *bl = clslua_checkbufferlist(L, 1); + int ret = cls_cxx_map_write_header(hctx, bl); + return clslua_opresult(L, (ret == 0), ret, 0); +} + +/* + * cls_cxx_map_set_vals + */ +static int clslua_map_set_vals(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + luaL_checktype(L, 1, LUA_TTABLE); + + map kvpairs; + + lua_pushnil(L); + while (lua_next(L, 1) != 0) { + /* + * In the case of a numeric key a copy is made on the stack because + * converting to a string would otherwise manipulate the original key and + * cause problems for iteration. + */ + string key; + int type_code = lua_type(L, -2); + switch (type_code) { + case LUA_TSTRING: + key.assign(lua_tolstring(L, -2, NULL)); + break; + + case LUA_TNUMBER: + lua_pushvalue(L, -2); + key.assign(lua_tolstring(L, -1, NULL)); + lua_pop(L, 1); + break; + + default: + lua_pushfstring(L, "map_set_vals: invalid key type (%s)", + lua_typename(L, type_code)); + return clslua_opresult(L, 0, -EINVAL, 0, true); + } + + bufferlist val; + type_code = lua_type(L, -1); + switch (type_code) { + case LUA_TSTRING: + { + size_t len; + const char *data = lua_tolstring(L, -1, &len); + val.append(data, len); + } + break; + + default: + lua_pushfstring(L, "map_set_vals: invalid val type (%s) for key (%s)", + lua_typename(L, type_code), key.c_str()); + return clslua_opresult(L, 0, -EINVAL, 0, true); + } + + kvpairs[key] = val; + + lua_pop(L, 1); + } + + int ret = cls_cxx_map_set_vals(hctx, &kvpairs); + + return clslua_opresult(L, (ret == 0), ret, 0); +} + +/* + * cls_cxx_map_remove_key + */ +static int clslua_map_remove_key(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + const char *key = luaL_checkstring(L, 1); + int ret = cls_cxx_map_remove_key(hctx, key); + return clslua_opresult(L, (ret == 0), ret, 0); +} + +/* + * cls_current_version + */ +static int clslua_current_version(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + uint64_t version = cls_current_version(hctx); + lua_pushinteger(L, version); + return clslua_opresult(L, 1, 0, 1); +} + +/* + * cls_current_subop_num + */ +static int clslua_current_subop_num(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + int num = cls_current_subop_num(hctx); + lua_pushinteger(L, num); + return clslua_opresult(L, 1, 0, 1); +} + +/* + * cls_current_subop_version + */ +static int clslua_current_subop_version(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + string s; + cls_cxx_subop_version(hctx, &s); + lua_pushstring(L, s.c_str()); + return clslua_opresult(L, 1, 0, 1); +} + +/* + * Functions registered in the 'cls' module. + */ +static const luaL_Reg clslua_lib[] = { + // mgmt + {"register", clslua_register}, + {"log", clslua_log}, + + // data + {"create", clslua_create}, + {"remove", clslua_remove}, + {"stat", clslua_stat}, + {"read", clslua_read}, + {"write", clslua_write}, + {"write_full", clslua_write_full}, + + // xattr + {"getxattr", clslua_getxattr}, + {"getxattrs", clslua_getxattrs}, + {"setxattr", clslua_setxattr}, + + // omap + {"map_clear", clslua_map_clear}, + {"map_get_keys", clslua_map_get_keys}, + {"map_get_vals", clslua_map_get_vals}, + {"map_read_header", clslua_map_read_header}, + {"map_write_header", clslua_map_write_header}, + {"map_get_val", clslua_map_get_val}, + {"map_set_val", clslua_map_set_val}, + {"map_set_vals", clslua_map_set_vals}, + {"map_remove_key", clslua_map_remove_key}, + + // env + {"current_version", clslua_current_version}, + {"current_subop_num", clslua_current_subop_num}, + {"current_subop_version", clslua_current_subop_version}, + + {NULL, NULL} +}; + +/* + * Set int const in table at top of stack + */ +#define SET_INT_CONST(var) do { \ + lua_pushinteger(L, var); \ + lua_setfield(L, -2, #var); \ +} while (0) + +/* + * + */ +static int luaopen_objclass(lua_State *L) +{ + lua_newtable(L); + + /* + * Register cls functions (cls.log, etc...) + */ + luaL_setfuncs(L, clslua_lib, 0); + + /* + * Register generic errno values under 'cls' + */ + SET_INT_CONST(EPERM); + SET_INT_CONST(ENOENT); + SET_INT_CONST(ESRCH); + SET_INT_CONST(EINTR); + SET_INT_CONST(EIO); + SET_INT_CONST(ENXIO); + SET_INT_CONST(E2BIG); + SET_INT_CONST(ENOEXEC); + SET_INT_CONST(EBADF); + SET_INT_CONST(ECHILD); + SET_INT_CONST(EAGAIN); + SET_INT_CONST(ENOMEM); + SET_INT_CONST(EACCES); + SET_INT_CONST(EFAULT); + SET_INT_CONST(EBUSY); + SET_INT_CONST(EEXIST); + SET_INT_CONST(EXDEV); + SET_INT_CONST(ENODEV); + SET_INT_CONST(ENOTDIR); + SET_INT_CONST(EISDIR); + SET_INT_CONST(EINVAL); + SET_INT_CONST(ENFILE); + SET_INT_CONST(EMFILE); + SET_INT_CONST(ENOTTY); + SET_INT_CONST(EFBIG); + SET_INT_CONST(ENOSPC); + SET_INT_CONST(ESPIPE); + SET_INT_CONST(EROFS); + SET_INT_CONST(EMLINK); + SET_INT_CONST(EPIPE); + SET_INT_CONST(EDOM); + SET_INT_CONST(ERANGE); + + return 1; +} + +/* + * Setup the execution environment. Our sandbox currently is not + * sophisticated. With a new Lua state per-request we don't need to work about + * users stepping on each other, but we do rip out access to the local file + * system. All this will change when/if we decide to use some shared Lua + * states, most likely for performance reasons. + */ +static void clslua_setup_env(lua_State *L) +{ + luaL_requiref(L, "_G", luaopen_base, 1); + lua_pop(L, 1); + + /* + * Wrap `pcall` to intercept errors. First save a reference to the default + * Lua `pcall` function, and then replace `pcall` with our version. + */ + lua_pushlightuserdata(L, &clslua_pcall_reg_key); + lua_getglobal(L, "pcall"); + lua_settable(L, LUA_REGISTRYINDEX); + + lua_pushcfunction(L, clslua_pcall); + lua_setglobal(L, "pcall"); + + /* mask unsafe */ + lua_pushnil(L); + lua_setglobal(L, "loadfile"); + + /* mask unsafe */ + lua_pushnil(L); + lua_setglobal(L, "dofile"); + + /* not integrated into our error handling */ + lua_pushnil(L); + lua_setglobal(L, "xpcall"); + + luaL_requiref(L, LUA_TABLIBNAME, luaopen_table, 1); + lua_pop(L, 1); + + luaL_requiref(L, LUA_STRLIBNAME, luaopen_string, 1); + lua_pop(L, 1); + + luaL_requiref(L, LUA_MATHLIBNAME, luaopen_math, 1); + lua_pop(L, 1); + + luaL_requiref(L, "objclass", luaopen_objclass, 1); + lua_pop(L, 1); + + luaL_requiref(L, "bufferlist", luaopen_bufferlist, 1); + lua_pop(L, 1); +} + +/* + * Schema: + * { + * "script": "...", + * "handler": "...", + * "input": "..." # optional + * } + */ +static int unpack_json_command(lua_State *L, struct clslua_hctx *ctx, + std::string& script, std::string& handler, std::string& input, + size_t *input_len) +{ + std::string json_input(ctx->inbl->c_str()); + json_spirit::mValue value; + + if (!json_spirit::read(json_input, value)) { + CLS_ERR("error: unparseable JSON"); + ctx->ret = -EINVAL; + return 1; + } + + if (value.type() != json_spirit::obj_type) { + CLS_ERR("error: input not a JSON object"); + ctx->ret = -EINVAL; + return 1; + } + json_spirit::mObject obj = value.get_obj(); + + // grab the script + std::map::const_iterator it = obj.find("script"); + if (it == obj.end()) { + CLS_ERR("error: 'script' field found in JSON object"); + ctx->ret = -EINVAL; + return 1; + } + + if (it->second.type() != json_spirit::str_type) { + CLS_ERR("error: script is not a string"); + ctx->ret = -EINVAL; + return 1; + } + script = it->second.get_str(); + + // grab the target function/handler name + it = obj.find("handler"); + if (it == obj.end()) { + CLS_ERR("error: no target handler found in JSON object"); + ctx->ret = -EINVAL; + return 1; + } + + if (it->second.type() != json_spirit::str_type) { + CLS_ERR("error: target handler is not a string"); + ctx->ret = -EINVAL; + return 1; + } + handler = it->second.get_str(); + + // grab the input (optional) + it = obj.find("input"); + if (it != obj.end()) { + if (it->second.type() != json_spirit::str_type) { + CLS_ERR("error: handler input is not a string"); + ctx->ret = -EINVAL; + return 1; + } + input = it->second.get_str(); + *input_len = input.size(); + } + + return 0; +} + +/* + * Runs the script, and calls handler. + */ +static int clslua_eval(lua_State *L) +{ + struct clslua_hctx *ctx = __clslua_get_hctx(L); + ctx->ret = -EIO; /* assume failure */ + + /* + * Load modules, errno value constants, and other environment goodies. Must + * be done before loading/compiling the chunk. + */ + clslua_setup_env(L); + + /* + * Deserialize the input that contains the script, the name of the handler + * to call, and the handler input. + */ + switch (ctx->in_enc) { + case JSON_ENC: + { + std::string input_str; + size_t input_str_len = 0; + + // if there is an error decoding json then ctx->ret will be set and we + // return normally from this function. + if (unpack_json_command(L, ctx, ctx->script, ctx->handler, input_str, + &input_str_len)) + return 0; + + bufferptr bp(input_str.c_str(), input_str_len); + ctx->input.push_back(bp); + } + break; + + case BUFFERLIST_ENC: + { + cls_lua_eval_op op; + + try { + bufferlist::iterator it = ctx->inbl->begin(); + ::decode(op, it); + } catch (const buffer::error &err) { + CLS_ERR("error: could not decode ceph encoded input"); + ctx->ret = -EINVAL; + return 0; + } + + ctx->script.swap(op.script); + ctx->handler.swap(op.handler); + ctx->input = op.input; + } + break; + + default: + CLS_ERR("error: unknown encoding type"); + ctx->ret = -EFAULT; + assert(0); + return 0; + } + + /* + * Create table to hold registered (valid) handlers. + * + * Must be done before running the script for the first time because the + * script will immediately try to register one or more handlers using + * cls.register(function), which depends on this table. + */ + lua_pushlightuserdata(L, &clslua_registered_handle_reg_key); + lua_newtable(L); + lua_settable(L, LUA_REGISTRYINDEX); + + /* load and compile chunk */ + if (luaL_loadstring(L, ctx->script.c_str())) + return lua_error(L); + + /* execute chunk */ + lua_call(L, 0, 0); + + /* no error, but nothing left to do */ + if (!ctx->handler.size()) { + CLS_LOG(10, "no handler name provided"); + ctx->ret = 0; /* success */ + return 0; + } + + lua_getglobal(L, ctx->handler.c_str()); + if (lua_type(L, -1) != LUA_TFUNCTION) { + CLS_ERR("error: unknown handler or not function: %s", ctx->handler.c_str()); + ctx->ret = -EOPNOTSUPP; + return 0; + } + + /* throw error if function is not registered */ + clslua_check_registered_handler(L); + + /* setup the input/output bufferlists */ + clslua_pushbufferlist(L, &ctx->input); + clslua_pushbufferlist(L, ctx->outbl); + + /* + * Call the target Lua object class handler. If the call is successful then + * we will examine the return value here and store it in the context. Errors + * that occur are handled in the top-level eval() function. + */ + int top = lua_gettop(L); + lua_call(L, 2, LUA_MULTRET); + + /* store return value in context */ + if (!(lua_gettop(L) + 3 - top)) + lua_pushinteger(L, 0); + ctx->ret = luaL_checkinteger(L, -1); + + return 0; +} + +/* + * Main handler. Proxies the Lua VM and the Lua-defined handler. + */ +static int eval_generic(cls_method_context_t hctx, bufferlist *in, bufferlist *out, + InputEncoding in_enc) +{ + struct clslua_hctx ctx; + lua_State *L = NULL; + int ret = -EIO; + + /* stash context for use in Lua VM */ + ctx.hctx = &hctx; + ctx.inbl = in; + ctx.in_enc = in_enc; + ctx.outbl = out; + ctx.error.error = false; + + /* build lua vm state */ + L = luaL_newstate(); + if (!L) { + CLS_ERR("error creating new Lua state"); + goto out; + } + + /* panic handler for unhandled errors */ + lua_atpanic(L, &cls_lua_atpanic); + + if (setjmp(cls_lua_panic_jump) == 0) { + + /* + * Stash the handler context in the register. It contains the objclass + * method context, global error state, and the command and reply structs. + */ + lua_pushlightuserdata(L, &clslua_hctx_reg_key); + lua_pushlightuserdata(L, &ctx); + lua_settable(L, LUA_REGISTRYINDEX); + + /* Process the input and run the script */ + lua_pushcfunction(L, clslua_eval); + ret = lua_pcall(L, 0, 0, 0); + + /* Encountered an error? */ + if (ret) { + struct clslua_err *err = clslua_checkerr(L); + if (!err) { + CLS_ERR("error: cls_lua state machine: unexpected error"); + assert(0); + } + + /* Error origin a cls_cxx_* method? */ + if (err->error) { + ret = err->ret; /* cls_cxx_* return value */ + + /* Errors always abort. Fix up ret and log error */ + if (ret >= 0) { + CLS_ERR("error: unexpected handler return value"); + ret = -EFAULT; + } + + } else + ret = -EIO; /* Generic error code */ + + CLS_ERR("error: %s", lua_tostring(L, -1)); + + } else { + /* + * No Lua error encountered while running the script, but the handler + * may still have returned an error code (e.g. an errno value). + */ + ret = ctx.ret; + } + + } else { + CLS_ERR("error: recovering from Lua panic"); + ret = -EFAULT; + } + +out: + if (L) + lua_close(L); + return ret; +} + +static int eval_json(cls_method_context_t hctx, bufferlist *in, bufferlist *out) +{ + return eval_generic(hctx, in, out, JSON_ENC); +} + +static int eval_bufferlist(cls_method_context_t hctx, bufferlist *in, bufferlist *out) +{ + return eval_generic(hctx, in, out, BUFFERLIST_ENC); +} + +void __cls_init() +{ + CLS_LOG(20, "Loaded lua class!"); + + cls_register("lua", &h_class); + + cls_register_cxx_method(h_class, "eval_json", + CLS_METHOD_RD | CLS_METHOD_WR, eval_json, &h_eval_json); + + cls_register_cxx_method(h_class, "eval_bufferlist", + CLS_METHOD_RD | CLS_METHOD_WR, eval_bufferlist, &h_eval_bufferlist); +} diff --git a/src/cls/lua/cls_lua.h b/src/cls/lua/cls_lua.h new file mode 100644 index 0000000000000..70ce9a9273438 --- /dev/null +++ b/src/cls/lua/cls_lua.h @@ -0,0 +1,14 @@ +#ifndef CEPH_CLS_LUA_H +#define CEPH_CLS_LUA_H + +#include +#include "include/types.h" + +#define LOG_LEVEL_DEFAULT 10 + +int luaopen_bufferlist(lua_State *L); + +bufferlist *clslua_checkbufferlist(lua_State *L, int pos = 1); +bufferlist *clslua_pushbufferlist(lua_State *L, bufferlist *set); + +#endif diff --git a/src/cls/lua/cls_lua_client.cc b/src/cls/lua/cls_lua_client.cc new file mode 100644 index 0000000000000..95dd2bb604340 --- /dev/null +++ b/src/cls/lua/cls_lua_client.cc @@ -0,0 +1,37 @@ +#include +#include +#include +#include "include/encoding.h" +#include "include/rados.h" +#include "include/rados/librados.h" +#include "include/types.h" +#include "cls_lua_client.h" +#include "cls_lua_ops.h" + +using std::string; +using std::vector; +using librados::IoCtx; +using librados::bufferlist; + +namespace cls_lua_client { + /* + * Currently the return code and return bufferlist are not wrapped in a + * protocol that allows object class vs Lua to be distinguished. For + * instance, -EOPNOTSUPP might refer to cls_lua not being found, but would + * also be returned when cls_lua is found, but a Lua handler is not found. + */ + int exec(IoCtx& ioctx, const string& oid, const string& script, + const string& handler, bufferlist& input, bufferlist& output) + { + cls_lua_eval_op op; + + op.script = script; + op.handler = handler; + op.input = input; + + bufferlist inbl; + ::encode(op, inbl); + + return ioctx.exec(oid, "lua", "eval_bufferlist", inbl, output); + } +} diff --git a/src/cls/lua/cls_lua_client.h b/src/cls/lua/cls_lua_client.h new file mode 100644 index 0000000000000..fd049247add08 --- /dev/null +++ b/src/cls/lua/cls_lua_client.h @@ -0,0 +1,13 @@ +#ifndef CLS_LUA_CLIENT_HPP +#define CLS_LUA_CLIENT_HPP +#include +#include +#include "include/rados/librados.hpp" + +namespace cls_lua_client { + int exec(librados::IoCtx& ioctx, const std::string& oid, + const std::string& script, const std::string& handler, + librados::bufferlist& inbl, librados::bufferlist& outbl); +} + +#endif diff --git a/src/cls/lua/cls_lua_ops.h b/src/cls/lua/cls_lua_ops.h new file mode 100644 index 0000000000000..ed297922044e6 --- /dev/null +++ b/src/cls/lua/cls_lua_ops.h @@ -0,0 +1,27 @@ +#ifndef CEPH_CLS_LUA_OPS_H +#define CEPH_CLS_LUA_OPS_H + +struct cls_lua_eval_op { + string script; + string handler; + bufferlist input; + + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + ::encode(script, bl); + ::encode(handler, bl); + ::encode(input, bl); + ENCODE_FINISH(bl); + } + + void decode(bufferlist::iterator &bl) { + DECODE_START(1, bl); + ::decode(script, bl); + ::decode(handler, bl); + ::decode(input, bl); + DECODE_FINISH(bl); + } +}; +WRITE_CLASS_ENCODER(cls_lua_eval_op) + +#endif diff --git a/src/cls/lua/lua_bufferlist.cc b/src/cls/lua/lua_bufferlist.cc new file mode 100644 index 0000000000000..18b9c81e21e37 --- /dev/null +++ b/src/cls/lua/lua_bufferlist.cc @@ -0,0 +1,180 @@ +/* + * Lua module wrapping librados::bufferlist + */ +#include +#include +#include +#include +#include +#include "include/types.h" +#include "include/buffer.h" +#include "objclass/objclass.h" +#include "cls/lua/cls_lua.h" + +#define LUA_BUFFERLIST "ClsLua.Bufferlist" + +struct bufferlist_wrap { + bufferlist *bl; + int gc; /* do garbage collect? */ +}; + +static inline struct bufferlist_wrap *to_blwrap(lua_State *L, int pos = 1) +{ + return (bufferlist_wrap *)luaL_checkudata(L, pos, LUA_BUFFERLIST); +} + +bufferlist *clslua_checkbufferlist(lua_State *L, int pos) +{ + struct bufferlist_wrap *blw = to_blwrap(L, pos); + return blw->bl; +} + +/* + * Pushes a new bufferlist userdata object onto the stack. If @set is non-null + * it is assumed to be a bufferlist that should not be garbage collected. + */ +bufferlist *clslua_pushbufferlist(lua_State *L, bufferlist *set) +{ + bufferlist_wrap *blw = (bufferlist_wrap *)lua_newuserdata(L, sizeof(*blw)); + blw->bl = set ? set : new bufferlist(); + blw->gc = set ? 0 : 1; + luaL_getmetatable(L, LUA_BUFFERLIST); + lua_setmetatable(L, -2); + return blw->bl; +} + +/* + * Create a new bufferlist + */ +static int bl_new(lua_State *L) +{ + clslua_pushbufferlist(L, NULL); + return 1; +} + +/* + * Convert bufferlist to Lua string + */ +static int bl_str(lua_State *L) +{ + bufferlist *bl = clslua_checkbufferlist(L); + lua_pushlstring(L, bl->c_str(), bl->length()); + return 1; +} + +/* + * Append a Lua string to bufferlist + */ +static int bl_append(lua_State *L) +{ + bufferlist *bl = clslua_checkbufferlist(L); + luaL_checktype(L, 2, LUA_TSTRING); + + size_t len; + const char *data = lua_tolstring(L, 2, &len); + bl->append(data, len); + + return 0; +} + +/* + * Return the length in bytes of bufferlist + */ +static int bl_len(lua_State *L) +{ + bufferlist *bl = clslua_checkbufferlist(L); + lua_pushinteger(L, bl->length()); + return 1; +} + +/* + * Perform byte-for-byte bufferlist equality test + */ +static int bl_eq(lua_State *L) +{ + bufferlist *bl1 = clslua_checkbufferlist(L, 1); + bufferlist *bl2 = clslua_checkbufferlist(L, 2); + lua_pushboolean(L, *bl1 == *bl2 ? 1 : 0); + return 1; +} + +/* + * Bufferlist < operator + */ +static int bl_lt(lua_State *L) +{ + bufferlist *bl1 = clslua_checkbufferlist(L, 1); + bufferlist *bl2 = clslua_checkbufferlist(L, 2); + lua_pushboolean(L, *bl1 < *bl2 ? 1 : 0); + return 1; +} + +/* + * Bufferlist <= operator + */ +static int bl_le(lua_State *L) +{ + bufferlist *bl1 = clslua_checkbufferlist(L, 1); + bufferlist *bl2 = clslua_checkbufferlist(L, 2); + lua_pushboolean(L, *bl1 <= *bl2 ? 1 : 0); + return 1; +} + +/* + * Bufferlist concatentation + */ +static int bl_concat(lua_State *L) +{ + bufferlist *bl1 = clslua_checkbufferlist(L, 1); + bufferlist *bl2 = clslua_checkbufferlist(L, 2); + bufferlist *ret = clslua_pushbufferlist(L, NULL); + ret->append(bl1->c_str(), bl1->length()); + ret->append(bl2->c_str(), bl2->length()); + return 1; +} + +/* + * Garbage collect bufferlist + */ +static int bl_gc(lua_State *L) +{ + struct bufferlist_wrap *blw = to_blwrap(L); + assert(blw); + assert(blw->bl); + if (blw->gc) + delete blw->bl; + return 0; +} + +static const struct luaL_Reg bufferlist_methods[] = { + {"str", bl_str}, + {"append", bl_append}, + {"__concat", bl_concat}, + {"__len", bl_len}, + {"__lt", bl_lt}, + {"__le", bl_le}, + {"__gc", bl_gc}, + {"__eq", bl_eq}, + {NULL, NULL} +}; + +static const struct luaL_Reg bllib_f[] = { + {"new", bl_new}, + {NULL, NULL} +}; + +LUALIB_API int luaopen_bufferlist(lua_State *L) +{ + /* Setup bufferlist user-data type */ + luaL_newmetatable(L, LUA_BUFFERLIST); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + + luaL_setfuncs(L, bufferlist_methods, 0); + lua_pop(L, 1); + + lua_newtable(L); + luaL_setfuncs(L, bllib_f, 0); + + return 1; +} diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 8c1c2814ad4bc..b290b14506a79 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -16,6 +16,7 @@ add_subdirectory(cls_replica_log) add_subdirectory(cls_rgw) add_subdirectory(cls_statelog) add_subdirectory(cls_version) +add_subdirectory(cls_lua) add_subdirectory(common) add_subdirectory(compressor) add_subdirectory(crush) diff --git a/src/test/cls_lua/CMakeLists.txt b/src/test/cls_lua/CMakeLists.txt new file mode 100644 index 0000000000000..2ccb851cd89a4 --- /dev/null +++ b/src/test/cls_lua/CMakeLists.txt @@ -0,0 +1,18 @@ +add_executable(ceph_test_cls_lua + test_cls_lua.cc +) +set_target_properties(ceph_test_cls_lua PROPERTIES COMPILE_FLAGS + ${UNITTEST_CXX_FLAGS}) +target_link_libraries(ceph_test_cls_lua + cls_lua_client + liblua + librados + global + ${UNITTEST_LIBS} + ${EXTRALIBS} + ${CMAKE_DL_LIBS} + radostest +) +install(TARGETS + ceph_test_cls_lua + DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/src/test/cls_lua/test_cls_lua.cc b/src/test/cls_lua/test_cls_lua.cc new file mode 100644 index 0000000000000..dd49f0181a356 --- /dev/null +++ b/src/test/cls_lua/test_cls_lua.cc @@ -0,0 +1,1106 @@ +#include +#include +#include "include/types.h" +#include "include/rados/librados.h" +#include "gtest/gtest.h" +#include "test/librados/test.h" +#include "cls/lua/cls_lua_client.h" +#include "cls/lua/cls_lua.h" + +/* + * JSON script to test JSON I/O protocol with cls_lua + */ +const std::string json_test_script = R"jsonscript( +{ + "script": "function json_echo(input, output) output:append(input:str()); end objclass.register(json_echo)", + "handler": "json_echo", + "input": "omg it works", +} +)jsonscript"; + +/* + * Lua test script thanks to the magical c++11 string literal syntax + */ +const std::string test_script = R"luascript( +-- +-- This Lua script file contains all of the handlers used in the cls_lua unit +-- tests (src/test/cls_lua/test_cls_lua.cc). Each section header corresponds +-- to the ClsLua.XYZ test. +-- + +-- +-- Read +-- +function read(input, output) + size = objclass.stat() + bl = objclass.read(0, size) + output:append(bl:str()) +end + +objclass.register(read) + +-- +-- Write +-- +function write(input, output) + objclass.write(0, #input, input) +end + +objclass.register(write) + +-- +-- MapGetVal +-- +function map_get_val_foo(input, output) + bl = objclass.map_get_val('foo') + output:append(bl:str()) +end + +function map_get_val_dne() + bl = objclass.map_get_val('dne') +end + +objclass.register(map_get_val_foo) +objclass.register(map_get_val_dne) + +-- +-- Stat +-- +function stat_ret(input, output) + size, mtime = objclass.stat() + output:append(size .. "," .. mtime) +end + +function stat_sdne() + size, mtime = objclass.stat() +end + +function stat_sdne_pcall() + ok, ret, size, mtime = pcall(objclass.stat, o) + assert(ok == false) + assert(ret == -objclass.ENOENT) + return ret +end + +objclass.register(stat_ret) +objclass.register(stat_sdne) +objclass.register(stat_sdne_pcall) + +-- +-- RetVal +-- +function rv_h() end +function rv_h1() return 1; end +function rv_h0() return 0; end +function rv_hn1() return -1; end +function rv_hs1() return '1'; end +function rv_hs0() return '0'; end +function rv_hsn1() return '-1'; end +function rv_hnil() return nil; end +function rv_ht() return {}; end +function rv_hstr() return 'asdf'; end + +objclass.register(rv_h) +objclass.register(rv_h1) +objclass.register(rv_h0) +objclass.register(rv_hn1) +objclass.register(rv_hs1) +objclass.register(rv_hs0) +objclass.register(rv_hsn1) +objclass.register(rv_hnil) +objclass.register(rv_ht) +objclass.register(rv_hstr) + +-- +-- Create +-- +function create_c() objclass.create(true); end +function create_cne() objclass.create(false); end + +objclass.register(create_c) +objclass.register(create_cne) + +-- +-- Pcall +-- +function pcall_c() objclass.create(true); end + +function pcall_pc() + ok, ret = pcall(objclass.create, true) + assert(ok == false) + assert(ret == -objclass.EEXIST) +end + +function pcall_pcr() + ok, ret = pcall(objclass.create, true) + assert(ok == false) + assert(ret == -objclass.EEXIST) + return ret +end + +function pcall_pcr2() + ok, ret = pcall(objclass.create, true) + assert(ok == false) + assert(ret == -objclass.EEXIST) + ok, ret = pcall(objclass.create, true) + assert(ok == false) + assert(ret == -objclass.EEXIST) + return -9999 +end + +objclass.register(pcall_c) +objclass.register(pcall_pc) +objclass.register(pcall_pcr) +objclass.register(pcall_pcr2) + +-- +-- Remove +-- +function remove_c() objclass.create(true); end +function remove_r() objclass.remove(); end + +objclass.register(remove_c) +objclass.register(remove_r) + +-- +-- MapSetVal +-- +function map_set_val(input, output) + objclass.map_set_val('foo', input) +end + +objclass.register(map_set_val) + +-- +-- MapClear +-- +function map_clear() + objclass.map_clear() +end + +objclass.register(map_clear) + +-- +-- BufferlistEquality +-- +function bl_eq_empty_equal(input, output) + bl1 = bufferlist.new() + bl2 = bufferlist.new() + assert(bl1 == bl2) +end + +function bl_eq_empty_selfequal() + bl1 = bufferlist.new() + assert(bl1 == bl1) +end + +function bl_eq_selfequal() + bl1 = bufferlist.new() + bl1:append('asdf') + assert(bl1 == bl1) +end + +function bl_eq_equal() + bl1 = bufferlist.new() + bl2 = bufferlist.new() + bl1:append('abc') + bl2:append('abc') + assert(bl1 == bl2) +end + +function bl_eq_notequal() + bl1 = bufferlist.new() + bl2 = bufferlist.new() + bl1:append('abc') + bl2:append('abcd') + assert(bl1 ~= bl2) +end + +objclass.register(bl_eq_empty_equal) +objclass.register(bl_eq_empty_selfequal) +objclass.register(bl_eq_selfequal) +objclass.register(bl_eq_equal) +objclass.register(bl_eq_notequal) + +-- +-- Bufferlist Compare +-- +function bl_lt() + local a = bufferlist.new() + local b = bufferlist.new() + a:append('A') + b:append('B') + assert(a < b) +end + +function bl_le() + local a = bufferlist.new() + local b = bufferlist.new() + a:append('A') + b:append('B') + assert(a <= b) +end + +objclass.register(bl_lt) +objclass.register(bl_le) + +-- +-- Bufferlist concat +-- +function bl_concat_eq() + local a = bufferlist.new() + local b = bufferlist.new() + local ab = bufferlist.new() + a:append('A') + b:append('B') + ab:append('AB') + assert(a .. b == ab) +end + +function bl_concat_ne() + local a = bufferlist.new() + local b = bufferlist.new() + local ab = bufferlist.new() + a:append('A') + b:append('B') + ab:append('AB') + assert(b .. a ~= ab) +end + +function bl_concat_immut() + local a = bufferlist.new() + local b = bufferlist.new() + local ab = bufferlist.new() + a:append('A') + b:append('B') + ab:append('AB') + x = a .. b + assert(x == ab) + b:append('C') + assert(x == ab) + local bc = bufferlist.new() + bc:append('BC') + assert(b == bc) +end + +objclass.register(bl_concat_eq) +objclass.register(bl_concat_ne) +objclass.register(bl_concat_immut) + +-- +-- RunError +-- +function runerr_a() + error('WTF') +end + +function runerr_b() + runerr_a() +end + +function runerr_c() + runerr_b() +end + +-- only runerr_c is called +objclass.register(runerr_c) + +-- +-- GetXattr +-- +function getxattr(input, output) + bl = objclass.getxattr("fooz") + output:append(bl:str()) +end + +objclass.register(getxattr) + +-- +-- SetXattr +-- +function setxattr(input, output) + objclass.setxattr("fooz2", input) +end + +objclass.register(setxattr) + +-- +-- WriteFull +-- +function write_full(input, output) + objclass.write_full(input) +end + +objclass.register(write_full) + +-- +-- GetXattrs +-- +function getxattrs(input, output) + -- result + xattrs = objclass.getxattrs() + + -- sort for determisitic test + arr = {} + for n in pairs(xattrs) do + table.insert(arr, n) + end + table.sort(arr) + + output_str = "" + for i,key in ipairs(arr) do + output_str = output_str .. key .. "/" .. xattrs[key]:str() .. "/" + end + output:append(output_str) +end + +objclass.register(getxattrs) + +-- +-- MapGetKeys +-- +function map_get_keys(input, output) + -- result + keys = objclass.map_get_keys("", 5) + + -- sort for determisitic test + arr = {} + for n in pairs(keys) do + table.insert(arr, n) + end + table.sort(arr) + + output_str = "" + for i,key in ipairs(arr) do + output_str = output_str .. key .. "/" + end + + output:append(output_str) +end + +objclass.register(map_get_keys) + +-- +-- MapGetVals +-- +function map_get_vals(input, output) + -- result + kvs = objclass.map_get_vals("", "", 10) + + -- sort for determisitic test + arr = {} + for n in pairs(kvs) do + table.insert(arr, n) + end + table.sort(arr) + + output_str = "" + for i,key in ipairs(arr) do + output_str = output_str .. key .. "/" .. kvs[key]:str() .. "/" + end + output:append(output_str) +end + +objclass.register(map_get_vals) + +-- +-- MapHeader (write) +-- +function map_write_header(input, output) + objclass.map_write_header(input) +end + +objclass.register(map_write_header) + +-- +-- MapHeader (read) +-- +function map_read_header(input, output) + hdr = objclass.map_read_header() + output:append(hdr:str()) +end + +objclass.register(map_read_header) + +-- +-- MapSetVals +-- +function map_set_vals_empty(input, output) + record = {} + objclass.map_set_vals(record) +end + +function map_set_vals_one(input, output) + record = { + a = "a_val" + } + objclass.map_set_vals(record) +end + +function map_set_vals_two(input, output) + record = { + a = "a_val", + b = "b_val" + } + objclass.map_set_vals(record) +end + +function map_set_vals_three(input, output) + record = { + a = "a_val", + b = "b_val", + c = "c_val" + } + objclass.map_set_vals(record) +end + +function map_set_vals_array(input, output) + array = {} + array[1] = "1_val" + array[2] = "2_val" + objclass.map_set_vals(array) +end + +function map_set_vals_mixed(input, output) + record = { + a = "a_val", + b = "b_val" + } + record[1] = "1_val" + record[2] = "2_val" + objclass.map_set_vals(record) +end + +function map_set_vals_bad_val(input, output) + record = { + a = {} + } + objclass.map_set_vals(record) +end + +objclass.register(map_set_vals_empty) +objclass.register(map_set_vals_one) +objclass.register(map_set_vals_two) +objclass.register(map_set_vals_three) +objclass.register(map_set_vals_array) +objclass.register(map_set_vals_mixed) +objclass.register(map_set_vals_bad_val) + +-- +-- MapRemoveKey +-- +function map_remove_key(input, output) + objclass.map_remove_key("a") +end + +objclass.register(map_remove_key) + +-- +-- Version/Subop +-- +function current_version(input, output) + ret = objclass.current_version() + output:append("" .. ret) + objclass.log(0, ret) +end + +function current_subop_num(input, output) + ret = objclass.current_subop_num() + output:append("" .. ret) + objclass.log(0, ret) +end + +function current_subop_version(input, output) + ret = objclass.current_subop_version() + output:append("" .. ret) + objclass.log(0, ret) +end + +objclass.register(current_version) +objclass.register(current_subop_num) +objclass.register(current_subop_version) + +)luascript"; + +/* + * Test harness uses single pool for the entire test case, and generates + * unique object names for each test. + */ +class ClsLua : public ::testing::Test { + protected: + static void SetUpTestCase() { + pool_name = get_temp_pool_name(); + ASSERT_EQ("", create_one_pool_pp(pool_name, rados)); + ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx)); + } + + static void TearDownTestCase() { + ioctx.close(); + ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados)); + } + + virtual void SetUp() { + /* Grab test names to build unique objects */ + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + + /* Create unique string using test/testname/pid */ + std::stringstream ss_oid; + ss_oid << test_info->test_case_name() << "_" << + test_info->name() << "_" << getpid(); + + /* Unique object for test to use */ + oid = ss_oid.str(); + } + + virtual void TearDown() { + } + + /* + * Helper function. This functionality should eventually make its way into + * a clslua client library of some sort. + */ + int __clslua_exec(const string& oid, const string& script, + librados::bufferlist *input = NULL, const string& funcname = "") + { + bufferlist inbl; + if (input) + inbl = *input; + + reply_output.clear(); + + return cls_lua_client::exec(ioctx, oid, script, funcname, inbl, + reply_output); + } + + int clslua_exec(const string& script, librados::bufferlist *input = NULL, + const string& funcname = "") + { + return __clslua_exec(oid, script, input, funcname); + } + + static librados::Rados rados; + static librados::IoCtx ioctx; + static string pool_name; + + string oid; + bufferlist reply_output; +}; + +librados::Rados ClsLua::rados; +librados::IoCtx ClsLua::ioctx; +string ClsLua::pool_name; + +TEST_F(ClsLua, Write) { + /* write some data into object */ + string written = "Hello World"; + bufferlist inbl; + ::encode(written, inbl); + ASSERT_EQ(0, clslua_exec(test_script, &inbl, "write")); + + /* have Lua read out of the object */ + uint64_t size; + bufferlist outbl; + ASSERT_EQ(0, ioctx.stat(oid, &size, NULL)); + ASSERT_EQ(size, (uint64_t)ioctx.read(oid, outbl, size, 0) ); + + /* compare what Lua read to what we wrote */ + string read; + ::decode(read, outbl); + ASSERT_EQ(read, written); +} + +TEST_F(ClsLua, SyntaxError) { + ASSERT_EQ(-EIO, clslua_exec("-")); +} + +TEST_F(ClsLua, EmptyScript) { + ASSERT_EQ(0, clslua_exec("")); +} + +TEST_F(ClsLua, RetVal) { + /* handlers can return numeric values */ + ASSERT_EQ(1, clslua_exec(test_script, NULL, "rv_h1")); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "rv_h0")); + ASSERT_EQ(-1, clslua_exec(test_script, NULL, "rv_hn1")); + ASSERT_EQ(1, clslua_exec(test_script, NULL, "rv_hs1")); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "rv_hs0")); + ASSERT_EQ(-1, clslua_exec(test_script, NULL, "rv_hsn1")); + + /* no return value is success */ + ASSERT_EQ(0, clslua_exec(test_script, NULL, "rv_h")); + + /* non-numeric return values are errors */ + ASSERT_EQ(-EIO, clslua_exec(test_script, NULL, "rv_hnil")); + ASSERT_EQ(-EIO, clslua_exec(test_script, NULL, "rv_ht")); + ASSERT_EQ(-EIO, clslua_exec(test_script, NULL, "rv_hstr")); +} + +TEST_F(ClsLua, Create) { + /* create works */ + ASSERT_EQ(0, clslua_exec(test_script, NULL, "create_c")); + + /* exclusive works */ + ASSERT_EQ(-EEXIST, clslua_exec(test_script, NULL, "create_c")); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "create_cne")); +} + +TEST_F(ClsLua, Pcall) { + /* create and error works */ + ASSERT_EQ(0, clslua_exec(test_script, NULL, "pcall_c")); + ASSERT_EQ(-EEXIST, clslua_exec(test_script, NULL, "pcall_c")); + + /* pcall masks the error */ + ASSERT_EQ(0, clslua_exec(test_script, NULL, "pcall_pc")); + + /* pcall lets us get the failed return value */ + ASSERT_EQ(-EEXIST, clslua_exec(test_script, NULL, "pcall_pcr")); + + /* + * the first call in pcr2 will fail (check ret != 0), and the second pcall + * should also fail (we check with a bogus return value to mask real + * errors). This is also an important check for our error handling because + * we need a case where two functions in the same handler fail to exercise + * our internal error book keeping. + */ + ASSERT_EQ(-9999, clslua_exec(test_script, NULL, "pcall_pcr2")); +} + +TEST_F(ClsLua, Remove) { + /* object doesn't exist */ + ASSERT_EQ(-ENOENT, clslua_exec(test_script, NULL, "remove_r")); + + /* can remove */ + ASSERT_EQ(0, clslua_exec(test_script, NULL, "remove_c")); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "remove_r")); + ASSERT_EQ(-ENOENT, clslua_exec(test_script, NULL, "remove_r")); +} + +TEST_F(ClsLua, Stat) { + /* build object and stat */ + char buf[1024]; + bufferlist bl; + bl.append(buf, sizeof(buf)); + ASSERT_EQ(0, ioctx.write_full(oid, bl)); + uint64_t size; + time_t mtime; + ASSERT_EQ(0, ioctx.stat(oid, &size, &mtime)); + + /* test stat success */ + ASSERT_EQ(0, clslua_exec(test_script, NULL, "stat_ret")); + + // size,mtime from ioctx call + std::stringstream s1; + s1 << size << "," << mtime; + + // lua constructed size,mtime string + std::string s2(reply_output.c_str(), reply_output.length()); + + ASSERT_EQ(s1.str(), s2); + + /* test object dne */ + ASSERT_EQ(-ENOENT, __clslua_exec("dne", test_script, NULL, "stat_sdne")); + + /* can capture error with pcall */ + ASSERT_EQ(-ENOENT, __clslua_exec("dne", test_script, NULL, "stat_sdne_pcall")); +} + +TEST_F(ClsLua, MapClear) { + /* write some data into a key */ + string msg = "This is a test message"; + bufferlist val; + val.append(msg.c_str(), msg.size()); + map map; + map["foo"] = val; + ASSERT_EQ(0, ioctx.omap_set(oid, map)); + + /* test we can get it back out */ + set keys; + keys.insert("foo"); + map.clear(); + ASSERT_EQ(0, (int)map.count("foo")); + ASSERT_EQ(0, ioctx.omap_get_vals_by_keys(oid, keys, &map)); + ASSERT_EQ(1, (int)map.count("foo")); + + /* now clear it */ + ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_clear")); + + /* test that the map we get back is empty now */ + map.clear(); + ASSERT_EQ(0, (int)map.count("foo")); + ASSERT_EQ(0, ioctx.omap_get_vals_by_keys(oid, keys, &map)); + ASSERT_EQ(0, (int)map.count("foo")); +} + +TEST_F(ClsLua, MapSetVal) { + /* build some input value */ + bufferlist orig_val; + ::encode("this is the original value yay", orig_val); + + /* have the lua script stuff the data into a map value */ + ASSERT_EQ(0, clslua_exec(test_script, &orig_val, "map_set_val")); + + /* grap the key now and compare to orig */ + map out_map; + set out_keys; + out_keys.insert("foo"); + ASSERT_EQ(0, ioctx.omap_get_vals_by_keys(oid, out_keys, &out_map)); + bufferlist out_bl = out_map["foo"]; + string out_val; + ::decode(out_val, out_bl); + ASSERT_EQ(out_val, "this is the original value yay"); +} + +TEST_F(ClsLua, MapGetVal) { + /* write some data into a key */ + string msg = "This is a test message"; + bufferlist orig_val; + orig_val.append(msg.c_str(), msg.size()); + map orig_map; + orig_map["foo"] = orig_val; + ASSERT_EQ(0, ioctx.omap_set(oid, orig_map)); + + /* now compare to what we put it */ + ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_get_val_foo")); + + /* check return */ + string ret_val; + ret_val.assign(reply_output.c_str(), reply_output.length()); + ASSERT_EQ(ret_val, msg); + + /* error case */ + ASSERT_EQ(-ENOENT, clslua_exec(test_script, NULL, "map_get_val_dne")); +} + +TEST_F(ClsLua, Read) { + /* put data into object */ + string msg = "This is a test message"; + bufferlist bl; + bl.append(msg.c_str(), msg.size()); + ASSERT_EQ(0, ioctx.write_full(oid, bl)); + + /* get lua to read it and send it back */ + ASSERT_EQ(0, clslua_exec(test_script, NULL, "read")); + + /* check return */ + string ret_val; + ret_val.assign(reply_output.c_str(), reply_output.length()); + ASSERT_EQ(ret_val, msg); +} + +TEST_F(ClsLua, Log) { + ASSERT_EQ(0, clslua_exec("objclass.log()")); + ASSERT_EQ(0, clslua_exec("s = objclass.log(); objclass.log(s);")); + ASSERT_EQ(0, clslua_exec("objclass.log(1)")); + ASSERT_EQ(0, clslua_exec("objclass.log(-1)")); + ASSERT_EQ(0, clslua_exec("objclass.log('x')")); + ASSERT_EQ(0, clslua_exec("objclass.log(0, 0)")); + ASSERT_EQ(0, clslua_exec("objclass.log(1, 1)")); + ASSERT_EQ(0, clslua_exec("objclass.log(-10, -10)")); + ASSERT_EQ(0, clslua_exec("objclass.log('x', 'y')")); + ASSERT_EQ(0, clslua_exec("objclass.log(1, 'one')")); + ASSERT_EQ(0, clslua_exec("objclass.log(1, 'one', 'two')")); + ASSERT_EQ(0, clslua_exec("objclass.log('one', 'two', 'three')")); + ASSERT_EQ(0, clslua_exec("s = objclass.log('one', 'two', 'three'); objclass.log(s);")); +} + +TEST_F(ClsLua, BufferlistEquality) { + ASSERT_EQ(0, clslua_exec(test_script, NULL, "bl_eq_empty_equal")); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "bl_eq_empty_selfequal")); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "bl_eq_selfequal")); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "bl_eq_equal")); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "bl_eq_notequal")); +} + +TEST_F(ClsLua, RunError) { + ASSERT_EQ(-EIO, clslua_exec(test_script, NULL, "runerr_c")); +} + +TEST_F(ClsLua, HandleNotFunc) { + string script = "x = 1;"; + ASSERT_EQ(-EOPNOTSUPP, clslua_exec(script, NULL, "x")); +} + +TEST_F(ClsLua, Register) { + /* normal cases: register and maybe call the handler */ + string script = "function h() end; objclass.register(h);"; + ASSERT_EQ(0, clslua_exec(script, NULL, "")); + ASSERT_EQ(0, clslua_exec(script, NULL, "h")); + + /* can register and call multiple handlers */ + script = "function h1() end; function h2() end;" + "objclass.register(h1); objclass.register(h2);"; + ASSERT_EQ(0, clslua_exec(script, NULL, "")); + ASSERT_EQ(0, clslua_exec(script, NULL, "h1")); + ASSERT_EQ(0, clslua_exec(script, NULL, "h2")); + + /* normal cases: register before function is defined */ + script = "objclass.register(h); function h() end;"; + ASSERT_EQ(-EIO, clslua_exec(script, NULL, "")); + ASSERT_EQ(-EIO, clslua_exec(script, NULL, "h")); + + /* cannot call handler that isn't registered */ + script = "function h() end;"; + ASSERT_EQ(-EIO, clslua_exec(script, NULL, "h")); + + /* handler doesn't exist */ + script = "objclass.register(lalala);"; + ASSERT_EQ(-EIO, clslua_exec(script, NULL, "")); + + /* handler isn't a function */ + script = "objclass.register('some string');"; + ASSERT_EQ(-EIO, clslua_exec(script, NULL, "")); + + /* cannot register handler multiple times */ + script = "function h() end; objclass.register(h); objclass.register(h);"; + ASSERT_EQ(-EIO, clslua_exec(script, NULL, "")); +} + +TEST_F(ClsLua, BufferlistCompare) { + ASSERT_EQ(0, clslua_exec(test_script, NULL, "bl_lt")); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "bl_le")); +} + +TEST_F(ClsLua, BufferlistConcat) { + ASSERT_EQ(0, clslua_exec(test_script, NULL, "bl_concat_eq")); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "bl_concat_ne")); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "bl_concat_immut")); +} + +TEST_F(ClsLua, GetXattr) { + bufferlist bl; + bl.append("blahblahblahblahblah"); + ASSERT_EQ(0, ioctx.setxattr(oid, "fooz", bl)); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "getxattr")); + ASSERT_TRUE(reply_output == bl); +} + +TEST_F(ClsLua, SetXattr) { + bufferlist inbl; + inbl.append("blahblahblahblahblah"); + ASSERT_EQ(0, clslua_exec(test_script, &inbl, "setxattr")); + bufferlist outbl; + ASSERT_EQ((int)inbl.length(), ioctx.getxattr(oid, "fooz2", outbl)); + ASSERT_TRUE(outbl == inbl); +} + +TEST_F(ClsLua, WriteFull) { + // write some data + char buf[1024]; + bufferlist blin; + blin.append(buf, sizeof(buf)); + ASSERT_EQ(0, ioctx.write(oid, blin, blin.length(), 0)); + bufferlist blout; + ASSERT_EQ((int)blin.length(), ioctx.read(oid, blout, 0, 0)); + ASSERT_EQ(blin, blout); + + // execute write_full from lua + blin.clear(); + char buf2[200]; + sprintf(buf2, "%s", "data replacing content"); + blin.append(buf2, sizeof(buf2)); + ASSERT_EQ(0, clslua_exec(test_script, &blin, "write_full")); + + // read it back + blout.clear(); + ASSERT_EQ((int)blin.length(), ioctx.read(oid, blout, 0, 0)); + ASSERT_EQ(blin, blout); +} + +TEST_F(ClsLua, GetXattrs) { + ASSERT_EQ(0, ioctx.create(oid, false)); + + ASSERT_EQ(0, clslua_exec(test_script, NULL, "getxattrs")); + ASSERT_EQ(0, (int)reply_output.length()); + + string key1str("key1str"); + bufferlist key1bl; + key1bl.append(key1str); + ASSERT_EQ(0, ioctx.setxattr(oid, "key1", key1bl)); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "getxattrs")); + string out1(reply_output.c_str(), reply_output.length()); // add the trailing \0 + ASSERT_STREQ(out1.c_str(), "key1/key1str/"); + + string key2str("key2str"); + bufferlist key2bl; + key2bl.append(key2str); + ASSERT_EQ(0, ioctx.setxattr(oid, "key2", key2bl)); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "getxattrs")); + string out2(reply_output.c_str(), reply_output.length()); // add the trailing \0 + ASSERT_STREQ(out2.c_str(), "key1/key1str/key2/key2str/"); + + string key3str("key3str"); + bufferlist key3bl; + key3bl.append(key3str); + ASSERT_EQ(0, ioctx.setxattr(oid, "key3", key3bl)); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "getxattrs")); + string out3(reply_output.c_str(), reply_output.length()); // add the trailing \0 + ASSERT_STREQ(out3.c_str(), "key1/key1str/key2/key2str/key3/key3str/"); +} + +TEST_F(ClsLua, MapGetKeys) { + ASSERT_EQ(0, ioctx.create(oid, false)); + + ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_get_keys")); + ASSERT_EQ(0, (int)reply_output.length()); + + map kvpairs; + + kvpairs["k1"] = bufferlist(); + ASSERT_EQ(0, ioctx.omap_set(oid, kvpairs)); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_get_keys")); + string out1(reply_output.c_str(), reply_output.length()); // add the trailing \0 + ASSERT_STREQ(out1.c_str(), "k1/"); + + kvpairs["k2"] = bufferlist(); + ASSERT_EQ(0, ioctx.omap_set(oid, kvpairs)); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_get_keys")); + string out2(reply_output.c_str(), reply_output.length()); // add the trailing \0 + ASSERT_STREQ(out2.c_str(), "k1/k2/"); + + kvpairs["xxx"] = bufferlist(); + ASSERT_EQ(0, ioctx.omap_set(oid, kvpairs)); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_get_keys")); + string out3(reply_output.c_str(), reply_output.length()); // add the trailing \0 + ASSERT_STREQ(out3.c_str(), "k1/k2/xxx/"); +} + +TEST_F(ClsLua, MapGetVals) { + ASSERT_EQ(0, ioctx.create(oid, false)); + + ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_get_vals")); + ASSERT_EQ(0, (int)reply_output.length()); + + map kvpairs; + + kvpairs["key1"] = bufferlist(); + kvpairs["key1"].append(string("key1str")); + ASSERT_EQ(0, ioctx.omap_set(oid, kvpairs)); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_get_vals")); + string out1(reply_output.c_str(), reply_output.length()); // add the trailing \0 + ASSERT_STREQ(out1.c_str(), "key1/key1str/"); + + kvpairs["key2"] = bufferlist(); + kvpairs["key2"].append(string("key2str")); + ASSERT_EQ(0, ioctx.omap_set(oid, kvpairs)); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_get_vals")); + string out2(reply_output.c_str(), reply_output.length()); // add the trailing \0 + ASSERT_STREQ(out2.c_str(), "key1/key1str/key2/key2str/"); + + kvpairs["key3"] = bufferlist(); + kvpairs["key3"].append(string("key3str")); + ASSERT_EQ(0, ioctx.omap_set(oid, kvpairs)); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_get_vals")); + string out3(reply_output.c_str(), reply_output.length()); // add the trailing \0 + ASSERT_STREQ(out3.c_str(), "key1/key1str/key2/key2str/key3/key3str/"); +} + +TEST_F(ClsLua, MapHeader) { + ASSERT_EQ(0, ioctx.create(oid, false)); + + bufferlist bl_out; + ASSERT_EQ(0, ioctx.omap_get_header(oid, &bl_out)); + ASSERT_EQ(0, (int)bl_out.length()); + + std::string val("this is a value"); + bufferlist hdr; + hdr.append(val); + + ASSERT_EQ(0, clslua_exec(test_script, &hdr, "map_write_header")); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_read_header")); + + ASSERT_EQ(reply_output, hdr); +} + +TEST_F(ClsLua, MapSetVals) { + ASSERT_EQ(0, ioctx.create(oid, false)); + + ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_set_vals_empty")); + + std::map out_vals; + ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_set_vals_one")); + ASSERT_EQ(0, ioctx.omap_get_vals(oid, "", 100, &out_vals)); + ASSERT_EQ(1, (int)out_vals.size()); + ASSERT_STREQ("a_val", std::string(out_vals["a"].c_str(), out_vals["a"].length()).c_str()); + + out_vals.clear(); + ASSERT_EQ(0, ioctx.omap_clear(oid)); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_set_vals_two")); + ASSERT_EQ(0, ioctx.omap_get_vals(oid, "", 100, &out_vals)); + ASSERT_EQ(2, (int)out_vals.size()); + ASSERT_STREQ("a_val", std::string(out_vals["a"].c_str(), out_vals["a"].length()).c_str()); + ASSERT_STREQ("b_val", std::string(out_vals["b"].c_str(), out_vals["b"].length()).c_str()); + + out_vals.clear(); + ASSERT_EQ(0, ioctx.omap_clear(oid)); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_set_vals_three")); + ASSERT_EQ(0, ioctx.omap_get_vals(oid, "", 100, &out_vals)); + ASSERT_EQ(3, (int)out_vals.size()); + ASSERT_STREQ("a_val", std::string(out_vals["a"].c_str(), out_vals["a"].length()).c_str()); + ASSERT_STREQ("b_val", std::string(out_vals["b"].c_str(), out_vals["b"].length()).c_str()); + ASSERT_STREQ("c_val", std::string(out_vals["c"].c_str(), out_vals["c"].length()).c_str()); + + out_vals.clear(); + ASSERT_EQ(0, ioctx.omap_clear(oid)); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_set_vals_array")); + ASSERT_EQ(0, ioctx.omap_get_vals(oid, "", 100, &out_vals)); + ASSERT_EQ(2, (int)out_vals.size()); + ASSERT_STREQ("1_val", std::string(out_vals["1"].c_str(), out_vals["1"].length()).c_str()); + ASSERT_STREQ("2_val", std::string(out_vals["2"].c_str(), out_vals["2"].length()).c_str()); + + out_vals.clear(); + ASSERT_EQ(0, ioctx.omap_clear(oid)); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_set_vals_mixed")); + ASSERT_EQ(0, ioctx.omap_get_vals(oid, "", 100, &out_vals)); + ASSERT_EQ(4, (int)out_vals.size()); + ASSERT_STREQ("a_val", std::string(out_vals["a"].c_str(), out_vals["a"].length()).c_str()); + ASSERT_STREQ("b_val", std::string(out_vals["b"].c_str(), out_vals["b"].length()).c_str()); + ASSERT_STREQ("1_val", std::string(out_vals["1"].c_str(), out_vals["1"].length()).c_str()); + ASSERT_STREQ("2_val", std::string(out_vals["2"].c_str(), out_vals["2"].length()).c_str()); + + ASSERT_EQ(-EINVAL, clslua_exec(test_script, NULL, "map_set_vals_bad_val")); +} + +TEST_F(ClsLua, MapRemoveKey) { + ASSERT_EQ(0, ioctx.create(oid, false)); + + std::map out_vals; + ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_set_vals_two")); + ASSERT_EQ(0, ioctx.omap_get_vals(oid, "", 100, &out_vals)); + ASSERT_EQ(2, (int)out_vals.size()); + ASSERT_STREQ("a_val", std::string(out_vals["a"].c_str(), out_vals["a"].length()).c_str()); + ASSERT_STREQ("b_val", std::string(out_vals["b"].c_str(), out_vals["b"].length()).c_str()); + + out_vals.clear(); + ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_remove_key")); + ASSERT_EQ(0, ioctx.omap_get_vals(oid, "", 100, &out_vals)); + ASSERT_EQ(1, (int)out_vals.size()); + ASSERT_STREQ("b_val", std::string(out_vals["b"].c_str(), out_vals["b"].length()).c_str()); +} + +TEST_F(ClsLua, VersionSubop) { + ASSERT_EQ(0, ioctx.create(oid, false)); + + ASSERT_EQ(0, clslua_exec(test_script, NULL, "current_version")); + ASSERT_GT((int)reply_output.length(), 0); + + ASSERT_EQ(0, clslua_exec(test_script, NULL, "current_subop_num")); + ASSERT_GT((int)reply_output.length(), 0); + + ASSERT_EQ(0, clslua_exec(test_script, NULL, "current_subop_version")); + ASSERT_GT((int)reply_output.length(), 0); +} + +TEST_F(ClsLua, Json) { + ASSERT_EQ(0, ioctx.create(oid, false)); + + bufferlist inbl, outbl; + + inbl.append(json_test_script); + + int ret = ioctx.exec(oid, "lua", "eval_json", inbl, outbl); + ASSERT_EQ(ret, 0); + + std::string out(outbl.c_str(), outbl.length()); + ASSERT_STREQ(out.c_str(), "omg it works"); +} -- 2.39.5