]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw/lua: run lua scripts in s3 requests context 35477/head
authorYuval Lifshitz <ylifshit@redhat.com>
Mon, 8 Jun 2020 12:26:47 +0000 (15:26 +0300)
committerYuval Lifshitz <ylifshit@redhat.com>
Mon, 12 Oct 2020 19:23:37 +0000 (22:23 +0300)
for more details on design and
remaining work see:
https://gist.github.com/yuvalif/60d5984c28af89ba17443ce947540c1f

Signed-off-by: Yuval Lifshitz <ylifshit@redhat.com>
19 files changed:
doc/radosgw/index.rst
doc/radosgw/lua-scripting.rst [new file with mode: 0644]
src/rgw/CMakeLists.txt
src/rgw/rgw_acl.h
src/rgw/rgw_admin.cc
src/rgw/rgw_common.h
src/rgw/rgw_lua.cc [new file with mode: 0644]
src/rgw/rgw_lua.h [new file with mode: 0644]
src/rgw/rgw_lua_request.cc [new file with mode: 0644]
src/rgw/rgw_lua_request.h [new file with mode: 0644]
src/rgw/rgw_lua_utils.cc [new file with mode: 0644]
src/rgw/rgw_lua_utils.h [new file with mode: 0644]
src/rgw/rgw_op_type.h [new file with mode: 0644]
src/rgw/rgw_process.cc
src/rgw/rgw_process.h
src/rgw/rgw_tag.h
src/test/cli/radosgw-admin/help.t
src/test/rgw/CMakeLists.txt
src/test/rgw/test_rgw_lua.cc [new file with mode: 0644]

index 5d1ee83cab1a204c1d7add825f8b0f2546d42175..098d50119a685dd5fe027896ccdc70eaf12feb09 100644 (file)
@@ -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 <qat-accel>
    S3-select <s3select>
+   Lua Scripting <lua-scripting>
 
