MockListener(TestMockPoolWatcher *test) : test(test) {
}
- MOCK_METHOD2(handle_update, void(const ImageIds &, const ImageIds &));
+ MOCK_METHOD3(handle_update, void(const std::string &, const ImageIds &,
+ const ImageIds &));
};
TestMockPoolWatcher() : m_lock("TestMockPoolWatcher::m_lock") {
}
void expect_listener_handle_update(MockListener &mock_listener,
+ const std::string &mirror_uuid,
const ImageIds &added_image_ids,
const ImageIds &removed_image_ids) {
- EXPECT_CALL(mock_listener, handle_update(added_image_ids, removed_image_ids))
+ EXPECT_CALL(mock_listener, handle_update(mirror_uuid, added_image_ids,
+ removed_image_ids))
.WillOnce(WithoutArgs(Invoke([this]() {
Mutex::Locker locker(m_lock);
++m_update_count;
})));
}
+ void expect_mirror_uuid_get(librados::IoCtx &io_ctx,
+ const std::string &uuid, int r) {
+ bufferlist out_bl;
+ ::encode(uuid, out_bl);
+
+ EXPECT_CALL(get_mock_io_ctx(io_ctx),
+ exec(RBD_MIRRORING, _, StrEq("rbd"), StrEq("mirror_uuid_get"),
+ _, _, _))
+ .WillOnce(DoAll(WithArg<5>(Invoke([this, out_bl](bufferlist *bl) {
+ *bl = out_bl;
+ })),
+ Return(r)));
+ }
+
void expect_dir_list(librados::IoCtx &io_ctx,
const std::string &id, const std::string &name, int r) {
bufferlist in_bl;
MockRefreshImagesRequest mock_refresh_images_request;
expect_refresh_images(mock_refresh_images_request, {}, 0);
+ expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0);
MockListener mock_listener(this);
- expect_listener_handle_update(mock_listener, {}, {});
+ expect_listener_handle_update(mock_listener, "remote uuid", {}, {});
MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx,
mock_listener);
{"global id 2", "remote id 2", "image name 2"}};
MockRefreshImagesRequest mock_refresh_images_request;
expect_refresh_images(mock_refresh_images_request, image_ids, 0);
+ expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0);
MockListener mock_listener(this);
- expect_listener_handle_update(mock_listener, image_ids, {});
+ expect_listener_handle_update(mock_listener, "remote uuid", image_ids, {});
MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx,
mock_listener);
expect_dir_list(m_remote_io_ctx, "remote id 3", "image name 3", 0);
expect_dir_list(m_remote_io_ctx, "dummy", "", -ENOENT);
+ expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0);
+
MockListener mock_listener(this);
image_ids = {
{"global id 1", "remote id 1a", "image name 1a"},
{"global id 3", "remote id 3", "image name 3"}};
- expect_listener_handle_update(mock_listener, image_ids, {});
+ expect_listener_handle_update(mock_listener, "remote uuid", image_ids, {});
MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx,
mock_listener);
{"global id 2", "remote id 2", "image name 2"}};
MockRefreshImagesRequest mock_refresh_images_request;
expect_refresh_images(mock_refresh_images_request, image_ids, 0);
+ expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0);
+
EXPECT_CALL(*mock_threads.work_queue, queue(_, _))
.WillOnce(Invoke([this](Context *ctx, int r) {
m_threads->work_queue->queue(ctx, r);
}));
MockListener mock_listener(this);
- expect_listener_handle_update(mock_listener, image_ids, {});
+ expect_listener_handle_update(mock_listener, "remote uuid", image_ids, {});
Context *notify_ctx = nullptr;
EXPECT_CALL(*mock_threads.work_queue, queue(_, _))
expect_dir_list(m_remote_io_ctx, "remote id 3", "image name 3", 0);
expect_dir_list(m_remote_io_ctx, "dummy", "", -ENOENT);
expect_listener_handle_update(
- mock_listener,
+ mock_listener, "remote uuid",
{{"global id 1", "remote id 1a", "image name 1a"},
{"global id 3", "remote id 3", "image name 3"}},
{{"global id 1", "remote id 1", "image name 1"},
MockRefreshImagesRequest mock_refresh_images_request;
expect_refresh_images(mock_refresh_images_request, {}, 0);
+ expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0);
MockListener mock_listener(this);
- expect_listener_handle_update(mock_listener, {}, {});
+ expect_listener_handle_update(mock_listener, "remote uuid", {}, {});
MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx,
mock_listener);
MockRefreshImagesRequest mock_refresh_images_request;
expect_refresh_images(mock_refresh_images_request, {}, 0);
+ expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0);
MockListener mock_listener(this);
- expect_listener_handle_update(mock_listener, {}, {});
+ expect_listener_handle_update(mock_listener, "remote uuid", {}, {});
MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx,
mock_listener);
MockRefreshImagesRequest mock_refresh_images_request;
expect_refresh_images(mock_refresh_images_request, {}, -ENOENT);
+ expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0);
MockListener mock_listener(this);
- expect_listener_handle_update(mock_listener, {}, {});
+ expect_listener_handle_update(mock_listener, "remote uuid", {}, {});
MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx,
mock_listener);
expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, false);
expect_refresh_images(mock_refresh_images_request, {}, 0);
+ expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0);
+
+ MockListener mock_listener(this);
+ expect_listener_handle_update(mock_listener, "remote uuid", {}, {});
+
+ MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx,
+ mock_listener);
+ C_SaferCond ctx;
+ mock_pool_watcher.init(&ctx);
+ ASSERT_EQ(0, ctx.wait());
+
+ ASSERT_TRUE(wait_for_update(1));
+ expect_mirroring_watcher_unregister(mock_mirroring_watcher, 0);
+ ASSERT_EQ(0, when_shut_down(mock_pool_watcher));
+}
+
+TEST_F(TestMockPoolWatcher, GetMirrorUuidBlacklist) {
+ MockThreads mock_threads(m_threads);
+ expect_work_queue(mock_threads);
+
+ InSequence seq;
+ MockMirroringWatcher mock_mirroring_watcher;
+ expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, true);
+ expect_mirroring_watcher_register(mock_mirroring_watcher, 0);
+
+ MockRefreshImagesRequest mock_refresh_images_request;
+ expect_refresh_images(mock_refresh_images_request, {}, 0);
+ expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", -EBLACKLISTED);
+
+ MockListener mock_listener(this);
+ MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx,
+ mock_listener);
+ C_SaferCond ctx;
+ mock_pool_watcher.init(&ctx);
+ ASSERT_EQ(-EBLACKLISTED, ctx.wait());
+ ASSERT_TRUE(mock_pool_watcher.is_blacklisted());
+
+ expect_mirroring_watcher_unregister(mock_mirroring_watcher, 0);
+ ASSERT_EQ(0, when_shut_down(mock_pool_watcher));
+}
+
+TEST_F(TestMockPoolWatcher, GetMirrorUuidMissing) {
+ MockThreads mock_threads(m_threads);
+ expect_work_queue(mock_threads);
+
+ InSequence seq;
+ MockMirroringWatcher mock_mirroring_watcher;
+ expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, true);
+ expect_mirroring_watcher_register(mock_mirroring_watcher, 0);
+
+ MockRefreshImagesRequest mock_refresh_images_request;
+ expect_refresh_images(mock_refresh_images_request, {}, 0);
+ expect_mirror_uuid_get(m_remote_io_ctx, "", -ENOENT);
+ expect_timer_add_event(mock_threads);
+
+ expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, false);
+ expect_refresh_images(mock_refresh_images_request, {}, 0);
+ expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0);
MockListener mock_listener(this);
- expect_listener_handle_update(mock_listener, {}, {});
+ expect_listener_handle_update(mock_listener, "remote uuid", {}, {});
+
+ MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx,
+ mock_listener);
+ C_SaferCond ctx;
+ mock_pool_watcher.init(&ctx);
+ ASSERT_EQ(-ENOENT, ctx.wait());
+
+ ASSERT_TRUE(wait_for_update(1));
+ expect_mirroring_watcher_unregister(mock_mirroring_watcher, 0);
+ ASSERT_EQ(0, when_shut_down(mock_pool_watcher));
+}
+
+TEST_F(TestMockPoolWatcher, GetMirrorUuidError) {
+ MockThreads mock_threads(m_threads);
+ expect_work_queue(mock_threads);
+
+ InSequence seq;
+ MockMirroringWatcher mock_mirroring_watcher;
+ expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, true);
+ expect_mirroring_watcher_register(mock_mirroring_watcher, 0);
+
+ MockRefreshImagesRequest mock_refresh_images_request;
+ expect_refresh_images(mock_refresh_images_request, {}, 0);
+ expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", -EINVAL);
+ expect_timer_add_event(mock_threads);
+
+ expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, false);
+ expect_refresh_images(mock_refresh_images_request, {}, 0);
+ expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0);
+
+ MockListener mock_listener(this);
+ expect_listener_handle_update(mock_listener, "remote uuid", {}, {});
MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx,
mock_listener);
MockRefreshImagesRequest mock_refresh_images_request;
expect_refresh_images(mock_refresh_images_request, {}, 0);
+ expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0);
MockListener mock_listener(this);
- expect_listener_handle_update(mock_listener, {}, {});
+ expect_listener_handle_update(mock_listener, "remote uuid", {}, {});
expect_timer_add_event(mock_threads);
expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, false);
expect_refresh_images(mock_refresh_images_request, {{"global id", "image id", "name"}}, 0);
- expect_listener_handle_update(mock_listener, {{"global id", "image id", "name"}}, {});
+ expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0);
+ expect_listener_handle_update(mock_listener, "remote uuid",
+ {{"global id", "image id", "name"}}, {});
MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx,
mock_listener);
MockRefreshImagesRequest mock_refresh_images_request;
expect_refresh_images(mock_refresh_images_request, {}, 0);
+ expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0);
MockListener mock_listener(this);
- expect_listener_handle_update(mock_listener, {}, {});
+ expect_listener_handle_update(mock_listener, "remote uuid", {}, {});
MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx,
mock_listener);
MockRefreshImagesRequest mock_refresh_images_request;
expect_refresh_images(mock_refresh_images_request, {}, 0);
+ expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0);
MockListener mock_listener(this);
- expect_listener_handle_update(mock_listener, {}, {});
+ expect_listener_handle_update(mock_listener, "remote uuid", {}, {});
expect_timer_add_event(mock_threads);
expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, false);
expect_refresh_images(mock_refresh_images_request, {{"global id", "image id", "name"}}, 0);
- expect_listener_handle_update(mock_listener, {{"global id", "image id", "name"}}, {});
+ expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0);
+ expect_listener_handle_update(mock_listener, "remote uuid",
+ {{"global id", "image id", "name"}}, {});
MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx,
mock_listener);
MockRefreshImagesRequest mock_refresh_images_request;
expect_refresh_images(mock_refresh_images_request, {}, 0);
+ expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0);
MockListener mock_listener(this);
- expect_listener_handle_update(mock_listener, {}, {});
+ expect_listener_handle_update(mock_listener, "remote uuid", {}, {});
expect_dir_list(m_remote_io_ctx, "remote id", "image name", -EBLACKLISTED);
MockRefreshImagesRequest mock_refresh_images_request;
expect_refresh_images(mock_refresh_images_request, {}, 0);
+ expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0);
MockListener mock_listener(this);
- expect_listener_handle_update(mock_listener, {}, {});
+ expect_listener_handle_update(mock_listener, "remote uuid", {}, {});
expect_dir_list(m_remote_io_ctx, "remote id", "image name", -EINVAL);
expect_timer_add_event(mock_threads);
expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, false);
expect_refresh_images(mock_refresh_images_request, {{"global id", "remote id", "name"}}, 0);
- expect_listener_handle_update(mock_listener, {{"global id", "remote id", "name"}}, {});
+ expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0);
+ expect_listener_handle_update(mock_listener, "remote uuid",
+ {{"global id", "remote id", "name"}}, {});
MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx,
mock_listener);
MirroringWatcher::get_instance().handle_rewatch_complete(0);
mock_refresh_images_request.on_finish->complete(0);
}));
+ expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0);
expect_timer_add_event(mock_threads);
+
expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, false);
expect_refresh_images(mock_refresh_images_request, {}, 0);
+ expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0);
MockListener mock_listener(this);
- expect_listener_handle_update(mock_listener, {}, {});
+ expect_listener_handle_update(mock_listener, "remote uuid", {}, {});
MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx,
mock_listener);
C_SaferCond ctx;
mock_pool_watcher.init(&ctx);
ASSERT_EQ(0, ctx.wait());
-
ASSERT_TRUE(wait_for_update(1));
+
expect_mirroring_watcher_unregister(mock_mirroring_watcher, 0);
ASSERT_EQ(0, when_shut_down(mock_pool_watcher));
}
+TEST_F(TestMockPoolWatcher, MirrorUuidUpdated) {
+ MockThreads mock_threads(m_threads);
+ expect_work_queue(mock_threads);
+
+ InSequence seq;
+ MockMirroringWatcher mock_mirroring_watcher;
+ expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, true);
+ expect_mirroring_watcher_register(mock_mirroring_watcher, 0);
+
+ ImageIds image_ids{
+ {"global id 1", "remote id 1", "image name 1"},
+ {"global id 2", "remote id 2", "image name 2"}};
+ MockRefreshImagesRequest mock_refresh_images_request;
+ expect_refresh_images(mock_refresh_images_request, image_ids, 0);
+ expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0);
+
+ MockListener mock_listener(this);
+ expect_listener_handle_update(mock_listener, "remote uuid", image_ids, {});
+
+ MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx,
+ mock_listener);
+ C_SaferCond ctx;
+ mock_pool_watcher.init(&ctx);
+ ASSERT_EQ(0, ctx.wait());
+
+ ASSERT_TRUE(wait_for_update(1));
+
+ expect_timer_add_event(mock_threads);
+ ImageIds new_image_ids{
+ {"global id 1", "remote id 1", "image name 1"}};
+ expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, false);
+ expect_refresh_images(mock_refresh_images_request, new_image_ids, 0);
+ expect_mirror_uuid_get(m_remote_io_ctx, "updated uuid", 0);
+ expect_listener_handle_update(mock_listener, "remote uuid", {}, image_ids);
+ expect_listener_handle_update(mock_listener, "updated uuid", new_image_ids,
+ {});
+
+ MirroringWatcher::get_instance().handle_rewatch_complete(0);
+ ASSERT_TRUE(wait_for_update(2));
+
+ expect_mirroring_watcher_unregister(mock_mirroring_watcher, 0);
+ ASSERT_EQ(0, when_shut_down(mock_pool_watcher));
+}
} // namespace mirror
} // namespace rbd
}
void handle_mode_updated(cls::rbd::MirrorMode mirror_mode) override {
- // do nothing
+ // invalidate all image state and refresh the pool contents
+ m_pool_watcher->schedule_refresh_images(5);
}
void handle_image_updated(cls::rbd::MirrorImageState state,
void PoolWatcher<I>::handle_refresh_images(int r) {
dout(5) << "r=" << r << dendl;
+ bool retry_refresh = false;
+ Context *on_init_finish = nullptr;
+ {
+ Mutex::Locker locker(m_lock);
+ assert(m_image_ids_invalid);
+ assert(m_refresh_in_progress);
+
+ if (r >= 0) {
+ m_pending_image_ids = std::move(m_refresh_image_ids);
+ } else if (r == -EBLACKLISTED) {
+ dout(0) << "detected client is blacklisted during image refresh" << dendl;
+
+ m_blacklisted = true;
+ m_refresh_in_progress = false;
+ std::swap(on_init_finish, m_on_init_finish);
+ } else if (r == -ENOENT) {
+ dout(5) << "mirroring directory not found" << dendl;
+ m_pending_image_ids.clear();
+ r = 0;
+ } else {
+ m_refresh_in_progress = false;
+ retry_refresh = true;
+ }
+ }
+
+ if (retry_refresh) {
+ derr << "failed to retrieve mirroring directory: " << cpp_strerror(r)
+ << dendl;
+ schedule_refresh_images(10);
+ } else if (r >= 0) {
+ get_mirror_uuid();
+ return;
+ }
+
+ m_async_op_tracker.finish_op();
+ if (on_init_finish != nullptr) {
+ assert(r == -EBLACKLISTED);
+ on_init_finish->complete(r);
+ }
+}
+
+template <typename I>
+void PoolWatcher<I>::get_mirror_uuid() {
+ dout(5) << dendl;
+
+ librados::ObjectReadOperation op;
+ librbd::cls_client::mirror_uuid_get_start(&op);
+
+ m_out_bl.clear();
+ librados::AioCompletion *aio_comp = create_rados_callback<
+ PoolWatcher, &PoolWatcher<I>::handle_get_mirror_uuid>(this);
+ int r = m_remote_io_ctx.aio_operate(RBD_MIRRORING, aio_comp, &op, &m_out_bl);
+ assert(r == 0);
+ aio_comp->release();
+}
+
+template <typename I>
+void PoolWatcher<I>::handle_get_mirror_uuid(int r) {
+ dout(5) << "r=" << r << dendl;
+
bool deferred_refresh = false;
bool retry_refresh = false;
Context *on_init_finish = nullptr;
assert(m_refresh_in_progress);
m_refresh_in_progress = false;
+ m_pending_mirror_uuid = "";
+ if (r >= 0) {
+ bufferlist::iterator it = m_out_bl.begin();
+ r = librbd::cls_client::mirror_uuid_get_finish(
+ &it, &m_pending_mirror_uuid);
+ }
+ if (r >= 0 && m_pending_mirror_uuid.empty()) {
+ r = -ENOENT;
+ }
+
if (m_deferred_refresh) {
// need to refresh -- skip the notification
deferred_refresh = true;
} else if (r >= 0) {
+ dout(10) << "mirror_uuid=" << m_pending_mirror_uuid << dendl;
m_image_ids_invalid = false;
- m_pending_image_ids = m_refresh_image_ids;
std::swap(on_init_finish, m_on_init_finish);
schedule_listener();
} else if (r == -EBLACKLISTED) {
m_blacklisted = true;
std::swap(on_init_finish, m_on_init_finish);
} else if (r == -ENOENT) {
- dout(5) << "mirroring directory not found" << dendl;
- m_image_ids_invalid = false;
- m_pending_image_ids.clear();
+ dout(5) << "mirroring uuid not found" << dendl;
std::swap(on_init_finish, m_on_init_finish);
- r = 0;
- schedule_listener();
+ retry_refresh = true;
} else {
retry_refresh = true;
}
dout(5) << "scheduling deferred refresh" << dendl;
schedule_refresh_images(0);
} else if (retry_refresh) {
- derr << "failed to retrieve mirroring directory: " << cpp_strerror(r)
+ derr << "failed to retrieve mirror uuid: " << cpp_strerror(r)
<< dendl;
schedule_refresh_images(10);
}
void PoolWatcher<I>::notify_listener() {
dout(10) << dendl;
+ std::string mirror_uuid;
ImageIds added_image_ids;
ImageIds removed_image_ids;
+ {
+ Mutex::Locker locker(m_lock);
+ assert(m_notify_listener_in_progress);
+
+ // if the mirror uuid is updated, treat it as the removal of all
+ // images in the pool
+ if (m_mirror_uuid != m_pending_mirror_uuid) {
+ if (!m_mirror_uuid.empty()) {
+ dout(0) << "mirror uuid updated:"
+ << "old=" << m_mirror_uuid << ", "
+ << "new=" << m_pending_mirror_uuid << dendl;
+ }
+
+ mirror_uuid = m_mirror_uuid;
+ removed_image_ids = std::move(m_image_ids);
+ m_image_ids.clear();
+ }
+ }
+
+ if (!removed_image_ids.empty()) {
+ m_listener.handle_update(mirror_uuid, {}, removed_image_ids);
+ removed_image_ids.clear();
+ }
+
{
Mutex::Locker locker(m_lock);
assert(m_notify_listener_in_progress);
m_pending_updates = false;
m_image_ids = m_pending_image_ids;
+
+ m_mirror_uuid = m_pending_mirror_uuid;
+ mirror_uuid = m_mirror_uuid;
}
- m_listener.handle_update(added_image_ids, removed_image_ids);
+ m_listener.handle_update(mirror_uuid, added_image_ids, removed_image_ids);
{
Mutex::Locker locker(m_lock);