Skip to content

Commit

Permalink
Merge pull request ceph#42779 from Matan-B/lua-rgw-map
Browse files Browse the repository at this point in the history
rgw: Lua scripting global map feature
Reviwed-by: dang, cbodley, yuvalif, anthonyeleven, tchaikov,  mattbenjamin, batrick
  • Loading branch information
yuvalif authored Jun 8, 2022
2 parents 1af1377 + f9d3be6 commit c824b00
Show file tree
Hide file tree
Showing 19 changed files with 679 additions and 128 deletions.
31 changes: 25 additions & 6 deletions doc/radosgw/lua-scripting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ Lua Scripting

.. contents::

This feature allows users to upload Lua scripts to different context in the radosgw. The two supported context are "preRequest" that will execute a script before the
operation was taken, and "postRequest" that will execute after each operation is taken. Script may be uploaded to address requests for users of a specific tenant.
The script can access fields in the request and modify some fields. All Lua language features can be used in the script.
This feature allows users to assign execution context to Lua scripts. The three supported contexts are ``preRequest``" which will execute a script before each
operation is performed, ``postRequest`` which will execute after each operation is performed, and ``background`` which will execute within a specified time interval.
A request context script may be constrained to operations belonging to a specific tenant's users.
The request context script can also access fields in the request and modify some fields. All Lua language features can be used.

By default, all lua standard libraries are available in the script, however, in order to allow for other lua modules to be used in the script, we support adding packages to an allowlist:

Expand All @@ -28,23 +29,27 @@ Script Management via CLI

To upload a script:


::
# radosgw-admin script put --infile={lua-file} --context={preRequest|postRequest} [--tenant={tenant-name}]
# radosgw-admin script put --infile={lua-file} --context={preRequest|postRequest|background} [--tenant={tenant-name}]


* When uploading a script with the ``background`` context, a tenant name may not be specified.


To print the content of the script to standard output:

::
# radosgw-admin script get --context={preRequest|postRequest} [--tenant={tenant-name}]
# radosgw-admin script get --context={preRequest|postRequest|background} [--tenant={tenant-name}]


To remove the script:

::
# radosgw-admin script rm --context={preRequest|postRequest} [--tenant={tenant-name}]
# radosgw-admin script rm --context={preRequest|postRequest|background} [--tenant={tenant-name}]


Package Management via CLI
Expand Down Expand Up @@ -301,6 +306,20 @@ Operations Log
~~~~~~~~~~~~~~
The ``Request.Log()`` function prints the requests into the operations log. This function has no parameters. It returns 0 for success and an error code if it fails.

Background Context
--------------------
The ``background`` context may be used for purposes that include analytics, monitoring, caching data for other context executions.

The ``RGW`` Lua table is accessible from all contexts and saves data written to it
during execution so that it may be read and used later during other executions, from the same context of a different one.

- Background script execution default interval is 5 seconds.

- Each RGW instance has its own private and ephemeral ``RGW`` Lua table that is lost when the daemon restarts. Note that ``background`` context scripts will run on every instance.

- The maximum number of entries in the table is 100,000. Each entry has a key and string value with a combined length of no more than 1KB.
A Lua script will abort with an error if the number of entries or entry size exceeds these limits.

Lua Code Samples
----------------
- Print information on source and destination objects in case of copy:
Expand Down
8 changes: 6 additions & 2 deletions src/rgw/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@ set(librgw_common_srcs
rgw_lua_utils.cc
rgw_lua.cc
rgw_bucket_encryption.cc
rgw_tracer.cc)
rgw_tracer.cc
rgw_lua_background.cc)

