From: Yuval Lifshitz Date: Mon, 8 Jun 2020 12:26:47 +0000 (+0300) Subject: rgw/lua: run lua scripts in s3 requests context X-Git-Tag: v16.1.0~860^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=refs%2Fpull%2F35477%2Fhead;p=ceph.git rgw/lua: run lua scripts in s3 requests context for more details on design and remaining work see: https://gist.github.com/yuvalif/60d5984c28af89ba17443ce947540c1f Signed-off-by: Yuval Lifshitz --- diff --git a/doc/radosgw/index.rst b/doc/radosgw/index.rst index 5d1ee83cab1a..098d50119a68 100644 --- a/doc/radosgw/index.rst +++ b/doc/radosgw/index.rst @@ -78,4 +78,5 @@ you may write data with one API and retrieve it with the other. Manpage radosgw-admin <../../man/8/radosgw-admin> QAT Acceleration for Encryption and Compression S3-select + Lua Scripting diff --git a/doc/radosgw/lua-scripting.rst b/doc/radosgw/lua-scripting.rst new file mode 100644 index 000000000000..6c79dce5c057 --- /dev/null +++ b/doc/radosgw/lua-scripting.rst @@ -0,0 +1,308 @@ +============= +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[""]`` | table | user ACL grant | no | no | no | ++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+ +| ``Request.UserAcl.Grants[""].Type`` | integer | user ACL grant type | no | no | no | ++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+ +| ``Request.UserAcl.Grants[""].User`` | table | user ACL grant user | no | no | no | ++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+ +| ``Request.UserAcl.Grants[""].User.Tenant`` | table | user ACL grant user tenant | no | no | no | ++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+ +| ``Request.UserAcl.Grants[""].User.Id`` | table | user ACL grant user id | no | no | no | ++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+ +| ``Request.UserAcl.Grants[""].GroupType`` | integer | user ACL grant group type | no | no | no | ++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+ +| ``Request.UserAcl.Grants[""].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[]`` | table | user policy. See: ``Request.Policy`` | no | no | no | ++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+ +| ``Request.RGWId`` | string | radosgw host id: ``--`` | 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 = " something bad happened :-( " + end + diff --git a/src/rgw/CMakeLists.txt b/src/rgw/CMakeLists.txt index 902474ce2097..7d4523d55a3d 100644 --- a/src/rgw/CMakeLists.txt +++ b/src/rgw/CMakeLists.txt @@ -155,7 +155,10 @@ set(librgw_common_srcs 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) @@ -170,6 +173,9 @@ target_include_directories(rgw_common SYSTEM PUBLIC "services") 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 @@ -252,6 +258,8 @@ if(WITH_CURL_OPENSSL) 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() @@ -266,6 +274,8 @@ if(WITH_RADOSGW_KAFKA_ENDPOINT) 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) @@ -400,6 +410,8 @@ if(WITH_RADOSGW_KAFKA_ENDPOINT) 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}) diff --git a/src/rgw/rgw_acl.h b/src/rgw/rgw_acl.h index c1a68845d8e8..ad09eee16456 100644 --- a/src/rgw/rgw_acl.h +++ b/src/rgw/rgw_acl.h @@ -141,6 +141,7 @@ public: return true; } } + ACLGranteeType& get_type() { return type; } const ACLGranteeType& get_type() const { return type; } ACLPermission& get_permission() { return permission; } @@ -295,6 +296,8 @@ namespace auth { } } +using ACLGrantMap = std::multimap; + class RGWAccessControlList { protected: @@ -304,7 +307,7 @@ protected: map acl_user_map; map acl_group_map; list referer_list; - multimap grant_map; + ACLGrantMap grant_map; void _add_grant(ACLGrant *grant); public: explicit RGWAccessControlList(CephContext *_cct) : cct(_cct) {} @@ -342,7 +345,7 @@ public: if (struct_v >= 2) { decode(acl_group_map, bl); } else if (!maps_initialized) { - multimap::iterator iter; + ACLGrantMap::iterator iter; for (iter = grant_map.begin(); iter != grant_map.end(); ++iter) { ACLGrant& grant = iter->second; _add_grant(&grant); @@ -359,8 +362,8 @@ public: void add_grant(ACLGrant *grant); void remove_canon_user_grant(rgw_user& user_id); - multimap& get_grant_map() { return grant_map; } - const multimap& 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(); @@ -411,7 +414,7 @@ public: 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); }; diff --git a/src/rgw/rgw_admin.cc b/src/rgw/rgw_admin.cc index b32561867256..77aca6864ff0 100644 --- a/src/rgw/rgw_admin.cc +++ b/src/rgw/rgw_admin.cc @@ -60,6 +60,7 @@ extern "C" { #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" @@ -279,6 +280,9 @@ void usage() 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 name\n"; cout << " --user_ns= namespace of user (oidc in case of users authenticated with oidc provider)\n"; @@ -428,6 +432,8 @@ void usage() 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(); } @@ -759,6 +765,9 @@ enum class OPT { PUBSUB_SUB_RM, PUBSUB_SUB_PULL, PUBSUB_EVENT_RM, + SCRIPT_PUT, + SCRIPT_GET, + SCRIPT_RM, }; } @@ -972,6 +981,9 @@ static SimpleCmd::Commands all_cmds = { { "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 = { @@ -3158,6 +3170,8 @@ int main(int argc, const char **argv) string sub_name; string event_id; + std::optional str_script_ctx; + std::optional opt_group_id; std::optional opt_status; std::optional opt_flow_type; @@ -3616,6 +3630,8 @@ int main(int argc, const char **argv) 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; @@ -3834,6 +3850,7 @@ int main(int argc, const char **argv) OPT::PUBSUB_TOPIC_GET, OPT::PUBSUB_SUB_GET, OPT::PUBSUB_SUB_PULL, + OPT::SCRIPT_GET, }; @@ -9261,5 +9278,79 @@ next: } } + 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; } + diff --git a/src/rgw/rgw_common.h b/src/rgw/rgw_common.h index 3c97553ed707..8658a0020ff0 100644 --- a/src/rgw/rgw_common.h +++ b/src/rgw/rgw_common.h @@ -32,6 +32,7 @@ #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" @@ -437,122 +438,6 @@ enum http_op { 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; diff --git a/src/rgw/rgw_lua.cc b/src/rgw/rgw_lua.cc new file mode 100644 index 000000000000..cc2c5aab2275 --- /dev/null +++ b/src/rgw/rgw_lua.cc @@ -0,0 +1,139 @@ +#include +#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; +} +} + diff --git a/src/rgw/rgw_lua.h b/src/rgw/rgw_lua.h new file mode 100644 index 000000000000..9ba9171bd3a7 --- /dev/null +++ b/src/rgw/rgw_lua.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#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); + +} + diff --git a/src/rgw/rgw_lua_request.cc b/src/rgw/rgw_lua_request.cc new file mode 100644 index 000000000000..ea7940daa878 --- /dev/null +++ b/src/rgw/rgw_lua_request.cc @@ -0,0 +1,817 @@ +#include +#include +#include +#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(lua_touserdata(L, lua_upvalueindex(1))); + const auto rest = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(2))); + const auto olog = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(3))); + const auto s = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(4))); + const std::string op_name(reinterpret_cast(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(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(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(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(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(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(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(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(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(L, false, &(bucket->get_info().quota)); + } else if (strcasecmp(index, "PlacementRule") == 0) { + create_metatable(L, false, &(bucket->get_info().placement_rule)); + } else if (strcasecmp(index, "User") == 0) { + create_metatable(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(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> +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(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(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(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(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(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(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(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(L, false, &(it->second)); + } + return ONE_RETURNVAL; + } + + static int PairsClosure(lua_State* L) { + auto map = reinterpret_cast(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(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(L, false, &(next_it->second)); + // return key, value + + return TWO_RETURNVALS; + } + + static int LenClosure(lua_State* L) { + const auto map = reinterpret_cast(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(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(L, false, &(acl->get_owner())); + } else if (strcasecmp(index, "Grants") == 0) { + create_metatable(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; + + 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(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(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(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(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(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(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; + + static int IndexClosure(lua_State* L) { + const auto policies = reinterpret_cast(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(L, false, &((*policies)[index])); + } + return ONE_RETURNVAL; + } + + static int PairsClosure(lua_State* L) { + auto policies = reinterpret_cast(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(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(L, false, &((*policies)[next_it])); + // return key, value + } + + return TWO_RETURNVALS; + } + + static int LenClosure(lua_State* L) { + const auto policies = reinterpret_cast(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(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>(L, false, &(info->args.get_params())); + } else if (strcasecmp(index, "Resources") == 0) { + // TODO: add non-const api to get resources + create_metatable>(L, false, + const_cast*>(&(info->args.get_sub_resources()))); + } else if (strcasecmp(index, "Metadata") == 0) { + create_metatable>(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(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(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(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(lua_touserdata(L, lua_upvalueindex(1))); + const auto op_name = reinterpret_cast(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>(L, false, &(s->generic_attrs)); + } else if (strcasecmp(index, "Response") == 0) { + create_metatable(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(L, false, s->bucket); + } else if (strcasecmp(index, "Object") == 0) { + create_metatable(L, false, s->object); + } else if (strcasecmp(index, "CopyFrom") == 0) { + if (s->op_type == RGW_OP_COPY_OBJ) { + create_metatable(L, s); + } else { + lua_pushnil(L); + } + } else if (strcasecmp(index, "ObjectOwner") == 0) { + create_metatable(L, false, &(s->owner)); + } else if (strcasecmp(index, "ZoneGroup") == 0) { + create_metatable(L, false, s); + } else if (strcasecmp(index, "UserACL") == 0) { + create_metatable(L, false, s->user_acl); + } else if (strcasecmp(index, "BucketACL") == 0) { + create_metatable(L, false, s->bucket_acl); + } else if (strcasecmp(index, "ObjectACL") == 0) { + create_metatable(L, false, s->object_acl); + } else if (strcasecmp(index, "Environment") == 0) { + create_metatable>(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(L, false, s->iam_policy.get_ptr()); + } + } else if (strcasecmp(index, "UserPolicies") == 0) { + create_metatable(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(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>(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(L, true, s, const_cast(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(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; +} + +} diff --git a/src/rgw/rgw_lua_request.h b/src/rgw/rgw_lua_request.h new file mode 100644 index 000000000000..5ace5bf157fc --- /dev/null +++ b/src/rgw/rgw_lua_request.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#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); + +} + diff --git a/src/rgw/rgw_lua_utils.cc b/src/rgw/rgw_lua_utils.cc new file mode 100644 index 000000000000..abb355592d4b --- /dev/null +++ b/src/rgw/rgw_lua_utils.cc @@ -0,0 +1,77 @@ +#include +#include +#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 lua_push(lua_State* L, const std::optional& 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(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; +} + +} diff --git a/src/rgw/rgw_lua_utils.h b/src/rgw/rgw_lua_utils.h new file mode 100644 index 000000000000..3462e0ed6861 --- /dev/null +++ b/src/rgw/rgw_lua_utils.h @@ -0,0 +1,175 @@ +#pragma once + +#include +#include +#include +#include + +class CephContext; + +namespace rgw::lua { + +// push ceph time in string format: "%Y-%m-%d %H:%M:%S" +template +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(lua_touserdata(L, lua_upvalueindex(1))); +// ... +// } + +// static int NewIndexClosure(lua_State* L) { +// auto value = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); +// ... +// } +// }; +// + +template +void create_metatable(lua_State* L, bool toplevel, Upvalues... upvalues) +{ + constexpr auto upvals_size = sizeof...(upvalues); + const std::array 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 +void create_metatable(lua_State* L, bool toplevel, std::unique_ptr& ptr) +{ + if (ptr) { + create_metatable(L, toplevel, reinterpret_cast(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); + +} + diff --git a/src/rgw/rgw_op_type.h b/src/rgw/rgw_op_type.h new file mode 100644 index 000000000000..609868d31b5e --- /dev/null +++ b/src/rgw/rgw_op_type.h @@ -0,0 +1,119 @@ +// -*- 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, +}; + diff --git a/src/rgw/rgw_process.cc b/src/rgw/rgw_process.cc index 07aad9b97854..61649104921e 100644 --- a/src/rgw/rgw_process.cc +++ b/src/rgw/rgw_process.cc @@ -15,6 +15,8 @@ #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" @@ -236,6 +238,20 @@ int process_request(rgw::sal::RGWRadosStore* const store, 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) { @@ -290,6 +306,21 @@ int process_request(rgw::sal::RGWRadosStore* const store, } 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) { diff --git a/src/rgw/rgw_process.h b/src/rgw/rgw_process.h index 27fed33ed08f..6d67c33967f1 100644 --- a/src/rgw/rgw_process.h +++ b/src/rgw/rgw_process.h @@ -41,6 +41,7 @@ struct RGWProcessEnv { }; class RGWFrontendConfig; +class RGWRequest; class RGWProcess { deque m_req_queue; diff --git a/src/rgw/rgw_tag.h b/src/rgw/rgw_tag.h index d427cf064b84..e8531031dbf6 100644 --- a/src/rgw/rgw_tag.h +++ b/src/rgw/rgw_tag.h @@ -45,6 +45,7 @@ protected: 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) diff --git a/src/test/cli/radosgw-admin/help.t b/src/test/cli/radosgw-admin/help.t index fc7dc8b873c0..7db2aee332f9 100644 --- a/src/test/cli/radosgw-admin/help.t +++ b/src/test/cli/radosgw-admin/help.t @@ -173,6 +173,9 @@ 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 name --user_ns= namespace of user (oidc in case of users authenticated with oidc provider) @@ -329,6 +332,9 @@ --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 diff --git a/src/test/rgw/CMakeLists.txt b/src/test/rgw/CMakeLists.txt index f518b7b2d347..1251e3c28111 100644 --- a/src/test/rgw/CMakeLists.txt +++ b/src/test/rgw/CMakeLists.txt @@ -206,8 +206,12 @@ install(TARGETS ceph_test_rgw_gc_log DESTINATION ${CMAKE_INSTALL_BINDIR}) 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}) + diff --git a/src/test/rgw/test_rgw_lua.cc b/src/test/rgw/test_rgw_lua.cc new file mode 100644 index 000000000000..632dda114cfa --- /dev/null +++ b/src/test/rgw/test_rgw_lua.cc @@ -0,0 +1,498 @@ +#include +#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 +#include + +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(new sal::RGWRadosStore); + store->setRados(new RGWRados); + auto olog = std::unique_ptr(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(); +} +