Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rgw/lua: support reloading lua packages on all RGWs #52326

Merged
merged 3 commits into from
Oct 9, 2023

Conversation

yuvalif
Copy link
Contributor

@yuvalif yuvalif commented Jul 5, 2023

test instructions:
https://gist.github.com/yuvalif/95b8ed9ea73ab4591c59644a050e01e2

Checklist

  • Tracker (select at least one)
    • References tracker ticket
    • Very recent bug; references commit where it was introduced
    • New feature (ticket optional)
    • Doc update (no ticket needed)
    • Code cleanup (no ticket needed)
  • Component impact
    • Affects Dashboard, opened tracker ticket
    • Affects Orchestrator, opened tracker ticket
    • No impact that needs to be tracked
  • Documentation (select at least one)
    • Updates relevant documentation
    • No doc update is appropriate
  • Tests (select at least one)
Show available Jenkins commands
  • jenkins retest this please
  • jenkins test classic perf
  • jenkins test crimson perf
  • jenkins test signed
  • jenkins test make check
  • jenkins test make check arm64
  • jenkins test submodules
  • jenkins test dashboard
  • jenkins test dashboard cephadm
  • jenkins test api
  • jenkins test docs
  • jenkins render docs
  • jenkins test ceph-volume all
  • jenkins test ceph-volume tox
  • jenkins test windows

@@ -3482,6 +3477,101 @@ int RadosLuaManager::list_packages(const DoutPrefixProvider *dpp, optional_yield
return 0;
}

int RadosLuaManager::watch_reload(const DoutPrefixProvider *dpp, uint64_t* watch_handle, librados::WatchCtx2* watcher)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could not make that a sal API since the callbacks are expected to belong to a class deriving from WatchCtx2 which is RASDOS specific

@@ -177,5 +189,46 @@ void Background::create_background_metatable(lua_State* L) {
create_metatable<rgw::lua::RGWTable>(L, true, &rgw_map, &table_mutex);
}

#ifdef WITH_RADOSGW_LUA_PACKAGES
int Background::PackagesWatcher::start() {
return static_cast<rgw::sal::RadosLuaManager*>(parent->lua_manager.get())->watch_reload(&parent->dp, &watch_handle, this);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ceph code, like comedy, is not pretty ;)

@github-actions
Copy link

This pull request can no longer be automatically merged: a rebase is needed and changes have to be manually resolved

@yuvalif yuvalif marked this pull request as ready for review July 18, 2023 10:29
@yuvalif yuvalif requested review from a team as code owners July 18, 2023 10:29
@@ -114,6 +115,13 @@ To print the list of packages in the allowlist:
# radosgw-admin script-package list


To apply all changes from the allowlist to all RGWs:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would remove the first "all"