if(WITH_RADOSGW_AMQP_ENDPOINT)
list(APPEND librgw_common_srcs rgw_amqp.cc)
Expand Down Expand Up @@ -358,7 +359,10 @@ add_library(radosgw SHARED
target_compile_definitions(radosgw PUBLIC "-DCLS_CLIENT_HIDE_IOCTX")
target_include_directories(radosgw
PUBLIC "${CMAKE_SOURCE_DIR}/src/dmclock/support/src"
PRIVATE "${CMAKE_SOURCE_DIR}/src/libkmip")
PRIVATE "${CMAKE_SOURCE_DIR}/src/libkmip"
PUBLIC "${CMAKE_SOURCE_DIR}/src/rgw"
PRIVATE "${LUA_INCLUDE_DIR}")

target_include_directories(radosgw SYSTEM PUBLIC "../rapidjson/include")

target_link_libraries(radosgw
Expand Down
12 changes: 8 additions & 4 deletions src/rgw/rgw_admin.cc
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ void usage()
cout << " --subscription pubsub subscription name\n";
cout << " --event-id event id in a pubsub subscription\n";
cout << "\nScript options:\n";
cout << " --context context in which the script runs. one of: preRequest, postRequest\n";
cout << " --context context in which the script runs. one of: preRequest, postRequest, background\n";
cout << " --package name of the lua package that should be added/removed to/from the allowlist\n";
cout << " --allow-compilation package is allowed to compile C code as part of its installation\n";
cout << "\nradoslist options:\n";
Expand Down Expand Up @@ -10346,7 +10346,11 @@ int main(int argc, const char **argv)
}
const rgw::lua::context script_ctx = rgw::lua::to_context(*str_script_ctx);
if (script_ctx == rgw::lua::context::none) {
cerr << "ERROR: invalid script context: " << *str_script_ctx << ". must be one of: preRequest, postRequest" << std::endl;
cerr << "ERROR: invalid script context: " << *str_script_ctx << ". must be one of: preRequest, postRequest, background" << std::endl;
return EINVAL;
}
if (script_ctx == rgw::lua::context::background && !tenant.empty()) {
cerr << "ERROR: cannot specify tenant in background context" << std::endl;
return EINVAL;
}
rc = rgw::lua::write_script(dpp(), store, tenant, null_yield, script_ctx, script);
Expand All @@ -10363,7 +10367,7 @@ int main(int argc, const char **argv)
}
const rgw::lua::context script_ctx = rgw::lua::to_context(*str_script_ctx);
if (script_ctx == rgw::lua::context::none) {
cerr << "ERROR: invalid script context: " << *str_script_ctx << ". must be one of: preRequest, postRequest" << std::endl;
cerr << "ERROR: invalid script context: " << *str_script_ctx << ". must be one of: preRequest, postRequest, background" << std::endl;
return EINVAL;
}
std::string script;
Expand All @@ -10386,7 +10390,7 @@ int main(int argc, const char **argv)
}
const rgw::lua::context script_ctx = rgw::lua::to_context(*str_script_ctx);
if (script_ctx == rgw::lua::context::none) {
cerr << "ERROR: invalid script context: " << *str_script_ctx << ". must be one of: preRequest, postRequest" << std::endl;
cerr << "ERROR: invalid script context: " << *str_script_ctx << ". must be one of: preRequest, postRequest, background" << std::endl;
return EINVAL;
}
const auto rc = rgw::lua::delete_script(dpp(), store, tenant, null_yield, script_ctx);
Expand Down
2 changes: 1 addition & 1 deletion src/rgw/rgw_asio_frontend.cc
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ void handle_connection(boost::asio::io_context& context,
*env.auth_registry, &client, env.olog, y,
scheduler, &user, &latency,
env.ratelimiting->get_active(),
&http_ret);
&http_ret, env.lua_background);