diff --git a/doc/radosgw/lua-scripting.rst b/doc/radosgw/lua-scripting.rst
new file mode 100644 (file)
index 0000000..6c79dce
--- /dev/null
@@ -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["<name>"]``               | table    | user ACL grant                                               | no       | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.UserAcl.Grants["<name>"].Type``          | integer  | user ACL grant type                                          | no       | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.UserAcl.Grants["<name>"].User``          | table    | user ACL grant user                                          | no       | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.UserAcl.Grants["<name>"].User.Tenant``   | table    | user ACL grant user tenant                                   | no       | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.UserAcl.Grants["<name>"].User.Id``       | table    | user ACL grant user id                                       | no       | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.UserAcl.Grants["<name>"].GroupType``     | integer  | user ACL grant group type                                    | no       | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.UserAcl.Grants["<name>"].Referer``       | string   | user ACL grant referer                                       | no       | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.BucketAcl``                              | table    | bucket ACL. See: ``Request.UserAcl``                         | no       | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.ObjectAcl``                              | table    | object ACL. See: ``Request.UserAcl``                         | no       | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Environment``                            | table    | string to string environment map                             | yes      | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Policy``                                 | table    | policy                                                       | no       | no        | yes      |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Policy.Text``                            | string   | policy text                                                  | no       | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Policy.Id``                              | string   | policy Id                                                    | no       | no        | yes      |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Policy.Statements``                      | table    | list of string statements                                    | yes      | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.UserPolicies``                           | table    | list of user policies                                        | yes      | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.UserPolicies[<index>]``                  | table    | user policy. See: ``Request.Policy``                         | no       | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.RGWId``                                  | string   | radosgw host id: ``<host>-<zone>-<zonegroup>``               | no       | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.HTTP``                                   | table    | HTTP header                                                  | no       | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.HTTP.Parameters``                        | table    | string to string parameter map                               | yes      | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.HTTP.Resources``                         | table    | string to string resource map                                | yes      | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.HTTP.Metadata``                          | table    | string to string metadata map                                | yes      | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.HTTP.Host``                              | string   | host name                                                    | no       | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.HTTP.Method``                            | string   | HTTP method                                                  | no       | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.HTTP.URI``                               | string   | URI                                                          | no       | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.HTTP.QueryString``                       | string   | HTTP query string                                            | no       | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.HTTP.Domain``                            | string   | domain name                                                  | no       | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Time``                                   | time     | request time                                                 | no       | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Dialect``                                | string   | "S3" or "Swift"                                              | no       | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Id``                                     | string   | request Id                                                   | no       | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.TransactionId``                          | string   | transaction Id                                               | no       | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+| ``Request.Tags``                                   | table    | object tags map                                              | yes      | no        | no       |
++----------------------------------------------------+----------+--------------------------------------------------------------+----------+-----------+----------+
+
+Request Functions
+--------------------
+Operations Log
+~~~~~~~~~~~~~~
+The ``Request.Log()`` function prints the requests into the operations log. This function has no parameters. It returns 0 for success and an error code if it fails.
+
+Lua Code Samples
+----------------
+- Print information on source and destination objects in case of copy:
+
+.. code-block:: lua
+  function print_object(object)
+    RGWDebugLog("  Name: " .. object.Name)
+    RGWDebugLog("  Instance: " .. object.Instance)
+    RGWDebugLog("  Id: " .. object.Id)
+    RGWDebugLog("  Size: " .. object.Size)
+    RGWDebugLog("  MTime: " .. object.MTime)
+  end
+
+  if Request.CopyFrom and Request.Object and Request.CopyFrom.Object then
+    RGWDebugLog("copy from object:")
+    print_object(Request.CopyFrom.Object)
+    RGWDebugLog("to object:")
+    print_object(Request.Object)
+  end
+
+- Print ACLs via a "generic function":
+
+.. code-block:: lua
+
+  function print_owner(owner)
+    RGWDebugLog("Owner:")
+    RGWDebugLog("  Dispaly Name: " .. owner.DisplayName)
+    RGWDebugLog("  Id: " .. owner.User.Id)
+    RGWDebugLog("  Tenanet: " .. owner.User.Tenant)
+  end
+
+  function print_acl(acl_type)
+    index = acl_type .. "ACL"
+    acl = Request[index]
+    if acl then
+      RGWDebugLog(acl_type .. "ACL Owner")
+      print_owner(acl.Owner)
+      RGWDebugLog("  there are " .. #acl.Grants .. " grant for owner")
+      for k,v in pairs(acl.Grants) do
+        RGWDebugLog("    Grant Key: " .. k)
+        RGWDebugLog("    Grant Type: " .. v.Type)
+        RGWDebugLog("    Grant Group Type: " .. v.GroupType)
+        RGWDebugLog("    Grant Referer: " .. v.Referer)
+        RGWDebugLog("    Grant User Tenant: " .. v.User.Tenant)
+        RGWDebugLog("    Grant User Id: " .. v.User.Id)
+      end
+    else
+      RGWDebugLog("no " .. acl_type .. " ACL in request: " .. Request.Id)
+    end 
+  end
+
+  print_acl("User")
+  print_acl("Bucket")
+  print_acl("Object")
+
+- Use of operations log only in case of errors:
+
+.. code-block:: lua
+
+  
+  if Request.Response.HTTPStatusCode ~= 200 then
+    RGWDebugLog("request is bad, use ops log")
+    rc = Request.Log()
+    RGWDebugLog("ops log return code: " .. rc)
+  end
+
+- Set values into the error message:
+
+.. code-block:: lua
+
+  if Request.Response.HTTPStatusCode == 500 then
+    Request.Response.Message = "<Message> something bad happened :-( </Message>"
+  end
+
index 902474ce209707c4ecaebc1dfd065c0d362ae334..7d4523d55a3dbb91a2024872fdf9cc00b8e4c947 100644 (file)
@@ -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})
index c1a68845d8e848c70fff9e90acd933fa6627671f..ad09eee164565dc265c1ed9c5002808f242ce6e3 100644 (file)
@@ -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<std::string, ACLGrant>;
+
 class RGWAccessControlList
 {
 protected:
@@ -304,7 +307,7 @@ protected:
   map<string, int> acl_user_map;
   map<uint32_t, int> acl_group_map;
   list<ACLReferer> referer_list;
-  multimap<string, ACLGrant> grant_map;
+  ACLGrantMap grant_map;
   void _add_grant(ACLGrant *grant);
 public:
   explicit RGWAccessControlList(CephContext *_cct) : cct(_cct) {}
@@ -342,7 +345,7 @@ public:
     if (struct_v >= 2) {
       decode(acl_group_map, bl);
     } else if (!maps_initialized) {
-      multimap<string, ACLGrant>::iterator iter;
+      ACLGrantMap::iterator iter;
       for (iter = grant_map.begin(); iter != grant_map.end(); ++iter) {
         ACLGrant& grant = iter->second;
         _add_grant(&grant);
@@ -359,8 +362,8 @@ public:
   void add_grant(ACLGrant *grant);
   void remove_canon_user_grant(rgw_user& user_id);
 
-  multimap<string, ACLGrant>& get_grant_map() { return grant_map; }
-  const multimap<string, ACLGrant>& get_grant_map() const { return grant_map; }
+  ACLGrantMap& get_grant_map() { return grant_map; }
+  const ACLGrantMap& get_grant_map() const { return grant_map; }
 
   void create_default(const rgw_user& id, string name) {
     acl_user_map.clear();
@@ -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);
 };
index b32561867256c57bb894c2bb612001ed15e27dd1..77aca6864ff027bc4dd634547f25e43c13563fe0 100644 (file)
@@ -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>         tenant name\n";
   cout << "   --user_ns=<namespace>     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<std::string> str_script_ctx;
+
   std::optional<string> opt_group_id;
   std::optional<string> opt_status;
   std::optional<string> 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;
 }
+
index 3c97553ed707368e06593912fb9d5f70fa1ed5d5..8658a0020ff03d175e7ebdcbc86db8907f9d0776 100644 (file)
@@ -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 (file)
index 0000000..cc2c5aa
--- /dev/null
@@ -0,0 +1,139 @@
+#include <lua.hpp>
+#include "services/svc_zone.h"
+#include "services/svc_sys_obj.h"
+#include "common/dout.h"
+#include "rgw_lua_utils.h"
+#include "rgw_sal_rados.h"
+#include "rgw_lua.h"
+
+#define dout_subsys ceph_subsys_rgw
+
+namespace rgw::lua {
+
+context to_context(const std::string& s) 
+{
+  if (strcasecmp(s.c_str(), "prerequest") == 0) {
+    return context::preRequest;
+  }
+  if (strcasecmp(s.c_str(), "postrequest") == 0) {
+    return context::postRequest;
+  }
+  return context::none;
+}
+
+std::string to_string(context ctx) 
+{
+  switch (ctx) {
+    case context::preRequest:
+      return "prerequest";
+    case context::postRequest:
+      return "postrequest";
+    case context::none:
+      break;
+  }
+  return "none";
+}
+
+bool verify(const std::string& script, std::string& err_msg) 
+{
+  lua_State *L = luaL_newstate();
+  lua_state_guard guard(L);
+  luaL_openlibs(L);
+  try {
+    if (luaL_loadstring(L, script.c_str()) != LUA_OK) {
+      err_msg.assign(lua_tostring(L, -1));
+      return false;
+    }
+  } catch (const std::runtime_error& e) {
+    err_msg = e.what();
+    return false;
+  }
+  err_msg = "";
+  return true;
+}
+
+static const std::string SCRIPT_OID_PREFIX("script.");
+
+int read_script(rgw::sal::RGWRadosStore* store, const std::string& tenant, optional_yield y, context ctx, std::string& script)
+{
+  RGWSysObjectCtx obj_ctx(store->svc()->sysobj->init_obj_ctx());
+  RGWObjVersionTracker objv_tracker;
+
+  const auto script_oid = SCRIPT_OID_PREFIX + to_string(ctx) + tenant;
+  rgw_raw_obj obj(store->svc()->zone->get_zone_params().log_pool, script_oid);
+
+  bufferlist bl;
+  
+  const auto rc = rgw_get_system_obj(
+      obj_ctx,
+      obj.pool, 
+      obj.oid,
+      bl,
+      &objv_tracker,
+      nullptr, 
+      y, 
+      nullptr, 
+      nullptr);
+
+  if (rc < 0) {
+    return rc;
+  }
+
+  auto iter = bl.cbegin();
+  try {
+    ceph::decode(script, iter);
+  } catch (buffer::error& err) {
+    return -EIO;
+  }
+
+  return 0;
+}
+
+int write_script(rgw::sal::RGWRadosStore* store, const std::string& tenant, optional_yield y, context ctx, const std::string& script)
+{
+  RGWSysObjectCtx obj_ctx(store->svc()->sysobj->init_obj_ctx());
+  RGWObjVersionTracker objv_tracker;
+
+  const auto script_oid = SCRIPT_OID_PREFIX + to_string(ctx) + tenant;
+  rgw_raw_obj obj(store->svc()->zone->get_zone_params().log_pool, script_oid);
+
+  bufferlist bl;
+  ceph::encode(script, bl);
+
+  const auto rc = rgw_put_system_obj(
+      obj_ctx, 
+      obj.pool, 
+      obj.oid,
+      bl,
+      false, 
+      &objv_tracker,
+      real_time());
+
+  if (rc < 0) {
+    return rc;
+  }
+
+  return 0;
+}
+
+int delete_script(rgw::sal::RGWRadosStore* store, const std::string& tenant, optional_yield y, context ctx)
+{
+  RGWObjVersionTracker objv_tracker;
+
+  const auto script_oid = SCRIPT_OID_PREFIX + to_string(ctx) + tenant;
+  rgw_raw_obj obj(store->svc()->zone->get_zone_params().log_pool, script_oid);
+
+  const auto rc = rgw_delete_system_obj(
+      store->svc()->sysobj, 
+      obj.pool, 
+      obj.oid,
+      &objv_tracker);
+
+  if (rc < 0 && rc != -ENOENT) {
+    return rc;
+  }
+
+  return 0;
+}
+}
+
diff --git a/src/rgw/rgw_lua.h b/src/rgw/rgw_lua.h
new file mode 100644 (file)
index 0000000..9ba9171
--- /dev/null
@@ -0,0 +1,38 @@
+#pragma once
+
+#include <string>
+#include "common/async/yield_context.h"
+
+class lua_State;
+class rgw_user;
+namespace rgw::sal {
+  class RGWRadosStore;
+}
+
+namespace rgw::lua {
+
+enum class context {
+  preRequest,
+  postRequest,
+  none
+};
+
+// get context enum from string 
+// the expected string the same as the enum (case insensitive)
+// return "none" if not matched
+context to_context(const std::string& s);
+
+// verify a lua script
+bool verify(const std::string& script, std::string& err_msg);
+
+// store a lua script in a context
+int write_script(rgw::sal::RGWRadosStore* store, const std::string& tenant, optional_yield y, context ctx, const std::string& script);
+
+// read the stored lua script from a context
+int read_script(rgw::sal::RGWRadosStore* store, const std::string& tenant, optional_yield y, context ctx, std::string& script);
+
+// delete the stored lua script from a context
+int delete_script(rgw::sal::RGWRadosStore* store, const std::string& tenant, optional_yield y, context ctx);
+
+}
+
diff --git a/src/rgw/rgw_lua_request.cc b/src/rgw/rgw_lua_request.cc
new file mode 100644 (file)
index 0000000..ea7940d
--- /dev/null
@@ -0,0 +1,817 @@
+#include <sstream>
+#include <stdexcept>
+#include <lua.hpp>
+#include "common/dout.h"
+#include "services/svc_zone.h"
+#include "rgw_lua_utils.h"
+#include "rgw_common.h"
+#include "rgw_log.h"
+#include "rgw_process.h"
+#include "rgw_zone.h"
+#include "rgw_acl.h"
+#include "rgw_sal_rados.h"
+
+#define dout_subsys ceph_subsys_rgw
+
+namespace rgw::lua::request {
+
+// closure that perform ops log action
+// e.g.
+//    Request.Log()
+//
+constexpr const char* RequestLogAction{"Log"};
+
+int RequestLog(lua_State* L) 
+{
+  const auto store = reinterpret_cast<rgw::sal::RGWRadosStore*>(lua_touserdata(L, lua_upvalueindex(1)));
+  const auto rest = reinterpret_cast<RGWREST*>(lua_touserdata(L, lua_upvalueindex(2)));
+  const auto olog = reinterpret_cast<OpsLogSocket*>(lua_touserdata(L, lua_upvalueindex(3)));
+  const auto s = reinterpret_cast<req_state*>(lua_touserdata(L, lua_upvalueindex(4)));
+  const std::string op_name(reinterpret_cast<const char*>(lua_touserdata(L, lua_upvalueindex(5))));
+  if (store && s) {
+    const auto rc = rgw_log_op(store->getRados(), rest, s, op_name, olog);
+    lua_pushinteger(L, rc);
+  } else {
+    ldout(s->cct, 1) << "Lua ERROR: missing rados store, cannot use ops log"  << dendl;
+    lua_pushinteger(L, -EINVAL);
+  }
+
+  return ONE_RETURNVAL;
+}
+
+struct ResponseMetaTable : public EmptyMetaTable {
+  static std::string TableName() {return "Response";} 
+  static std::string Name() {return TableName() + "Meta";}
+
+  static int IndexClosure(lua_State* L) {
+    const auto err = reinterpret_cast<const rgw_err*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+    ceph_assert(lua_isstring(L, -1));
+    const char* index = lua_tostring(L, -1);
+
+    if (strcasecmp(index, "HTTPStatusCode") == 0) {
+      lua_pushinteger(L, err->http_ret);
+    } else if (strcasecmp(index, "RGWCode") == 0) {
+      lua_pushinteger(L, err->ret);
+    } else if (strcasecmp(index, "HTTPStatus") == 0) {
+      lua_pushstring(L, err->err_code.c_str());
+    } else if (strcasecmp(index, "Message") == 0) {
+      lua_pushstring(L, err->message.c_str());
+    } else {
+      throw_unknown_field(index, TableName());
+    }
+    return ONE_RETURNVAL;
+  }
+  
+  static int NewIndexClosure(lua_State* L) {
+    auto err = reinterpret_cast<rgw_err*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+    ceph_assert(lua_isstring(L, -2));
+    const char* index = lua_tostring(L, -2);
+
+    if (strcasecmp(index, "HTTPStatusCode") == 0) {
+      err->http_ret = lua_tointeger(L, -1);
+    } else if (strcasecmp(index, "RGWCode") == 0) {
+      err->ret = lua_tointeger(L, -1);
+    } else if (strcasecmp(index, "HTTPStatus") == 0) {
+      err->err_code.assign(lua_tostring(L, -1));
+    } else if (strcasecmp(index, "Message") == 0) {
+      err->message.assign(lua_tostring(L, -1));
+    } else {
+      throw_unknown_field(index, TableName());
+    }
+    return ONE_RETURNVAL;
+  }
+};
+
+struct QuotaMetaTable : public EmptyMetaTable {
+  static std::string TableName() {return "Quota";}
+  static std::string Name() {return TableName() + "Meta";}
+
+  static int IndexClosure(lua_State* L) {
+    const auto info = reinterpret_cast<RGWQuotaInfo*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+    ceph_assert(lua_isstring(L, -1));
+    const char* index = lua_tostring(L, -1);
+
+    if (strcasecmp(index, "MaxSize") == 0) {
+      lua_pushinteger(L, info->max_size);
+    } else if (strcasecmp(index, "MaxObjects") == 0) {
+      lua_pushinteger(L, info->max_objects);
+    } else if (strcasecmp(index, "Enabled") == 0) {
+      lua_pushboolean(L, info->enabled);
+    } else if (strcasecmp(index, "Rounded") == 0) {
+      lua_pushboolean(L, !info->check_on_raw);
+    } else {
+      throw_unknown_field(index, TableName());
+    }
+    return ONE_RETURNVAL;
+  }
+};
+
+struct PlacementRuleMetaTable : public EmptyMetaTable {
+  static std::string TableName() {return "PlacementRule";}
+  static std::string Name() {return TableName() + "Meta";}
+
+  static int IndexClosure(lua_State* L) {
+    const auto rule = reinterpret_cast<rgw_placement_rule*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+    ceph_assert(lua_isstring(L, -1));
+    const char* index = lua_tostring(L, -1);
+
+    if (strcasecmp(index, "Name") == 0) {
+      lua_pushstring(L, rule->name.c_str());
+    } else if (strcasecmp(index, "StorageClass") == 0) {
+      lua_pushstring(L, rule->storage_class.c_str());
+    } else {
+      throw_unknown_field(index, TableName());
+    }
+    return ONE_RETURNVAL;
+  }
+};
+
+struct UserMetaTable : public EmptyMetaTable {
+  static std::string TableName() {return "User";}
+  static std::string Name() {return TableName() + "Meta";}
+
+  static int IndexClosure(lua_State* L) {
+    const auto user = reinterpret_cast<const rgw_user*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+    ceph_assert(lua_isstring(L, -1));
+    const char* index = lua_tostring(L, -1);
+
+    if (strcasecmp(index, "Tenant") == 0) {
+      lua_pushstring(L, user->tenant);
+    } else if (strcasecmp(index, "Id") == 0) {
+      lua_pushstring(L, user->id);
+    } else {
+      throw_unknown_field(index, TableName());
+    }
+    return ONE_RETURNVAL;
+  }
+};
+
+struct OwnerMetaTable : public EmptyMetaTable {
+  static std::string TableName() {return "Owner";}
+  static std::string Name() {return TableName() + "Meta";}
+
+  static int IndexClosure(lua_State* L) {
+    const auto owner = reinterpret_cast<ACLOwner*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+    ceph_assert(lua_isstring(L, -1));
+    const char* index = lua_tostring(L, -1);
+
+    if (strcasecmp(index, "DisplayName") == 0) {
+      lua_pushstring(L, owner->get_display_name());
+    } else if (strcasecmp(index, "User") == 0) {
+      create_metatable<UserMetaTable>(L, false, &(owner->get_id()));
+    } else {
+      throw_unknown_field(index, TableName());
+    }
+    return ONE_RETURNVAL;
+  }
+};
+
+struct BucketMetaTable : public EmptyMetaTable {
+  static std::string TableName() {return "Bucket";}
+  static std::string Name() {return TableName() + "Meta";}
+
+  using Type = rgw::sal::RGWBucket;
+
+  static int IndexClosure(lua_State* L) {
+    const auto bucket = reinterpret_cast<Type*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+    ceph_assert(lua_isstring(L, -1));
+    const char* index = lua_tostring(L, -1);
+
+    if (strcasecmp(index, "Tenant") == 0) {
+      lua_pushstring(L, bucket->get_tenant());
+    } else if (strcasecmp(index, "Name") == 0) {
+      lua_pushstring(L, bucket->get_name());
+    } else if (strcasecmp(index, "Marker") == 0) {
+      lua_pushstring(L, bucket->get_marker());
+    } else if (strcasecmp(index, "Id") == 0) {
+      lua_pushstring(L, bucket->get_bucket_id());
+    } else if (strcasecmp(index, "Count") == 0) {
+      lua_pushinteger(L, bucket->get_count());
+    } else if (strcasecmp(index, "Size") == 0) {
+      lua_pushinteger(L, bucket->get_size());
+    } else if (strcasecmp(index, "ZoneGroupId") == 0) {
+      lua_pushstring(L, bucket->get_info().zonegroup);
+    } else if (strcasecmp(index, "CreationTime") == 0) {
+      lua_pushtime(L, bucket->get_creation_time());
+    } else if (strcasecmp(index, "MTime") == 0) {
+      lua_pushtime(L, bucket->get_modification_time());
+    } else if (strcasecmp(index, "Quota") == 0) {
+      create_metatable<QuotaMetaTable>(L, false, &(bucket->get_info().quota));
+    } else if (strcasecmp(index, "PlacementRule") == 0) {
+      create_metatable<PlacementRuleMetaTable>(L, false, &(bucket->get_info().placement_rule));
+    } else if (strcasecmp(index, "User") == 0) {
+      create_metatable<UserMetaTable>(L, false, &(bucket->get_info().owner));
+    } else {
+      throw_unknown_field(index, TableName());
+    }
+    return ONE_RETURNVAL;
+  }
+};
+
+struct ObjectMetaTable : public EmptyMetaTable {
+  static const std::string TableName() {return "Object";}
+  static std::string Name() {return TableName() + "Meta";}
+  
+  using Type = rgw::sal::RGWObject;
+
+  static int IndexClosure(lua_State* L) {
+    const auto obj = reinterpret_cast<const Type*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+    ceph_assert(lua_isstring(L, -1));
+    const char* index = lua_tostring(L, -1);
+
+    if (strcasecmp(index, "Name") == 0) {
+      lua_pushstring(L, obj->get_name().c_str());
+    } else if (strcasecmp(index, "Instance") == 0) {
+      lua_pushstring(L, obj->get_instance().c_str());
+    } else if (strcasecmp(index, "Id") == 0) {
+      lua_pushstring(L, obj->get_oid());
+    } else if (strcasecmp(index, "Size") == 0) {
+      lua_pushinteger(L, obj->get_obj_size());
+    } else if (strcasecmp(index, "MTime") == 0) {
+      lua_pushtime(L, obj->get_mtime());
+    } else {
+      throw_unknown_field(index, TableName());
+    }
+    return ONE_RETURNVAL;
+  }
+};
+
+template<typename MapType=std::map<std::string, std::string>>
+struct StringMapMetaTable : public EmptyMetaTable {
+
+  static std::string TableName() {return "StringMap";}
+  static std::string Name() {return TableName() + "Meta";}
+
+  static int IndexClosure(lua_State* L) {
+    const auto map = reinterpret_cast<MapType*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+    ceph_assert(lua_isstring(L, -1));
+    const char* index = lua_tostring(L, -1);
+
+    const auto it = map->find(std::string(index));
+    if (it == map->end()) {
+      lua_pushnil(L);
+    } else {
+      lua_pushstring(L, it->second);
+    }
+    return ONE_RETURNVAL;
+  }
+
+  static int PairsClosure(lua_State* L) {
+    auto map = reinterpret_cast<MapType*>(lua_touserdata(L, lua_upvalueindex(1)));
+    ceph_assert(map);
+    lua_pushlightuserdata(L, map);
+    lua_pushcclosure(L, stateless_iter, ONE_UPVAL); // push the stateless iterator function
+    lua_pushnil(L);                                 // indicate this is the first call
+    // return stateless_iter, nil
+
+    return TWO_RETURNVALS;
+  }
+  
+  static int stateless_iter(lua_State* L) {
+    // based on: http://lua-users.org/wiki/GeneralizedPairsAndIpairs
+    auto map = reinterpret_cast<MapType*>(lua_touserdata(L, lua_upvalueindex(1)));
+    typename MapType::const_iterator next_it;
+    if (lua_isnil(L, -1)) {
+      next_it = map->begin();
+    } else {
+      ceph_assert(lua_isstring(L, -1));
+      const char* index = lua_tostring(L, -1);
+      const auto it = map->find(std::string(index));
+      ceph_assert(it != map->end());
+      next_it = std::next(it);
+    }
+
+    if (next_it == map->end()) {
+      // index of the last element was provided
+      lua_pushnil(L);
+      lua_pushnil(L);
+      // return nil, nil
+    } else {
+      lua_pushstring(L, next_it->first);
+      lua_pushstring(L, next_it->second);
+      // return key, value
+    }
+
+    return TWO_RETURNVALS;
+  }
+
+  static int LenClosure(lua_State* L) {
+    const auto map = reinterpret_cast<MapType*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+    lua_pushinteger(L, map->size());
+
+    return ONE_RETURNVAL;
+  }
+};
+
+struct GrantMetaTable : public EmptyMetaTable {
+  static std::string TableName() {return "Grant";}
+  static std::string Name() {return TableName() + "Meta";}
+
+  static int IndexClosure(lua_State* L) {
+    const auto grant = reinterpret_cast<ACLGrant*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+    ceph_assert(lua_isstring(L, -1));
+    const char* index = lua_tostring(L, -1);
+
+    if (strcasecmp(index, "Type") == 0) {
+      lua_pushinteger(L, grant->get_type().get_type());
+    } else if (strcasecmp(index, "User") == 0) {
+      rgw_user id;
+      if (grant->get_id(id)) {
+        create_metatable<UserMetaTable>(L, false, &id);
+      } else {
+        lua_pushnil(L);
+      }
+    } else if (strcasecmp(index, "Permission") == 0) {
+      lua_pushinteger(L, grant->get_permission().get_permissions());
+    } else if (strcasecmp(index, "GroupType") == 0) {
+      lua_pushinteger(L, grant->get_group());
+    } else if (strcasecmp(index, "Referer") == 0) {
+      lua_pushstring(L, grant->get_referer());
+    } else {
+      throw_unknown_field(index, TableName());
+    }
+    return ONE_RETURNVAL;
+  }
+};
+
+struct GrantsMetaTable : public EmptyMetaTable {
+  static std::string TableName() {return "Grants";}
+  static std::string Name() {return TableName() + "Meta";}
+
+  static int IndexClosure(lua_State* L) {
+    const auto map = reinterpret_cast<ACLGrantMap*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+    ceph_assert(lua_isstring(L, -1));
+    const char* index = lua_tostring(L, -1);
+
+    const auto it = map->find(std::string(index));
+    if (it == map->end()) {
+      lua_pushnil(L);
+    } else {
+      create_metatable<GrantMetaTable>(L, false, &(it->second));
+    }
+    return ONE_RETURNVAL;
+  }
+  
+  static int PairsClosure(lua_State* L) {
+    auto map = reinterpret_cast<ACLGrantMap*>(lua_touserdata(L, lua_upvalueindex(1)));
+    ceph_assert(map);
+    lua_pushlightuserdata(L, map);
+    lua_pushcclosure(L, stateless_iter, ONE_UPVAL); // push the stateless iterator function
+    lua_pushnil(L);                                 // indicate this is the first call
+    // return stateless_iter, nil
+
+    return TWO_RETURNVALS;
+  }
+  
+  static int stateless_iter(lua_State* L) {
+    // based on: http://lua-users.org/wiki/GeneralizedPairsAndIpairs
+    auto map = reinterpret_cast<ACLGrantMap*>(lua_touserdata(L, lua_upvalueindex(1)));
+    ACLGrantMap::iterator next_it;
+    if (lua_isnil(L, -1)) {
+      next_it = map->begin();
+    } else {
+      ceph_assert(lua_isstring(L, -1));
+      const char* index = lua_tostring(L, -1);
+      const auto it = map->find(std::string(index));
+      ceph_assert(it != map->end());
+      next_it = std::next(it);
+    }
+
+    if (next_it == map->end()) {
+      // index of the last element was provided
+      lua_pushnil(L);
+      lua_pushnil(L);
+      return TWO_RETURNVALS;
+      // return nil, nil
+    }
+
+    while (next_it->first.empty()) {
+      // this is a multimap and the next element does not have a unique key
+      ++next_it;
+      if (next_it == map->end()) {
+        // index of the last element was provided
+        lua_pushnil(L);
+        lua_pushnil(L);
+        return TWO_RETURNVALS;
+        // return nil, nil
+      }
+    }
+
+    lua_pushstring(L, next_it->first);
+    create_metatable<GrantMetaTable>(L, false, &(next_it->second));
+    // return key, value
+    
+    return TWO_RETURNVALS;
+  }
+
+  static int LenClosure(lua_State* L) {
+    const auto map = reinterpret_cast<ACLGrantMap*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+    lua_pushinteger(L, map->size());
+
+    return ONE_RETURNVAL;
+  }
+};
+
+struct ACLMetaTable : public EmptyMetaTable {
+  static std::string TableName() {return "ACL";}
+  static std::string Name() {return TableName() + "Meta";}
+
+  using Type = RGWAccessControlPolicy;
+
+  static int IndexClosure(lua_State* L) {
+    const auto acl = reinterpret_cast<Type*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+    ceph_assert(lua_isstring(L, -1));
+    const char* index = lua_tostring(L, -1);
+
+    if (strcasecmp(index, "Owner") == 0) {
+      create_metatable<OwnerMetaTable>(L, false, &(acl->get_owner()));
+    } else if (strcasecmp(index, "Grants") == 0) {
+      create_metatable<GrantsMetaTable>(L, false, &(acl->get_acl().get_grant_map()));
+    } else {
+      throw_unknown_field(index, TableName());
+    }
+    return ONE_RETURNVAL;
+  }
+};
+
+struct StatementsMetaTable : public EmptyMetaTable {
+  static std::string TableName() {return "Statements";}
+  static std::string Name() {return TableName() + "Meta";}
+
+  using Type = std::vector<rgw::IAM::Statement>;
+
+  static std::string statement_to_string(const rgw::IAM::Statement& statement) {
+    std::stringstream ss;
+    ss << statement;
+    return ss.str();
+  }
+
+  static int IndexClosure(lua_State* L) {
+    const auto statements = reinterpret_cast<Type*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+    const auto index = lua_tointeger(L, -1);
+
+    if (index >= (int)statements->size() || index < 0) {
+      lua_pushnil(L);
+    } else {
+      // TODO: policy language could be interpreted to lua and executed as such
+      lua_pushstring(L, statement_to_string((*statements)[index]));
+    }
+    return ONE_RETURNVAL;
+  }
+  
+  static int PairsClosure(lua_State* L) {
+    auto statements = reinterpret_cast<Type*>(lua_touserdata(L, lua_upvalueindex(1)));
+    ceph_assert(statements);
+    lua_pushlightuserdata(L, statements);
+    lua_pushcclosure(L, stateless_iter, ONE_UPVAL); // push the stateless iterator function
+    lua_pushnil(L);                                 // indicate this is the first call
+    // return stateless_iter, nil
+
+    return TWO_RETURNVALS;
+  }
+  
+  static int stateless_iter(lua_State* L) {
+    auto statements = reinterpret_cast<Type*>(lua_touserdata(L, lua_upvalueindex(1)));
+    size_t next_it;
+    if (lua_isnil(L, -1)) {
+      next_it = 0;
+    } else {
+      ceph_assert(lua_isinteger(L, -1));
+      const auto it = lua_tointeger(L, -1);
+      next_it = it+1;
+    }
+
+    if (next_it >= statements->size()) {
+      // index of the last element was provided
+      lua_pushnil(L);
+      lua_pushnil(L);
+      // return nil, nil
+    } else {
+      lua_pushinteger(L, next_it);
+      lua_pushstring(L, statement_to_string((*statements)[next_it]));
+      // return key, value
+    }
+
+    return TWO_RETURNVALS;
+  }
+
+  static int LenClosure(lua_State* L) {
+    const auto statements = reinterpret_cast<Type*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+    lua_pushinteger(L, statements->size());
+
+    return ONE_RETURNVAL;
+  }
+};
+
+struct PolicyMetaTable : public EmptyMetaTable {
+  static std::string TableName() {return "Policy";}
+  static std::string Name() {return TableName() + "Meta";}
+
+  static int IndexClosure(lua_State* L) {
+    const auto policy = reinterpret_cast<rgw::IAM::Policy*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+    ceph_assert(lua_isstring(L, -1));
+    const char* index = lua_tostring(L, -1);
+
+    if (strcasecmp(index, "Text") == 0) {
+      lua_pushstring(L, policy->text);
+    } else if (strcasecmp(index, "Id") == 0) {
+      // TODO create lua_pushstring for std::unique_ptr
+      if (!policy->id) {
+        lua_pushnil(L);
+      } else {
+        lua_pushstring(L, policy->id.get());
+      }
+    } else if (strcasecmp(index, "Statements") == 0) {
+      create_metatable<StatementsMetaTable>(L, &(policy->statements));
+    } else {
+      throw_unknown_field(index, TableName());
+    }
+    return ONE_RETURNVAL;
+  }
+};
+
+struct PoliciesMetaTable : public EmptyMetaTable {
+  static std::string TableName() {return "Policies";}
+  static std::string Name() {return TableName() + "Meta";}
+  
+  using Type = std::vector<rgw::IAM::Policy>;
+
+  static int IndexClosure(lua_State* L) {
+    const auto policies = reinterpret_cast<Type*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+    const auto index = lua_tointeger(L, -1);
+
+    if (index >= (int)policies->size() || index < 0) {
+      lua_pushnil(L);
+    } else {
+      create_metatable<PolicyMetaTable>(L, false, &((*policies)[index]));
+    }
+    return ONE_RETURNVAL;
+  }
+  
+  static int PairsClosure(lua_State* L) {
+    auto policies = reinterpret_cast<Type*>(lua_touserdata(L, lua_upvalueindex(1)));
+    ceph_assert(policies);
+    lua_pushlightuserdata(L, policies);
+    lua_pushcclosure(L, stateless_iter, ONE_UPVAL); // push the stateless iterator function
+    lua_pushnil(L);                                 // indicate this is the first call
+    // return stateless_iter, nil
+
+    return TWO_RETURNVALS;
+  }
+  
+  static int stateless_iter(lua_State* L) {
+    auto policies = reinterpret_cast<Type*>(lua_touserdata(L, lua_upvalueindex(1)));
+    size_t next_it;
+    if (lua_isnil(L, -1)) {
+      next_it = 0;
+    } else {
+      ceph_assert(lua_isinteger(L, -1));
+      const auto it = lua_tointeger(L, -1);
+      next_it = it+1;
+    }
+
+    if (next_it >= policies->size()) {
+      // index of the last element was provided
+      lua_pushnil(L);
+      lua_pushnil(L);
+      // return nil, nil
+    } else {
+      lua_pushinteger(L, next_it);
+      create_metatable<PolicyMetaTable>(L, false, &((*policies)[next_it]));
+      // return key, value
+    }
+
+    return TWO_RETURNVALS;
+  }
+
+  static int LenClosure(lua_State* L) {
+    const auto policies = reinterpret_cast<Type*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+    lua_pushinteger(L, policies->size());
+
+    return ONE_RETURNVAL;
+  }
+};
+
+struct HTTPMetaTable : public EmptyMetaTable {
+  static std::string TableName() {return "HTTP";}
+  static std::string Name() {return TableName() + "Meta";}
+
+  static int IndexClosure(lua_State* L) {
+    const auto info = reinterpret_cast<req_info*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+    ceph_assert(lua_isstring(L, -1));
+    const char* index = lua_tostring(L, -1);
+
+    if (strcasecmp(index, "Parameters") == 0) {
+      create_metatable<StringMapMetaTable<>>(L, false, &(info->args.get_params()));
+    } else if (strcasecmp(index, "Resources") == 0) {
+      // TODO: add non-const api to get resources
+      create_metatable<StringMapMetaTable<>>(L, false, 
+          const_cast<std::map<std::string, std::string>*>(&(info->args.get_sub_resources())));
+    } else if (strcasecmp(index, "Metadata") == 0) {
+      create_metatable<StringMapMetaTable<meta_map_t>>(L, false, &(info->x_meta_map));
+    } else if (strcasecmp(index, "Host") == 0) {
+      lua_pushstring(L, info->host);
+    } else if (strcasecmp(index, "Method") == 0) {
+      lua_pushstring(L, info->method);
+    } else if (strcasecmp(index, "URI") == 0) {
+      lua_pushstring(L, info->request_uri);
+    } else if (strcasecmp(index, "QueryString") == 0) {
+      lua_pushstring(L, info->request_params);
+    } else if (strcasecmp(index, "Domain") == 0) {
+      lua_pushstring(L, info->domain);
+    } else {
+      throw_unknown_field(index, TableName());
+    }
+    return ONE_RETURNVAL;
+  }
+};
+
+struct CopyFromMetaTable : public EmptyMetaTable {
+  static std::string TableName() {return "CopyFrom";}
+  static std::string Name() {return TableName() + "Meta";}
+
+  static int IndexClosure(lua_State* L) {
+    const auto s = reinterpret_cast<req_state*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+    ceph_assert(lua_isstring(L, -1));
+    const char* index = lua_tostring(L, -1);
+
+    if (strcasecmp(index, "Tenant") == 0) {
+      lua_pushstring(L, s->src_tenant_name);
+    } else if (strcasecmp(index, "Bucket") == 0) {
+      lua_pushstring(L, s->src_bucket_name);
+    } else if (strcasecmp(index, "Object") == 0) {
+      create_metatable<ObjectMetaTable>(L, false, s->src_object);
+    } else {
+      throw_unknown_field(index, TableName());
+    }
+    return ONE_RETURNVAL;
+  }
+};
+
+struct ZoneGroupMetaTable : public EmptyMetaTable {
+  static std::string TableName() {return "ZoneGroup";}
+  static std::string Name() {return TableName() + "Meta";}
+
+  static int IndexClosure(lua_State* L) {
+    const auto s = reinterpret_cast<req_state*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+    ceph_assert(lua_isstring(L, -1));
+    const char* index = lua_tostring(L, -1);
+
+    if (strcasecmp(index, "Name") == 0) {
+      lua_pushstring(L, s->zonegroup_name);
+    } else if (strcasecmp(index, "Endpoint") == 0) {
+      lua_pushstring(L, s->zonegroup_endpoint);
+    } else {
+      throw_unknown_field(index, TableName());
+    }
+    return ONE_RETURNVAL;
+  }
+};
+
+struct RequestMetaTable : public EmptyMetaTable {
+  static std::string TableName() {return "Request";}
+  static std::string Name() {return TableName() + "Meta";}
+
+  // __index closure that expect req_state to be captured
+  static int IndexClosure(lua_State* L) {
+    const auto s = reinterpret_cast<req_state*>(lua_touserdata(L, lua_upvalueindex(1)));
+    const auto op_name = reinterpret_cast<const char*>(lua_touserdata(L, lua_upvalueindex(2)));
+
+    ceph_assert(lua_isstring(L, -1));
+    const char* index = lua_tostring(L, -1);
+
+    if (strcasecmp(index, "RGWOp") == 0) {
+      lua_pushstring(L, op_name);
+    } else if (strcasecmp(index, "DecodedURI") == 0) {
+      lua_pushstring(L, s->decoded_uri);
+    } else if (strcasecmp(index, "ContentLength") == 0) {
+      lua_pushinteger(L, s->content_length);
+    } else if (strcasecmp(index, "GenericAttributes") == 0) {
+      create_metatable<StringMapMetaTable<>>(L, false, &(s->generic_attrs));
+    } else if (strcasecmp(index, "Response") == 0) {
+      create_metatable<ResponseMetaTable>(L, false, &(s->err));
+    } else if (strcasecmp(index, "SwiftAccountName") == 0) {
+      if (s->dialect == "swift") {
+        lua_pushstring(L, s->account_name);
+      } else {
+        lua_pushnil(L);
+      }
+    } else if (strcasecmp(index, "Bucket") == 0) {
+      create_metatable<BucketMetaTable>(L, false, s->bucket);
+    } else if (strcasecmp(index, "Object") == 0) {
+      create_metatable<ObjectMetaTable>(L, false, s->object);
+    } else if (strcasecmp(index, "CopyFrom") == 0) {
+      if (s->op_type == RGW_OP_COPY_OBJ) {
+        create_metatable<CopyFromMetaTable>(L, s);
+      } else {
+        lua_pushnil(L);
+      }
+    } else if (strcasecmp(index, "ObjectOwner") == 0) {
+      create_metatable<OwnerMetaTable>(L, false, &(s->owner));
+    } else if (strcasecmp(index, "ZoneGroup") == 0) {
+      create_metatable<ZoneGroupMetaTable>(L, false, s);
+    } else if (strcasecmp(index, "UserACL") == 0) {
+      create_metatable<ACLMetaTable>(L, false, s->user_acl);
+    } else if (strcasecmp(index, "BucketACL") == 0) {
+      create_metatable<ACLMetaTable>(L, false, s->bucket_acl);
+    } else if (strcasecmp(index, "ObjectACL") == 0) {
+      create_metatable<ACLMetaTable>(L, false, s->object_acl);
+    } else if (strcasecmp(index, "Environment") == 0) {
+        create_metatable<StringMapMetaTable<rgw::IAM::Environment>>(L, false, &(s->env));
+    } else if (strcasecmp(index, "Policy") == 0) {
+      // TODO: create a wrapper to std::optional
+      if (!s->iam_policy) {
+        lua_pushnil(L);
+      } else {
+        create_metatable<PolicyMetaTable>(L, false, s->iam_policy.get_ptr());
+      }
+    } else if (strcasecmp(index, "UserPolicies") == 0) {
+        create_metatable<PoliciesMetaTable>(L, false, &(s->iam_user_policies));
+    } else if (strcasecmp(index, "RGWId") == 0) {
+      lua_pushstring(L, s->host_id);
+    } else if (strcasecmp(index, "HTTP") == 0) {
+        create_metatable<HTTPMetaTable>(L, false, &(s->info));
+    } else if (strcasecmp(index, "Time") == 0) {
+      lua_pushtime(L, s->time);
+    } else if (strcasecmp(index, "Dialect") == 0) {
+      lua_pushstring(L, s->dialect);
+    } else if (strcasecmp(index, "Id") == 0) {
+      lua_pushstring(L, s->req_id);
+    } else if (strcasecmp(index, "TransactionId") == 0) {
+      lua_pushstring(L, s->trans_id);
+    } else if (strcasecmp(index, "Tags") == 0) {
+      create_metatable<StringMapMetaTable<RGWObjTags::tag_map_t>>(L, false, &(s->tagset.get_tags()));
+    } else {
+      throw_unknown_field(index, TableName());
+    }
+    return ONE_RETURNVAL;
+  }
+};
+
+int execute(
+    rgw::sal::RGWRadosStore* store,
+    RGWREST* rest,
+    OpsLogSocket* olog,
+    req_state* s, 
+    const char* op_name,
+    const std::string& script) 
+{
+  auto L = luaL_newstate();
+  lua_state_guard lguard(L);
+
+  luaL_openlibs(L);
+
+  create_debug_action(L, s->cct);  
+
+  create_metatable<RequestMetaTable>(L, true, s, const_cast<char*>(op_name));
+  
+  // add the ops log action
+  lua_getglobal(L, RequestMetaTable::TableName().c_str());
+  ceph_assert(lua_istable(L, -1));
+  lua_pushstring(L, RequestLogAction);
+  lua_pushlightuserdata(L, store);
+  lua_pushlightuserdata(L, rest);
+  lua_pushlightuserdata(L, olog);
+  lua_pushlightuserdata(L, s);
+  lua_pushlightuserdata(L, const_cast<char*>(op_name));
+  lua_pushcclosure(L, RequestLog, FIVE_UPVALS);
+  lua_rawset(L, -3);
+
+  try {
+    // execute the lua script
+    if (luaL_dostring(L, script.c_str()) != LUA_OK) {
+      const std::string err(lua_tostring(L, -1));
+      ldout(s->cct, 1) << "Lua ERROR: " << err << dendl;
+      return -1;
+    }
+  } catch (const std::runtime_error& e) {
+    ldout(s->cct, 1) << "Lua ERROR: " << e.what() << dendl;
+    return -1;
+  }
+
+  return 0;
+}
+
+}
diff --git a/src/rgw/rgw_lua_request.h b/src/rgw/rgw_lua_request.h
new file mode 100644 (file)
index 0000000..5ace5bf
--- /dev/null
@@ -0,0 +1,25 @@
+#pragma once
+
+#include <string>
+#include "include/common_fwd.h"
+
+class req_state;
+class RGWREST;
+class OpsLogSocket;
+namespace rgw::sal {
+  class RGWRadosStore;
+}
+
+namespace rgw::lua::request {
+
+// execute a lua script in the Request context
+int execute(
+    rgw::sal::RGWRadosStore* store,
+    RGWREST* rest,
+    OpsLogSocket* olog,
+    req_state *s, 
+    const char* op_name,
+    const std::string& script);
+
+}
+
diff --git a/src/rgw/rgw_lua_utils.cc b/src/rgw/rgw_lua_utils.cc
new file mode 100644 (file)
index 0000000..abb3555
--- /dev/null
@@ -0,0 +1,77 @@
+#include <string>
+#include <lua.hpp>
+#include "common/ceph_context.h"
+#include "common/dout.h"
+#include "rgw_lua_utils.h"
+
+#define dout_subsys ceph_subsys_rgw
+
+namespace rgw::lua {
+
+void lua_pushstring(lua_State* L, const std::string& str) 
+{
+  lua_pushstring(L, str.c_str());
+}
+
+// TODO - add the folowing generic functions
+// lua_push(lua_State* L, const std::string& str)
+// template<typename T> lua_push(lua_State* L, const std::optional<T>& val)
+// lua_push(lua_State* L, const ceph::real_time& tp)
+
+constexpr const char* RGWDebugLogAction{"RGWDebugLog"};
+
+int RGWDebugLog(lua_State* L) 
+{
+  auto cct = reinterpret_cast<CephContext*>(lua_touserdata(L, lua_upvalueindex(1)));
+
+  constexpr auto NUM_RETURN = 0;
+
+  if (!lua_isstring(L, -1)) {
+    if (cct) {
+      ldout(cct, 1) << "Lua ERROR: missing/invalid 'message' parameter when calling Log"  << dendl;
+    }
+    return NUM_RETURN;
+  }
+  const char* message = lua_tostring(L, -1);
+  if (cct) {
+    ldout(cct, 20) << "Lua INFO: " << message << dendl;
+  }
+
+  return NUM_RETURN;
+};
+
+void create_debug_action(lua_State* L, CephContext* cct) {
+  lua_pushlightuserdata(L, cct);
+  lua_pushcclosure(L, RGWDebugLog, ONE_UPVAL);
+  lua_setglobal(L, RGWDebugLogAction);
+}
+
+void stack_dump(lua_State* L) {
+       auto i = lua_gettop(L);
+  std::cout << std::endl << " ----------------  Stack Dump ----------------" << std::endl;
+  std::cout << "Stack Size: " << i << std::endl;
+  while (i > 0) {
+    const auto t = lua_type(L, i);
+    switch (t) {
+      case LUA_TNIL:
+        std::cout << i << ": nil" << std::endl;
+        break;
+      case LUA_TSTRING:
+        std::cout << i << ": " << lua_tostring(L, i) << std::endl;
+        break;
+      case LUA_TBOOLEAN:
+        std::cout << i << ": " << lua_toboolean(L, i) << std::endl;
+        break;
+      case LUA_TNUMBER:
+        std::cout << i << ": " << lua_tonumber(L, i) << std::endl;
+        break;
+      default: 
+        std::cout << i << ": " << lua_typename(L, t) << std::endl;
+        break;
+    }
+    i--;
+  }
+  std::cout << "--------------- Stack Dump Finished ---------------" << std::endl;
+}
+
+}
diff --git a/src/rgw/rgw_lua_utils.h b/src/rgw/rgw_lua_utils.h
new file mode 100644 (file)
index 0000000..3462e0e
--- /dev/null
@@ -0,0 +1,175 @@
+#pragma once
+
+#include <memory>
+#include <string>
+#include <ctime>
+#include <lua.hpp>
+
+class CephContext;
+
+namespace rgw::lua {
+
+// push ceph time in string format: "%Y-%m-%d %H:%M:%S"
+template <typename CephTime>
+void lua_pushtime(lua_State* L, const CephTime& tp) 
+{
+  const auto tt = CephTime::clock::to_time_t(tp);
+  const auto tm = *std::localtime(&tt);
+  char buff[64];
+  std::strftime(buff, sizeof(buff), "%Y-%m-%d %H:%M:%S", &tm);
+  lua_pushstring(L, buff);
+}
+
+// push std::string
+void lua_pushstring(lua_State* L, const std::string& str);
+
+// dump the lua stack to stdout
+void stack_dump(lua_State* L);
+
+class lua_state_guard {
+  lua_State* l;
+public:
+  lua_state_guard(lua_State* _l) : l(_l) {}
+  ~lua_state_guard() {lua_close(l);}
+  void reset(lua_State* _l=nullptr) {l = _l;}
+};
+
+constexpr auto ONE_UPVAL    = 1;
+constexpr auto TWO_UPVALS   = 2;
+constexpr auto THREE_UPVALS = 3;
+constexpr auto FOUR_UPVALS  = 4;
+constexpr auto FIVE_UPVALS  = 5;
+
+constexpr auto ONE_RETURNVAL    = 1;
+constexpr auto TWO_RETURNVALS   = 2;
+constexpr auto THREE_RETURNVALS = 3;
+constexpr auto FOUR_RETURNVALS  = 4;
+// utility functions to create a metatable
+// and tie it to an unnamed table
+//
+// add an __index method to it, to allow reading values
+// if "readonly" parameter is set to "false", it will also add
+// a __newindex method to it, to allow writing values
+// if the "toplevel" parameter is set to "true", it will name the
+// table as well as the metatable, this would allow direct access from
+// the lua script.
+//
+// The MetaTable is expected to be a class with the following members:
+// Name (static function returning the unique name of the metatable)
+// TableName (static function returning the unique name of the table - needed only for "toplevel" tables)
+// Type (typename) - the type of the "upvalue" (the type that the meta table represent)
+// IndexClosure (static function return "int" and accept "lua_State*") 
+// NewIndexClosure (static function return "int" and accept "lua_State*") 
+// e.g.
+// struct MyStructMetaTable {
+//   static std::string TableName() {
+//     return "MyStruct";
+//   }
+//
+//   using Type = MyStruct;
+//
+//   static int IndexClosure(lua_State* L) {
+//     const auto value = reinterpret_cast<const Type*>(lua_touserdata(L, lua_upvalueindex(1)));
+//     ...
+//   }
+
+//   static int NewIndexClosure(lua_State* L) {
+//     auto value = reinterpret_cast<Type*>(lua_touserdata(L, lua_upvalueindex(1)));
+//     ...
+//   }
+// };
+//
+
+template<typename MetaTable, typename... Upvalues>
+void create_metatable(lua_State* L, bool toplevel, Upvalues... upvalues)
+{
+  constexpr auto upvals_size = sizeof...(upvalues);
+  const std::array<void*, upvals_size> upvalue_arr = {upvalues...};
+  // create table
+  lua_newtable(L);
+  if (toplevel) {
+    // duplicate the table to make sure it remain in the stack
+    lua_pushvalue(L, -1);
+    // give table a name (in cae of "toplevel")
+    lua_setglobal(L, MetaTable::TableName().c_str());
+  }
+  // create metatable
+  [[maybe_unused]] const auto rc = luaL_newmetatable(L, MetaTable::Name().c_str());
+  lua_pushstring(L, "__index");
+  for (const auto upvalue : upvalue_arr) {
+    lua_pushlightuserdata(L, upvalue);
+  }
+  lua_pushcclosure(L, MetaTable::IndexClosure, upvals_size);
+  lua_rawset(L, -3);
+  lua_pushstring(L, "__newindex");
+  for (const auto upvalue : upvalue_arr) {
+    lua_pushlightuserdata(L, upvalue);
+  }
+  lua_pushcclosure(L, MetaTable::NewIndexClosure, upvals_size);
+  lua_rawset(L, -3);
+  lua_pushstring(L, "__pairs");
+  for (const auto upvalue : upvalue_arr) {
+    lua_pushlightuserdata(L, upvalue);
+  }
+  lua_pushcclosure(L, MetaTable::PairsClosure, upvals_size);
+  lua_rawset(L, -3);
+  lua_pushstring(L, "__len");
+  for (const auto upvalue : upvalue_arr) {
+    lua_pushlightuserdata(L, upvalue);
+  }
+  lua_pushcclosure(L, MetaTable::LenClosure, upvals_size);
+  lua_rawset(L, -3);
+  // tie metatable and table
+  lua_setmetatable(L, -2);
+}
+
+template<typename MetaTable>
+void create_metatable(lua_State* L, bool toplevel, std::unique_ptr<typename MetaTable::Type>& ptr)
+{
+  if (ptr) {
+    create_metatable<MetaTable>(L, toplevel, reinterpret_cast<void*>(ptr.get()));
+  } else {
+    lua_pushnil(L);
+  }
+}
+
+// following struct may be used as a base class for other MetaTable classes
+// note, however, this is not mandatory to use it as a base
+struct EmptyMetaTable {
+  // by default everythinmg is "readonly"
+  // to change, overload this function in the derived
+  static int NewIndexClosure(lua_State* L) {
+    throw std::runtime_error("trying to write to readonly field");
+    return 1;
+  }
+  
+  // by default nothing is iterable
+  // to change, overload this function in the derived
+  static int PairsClosure(lua_State* L) {
+    throw std::runtime_error("trying to iterate over non-iterable field");
+    return 1;
+  }
+  
+  // by default nothing is iterable
+  // to change, overload this function in the derived
+  static int LenClosure(lua_State* L) {
+    throw std::runtime_error("trying to get length of non-iterable field");
+    return 1;
+  }
+
+  static void throw_unknown_field(const std::string& index, const std::string& table) {
+    throw std::runtime_error("unknown field name: " + index + " provided to: " + table);
+  }
+};
+
+// create a debug log action
+// it expects CephContext to be captured
+// it expects one string parameter, which is the message to log
+// could be executed from any context that has CephContext
+// e.g.
+//    RGWDebugLog("hello world from lua")
+//
+void create_debug_action(lua_State* L, CephContext* cct);
+
+}
+
diff --git a/src/rgw/rgw_op_type.h b/src/rgw/rgw_op_type.h
new file mode 100644 (file)
index 0000000..609868d
--- /dev/null
@@ -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,
+};
+
index 07aad9b9785436f3810bdde2e61b1b9261c5c3fa..61649104921e20bd70d8802938de306a79a246bc 100644 (file)
@@ -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) {
index 27fed33ed08f69439670999e7ec56a30c30550b2..6d67c33967f1fd3f4c28d70bc898e23328627fb3 100644 (file)
@@ -41,6 +41,7 @@ struct RGWProcessEnv {
 };
 
 class RGWFrontendConfig;
+class RGWRequest;
 
 class RGWProcess {
   deque<RGWRequest*> m_req_queue;
index d427cf064b8457eb1fb663c2b8d2e0e1f0f1ce99..e8531031dbf6fbabdb95a818123b96ca8fa6b32f 100644 (file)
@@ -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)
 
index fc7dc8b873c0276365fe937f215368faacdd29ff..7db2aee332f949834bc80b3826c2ab5eab2e2b95 100644 (file)
     subscription rm            remove a pubsub subscription
     subscription pull          show events in a pubsub subscription
     subscription ack           ack (remove) an events in a pubsub subscription
+    script put                 upload a lua script to a context
+    script get                 get the lua script of a context
+    script rm                  remove the lua scripts of a context
   options:
      --tenant=<tenant>         tenant name
      --user_ns=<namespace>     namespace of user (oidc in case of users authenticated with oidc provider)
      --subscription            pubsub subscription name
      --event-id                event id in a pubsub subscription
   
+  Script options:
+     --context                 context in which the script runs. one of: preRequest, postRequest
+  
     --conf/-c FILE    read configuration from the given configuration file
     --id ID           set ID portion of my name
     --name/-n TYPE.ID set name
index f518b7b2d3479902c7302c65620ed0517b18be9d..1251e3c281114101139e26e3c90c98d786e9e856 100644 (file)
@@ -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 (file)
index 0000000..632dda1
--- /dev/null
@@ -0,0 +1,498 @@
+#include <gtest/gtest.h>
+#include "common/ceph_context.h"
+#include "rgw/rgw_common.h"
+#include "rgw/rgw_process.h"
+#include "rgw/rgw_sal_rados.h"
+#include "rgw/rgw_lua_request.h"
+
+using namespace rgw;
+
+class CctCleaner {
+  CephContext* cct;
+public:
+  CctCleaner(CephContext* _cct) : cct(_cct) {}
+  ~CctCleaner() { 
+#ifdef WITH_SEASTAR
+    delete cct; 
+#else
+    cct->put(); 
+#endif
+  }
+};
+
+class TestRGWUser : public sal::RGWUser {
+public:
+  virtual int list_buckets(const string&, const string&, uint64_t, bool, sal::RGWBucketList&) override {
+    return 0;
+  }
+
+  virtual sal::RGWBucket* create_bucket(rgw_bucket& bucket, ceph::real_time creation_time) override {
+    return nullptr;
+  }
+
+  virtual int load_by_id(optional_yield y) override {
+    return 0;
+  }
+
+  virtual ~TestRGWUser() = default;
+};
+
+class TestAccounter : public io::Accounter, public io::BasicClient {
+  RGWEnv env;
+
+protected:
+  virtual int init_env(CephContext *cct) override {
+    return 0;
+  }
+
+public:
+  ~TestAccounter() = default;
+
+  virtual void set_account(bool enabled) override {
+  }
+
+  virtual uint64_t get_bytes_sent() const override {
+    return 0;
+  }
+
+  virtual uint64_t get_bytes_received() const override {
+    return 0;
+  }
+  
+  virtual RGWEnv& get_env() noexcept override {
+    return env;
+  }
+  
+  virtual size_t complete_request() override {
+    return 0;
+  }
+};
+
+auto cct = new CephContext(CEPH_ENTITY_TYPE_CLIENT);
+
+CctCleaner cleaner(cct);
+
+TEST(TestRGWLua, EmptyScript)
+{
+  const std::string script;
+
+  RGWEnv e;
+  uint64_t id = 0;
+  req_state s(cct, &e, id); 
+
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", script);
+  ASSERT_EQ(rc, 0);
+}
+
+#define DEFINE_REQ_STATE RGWEnv e; req_state s(cct, &e, 0);
+
+TEST(TestRGWLua, SyntaxError)
+{
+  const std::string script = R"(
+    if 3 < 5 then
+      RGWDebugLog("missing 'end'")
+  )";
+
+  DEFINE_REQ_STATE;
+
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", script);
+  ASSERT_EQ(rc, -1);
+}
+
+TEST(TestRGWLua, Hello)
+{
+  const std::string script = R"(
+    RGWDebugLog("hello from lua")
+  )";
+
+  DEFINE_REQ_STATE;
+
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", script);
+  ASSERT_EQ(rc, 0);
+}
+
+TEST(TestRGWLua, URI)
+{
+  const std::string script = R"(
+    msg = "URI is: " .. Request.DecodedURI
+    RGWDebugLog(msg)
+    print(msg)
+  )";
+
+  DEFINE_REQ_STATE;
+  s.decoded_uri = "http://hello.world/";
+
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", script);
+  ASSERT_EQ(rc, 0);
+}
+
+TEST(TestRGWLua, Response)
+{
+  const std::string script = R"(
+    print(Request.Response.Message)
+    print(Request.Response.HTTPStatus)
+    print(Request.Response.RGWCode)
+    print(Request.Response.HTTPStatusCode)
+  )";
+
+  DEFINE_REQ_STATE;
+  s.err.http_ret = 400;
+  s.err.ret = 4000;
+  s.err.err_code = "Bad Request";
+  s.err.message = "This is a bad request";
+
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+  ASSERT_EQ(rc, 0);
+}
+
+TEST(TestRGWLua, SetResponse)
+{
+  const std::string script = R"(
+    print(Request.Response.Message)
+    Request.Response.Message = "this is a good request"
+    print(Request.Response.Message)
+  )";
+
+  DEFINE_REQ_STATE;
+  s.err.message = "this is a bad request";
+
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+  ASSERT_EQ(rc, 0);
+}
+
+TEST(TestRGWLua, SetRGWId)
+{
+  const std::string script = R"(
+    print(Request.RGWId)
+    Request.RGWId = "bar"
+    print(Request.RGWId)
+  )";
+
+  DEFINE_REQ_STATE;
+  s.host_id = "foo";
+
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+  ASSERT_NE(rc, 0);
+}
+
+TEST(TestRGWLua, InvalidField)
+{
+  const std::string script = R"(
+    RGWDebugLog(Request.Kaboom)
+  )";
+
+  DEFINE_REQ_STATE;
+  s.host_id = "foo";
+
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "kaboom", script);
+  ASSERT_EQ(rc, -1);
+}
+
+TEST(TestRGWLua, InvalidSubField)
+{
+  const std::string script = R"(
+    RGWDebugLog(Request.Error.Kaboom)
+  )";
+
+  DEFINE_REQ_STATE;
+
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "kaboom", script);
+  ASSERT_EQ(rc, -1);
+}
+
+TEST(TestRGWLua, Bucket)
+{
+  const std::string script = R"(
+    if Request.Bucket then
+      msg = "Bucket Id: " .. Request.Bucket.Id
+      RGWDebugLog(msg)
+      print(msg)
+      print("Bucket Marker: " .. Request.Bucket.Marker)
+      print("Bucket Name: " .. Request.Bucket.Name)
+      print("Bucket Tenant: " .. Request.Bucket.Tenant)
+      print("Bucket Count: " .. Request.Bucket.Count)
+      print("Bucket Size: " .. Request.Bucket.Size)
+      print("Bucket ZoneGroupId: " .. Request.Bucket.ZoneGroupId)
+      print("Bucket Creation Time: " .. Request.Bucket.CreationTime)
+      print("Bucket MTime: " .. Request.Bucket.MTime)
+      print("Bucket Quota Max Size: " .. Request.Bucket.Quota.MaxSize)
+      print("Bucket Quota Max Objects: " .. Request.Bucket.Quota.MaxObjects)
+      print("Bucket Quota Enabled: " .. tostring(Request.Bucket.Quota.Enabled))
+      print("Bucket Quota Rounded: " .. tostring(Request.Bucket.Quota.Rounded))
+      print("Bucket User Id: " .. Request.Bucket.User.Id)
+      print("Bucket User Tenant: " .. Request.Bucket.User.Tenant)
+    else
+      print("No bucket")
+    end
+  )";
+
+  DEFINE_REQ_STATE;
+
+  rgw_bucket b;
+  b.tenant = "mytenant";
+  b.name = "myname";
+  b.marker = "mymarker";
+  b.bucket_id = "myid"; 
+  s.bucket.reset(new sal::RGWRadosBucket(nullptr, b));
+
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+  ASSERT_EQ(rc, 0);
+}
+
+TEST(TestRGWLua, GenericAttributes)
+{
+  const std::string script = R"(
+    print("hello  = " .. (Request.GenericAttributes["hello"] or "nil"))
+    print("foo    = " .. (Request.GenericAttributes["foo"] or "nil"))
+    print("kaboom = " .. (Request.GenericAttributes["kaboom"] or "nil"))
+    print("number of attributes is: " .. #Request.GenericAttributes)
+    for k, v in pairs(Request.GenericAttributes) do
+      print("key=" .. k .. ", " .. "value=" .. v)
+    end
+  )";
+
+  DEFINE_REQ_STATE;
+  s.generic_attrs["hello"] = "world";
+  s.generic_attrs["foo"] = "bar";
+  s.generic_attrs["goodbye"] = "cruel world";
+  s.generic_attrs["ka"] = "boom";
+
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+  ASSERT_EQ(rc, 0);
+}
+
+TEST(TestRGWLua, Environment)
+{
+  const std::string script = R"(
+    print("number of env entries is: " .. #Request.Environment)
+    for k, v in pairs(Request.Environment) do
+      print("key=" .. k .. ", " .. "value=" .. v)
+    end
+  )";
+
+  DEFINE_REQ_STATE;
+  s.env[""] = "world";
+  s.env[""] = "bar";
+  s.env["goodbye"] = "cruel world";
+  s.env["ka"] = "boom";
+
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+  ASSERT_EQ(rc, 0);
+}
+
+TEST(TestRGWLua, Tags)
+{
+  const std::string script = R"(
+    print("number of tags is: " .. #Request.Tags)
+    for k, v in pairs(Request.Tags) do
+      print("key=" .. k .. ", " .. "value=" .. v)
+    end
+  )";
+
+  DEFINE_REQ_STATE;
+  s.tagset.add_tag("hello", "world");
+  s.tagset.add_tag("foo", "bar");
+  s.tagset.add_tag("goodbye", "cruel world");
+  s.tagset.add_tag("ka", "boom");
+
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+  ASSERT_EQ(rc, 0);
+}
+
+TEST(TestRGWLua, Acl)
+{
+  const std::string script = R"(
+    function print_grant(g)
+      print("Type: " .. g.Type)
+      print("GroupType: " .. g.GroupType)
+      print("Permission: " .. g.Permission)
+      print("Referer: " .. g.Referer)
+      if (g.User) then
+        print("User.Tenant: " .. g.User.Tenant)
+        print("User.Id: " .. g.User.Id)
+      end
+    end
+
+    print(Request.UserAcl.Owner.DisplayName)
+    print(Request.UserAcl.Owner.User.Id)
+    print(Request.UserAcl.Owner.User.Tenant)
+    print("number of grants is: " .. #Request.UserAcl.Grants)
+    for k, v in pairs(Request.UserAcl.Grants) do
+      print("grant key=" .. k)
+      print("grant values=")
+      print_grant(v)
+    end
+  )";
+
+  DEFINE_REQ_STATE;
+  ACLOwner owner;
+  owner.set_id(rgw_user("john", "doe"));
+  owner.set_name("john doe");
+  s.user_acl.reset(new RGWAccessControlPolicy(cct));
+  s.user_acl->set_owner(owner);
+  ACLGrant grant1, grant2, grant3, grant4, grant5;
+  grant1.set_canon(rgw_user("jane", "doe"), "her grant", 1);
+  grant2.set_referer("http://localhost/ref1", 2);
+  grant3.set_referer("http://localhost/ref2", 3);
+  grant4.set_canon(rgw_user("john", "doe"), "his grant", 4);
+  grant5.set_group(ACL_GROUP_AUTHENTICATED_USERS, 5);
+  s.user_acl->get_acl().add_grant(&grant1);
+  s.user_acl->get_acl().add_grant(&grant2);
+  s.user_acl->get_acl().add_grant(&grant3);
+  s.user_acl->get_acl().add_grant(&grant4);
+  s.user_acl->get_acl().add_grant(&grant5);
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+  ASSERT_EQ(rc, 0);
+}
+
+TEST(TestRGWLua, UseFunction)
+{
+       const std::string script = R"(
+               function print_owner(owner)
+               print("Owner Dispaly Name: " .. owner.DisplayName)
+               print("Owner Id: " .. owner.User.Id)
+               print("Owner Tenanet: " .. owner.User.Tenant)
+               end
+
+               print_owner(Request.ObjectOwner)
+    
+    function print_acl(acl_type)
+      index = acl_type .. "ACL"
+      acl = Request[index]
+      if acl then
+        print(acl_type .. "ACL Owner")
+        print_owner(acl.Owner)
+      else
+        print("no " .. acl_type .. " ACL in request: " .. Request.Id)
+      end 
+    end
+
+    print_acl("User")
+    print_acl("Bucket")
+    print_acl("Object")
+       )";
+
+  DEFINE_REQ_STATE;
+  s.owner.set_name("user two");
+  s.owner.set_id(rgw_user("tenant2", "user2"));
+  s.user_acl.reset(new RGWAccessControlPolicy());
+  s.user_acl->get_owner().set_name("user three");
+  s.user_acl->get_owner().set_id(rgw_user("tenant3", "user3"));
+  s.bucket_acl.reset(new RGWAccessControlPolicy());
+  s.bucket_acl->get_owner().set_name("user four");
+  s.bucket_acl->get_owner().set_id(rgw_user("tenant4", "user4"));
+  s.object_acl.reset(new RGWAccessControlPolicy());
+  s.object_acl->get_owner().set_name("user five");
+  s.object_acl->get_owner().set_id(rgw_user("tenant5", "user5"));
+
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+  ASSERT_EQ(rc, 0);
+}
+
+TEST(TestRGWLua, WithLib)
+{
+  const std::string script = R"(
+    print("bucket name split:")
+    for i in string.gmatch(Request.Bucket.Name, "%a+") do
+      print("lua print: part: " .. i)
+    end
+  )";
+
+  DEFINE_REQ_STATE;
+
+  rgw_bucket b;
+  b.name = "my-bucket-name-is-fish";
+  s.bucket.reset(new sal::RGWRadosBucket(nullptr, b));
+
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+  ASSERT_EQ(rc, 0);
+}
+
+#include <sys/socket.h>
+#include <stdlib.h>
+
+void unix_socket_client(const std::string& path) {
+  int fd;
+  // create the socket
+  if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+    std::cout << "unix socket error: " << errno << std::endl;
+    return;
+  }
+  // set the path
+  struct sockaddr_un addr;
+  memset(&addr, 0, sizeof(addr));
+  addr.sun_family = AF_UNIX;
+  strncpy(addr.sun_path, path.c_str(), sizeof(addr.sun_path)-1);
+
+       // let the socket be created by the "rgw" side
+       std::this_thread::sleep_for(std::chrono::seconds(2));
+       if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
+               std::cout << "unix socket connect error: " << errno << std::endl;
+               return;
+       }
+
+  std::cout << "unix socket connected to: " << path << std::endl;
+  char buff[256];
+       int rc;
+       while((rc=read(fd, buff, sizeof(buff))) > 0) {
+               std::cout << std::string(buff, rc);
+  }
+}
+
+TEST(TestRGWLua, OpsLog)
+{
+       const std::string unix_socket_path = "./aSocket.sock";
+       unlink(unix_socket_path.c_str());
+
+       std::thread unix_socket_thread(unix_socket_client, unix_socket_path);
+
+  const std::string script = R"(
+               if Request.Response.HTTPStatusCode == 200 then
+                       print("request is good, just log to lua: " .. Request.Response.Message)
+               else 
+                       print("request is bad, use ops log:")
+      if Request.Bucket then
+         rc = Request.Log()
+         print("ops log return code: " .. rc)
+      else
+        print("no bucket, ops log wasn't called")
+      end
+               end
+  )";
+
+  auto store = std::unique_ptr<sal::RGWRadosStore>(new sal::RGWRadosStore);
+  store->setRados(new RGWRados);
+  auto olog = std::unique_ptr<OpsLogSocket>(new OpsLogSocket(cct, 1024));
+  ASSERT_TRUE(olog->init(unix_socket_path));
+
+  DEFINE_REQ_STATE;
+  s.err.http_ret = 200;
+  s.err.ret = 0;
+  s.err.err_code = "200OK";
+  s.err.message = "Life is great";
+  rgw_bucket b;
+  b.tenant = "tenant";
+  b.name = "name";
+  b.marker = "marker";
+  b.bucket_id = "id"; 
+  s.bucket.reset(new sal::RGWRadosBucket(nullptr, b));
+  s.bucket_name = "name";
+       s.enable_ops_log = true;
+       s.enable_usage_log = false;
+       s.user.reset(new TestRGWUser());
+  TestAccounter ac;
+  s.cio = &ac; 
+       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();
+}
+