]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw/lua: add atomic increment/decrement to RGW table
authorYuval Lifshitz <ylifshit@redhat.com>
Mon, 23 May 2022 11:38:34 +0000 (14:38 +0300)
committeryuval Lifshitz <ylifshit@redhat.com>
Thu, 9 Jun 2022 18:43:51 +0000 (21:43 +0300)
Signed-off-by: Yuval Lifshitz <ylifshit@redhat.com>
doc/radosgw/lua-scripting.rst
src/rgw/rgw_lua_background.cc
src/rgw/rgw_lua_background.h
src/test/rgw/test_rgw_lua.cc

index 39268d8aae2085164a6ec6d63e0f937ce8dcde57..fe8c6e271653fb614135f74e461931892abc9d77 100644 (file)
@@ -309,16 +309,26 @@ The ``Request.Log()`` function prints the requests into the operations log. This
 Background Context
 --------------------
 The ``background`` context may be used for purposes that include analytics, monitoring, caching data for other context executions.
+- Background script execution default interval is 5 seconds.
 
+Global ``RGW`` Table
+--------------------
 The ``RGW`` Lua table is accessible from all contexts and saves data written to it
 during execution so that it may be read and used later during other executions, from the same context of a different one.
-
-- Background script execution default interval is 5 seconds.
-
 - Each RGW instance has its own private and ephemeral ``RGW`` Lua table that is lost when the daemon restarts. Note that ``background`` context scripts will run on every instance.
+- The maximum number of entries in the table is 100,000. Each entry has a string key a value with a combined length of no more than 1KB.
+A Lua script will abort with an error if the number of entries or entry size exceeds these limits.
+- The ``RGW`` Lua table uses string indeces and can store values of type: string, integer, double and boolean
+
+Increment/Decrement Functions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Since entries in the ``RGW`` table could be accessed from multiple places at the same time we need a way
+to atomically increment and decrement numeric values in it. For that the following functions should be used:
+- ``RGW.increment(<key>, [value])`` would increment the value of ``key`` by ``value`` if value is provided or by 1 if not
+- ``RGW.decrement(<key>, [value])`` would decrement the value of ``key`` by ``value`` if value is provided or by 1 if not
+- if the value of ``key`` is not numeric, the execution of the script would fail
+- if we try to increment or decrement by non-numeric values, the execution of the script would fail
 
-- The maximum number of entries in the table is 100,000. Each entry has a key and string value with a combined length of no more than 1KB. 
-  A Lua script will abort with an error if the number of entries or entry size exceeds these limits.
 
 Lua Code Samples
 ----------------
