Noah Watkins

github
twitter
linkedin

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

30bd762cd913e5b33d66499bed483624ef44ed89