ceph::encode(reload_status, reply);
auto pool = store->getRados()->get_lc_pool_ctx();
if (!pool->is_valid()) {
ldpp_dout(_dpp, 1) << "ERROR: failed to ack reload of " << PACKAGE_LIST_OBJECT_NAME
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: ACK

auto iter = ack.payload_bl.cbegin();
ceph::decode(r, iter);
} catch (buffer::error& err) {
ldpp_dout(_dpp, 1) << "ERROR: couldn't decode Lua packages reload status. error: " <<
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: lua here and below should be Lua

auto r = install_packages(lua_manager->dpp(), lua_manager->driver(),
y, lua_manager->luarocks_path(), failed_packages);
if (r < 0) {
ldpp_dout(lua_manager->dpp(), 1) << "WARNING: failed to install lua packages from allowlist. error: " << r
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING and errror seem at odds. Perhaps WARNING: failed to install Lua packages from allowlist: " << r << dendl; ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it says "WARNING" because failing to install packages does not necesarily impact service
only if there is a script that want to use these packages the service may be impacted.
mayebe will change to "error code" istead of "error"?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe "return code"?

@@ -177,5 +189,46 @@ void Background::create_background_metatable(lua_State* L) {
create_metatable<rgw::lua::RGWTable>(L, true, &rgw_map, &table_mutex);
}

#ifdef WITH_RADOSGW_LUA_PACKAGES
int Background::PackagesWatcher::start() {
return static_cast<rgw::sal::RadosLuaManager*>(parent->lua_manager.get())->watch_reload(&parent->dp, &watch_handle, this);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ceph code, like comedy, is not pretty ;)

@yuvalif
Copy link
Contributor Author

yuvalif commented Jul 19, 2023

jenkins test api

@yuvalif
Copy link
Contributor Author

yuvalif commented Jul 23, 2023

jenkins test make check

@yuvalif
Copy link
Contributor Author

yuvalif commented Jul 24, 2023

Copy link
Contributor

@cbodley cbodley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm hoping we can clean up the sal interface

Comment on lines 1565 to 1572
/** Add a callback to watch packages reload **/
virtual int watch_reload() = 0;
/** Stop watching the packages reload **/
virtual int unwatch_reload() = 0;
/** Send a reply to the reloader of the packages indicating if the reload was successfull **/
virtual void ack_reload(uint64_t notify_id, uint64_t cookie, int reload_status) = 0;
/** Get the watch handle which should be shared between watch_reload() and unwatch_reload() **/
virtual uint64_t watch_handle() const = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are all internals of rados' watch/notify protocol that don't belong in the sal interface

can the RadosLuaManager just initialize the watch on construction and drop it on shutdown/destruction to avoid exposing the watch_reload() and unwatch_reload() APIs? we shouldn't need an abstraction for ack_reload(), either - the rados implementation would just provide a callback that calls install_packages() from rgw_lua.cc

the only thing i think we actually need in the sal API is reload_packages() to trigger the reload. for rados, that would just send a notify message and wait for acks. that would (indirectly) invoke our callback to do the actual reload

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

donne in fe641a9

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cbodley can you please re-review?

src/rgw/rgw_lua.cc Outdated Show resolved Hide resolved
Comment on lines 1573 to 1574
/** Get dout prefix provider **/
virtual const DoutPrefixProvider* dpp() const = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i see that RadosLuaManager needs a dpp for its watch/notify stuff, but that shouldn't need to be exposed in the interface

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

src/rgw/rgw_sal.h Show resolved Hide resolved
src/rgw/rgw_admin.cc Show resolved Hide resolved
/** Get a Lua script manager for running lua scripts */
virtual std::unique_ptr<LuaManager> get_lua_manager() = 0;
/** Get a Lua script manager for running lua scripts and reloading packages */
virtual std::unique_ptr<LuaManager> get_lua_manager(const DoutPrefixProvider* dpp = nullptr, const std::string& luarocks_path = "") = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please avoid using default arguments for virtual functions

@github-actions
Copy link

github-actions bot commented Aug 9, 2023

This pull request can no longer be automatically merged: a rebase is needed and changes have to be manually resolved

@yuvalif
Copy link
Contributor Author

yuvalif commented Sep 10, 2023

jenkins test api

@github-actions
Copy link

This pull request can no longer be automatically merged: a rebase is needed and changes have to be manually resolved

yuvalif added a commit to yuvalif/ceph that referenced this pull request Sep 14, 2023
and move the rest of the functionality to sal::Driver
this is to prevent the cases where the lua manager was allocated
only to invoke a single functions, and did not have any real lifetime.

see discussion here:
ceph#52326 (comment)

Signed-off-by: Yuval Lifshitz <ylifshit@redhat.com>
yuvalif added a commit to yuvalif/ceph that referenced this pull request Sep 14, 2023
see PR comments:
ceph#52326 (comment)

Signed-off-by: Yuval Lifshitz <ylifshit@redhat.com>
@yuvalif
Copy link
Contributor Author

yuvalif commented Sep 18, 2023

@dang could you please review the latest changes, as I moved most APIs from the Lua manager to the sal Driver?

Copy link
Contributor

@dang dang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a strong opinion about this either way. On the one hand, it's nice to break up the API into chunks. On the other, objects without well defined lifecycles can be problematic. My very slight preference is to have a LuaManager to separate out the API, but it's not a big deal to me.

Copy link
Contributor

@cbodley cbodley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will need to apply the sal changes to driver/posix also

lua_background->start();
env.lua.background = lua_background.get();
static_cast<rgw::sal::RadosLuaManager*>(env.lua.manager.get())->watch_reload(dpp);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you mean? i don't think AppMain or the realm reloader should have to manage this watch. RadosLuaManager can call watch/unwatch in its own constructor/destructor without needing anything else

env.lua.manager = env.driver->get_lua_manager();
env.lua.manager = env.driver->get_lua_manager(env.lua.manager->get_luarocks_path());
if (env.lua.background) {
env.lua.background->set_manager(env.lua.manager.get());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

even if this updates the LuaManager pointer, it leaves Background::driver as a dangling pointer to the Driver that we destroyed with DriverManager::close_storage() above. the RadosLuaManager would also hold a handling pointer to RadosStore* const store

we really should shut down this background thread before close_storage(), and only restart it once we've recreated the new driver and lua manager

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

regarding the background thread. it is paused during reload (and the driver is set to null), and resumed only after the driver is updated to the new one. why do you think there is an issue?

regarding the lua package manager, it does not have any thread, just getting notifications from the rados thread (that should be paused during the reload). so, i'm not sure when the issue can happen?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you think there is an issue?

env.lua.manager extends the lifetime of RadosLuaManager past the point of its driver's deletion. this means that the ~RadosLuaManager() dtor would be making calls on a librados::IoCtx whose librados::Rados client was already destroyed. we should free env.lua.manager before calling close_storage()

@yuvalif
Copy link
Contributor Author

yuvalif commented Sep 21, 2023

will need to apply the sal changes to driver/posix also

the posix driver derives from FilterDriver, so, the API are already implemented

yuvalif added a commit to yuvalif/ceph that referenced this pull request Sep 21, 2023
and move the rest of the functionality to sal::Driver
this is to prevent the cases where the lua manager was allocated
only to invoke a single functions, and did not have any real lifetime.

see discussion here:
ceph#52326 (comment)

Signed-off-by: Yuval Lifshitz <ylifshit@redhat.com>
yuvalif added a commit to yuvalif/ceph that referenced this pull request Sep 21, 2023
see PR comments:
ceph#52326 (comment)

Signed-off-by: Yuval Lifshitz <ylifshit@redhat.com>
Copy link
Contributor

@cbodley cbodley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the Background pause/resume logic does look like it would avoid dangling pointer issues, just a question about locking there

Comment on lines 156 to 161
std::unique_lock cond_lock(pause_mutex);
if (!paused) {
set_package_path(L, lua_manager->get_luarocks_path());
std::string no_tenant;
const auto rc = rgw::lua::read_script(&dp, driver, no_tenant,
null_yield, rgw::lua::context::background, script);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is it necessary to hold this lock over the call to read_script()?

looking at the lock above:

    if (paused) {
      ldpp_dout(dpp, 10) << "Lua background thread paused" << dendl;
      std::unique_lock cond_lock(cond_mutex);
      cond.wait(cond_lock, [this]{return !paused || stopped;}); 

paused isn't atomic - shouldn't we acquire the lock before checking it? if we take the lock there, we should be able to call set_package_path() under that same lock instead of reacquiring it here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is it necessary to hold this lock over the call to read_script()?

what if I'm reading the script (= RADOS object), and then real reload is triggered?
I want the function that tries to call Background::pause() to be blocked until the read ends

looking at the lock above:

    if (paused) {
      ldpp_dout(dpp, 10) << "Lua background thread paused" << dendl;
      std::unique_lock cond_lock(cond_mutex);
      cond.wait(cond_lock, [this]{return !paused || stopped;}); 

paused isn't atomic - shouldn't we acquire the lock before checking it? if we take the lock there, we should be able to call set_package_path() under that same lock instead of reacquiring it here

paused is just a boolean, it is being set atomically (although there could be a race condition when one thread sets it and another one reads it but still doe not see the new value).
this means that:

  • puase() was called, but we still see "pause=false", but in this case, we would see the right value in line 157, won't read the script and won't run it. and just sleep until the next iteration
  • resume() was called, but we still see "pause=true", in this case we would get into the "if" statement in line 142, and wait. agree there is an issue here, we may get stuck on wait :-(

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

paused is just a boolean, it is being set atomically (although there could be a race condition when one thread sets it and another one reads it but still doe not see the new value).

there's really no need to be clever about this, just to avoid a lock every 5 seconds

and i still think this would be simpler if we took out this pause/resume logic and just stop/restart the background thread during realm reload

what if I'm reading the script (= RADOS object), and then real reload is triggered?

this wouldn't be a problem if we shutdown/join the thread before destroying the rados store

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok. agree. its is going to be simpler with paus/resume == shutdown/start

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed here: 0beafd6

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cbodley now background is explicitly shutdown+start in the realm reloader.
this also makes the lua background independent of RADOS

env.lua.manager = env.driver->get_lua_manager();
env.lua.manager = env.driver->get_lua_manager(env.lua.manager->get_luarocks_path());
if (env.lua.background) {
env.lua.background->set_manager(env.lua.manager.get());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you think there is an issue?

env.lua.manager extends the lifetime of RadosLuaManager past the point of its driver's deletion. this means that the ~RadosLuaManager() dtor would be making calls on a librados::IoCtx whose librados::Rados client was already destroyed. we should free env.lua.manager before calling close_storage()

@github-actions
Copy link

github-actions bot commented Oct 5, 2023

This pull request can no longer be automatically merged: a rebase is needed and changes have to be manually resolved

Signed-off-by: Yuval Lifshitz <ylifshit@ibm.com>
and switch to it only once installation is done.
this is needed for cases where installation can happen
while RGW is running

Signed-off-by: Yuval Lifshitz <ylifshit@ibm.com>
Copy link
Contributor

@cbodley cbodley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks great! we'll eventually want some reload coverage in teuthology

without requiring a restart of the RGWs
test instructions:
https://gist.github.com/yuvalif/95b8ed9ea73ab4591c59644a050e01e2
also use capitalized "Lua" in logs/doc

Signed-off-by: Yuval Lifshitz <ylifshit@ibm.com>
@yuvalif
Copy link
Contributor Author

yuvalif commented Oct 6, 2023

looks great! we'll eventually want some reload coverage in teuthology

thanks!
the initil drive for this PR is to be able to add lua automation more easily - will do that next.

@yuvalif
Copy link
Contributor Author

yuvalif commented Oct 9, 2023

teuthology results: http://pulpito.ceph.com/yuvalif-2023-10-08_12:46:18-rgw-wip-yuval-lua-reload-distro-default-smithi/

  • multisite crash:
 "backtrace": [
        "/lib/x86_64-linux-gnu/libpthread.so.0(+0x14420) [0x7f4273e57420]",
        "(std::_Rb_tree<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, RGWSyncShardMarkerTrack<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::marker_entry>, std::_Select1st<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, RGWSyncShardMarkerTrack<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::marker_entry> >, std::less<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, RGWSyncShardMarkerTrack<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::marker_entry> > >::find(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)+0x5d) [0x56317242ee2d]",
        "(RGWSyncShardMarkerTrack<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::finish(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)+0x69) [0x563172431e69]",
        "(RGWDataFullSyncSingleEntryCR::operate(DoutPrefixProvider const*)+0xaff) [0x5631726ed8df]",
        "(RGWCoroutinesStack::operate(DoutPrefixProvider const*, RGWCoroutinesEnv*)+0x17c) [0x5631720c86dc]",
        "(RGWCoroutinesManager::run(DoutPrefixProvider const*, std::__cxx11::list<RGWCoroutinesStack*, std::allocator<RGWCoroutinesStack*> >&)+0x35d) [0x5631720c9cad]",
        "(RGWCoroutinesManager::run(DoutPrefixProvider const*, RGWCoroutine*)+0x91) [0x5631720cb111]",
        "(RGWRemoteDataLog::run_sync(DoutPrefixProvider const*, int)+0x351) [0x563172696f51]",
        "(RGWDataSyncProcessorThread::process(DoutPrefixProvider const*)+0x58) [0x56317239da78]",
        "(RGWRadosThread::Worker::entry()+0xbd) [0x563172364abd]",
        "/lib/x86_64-linux-gnu/libpthread.so.0(+0x8609) [0x7f4273e4b609]",
        "clone()"
  • test issue:
teuthology.exceptions.CommandFailedError: Command failed on smithi081 with status 100: 'DEBIAN_FRONTEND=noninteractive sudo -E apt-get -y --force-yes install python-dev'
  • ragweed failure:
self = <ragweed.tests.tests.r_test_multipart_index_versioning object at 0x7fb923508a30>
    def check(self):
        for rb in self.get_buckets():
            break
        index_check_result = rgwa().check_bucket_index(rb.name)
        print(index_check_result)

       assert len(index_check_result.invalid_multipart_entries) == 0
       AttributeError: 'list' object has no attribute 'invalid_multipart_entries'

ragweed/tests/tests.py:299: AttributeError

@yuvalif
Copy link
Contributor Author

yuvalif commented Oct 9, 2023

@yuvalif yuvalif merged commit 7b774c4 into ceph:main Oct 9, 2023
10 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants