// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab #include "test/rbd_mirror/test_mock_fixture.h" #include "test/librados_test_stub/MockTestMemIoCtxImpl.h" #include "test/librados_test_stub/MockTestMemRadosClient.h" #include "test/librbd/mock/MockImageCtx.h" #include "test/rbd_mirror/mock/MockContextWQ.h" #include "test/rbd_mirror/mock/MockSafeTimer.h" #include "librbd/MirroringWatcher.h" #include "tools/rbd_mirror/Threads.h" #include "tools/rbd_mirror/ImageMap.h" #include "tools/rbd_mirror/image_map/LoadRequest.h" #include "tools/rbd_mirror/image_map/UpdateRequest.h" #include "tools/rbd_mirror/image_map/Types.h" #include "include/stringify.h" namespace librbd { namespace { struct MockTestImageCtx : public librbd::MockImageCtx { MockTestImageCtx(librbd::ImageCtx &image_ctx) : librbd::MockImageCtx(image_ctx) { } }; } // anonymous namespace } // namespace librbd namespace rbd { namespace mirror { template <> struct Threads { MockSafeTimer *timer; ceph::mutex &timer_lock; MockContextWQ *work_queue; Threads(Threads *threads) : timer(new MockSafeTimer()), timer_lock(threads->timer_lock), work_queue(new MockContextWQ()) { } ~Threads() { delete timer; delete work_queue; } }; namespace image_map { template <> struct LoadRequest { std::map *image_map; Context *on_finish = nullptr; static LoadRequest *s_instance; static LoadRequest *create(librados::IoCtx &ioctx, std::map *image_map, Context *on_finish) { ceph_assert(s_instance != nullptr); s_instance->image_map = image_map; s_instance->on_finish = on_finish; return s_instance; } MOCK_METHOD0(send, void()); LoadRequest() { s_instance = this; } }; template <> struct UpdateRequest { Context *on_finish = nullptr; static UpdateRequest *s_instance; static UpdateRequest *create(librados::IoCtx &ioctx, std::map &&update_mapping, std::set &&global_image_ids, Context *on_finish) { ceph_assert(s_instance != nullptr); s_instance->on_finish = on_finish; return s_instance; } MOCK_METHOD0(send, void()); UpdateRequest() { s_instance = this; } }; LoadRequest * LoadRequest::s_instance = nullptr; UpdateRequest * UpdateRequest::s_instance = nullptr; } // namespace image_map } // namespace mirror } // namespace rbd // template definitions #include "tools/rbd_mirror/ImageMap.cc" namespace rbd { namespace mirror { using ::testing::_; using ::testing::DoAll; using ::testing::WithArg; using ::testing::AtLeast; using ::testing::InSequence; using ::testing::Invoke; using ::testing::ReturnArg; using ::testing::StrEq; using image_map::Listener; using image_map::LoadRequest; using image_map::UpdateRequest; using ::rbd::mirror::Threads; class TestMockImageMap : public TestMockFixture { public: typedef Threads MockThreads; typedef ImageMap MockImageMap; typedef LoadRequest MockLoadRequest; typedef UpdateRequest MockUpdateRequest; struct MockListener : Listener { TestMockImageMap *test_mock_image_map; MockListener(TestMockImageMap *test_mock_image_map) : test_mock_image_map(test_mock_image_map) { } MOCK_METHOD2(mock_acquire_image, void(const std::string &, Context*)); MOCK_METHOD2(mock_release_image, void(const std::string &, Context*)); MOCK_METHOD3(mock_remove_image, void(const std::string &, const std::string &, Context*)); void acquire_image(const std::string &global_image_id, const std::string &instance_id, Context* on_finish) { mock_acquire_image(global_image_id, on_finish); } void release_image(const std::string &global_image_id, const std::string &instance_id, Context* on_finish) { mock_release_image(global_image_id, on_finish); } void remove_image(const std::string &mirror_uuid, const std::string &global_image_id, const std::string &instance_id, Context* on_finish) { mock_remove_image(mirror_uuid, global_image_id, on_finish); } }; TestMockImageMap() = default; void SetUp() override { TestFixture::SetUp(); m_local_instance_id = stringify(m_local_io_ctx.get_instance_id()); EXPECT_EQ(0, _rados->conf_set("rbd_mirror_image_policy_migration_throttle", "0")); EXPECT_EQ(0, _rados->conf_set("rbd_mirror_image_policy_type", "simple")); } void TearDown() override { EXPECT_EQ(0, _rados->conf_set("rbd_mirror_image_policy_type", "none")); TestFixture::TearDown(); } void expect_work_queue(MockThreads &mock_threads) { EXPECT_CALL(*mock_threads.work_queue, queue(_, _)) .WillRepeatedly(Invoke([this](Context *ctx, int r) { m_threads->work_queue->queue(ctx, r); })); } void expect_add_event(MockThreads &mock_threads) { EXPECT_CALL(*mock_threads.timer, add_event_after(_,_)) .WillOnce(DoAll(WithArg<1>(Invoke([this](Context *ctx) { auto wrapped_ctx = new LambdaContext([this, ctx](int r) { std::lock_guard timer_locker{m_threads->timer_lock}; ctx->complete(r); }); m_threads->work_queue->queue(wrapped_ctx, 0); })), ReturnArg<1>())); } void expect_rebalance_event(MockThreads &mock_threads) { EXPECT_CALL(*mock_threads.timer, add_event_after(_,_)) .WillOnce(DoAll(WithArg<1>(Invoke([this](Context *ctx) { // disable rebalance so as to not reschedule it again CephContext *cct = reinterpret_cast(m_local_io_ctx.cct()); cct->_conf.set_val("rbd_mirror_image_policy_rebalance_timeout", "0"); auto wrapped_ctx = new LambdaContext([this, ctx](int r) { std::lock_guard timer_locker{m_threads->timer_lock}; ctx->complete(r); }); m_threads->work_queue->queue(wrapped_ctx, 0); })), ReturnArg<1>())); } void expect_load_request(MockLoadRequest &request, int r) { EXPECT_CALL(request, send()) .WillOnce(Invoke([&request, r]() { request.on_finish->complete(r); })); } void expect_update_request(MockUpdateRequest &request, int r) { EXPECT_CALL(request, send()) .WillOnce(Invoke([this, &request, r]() { request.on_finish->complete(r); if (r == 0) { std::lock_guard locker{m_lock}; ++m_map_update_count; m_cond.notify_all(); } })); } void expect_listener_acquire_image(MockListener &mock_listener, const std::string &global_image_id, std::map *peer_ack_ctxs) { EXPECT_CALL(mock_listener, mock_acquire_image(global_image_id, _)) .WillOnce(WithArg<1>(Invoke([this, global_image_id, peer_ack_ctxs](Context* ctx) { std::lock_guard locker{m_lock}; peer_ack_ctxs->insert({global_image_id, ctx}); ++m_notify_update_count; m_cond.notify_all(); }))); } void expect_listener_release_image(MockListener &mock_listener, const std::string &global_image_id, std::map *peer_ack_ctxs) { EXPECT_CALL(mock_listener, mock_release_image(global_image_id, _)) .WillOnce(WithArg<1>(Invoke([this, global_image_id, peer_ack_ctxs](Context* ctx) { std::lock_guard locker{m_lock}; peer_ack_ctxs->insert({global_image_id, ctx}); ++m_notify_update_count; m_cond.notify_all(); }))); } void expect_listener_remove_image(MockListener &mock_listener, const std::string &mirror_uuid, const std::string &global_image_id, std::map *peer_ack_ctxs) { EXPECT_CALL(mock_listener, mock_remove_image(mirror_uuid, global_image_id, _)) .WillOnce(WithArg<2>(Invoke([this, global_image_id, peer_ack_ctxs](Context* ctx) { std::lock_guard locker{m_lock}; peer_ack_ctxs->insert({global_image_id, ctx}); ++m_notify_update_count; m_cond.notify_all(); }))); } void expect_listener_images_unmapped(MockListener &mock_listener, size_t count, std::set *global_image_ids, std::map *peer_ack_ctxs) { EXPECT_CALL(mock_listener, mock_release_image(_, _)) .Times(count) .WillRepeatedly(Invoke([this, global_image_ids, peer_ack_ctxs](std::string global_image_id, Context* ctx) { std::lock_guard locker{m_lock}; global_image_ids->emplace(global_image_id); peer_ack_ctxs->insert({global_image_id, ctx}); ++m_notify_update_count; m_cond.notify_all(); })); } void remote_peer_ack_nowait(MockImageMap *image_map, const std::set &global_image_ids, int ret, std::map *peer_ack_ctxs) { for (auto& global_image_id : global_image_ids) { auto it = peer_ack_ctxs->find(global_image_id); ASSERT_TRUE(it != peer_ack_ctxs->end()); auto ack_ctx = it->second; peer_ack_ctxs->erase(it); ack_ctx->complete(ret); wait_for_scheduled_task(); } } void remote_peer_ack_wait(MockImageMap *image_map, const std::set &global_image_ids, int ret, std::map *peer_ack_ctxs) { for (auto& global_image_id : global_image_ids) { auto it = peer_ack_ctxs->find(global_image_id); ASSERT_TRUE(it != peer_ack_ctxs->end()); auto ack_ctx = it->second; peer_ack_ctxs->erase(it); ack_ctx->complete(ret); wait_for_scheduled_task(); ASSERT_TRUE(wait_for_map_update(1)); } } void remote_peer_ack_listener_wait(MockImageMap *image_map, const std::set &global_image_ids, int ret, std::map *peer_ack_ctxs) { for (auto& global_image_id : global_image_ids) { auto it = peer_ack_ctxs->find(global_image_id); ASSERT_TRUE(it != peer_ack_ctxs->end()); auto ack_ctx = it->second; peer_ack_ctxs->erase(it); ack_ctx->complete(ret); ASSERT_TRUE(wait_for_map_update(1)); ASSERT_TRUE(wait_for_listener_notify(1)); } } void update_map_and_acquire(MockThreads &mock_threads, MockUpdateRequest &mock_update_request, MockListener &mock_listener, const std::set &global_image_ids, int ret, std::map *peer_ack_ctxs) { for (auto const &global_image_id : global_image_ids) { expect_add_event(mock_threads); expect_update_request(mock_update_request, ret); expect_add_event(mock_threads); expect_listener_acquire_image(mock_listener, global_image_id, peer_ack_ctxs); } } void update_map_request(MockThreads &mock_threads, MockUpdateRequest &mock_update_request, const std::set &global_image_ids, int ret) { for (uint32_t i = 0; i < global_image_ids.size(); ++i) { expect_add_event(mock_threads); expect_update_request(mock_update_request, ret); } } void wait_for_scheduled_task() { m_threads->work_queue->drain(); } bool wait_for_listener_notify(uint32_t count) { std::unique_lock locker{m_lock}; while (m_notify_update_count < count) { if (m_cond.wait_for(locker, 10s) == std::cv_status::timeout) { break; } } if (m_notify_update_count < count) { return false; } m_notify_update_count -= count; return true; } bool wait_for_map_update(uint32_t count) { std::unique_lock locker{m_lock}; while (m_map_update_count < count) { if (m_cond.wait_for(locker, 10s) == std::cv_status::timeout) { break; } } if (m_map_update_count < count) { return false; } m_map_update_count -= count; return true; } int when_shut_down(MockImageMap *image_map) { C_SaferCond ctx; image_map->shut_down(&ctx); return ctx.wait(); } void listener_acquire_images(MockListener &mock_listener, const std::set &global_image_ids, std::map *peer_ack_ctxs) { for (auto const &global_image_id : global_image_ids) { expect_listener_acquire_image(mock_listener, global_image_id, peer_ack_ctxs); } } void listener_release_images(MockListener &mock_listener, const std::set &global_image_ids, std::map *peer_ack_ctxs) { for (auto const &global_image_id : global_image_ids) { expect_listener_release_image(mock_listener, global_image_id, peer_ack_ctxs); } } void listener_remove_images(MockListener &mock_listener, const std::string &mirror_uuid, std::set &global_image_ids, std::map *peer_ack_ctxs) { for (auto const &global_image_id : global_image_ids) { expect_listener_remove_image(mock_listener, mirror_uuid, global_image_id, peer_ack_ctxs); } } ceph::mutex m_lock = ceph::make_mutex("TestMockImageMap::m_lock"); ceph::condition_variable m_cond; uint32_t m_notify_update_count = 0; uint32_t m_map_update_count = 0; std::string m_local_instance_id; }; TEST_F(TestMockImageMap, SetLocalImages) { MockThreads mock_threads(m_threads); expect_work_queue(mock_threads); InSequence seq; MockLoadRequest mock_load_request; expect_load_request(mock_load_request, 0); MockListener mock_listener(this); std::unique_ptr mock_image_map{ MockImageMap::create(m_local_io_ctx, &mock_threads, m_local_instance_id, mock_listener)}; C_SaferCond cond; mock_image_map->init(&cond); ASSERT_EQ(0, cond.wait()); std::set global_image_ids{ "global id 1", "global id 2" }; std::set global_image_ids_ack(global_image_ids); // UPDATE_MAPPING+ACQUIRE expect_add_event(mock_threads); MockUpdateRequest mock_update_request; expect_update_request(mock_update_request, 0); expect_add_event(mock_threads); std::map peer_ack_ctxs; listener_acquire_images(mock_listener, global_image_ids, &peer_ack_ctxs); // initial image list mock_image_map->update_images("", std::move(global_image_ids), {}); ASSERT_TRUE(wait_for_map_update(1)); ASSERT_TRUE(wait_for_listener_notify(global_image_ids_ack.size())); // remote peer ACKs image acquire request remote_peer_ack_nowait(mock_image_map.get(), global_image_ids_ack, 0, &peer_ack_ctxs); wait_for_scheduled_task(); ASSERT_EQ(0, when_shut_down(mock_image_map.get())); } TEST_F(TestMockImageMap, AddRemoveLocalImage) { MockThreads mock_threads(m_threads); expect_work_queue(mock_threads); InSequence seq; MockLoadRequest mock_load_request; expect_load_request(mock_load_request, 0); MockListener mock_listener(this); std::unique_ptr mock_image_map{ MockImageMap::create(m_local_io_ctx, &mock_threads, m_local_instance_id, mock_listener)}; C_SaferCond cond; mock_image_map->init(&cond); ASSERT_EQ(0, cond.wait()); std::set initial_global_image_ids{ "global id 1", "global id 2" }; std::set initial_global_image_ids_ack(initial_global_image_ids); std::set remove_global_image_ids{ "global id 1", "global id 2" }; std::set remove_global_image_ids_ack(remove_global_image_ids); // UPDATE_MAPPING+ACQUIRE expect_add_event(mock_threads); MockUpdateRequest mock_update_request; expect_update_request(mock_update_request, 0); expect_add_event(mock_threads); std::map peer_ack_ctxs; listener_acquire_images(mock_listener, initial_global_image_ids, &peer_ack_ctxs); // initial image list mock_image_map->update_images("", std::move(initial_global_image_ids), {}); ASSERT_TRUE(wait_for_map_update(1)); ASSERT_TRUE(wait_for_listener_notify(initial_global_image_ids_ack.size())); // remote peer ACKs image acquire request remote_peer_ack_nowait(mock_image_map.get(), initial_global_image_ids_ack, 0, &peer_ack_ctxs); // RELEASE+REMOVE_MAPPING expect_add_event(mock_threads); listener_release_images(mock_listener, remove_global_image_ids, &peer_ack_ctxs); update_map_request(mock_threads, mock_update_request, remove_global_image_ids, 0); // remove images mock_image_map->update_images("", {}, std::move(remove_global_image_ids)); ASSERT_TRUE(wait_for_listener_notify(remove_global_image_ids_ack.size())); remote_peer_ack_wait(mock_image_map.get(), remove_global_image_ids_ack, 0, &peer_ack_ctxs); wait_for_scheduled_task(); ASSERT_EQ(0, when_shut_down(mock_image_map.get())); } TEST_F(TestMockImageMap, AddRemoveRemoteImage) { MockThreads mock_threads(m_threads); expect_work_queue(mock_threads); InSequence seq; MockLoadRequest mock_load_request; expect_load_request(mock_load_request, 0); MockListener mock_listener(this); std::unique_ptr mock_image_map{ MockImageMap::create(m_local_io_ctx, &mock_threads, m_local_instance_id, mock_listener)}; C_SaferCond cond; mock_image_map->init(&cond); ASSERT_EQ(0, cond.wait()); std::set initial_global_image_ids{ "global id 1", "global id 2" }; std::set initial_global_image_ids_ack(initial_global_image_ids); std::set remove_global_image_ids{ "global id 1", "global id 2" }; std::set remove_global_image_ids_ack(remove_global_image_ids); // UPDATE_MAPPING+ACQUIRE expect_add_event(mock_threads); MockUpdateRequest mock_update_request; expect_update_request(mock_update_request, 0); expect_add_event(mock_threads); std::map peer_ack_ctxs; listener_acquire_images(mock_listener, initial_global_image_ids, &peer_ack_ctxs); // initial image list mock_image_map->update_images("uuid1", std::move(initial_global_image_ids), {}); ASSERT_TRUE(wait_for_map_update(1)); ASSERT_TRUE(wait_for_listener_notify(initial_global_image_ids_ack.size())); // remote peer ACKs image acquire request remote_peer_ack_nowait(mock_image_map.get(), initial_global_image_ids_ack, 0, &peer_ack_ctxs); // RELEASE+REMOVE_MAPPING std::map peer_remove_ack_ctxs; listener_remove_images(mock_listener, "uuid1", remove_global_image_ids, &peer_remove_ack_ctxs); expect_add_event(mock_threads); listener_release_images(mock_listener, remove_global_image_ids, &peer_ack_ctxs); update_map_request(mock_threads, mock_update_request, remove_global_image_ids, 0); // remove images mock_image_map->update_images("uuid1", {}, std::move(remove_global_image_ids)); ASSERT_TRUE(wait_for_listener_notify(remove_global_image_ids_ack.size() * 2)); remote_peer_ack_nowait(mock_image_map.get(), remove_global_image_ids_ack, 0, &peer_remove_ack_ctxs); remote_peer_ack_wait(mock_image_map.get(), remove_global_image_ids_ack, 0, &peer_ack_ctxs); wait_for_scheduled_task(); ASSERT_EQ(0, when_shut_down(mock_image_map.get())); } TEST_F(TestMockImageMap, AddRemoveRemoteImageDuplicateNotification) { MockThreads mock_threads(m_threads); expect_work_queue(mock_threads); InSequence seq; MockLoadRequest mock_load_request; expect_load_request(mock_load_request, 0); MockListener mock_listener(this); std::unique_ptr mock_image_map{ MockImageMap::create(m_local_io_ctx, &mock_threads, m_local_instance_id, mock_listener)}; C_SaferCond cond; mock_image_map->init(&cond); ASSERT_EQ(0, cond.wait()); std::set initial_global_image_ids{ "global id 1", "global id 2" }; std::set initial_global_image_ids_dup(initial_global_image_ids); std::set initial_global_image_ids_ack(initial_global_image_ids); std::set remove_global_image_ids{ "global id 1", "global id 2" }; std::set remove_global_image_ids_dup(remove_global_image_ids); std::set remove_global_image_ids_ack(remove_global_image_ids); // UPDATE_MAPPING+ACQUIRE expect_add_event(mock_threads); MockUpdateRequest mock_update_request; expect_update_request(mock_update_request, 0); expect_add_event(mock_threads); std::map peer_ack_ctxs; listener_acquire_images(mock_listener, initial_global_image_ids, &peer_ack_ctxs); // initial image list mock_image_map->update_images("uuid1", std::move(initial_global_image_ids), {}); ASSERT_TRUE(wait_for_map_update(1)); ASSERT_TRUE(wait_for_listener_notify(initial_global_image_ids_ack.size())); // trigger duplicate "add" event wait_for_scheduled_task(); mock_image_map->update_images("uuid1", std::move(initial_global_image_ids_dup), {}); // remote peer ACKs image acquire request remote_peer_ack_nowait(mock_image_map.get(), initial_global_image_ids_ack, 0, &peer_ack_ctxs); // RELEASE+REMOVE_MAPPING std::map peer_remove_ack_ctxs; listener_remove_images(mock_listener, "uuid1", remove_global_image_ids, &peer_remove_ack_ctxs); expect_add_event(mock_threads); listener_release_images(mock_listener, remove_global_image_ids, &peer_ack_ctxs); update_map_request(mock_threads, mock_update_request, remove_global_image_ids, 0); // remove images mock_image_map->update_images("uuid1", {}, std::move(remove_global_image_ids)); ASSERT_TRUE(wait_for_listener_notify(remove_global_image_ids_ack.size() * 2)); remote_peer_ack_nowait(mock_image_map.get(), remove_global_image_ids_ack, 0, &peer_remove_ack_ctxs); remote_peer_ack_wait(mock_image_map.get(), remove_global_image_ids_ack, 0, &peer_ack_ctxs); // trigger duplicate "remove" notification mock_image_map->update_images("uuid1", {}, std::move(remove_global_image_ids_dup)); wait_for_scheduled_task(); ASSERT_EQ(0, when_shut_down(mock_image_map.get())); } TEST_F(TestMockImageMap, AcquireImageErrorRetry) { MockThreads mock_threads(m_threads); expect_work_queue(mock_threads); InSequence seq; MockLoadRequest mock_load_request; expect_load_request(mock_load_request, 0); MockListener mock_listener(this); std::unique_ptr mock_image_map{ MockImageMap::create(m_local_io_ctx, &mock_threads, m_local_instance_id, mock_listener)}; C_SaferCond cond; mock_image_map->init(&cond); ASSERT_EQ(0, cond.wait()); std::set initial_global_image_ids{ "global id 1", "global id 2" }; std::set initial_global_image_ids_ack(initial_global_image_ids); // UPDATE_MAPPING failure expect_add_event(mock_threads); MockUpdateRequest mock_update_request; expect_update_request(mock_update_request, -EIO); // UPDATE_MAPPING+ACQUIRE expect_add_event(mock_threads); expect_update_request(mock_update_request, 0); expect_add_event(mock_threads); std::map peer_ack_ctxs; listener_acquire_images(mock_listener, initial_global_image_ids, &peer_ack_ctxs); // initial image list mock_image_map->update_images("uuid1", std::move(initial_global_image_ids), {}); ASSERT_TRUE(wait_for_map_update(1)); ASSERT_TRUE(wait_for_listener_notify(initial_global_image_ids_ack.size())); // remote peer ACKs image acquire request remote_peer_ack_nowait(mock_image_map.get(), initial_global_image_ids_ack, 0, &peer_ack_ctxs); wait_for_scheduled_task(); ASSERT_EQ(0, when_shut_down(mock_image_map.get())); } TEST_F(TestMockImageMap, RemoveRemoteAndLocalImage) { MockThreads mock_threads(m_threads); expect_work_queue(mock_threads); InSequence seq; MockLoadRequest mock_load_request; expect_load_request(mock_load_request, 0); MockListener mock_listener(this); std::unique_ptr mock_image_map{ MockImageMap::create(m_local_io_ctx, &mock_threads, m_local_instance_id, mock_listener)}; C_SaferCond cond; mock_image_map->init(&cond); ASSERT_EQ(0, cond.wait()); // remote image set std::set initial_remote_global_image_ids{ "global id 1" }; std::set initial_remote_global_image_ids_ack(initial_remote_global_image_ids); // local image set std::set initial_local_global_image_ids{ "global id 1" }; // remote/local images to remove std::set remote_remove_global_image_ids{ "global id 1" }; std::set remote_remove_global_image_ids_ack(remote_remove_global_image_ids); std::set local_remove_global_image_ids{ "global id 1" }; std::set local_remove_global_image_ids_ack(local_remove_global_image_ids); // UPDATE_MAPPING+ACQUIRE expect_add_event(mock_threads); MockUpdateRequest mock_update_request; expect_update_request(mock_update_request, 0); expect_add_event(mock_threads); std::map peer_ack_ctxs; listener_acquire_images(mock_listener, initial_remote_global_image_ids, &peer_ack_ctxs); // initial remote image list mock_image_map->update_images("uuid1", std::move(initial_remote_global_image_ids), {}); ASSERT_TRUE(wait_for_map_update(1)); ASSERT_TRUE(wait_for_listener_notify(initial_remote_global_image_ids_ack.size())); // remote peer ACKs image acquire request remote_peer_ack_nowait(mock_image_map.get(), initial_remote_global_image_ids_ack, 0, &peer_ack_ctxs); // set initial local image list -- this is a no-op from policy pov mock_image_map->update_images("", std::move(initial_local_global_image_ids), {}); // remove remote images -- this should be a no-op from policy pov // except the listener notification std::map peer_ack_remove_ctxs; listener_remove_images(mock_listener, "uuid1", remote_remove_global_image_ids, &peer_ack_remove_ctxs); mock_image_map->update_images("uuid1", {}, std::move(remote_remove_global_image_ids)); ASSERT_TRUE(wait_for_listener_notify(remote_remove_global_image_ids_ack.size())); // RELEASE+REMOVE_MAPPING expect_add_event(mock_threads); listener_release_images(mock_listener, local_remove_global_image_ids, &peer_ack_ctxs); update_map_request(mock_threads, mock_update_request, local_remove_global_image_ids, 0); // remove local images mock_image_map->update_images("", {}, std::move(local_remove_global_image_ids)); ASSERT_TRUE(wait_for_listener_notify(local_remove_global_image_ids_ack.size())); remote_peer_ack_nowait(mock_image_map.get(), local_remove_global_image_ids_ack, 0, &peer_ack_remove_ctxs); remote_peer_ack_wait(mock_image_map.get(), local_remove_global_image_ids_ack, 0, &peer_ack_ctxs); wait_for_scheduled_task(); ASSERT_EQ(0, when_shut_down(mock_image_map.get())); } TEST_F(TestMockImageMap, AddInstance) { MockThreads mock_threads(m_threads); expect_work_queue(mock_threads); InSequence seq; MockLoadRequest mock_load_request; expect_load_request(mock_load_request, 0); MockListener mock_listener(this); std::unique_ptr mock_image_map{ MockImageMap::create(m_local_io_ctx, &mock_threads, m_local_instance_id, mock_listener)}; C_SaferCond cond; mock_image_map->init(&cond); ASSERT_EQ(0, cond.wait()); std::set global_image_ids{ "global id 1", "global id 2", "global id 3", "global id 4", "global id 5" }; std::set global_image_ids_ack(global_image_ids); // UPDATE_MAPPING+ACQUIRE expect_add_event(mock_threads); MockUpdateRequest mock_update_request; expect_update_request(mock_update_request, 0); expect_add_event(mock_threads); std::map peer_ack_ctxs; listener_acquire_images(mock_listener, global_image_ids, &peer_ack_ctxs); // initial image list mock_image_map->update_images("uuid1", std::move(global_image_ids), {}); ASSERT_TRUE(wait_for_map_update(1)); ASSERT_TRUE(wait_for_listener_notify(global_image_ids_ack.size())); // remote peer ACKs image acquire request remote_peer_ack_nowait(mock_image_map.get(), global_image_ids_ack, 0, &peer_ack_ctxs); wait_for_scheduled_task(); mock_image_map->update_instances_added({m_local_instance_id}); std::set shuffled_global_image_ids; // RELEASE+UPDATE_MAPPING+ACQUIRE expect_add_event(mock_threads); expect_listener_images_unmapped(mock_listener, 3, &shuffled_global_image_ids, &peer_ack_ctxs); mock_image_map->update_instances_added({"9876"}); wait_for_scheduled_task(); ASSERT_TRUE(wait_for_listener_notify(shuffled_global_image_ids.size())); update_map_and_acquire(mock_threads, mock_update_request, mock_listener, shuffled_global_image_ids, 0, &peer_ack_ctxs); remote_peer_ack_listener_wait(mock_image_map.get(), shuffled_global_image_ids, 0, &peer_ack_ctxs); // completion shuffle action for now (re)mapped images remote_peer_ack_nowait(mock_image_map.get(), shuffled_global_image_ids, 0, &peer_ack_ctxs); wait_for_scheduled_task(); ASSERT_EQ(0, when_shut_down(mock_image_map.get())); } TEST_F(TestMockImageMap, RemoveInstance) { MockThreads mock_threads(m_threads); expect_work_queue(mock_threads); InSequence seq; MockLoadRequest mock_load_request; expect_load_request(mock_load_request, 0); MockListener mock_listener(this); std::unique_ptr mock_image_map{ MockImageMap::create(m_local_io_ctx, &mock_threads, m_local_instance_id, mock_listener)}; C_SaferCond cond; mock_image_map->init(&cond); ASSERT_EQ(0, cond.wait()); std::set global_image_ids{ "global id 1", "global id 2", "global id 3", "global id 4", "global id 5" }; std::set global_image_ids_ack(global_image_ids); expect_add_event(mock_threads); // UPDATE_MAPPING+ACQUIRE MockUpdateRequest mock_update_request; expect_update_request(mock_update_request, 0); expect_add_event(mock_threads); std::map peer_ack_ctxs; listener_acquire_images(mock_listener, global_image_ids, &peer_ack_ctxs); // set initial image list mock_image_map->update_images("uuid1", std::move(global_image_ids), {}); ASSERT_TRUE(wait_for_map_update(1)); ASSERT_TRUE(wait_for_listener_notify(global_image_ids_ack.size())); // remote peer ACKs image acquire request -- completing action remote_peer_ack_nowait(mock_image_map.get(), global_image_ids_ack, 0, &peer_ack_ctxs); wait_for_scheduled_task(); mock_image_map->update_instances_added({m_local_instance_id}); std::set shuffled_global_image_ids; // RELEASE+UPDATE_MAPPING+ACQUIRE expect_add_event(mock_threads); expect_listener_images_unmapped(mock_listener, 3, &shuffled_global_image_ids, &peer_ack_ctxs); mock_image_map->update_instances_added({"9876"}); wait_for_scheduled_task(); ASSERT_TRUE(wait_for_listener_notify(shuffled_global_image_ids.size())); update_map_and_acquire(mock_threads, mock_update_request, mock_listener, shuffled_global_image_ids, 0, &peer_ack_ctxs); remote_peer_ack_listener_wait(mock_image_map.get(), shuffled_global_image_ids, 0, &peer_ack_ctxs); // completion shuffle action for now (re)mapped images remote_peer_ack_nowait(mock_image_map.get(), shuffled_global_image_ids, 0, &peer_ack_ctxs); wait_for_scheduled_task(); shuffled_global_image_ids.clear(); // remove added instance expect_add_event(mock_threads); expect_listener_images_unmapped(mock_listener, 2, &shuffled_global_image_ids, &peer_ack_ctxs); mock_image_map->update_instances_removed({"9876"}); wait_for_scheduled_task(); ASSERT_TRUE(wait_for_listener_notify(shuffled_global_image_ids.size())); update_map_and_acquire(mock_threads, mock_update_request, mock_listener, shuffled_global_image_ids, 0, &peer_ack_ctxs); remote_peer_ack_listener_wait(mock_image_map.get(), shuffled_global_image_ids, 0, &peer_ack_ctxs); // completion shuffle action for now (re)mapped images remote_peer_ack_nowait(mock_image_map.get(), shuffled_global_image_ids, 0, &peer_ack_ctxs); wait_for_scheduled_task(); ASSERT_EQ(0, when_shut_down(mock_image_map.get())); } TEST_F(TestMockImageMap, AddInstancePingPongImageTest) { EXPECT_EQ(0, _rados->conf_set("rbd_mirror_image_policy_migration_throttle", "600")); MockThreads mock_threads(m_threads); expect_work_queue(mock_threads); InSequence seq; std::set global_image_ids{ "global id 1", "global id 2", "global id 3", "global id 4", "global id 5", "global id 6", "global id 7", "global id 8", "global id 9", "global id 10", "global id 11", "global id 12", "global id 13", "global id 14" }; std::map image_mapping; for (auto& global_image_id : global_image_ids) { image_mapping[global_image_id] = {m_local_instance_id, {}, {}}; } // ACQUIRE MockLoadRequest mock_load_request; EXPECT_CALL(mock_load_request, send()).WillOnce( Invoke([&mock_load_request, &image_mapping]() { *mock_load_request.image_map = image_mapping; mock_load_request.on_finish->complete(0); })); expect_add_event(mock_threads); MockListener mock_listener(this); std::map peer_ack_ctxs; listener_acquire_images(mock_listener, global_image_ids, &peer_ack_ctxs); std::unique_ptr mock_image_map{ MockImageMap::create(m_local_io_ctx, &mock_threads, m_local_instance_id, mock_listener)}; C_SaferCond cond; mock_image_map->init(&cond); ASSERT_EQ(0, cond.wait()); mock_image_map->update_instances_added({m_local_instance_id}); std::set global_image_ids_ack(global_image_ids); // remote peer ACKs image acquire request -- completing action ASSERT_TRUE(wait_for_listener_notify(global_image_ids_ack.size())); remote_peer_ack_nowait(mock_image_map.get(), global_image_ids_ack, 0, &peer_ack_ctxs); wait_for_scheduled_task(); // RELEASE+UPDATE_MAPPING+ACQUIRE expect_add_event(mock_threads); MockUpdateRequest mock_update_request; expect_update_request(mock_update_request, 0); expect_add_event(mock_threads); listener_acquire_images(mock_listener, global_image_ids, &peer_ack_ctxs); // set initial image list mock_image_map->update_images("uuid1", std::move(global_image_ids), {}); ASSERT_TRUE(wait_for_map_update(1)); ASSERT_TRUE(wait_for_listener_notify(global_image_ids_ack.size())); // remote peer ACKs image acquire request -- completing action remote_peer_ack_nowait(mock_image_map.get(), global_image_ids_ack, 0, &peer_ack_ctxs); wait_for_scheduled_task(); std::set shuffled_global_image_ids; // RELEASE+UPDATE_MAPPING+ACQUIRE expect_add_event(mock_threads); expect_listener_images_unmapped(mock_listener, 7, &shuffled_global_image_ids, &peer_ack_ctxs); mock_image_map->update_instances_added({"9876"}); wait_for_scheduled_task(); ASSERT_TRUE(wait_for_listener_notify(shuffled_global_image_ids.size())); update_map_and_acquire(mock_threads, mock_update_request, mock_listener, shuffled_global_image_ids, 0, &peer_ack_ctxs); remote_peer_ack_listener_wait(mock_image_map.get(), shuffled_global_image_ids, 0, &peer_ack_ctxs); // completion shuffle action for now (re)mapped images remote_peer_ack_nowait(mock_image_map.get(), shuffled_global_image_ids, 0, &peer_ack_ctxs); wait_for_scheduled_task(); std::set migrated_global_image_ids(shuffled_global_image_ids); shuffled_global_image_ids.clear(); // RELEASE+UPDATE_MAPPING+ACQUIRE expect_add_event(mock_threads); expect_listener_images_unmapped(mock_listener, 3, &shuffled_global_image_ids, &peer_ack_ctxs); // add another instance mock_image_map->update_instances_added({"5432"}); wait_for_scheduled_task(); ASSERT_TRUE(wait_for_listener_notify(shuffled_global_image_ids.size())); update_map_and_acquire(mock_threads, mock_update_request, mock_listener, shuffled_global_image_ids, 0, &peer_ack_ctxs); remote_peer_ack_listener_wait(mock_image_map.get(), shuffled_global_image_ids, 0, &peer_ack_ctxs); // completion shuffle action for now (re)mapped images remote_peer_ack_nowait(mock_image_map.get(), shuffled_global_image_ids, 0, &peer_ack_ctxs); // shuffle set should be distinct std::set reshuffled; std::set_intersection(migrated_global_image_ids.begin(), migrated_global_image_ids.end(), shuffled_global_image_ids.begin(), shuffled_global_image_ids.end(), std::inserter(reshuffled, reshuffled.begin())); ASSERT_TRUE(reshuffled.empty()); wait_for_scheduled_task(); ASSERT_EQ(0, when_shut_down(mock_image_map.get())); } TEST_F(TestMockImageMap, RemoveInstanceWithRemoveImage) { MockThreads mock_threads(m_threads); expect_work_queue(mock_threads); InSequence seq; MockLoadRequest mock_load_request; expect_load_request(mock_load_request, 0); MockListener mock_listener(this); std::unique_ptr mock_image_map{ MockImageMap::create(m_local_io_ctx, &mock_threads, m_local_instance_id, mock_listener)}; C_SaferCond cond; mock_image_map->init(&cond); ASSERT_EQ(0, cond.wait()); std::set global_image_ids{ "global id 1", "global id 2", "global id 3", "remote id 4", }; std::set global_image_ids_ack(global_image_ids); std::set remove_global_image_ids{ "global id 1" }; std::set remove_global_image_ids_ack(remove_global_image_ids); expect_add_event(mock_threads); // UPDATE_MAPPING+ACQUIRE MockUpdateRequest mock_update_request; expect_update_request(mock_update_request, 0); expect_add_event(mock_threads); std::map peer_ack_ctxs; listener_acquire_images(mock_listener, global_image_ids, &peer_ack_ctxs); // initial image list mock_image_map->update_images("uuid1", std::move(global_image_ids), {}); ASSERT_TRUE(wait_for_map_update(1)); ASSERT_TRUE(wait_for_listener_notify(global_image_ids_ack.size())); remote_peer_ack_nowait(mock_image_map.get(), global_image_ids_ack, 0, &peer_ack_ctxs); wait_for_scheduled_task(); mock_image_map->update_instances_added({m_local_instance_id}); std::set shuffled_global_image_ids; // RELEASE+UPDATE_MAPPING+ACQUIRE expect_add_event(mock_threads); expect_listener_images_unmapped(mock_listener, 2, &shuffled_global_image_ids, &peer_ack_ctxs); mock_image_map->update_instances_added({"9876"}); wait_for_scheduled_task(); ASSERT_TRUE(wait_for_listener_notify(shuffled_global_image_ids.size())); update_map_and_acquire(mock_threads, mock_update_request, mock_listener, shuffled_global_image_ids, 0, &peer_ack_ctxs); remote_peer_ack_listener_wait(mock_image_map.get(), shuffled_global_image_ids, 0, &peer_ack_ctxs); // completion shuffle action for now (re)mapped images remote_peer_ack_nowait(mock_image_map.get(), shuffled_global_image_ids, 0, &peer_ack_ctxs); wait_for_scheduled_task(); std::set shuffled_global_image_ids_ack(shuffled_global_image_ids); // RELEASE std::map peer_ack_remove_ctxs; listener_remove_images(mock_listener, "uuid1", shuffled_global_image_ids, &peer_ack_remove_ctxs); expect_add_event(mock_threads); listener_release_images(mock_listener, shuffled_global_image_ids, &peer_ack_ctxs); expect_add_event(mock_threads); expect_update_request(mock_update_request, 0); expect_add_event(mock_threads); expect_update_request(mock_update_request, 0); mock_image_map->update_images("uuid1", {}, std::move(shuffled_global_image_ids)); ASSERT_TRUE(wait_for_listener_notify(shuffled_global_image_ids_ack.size() * 2)); // instance failed -- update policy for instance removal mock_image_map->update_instances_removed({"9876"}); remote_peer_ack_nowait(mock_image_map.get(), shuffled_global_image_ids, -ENOENT, &peer_ack_remove_ctxs); remote_peer_ack_wait(mock_image_map.get(), shuffled_global_image_ids, -EBLOCKLISTED, &peer_ack_ctxs); wait_for_scheduled_task(); ASSERT_EQ(0, when_shut_down(mock_image_map.get())); } TEST_F(TestMockImageMap, AddErrorAndRemoveImage) { MockThreads mock_threads(m_threads); expect_work_queue(mock_threads); InSequence seq; MockLoadRequest mock_load_request; expect_load_request(mock_load_request, 0); MockListener mock_listener(this); std::unique_ptr mock_image_map{ MockImageMap::create(m_local_io_ctx, &mock_threads, m_local_instance_id, mock_listener)}; C_SaferCond cond; mock_image_map->init(&cond); ASSERT_EQ(0, cond.wait()); mock_image_map->update_instances_added({m_local_instance_id}); std::set global_image_ids{ "global id 1", "global id 2", "global id 3", "remote id 4", }; std::set global_image_ids_ack(global_image_ids); // UPDATE_MAPPING+ACQUIRE expect_add_event(mock_threads); MockUpdateRequest mock_update_request; expect_update_request(mock_update_request, 0); expect_add_event(mock_threads); std::map peer_ack_ctxs; listener_acquire_images(mock_listener, global_image_ids, &peer_ack_ctxs); // initial image list mock_image_map->update_images("uuid1", std::move(global_image_ids), {}); ASSERT_TRUE(wait_for_map_update(1)); ASSERT_TRUE(wait_for_listener_notify(global_image_ids_ack.size())); // remote peer ACKs image acquire request remote_peer_ack_nowait(mock_image_map.get(), global_image_ids_ack, 0, &peer_ack_ctxs); wait_for_scheduled_task(); std::set shuffled_global_image_ids; // RELEASE+UPDATE_MAPPING+ACQUIRE expect_add_event(mock_threads); expect_listener_images_unmapped(mock_listener, 2, &shuffled_global_image_ids, &peer_ack_ctxs); mock_image_map->update_instances_added({"9876"}); wait_for_scheduled_task(); ASSERT_TRUE(wait_for_listener_notify(shuffled_global_image_ids.size())); update_map_and_acquire(mock_threads, mock_update_request, mock_listener, shuffled_global_image_ids, 0, &peer_ack_ctxs); remote_peer_ack_listener_wait(mock_image_map.get(), shuffled_global_image_ids, 0, &peer_ack_ctxs); wait_for_scheduled_task(); mock_image_map->update_instances_removed({"9876"}); std::set released_global_image_ids; std::map release_peer_ack_ctxs; expect_add_event(mock_threads); expect_listener_images_unmapped(mock_listener, 1, &released_global_image_ids, &release_peer_ack_ctxs); expect_add_event(mock_threads); expect_listener_images_unmapped(mock_listener, 1, &released_global_image_ids, &release_peer_ack_ctxs); // instance blocklisted -- ACQUIRE request fails remote_peer_ack_nowait(mock_image_map.get(), shuffled_global_image_ids, -EBLOCKLISTED, &peer_ack_ctxs); ASSERT_TRUE(wait_for_listener_notify(shuffled_global_image_ids.size())); std::map remap_peer_ack_ctxs; update_map_and_acquire(mock_threads, mock_update_request, mock_listener, shuffled_global_image_ids, 0, &remap_peer_ack_ctxs); // instance blocklisted -- RELEASE request fails remote_peer_ack_listener_wait(mock_image_map.get(), shuffled_global_image_ids, -ENOENT, &release_peer_ack_ctxs); wait_for_scheduled_task(); // new peer acks acquire request remote_peer_ack_nowait(mock_image_map.get(), shuffled_global_image_ids, 0, &remap_peer_ack_ctxs); wait_for_scheduled_task(); std::set shuffled_global_image_ids_ack(shuffled_global_image_ids); // remove image std::map peer_ack_remove_ctxs; listener_remove_images(mock_listener, "uuid1", shuffled_global_image_ids, &peer_ack_remove_ctxs); expect_add_event(mock_threads); listener_release_images(mock_listener, shuffled_global_image_ids, &peer_ack_ctxs); update_map_request(mock_threads, mock_update_request, shuffled_global_image_ids, 0); mock_image_map->update_images("uuid1", {}, std::move(shuffled_global_image_ids)); ASSERT_TRUE(wait_for_listener_notify(shuffled_global_image_ids_ack.size() * 2)); remote_peer_ack_nowait(mock_image_map.get(), shuffled_global_image_ids_ack, 0, &peer_ack_remove_ctxs); remote_peer_ack_wait(mock_image_map.get(), shuffled_global_image_ids_ack, 0, &peer_ack_ctxs); wait_for_scheduled_task(); ASSERT_EQ(0, when_shut_down(mock_image_map.get())); } TEST_F(TestMockImageMap, MirrorUUIDUpdated) { MockThreads mock_threads(m_threads); expect_work_queue(mock_threads); InSequence seq; MockLoadRequest mock_load_request; expect_load_request(mock_load_request, 0); MockListener mock_listener(this); std::unique_ptr mock_image_map{ MockImageMap::create(m_local_io_ctx, &mock_threads, m_local_instance_id, mock_listener)}; C_SaferCond cond; mock_image_map->init(&cond); ASSERT_EQ(0, cond.wait()); // remote image set std::set initial_remote_global_image_ids{ "global id 1", "global id 2", "global id 3" }; std::set initial_remote_global_image_ids_ack(initial_remote_global_image_ids); // remote/local images to remove std::set remote_removed_global_image_ids{ "global id 1", "global id 2", "global id 3" }; std::set remote_removed_global_image_ids_ack(remote_removed_global_image_ids); std::set remote_added_global_image_ids{ "global id 1", "global id 2", "global id 3" }; std::set remote_added_global_image_ids_ack(remote_added_global_image_ids); // UPDATE_MAPPING+ACQUIRE expect_add_event(mock_threads); MockUpdateRequest mock_update_request; expect_update_request(mock_update_request, 0); expect_add_event(mock_threads); std::map peer_ack_ctxs; listener_acquire_images(mock_listener, initial_remote_global_image_ids, &peer_ack_ctxs); // initial remote image list mock_image_map->update_images("uuid1", std::move(initial_remote_global_image_ids), {}); ASSERT_TRUE(wait_for_map_update(1)); ASSERT_TRUE(wait_for_listener_notify(initial_remote_global_image_ids_ack.size())); // remote peer ACKs image acquire request remote_peer_ack_nowait(mock_image_map.get(), initial_remote_global_image_ids_ack, 0, &peer_ack_ctxs); wait_for_scheduled_task(); // RELEASE+REMOVE_MAPPING std::map peer_remove_ack_ctxs; listener_remove_images(mock_listener, "uuid1", remote_removed_global_image_ids, &peer_remove_ack_ctxs); expect_add_event(mock_threads); listener_release_images(mock_listener, remote_removed_global_image_ids, &peer_ack_ctxs); update_map_request(mock_threads, mock_update_request, remote_removed_global_image_ids, 0); mock_image_map->update_images("uuid1", {}, std::move(remote_removed_global_image_ids)); ASSERT_TRUE(wait_for_listener_notify(remote_removed_global_image_ids_ack.size() * 2)); remote_peer_ack_nowait(mock_image_map.get(), remote_removed_global_image_ids_ack, 0, &peer_remove_ack_ctxs); remote_peer_ack_wait(mock_image_map.get(), remote_removed_global_image_ids_ack, 0, &peer_ack_ctxs); // UPDATE_MAPPING+ACQUIRE expect_add_event(mock_threads); expect_update_request(mock_update_request, 0); expect_add_event(mock_threads); listener_acquire_images(mock_listener, remote_added_global_image_ids, &peer_ack_ctxs); mock_image_map->update_images("uuid2", std::move(remote_added_global_image_ids), {}); ASSERT_TRUE(wait_for_map_update(1)); ASSERT_TRUE(wait_for_listener_notify(remote_added_global_image_ids_ack.size())); // remote peer ACKs image acquire request remote_peer_ack_nowait(mock_image_map.get(), remote_added_global_image_ids_ack, 0, &peer_ack_ctxs); wait_for_scheduled_task(); ASSERT_EQ(0, when_shut_down(mock_image_map.get())); } TEST_F(TestMockImageMap, RebalanceImageMap) { MockThreads mock_threads(m_threads); expect_work_queue(mock_threads); InSequence seq; MockLoadRequest mock_load_request; expect_load_request(mock_load_request, 0); MockListener mock_listener(this); std::unique_ptr mock_image_map{ MockImageMap::create(m_local_io_ctx, &mock_threads, m_local_instance_id, mock_listener)}; C_SaferCond cond; mock_image_map->init(&cond); ASSERT_EQ(0, cond.wait()); std::set global_image_ids{ "global id 1", "global id 2", "global id 3", "global id 4", "global id 5", "global id 6", "global id 7", "global id 8", "global id 9", "global id 10", }; std::set global_image_ids_ack(global_image_ids); // UPDATE_MAPPING+ACQUIRE expect_add_event(mock_threads); MockUpdateRequest mock_update_request; expect_update_request(mock_update_request, 0); expect_add_event(mock_threads); std::map peer_ack_ctxs; listener_acquire_images(mock_listener, global_image_ids, &peer_ack_ctxs); // initial image list mock_image_map->update_images("", std::move(global_image_ids), {}); ASSERT_TRUE(wait_for_map_update(1)); ASSERT_TRUE(wait_for_listener_notify(global_image_ids_ack.size())); // remote peer ACKs image acquire request remote_peer_ack_nowait(mock_image_map.get(), global_image_ids_ack, 0, &peer_ack_ctxs); wait_for_scheduled_task(); mock_image_map->update_instances_added({m_local_instance_id}); std::set shuffled_global_image_ids; // RELEASE+UPDATE_MAPPING+ACQUIRE expect_add_event(mock_threads); expect_listener_images_unmapped(mock_listener, 5, &shuffled_global_image_ids, &peer_ack_ctxs); mock_image_map->update_instances_added({"9876"}); wait_for_scheduled_task(); ASSERT_TRUE(wait_for_listener_notify(shuffled_global_image_ids.size())); update_map_and_acquire(mock_threads, mock_update_request, mock_listener, shuffled_global_image_ids, 0, &peer_ack_ctxs); remote_peer_ack_listener_wait(mock_image_map.get(), shuffled_global_image_ids, 0, &peer_ack_ctxs); // completion shuffle action for now (re)mapped images remote_peer_ack_nowait(mock_image_map.get(), shuffled_global_image_ids, 0, &peer_ack_ctxs); wait_for_scheduled_task(); // remove all shuffled images -- make way for rebalance std::set shuffled_global_image_ids_ack(shuffled_global_image_ids); // RELEASE+REMOVE_MAPPING expect_add_event(mock_threads); listener_release_images(mock_listener, shuffled_global_image_ids, &peer_ack_ctxs); update_map_request(mock_threads, mock_update_request, shuffled_global_image_ids, 0); mock_image_map->update_images("", {}, std::move(shuffled_global_image_ids)); ASSERT_TRUE(wait_for_listener_notify(shuffled_global_image_ids_ack.size())); remote_peer_ack_wait(mock_image_map.get(), shuffled_global_image_ids_ack, 0, &peer_ack_ctxs); wait_for_scheduled_task(); shuffled_global_image_ids.clear(); shuffled_global_image_ids_ack.clear(); std::set new_global_image_ids = { "global id 11" }; std::set new_global_image_ids_ack(new_global_image_ids); expect_add_event(mock_threads); expect_update_request(mock_update_request, 0); expect_add_event(mock_threads); listener_acquire_images(mock_listener, new_global_image_ids, &peer_ack_ctxs); expect_rebalance_event(mock_threads); // rebalance task expect_add_event(mock_threads); // update task scheduled by // rebalance task expect_listener_images_unmapped(mock_listener, 2, &shuffled_global_image_ids, &peer_ack_ctxs); mock_image_map->update_images("", std::move(new_global_image_ids), {}); ASSERT_TRUE(wait_for_map_update(1)); ASSERT_TRUE(wait_for_listener_notify(new_global_image_ids_ack.size())); // set rebalance interval CephContext *cct = reinterpret_cast(m_local_io_ctx.cct()); cct->_conf.set_val("rbd_mirror_image_policy_rebalance_timeout", "5"); remote_peer_ack_nowait(mock_image_map.get(), new_global_image_ids_ack, 0, &peer_ack_ctxs); wait_for_scheduled_task(); ASSERT_TRUE(wait_for_listener_notify(shuffled_global_image_ids.size())); update_map_and_acquire(mock_threads, mock_update_request, mock_listener, shuffled_global_image_ids, 0, &peer_ack_ctxs); remote_peer_ack_listener_wait(mock_image_map.get(), shuffled_global_image_ids, 0, &peer_ack_ctxs); // completion shuffle action for now (re)mapped images remote_peer_ack_nowait(mock_image_map.get(), shuffled_global_image_ids, 0, &peer_ack_ctxs); wait_for_scheduled_task(); ASSERT_EQ(0, when_shut_down(mock_image_map.get())); } } // namespace mirror } // namespace rbd