if (cct->_conf->subsys.should_gather(dout_subsys, 1)) {
// access log line elements begin per Apache Combined Log Format with additions following
Expand Down
2 changes: 1 addition & 1 deletion src/rgw/rgw_loadgen_process.cc
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ void RGWLoadGenProcess::handle_request(const DoutPrefixProvider *dpp, RGWRequest
int ret = process_request(store, rest, req, uri_prefix,
*auth_registry, &client_io, olog,
null_yield, nullptr, nullptr, nullptr,
ratelimit.get_active());
ratelimit.get_active(), nullptr);
if (ret < 0) {
/* we don't really care about return code */
dout(20) << "process_request() returned " << ret << dendl;
Expand Down
5 changes: 5 additions & 0 deletions src/rgw/rgw_lua.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ context to_context(const std::string& s)
if (strcasecmp(s.c_str(), "postrequest") == 0) {
return context::postRequest;
}
if (strcasecmp(s.c_str(), "background") == 0) {
return context::background;
}
return context::none;
}

Expand All @@ -33,6 +36,8 @@ std::string to_string(context ctx)
return "prerequest";
case context::postRequest:
return "postrequest";
case context::background:
return "background";
case context::none:
break;
}
Expand Down
1 change: 1 addition & 0 deletions src/rgw/rgw_lua.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace rgw::lua {
enum class context {
preRequest,
postRequest,
background,
none
};

Expand Down
139 changes: 139 additions & 0 deletions src/rgw/rgw_lua_background.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#include "rgw_lua_background.h"
#include "rgw_lua.h"
#include "rgw_lua_utils.h"
#include "rgw_perf_counters.h"
#include "include/ceph_assert.h"
#include <lua.hpp>

#define dout_subsys ceph_subsys_rgw

namespace rgw::lua {

Background::Background(rgw::sal::Store* store,
CephContext* cct,
const std::string& luarocks_path,
int execute_interval) :
execute_interval(execute_interval),
dp(cct, dout_subsys, "lua background: "),
store(store),
cct(cct),
luarocks_path(luarocks_path) {}

void Background::shutdown(){
stopped = true;
cond.notify_all();
if (runner.joinable()) {
runner.join();
}
started = false;
stopped = false;
}

void Background::start() {
if (started) {
// start the thread only once
return;
}
started = true;
runner = std::thread(&Background::run, this);
const auto rc = ceph_pthread_setname(runner.native_handle(),
"lua_background");
ceph_assert(rc == 0);
}

void Background::pause() {
{
std::unique_lock cond_lock(pause_mutex);
paused = true;
}
cond.notify_all();
}

void Background::resume(rgw::sal::Store* _store) {
store = _store;
paused = false;
cond.notify_all();
}

int Background::read_script() {
std::unique_lock cond_lock(pause_mutex);
if (paused) {
return -EAGAIN;
}
std::string tenant;
return rgw::lua::read_script(&dp, store, tenant, null_yield, rgw::lua::context::background, rgw_script);
}

const std::string Background::empty_table_value;

const std::string& Background::get_table_value(const std::string& key) const {
std::unique_lock cond_lock(table_mutex);
const auto it = rgw_map.find(key);
if (it == rgw_map.end()) {
return empty_table_value;
}
return it->second;
}

void Background::put_table_value(const std::string& key, const std::string& value) {
std::unique_lock cond_lock(table_mutex);
rgw_map[key] = value;
}

//(1) Loads the script from the object if not paused
//(2) Executes the script
//(3) Sleep (configurable)
void Background::run() {
lua_State* const L = luaL_newstate();
rgw::lua::lua_state_guard lguard(L);
open_standard_libs(L);
set_package_path(L, luarocks_path);
create_debug_action(L, cct);
create_background_metatable(L);
const DoutPrefixProvider* const dpp = &dp;

while (!stopped) {
if (paused) {
ldpp_dout(dpp, 10) << "Lua background thread paused" << dendl;
std::unique_lock cond_lock(cond_mutex);
cond.wait(cond_lock, [this]{return !paused || stopped;});
if (stopped) {
ldpp_dout(dpp, 10) << "Lua background thread stopped" << dendl;
return;
}
ldpp_dout(dpp, 10) << "Lua background thread resumed" << dendl;
}
const auto rc = read_script();
if (rc == -ENOENT || rc == -EAGAIN) {
// either no script or paused, nothing to do
} else if (rc < 0) {
ldpp_dout(dpp, 1) << "WARNING: failed to read background script. error " << rc << dendl;
} else {
auto failed = false;
try {
//execute the background lua script
if (luaL_dostring(L, rgw_script.c_str()) != LUA_OK) {
const std::string err(lua_tostring(L, -1));
ldpp_dout(dpp, 1) << "Lua ERROR: " << err << dendl;
failed = true;
}
} catch (const std::exception& e) {
ldpp_dout(dpp, 1) << "Lua ERROR: " << e.what() << dendl;
failed = true;
}
if (perfcounter) {
perfcounter->inc((failed ? l_rgw_lua_script_fail : l_rgw_lua_script_ok), 1);
}
}
std::unique_lock cond_lock(cond_mutex);
cond.wait_for(cond_lock, std::chrono::seconds(execute_interval), [this]{return stopped;});
}
ldpp_dout(dpp, 10) << "Lua background thread stopped" << dendl;
}

void Background::create_background_metatable(lua_State* L) {
create_metatable<rgw::lua::RGWTable>(L, true, &rgw_map, &table_mutex);
}

} //namespace lua

79 changes: 79 additions & 0 deletions src/rgw/rgw_lua_background.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#pragma once
#include "common/dout.h"
#include "rgw_common.h"
#include <string>
#include "rgw_lua_utils.h"
#include "rgw_realm_reloader.h"

namespace rgw::lua {

//Interval between each execution of the script is set to 5 seconds
constexpr const int INIT_EXECUTE_INTERVAL = 5;

//Writeable meta table named RGW with mutex protection
using BackgroundMap = std::unordered_map<std::string, std::string>;
struct RGWTable : StringMapMetaTable<BackgroundMap,
StringMapWriteableNewIndex<BackgroundMap>> {
static std::string TableName() {return "RGW";}
static std::string Name() {return TableName() + "Meta";}
static int IndexClosure(lua_State* L) {
auto& mtx = *reinterpret_cast<std::mutex*>(lua_touserdata(L, lua_upvalueindex(2)));
std::lock_guard l(mtx);
return StringMapMetaTable::IndexClosure(L);
}
static int LenClosure(lua_State* L) {
auto& mtx = *reinterpret_cast<std::mutex*>(lua_touserdata(L, lua_upvalueindex(2)));
std::lock_guard l(mtx);
return StringMapMetaTable::LenClosure(L);
}
static int NewIndexClosure(lua_State* L) {
auto& mtx = *reinterpret_cast<std::mutex*>(lua_touserdata(L, lua_upvalueindex(2)));
std::lock_guard l(mtx);
return StringMapMetaTable::NewIndexClosure(L);
}
};

class Background : public RGWRealmReloader::Pauser {

private:
BackgroundMap rgw_map;
bool stopped = false;
bool started = false;
bool paused = false;
int execute_interval;
const DoutPrefix dp;
rgw::sal::Store* store;
CephContext* const cct;
const std::string luarocks_path;
std::thread runner;
mutable std::mutex table_mutex;
std::mutex cond_mutex;
std::mutex pause_mutex;
std::condition_variable cond;
static const std::string empty_table_value;

void run();

protected:
std::string rgw_script;
virtual int read_script();

public:
Background(rgw::sal::Store* store,
CephContext* cct,
const std::string& luarocks_path,
int execute_interval = INIT_EXECUTE_INTERVAL);

virtual ~Background() = default;
void start();
void shutdown();
void create_background_metatable(lua_State* L);
const std::string& get_table_value(const std::string& key) const;
void put_table_value(const std::string& key, const std::string& value);

void pause() override;
void resume(rgw::sal::Store* _store) override;
};

} //namepsace lua

Loading

0 comments on commit c824b00

Please sign in to comment.