Writing RADOS object class handlers in Lua
21 February 2013
This post describes the API of the Lua object class handler system. In a previous post I provided some motivation for the project, and provided a description of the Lua object class error handling design. Another helpful resource is the Lua script used for internal unit testing that has working examples of the entire API. The previous link is to the C++ unit test suite, but at the top of the file is a long Lua script that is compiled into a string and used in the unit tests.
Update 27 March 2017: The Lua object class handlers are now merged into upstream Ceph as of the Kraken version. https://github.com/ceph/ceph/blob/master/src/cls/lua/cls_lua.cc.
Lua Object Class Structure
A Lua object class is an arbitrary Lua script containing at least one exported function handler that serves as a named entry point. By building up a collection of handlers, new and interesting interfaces to objects can be constructed and dynamically loaded into a running RADOS cluster. The basic structure is shown in the following code snippet:
-- helper modules -- helper functions -- etc... function helper() end function handler1(input, output) helper() end function handler2(input, output) end objclass.register(handler1) objclass.register(handler2)
In the above Lua script any number of functions and modules can be used to
support the behavior exported by the functions
client can remotely execute any registered function and provide an arbitrary
input, and receive an arbitrary output. Attempting to call an unregistered
handler results in a error.
Logging and Tracing
An object class can write into the OSD log to record debugging information
log function. The function takes any number of arguments which are
converted into strings and separated by spaces in the final output. If the
first argument is numeric then it is interpreted as a log-level. If no
log-level is specified a default log-level is used.
objclass.log('hi') -- will log 'hi' objclass.log(0, 'ouch') -- log 'ouch' at log-level = 0 objclass.log('foo', 'bar') -- log 'foo bar' objclass.log(1) -- will log '1' at default log-level
Logging can also be used to trace execution to aid in debugging. Any message
logged using the
log function will also be recorded in an ordered list and
returned to the client after execution. A client can print the list to view
the order of execution, analogous to using
printf statements for debugging.
Object classes written in Lua may have many functions, only a subset of which
represent entry points to the functionality provided by the script. In order
to be able to call a function defined in a Lua object class, the function must
first be exported by registering it. This is done using the
function. The following code snippet illustrates how this works.
function helper() -- help out with stuff end function thehandler(input, output) helper() end objclass.register(thehandler)
In the above example
objclass.register(thehandler) exports the function
thehandler, making it available for clients to access. A client that
attempts to call the
helper function (an unregistered function), will
receive a return value of
Metadata and Life Cycle
stat function to retrieve object size and modification time
-- grab both stats size, mtime = objclass.stat() -- only size size = objclass.stat()
An object is created using the
create function which takes a boolean
parameter specifying exclusivity semantics. If
true is passed to
and the object already exists then
-ENOENT is returned.
objclass.create(false) ok, ret = pcall(objclass.create, true) assert(ret == -objclass.ENOENT)
An object is deleted using the
Object Payload I/O
The payload data of an object can be read from and written to using the
write functions. Each function takes an offset and length parameter.
size = objclass.stat() data = objclass.read(0, size) -- size bytes from offset 0 objclass.write(0, data:length(), data) -- length of data at offset 0
A key/value store supporting range queries (based on Google’s LevelDB) can be
accessed using the
map_get_val functions. A key can be any
string and a value is a standard blob of any size.
function handler(input, output) objclass.map_set_val("foo", input) data = objclass.map_get_val("foo") assert(data == input) end
Note: there are additional functions for interacting with the indexing service that are not reflected in the Lua bindings. While we covered the major components, we are expanding the interface as needed.
Thanks for reading
If you find this content interesting or helpful, please consider sharing it with others.