Manpage radosgw-admin <../../man/8/radosgw-admin>
QAT Acceleration for Encryption and Compression <qat-accel>
S3-select <s3select>
+ Lua Scripting <lua-scripting>
--- /dev/null
+=============
+Lua Scripting
+=============
+
+.. versionadded:: Pacific
+
+.. 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.
+
+.. toctree::
+ :maxdepth: 1
+
+
+Script Management via CLI
+-------------------------
+
+To upload a script:
+
+::
+
+ # radosgw-admin script put --infile={lua-file} --context={preRequest|postRequest} [--tenant={tenant-name}]
+
+
+To print the content of the script to standard output:
+
+::
+
+ # radosgw-admin script get --context={preRequest|postRequest} [--tenant={tenant-name}]
+
+
+To remove the script:
+
+::
+
+ # radosgw-admin script rm --context={preRequest|postRequest} [--tenant={tenant-name}]
+
+
+Context Free Functions
+----------------------
+Debug Log
+~~~~~~~~~
+The ``RGWDebugLog()`` function accepts a string and prints it to the debug log with priority 20.
+Each log message is prefixed ``Lua INFO:``. This function has no return value.
+
+Request Fields
+-----------------
+
+.. warning:: This feature is experimental. Fields may be removed or renamed in the future.
+
+.. note::
+
+ - Although Lua is a case-sensitive language, field names provided by the radosgw are case-insensitive. Function names remain case-sensitive.
+ - Fields marked "optional" can have a nil value.
+ - Fields marked as "iterable" can be used by the pairs() function and with the # length operator.
+ - All table fields can be used with the bracket operator ``[]``.
+ - ``time`` fields are strings with the following format: ``%Y-%m-%d %H:%M:%S``.
+
+
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| Field | Type | Description | Iterable | Writeable | Optional |
++====================================================+==========+==============================================================+==========+===========+==========+
+| ``Request.RGWOp`` | string | radosgw operation | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.DecodedURI`` | string | decoded URI | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.ContentLength`` | integer | size of the request | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.GenericAttributes`` | table | string to string generic attributes map | yes | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Response`` | table | response to the request | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Response.HTTPStatusCode`` | integer | HTTP status code | no | yes | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Response.HTTPStatus`` | string | HTTP status text | no | yes | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Response.RGWCode`` | integer | radosgw error code | no | yes | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Response.Message`` | string | response message | no | yes | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.SwiftAccountName`` | string | swift account name | no | no | yes |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Bucket`` | table | info on the bucket | no | no | yes |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Bucket.Tenant`` | string | tenant of the bucket | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Bucket.Name`` | string | bucket name | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Bucket.Marker`` | string | bucket marker (initial id) | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Bucket.Id`` | string | bucket id | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Bucket.Count`` | integer | number of objects in the bucket | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Bucket.Size`` | integer | total size of objects in the bucket | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Bucket.ZoneGroupId`` | string | zone group of the bucket | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Bucket.CreationTime`` | time | creation time of the bucket | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Bucket.MTime`` | time | modification time of the bucket | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Bucket.Quota`` | table | bucket quota | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Bucket.Quota.MaxSize`` | integer | bucket quota max size | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Bucket.Quota.MaxObjects`` | integer | bucket quota max number of objects | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Reques.Bucket.Quota.Enabled`` | boolean | bucket quota is enabled | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Bucket.Quota.Rounded`` | boolean | bucket quota is rounded to 4K | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Bucket.PlacementRule`` | table | bucket placement rule | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Bucket.PlacementRule.Name`` | string | bucket placement rule name | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Bucket.PlacementRule.StorageClass`` | string | bucket placement rule storage class | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Bucket.User`` | table | bucket owner | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Bucket.User.Tenant`` | string | bucket owner tenant | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Bucket.User.Id`` | string | bucket owner id | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Object`` | table | info on the object | no | no | yes |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Object.Name`` | string | object name | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Object.Instance`` | string | object version | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Object.Id`` | string | object id | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Object.Size`` | integer | object size | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Object.MTime`` | time | object mtime | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.CopyFrom`` | table | information on copy operation | no | no | yes |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.CopyFrom.Tenant`` | string | tenant of the object copied from | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.CopyFrom.Bucket`` | string | bucket of the object copied from | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.CopyFrom.Object`` | table | object copied from. See: ``Request.Object`` | no | no | yes |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.ObjectOwner`` | table | object owner | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.ObjectOwner.DisplayName`` | string | object owner display name | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.ObjectOwner.User`` | table | object user. See: ``Request.Bucket.User`` | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.ZoneGroup.Name`` | string | name of zone group | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.ZoneGroup.Endpoint`` | string | endpoint of zone group | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.UserAcl`` | table | user ACL | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.UserAcl.Owner`` | table | user ACL owner. See: ``Request.ObjectOwner`` | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.UserAcl.Grants`` | table | user ACL map of string to grant | yes | no | no |
+| | | note: grants without an Id are not presented when iterated | | | |
+| | | and only one of them can be accessed via brackets | | | |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.UserAcl.Grants["<name>"]`` | table | user ACL grant | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.UserAcl.Grants["<name>"].Type`` | integer | user ACL grant type | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.UserAcl.Grants["<name>"].User`` | table | user ACL grant user | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.UserAcl.Grants["<name>"].User.Tenant`` | table | user ACL grant user tenant | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.UserAcl.Grants["<name>"].User.Id`` | table | user ACL grant user id | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.UserAcl.Grants["<name>"].GroupType`` | integer | user ACL grant group type | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.UserAcl.Grants["<name>"].Referer`` | string | user ACL grant referer | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.BucketAcl`` | table | bucket ACL. See: ``Request.UserAcl`` | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.ObjectAcl`` | table | object ACL. See: ``Request.UserAcl`` | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Environment`` | table | string to string environment map | yes | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Policy`` | table | policy | no | no | yes |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Policy.Text`` | string | policy text | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Policy.Id`` | string | policy Id | no | no | yes |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Policy.Statements`` | table | list of string statements | yes | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.UserPolicies`` | table | list of user policies | yes | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.UserPolicies[<index>]`` | table | user policy. See: ``Request.Policy`` | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.RGWId`` | string | radosgw host id: ``<host>-<zone>-<zonegroup>`` | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.HTTP`` | table | HTTP header | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.HTTP.Parameters`` | table | string to string parameter map | yes | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.HTTP.Resources`` | table | string to string resource map | yes | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.HTTP.Metadata`` | table | string to string metadata map | yes | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.HTTP.Host`` | string | host name | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.HTTP.Method`` | string | HTTP method | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.HTTP.URI`` | string | URI | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.HTTP.QueryString`` | string | HTTP query string | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.HTTP.Domain`` | string | domain name | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Time`` | time | request time | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Dialect`` | string | "S3" or "Swift" | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Id`` | string | request Id | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.TransactionId`` | string | transaction Id | no | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Tags`` | table | object tags map | yes | no | no |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+
+Request Functions
+--------------------
+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.
+
+Lua Code Samples
+----------------
+- Print information on source and destination objects in case of copy:
+
+.. code-block:: lua
+
+ function print_object(object)
+ RGWDebugLog(" Name: " .. object.Name)
+ RGWDebugLog(" Instance: " .. object.Instance)
+ RGWDebugLog(" Id: " .. object.Id)
+ RGWDebugLog(" Size: " .. object.Size)
+ RGWDebugLog(" MTime: " .. object.MTime)
+ end
+
+ if Request.CopyFrom and Request.Object and Request.CopyFrom.Object then
+ RGWDebugLog("copy from object:")
+ print_object(Request.CopyFrom.Object)
+ RGWDebugLog("to object:")
+ print_object(Request.Object)
+ end
+
+- Print ACLs via a "generic function":
+
+.. code-block:: lua
+
+ function print_owner(owner)
+ RGWDebugLog("Owner:")
+ RGWDebugLog(" Dispaly Name: " .. owner.DisplayName)
+ RGWDebugLog(" Id: " .. owner.User.Id)
+ RGWDebugLog(" Tenanet: " .. owner.User.Tenant)
+ end
+
+ function print_acl(acl_type)
+ index = acl_type .. "ACL"
+ acl = Request[index]
+ if acl then
+ RGWDebugLog(acl_type .. "ACL Owner")
+ print_owner(acl.Owner)
+ RGWDebugLog(" there are " .. #acl.Grants .. " grant for owner")
+ for k,v in pairs(acl.Grants) do
+ RGWDebugLog(" Grant Key: " .. k)
+ RGWDebugLog(" Grant Type: " .. v.Type)
+ RGWDebugLog(" Grant Group Type: " .. v.GroupType)
+ RGWDebugLog(" Grant Referer: " .. v.Referer)
+ RGWDebugLog(" Grant User Tenant: " .. v.User.Tenant)
+ RGWDebugLog(" Grant User Id: " .. v.User.Id)
+ end
+ else
+ RGWDebugLog("no " .. acl_type .. " ACL in request: " .. Request.Id)
+ end
+ end
+
+ print_acl("User")
+ print_acl("Bucket")
+ print_acl("Object")
+
+- Use of operations log only in case of errors:
+
+.. code-block:: lua
+
+
+ if Request.Response.HTTPStatusCode ~= 200 then
+ RGWDebugLog("request is bad, use ops log")
+ rc = Request.Log()
+ RGWDebugLog("ops log return code: " .. rc)
+ end
+
+- Set values into the error message:
+
+.. code-block:: lua
+
+ if Request.Response.HTTPStatusCode == 500 then
+ Request.Response.Message = "<Message> something bad happened :-( </Message>"
+ end
+
rgw_url.cc
rgw_oidc_provider
rgw_datalog.cc
- cls_fifo_legacy.cc)
+ cls_fifo_legacy.cc
+ rgw_lua_utils.cc
+ rgw_lua.cc
+ rgw_lua_request.cc)
if(WITH_RADOSGW_AMQP_ENDPOINT)
list(APPEND librgw_common_srcs rgw_amqp.cc)
target_include_directories(rgw_common PUBLIC "${CMAKE_SOURCE_DIR}/src/dmclock/support/src")
target_include_directories(rgw_common PUBLIC "${CMAKE_SOURCE_DIR}/src/fmt/include")
target_include_directories(rgw_common PUBLIC "${CMAKE_SOURCE_DIR}/src/rgw")
+target_include_directories(rgw_common PRIVATE "${CMAKE_SOURCE_DIR}/src/lua/src")
+target_include_directories(rgw_common PRIVATE "${CMAKE_BINARY_DIR}/src/lua")
+
if(WITH_BOOST_CONTEXT)
target_include_directories(rgw_common PRIVATE
target_link_libraries(rgw_a PRIVATE OpenSSL::Crypto)
endif()
+target_link_libraries(rgw_a PRIVATE liblua)
+
if(WITH_BOOST_CONTEXT)
target_link_libraries(rgw_a PUBLIC spawn)
endif()
list(APPEND rgw_libs RDKafka::RDKafka)
endif()
+list(APPEND rgw_libs ${LUA_LIBRARIES})
+
set(rgw_schedulers_srcs
rgw_dmclock_scheduler_ctx.cc
rgw_dmclock_sync_scheduler.cc)
target_link_libraries(rgw PRIVATE RDKafka::RDKafka)
endif()
+target_link_libraries(rgw PRIVATE ${LUA_LIBRARIES})
+
set_target_properties(rgw PROPERTIES OUTPUT_NAME rgw VERSION 2.0.0
SOVERSION 2)
install(TARGETS rgw DESTINATION ${CMAKE_INSTALL_LIBDIR})
return true;
}
}
+
ACLGranteeType& get_type() { return type; }
const ACLGranteeType& get_type() const { return type; }
ACLPermission& get_permission() { return permission; }
}
}
+using ACLGrantMap = std::multimap<std::string, ACLGrant>;
+
class RGWAccessControlList
{
protected:
map<string, int> acl_user_map;
map<uint32_t, int> acl_group_map;
list<ACLReferer> referer_list;
- multimap<string, ACLGrant> grant_map;
+ ACLGrantMap grant_map;
void _add_grant(ACLGrant *grant);
public:
explicit RGWAccessControlList(CephContext *_cct) : cct(_cct) {}
if (struct_v >= 2) {
decode(acl_group_map, bl);
} else if (!maps_initialized) {
- multimap<string, ACLGrant>::iterator iter;
+ ACLGrantMap::iterator iter;
for (iter = grant_map.begin(); iter != grant_map.end(); ++iter) {
ACLGrant& grant = iter->second;
_add_grant(&grant);
void add_grant(ACLGrant *grant);
void remove_canon_user_grant(rgw_user& user_id);
- multimap<string, ACLGrant>& get_grant_map() { return grant_map; }
- const multimap<string, ACLGrant>& get_grant_map() const { return grant_map; }
+ ACLGrantMap& get_grant_map() { return grant_map; }
+ const ACLGrantMap& get_grant_map() const { return grant_map; }
void create_default(const rgw_user& id, string name) {
acl_user_map.clear();
rgw_user& get_id() { return id; }
const rgw_user& get_id() const { return id; }
string& get_display_name() { return display_name; }
-
+ const string& get_display_name() const { return display_name; }
friend bool operator==(const ACLOwner& lhs, const ACLOwner& rhs);
friend bool operator!=(const ACLOwner& lhs, const ACLOwner& rhs);
};
#include "rgw_sync_module_pubsub.h"
#include "rgw_bucket_sync.h"
#include "rgw_sync_checkpoint.h"
+#include "rgw_lua.h"
#include "services/svc_sync_modules.h"
#include "services/svc_cls.h"
cout << " subscription rm remove a pubsub subscription\n";
cout << " subscription pull show events in a pubsub subscription\n";
cout << " subscription ack ack (remove) an events in a pubsub subscription\n";
+ cout << " script put upload a lua script to a context\n";
+ cout << " script get get the lua script of a context\n";
+ cout << " script rm remove the lua scripts of a context\n";
cout << "options:\n";
cout << " --tenant=<tenant> tenant name\n";
cout << " --user_ns=<namespace> namespace of user (oidc in case of users authenticated with oidc provider)\n";
cout << " --topic bucket notifications/pubsub topic name\n";
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 << "\n";
generic_client_usage();
}
PUBSUB_SUB_RM,
PUBSUB_SUB_PULL,
PUBSUB_EVENT_RM,
+ SCRIPT_PUT,
+ SCRIPT_GET,
+ SCRIPT_RM,
};
}
{ "subscription rm", OPT::PUBSUB_SUB_RM },
{ "subscription pull", OPT::PUBSUB_SUB_PULL },
{ "subscription ack", OPT::PUBSUB_EVENT_RM },
+ { "script put", OPT::SCRIPT_PUT },
+ { "script get", OPT::SCRIPT_GET },
+ { "script rm", OPT::SCRIPT_RM },
};
static SimpleCmd::Aliases cmd_aliases = {
string sub_name;
string event_id;
+ std::optional<std::string> str_script_ctx;
+
std::optional<string> opt_group_id;
std::optional<string> opt_status;
std::optional<string> opt_flow_type;
opt_timeout_sec = std::chrono::seconds(atoi(val.c_str()));
} else if (ceph_argparse_binary_flag(args, i, &detail, NULL, "--detail", (char*)NULL)) {
// do nothing
+ } else if (ceph_argparse_witharg(args, i, &val, "--context", (char*)NULL)) {
+ str_script_ctx = val;
} else if (strncmp(*i, "-", 1) == 0) {
cerr << "ERROR: invalid flag " << *i << std::endl;
return EINVAL;
OPT::PUBSUB_TOPIC_GET,
OPT::PUBSUB_SUB_GET,
OPT::PUBSUB_SUB_PULL,
+ OPT::SCRIPT_GET,
};
}
}
+ if (opt_cmd == OPT::SCRIPT_PUT) {
+ if (!str_script_ctx) {
+ cerr << "ERROR: context was not provided (via --context)" << std::endl;
+ return EINVAL;
+ }
+ if (infile.empty()) {
+ cerr << "ERROR: infile was not provided (via --infile)" << std::endl;
+ return EINVAL;
+ }
+ bufferlist bl;
+ auto rc = read_input(infile, bl);
+ if (rc < 0) {
+ cerr << "ERROR: failed to read script: '" << infile << "'. error: " << rc << std::endl;
+ return rc;
+ }
+ const std::string script = bl.to_str();
+ std::string err_msg;
+ if (!rgw::lua::verify(script, err_msg)) {
+ cerr << "ERROR: script: '" << infile << "' has error: " << std::endl << err_msg << std::endl;
+ return EINVAL;
+ }
+ 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;
+ return EINVAL;
+ }
+ rc = rgw::lua::write_script(store, tenant, null_yield, script_ctx, script);
+ if (rc < 0) {
+ cerr << "ERROR: failed to put script. error: " << rc << std::endl;
+ return rc;
+ }
+ }
+
+ if (opt_cmd == OPT::SCRIPT_GET) {
+ if (!str_script_ctx) {
+ cerr << "ERROR: context was not provided (via --context)" << std::endl;
+ return EINVAL;
+ }
+ 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;
+ return EINVAL;
+ }
+ std::string script;
+ const auto rc = rgw::lua::read_script(store, tenant, null_yield, script_ctx, script);
+ if (rc == -ENOENT) {
+ std::cout << "no script exists for context: " << *str_script_ctx <<
+ (tenant.empty() ? "" : (" in tenant: " + tenant)) << std::endl;
+ } else if (rc < 0) {
+ cerr << "ERROR: failed to read script. error: " << rc << std::endl;
+ return rc;
+ } else {
+ std::cout << script << std::endl;
+ }
+ }
+
+ if (opt_cmd == OPT::SCRIPT_RM) {
+ if (!str_script_ctx) {
+ cerr << "ERROR: context was not provided (via --context)" << std::endl;
+ return EINVAL;
+ }
+ 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;
+ return EINVAL;
+ }
+ const auto rc = rgw::lua::delete_script(store, tenant, null_yield, script_ctx);
+ if (rc < 0) {
+ cerr << "ERROR: failed to remove script. error: " << rc << std::endl;
+ return rc;
+ }
+ }
+
return 0;
}
+
#include "rgw_website.h"
#include "rgw_object_lock.h"
#include "rgw_tag.h"
+#include "rgw_op_type.h"
#include "rgw_sync_policy.h"
#include "cls/version/cls_version_types.h"
#include "cls/user/cls_user_types.h"
OP_UNKNOWN,
};
-enum RGWOpType {
- RGW_OP_UNKNOWN = 0,
- RGW_OP_GET_OBJ,
- RGW_OP_LIST_BUCKETS,
- RGW_OP_STAT_ACCOUNT,
- RGW_OP_LIST_BUCKET,
- RGW_OP_GET_BUCKET_LOGGING,
- RGW_OP_GET_BUCKET_LOCATION,
- RGW_OP_GET_BUCKET_VERSIONING,
- RGW_OP_SET_BUCKET_VERSIONING,
- RGW_OP_GET_BUCKET_WEBSITE,
- RGW_OP_SET_BUCKET_WEBSITE,
- RGW_OP_STAT_BUCKET,
- RGW_OP_CREATE_BUCKET,
- RGW_OP_DELETE_BUCKET,
- RGW_OP_PUT_OBJ,
- RGW_OP_STAT_OBJ,
- RGW_OP_POST_OBJ,
- RGW_OP_PUT_METADATA_ACCOUNT,
- RGW_OP_PUT_METADATA_BUCKET,
- RGW_OP_PUT_METADATA_OBJECT,
- RGW_OP_SET_TEMPURL,
- RGW_OP_DELETE_OBJ,
- RGW_OP_COPY_OBJ,
- RGW_OP_GET_ACLS,
- RGW_OP_PUT_ACLS,
- RGW_OP_GET_CORS,
- RGW_OP_PUT_CORS,
- RGW_OP_DELETE_CORS,
- RGW_OP_OPTIONS_CORS,
- RGW_OP_GET_REQUEST_PAYMENT,
- RGW_OP_SET_REQUEST_PAYMENT,
- RGW_OP_INIT_MULTIPART,
- RGW_OP_COMPLETE_MULTIPART,
- RGW_OP_ABORT_MULTIPART,
- RGW_OP_LIST_MULTIPART,
- RGW_OP_LIST_BUCKET_MULTIPARTS,
- RGW_OP_DELETE_MULTI_OBJ,
- RGW_OP_BULK_DELETE,
- RGW_OP_SET_ATTRS,
- RGW_OP_GET_CROSS_DOMAIN_POLICY,
- RGW_OP_GET_HEALTH_CHECK,
- RGW_OP_GET_INFO,
- RGW_OP_CREATE_ROLE,
- RGW_OP_DELETE_ROLE,
- RGW_OP_GET_ROLE,
- RGW_OP_MODIFY_ROLE,
- RGW_OP_LIST_ROLES,
- RGW_OP_PUT_ROLE_POLICY,
- RGW_OP_GET_ROLE_POLICY,
- RGW_OP_LIST_ROLE_POLICIES,
- RGW_OP_DELETE_ROLE_POLICY,
- RGW_OP_PUT_BUCKET_POLICY,
- RGW_OP_GET_BUCKET_POLICY,
- RGW_OP_DELETE_BUCKET_POLICY,
- RGW_OP_PUT_OBJ_TAGGING,
- RGW_OP_GET_OBJ_TAGGING,
- RGW_OP_DELETE_OBJ_TAGGING,
- RGW_OP_PUT_LC,
- RGW_OP_GET_LC,
- RGW_OP_DELETE_LC,
- RGW_OP_PUT_USER_POLICY,
- RGW_OP_GET_USER_POLICY,
- RGW_OP_LIST_USER_POLICIES,
- RGW_OP_DELETE_USER_POLICY,
- RGW_OP_PUT_BUCKET_OBJ_LOCK,
- RGW_OP_GET_BUCKET_OBJ_LOCK,
- RGW_OP_PUT_OBJ_RETENTION,
- RGW_OP_GET_OBJ_RETENTION,
- RGW_OP_PUT_OBJ_LEGAL_HOLD,
- RGW_OP_GET_OBJ_LEGAL_HOLD,
- /* rgw specific */
- RGW_OP_ADMIN_SET_METADATA,
- RGW_OP_GET_OBJ_LAYOUT,
- RGW_OP_BULK_UPLOAD,
- RGW_OP_METADATA_SEARCH,
- RGW_OP_CONFIG_BUCKET_META_SEARCH,
- RGW_OP_GET_BUCKET_META_SEARCH,
- RGW_OP_DEL_BUCKET_META_SEARCH,
- /* sts specific*/
- RGW_STS_ASSUME_ROLE,
- RGW_STS_GET_SESSION_TOKEN,
- RGW_STS_ASSUME_ROLE_WEB_IDENTITY,
- /* pubsub */
- RGW_OP_PUBSUB_TOPIC_CREATE,
- RGW_OP_PUBSUB_TOPICS_LIST,
- RGW_OP_PUBSUB_TOPIC_GET,
- RGW_OP_PUBSUB_TOPIC_DELETE,
- RGW_OP_PUBSUB_SUB_CREATE,
- RGW_OP_PUBSUB_SUB_GET,
- RGW_OP_PUBSUB_SUB_DELETE,
- RGW_OP_PUBSUB_SUB_PULL,
- RGW_OP_PUBSUB_SUB_ACK,
- RGW_OP_PUBSUB_NOTIF_CREATE,
- RGW_OP_PUBSUB_NOTIF_DELETE,
- RGW_OP_PUBSUB_NOTIF_LIST,
- RGW_OP_GET_BUCKET_TAGGING,
- RGW_OP_PUT_BUCKET_TAGGING,
- RGW_OP_DELETE_BUCKET_TAGGING,
- RGW_OP_GET_BUCKET_REPLICATION,
- RGW_OP_PUT_BUCKET_REPLICATION,
- RGW_OP_DELETE_BUCKET_REPLICATION,
-
- /* public access */
- RGW_OP_GET_BUCKET_POLICY_STATUS,
- RGW_OP_PUT_BUCKET_PUBLIC_ACCESS_BLOCK,
- RGW_OP_GET_BUCKET_PUBLIC_ACCESS_BLOCK,
- RGW_OP_DELETE_BUCKET_PUBLIC_ACCESS_BLOCK,
-
- /*OIDC provider specific*/
- RGW_OP_CREATE_OIDC_PROVIDER,
- RGW_OP_DELETE_OIDC_PROVIDER,
- RGW_OP_GET_OIDC_PROVIDER,
- RGW_OP_LIST_OIDC_PROVIDERS,
-};
-
class RGWAccessControlPolicy;
class JSONObj;
--- /dev/null
+#include <lua.hpp>
+#include "services/svc_zone.h"
+#include "services/svc_sys_obj.h"
+#include "common/dout.h"
+#include "rgw_lua_utils.h"
+#include "rgw_sal_rados.h"
+#include "rgw_lua.h"
+
+#define dout_subsys ceph_subsys_rgw
+
+namespace rgw::lua {
+
+context to_context(const std::string& s)
+{
+ if (strcasecmp(s.c_str(), "prerequest") == 0) {
+ return context::preRequest;
+ }
+ if (strcasecmp(s.c_str(), "postrequest") == 0) {
+ return context::postRequest;
+ }
+ return context::none;
+}
+
+std::string to_string(context ctx)
+{
+ switch (ctx) {
+ case context::preRequest:
+ return "prerequest";
+ case context::postRequest:
+ return "postrequest";
+ case context::none:
+ break;
+ }
+ return "none";
+}
+
+bool verify(const std::string& script, std::string& err_msg)
+{
+ lua_State *L = luaL_newstate();
+ lua_state_guard guard(L);
+ luaL_openlibs(L);
+ try {
+ if (luaL_loadstring(L, script.c_str()) != LUA_OK) {
+ err_msg.assign(lua_tostring(L, -1));
+ return false;
+ }
+ } catch (const std::runtime_error& e) {
+ err_msg = e.what();
+ return false;
+ }
+ err_msg = "";
+ return true;
+}
+
+static const std::string SCRIPT_OID_PREFIX("script.");
+
+int read_script(rgw::sal::RGWRadosStore* store, const std::string& tenant, optional_yield y, context ctx, std::string& script)
+{
+ RGWSysObjectCtx obj_ctx(store->svc()->sysobj->init_obj_ctx());
+ RGWObjVersionTracker objv_tracker;
+
+ const auto script_oid = SCRIPT_OID_PREFIX + to_string(ctx) + tenant;
+ rgw_raw_obj obj(store->svc()->zone->get_zone_params().log_pool, script_oid);
+
+ bufferlist bl;
+
+ const auto rc = rgw_get_system_obj(
+ obj_ctx,
+ obj.pool,
+ obj.oid,
+ bl,
+ &objv_tracker,
+ nullptr,
+ y,
+ nullptr,
+ nullptr);
+
+ if (rc < 0) {
+ return rc;
+ }
+
+ auto iter = bl.cbegin();
+ try {
+ ceph::decode(script, iter);
+ } catch (buffer::error& err) {
+ return -EIO;
+ }
+
+ return 0;
+}
+
+int write_script(rgw::sal::RGWRadosStore* store, const std::string& tenant, optional_yield y, context ctx, const std::string& script)
+{
+ RGWSysObjectCtx obj_ctx(store->svc()->sysobj->init_obj_ctx());
+ RGWObjVersionTracker objv_tracker;
+
+ const auto script_oid = SCRIPT_OID_PREFIX + to_string(ctx) + tenant;
+ rgw_raw_obj obj(store->svc()->zone->get_zone_params().log_pool, script_oid);
+
+ bufferlist bl;
+ ceph::encode(script, bl);
+
+ const auto rc = rgw_put_system_obj(
+ obj_ctx,
+ obj.pool,
+ obj.oid,
+ bl,
+ false,
+ &objv_tracker,
+ real_time());
+
+ if (rc < 0) {
+ return rc;
+ }
+
+ return 0;
+}
+
+int delete_script(rgw::sal::RGWRadosStore* store, const std::string& tenant, optional_yield y, context ctx)
+{
+ RGWObjVersionTracker objv_tracker;
+
+ const auto script_oid = SCRIPT_OID_PREFIX + to_string(ctx) + tenant;
+ rgw_raw_obj obj(store->svc()->zone->get_zone_params().log_pool, script_oid);
+
+ const auto rc = rgw_delete_system_obj(
+ store->svc()->sysobj,
+ obj.pool,
+ obj.oid,
+ &objv_tracker);
+
+ if (rc < 0 && rc != -ENOENT) {
+ return rc;
+ }
+
+ return 0;
+}
+}
+
--- /dev/null
+#pragma once
+
+#include <string>
+#include "common/async/yield_context.h"
+
+class lua_State;
+class rgw_user;
+namespace rgw::sal {
+ class RGWRadosStore;
+}
+
+namespace rgw::lua {
+
+enum class context {
+ preRequest,
+ postRequest,
+ none
+};
+
+// get context enum from string
+// the expected string the same as the enum (case insensitive)
+// return "none" if not matched
+context to_context(const std::string& s);
+
+// verify a lua script
+bool verify(const std::string& script, std::string& err_msg);
+
+// store a lua script in a context
+int write_script(rgw::sal::RGWRadosStore* store, const std::string& tenant, optional_yield y, context ctx, const std::string& script);
+
+// read the stored lua script from a context
+int read_script(rgw::sal::RGWRadosStore* store, const std::string& tenant, optional_yield y, context ctx, std::string& script);
+
+// delete the stored lua script from a context
+int delete_script(rgw::sal::RGWRadosStore* store, const std::string& tenant, optional_yield y, context ctx);
+
+}
+
--- /dev/null
+#include <sstream>
+#include <stdexcept>
+#include <lua.hpp>
+#include "common/dout.h"
+#include "services/svc_zone.h"
+#include "rgw_lua_utils.h"
+#include "rgw_common.h"
+#include "rgw_log.h"
+#include "rgw_process.h"
+#include "rgw_zone.h"
+#include "rgw_acl.h"
+#include "rgw_sal_rados.h"
+
+#define dout_subsys ceph_subsys_rgw
+
+namespace rgw::lua::request {
+
+// closure that perform ops log action
+// e.g.
+// Request.Log()
+//
+constexpr const char* RequestLogAction{"Log"};
+
+int RequestLog(lua_State* L)
+{
+ const auto store = reinterpret_cast<rgw::sal::RGWRadosStore*>(lua_touserdata(L, lua_upvalueindex(1)));
+ const auto rest = reinterpret_cast<RGWREST*>(lua_touserdata(L, lua_upvalueindex(2)));
+ const auto olog = reinterpret_cast<OpsLogSocket*>(lua_touserdata(L, lua_upvalueindex(3)));
+ const auto s = reinterpret_cast<req_state*>(lua_touserdata(L, lua_upvalueindex(4)));
+ const std::string op_name(reinterpret_cast<const char*>(lua_touserdata(L, lua_upvalueindex(5))));
+ if (store && s) {
+ const auto rc = rgw_log_op(store->getRados(), rest, s, op_name, olog);
+ lua_pushinteger(L, rc);
+ } else {
+ ldout(s->cct, 1) << "Lua ERROR: missing rados store, cannot use ops log" << dendl;
+ lua_pushinteger(L, -EINVAL);
+ }
+
+ return ONE_RETURNVAL;
+}
+
+struct ResponseMetaTable : public EmptyMetaTable {
+ static std::string TableName() {return "Response";}
+ static std::string Name() {return TableName() + "Meta";}
+
+ static int IndexClosure(lua_State* L) {
+ const auto err = reinterpret_cast<const rgw_err*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+ ceph_assert(lua_isstring(L, -1));
+ const char* index = lua_tostring(L, -1);
+
+ if (strcasecmp(index, "HTTPStatusCode") == 0) {
+ lua_pushinteger(L, err->http_ret);
+ } else if (strcasecmp(index, "RGWCode") == 0) {
+ lua_pushinteger(L, err->ret);
+ } else if (strcasecmp(index, "HTTPStatus") == 0) {
+ lua_pushstring(L, err->err_code.c_str());
+ } else if (strcasecmp(index, "Message") == 0) {
+ lua_pushstring(L, err->message.c_str());
+ } else {
+ throw_unknown_field(index, TableName());
+ }
+ return ONE_RETURNVAL;
+ }
+
+ static int NewIndexClosure(lua_State* L) {
+ auto err = reinterpret_cast<rgw_err*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+ ceph_assert(lua_isstring(L, -2));
+ const char* index = lua_tostring(L, -2);
+
+ if (strcasecmp(index, "HTTPStatusCode") == 0) {
+ err->http_ret = lua_tointeger(L, -1);
+ } else if (strcasecmp(index, "RGWCode") == 0) {
+ err->ret = lua_tointeger(L, -1);
+ } else if (strcasecmp(index, "HTTPStatus") == 0) {
+ err->err_code.assign(lua_tostring(L, -1));
+ } else if (strcasecmp(index, "Message") == 0) {
+ err->message.assign(lua_tostring(L, -1));
+ } else {
+ throw_unknown_field(index, TableName());
+ }
+ return ONE_RETURNVAL;
+ }
+};
+
+struct QuotaMetaTable : public EmptyMetaTable {
+ static std::string TableName() {return "Quota";}
+ static std::string Name() {return TableName() + "Meta";}
+
+ static int IndexClosure(lua_State* L) {
+ const auto info = reinterpret_cast<RGWQuotaInfo*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+ ceph_assert(lua_isstring(L, -1));
+ const char* index = lua_tostring(L, -1);
+
+ if (strcasecmp(index, "MaxSize") == 0) {
+ lua_pushinteger(L, info->max_size);
+ } else if (strcasecmp(index, "MaxObjects") == 0) {
+ lua_pushinteger(L, info->max_objects);
+ } else if (strcasecmp(index, "Enabled") == 0) {
+ lua_pushboolean(L, info->enabled);
+ } else if (strcasecmp(index, "Rounded") == 0) {
+ lua_pushboolean(L, !info->check_on_raw);
+ } else {
+ throw_unknown_field(index, TableName());
+ }
+ return ONE_RETURNVAL;
+ }
+};
+
+struct PlacementRuleMetaTable : public EmptyMetaTable {
+ static std::string TableName() {return "PlacementRule";}
+ static std::string Name() {return TableName() + "Meta";}
+
+ static int IndexClosure(lua_State* L) {
+ const auto rule = reinterpret_cast<rgw_placement_rule*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+ ceph_assert(lua_isstring(L, -1));
+ const char* index = lua_tostring(L, -1);
+
+ if (strcasecmp(index, "Name") == 0) {
+ lua_pushstring(L, rule->name.c_str());
+ } else if (strcasecmp(index, "StorageClass") == 0) {
+ lua_pushstring(L, rule->storage_class.c_str());
+ } else {
+ throw_unknown_field(index, TableName());
+ }
+ return ONE_RETURNVAL;
+ }
+};
+
+struct UserMetaTable : public EmptyMetaTable {
+ static std::string TableName() {return "User";}
+ static std::string Name() {return TableName() + "Meta";}
+
+ static int IndexClosure(lua_State* L) {
+ const auto user = reinterpret_cast<const rgw_user*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+ ceph_assert(lua_isstring(L, -1));
+ const char* index = lua_tostring(L, -1);
+
+ if (strcasecmp(index, "Tenant") == 0) {
+ lua_pushstring(L, user->tenant);
+ } else if (strcasecmp(index, "Id") == 0) {
+ lua_pushstring(L, user->id);
+ } else {
+ throw_unknown_field(index, TableName());
+ }
+ return ONE_RETURNVAL;
+ }
+};
+
+struct OwnerMetaTable : public EmptyMetaTable {
+ static std::string TableName() {return "Owner";}
+ static std::string Name() {return TableName() + "Meta";}
+
+ static int IndexClosure(lua_State* L) {
+ const auto owner = reinterpret_cast<ACLOwner*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+ ceph_assert(lua_isstring(L, -1));
+ const char* index = lua_tostring(L, -1);
+
+ if (strcasecmp(index, "DisplayName") == 0) {
+ lua_pushstring(L, owner->get_display_name());
+ } else if (strcasecmp(index, "User") == 0) {
+ create_metatable<UserMetaTable>(L, false, &(owner->get_id()));
+ } else {
+ throw_unknown_field(index, TableName());
+ }
+ return ONE_RETURNVAL;
+ }
+};
+
+struct BucketMetaTable : public EmptyMetaTable {
+ static std::string TableName() {return "Bucket";}
+ static std::string Name() {return TableName() + "Meta";}
+
+ using Type = rgw::sal::RGWBucket;
+
+ static int IndexClosure(lua_State* L) {
+ const auto bucket = reinterpret_cast<Type*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+ ceph_assert(lua_isstring(L, -1));
+ const char* index = lua_tostring(L, -1);
+
+ if (strcasecmp(index, "Tenant") == 0) {
+ lua_pushstring(L, bucket->get_tenant());
+ } else if (strcasecmp(index, "Name") == 0) {
+ lua_pushstring(L, bucket->get_name());
+ } else if (strcasecmp(index, "Marker") == 0) {
+ lua_pushstring(L, bucket->get_marker());
+ } else if (strcasecmp(index, "Id") == 0) {
+ lua_pushstring(L, bucket->get_bucket_id());
+ } else if (strcasecmp(index, "Count") == 0) {
+ lua_pushinteger(L, bucket->get_count());
+ } else if (strcasecmp(index, "Size") == 0) {
+ lua_pushinteger(L, bucket->get_size());
+ } else if (strcasecmp(index, "ZoneGroupId") == 0) {
+ lua_pushstring(L, bucket->get_info().zonegroup);
+ } else if (strcasecmp(index, "CreationTime") == 0) {
+ lua_pushtime(L, bucket->get_creation_time());
+ } else if (strcasecmp(index, "MTime") == 0) {
+ lua_pushtime(L, bucket->get_modification_time());
+ } else if (strcasecmp(index, "Quota") == 0) {
+ create_metatable<QuotaMetaTable>(L, false, &(bucket->get_info().quota));
+ } else if (strcasecmp(index, "PlacementRule") == 0) {
+ create_metatable<PlacementRuleMetaTable>(L, false, &(bucket->get_info().placement_rule));
+ } else if (strcasecmp(index, "User") == 0) {
+ create_metatable<UserMetaTable>(L, false, &(bucket->get_info().owner));
+ } else {
+ throw_unknown_field(index, TableName());
+ }
+ return ONE_RETURNVAL;
+ }
+};
+
+struct ObjectMetaTable : public EmptyMetaTable {
+ static const std::string TableName() {return "Object";}
+ static std::string Name() {return TableName() + "Meta";}
+
+ using Type = rgw::sal::RGWObject;
+
+ static int IndexClosure(lua_State* L) {
+ const auto obj = reinterpret_cast<const Type*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+ ceph_assert(lua_isstring(L, -1));
+ const char* index = lua_tostring(L, -1);
+
+ if (strcasecmp(index, "Name") == 0) {
+ lua_pushstring(L, obj->get_name().c_str());
+ } else if (strcasecmp(index, "Instance") == 0) {
+ lua_pushstring(L, obj->get_instance().c_str());
+ } else if (strcasecmp(index, "Id") == 0) {
+ lua_pushstring(L, obj->get_oid());
+ } else if (strcasecmp(index, "Size") == 0) {
+ lua_pushinteger(L, obj->get_obj_size());
+ } else if (strcasecmp(index, "MTime") == 0) {
+ lua_pushtime(L, obj->get_mtime());
+ } else {
+ throw_unknown_field(index, TableName());
+ }
+ return ONE_RETURNVAL;
+ }
+};
+
+template<typename MapType=std::map<std::string, std::string>>
+struct StringMapMetaTable : public EmptyMetaTable {
+
+ static std::string TableName() {return "StringMap";}
+ static std::string Name() {return TableName() + "Meta";}
+
+ static int IndexClosure(lua_State* L) {
+ const auto map = reinterpret_cast<MapType*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+ ceph_assert(lua_isstring(L, -1));
+ const char* index = lua_tostring(L, -1);
+
+ const auto it = map->find(std::string(index));
+ if (it == map->end()) {
+ lua_pushnil(L);
+ } else {
+ lua_pushstring(L, it->second);
+ }
+ return ONE_RETURNVAL;
+ }
+
+ static int PairsClosure(lua_State* L) {
+ auto map = reinterpret_cast<MapType*>(lua_touserdata(L, lua_upvalueindex(1)));
+ ceph_assert(map);
+ lua_pushlightuserdata(L, map);
+ lua_pushcclosure(L, stateless_iter, ONE_UPVAL); // push the stateless iterator function
+ lua_pushnil(L); // indicate this is the first call
+ // return stateless_iter, nil
+
+ return TWO_RETURNVALS;
+ }
+
+ static int stateless_iter(lua_State* L) {
+ // based on: http://lua-users.org/wiki/GeneralizedPairsAndIpairs
+ auto map = reinterpret_cast<MapType*>(lua_touserdata(L, lua_upvalueindex(1)));
+ typename MapType::const_iterator next_it;
+ if (lua_isnil(L, -1)) {
+ next_it = map->begin();
+ } else {
+ ceph_assert(lua_isstring(L, -1));
+ const char* index = lua_tostring(L, -1);
+ const auto it = map->find(std::string(index));
+ ceph_assert(it != map->end());
+ next_it = std::next(it);
+ }
+
+ if (next_it == map->end()) {
+ // index of the last element was provided
+ lua_pushnil(L);
+ lua_pushnil(L);
+ // return nil, nil
+ } else {
+ lua_pushstring(L, next_it->first);
+ lua_pushstring(L, next_it->second);
+ // return key, value
+ }
+
+ return TWO_RETURNVALS;
+ }
+
+ static int LenClosure(lua_State* L) {
+ const auto map = reinterpret_cast<MapType*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+ lua_pushinteger(L, map->size());
+
+ return ONE_RETURNVAL;
+ }
+};
+
+struct GrantMetaTable : public EmptyMetaTable {
+ static std::string TableName() {return "Grant";}
+ static std::string Name() {return TableName() + "Meta";}
+
+ static int IndexClosure(lua_State* L) {
+ const auto grant = reinterpret_cast<ACLGrant*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+ ceph_assert(lua_isstring(L, -1));
+ const char* index = lua_tostring(L, -1);
+
+ if (strcasecmp(index, "Type") == 0) {
+ lua_pushinteger(L, grant->get_type().get_type());
+ } else if (strcasecmp(index, "User") == 0) {
+ rgw_user id;
+ if (grant->get_id(id)) {
+ create_metatable<UserMetaTable>(L, false, &id);
+ } else {
+ lua_pushnil(L);
+ }
+ } else if (strcasecmp(index, "Permission") == 0) {
+ lua_pushinteger(L, grant->get_permission().get_permissions());
+ } else if (strcasecmp(index, "GroupType") == 0) {
+ lua_pushinteger(L, grant->get_group());
+ } else if (strcasecmp(index, "Referer") == 0) {
+ lua_pushstring(L, grant->get_referer());
+ } else {
+ throw_unknown_field(index, TableName());
+ }
+ return ONE_RETURNVAL;
+ }
+};
+
+struct GrantsMetaTable : public EmptyMetaTable {
+ static std::string TableName() {return "Grants";}
+ static std::string Name() {return TableName() + "Meta";}
+
+ static int IndexClosure(lua_State* L) {
+ const auto map = reinterpret_cast<ACLGrantMap*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+ ceph_assert(lua_isstring(L, -1));
+ const char* index = lua_tostring(L, -1);
+
+ const auto it = map->find(std::string(index));
+ if (it == map->end()) {
+ lua_pushnil(L);
+ } else {
+ create_metatable<GrantMetaTable>(L, false, &(it->second));
+ }
+ return ONE_RETURNVAL;
+ }
+
+ static int PairsClosure(lua_State* L) {
+ auto map = reinterpret_cast<ACLGrantMap*>(lua_touserdata(L, lua_upvalueindex(1)));
+ ceph_assert(map);
+ lua_pushlightuserdata(L, map);
+ lua_pushcclosure(L, stateless_iter, ONE_UPVAL); // push the stateless iterator function
+ lua_pushnil(L); // indicate this is the first call
+ // return stateless_iter, nil
+
+ return TWO_RETURNVALS;
+ }
+
+ static int stateless_iter(lua_State* L) {
+ // based on: http://lua-users.org/wiki/GeneralizedPairsAndIpairs
+ auto map = reinterpret_cast<ACLGrantMap*>(lua_touserdata(L, lua_upvalueindex(1)));
+ ACLGrantMap::iterator next_it;
+ if (lua_isnil(L, -1)) {
+ next_it = map->begin();
+ } else {
+ ceph_assert(lua_isstring(L, -1));
+ const char* index = lua_tostring(L, -1);
+ const auto it = map->find(std::string(index));
+ ceph_assert(it != map->end());
+ next_it = std::next(it);
+ }
+
+ if (next_it == map->end()) {
+ // index of the last element was provided
+ lua_pushnil(L);
+ lua_pushnil(L);
+ return TWO_RETURNVALS;
+ // return nil, nil
+ }
+
+ while (next_it->first.empty()) {
+ // this is a multimap and the next element does not have a unique key
+ ++next_it;
+ if (next_it == map->end()) {
+ // index of the last element was provided
+ lua_pushnil(L);
+ lua_pushnil(L);
+ return TWO_RETURNVALS;
+ // return nil, nil
+ }
+ }
+
+ lua_pushstring(L, next_it->first);
+ create_metatable<GrantMetaTable>(L, false, &(next_it->second));
+ // return key, value
+
+ return TWO_RETURNVALS;
+ }
+
+ static int LenClosure(lua_State* L) {
+ const auto map = reinterpret_cast<ACLGrantMap*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+ lua_pushinteger(L, map->size());
+
+ return ONE_RETURNVAL;
+ }
+};
+
+struct ACLMetaTable : public EmptyMetaTable {
+ static std::string TableName() {return "ACL";}
+ static std::string Name() {return TableName() + "Meta";}
+
+ using Type = RGWAccessControlPolicy;
+
+ static int IndexClosure(lua_State* L) {
+ const auto acl = reinterpret_cast<Type*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+ ceph_assert(lua_isstring(L, -1));
+ const char* index = lua_tostring(L, -1);
+
+ if (strcasecmp(index, "Owner") == 0) {
+ create_metatable<OwnerMetaTable>(L, false, &(acl->get_owner()));
+ } else if (strcasecmp(index, "Grants") == 0) {
+ create_metatable<GrantsMetaTable>(L, false, &(acl->get_acl().get_grant_map()));
+ } else {
+ throw_unknown_field(index, TableName());
+ }
+ return ONE_RETURNVAL;
+ }
+};
+
+struct StatementsMetaTable : public EmptyMetaTable {
+ static std::string TableName() {return "Statements";}
+ static std::string Name() {return TableName() + "Meta";}
+
+ using Type = std::vector<rgw::IAM::Statement>;
+
+ static std::string statement_to_string(const rgw::IAM::Statement& statement) {
+ std::stringstream ss;
+ ss << statement;
+ return ss.str();
+ }
+
+ static int IndexClosure(lua_State* L) {
+ const auto statements = reinterpret_cast<Type*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+ const auto index = lua_tointeger(L, -1);
+
+ if (index >= (int)statements->size() || index < 0) {
+ lua_pushnil(L);
+ } else {
+ // TODO: policy language could be interpreted to lua and executed as such
+ lua_pushstring(L, statement_to_string((*statements)[index]));
+ }
+ return ONE_RETURNVAL;
+ }
+
+ static int PairsClosure(lua_State* L) {
+ auto statements = reinterpret_cast<Type*>(lua_touserdata(L, lua_upvalueindex(1)));
+ ceph_assert(statements);
+ lua_pushlightuserdata(L, statements);
+ lua_pushcclosure(L, stateless_iter, ONE_UPVAL); // push the stateless iterator function
+ lua_pushnil(L); // indicate this is the first call
+ // return stateless_iter, nil
+
+ return TWO_RETURNVALS;
+ }
+
+ static int stateless_iter(lua_State* L) {
+ auto statements = reinterpret_cast<Type*>(lua_touserdata(L, lua_upvalueindex(1)));
+ size_t next_it;
+ if (lua_isnil(L, -1)) {
+ next_it = 0;
+ } else {
+ ceph_assert(lua_isinteger(L, -1));
+ const auto it = lua_tointeger(L, -1);
+ next_it = it+1;
+ }
+
+ if (next_it >= statements->size()) {
+ // index of the last element was provided
+ lua_pushnil(L);
+ lua_pushnil(L);
+ // return nil, nil
+ } else {
+ lua_pushinteger(L, next_it);
+ lua_pushstring(L, statement_to_string((*statements)[next_it]));
+ // return key, value
+ }
+
+ return TWO_RETURNVALS;
+ }
+
+ static int LenClosure(lua_State* L) {
+ const auto statements = reinterpret_cast<Type*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+ lua_pushinteger(L, statements->size());
+
+ return ONE_RETURNVAL;
+ }
+};
+
+struct PolicyMetaTable : public EmptyMetaTable {
+ static std::string TableName() {return "Policy";}
+ static std::string Name() {return TableName() + "Meta";}
+
+ static int IndexClosure(lua_State* L) {
+ const auto policy = reinterpret_cast<rgw::IAM::Policy*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+ ceph_assert(lua_isstring(L, -1));
+ const char* index = lua_tostring(L, -1);
+
+ if (strcasecmp(index, "Text") == 0) {
+ lua_pushstring(L, policy->text);
+ } else if (strcasecmp(index, "Id") == 0) {
+ // TODO create lua_pushstring for std::unique_ptr
+ if (!policy->id) {
+ lua_pushnil(L);
+ } else {
+ lua_pushstring(L, policy->id.get());
+ }
+ } else if (strcasecmp(index, "Statements") == 0) {
+ create_metatable<StatementsMetaTable>(L, &(policy->statements));
+ } else {
+ throw_unknown_field(index, TableName());
+ }
+ return ONE_RETURNVAL;
+ }
+};
+
+struct PoliciesMetaTable : public EmptyMetaTable {
+ static std::string TableName() {return "Policies";}
+ static std::string Name() {return TableName() + "Meta";}
+
+ using Type = std::vector<rgw::IAM::Policy>;
+
+ static int IndexClosure(lua_State* L) {
+ const auto policies = reinterpret_cast<Type*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+ const auto index = lua_tointeger(L, -1);
+
+ if (index >= (int)policies->size() || index < 0) {
+ lua_pushnil(L);
+ } else {
+ create_metatable<PolicyMetaTable>(L, false, &((*policies)[index]));
+ }
+ return ONE_RETURNVAL;
+ }
+
+ static int PairsClosure(lua_State* L) {
+ auto policies = reinterpret_cast<Type*>(lua_touserdata(L, lua_upvalueindex(1)));
+ ceph_assert(policies);
+ lua_pushlightuserdata(L, policies);
+ lua_pushcclosure(L, stateless_iter, ONE_UPVAL); // push the stateless iterator function
+ lua_pushnil(L); // indicate this is the first call
+ // return stateless_iter, nil
+
+ return TWO_RETURNVALS;
+ }
+
+ static int stateless_iter(lua_State* L) {
+ auto policies = reinterpret_cast<Type*>(lua_touserdata(L, lua_upvalueindex(1)));
+ size_t next_it;
+ if (lua_isnil(L, -1)) {
+ next_it = 0;
+ } else {
+ ceph_assert(lua_isinteger(L, -1));
+ const auto it = lua_tointeger(L, -1);
+ next_it = it+1;
+ }
+
+ if (next_it >= policies->size()) {
+ // index of the last element was provided
+ lua_pushnil(L);
+ lua_pushnil(L);
+ // return nil, nil
+ } else {
+ lua_pushinteger(L, next_it);
+ create_metatable<PolicyMetaTable>(L, false, &((*policies)[next_it]));
+ // return key, value
+ }
+
+ return TWO_RETURNVALS;
+ }
+
+ static int LenClosure(lua_State* L) {
+ const auto policies = reinterpret_cast<Type*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+ lua_pushinteger(L, policies->size());
+
+ return ONE_RETURNVAL;
+ }
+};
+
+struct HTTPMetaTable : public EmptyMetaTable {
+ static std::string TableName() {return "HTTP";}
+ static std::string Name() {return TableName() + "Meta";}
+
+ static int IndexClosure(lua_State* L) {
+ const auto info = reinterpret_cast<req_info*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+ ceph_assert(lua_isstring(L, -1));
+ const char* index = lua_tostring(L, -1);
+
+ if (strcasecmp(index, "Parameters") == 0) {
+ create_metatable<StringMapMetaTable<>>(L, false, &(info->args.get_params()));
+ } else if (strcasecmp(index, "Resources") == 0) {
+ // TODO: add non-const api to get resources
+ create_metatable<StringMapMetaTable<>>(L, false,
+ const_cast<std::map<std::string, std::string>*>(&(info->args.get_sub_resources())));
+ } else if (strcasecmp(index, "Metadata") == 0) {
+ create_metatable<StringMapMetaTable<meta_map_t>>(L, false, &(info->x_meta_map));
+ } else if (strcasecmp(index, "Host") == 0) {
+ lua_pushstring(L, info->host);
+ } else if (strcasecmp(index, "Method") == 0) {
+ lua_pushstring(L, info->method);
+ } else if (strcasecmp(index, "URI") == 0) {
+ lua_pushstring(L, info->request_uri);
+ } else if (strcasecmp(index, "QueryString") == 0) {
+ lua_pushstring(L, info->request_params);
+ } else if (strcasecmp(index, "Domain") == 0) {
+ lua_pushstring(L, info->domain);
+ } else {
+ throw_unknown_field(index, TableName());
+ }
+ return ONE_RETURNVAL;
+ }
+};
+
+struct CopyFromMetaTable : public EmptyMetaTable {
+ static std::string TableName() {return "CopyFrom";}
+ static std::string Name() {return TableName() + "Meta";}
+
+ static int IndexClosure(lua_State* L) {
+ const auto s = reinterpret_cast<req_state*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+ ceph_assert(lua_isstring(L, -1));
+ const char* index = lua_tostring(L, -1);
+
+ if (strcasecmp(index, "Tenant") == 0) {
+ lua_pushstring(L, s->src_tenant_name);
+ } else if (strcasecmp(index, "Bucket") == 0) {
+ lua_pushstring(L, s->src_bucket_name);
+ } else if (strcasecmp(index, "Object") == 0) {
+ create_metatable<ObjectMetaTable>(L, false, s->src_object);
+ } else {
+ throw_unknown_field(index, TableName());
+ }
+ return ONE_RETURNVAL;
+ }
+};
+
+struct ZoneGroupMetaTable : public EmptyMetaTable {
+ static std::string TableName() {return "ZoneGroup";}
+ static std::string Name() {return TableName() + "Meta";}
+
+ static int IndexClosure(lua_State* L) {
+ const auto s = reinterpret_cast<req_state*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+ ceph_assert(lua_isstring(L, -1));
+ const char* index = lua_tostring(L, -1);
+
+ if (strcasecmp(index, "Name") == 0) {
+ lua_pushstring(L, s->zonegroup_name);
+ } else if (strcasecmp(index, "Endpoint") == 0) {
+ lua_pushstring(L, s->zonegroup_endpoint);
+ } else {
+ throw_unknown_field(index, TableName());
+ }
+ return ONE_RETURNVAL;
+ }
+};
+
+struct RequestMetaTable : public EmptyMetaTable {
+ static std::string TableName() {return "Request";}
+ static std::string Name() {return TableName() + "Meta";}
+
+ // __index closure that expect req_state to be captured
+ static int IndexClosure(lua_State* L) {
+ const auto s = reinterpret_cast<req_state*>(lua_touserdata(L, lua_upvalueindex(1)));
+ const auto op_name = reinterpret_cast<const char*>(lua_touserdata(L, lua_upvalueindex(2)));
+
+ ceph_assert(lua_isstring(L, -1));
+ const char* index = lua_tostring(L, -1);
+
+ if (strcasecmp(index, "RGWOp") == 0) {
+ lua_pushstring(L, op_name);
+ } else if (strcasecmp(index, "DecodedURI") == 0) {
+ lua_pushstring(L, s->decoded_uri);
+ } else if (strcasecmp(index, "ContentLength") == 0) {
+ lua_pushinteger(L, s->content_length);
+ } else if (strcasecmp(index, "GenericAttributes") == 0) {
+ create_metatable<StringMapMetaTable<>>(L, false, &(s->generic_attrs));
+ } else if (strcasecmp(index, "Response") == 0) {
+ create_metatable<ResponseMetaTable>(L, false, &(s->err));
+ } else if (strcasecmp(index, "SwiftAccountName") == 0) {
+ if (s->dialect == "swift") {
+ lua_pushstring(L, s->account_name);
+ } else {
+ lua_pushnil(L);
+ }
+ } else if (strcasecmp(index, "Bucket") == 0) {
+ create_metatable<BucketMetaTable>(L, false, s->bucket);
+ } else if (strcasecmp(index, "Object") == 0) {
+ create_metatable<ObjectMetaTable>(L, false, s->object);
+ } else if (strcasecmp(index, "CopyFrom") == 0) {
+ if (s->op_type == RGW_OP_COPY_OBJ) {
+ create_metatable<CopyFromMetaTable>(L, s);
+ } else {
+ lua_pushnil(L);
+ }
+ } else if (strcasecmp(index, "ObjectOwner") == 0) {
+ create_metatable<OwnerMetaTable>(L, false, &(s->owner));
+ } else if (strcasecmp(index, "ZoneGroup") == 0) {
+ create_metatable<ZoneGroupMetaTable>(L, false, s);
+ } else if (strcasecmp(index, "UserACL") == 0) {
+ create_metatable<ACLMetaTable>(L, false, s->user_acl);
+ } else if (strcasecmp(index, "BucketACL") == 0) {
+ create_metatable<ACLMetaTable>(L, false, s->bucket_acl);
+ } else if (strcasecmp(index, "ObjectACL") == 0) {
+ create_metatable<ACLMetaTable>(L, false, s->object_acl);
+ } else if (strcasecmp(index, "Environment") == 0) {
+ create_metatable<StringMapMetaTable<rgw::IAM::Environment>>(L, false, &(s->env));
+ } else if (strcasecmp(index, "Policy") == 0) {
+ // TODO: create a wrapper to std::optional
+ if (!s->iam_policy) {
+ lua_pushnil(L);
+ } else {
+ create_metatable<PolicyMetaTable>(L, false, s->iam_policy.get_ptr());
+ }
+ } else if (strcasecmp(index, "UserPolicies") == 0) {
+ create_metatable<PoliciesMetaTable>(L, false, &(s->iam_user_policies));
+ } else if (strcasecmp(index, "RGWId") == 0) {
+ lua_pushstring(L, s->host_id);
+ } else if (strcasecmp(index, "HTTP") == 0) {
+ create_metatable<HTTPMetaTable>(L, false, &(s->info));
+ } else if (strcasecmp(index, "Time") == 0) {
+ lua_pushtime(L, s->time);
+ } else if (strcasecmp(index, "Dialect") == 0) {
+ lua_pushstring(L, s->dialect);
+ } else if (strcasecmp(index, "Id") == 0) {
+ lua_pushstring(L, s->req_id);
+ } else if (strcasecmp(index, "TransactionId") == 0) {
+ lua_pushstring(L, s->trans_id);
+ } else if (strcasecmp(index, "Tags") == 0) {
+ create_metatable<StringMapMetaTable<RGWObjTags::tag_map_t>>(L, false, &(s->tagset.get_tags()));
+ } else {
+ throw_unknown_field(index, TableName());
+ }
+ return ONE_RETURNVAL;
+ }
+};
+
+int execute(
+ rgw::sal::RGWRadosStore* store,
+ RGWREST* rest,
+ OpsLogSocket* olog,
+ req_state* s,
+ const char* op_name,
+ const std::string& script)
+{
+ auto L = luaL_newstate();
+ lua_state_guard lguard(L);
+
+ luaL_openlibs(L);
+
+ create_debug_action(L, s->cct);
+
+ create_metatable<RequestMetaTable>(L, true, s, const_cast<char*>(op_name));
+
+ // add the ops log action
+ lua_getglobal(L, RequestMetaTable::TableName().c_str());
+ ceph_assert(lua_istable(L, -1));
+ lua_pushstring(L, RequestLogAction);
+ lua_pushlightuserdata(L, store);
+ lua_pushlightuserdata(L, rest);
+ lua_pushlightuserdata(L, olog);
+ lua_pushlightuserdata(L, s);
+ lua_pushlightuserdata(L, const_cast<char*>(op_name));
+ lua_pushcclosure(L, RequestLog, FIVE_UPVALS);
+ lua_rawset(L, -3);
+
+ try {
+ // execute the lua script
+ if (luaL_dostring(L, script.c_str()) != LUA_OK) {
+ const std::string err(lua_tostring(L, -1));
+ ldout(s->cct, 1) << "Lua ERROR: " << err << dendl;
+ return -1;
+ }
+ } catch (const std::runtime_error& e) {
+ ldout(s->cct, 1) << "Lua ERROR: " << e.what() << dendl;
+ return -1;
+ }
+
+ return 0;
+}
+
+}
--- /dev/null
+#pragma once
+
+#include <string>
+#include "include/common_fwd.h"
+
+class req_state;
+class RGWREST;
+class OpsLogSocket;
+namespace rgw::sal {
+ class RGWRadosStore;
+}
+
+namespace rgw::lua::request {
+
+// execute a lua script in the Request context
+int execute(
+ rgw::sal::RGWRadosStore* store,
+ RGWREST* rest,
+ OpsLogSocket* olog,
+ req_state *s,
+ const char* op_name,
+ const std::string& script);
+
+}
+
--- /dev/null
+#include <string>
+#include <lua.hpp>
+#include "common/ceph_context.h"
+#include "common/dout.h"
+#include "rgw_lua_utils.h"
+
+#define dout_subsys ceph_subsys_rgw
+
+namespace rgw::lua {
+
+void lua_pushstring(lua_State* L, const std::string& str)
+{
+ lua_pushstring(L, str.c_str());
+}
+
+// TODO - add the folowing generic functions
+// lua_push(lua_State* L, const std::string& str)
+// template<typename T> lua_push(lua_State* L, const std::optional<T>& val)
+// lua_push(lua_State* L, const ceph::real_time& tp)
+
+constexpr const char* RGWDebugLogAction{"RGWDebugLog"};
+
+int RGWDebugLog(lua_State* L)
+{
+ auto cct = reinterpret_cast<CephContext*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+ constexpr auto NUM_RETURN = 0;
+
+ if (!lua_isstring(L, -1)) {
+ if (cct) {
+ ldout(cct, 1) << "Lua ERROR: missing/invalid 'message' parameter when calling Log" << dendl;
+ }
+ return NUM_RETURN;
+ }
+ const char* message = lua_tostring(L, -1);
+ if (cct) {
+ ldout(cct, 20) << "Lua INFO: " << message << dendl;
+ }
+
+ return NUM_RETURN;
+};
+
+void create_debug_action(lua_State* L, CephContext* cct) {
+ lua_pushlightuserdata(L, cct);
+ lua_pushcclosure(L, RGWDebugLog, ONE_UPVAL);
+ lua_setglobal(L, RGWDebugLogAction);
+}
+
+void stack_dump(lua_State* L) {
+ auto i = lua_gettop(L);
+ std::cout << std::endl << " ---------------- Stack Dump ----------------" << std::endl;
+ std::cout << "Stack Size: " << i << std::endl;
+ while (i > 0) {
+ const auto t = lua_type(L, i);
+ switch (t) {
+ case LUA_TNIL:
+ std::cout << i << ": nil" << std::endl;
+ break;
+ case LUA_TSTRING:
+ std::cout << i << ": " << lua_tostring(L, i) << std::endl;
+ break;
+ case LUA_TBOOLEAN:
+ std::cout << i << ": " << lua_toboolean(L, i) << std::endl;
+ break;
+ case LUA_TNUMBER:
+ std::cout << i << ": " << lua_tonumber(L, i) << std::endl;
+ break;
+ default:
+ std::cout << i << ": " << lua_typename(L, t) << std::endl;
+ break;
+ }
+ i--;
+ }
+ std::cout << "--------------- Stack Dump Finished ---------------" << std::endl;
+}
+
+}
--- /dev/null
+#pragma once
+
+#include <memory>
+#include <string>
+#include <ctime>
+#include <lua.hpp>
+
+class CephContext;
+
+namespace rgw::lua {
+
+// push ceph time in string format: "%Y-%m-%d %H:%M:%S"
+template <typename CephTime>
+void lua_pushtime(lua_State* L, const CephTime& tp)
+{
+ const auto tt = CephTime::clock::to_time_t(tp);
+ const auto tm = *std::localtime(&tt);
+ char buff[64];
+ std::strftime(buff, sizeof(buff), "%Y-%m-%d %H:%M:%S", &tm);
+ lua_pushstring(L, buff);
+}
+
+// push std::string
+void lua_pushstring(lua_State* L, const std::string& str);
+
+// dump the lua stack to stdout
+void stack_dump(lua_State* L);
+
+class lua_state_guard {
+ lua_State* l;
+public:
+ lua_state_guard(lua_State* _l) : l(_l) {}
+ ~lua_state_guard() {lua_close(l);}
+ void reset(lua_State* _l=nullptr) {l = _l;}
+};
+
+constexpr auto ONE_UPVAL = 1;
+constexpr auto TWO_UPVALS = 2;
+constexpr auto THREE_UPVALS = 3;
+constexpr auto FOUR_UPVALS = 4;
+constexpr auto FIVE_UPVALS = 5;
+
+constexpr auto ONE_RETURNVAL = 1;
+constexpr auto TWO_RETURNVALS = 2;
+constexpr auto THREE_RETURNVALS = 3;
+constexpr auto FOUR_RETURNVALS = 4;
+// utility functions to create a metatable
+// and tie it to an unnamed table
+//
+// add an __index method to it, to allow reading values
+// if "readonly" parameter is set to "false", it will also add
+// a __newindex method to it, to allow writing values
+// if the "toplevel" parameter is set to "true", it will name the
+// table as well as the metatable, this would allow direct access from
+// the lua script.
+//
+// The MetaTable is expected to be a class with the following members:
+// Name (static function returning the unique name of the metatable)
+// TableName (static function returning the unique name of the table - needed only for "toplevel" tables)
+// Type (typename) - the type of the "upvalue" (the type that the meta table represent)
+// IndexClosure (static function return "int" and accept "lua_State*")
+// NewIndexClosure (static function return "int" and accept "lua_State*")
+// e.g.
+// struct MyStructMetaTable {
+// static std::string TableName() {
+// return "MyStruct";
+// }
+//
+// using Type = MyStruct;
+//
+// static int IndexClosure(lua_State* L) {
+// const auto value = reinterpret_cast<const Type*>(lua_touserdata(L, lua_upvalueindex(1)));
+// ...
+// }
+
+// static int NewIndexClosure(lua_State* L) {
+// auto value = reinterpret_cast<Type*>(lua_touserdata(L, lua_upvalueindex(1)));
+// ...
+// }
+// };
+//
+
+template<typename MetaTable, typename... Upvalues>
+void create_metatable(lua_State* L, bool toplevel, Upvalues... upvalues)
+{
+ constexpr auto upvals_size = sizeof...(upvalues);
+ const std::array<void*, upvals_size> upvalue_arr = {upvalues...};
+ // create table
+ lua_newtable(L);
+ if (toplevel) {
+ // duplicate the table to make sure it remain in the stack
+ lua_pushvalue(L, -1);
+ // give table a name (in cae of "toplevel")
+ lua_setglobal(L, MetaTable::TableName().c_str());
+ }
+ // create metatable
+ [[maybe_unused]] const auto rc = luaL_newmetatable(L, MetaTable::Name().c_str());
+ lua_pushstring(L, "__index");
+ for (const auto upvalue : upvalue_arr) {
+ lua_pushlightuserdata(L, upvalue);
+ }
+ lua_pushcclosure(L, MetaTable::IndexClosure, upvals_size);
+ lua_rawset(L, -3);
+ lua_pushstring(L, "__newindex");
+ for (const auto upvalue : upvalue_arr) {
+ lua_pushlightuserdata(L, upvalue);
+ }
+ lua_pushcclosure(L, MetaTable::NewIndexClosure, upvals_size);
+ lua_rawset(L, -3);
+ lua_pushstring(L, "__pairs");
+ for (const auto upvalue : upvalue_arr) {
+ lua_pushlightuserdata(L, upvalue);
+ }
+ lua_pushcclosure(L, MetaTable::PairsClosure, upvals_size);
+ lua_rawset(L, -3);
+ lua_pushstring(L, "__len");
+ for (const auto upvalue : upvalue_arr) {
+ lua_pushlightuserdata(L, upvalue);
+ }
+ lua_pushcclosure(L, MetaTable::LenClosure, upvals_size);
+ lua_rawset(L, -3);
+ // tie metatable and table
+ lua_setmetatable(L, -2);
+}
+
+template<typename MetaTable>
+void create_metatable(lua_State* L, bool toplevel, std::unique_ptr<typename MetaTable::Type>& ptr)
+{
+ if (ptr) {
+ create_metatable<MetaTable>(L, toplevel, reinterpret_cast<void*>(ptr.get()));
+ } else {
+ lua_pushnil(L);
+ }
+}
+
+// following struct may be used as a base class for other MetaTable classes
+// note, however, this is not mandatory to use it as a base
+struct EmptyMetaTable {
+ // by default everythinmg is "readonly"
+ // to change, overload this function in the derived
+ static int NewIndexClosure(lua_State* L) {
+ throw std::runtime_error("trying to write to readonly field");
+ return 1;
+ }
+
+ // by default nothing is iterable
+ // to change, overload this function in the derived
+ static int PairsClosure(lua_State* L) {
+ throw std::runtime_error("trying to iterate over non-iterable field");
+ return 1;
+ }
+
+ // by default nothing is iterable
+ // to change, overload this function in the derived
+ static int LenClosure(lua_State* L) {
+ throw std::runtime_error("trying to get length of non-iterable field");
+ return 1;
+ }
+
+ static void throw_unknown_field(const std::string& index, const std::string& table) {
+ throw std::runtime_error("unknown field name: " + index + " provided to: " + table);
+ }
+};
+
+// create a debug log action
+// it expects CephContext to be captured
+// it expects one string parameter, which is the message to log
+// could be executed from any context that has CephContext
+// e.g.
+// RGWDebugLog("hello world from lua")
+//
+void create_debug_action(lua_State* L, CephContext* cct);
+
+}
+
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab ft=cpp
+
+#pragma once
+
+enum RGWOpType {
+ RGW_OP_UNKNOWN = 0,
+ RGW_OP_GET_OBJ,
+ RGW_OP_LIST_BUCKETS,
+ RGW_OP_STAT_ACCOUNT,
+ RGW_OP_LIST_BUCKET,
+ RGW_OP_GET_BUCKET_LOGGING,
+ RGW_OP_GET_BUCKET_LOCATION,
+ RGW_OP_GET_BUCKET_VERSIONING,
+ RGW_OP_SET_BUCKET_VERSIONING,
+ RGW_OP_GET_BUCKET_WEBSITE,
+ RGW_OP_SET_BUCKET_WEBSITE,
+ RGW_OP_STAT_BUCKET,
+ RGW_OP_CREATE_BUCKET,
+ RGW_OP_DELETE_BUCKET,
+ RGW_OP_PUT_OBJ,
+ RGW_OP_STAT_OBJ,
+ RGW_OP_POST_OBJ,
+ RGW_OP_PUT_METADATA_ACCOUNT,
+ RGW_OP_PUT_METADATA_BUCKET,
+ RGW_OP_PUT_METADATA_OBJECT,
+ RGW_OP_SET_TEMPURL,
+ RGW_OP_DELETE_OBJ,
+ RGW_OP_COPY_OBJ,
+ RGW_OP_GET_ACLS,
+ RGW_OP_PUT_ACLS,
+ RGW_OP_GET_CORS,
+ RGW_OP_PUT_CORS,
+ RGW_OP_DELETE_CORS,
+ RGW_OP_OPTIONS_CORS,
+ RGW_OP_GET_REQUEST_PAYMENT,
+ RGW_OP_SET_REQUEST_PAYMENT,
+ RGW_OP_INIT_MULTIPART,
+ RGW_OP_COMPLETE_MULTIPART,
+ RGW_OP_ABORT_MULTIPART,
+ RGW_OP_LIST_MULTIPART,
+ RGW_OP_LIST_BUCKET_MULTIPARTS,
+ RGW_OP_DELETE_MULTI_OBJ,
+ RGW_OP_BULK_DELETE,
+ RGW_OP_SET_ATTRS,
+ RGW_OP_GET_CROSS_DOMAIN_POLICY,
+ RGW_OP_GET_HEALTH_CHECK,
+ RGW_OP_GET_INFO,
+ RGW_OP_CREATE_ROLE,
+ RGW_OP_DELETE_ROLE,
+ RGW_OP_GET_ROLE,
+ RGW_OP_MODIFY_ROLE,
+ RGW_OP_LIST_ROLES,
+ RGW_OP_PUT_ROLE_POLICY,
+ RGW_OP_GET_ROLE_POLICY,
+ RGW_OP_LIST_ROLE_POLICIES,
+ RGW_OP_DELETE_ROLE_POLICY,
+ RGW_OP_PUT_BUCKET_POLICY,
+ RGW_OP_GET_BUCKET_POLICY,
+ RGW_OP_DELETE_BUCKET_POLICY,
+ RGW_OP_PUT_OBJ_TAGGING,
+ RGW_OP_GET_OBJ_TAGGING,
+ RGW_OP_DELETE_OBJ_TAGGING,
+ RGW_OP_PUT_LC,
+ RGW_OP_GET_LC,
+ RGW_OP_DELETE_LC,
+ RGW_OP_PUT_USER_POLICY,
+ RGW_OP_GET_USER_POLICY,
+ RGW_OP_LIST_USER_POLICIES,
+ RGW_OP_DELETE_USER_POLICY,
+ RGW_OP_PUT_BUCKET_OBJ_LOCK,
+ RGW_OP_GET_BUCKET_OBJ_LOCK,
+ RGW_OP_PUT_OBJ_RETENTION,
+ RGW_OP_GET_OBJ_RETENTION,
+ RGW_OP_PUT_OBJ_LEGAL_HOLD,
+ RGW_OP_GET_OBJ_LEGAL_HOLD,
+ /* rgw specific */
+ RGW_OP_ADMIN_SET_METADATA,
+ RGW_OP_GET_OBJ_LAYOUT,
+ RGW_OP_BULK_UPLOAD,
+ RGW_OP_METADATA_SEARCH,
+ RGW_OP_CONFIG_BUCKET_META_SEARCH,
+ RGW_OP_GET_BUCKET_META_SEARCH,
+ RGW_OP_DEL_BUCKET_META_SEARCH,
+ /* sts specific*/
+ RGW_STS_ASSUME_ROLE,
+ RGW_STS_GET_SESSION_TOKEN,
+ RGW_STS_ASSUME_ROLE_WEB_IDENTITY,
+ /* pubsub */
+ RGW_OP_PUBSUB_TOPIC_CREATE,
+ RGW_OP_PUBSUB_TOPICS_LIST,
+ RGW_OP_PUBSUB_TOPIC_GET,
+ RGW_OP_PUBSUB_TOPIC_DELETE,
+ RGW_OP_PUBSUB_SUB_CREATE,
+ RGW_OP_PUBSUB_SUB_GET,
+ RGW_OP_PUBSUB_SUB_DELETE,
+ RGW_OP_PUBSUB_SUB_PULL,
+ RGW_OP_PUBSUB_SUB_ACK,
+ RGW_OP_PUBSUB_NOTIF_CREATE,
+ RGW_OP_PUBSUB_NOTIF_DELETE,
+ RGW_OP_PUBSUB_NOTIF_LIST,
+ RGW_OP_GET_BUCKET_TAGGING,
+ RGW_OP_PUT_BUCKET_TAGGING,
+ RGW_OP_DELETE_BUCKET_TAGGING,
+ RGW_OP_GET_BUCKET_REPLICATION,
+ RGW_OP_PUT_BUCKET_REPLICATION,
+ RGW_OP_DELETE_BUCKET_REPLICATION,
+ /* public access */
+ RGW_OP_GET_BUCKET_POLICY_STATUS,
+ RGW_OP_PUT_BUCKET_PUBLIC_ACCESS_BLOCK,
+ RGW_OP_GET_BUCKET_PUBLIC_ACCESS_BLOCK,
+ RGW_OP_DELETE_BUCKET_PUBLIC_ACCESS_BLOCK,
+ /*OIDC provider specific*/
+ RGW_OP_CREATE_OIDC_PROVIDER,
+ RGW_OP_DELETE_OIDC_PROVIDER,
+ RGW_OP_GET_OIDC_PROVIDER,
+ RGW_OP_LIST_OIDC_PROVIDERS,
+};
+
#include "rgw_client_io.h"
#include "rgw_opa.h"
#include "rgw_perf_counters.h"
+#include "rgw_lua.h"
+#include "rgw_lua_request.h"
#include "services/svc_zone_utils.h"
abort_early(s, NULL, -ERR_METHOD_NOT_ALLOWED, handler);
goto done;
}
+ {
+ std::string script;
+ auto rc = rgw::lua::read_script(store, s->bucket_tenant, s->yield, rgw::lua::context::preRequest, script);
+ if (rc == -ENOENT) {
+ // no script, nothing to do
+ } else if (rc < 0) {
+ ldpp_dout(op, 5) << "WARNING: failed to read pre request script. error: " << rc << dendl;
+ } else {
+ rc = rgw::lua::request::execute(store, rest, olog, s, op->name(), script);
+ if (rc < 0) {
+ ldpp_dout(op, 5) << "WARNING: failed to execute pre request script. error: " << rc << dendl;
+ }
+ }
+ }
std::tie(ret,c) = schedule_request(scheduler, s, op);
if (ret < 0) {
if (ret == -EAGAIN) {
}
done:
+ if (op) {
+ std::string script;
+ auto rc = rgw::lua::read_script(store, s->bucket_tenant, s->yield, rgw::lua::context::postRequest, script);
+ if (rc == -ENOENT) {
+ // no script, nothing to do
+ } else if (rc < 0) {
+ ldpp_dout(op, 5) << "WARNING: failed to read post request script. error: " << rc << dendl;
+ } else {
+ rc = rgw::lua::request::execute(store, rest, olog, s, op->name(), script);
+ if (rc < 0) {
+ ldpp_dout(op, 5) << "WARNING: failed to execute post request script. error: " << rc << dendl;
+ }
+ }
+ }
+
try {
client_io->complete_request();
} catch (rgw::io::Exception& e) {
};
class RGWFrontendConfig;
+class RGWRequest;
class RGWProcess {
deque<RGWRequest*> m_req_queue;
void clear() { tag_map.clear(); }
bool empty() const noexcept { return tag_map.empty(); }
const tag_map_t& get_tags() const {return tag_map;}
+ tag_map_t& get_tags() {return tag_map;}
};
WRITE_CLASS_ENCODER(RGWObjTags)
subscription rm remove a pubsub subscription
subscription pull show events in a pubsub subscription
subscription ack ack (remove) an events in a pubsub subscription
+ script put upload a lua script to a context
+ script get get the lua script of a context
+ script rm remove the lua scripts of a context
options:
--tenant=<tenant> tenant name
--user_ns=<namespace> namespace of user (oidc in case of users authenticated with oidc provider)
--subscription pubsub subscription name
--event-id event id in a pubsub subscription
+ Script options:
+ --context context in which the script runs. one of: preRequest, postRequest
+
--conf/-c FILE read configuration from the given configuration file
--id ID set ID portion of my name
--name/-n TYPE.ID set name
add_ceph_test(test-ceph-diff-sorted.sh
${CMAKE_CURRENT_SOURCE_DIR}/test-ceph-diff-sorted.sh)
-
# unittest_cls_fifo_legacy
add_executable(unittest_cls_fifo_legacy test_cls_fifo_legacy.cc)
target_link_libraries(unittest_cls_fifo_legacy radostest-cxx ${UNITTEST_LIBS}
${rgw_libs})
+
+add_executable(unittest_rgw_lua test_rgw_lua.cc)
+add_ceph_unittest(unittest_rgw_lua)
+target_link_libraries(unittest_rgw_lua ${rgw_libs} ${LUA_LIBRARIES})
+
--- /dev/null
+#include <gtest/gtest.h>
+#include "common/ceph_context.h"
+#include "rgw/rgw_common.h"
+#include "rgw/rgw_process.h"
+#include "rgw/rgw_sal_rados.h"
+#include "rgw/rgw_lua_request.h"
+
+using namespace rgw;
+
+class CctCleaner {
+ CephContext* cct;
+public:
+ CctCleaner(CephContext* _cct) : cct(_cct) {}
+ ~CctCleaner() {
+#ifdef WITH_SEASTAR
+ delete cct;
+#else
+ cct->put();
+#endif
+ }
+};
+
+class TestRGWUser : public sal::RGWUser {
+public:
+ virtual int list_buckets(const string&, const string&, uint64_t, bool, sal::RGWBucketList&) override {
+ return 0;
+ }
+
+ virtual sal::RGWBucket* create_bucket(rgw_bucket& bucket, ceph::real_time creation_time) override {
+ return nullptr;
+ }
+
+ virtual int load_by_id(optional_yield y) override {
+ return 0;
+ }
+
+ virtual ~TestRGWUser() = default;
+};
+
+class TestAccounter : public io::Accounter, public io::BasicClient {
+ RGWEnv env;
+
+protected:
+ virtual int init_env(CephContext *cct) override {
+ return 0;
+ }
+
+public:
+ ~TestAccounter() = default;
+
+ virtual void set_account(bool enabled) override {
+ }
+
+ virtual uint64_t get_bytes_sent() const override {
+ return 0;
+ }
+
+ virtual uint64_t get_bytes_received() const override {
+ return 0;
+ }
+
+ virtual RGWEnv& get_env() noexcept override {
+ return env;
+ }
+
+ virtual size_t complete_request() override {
+ return 0;
+ }
+};
+
+auto cct = new CephContext(CEPH_ENTITY_TYPE_CLIENT);
+
+CctCleaner cleaner(cct);
+
+TEST(TestRGWLua, EmptyScript)
+{
+ const std::string script;
+
+ RGWEnv e;
+ uint64_t id = 0;
+ req_state s(cct, &e, id);
+
+ const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", script);
+ ASSERT_EQ(rc, 0);
+}
+
+#define DEFINE_REQ_STATE RGWEnv e; req_state s(cct, &e, 0);
+
+TEST(TestRGWLua, SyntaxError)
+{
+ const std::string script = R"(
+ if 3 < 5 then
+ RGWDebugLog("missing 'end'")
+ )";
+
+ DEFINE_REQ_STATE;
+
+ const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", script);
+ ASSERT_EQ(rc, -1);
+}
+
+TEST(TestRGWLua, Hello)
+{
+ const std::string script = R"(
+ RGWDebugLog("hello from lua")
+ )";
+
+ DEFINE_REQ_STATE;
+
+ const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", script);
+ ASSERT_EQ(rc, 0);
+}
+
+TEST(TestRGWLua, URI)
+{
+ const std::string script = R"(
+ msg = "URI is: " .. Request.DecodedURI
+ RGWDebugLog(msg)
+ print(msg)
+ )";
+
+ DEFINE_REQ_STATE;
+ s.decoded_uri = "http://hello.world/";
+
+ const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", script);
+ ASSERT_EQ(rc, 0);
+}
+
+TEST(TestRGWLua, Response)
+{
+ const std::string script = R"(
+ print(Request.Response.Message)
+ print(Request.Response.HTTPStatus)
+ print(Request.Response.RGWCode)
+ print(Request.Response.HTTPStatusCode)
+ )";
+
+ DEFINE_REQ_STATE;
+ s.err.http_ret = 400;
+ s.err.ret = 4000;
+ s.err.err_code = "Bad Request";
+ s.err.message = "This is a bad request";
+
+ const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+ ASSERT_EQ(rc, 0);
+}
+
+TEST(TestRGWLua, SetResponse)
+{
+ const std::string script = R"(
+ print(Request.Response.Message)
+ Request.Response.Message = "this is a good request"
+ print(Request.Response.Message)
+ )";
+
+ DEFINE_REQ_STATE;
+ s.err.message = "this is a bad request";
+
+ const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+ ASSERT_EQ(rc, 0);
+}
+
+TEST(TestRGWLua, SetRGWId)
+{
+ const std::string script = R"(
+ print(Request.RGWId)
+ Request.RGWId = "bar"
+ print(Request.RGWId)
+ )";
+
+ DEFINE_REQ_STATE;
+ s.host_id = "foo";
+
+ const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+ ASSERT_NE(rc, 0);
+}
+
+TEST(TestRGWLua, InvalidField)
+{
+ const std::string script = R"(
+ RGWDebugLog(Request.Kaboom)
+ )";
+
+ DEFINE_REQ_STATE;
+ s.host_id = "foo";
+
+ const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "kaboom", script);
+ ASSERT_EQ(rc, -1);
+}
+
+TEST(TestRGWLua, InvalidSubField)
+{
+ const std::string script = R"(
+ RGWDebugLog(Request.Error.Kaboom)
+ )";
+
+ DEFINE_REQ_STATE;
+
+ const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "kaboom", script);
+ ASSERT_EQ(rc, -1);
+}
+
+TEST(TestRGWLua, Bucket)
+{
+ const std::string script = R"(
+ if Request.Bucket then
+ msg = "Bucket Id: " .. Request.Bucket.Id
+ RGWDebugLog(msg)
+ print(msg)
+ print("Bucket Marker: " .. Request.Bucket.Marker)
+ print("Bucket Name: " .. Request.Bucket.Name)
+ print("Bucket Tenant: " .. Request.Bucket.Tenant)
+ print("Bucket Count: " .. Request.Bucket.Count)
+ print("Bucket Size: " .. Request.Bucket.Size)
+ print("Bucket ZoneGroupId: " .. Request.Bucket.ZoneGroupId)
+ print("Bucket Creation Time: " .. Request.Bucket.CreationTime)
+ print("Bucket MTime: " .. Request.Bucket.MTime)
+ print("Bucket Quota Max Size: " .. Request.Bucket.Quota.MaxSize)
+ print("Bucket Quota Max Objects: " .. Request.Bucket.Quota.MaxObjects)
+ print("Bucket Quota Enabled: " .. tostring(Request.Bucket.Quota.Enabled))
+ print("Bucket Quota Rounded: " .. tostring(Request.Bucket.Quota.Rounded))
+ print("Bucket User Id: " .. Request.Bucket.User.Id)
+ print("Bucket User Tenant: " .. Request.Bucket.User.Tenant)
+ else
+ print("No bucket")
+ end
+ )";
+
+ DEFINE_REQ_STATE;
+
+ rgw_bucket b;
+ b.tenant = "mytenant";
+ b.name = "myname";
+ b.marker = "mymarker";
+ b.bucket_id = "myid";
+ s.bucket.reset(new sal::RGWRadosBucket(nullptr, b));
+
+ const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+ ASSERT_EQ(rc, 0);
+}
+
+TEST(TestRGWLua, GenericAttributes)
+{
+ const std::string script = R"(
+ print("hello = " .. (Request.GenericAttributes["hello"] or "nil"))
+ print("foo = " .. (Request.GenericAttributes["foo"] or "nil"))
+ print("kaboom = " .. (Request.GenericAttributes["kaboom"] or "nil"))
+ print("number of attributes is: " .. #Request.GenericAttributes)
+ for k, v in pairs(Request.GenericAttributes) do
+ print("key=" .. k .. ", " .. "value=" .. v)
+ end
+ )";
+
+ DEFINE_REQ_STATE;
+ s.generic_attrs["hello"] = "world";
+ s.generic_attrs["foo"] = "bar";
+ s.generic_attrs["goodbye"] = "cruel world";
+ s.generic_attrs["ka"] = "boom";
+
+ const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+ ASSERT_EQ(rc, 0);
+}
+
+TEST(TestRGWLua, Environment)
+{
+ const std::string script = R"(
+ print("number of env entries is: " .. #Request.Environment)
+ for k, v in pairs(Request.Environment) do
+ print("key=" .. k .. ", " .. "value=" .. v)
+ end
+ )";
+
+ DEFINE_REQ_STATE;
+ s.env[""] = "world";
+ s.env[""] = "bar";
+ s.env["goodbye"] = "cruel world";
+ s.env["ka"] = "boom";
+
+ const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+ ASSERT_EQ(rc, 0);
+}
+
+TEST(TestRGWLua, Tags)
+{
+ const std::string script = R"(
+ print("number of tags is: " .. #Request.Tags)
+ for k, v in pairs(Request.Tags) do
+ print("key=" .. k .. ", " .. "value=" .. v)
+ end
+ )";
+
+ DEFINE_REQ_STATE;
+ s.tagset.add_tag("hello", "world");
+ s.tagset.add_tag("foo", "bar");
+ s.tagset.add_tag("goodbye", "cruel world");
+ s.tagset.add_tag("ka", "boom");
+
+ const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+ ASSERT_EQ(rc, 0);
+}
+
+TEST(TestRGWLua, Acl)
+{
+ const std::string script = R"(
+ function print_grant(g)
+ print("Type: " .. g.Type)
+ print("GroupType: " .. g.GroupType)
+ print("Permission: " .. g.Permission)
+ print("Referer: " .. g.Referer)
+ if (g.User) then
+ print("User.Tenant: " .. g.User.Tenant)
+ print("User.Id: " .. g.User.Id)
+ end
+ end
+
+ print(Request.UserAcl.Owner.DisplayName)
+ print(Request.UserAcl.Owner.User.Id)
+ print(Request.UserAcl.Owner.User.Tenant)
+ print("number of grants is: " .. #Request.UserAcl.Grants)
+ for k, v in pairs(Request.UserAcl.Grants) do
+ print("grant key=" .. k)
+ print("grant values=")
+ print_grant(v)
+ end
+ )";
+
+ DEFINE_REQ_STATE;
+ ACLOwner owner;
+ owner.set_id(rgw_user("john", "doe"));
+ owner.set_name("john doe");
+ s.user_acl.reset(new RGWAccessControlPolicy(cct));
+ s.user_acl->set_owner(owner);
+ ACLGrant grant1, grant2, grant3, grant4, grant5;
+ grant1.set_canon(rgw_user("jane", "doe"), "her grant", 1);
+ grant2.set_referer("http://localhost/ref1", 2);
+ grant3.set_referer("http://localhost/ref2", 3);
+ grant4.set_canon(rgw_user("john", "doe"), "his grant", 4);
+ grant5.set_group(ACL_GROUP_AUTHENTICATED_USERS, 5);
+ s.user_acl->get_acl().add_grant(&grant1);
+ s.user_acl->get_acl().add_grant(&grant2);
+ s.user_acl->get_acl().add_grant(&grant3);
+ s.user_acl->get_acl().add_grant(&grant4);
+ s.user_acl->get_acl().add_grant(&grant5);
+ const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+ ASSERT_EQ(rc, 0);
+}
+
+TEST(TestRGWLua, UseFunction)
+{
+ const std::string script = R"(
+ function print_owner(owner)
+ print("Owner Dispaly Name: " .. owner.DisplayName)
+ print("Owner Id: " .. owner.User.Id)
+ print("Owner Tenanet: " .. owner.User.Tenant)
+ end
+
+ print_owner(Request.ObjectOwner)
+
+ function print_acl(acl_type)
+ index = acl_type .. "ACL"
+ acl = Request[index]
+ if acl then
+ print(acl_type .. "ACL Owner")
+ print_owner(acl.Owner)
+ else
+ print("no " .. acl_type .. " ACL in request: " .. Request.Id)
+ end
+ end
+
+ print_acl("User")
+ print_acl("Bucket")
+ print_acl("Object")
+ )";
+
+ DEFINE_REQ_STATE;
+ s.owner.set_name("user two");
+ s.owner.set_id(rgw_user("tenant2", "user2"));
+ s.user_acl.reset(new RGWAccessControlPolicy());
+ s.user_acl->get_owner().set_name("user three");
+ s.user_acl->get_owner().set_id(rgw_user("tenant3", "user3"));
+ s.bucket_acl.reset(new RGWAccessControlPolicy());
+ s.bucket_acl->get_owner().set_name("user four");
+ s.bucket_acl->get_owner().set_id(rgw_user("tenant4", "user4"));
+ s.object_acl.reset(new RGWAccessControlPolicy());
+ s.object_acl->get_owner().set_name("user five");
+ s.object_acl->get_owner().set_id(rgw_user("tenant5", "user5"));
+
+ const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+ ASSERT_EQ(rc, 0);
+}
+
+TEST(TestRGWLua, WithLib)
+{
+ const std::string script = R"(
+ print("bucket name split:")
+ for i in string.gmatch(Request.Bucket.Name, "%a+") do
+ print("lua print: part: " .. i)
+ end
+ )";
+
+ DEFINE_REQ_STATE;
+
+ rgw_bucket b;
+ b.name = "my-bucket-name-is-fish";
+ s.bucket.reset(new sal::RGWRadosBucket(nullptr, b));
+
+ const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+ ASSERT_EQ(rc, 0);
+}
+
+#include <sys/socket.h>
+#include <stdlib.h>
+
+void unix_socket_client(const std::string& path) {
+ int fd;
+ // create the socket
+ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+ std::cout << "unix socket error: " << errno << std::endl;
+ return;
+ }
+ // set the path
+ struct sockaddr_un addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, path.c_str(), sizeof(addr.sun_path)-1);
+
+ // let the socket be created by the "rgw" side
+ std::this_thread::sleep_for(std::chrono::seconds(2));
+ if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
+ std::cout << "unix socket connect error: " << errno << std::endl;
+ return;
+ }
+
+ std::cout << "unix socket connected to: " << path << std::endl;
+ char buff[256];
+ int rc;
+ while((rc=read(fd, buff, sizeof(buff))) > 0) {
+ std::cout << std::string(buff, rc);
+ }
+}
+
+TEST(TestRGWLua, OpsLog)
+{
+ const std::string unix_socket_path = "./aSocket.sock";
+ unlink(unix_socket_path.c_str());
+
+ std::thread unix_socket_thread(unix_socket_client, unix_socket_path);
+
+ const std::string script = R"(
+ if Request.Response.HTTPStatusCode == 200 then
+ print("request is good, just log to lua: " .. Request.Response.Message)
+ else
+ print("request is bad, use ops log:")
+ if Request.Bucket then
+ rc = Request.Log()
+ print("ops log return code: " .. rc)
+ else
+ print("no bucket, ops log wasn't called")
+ end
+ end
+ )";
+
+ auto store = std::unique_ptr<sal::RGWRadosStore>(new sal::RGWRadosStore);
+ store->setRados(new RGWRados);
+ auto olog = std::unique_ptr<OpsLogSocket>(new OpsLogSocket(cct, 1024));
+ ASSERT_TRUE(olog->init(unix_socket_path));
+
+ DEFINE_REQ_STATE;
+ s.err.http_ret = 200;
+ s.err.ret = 0;
+ s.err.err_code = "200OK";
+ s.err.message = "Life is great";
+ rgw_bucket b;
+ b.tenant = "tenant";
+ b.name = "name";
+ b.marker = "marker";
+ b.bucket_id = "id";
+ s.bucket.reset(new sal::RGWRadosBucket(nullptr, b));
+ s.bucket_name = "name";
+ s.enable_ops_log = true;
+ s.enable_usage_log = false;
+ s.user.reset(new TestRGWUser());
+ TestAccounter ac;
+ s.cio = ∾
+ s.cct->_conf->rgw_ops_log_rados = false;
+
+ auto rc = lua::request::execute(store.get(), nullptr, olog.get(), &s, "put_obj", script);
+ EXPECT_EQ(rc, 0);
+
+ s.err.http_ret = 400;
+ rc = lua::request::execute(store.get(), nullptr, olog.get(), &s, "put_obj", script);
+ EXPECT_EQ(rc, 0);
+
+ // give the socket client time to read
+ std::this_thread::sleep_for(std::chrono::seconds(2));
+ unix_socket_thread.detach();
+}
+