Writing RADOS object class handlers in Lua
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 22 January 2014: Here is a larger tutorial on the topic of building RADOS object classes with Lua.
Update 27 March 2017: The Lua object class handlers are now merged into upstream Ceph as of the Kraken version.
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 handler1
and handler2
. A
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
using the 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.
Handler Registration #
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 register
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 -ENOTSUPP
.
Metadata and Life Cycle #
Use the stat
function to retrieve object size and modification time
information.
-- 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 create
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 remove
function.
objclass.remove()
Object Payload I/O #
The payload data of an object can be read from and written to using the read
and 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
Indexing #
A key/value store supporting range queries (based on Google’s LevelDB) can be
accessed using the map_set_val
and 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.