From 4466800cf0672debebe30ef625b1f5367ae3c5dc Mon Sep 17 00:00:00 2001 From: Jason Dillaman Date: Wed, 29 Nov 2017 15:09:13 -0500 Subject: [PATCH] librbd: watch/notify types for trash add/remove events Signed-off-by: Jason Dillaman --- src/cls/rbd/cls_rbd_types.h | 7 ++ src/librbd/CMakeLists.txt | 2 + src/librbd/TrashWatcher.cc | 115 ++++++++++++++++++++ src/librbd/TrashWatcher.h | 58 ++++++++++ src/librbd/trash_watcher/Types.cc | 126 ++++++++++++++++++++++ src/librbd/trash_watcher/Types.h | 97 +++++++++++++++++ src/test/encoding/types.h | 2 + src/test/librbd/CMakeLists.txt | 1 + src/test/librbd/test_mock_TrashWatcher.cc | 95 ++++++++++++++++ 9 files changed, 503 insertions(+) create mode 100644 src/librbd/TrashWatcher.cc create mode 100644 src/librbd/TrashWatcher.h create mode 100644 src/librbd/trash_watcher/Types.cc create mode 100644 src/librbd/trash_watcher/Types.h create mode 100644 src/test/librbd/test_mock_TrashWatcher.cc diff --git a/src/cls/rbd/cls_rbd_types.h b/src/cls/rbd/cls_rbd_types.h index 905f53076ba5e..d50281d8bf406 100644 --- a/src/cls/rbd/cls_rbd_types.h +++ b/src/cls/rbd/cls_rbd_types.h @@ -363,6 +363,13 @@ struct TrashImageSpec { void encode(bufferlist &bl) const; void decode(bufferlist::iterator& it); void dump(Formatter *f) const; + + inline bool operator==(const TrashImageSpec& rhs) const { + return (source == rhs.source && + name == rhs.name && + deletion_time == rhs.deletion_time && + deferment_end_time == rhs.deferment_end_time); + } }; WRITE_CLASS_ENCODER(TrashImageSpec); diff --git a/src/librbd/CMakeLists.txt b/src/librbd/CMakeLists.txt index 9c0d8838d02ce..4c16eded6116b 100644 --- a/src/librbd/CMakeLists.txt +++ b/src/librbd/CMakeLists.txt @@ -1,6 +1,7 @@ add_library(rbd_types STATIC journal/Types.cc mirroring_watcher/Types.cc + trash_watcher/Types.cc watcher/Types.cc WatchNotifyTypes.cc) @@ -20,6 +21,7 @@ set(librbd_internal_srcs MirroringWatcher.cc ObjectMap.cc Operations.cc + TrashWatcher.cc Utils.cc Watcher.cc api/DiffIterate.cc diff --git a/src/librbd/TrashWatcher.cc b/src/librbd/TrashWatcher.cc new file mode 100644 index 0000000000000..bed93c67bcad2 --- /dev/null +++ b/src/librbd/TrashWatcher.cc @@ -0,0 +1,115 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "librbd/TrashWatcher.h" +#include "include/rbd_types.h" +#include "include/rados/librados.hpp" +#include "common/errno.h" +#include "librbd/Utils.h" +#include "librbd/watcher/Utils.h" + +#define dout_subsys ceph_subsys_rbd +#undef dout_prefix +#define dout_prefix *_dout << "librbd::TrashWatcher: " << __func__ << ": " + +namespace librbd { + +using namespace trash_watcher; +using namespace watcher; + +using librbd::util::create_rados_callback; + +namespace { + +static const uint64_t NOTIFY_TIMEOUT_MS = 5000; + +} // anonymous namespace + +template +TrashWatcher::TrashWatcher(librados::IoCtx &io_ctx, ContextWQ *work_queue) + : Watcher(io_ctx, work_queue, RBD_TRASH) { +} + +template +void TrashWatcher::notify_image_added( + librados::IoCtx &io_ctx, const std::string& image_id, + const cls::rbd::TrashImageSpec& trash_image_spec, Context *on_finish) { + CephContext *cct = reinterpret_cast(io_ctx.cct()); + ldout(cct, 20) << dendl; + + bufferlist bl; + ::encode(NotifyMessage{ImageAddedPayload{image_id, trash_image_spec}}, bl); + + librados::AioCompletion *comp = create_rados_callback(on_finish); + int r = io_ctx.aio_notify(RBD_TRASH, comp, bl, NOTIFY_TIMEOUT_MS, nullptr); + assert(r == 0); + comp->release(); +} + +template +void TrashWatcher::notify_image_removed(librados::IoCtx &io_ctx, + const std::string& image_id, + Context *on_finish) { + CephContext *cct = reinterpret_cast(io_ctx.cct()); + ldout(cct, 20) << dendl; + + bufferlist bl; + ::encode(NotifyMessage{ImageRemovedPayload{image_id}}, bl); + + librados::AioCompletion *comp = create_rados_callback(on_finish); + int r = io_ctx.aio_notify(RBD_TRASH, comp, bl, NOTIFY_TIMEOUT_MS, nullptr); + assert(r == 0); + comp->release(); +} + +template +void TrashWatcher::handle_notify(uint64_t notify_id, uint64_t handle, + uint64_t notifier_id, bufferlist &bl) { + CephContext *cct = this->m_cct; + ldout(cct, 15) << "notify_id=" << notify_id << ", " + << "handle=" << handle << dendl; + + + NotifyMessage notify_message; + try { + bufferlist::iterator iter = bl.begin(); + ::decode(notify_message, iter); + } catch (const buffer::error &err) { + lderr(cct) << "error decoding image notification: " << err.what() + << dendl; + Context *ctx = new C_NotifyAck(this, notify_id, handle); + ctx->complete(0); + return; + } + + apply_visitor(watcher::util::HandlePayloadVisitor>( + this, notify_id, handle), notify_message.payload); +} + +template +bool TrashWatcher::handle_payload(const ImageAddedPayload &payload, + Context *on_notify_ack) { + CephContext *cct = this->m_cct; + ldout(cct, 20) << dendl; + handle_image_added(payload.image_id, payload.trash_image_spec); + return true; +} + +template +bool TrashWatcher::handle_payload(const ImageRemovedPayload &payload, + Context *on_notify_ack) { + CephContext *cct = this->m_cct; + ldout(cct, 20) << dendl; + handle_image_removed(payload.image_id); + return true; +} + +template +bool TrashWatcher::handle_payload(const UnknownPayload &payload, + Context *on_notify_ack) { + return true; +} + +} // namespace librbd + +template class librbd::TrashWatcher; diff --git a/src/librbd/TrashWatcher.h b/src/librbd/TrashWatcher.h new file mode 100644 index 0000000000000..1367b82624e89 --- /dev/null +++ b/src/librbd/TrashWatcher.h @@ -0,0 +1,58 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_LIBRBD_TRASH_WATCHER_H +#define CEPH_LIBRBD_TRASH_WATCHER_H + +#include "include/int_types.h" +#include "cls/rbd/cls_rbd_types.h" +#include "librbd/ImageCtx.h" +#include "librbd/Watcher.h" +#include "librbd/trash_watcher/Types.h" + +namespace librados { class IoCtx; } + +namespace librbd { + +namespace watcher { +namespace util { +template struct HandlePayloadVisitor; +} // namespace util +} // namespace watcher + +template +class TrashWatcher : public Watcher { + friend struct watcher::util::HandlePayloadVisitor>; +public: + TrashWatcher(librados::IoCtx &io_ctx, ContextWQ *work_queue); + + static void notify_image_added(librados::IoCtx &io_ctx, + const std::string& image_id, + const cls::rbd::TrashImageSpec& spec, + Context *on_finish); + static void notify_image_removed(librados::IoCtx &io_ctx, + const std::string& image_id, + Context *on_finish); + +protected: + virtual void handle_image_added(const std::string &image_id, + const cls::rbd::TrashImageSpec& spec) = 0; + virtual void handle_image_removed(const std::string &image_id) = 0; + +private: + void handle_notify(uint64_t notify_id, uint64_t handle, + uint64_t notifier_id, bufferlist &bl) override; + + bool handle_payload(const trash_watcher::ImageAddedPayload &payload, + Context *on_notify_ack); + bool handle_payload(const trash_watcher::ImageRemovedPayload &payload, + Context *on_notify_ack); + bool handle_payload(const trash_watcher::UnknownPayload &payload, + Context *on_notify_ack); +}; + +} // namespace librbd + +extern template class librbd::TrashWatcher; + +#endif // CEPH_LIBRBD_TRASH_WATCHER_H diff --git a/src/librbd/trash_watcher/Types.cc b/src/librbd/trash_watcher/Types.cc new file mode 100644 index 0000000000000..1cbfe7a1bf834 --- /dev/null +++ b/src/librbd/trash_watcher/Types.cc @@ -0,0 +1,126 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "common/Formatter.h" +#include "include/assert.h" +#include "include/stringify.h" +#include "librbd/trash_watcher/Types.h" +#include "librbd/watcher/Utils.h" + +namespace librbd { +namespace trash_watcher { + +namespace { + +class DumpPayloadVisitor : public boost::static_visitor { +public: + explicit DumpPayloadVisitor(Formatter *formatter) : m_formatter(formatter) {} + + template + inline void operator()(const Payload &payload) const { + NotifyOp notify_op = Payload::NOTIFY_OP; + m_formatter->dump_string("notify_op", stringify(notify_op)); + payload.dump(m_formatter); + } + +private: + ceph::Formatter *m_formatter; +}; + +} // anonymous namespace + +void ImageAddedPayload::encode(bufferlist &bl) const { + ::encode(image_id, bl); + ::encode(trash_image_spec, bl); +} + +void ImageAddedPayload::decode(__u8 version, bufferlist::iterator &iter) { + ::decode(image_id, iter); + ::decode(trash_image_spec, iter); +} + +void ImageAddedPayload::dump(Formatter *f) const { + f->dump_string("image_id", image_id); + f->open_object_section("trash_image_spec"); + trash_image_spec.dump(f); + f->close_section(); +} + +void ImageRemovedPayload::encode(bufferlist &bl) const { + ::encode(image_id, bl); +} + +void ImageRemovedPayload::decode(__u8 version, bufferlist::iterator &iter) { + ::decode(image_id, iter); +} + +void ImageRemovedPayload::dump(Formatter *f) const { + f->dump_string("image_id", image_id); +} + +void UnknownPayload::encode(bufferlist &bl) const { + ceph_abort(); +} + +void UnknownPayload::decode(__u8 version, bufferlist::iterator &iter) { +} + +void UnknownPayload::dump(Formatter *f) const { +} + +void NotifyMessage::encode(bufferlist& bl) const { + ENCODE_START(1, 1, bl); + boost::apply_visitor(watcher::util::EncodePayloadVisitor(bl), payload); + ENCODE_FINISH(bl); +} + +void NotifyMessage::decode(bufferlist::iterator& iter) { + DECODE_START(1, iter); + + uint32_t notify_op; + ::decode(notify_op, iter); + + // select the correct payload variant based upon the encoded op + switch (notify_op) { + case NOTIFY_OP_IMAGE_ADDED: + payload = ImageAddedPayload(); + break; + case NOTIFY_OP_IMAGE_REMOVED: + payload = ImageRemovedPayload(); + break; + default: + payload = UnknownPayload(); + break; + } + + apply_visitor(watcher::util::DecodePayloadVisitor(struct_v, iter), payload); + DECODE_FINISH(iter); +} + +void NotifyMessage::dump(Formatter *f) const { + apply_visitor(DumpPayloadVisitor(f), payload); +} + +void NotifyMessage::generate_test_instances(std::list &o) { + o.push_back(new NotifyMessage{ImageAddedPayload{ + "id", {cls::rbd::TRASH_IMAGE_SOURCE_USER, "name", {}, {}}}}); + o.push_back(new NotifyMessage{ImageRemovedPayload{"id"}}); +} + +std::ostream &operator<<(std::ostream &out, const NotifyOp &op) { + switch (op) { + case NOTIFY_OP_IMAGE_ADDED: + out << "ImageAdded"; + break; + case NOTIFY_OP_IMAGE_REMOVED: + out << "ImageRemoved"; + break; + default: + out << "Unknown (" << static_cast(op) << ")"; + break; + } + return out; +} + +} // namespace trash_watcher +} // namespace librbd diff --git a/src/librbd/trash_watcher/Types.h b/src/librbd/trash_watcher/Types.h new file mode 100644 index 0000000000000..3f0040cd69d6b --- /dev/null +++ b/src/librbd/trash_watcher/Types.h @@ -0,0 +1,97 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_LIBRBD_TRASH_WATCHER_TYPES_H +#define CEPH_LIBRBD_TRASH_WATCHER_TYPES_H + +#include "include/int_types.h" +#include "include/buffer_fwd.h" +#include "include/encoding.h" +#include "cls/rbd/cls_rbd_types.h" +#include +#include +#include +#include + + +namespace librbd { +namespace trash_watcher { + +enum NotifyOp { + NOTIFY_OP_IMAGE_ADDED = 0, + NOTIFY_OP_IMAGE_REMOVED = 1 +}; + +struct ImageAddedPayload { + static const NotifyOp NOTIFY_OP = NOTIFY_OP_IMAGE_ADDED; + + std::string image_id; + cls::rbd::TrashImageSpec trash_image_spec; + + ImageAddedPayload() { + } + ImageAddedPayload(const std::string& image_id, + const cls::rbd::TrashImageSpec& trash_image_spec) + : image_id(image_id), trash_image_spec(trash_image_spec) { + } + + void encode(bufferlist &bl) const; + void decode(__u8 version, bufferlist::iterator &iter); + void dump(Formatter *f) const; +}; + +struct ImageRemovedPayload { + static const NotifyOp NOTIFY_OP = NOTIFY_OP_IMAGE_REMOVED; + + std::string image_id; + + ImageRemovedPayload() { + } + ImageRemovedPayload(const std::string& image_id) + : image_id(image_id) { + } + + void encode(bufferlist &bl) const; + void decode(__u8 version, bufferlist::iterator &iter); + void dump(Formatter *f) const; +}; + +struct UnknownPayload { + static const NotifyOp NOTIFY_OP = static_cast(-1); + + UnknownPayload() { + } + + void encode(bufferlist &bl) const; + void decode(__u8 version, bufferlist::iterator &iter); + void dump(Formatter *f) const; +}; + +typedef boost::variant Payload; + +struct NotifyMessage { + NotifyMessage(const Payload &payload = UnknownPayload()) : payload(payload) { + } + + Payload payload; + + void encode(bufferlist& bl) const; + void decode(bufferlist::iterator& it); + void dump(Formatter *f) const; + + static void generate_test_instances(std::list &o); +}; + +WRITE_CLASS_ENCODER(NotifyMessage); + +std::ostream &operator<<(std::ostream &out, const NotifyOp &op); + +} // namespace trash_watcher +} // namespace librbd + +using librbd::trash_watcher::encode; +using librbd::trash_watcher::decode; + +#endif // CEPH_LIBRBD_TRASH_WATCHER_TYPES_H diff --git a/src/test/encoding/types.h b/src/test/encoding/types.h index 4cfcb15edff1b..df83d84f3616b 100644 --- a/src/test/encoding/types.h +++ b/src/test/encoding/types.h @@ -274,6 +274,8 @@ TYPE(librbd::journal::ClientData) TYPE(librbd::journal::TagData) #include "librbd/mirroring_watcher/Types.h" TYPE(librbd::mirroring_watcher::NotifyMessage) +#include "librbd/trash_watcher/Types.h" +TYPE(librbd::mirroring_watcher::NotifyMessage) #include "librbd/WatchNotifyTypes.h" TYPE(librbd::watch_notify::NotifyMessage) TYPE(librbd::watch_notify::ResponseMessage) diff --git a/src/test/librbd/CMakeLists.txt b/src/test/librbd/CMakeLists.txt index 4d3761c760a8c..28ad02a83285a 100644 --- a/src/test/librbd/CMakeLists.txt +++ b/src/test/librbd/CMakeLists.txt @@ -33,6 +33,7 @@ set(unittest_librbd_srcs test_mock_Journal.cc test_mock_ManagedLock.cc test_mock_ObjectMap.cc + test_mock_TrashWatcher.cc deep_copy/test_mock_ImageCopyRequest.cc deep_copy/test_mock_MetadataCopyRequest.cc deep_copy/test_mock_ObjectCopyRequest.cc diff --git a/src/test/librbd/test_mock_TrashWatcher.cc b/src/test/librbd/test_mock_TrashWatcher.cc new file mode 100644 index 0000000000000..d7f3c679e2d1a --- /dev/null +++ b/src/test/librbd/test_mock_TrashWatcher.cc @@ -0,0 +1,95 @@ +// -*- 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 "librbd/TrashWatcher.h" +#include "gtest/gtest.h" +#include "gmock/gmock.h" +#include + +namespace librbd { +namespace { + +struct MockTrashWatcher : public TrashWatcher<> { + MockTrashWatcher(ImageCtx &image_ctx) + : TrashWatcher<>(image_ctx.md_ctx, image_ctx.op_work_queue) { + } + + MOCK_METHOD2(handle_image_added, void(const std::string&, + const cls::rbd::TrashImageSpec&)); + MOCK_METHOD1(handle_image_removed, void(const std::string&)); +}; + +} // anonymous namespace + +using ::testing::_; +using ::testing::AtLeast; +using ::testing::StrEq; + +class TestTrashWatcher : public TestMockFixture { +public: + void SetUp() override { + TestFixture::SetUp(); + + bufferlist bl; + ASSERT_EQ(0, m_ioctx.write_full(RBD_TRASH, bl)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + m_trash_watcher = new MockTrashWatcher(*ictx); + + C_SaferCond ctx; + m_trash_watcher->register_watch(&ctx); + if (ctx.wait() != 0) { + delete m_trash_watcher; + m_trash_watcher = nullptr; + FAIL(); + } + } + + void TearDown() override { + if (m_trash_watcher != nullptr) { + C_SaferCond ctx; + m_trash_watcher->unregister_watch(&ctx); + ASSERT_EQ(0, ctx.wait()); + delete m_trash_watcher; + } + + TestFixture::TearDown(); + } + + MockTrashWatcher *m_trash_watcher = nullptr; +}; + +TEST_F(TestTrashWatcher, ImageAdded) { + REQUIRE_FORMAT_V2(); + + cls::rbd::TrashImageSpec trash_image_spec{ + cls::rbd::TRASH_IMAGE_SOURCE_USER, "image name", + ceph_clock_now(), ceph_clock_now()}; + + EXPECT_CALL(*m_trash_watcher, handle_image_added(StrEq("image id"), + trash_image_spec)) + .Times(AtLeast(1)); + + C_SaferCond ctx; + MockTrashWatcher::notify_image_added(m_ioctx, "image id", trash_image_spec, + &ctx); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestTrashWatcher, ImageRemoved) { + REQUIRE_FORMAT_V2(); + + EXPECT_CALL(*m_trash_watcher, handle_image_removed(StrEq("image id"))) + .Times(AtLeast(1)); + + C_SaferCond ctx; + MockTrashWatcher::notify_image_removed(m_ioctx, "image id", &ctx); + ASSERT_EQ(0, ctx.wait()); +} + +} // namespace librbd -- 2.39.5