index 21c5fcc1a3d9f21451fc022b830dfad227e8ba74..592848c0ff9eaa8626f2e19f64b368fa70d17239 100644 (file)
@@ -9,6 +9,52 @@
 
 namespace rgw::lua {
 
+const char* RGWTable::INCREMENT = "increment";
+const char* RGWTable::DECREMENT = "decrement";
+
+int RGWTable::increment_by(lua_State* L) {
+  const auto map = reinterpret_cast<BackgroundMap*>(lua_touserdata(L, lua_upvalueindex(1)));
+  auto& mtx = *reinterpret_cast<std::mutex*>(lua_touserdata(L, lua_upvalueindex(2)));
+  auto decrement = lua_toboolean(L, lua_upvalueindex(3));
+
+  const auto args = lua_gettop(L);
+  const auto index = luaL_checkstring(L, 1);
+
+  // by default we increment by 1/-1
+  const auto default_inc = (decrement ? -1 : 1);
+  BackgroundMapValue inc_by = default_inc;
+  if (args == 2) {
+    if (lua_isinteger(L, 2)) {
+      inc_by = lua_tointeger(L, 2)*default_inc;
+    } else if (lua_isnumber(L, 2)){
+      inc_by = lua_tonumber(L, 2)*static_cast<double>(default_inc);
+    } else {
+      return luaL_error(L, "can increment only by numeric values");
+    }
+  }
+
+  std::unique_lock l(mtx);
+
+  const auto it = map->find(std::string(index));
+  if (it != map->end()) {
+    auto& value = it->second;
+    if (std::holds_alternative<double>(value) && std::holds_alternative<double>(inc_by)) {
+      value = std::get<double>(value) + std::get<double>(inc_by);
+    } else if (std::holds_alternative<int64_t>(value) && std::holds_alternative<int64_t>(inc_by)) {
+      value = std::get<int64_t>(value) + std::get<int64_t>(inc_by);
+    } else if (std::holds_alternative<double>(value) && std::holds_alternative<int64_t>(inc_by)) {
+      value = std::get<double>(value) + static_cast<double>(std::get<int64_t>(inc_by));
+    } else if (std::holds_alternative<int64_t>(value) && std::holds_alternative<double>(inc_by)) {
+      value = static_cast<double>(std::get<int64_t>(value)) + std::get<double>(inc_by);
+    } else {
+      mtx.unlock();
+      return luaL_error(L, "can increment only numeric values");
+    }
+  }
+
+  return 0;
+}
+
 Background::Background(rgw::sal::Store* store,
     CephContext* cct,
       const std::string& luarocks_path,
index e2fa9a195e98f147af1923051b105d09ebb178e9..e8ebc1cebc2b1ec2f215d2f0de1f606368afa097 100644 (file)
@@ -32,15 +32,37 @@ inline void pushvalue(lua_State* L, bool value) {
   lua_pushboolean(L, value);
 }
 
+
 struct RGWTable : EmptyMetaTable {
+
+  static const char* INCREMENT;
+  static const char* DECREMENT;
+
   static std::string TableName() {return "RGW";}
   static std::string Name() {return TableName() + "Meta";}
   
+  static int increment_by(lua_State* L);
+
   static int IndexClosure(lua_State* L) {
-    auto& mtx = *reinterpret_cast<std::mutex*>(lua_touserdata(L, lua_upvalueindex(2)));
     const auto map = reinterpret_cast<BackgroundMap*>(lua_touserdata(L, lua_upvalueindex(1)));
+    auto& mtx = *reinterpret_cast<std::mutex*>(lua_touserdata(L, lua_upvalueindex(2)));
     const char* index = luaL_checkstring(L, 2);
 
+    if (strcasecmp(index, INCREMENT) == 0) {
+      lua_pushlightuserdata(L, map);
+      lua_pushlightuserdata(L, &mtx);
+      lua_pushboolean(L, false /*increment*/);
+      lua_pushcclosure(L, increment_by, THREE_UPVALS);
+      return ONE_RETURNVAL;
+    } 
+    if (strcasecmp(index, DECREMENT) == 0) {
+      lua_pushlightuserdata(L, map);
+      lua_pushlightuserdata(L, &mtx);
+      lua_pushboolean(L, true /*decrement*/);
+      lua_pushcclosure(L, increment_by, THREE_UPVALS);
+      return ONE_RETURNVAL;
+    }
+
     std::lock_guard l(mtx);
 
     const auto it = map->find(std::string(index));
@@ -67,6 +89,10 @@ struct RGWTable : EmptyMetaTable {
     auto& mtx = *reinterpret_cast<std::mutex*>(lua_touserdata(L, lua_upvalueindex(2)));
     const auto map = reinterpret_cast<BackgroundMap*>(lua_touserdata(L, lua_upvalueindex(1)));
     const auto index = luaL_checkstring(L, 2);
+    
+    if (strcasecmp(index, INCREMENT) == 0 || strcasecmp(index, DECREMENT) == 0) {
+        return luaL_error(L, "increment/decrement are reserved function names for RGW");
+    }
 
     std::unique_lock l(mtx);
 
index 1d386197127771706bd27dcc8d85b9c444c2a1c1..3a2d43af2d35de942c45d8daf37563e748972565 100644 (file)
@@ -1023,3 +1023,141 @@ TEST(TestRGWLuaBackground, TableIterate)
   EXPECT_EQ(get_table_value<int64_t>(lua_background, "size"), 5);
 }
 
+TEST(TestRGWLuaBackground, TableIncrement)
+{
+  TestBackground lua_background("");
+
+  const std::string request_script = R"(
+    RGW["key1"] = 42
+    RGW["key2"] = 42.2
+    RGW.increment("key1")
+    assert(RGW["key1"] == 43)
+    RGW.increment("key2")
+    assert(RGW["key2"] == 43.2)
+  )";
+
+  DEFINE_REQ_STATE;
+
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", request_script, &lua_background);
+  ASSERT_EQ(rc, 0);
+}
+
+TEST(TestRGWLuaBackground, TableIncrementBy)
+{
+  TestBackground lua_background("");
+
+  const std::string request_script = R"(
+    RGW["key1"] = 42
+    RGW["key2"] = 42.2
+    RGW.increment("key1", 10)
+    assert(RGW["key1"] == 52)
+    RGW.increment("key2", 10)
+    assert(RGW["key2"] == 52.2)
+    RGW.increment("key1", 0.2)
+    assert(RGW["key1"] == 52.2)
+  )";
+
+  DEFINE_REQ_STATE;
+
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", request_script, &lua_background);
+  ASSERT_EQ(rc, 0);
+}
+
+TEST(TestRGWLuaBackground, TableDecrement)
+{
+  TestBackground lua_background("");
+
+  const std::string request_script = R"(
+    RGW["key1"] = 42
+    RGW["key2"] = 42.2
+    RGW.decrement("key1")
+    assert(RGW["key1"] == 41)
+    RGW.decrement("key2")
+    assert(RGW["key2"] == 41.2)
+  )";
+
+  DEFINE_REQ_STATE;
+
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", request_script, &lua_background);
+  ASSERT_EQ(rc, 0);
+}
+
+TEST(TestRGWLuaBackground, TableDecrementBy)
+{
+  TestBackground lua_background("");
+
+  const std::string request_script = R"(
+    RGW["key1"] = 42
+    RGW["key2"] = 42.2
+    RGW.decrement("key1", 10)
+    assert(RGW["key1"] == 32)
+    RGW.decrement("key2", 10)
+    assert(RGW["key2"] == 32.2)
+    RGW.decrement("key1", 0.8)
+    assert(RGW["key1"] == 31.2)
+  )";
+
+  DEFINE_REQ_STATE;
+
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", request_script, &lua_background);
+  ASSERT_EQ(rc, 0);
+}
+
+TEST(TestRGWLuaBackground, TableIncrementValueError)
+{
+  TestBackground lua_background("");
+
+  std::string request_script = R"(
+    -- cannot increment string values
+    RGW["key1"] = "hello"
+    RGW.increment("key1")
+  )";
+
+  DEFINE_REQ_STATE;
+
+  auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", request_script, &lua_background);
+  ASSERT_NE(rc, 0);
+  
+  request_script = R"(
+    -- cannot increment bool values
+    RGW["key1"] = true
+    RGW.increment("key1")
+  )";
+
+  rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", request_script, &lua_background);
+  ASSERT_NE(rc, 0);
+  
+  request_script = R"(
+    -- cannot increment by string values
+    RGW["key1"] = 99
+    RGW.increment("key1", "kaboom")
+  )";
+
+  rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", request_script, &lua_background);
+  ASSERT_NE(rc, 0);
+}
+
+TEST(TestRGWLuaBackground, TableIncrementError)
+{
+  TestBackground lua_background("");
+
+  std::string request_script = R"(
+    -- missing argument
+    RGW["key1"] = 11
+    RGW.increment()
+  )";
+
+  DEFINE_REQ_STATE;
+
+  auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", request_script, &lua_background);
+  ASSERT_NE(rc, 0);
+  
+  request_script = R"(
+    -- used as settable field
+    RGW.increment = 11
+  )";
+
+  rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", request_script, &lua_background);
+  ASSERT_NE(rc, 0);
+}
+