set(librbd_internal_srcs
AsyncObjectThrottle.cc
AsyncRequest.cc
+ ConfigWatcher.cc
DeepCopyRequest.cc
ExclusiveLock.cc
ImageCtx.cc
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/ConfigWatcher.h"
+#include "common/config_obs.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/ImageState.h"
+#include "librbd/api/Config.h"
+#include <deque>
+#include <string>
+#include <vector>
+#include <boost/algorithm/string/predicate.hpp>
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::ConfigWatcher: " \
+ << __func__ << ": "
+
+namespace librbd {
+
+template <typename I>
+struct ConfigWatcher<I>::Observer : public md_config_obs_t {
+ ConfigWatcher<I>* m_config_watcher;
+
+ std::deque<std::string> m_config_key_strs;
+ mutable std::vector<const char*> m_config_keys;
+
+ Observer(CephContext* cct, ConfigWatcher<I>* config_watcher)
+ : m_config_watcher(config_watcher) {
+ const std::string rbd_key_prefix("rbd_");
+ auto& schema = cct->_conf.get_schema();
+ for (auto& pair : schema) {
+ // watch all "rbd_" keys for simplicity
+ if (!boost::starts_with(pair.first, rbd_key_prefix)) {
+ continue;
+ }
+
+ m_config_key_strs.emplace_back(pair.first);
+ }
+
+ m_config_keys.reserve(m_config_key_strs.size());
+ for (auto& key : m_config_key_strs) {
+ m_config_keys.emplace_back(key.c_str());
+ }
+ m_config_keys.emplace_back(nullptr);
+ }
+
+ const char** get_tracked_conf_keys() const override {
+ ceph_assert(!m_config_keys.empty());
+ return &m_config_keys[0];
+ }
+
+ void handle_conf_change(const ConfigProxy& conf,
+ const std::set <std::string> &changed) override {
+ m_config_watcher->handle_global_config_change(changed);
+ }
+};
+
+template <typename I>
+ConfigWatcher<I>::ConfigWatcher(I& image_ctx)
+ : m_image_ctx(image_ctx) {
+}
+
+template <typename I>
+ConfigWatcher<I>::~ConfigWatcher() {
+ ceph_assert(m_observer == nullptr);
+}
+
+template <typename I>
+void ConfigWatcher<I>::init() {
+ auto cct = m_image_ctx.cct;
+ ldout(cct, 10) << dendl;
+
+ m_observer = new Observer(cct, this);
+ cct->_conf.add_observer(m_observer);
+}
+
+template <typename I>
+void ConfigWatcher<I>::shut_down() {
+ auto cct = m_image_ctx.cct;
+ ldout(cct, 10) << dendl;
+
+ ceph_assert(m_observer != nullptr);
+ cct->_conf.remove_observer(m_observer);
+
+ delete m_observer;
+ m_observer = nullptr;
+}
+
+template <typename I>
+void ConfigWatcher<I>::handle_global_config_change(
+ std::set<std::string> changed_keys) {
+
+ {
+ // ignore any global changes that are being overridden
+ RWLock::RLocker md_locker(m_image_ctx.md_lock);
+ for (auto& key : m_image_ctx.config_overrides) {
+ changed_keys.erase(key);
+ }
+ }
+ if (changed_keys.empty()) {
+ return;
+ }
+
+ auto cct = m_image_ctx.cct;
+ ldout(cct, 10) << "changed_keys=" << changed_keys << dendl;
+
+ // refresh the image to pick up any global config overrides
+ m_image_ctx.state->handle_update_notification();
+}
+
+} // namespace librbd
+
+template class librbd::ConfigWatcher<librbd::ImageCtx>;
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_CONFIG_WATCHER_H
+#define CEPH_LIBRBD_CONFIG_WATCHER_H
+
+#include <set>
+#include <string>
+
+struct Context;
+
+namespace librbd {
+
+struct ImageCtx;
+
+template <typename ImageCtxT>
+class ConfigWatcher {
+public:
+ static ConfigWatcher* create(ImageCtxT& image_ctx) {
+ return new ConfigWatcher(image_ctx);
+ }
+
+ ConfigWatcher(ImageCtxT& image_ctx);
+ ~ConfigWatcher();
+
+ ConfigWatcher(const ConfigWatcher&) = delete;
+ ConfigWatcher& operator=(const ConfigWatcher&) = delete;
+
+ void init();
+ void shut_down();
+
+private:
+ struct Observer;
+
+ ImageCtxT& m_image_ctx;
+
+ Observer* m_observer = nullptr;
+
+ void handle_global_config_change(std::set<std::string> changed);
+
+};
+
+} // namespace librbd
+
+extern template class librbd::ConfigWatcher<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_CONFIG_WATCHER_H
set(unittest_librbd_srcs
test_main.cc
test_mock_fixture.cc
+ test_mock_ConfigWatcher.cc
test_mock_DeepCopyRequest.cc
test_mock_ExclusiveLock.cc
test_mock_Journal.cc
bool cache;
ConfigProxy config;
+ std::set<std::string> config_overrides;
};
} // namespace librbd
MOCK_METHOD1(prepare_lock, void(Context*));
MOCK_METHOD0(handle_prepare_lock_complete, void());
+
+ MOCK_METHOD0(handle_update_notification, void());
};
} // namespace librbd
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librbd/test_mock_fixture.h"
+#include "test/librbd/test_support.h"
+#include "include/rbd_types.h"
+#include "common/ceph_mutex.h"
+#include "librbd/ConfigWatcher.h"
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+#include <list>
+
+namespace librbd {
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+} // namespace librbd
+
+#include "librbd/ConfigWatcher.cc"
+
+namespace librbd {
+
+using ::testing::Invoke;
+
+class TestMockConfigWatcher : public TestMockFixture {
+public:
+ typedef ConfigWatcher<MockTestImageCtx> MockConfigWatcher;
+
+ librbd::ImageCtx *m_image_ctx;
+
+ ceph::mutex m_lock = ceph::make_mutex("m_lock");
+ ceph::condition_variable m_cv;
+ bool m_refreshed = false;
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+
+ ASSERT_EQ(0, open_image(m_image_name, &m_image_ctx));
+ }
+
+ void expect_update_notification(MockTestImageCtx& mock_image_ctx) {
+ EXPECT_CALL(*mock_image_ctx.state, handle_update_notification())
+ .WillOnce(Invoke([this]() {
+ std::unique_lock locker{m_lock};
+ m_refreshed = true;
+ m_cv.notify_all();
+ }));
+ }
+
+ void wait_for_update_notification() {
+ std::unique_lock locker{m_lock};
+ m_cv.wait(locker, [this] {
+ if (m_refreshed) {
+ m_refreshed = false;
+ return true;
+ }
+ return false;
+ });
+ }
+};
+
+TEST_F(TestMockConfigWatcher, GlobalConfig) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ MockConfigWatcher mock_config_watcher(mock_image_ctx);
+ mock_config_watcher.init();
+
+ expect_update_notification(mock_image_ctx);
+ mock_image_ctx.cct->_conf.set_val("rbd_cache", "false");
+ mock_image_ctx.cct->_conf.set_val("rbd_cache", "true");
+ mock_image_ctx.cct->_conf.apply_changes(nullptr);
+ wait_for_update_notification();
+
+ mock_config_watcher.shut_down();
+}
+
+TEST_F(TestMockConfigWatcher, IgnoreOverriddenGlobalConfig) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ MockConfigWatcher mock_config_watcher(mock_image_ctx);
+ mock_config_watcher.init();
+
+ EXPECT_CALL(*mock_image_ctx.state, handle_update_notification())
+ .Times(0);
+ mock_image_ctx.config_overrides.insert("rbd_cache");
+ mock_image_ctx.cct->_conf.set_val("rbd_cache", "false");
+ mock_image_ctx.cct->_conf.set_val("rbd_cache", "true");
+ mock_image_ctx.cct->_conf.apply_changes(nullptr);
+
+ mock_config_watcher.shut_down();
+
+ ASSERT_FALSE(m_refreshed);
+}
+
+} // namespace librbd