]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
librbd: behave more gracefully when data pool removed
authorMykola Golub <mgolub@suse.com>
Mon, 12 Aug 2019 13:42:51 +0000 (14:42 +0100)
committerMykola Golub <mgolub@suse.com>
Tue, 27 Aug 2019 15:42:51 +0000 (16:42 +0100)
allowing to open the image and do some maintenance operations,
and returning -ENODEV for ops that require data pool.

Fixes: https://tracker.ceph.com/issues/41206
Signed-off-by: Mykola Golub <mgolub@suse.com>
26 files changed:
src/librbd/DeepCopyRequest.cc
src/librbd/ImageCtx.cc
src/librbd/ImageCtx.h
src/librbd/api/DiffIterate.cc
src/librbd/api/Image.cc
src/librbd/api/Image.h
src/librbd/api/Snapshot.cc
src/librbd/cache/ObjectCacherObjectDispatch.cc
src/librbd/cache/ParentCacheObjectDispatch.cc
src/librbd/deep_copy/ObjectCopyRequest.cc
src/librbd/image/OpenRequest.cc
src/librbd/image/RefreshRequest.cc
src/librbd/image/RemoveRequest.cc
src/librbd/image/RemoveRequest.h
src/librbd/io/CopyupRequest.cc
src/librbd/io/ImageRequestWQ.cc
src/librbd/io/ObjectRequest.cc
src/librbd/librbd.cc
src/librbd/operation/ObjectMapIterate.cc
src/librbd/operation/SnapshotCreateRequest.cc
src/librbd/operation/SnapshotRemoveRequest.cc
src/librbd/operation/SparsifyRequest.cc
src/librbd/operation/TrimRequest.cc
src/test/librbd/image/test_mock_RefreshRequest.cc
src/test/librbd/mock/MockImageCtx.h
src/test/librbd/test_internal.cc

index ddbd34a7611870133ec26fdc4031286b697b40bd..64615685202a8f816913605143c29e22ab4993bc 100644 (file)
@@ -50,6 +50,18 @@ DeepCopyRequest<I>::~DeepCopyRequest() {
 
 template <typename I>
 void DeepCopyRequest<I>::send() {
+  if (!m_src_image_ctx->data_ctx.is_valid()) {
+    lderr(m_cct) << "missing data pool for source image" << dendl;
+    finish(-ENODEV);
+    return;
+  }
+
+  if (!m_dst_image_ctx->data_ctx.is_valid()) {
+    lderr(m_cct) << "missing data pool for destination image" << dendl;
+    finish(-ENODEV);
+    return;
+  }
+
   int r = validate_copy_points();
   if (r < 0) {
     finish(r);
index b5e0390b3654a7e51c715d7212ddaf572bfbaf00..6c70df9a53564bc8fa9fc8455dbc305339f0ea82 100644 (file)
@@ -168,7 +168,9 @@ public:
     delete[] format_string;
 
     md_ctx.aio_flush();
-    data_ctx.aio_flush();
+    if (data_ctx.is_valid()) {
+      data_ctx.aio_flush();
+    }
     io_work_queue->drain();
 
     delete io_object_dispatcher;
@@ -187,7 +189,7 @@ public:
     asok_hook = new LibrbdAdminSocketHook(this);
 
     string pname = string("librbd-") + id + string("-") +
-      data_ctx.get_pool_name() + string("-") + name;
+      md_ctx.get_pool_name() + string("-") + name;
     if (!snap_name.empty()) {
       pname += "-";
       pname += snap_name;
@@ -208,7 +210,7 @@ public:
     asok_hook = nullptr;
   }
 
-  void ImageCtx::init_layout()
+  void ImageCtx::init_layout(int64_t pool_id)
   {
     if (stripe_unit == 0 || stripe_count == 0) {
       stripe_unit = 1ull << order;
@@ -225,7 +227,7 @@ public:
     layout.stripe_unit = stripe_unit;
     layout.stripe_count = stripe_count;
     layout.object_size = 1ull << order;
-    layout.pool_id = data_ctx.get_id();  // FIXME: pool id overflow?
+    layout.pool_id = pool_id;  // FIXME: pool id overflow?
 
     delete[] format_string;
     size_t len = object_prefix.length() + 16;
@@ -325,7 +327,9 @@ public:
       snap_namespace = it->second.snap_namespace;
       snap_name = it->second.name;
       snap_exists = true;
-      data_ctx.snap_set_read(snap_id);
+      if (data_ctx.is_valid()) {
+        data_ctx.snap_set_read(snap_id);
+      }
       return 0;
     }
     return -ENOENT;
@@ -338,7 +342,9 @@ public:
     snap_namespace = {};
     snap_name = "";
     snap_exists = true;
-    data_ctx.snap_set_read(snap_id);
+    if (data_ctx.is_valid()) {
+      data_ctx.snap_set_read(snap_id);
+    }
   }
 
   snap_t ImageCtx::get_snap_id(const cls::rbd::SnapshotNamespace& in_snap_namespace,
index 84a5dbec2edf135f171058754859f81ed72aa26e..b15b1506716eac3f7950ff14b9aef3e519ae734e 100644 (file)
@@ -234,7 +234,7 @@ namespace librbd {
     ~ImageCtx();
     void init();
     void shutdown();
-    void init_layout();
+    void init_layout(int64_t pool_id);
     void perf_start(std::string name);
     void perf_stop();
     void set_read_flag(unsigned flag);
index 86be5b4e948a2e21211dc21bdbc12276b3a8ebbc..e4c850f39d0a7c30c77fb3cc38e1ee54fba7e8be 100644 (file)
@@ -239,6 +239,10 @@ int DiffIterate<I>::diff_iterate(I *ictx,
   ldout(ictx->cct, 20) << "diff_iterate " << ictx << " off = " << off
                 << " len = " << len << dendl;
 
+  if (!ictx->data_ctx.is_valid()) {
+    return -ENODEV;
+  }
+
   // ensure previous writes are visible to listsnaps
   C_SaferCond flush_ctx;
   {
@@ -277,6 +281,8 @@ template <typename I>
 int DiffIterate<I>::execute() {
   CephContext* cct = m_image_ctx.cct;
 
+  ceph_assert(m_image_ctx.data_ctx.is_valid());
+
   librados::IoCtx head_ctx;
   librados::snap_t from_snap_id = 0;
   librados::snap_t end_snap_id;
index 69e9f80b29c38a881dc9571d87091e4979c6c524..2737039d5254583a46fe760ca7ee3499520e579f 100644 (file)
@@ -76,6 +76,23 @@ int pre_remove_image(librados::IoCtx& io_ctx, const std::string& image_id) {
 
 } // anonymous namespace
 
+template <typename I>
+int64_t Image<I>::get_data_pool_id(I *ictx) {
+  if (ictx->data_ctx.is_valid()) {
+    return ictx->data_ctx.get_id();
+  }
+
+  int64_t pool_id;
+  int r = cls_client::get_data_pool(&ictx->md_ctx, ictx->header_oid, &pool_id);
+  if (r < 0) {
+    CephContext *cct = ictx->cct;
+    lderr(cct) << "error getting data pool ID: " << cpp_strerror(r) << dendl;
+    return r;
+  }
+
+  return pool_id;
+}
+
 template <typename I>
 int Image<I>::get_op_features(I *ictx, uint64_t *op_features) {
   CephContext *cct = ictx->cct;
index 8bfadae70de931f4cd503afc00a76e8eb902a22b..af928c84c06c93f8d1f39407fbcd40b4460b8ad2 100644 (file)
@@ -24,6 +24,8 @@ template <typename ImageCtxT = librbd::ImageCtx>
 struct Image {
   typedef std::map<std::string, std::string> ImageNameToIds;
 
+  static int64_t get_data_pool_id(ImageCtxT *ictx);
+
   static int get_op_features(ImageCtxT *ictx, uint64_t *op_features);
 
   static int list_images(librados::IoCtx& io_ctx,
index 4d24f91a61594474b2fc0d4c1f1f26b0a8397594..8ca4c52be3c6a2a9a8ae43e7c4828b2e67b2d240 100644 (file)
@@ -111,7 +111,7 @@ int Snapshot<I>::get_group_namespace(I *ictx, uint64_t snap_id,
     return -ENOENT;
   }
 
-  GetGroupVisitor ggv = GetGroupVisitor(ictx->cct, &ictx->data_ctx, group_snap);
+  GetGroupVisitor ggv = GetGroupVisitor(ictx->cct, &ictx->md_ctx, group_snap);
   r = boost::apply_visitor(ggv, snap_info->snap_namespace);
   if (r < 0) {
     return r;
index 0b57201bbbb88efe7fd0a26d753bccd0156dab21..8343dca5d5a7201551bfe0b64ed0fb9eaf23564c 100644 (file)
@@ -82,6 +82,7 @@ ObjectCacherObjectDispatch<I>::ObjectCacherObjectDispatch(
     m_writethrough_until_flush(writethrough_until_flush),
     m_cache_lock(ceph::make_mutex(util::unique_lock_name(
       "librbd::cache::ObjectCacherObjectDispatch::cache_lock", this))) {
+  ceph_assert(m_image_ctx->data_ctx.is_valid());
 }
 
 template <typename I>
index f1a1b5edabc8813d438c6788b0e92e768cc44c08..87fba3c560b468c8be9b2948baa70ac2ea9c8c2e 100644 (file)
@@ -29,6 +29,7 @@ template <typename I>
 ParentCacheObjectDispatch<I>::ParentCacheObjectDispatch(
     I* image_ctx) : m_image_ctx(image_ctx), m_cache_client(nullptr),
     m_initialized(false), m_connecting(false) {
+  ceph_assert(m_image_ctx->data_ctx.is_valid());
   std::string controller_path =
     ((CephContext*)(m_image_ctx->cct))->_conf.get_val<std::string>("immutable_object_cache_sock");
   m_cache_client = new CacheClient(controller_path.c_str(), m_image_ctx->cct);
index 15c647975f30c7aa3a0b6a2c2df913eb65cedd90..d630c03a83999ff115486b6b9e71d09faa4f80a8 100644 (file)
@@ -50,6 +50,8 @@ ObjectCopyRequest<I>::ObjectCopyRequest(I *src_image_ctx,
     m_dst_image_ctx(dst_image_ctx), m_cct(dst_image_ctx->cct),
     m_snap_map(snap_map), m_dst_object_number(dst_object_number),
     m_flatten(flatten), m_on_finish(on_finish) {
+  ceph_assert(src_image_ctx->data_ctx.is_valid());
+  ceph_assert(dst_image_ctx->data_ctx.is_valid());
   ceph_assert(!m_snap_map.empty());
 
   m_src_async_op = new io::AsyncOperation();
index 69269f59f4616a8ba0b301527d5f3ba5affa98aa..0188230c9f4ac4796bb487a3862f50007d3ef564 100644 (file)
@@ -476,13 +476,19 @@ Context *OpenRequest<I>::handle_v2_get_data_pool(int *result) {
     *result = util::create_ioctx(m_image_ctx->md_ctx, "data pool", data_pool_id,
                                  {}, &m_image_ctx->data_ctx);
     if (*result < 0) {
-      send_close_image(*result);
-      return nullptr;
+      if (*result != -ENOENT) {
+        send_close_image(*result);
+        return nullptr;
+      }
+      m_image_ctx->data_ctx.close();
+    } else {
+      m_image_ctx->data_ctx.set_namespace(m_image_ctx->md_ctx.get_namespace());
     }
-    m_image_ctx->data_ctx.set_namespace(m_image_ctx->md_ctx.get_namespace());
+  } else {
+    data_pool_id = m_image_ctx->md_ctx.get_id();
   }
 
-  m_image_ctx->init_layout();
+  m_image_ctx->init_layout(data_pool_id);
   send_refresh();
   return nullptr;
 }
index d521e33bff6177315beecfcb4bd37b330c3a8efa..8f0400a1f47d45ada2db3192dc1d09f32b94616e 100644 (file)
@@ -1296,7 +1296,7 @@ void RefreshRequest<I>::apply() {
     m_image_ctx.op_features = 0;
     m_image_ctx.operations_disabled = false;
     m_image_ctx.object_prefix = std::move(m_object_prefix);
-    m_image_ctx.init_layout();
+    m_image_ctx.init_layout(m_image_ctx.md_ctx.get_id());
   } else {
     // HEAD revision doesn't have a defined overlap so it's only
     // applicable to snapshots
@@ -1386,8 +1386,10 @@ void RefreshRequest<I>::apply() {
   if (m_refresh_parent != nullptr) {
     m_refresh_parent->apply();
   }
-  m_image_ctx.data_ctx.selfmanaged_snap_set_write_ctx(m_image_ctx.snapc.seq,
-                                                      m_image_ctx.snaps);
+  if (m_image_ctx.data_ctx.is_valid()) {
+    m_image_ctx.data_ctx.selfmanaged_snap_set_write_ctx(m_image_ctx.snapc.seq,
+                                                        m_image_ctx.snaps);
+  }
 
   // handle dynamically enabled / disabled features
   if (m_image_ctx.exclusive_lock != nullptr &&
index f1fe0d96cd7b941bc1d06eea99f1fb40db4d1042..f5d9c227fda9571b3a3332295e923395aad862cb 100644 (file)
@@ -129,6 +129,11 @@ void RemoveRequest<I>::handle_pre_remove_image(int r) {
     return;
   }
 
+  if (!m_image_ctx->data_ctx.is_valid()) {
+    detach_child();
+    return;
+  }
+
   trim_image();
 }
 
index 40a571e5aab85b9e895101638188a83127d01fc8..98d597645a4913bfc418bf46526f96be36de25dd 100644 (file)
@@ -63,7 +63,7 @@ private:
    *                              PRE REMOVE IMAGE * * *        |
    *                                     |             *        |
    *                                     v             *        |
-   *                                TRIM IMAGE * * * * *        |
+   *    (skip if invalid data pool) TRIM IMAGE * * * * *        |
    *                                     |             *        |
    *                                     v             *        |
    *                                DETACH CHILD       *        |
index e9f5d2487ff89fb4acccc98dbbcd645a7d60d43b..b65ab475e14db2121e9e37a330d42d3edade0bcc 100644 (file)
@@ -117,6 +117,7 @@ CopyupRequest<I>::CopyupRequest(I *ictx, uint64_t objectno,
   : m_image_ctx(ictx), m_object_no(objectno), m_image_extents(image_extents),
     m_trace(util::create_trace(*m_image_ctx, "copy-up", parent_trace))
 {
+  ceph_assert(m_image_ctx->data_ctx.is_valid());
   m_async_op.start_op(*util::get_image_ctx(m_image_ctx));
 }
 
index 5bb0f1664f9de46f30e8d91edcece498243fc907..a3a70be4cb986740e0feb718b82926279fd6d6fe 100644 (file)
@@ -844,6 +844,14 @@ int ImageRequestWQ<I>::start_in_flight_io(AioCompletion *c) {
     return false;
   }
 
+  if (!m_image_ctx.data_ctx.is_valid()) {
+    CephContext *cct = m_image_ctx.cct;
+    lderr(cct) << "missing data pool" << dendl;
+
+    c->fail(-ENODEV);
+    return false;
+  }
+
   m_in_flight_ios++;
   return true;
 }
index b8845f6b7f4e20b6028ed7cb9e8e961dab883056..a8eb54e0be0df9c6ebea685134d39ee9e35f8772 100644 (file)
@@ -103,6 +103,7 @@ ObjectRequest<I>::ObjectRequest(
   : m_ictx(ictx), m_object_no(objectno), m_object_off(off),
     m_object_len(len), m_snap_id(snap_id), m_completion(completion),
     m_trace(util::create_trace(*ictx, "", trace)) {
+  ceph_assert(m_ictx->data_ctx.is_valid());
   if (m_trace.valid()) {
     m_trace.copy_name(trace_name + std::string(" ") +
                       data_object_name(ictx, objectno));
index 6c303c963fa49db1e06259d9dc2e047c4b25e5a0..58eb056b493643b492e99fdb61022415c0f72f93 100644 (file)
@@ -1487,7 +1487,7 @@ namespace librbd {
   int64_t Image::get_data_pool_id()
   {
     ImageCtx *ictx = reinterpret_cast<ImageCtx *>(ctx);
-    return ictx->data_ctx.get_id();
+    return librbd::api::Image<>::get_data_pool_id(ictx);
   }
 
   int Image::parent_info(string *parent_pool_name, string *parent_name,
@@ -4261,7 +4261,7 @@ extern "C" int rbd_get_block_name_prefix(rbd_image_t image, char *prefix,
 extern "C" int64_t rbd_get_data_pool_id(rbd_image_t image)
 {
   librbd::ImageCtx *ictx = reinterpret_cast<librbd::ImageCtx *>(image);
-  return ictx->data_ctx.get_id();
+  return librbd::api::Image<>::get_data_pool_id(ictx);
 }
 
 extern "C" int rbd_get_parent_info(rbd_image_t image,
index 37f9303fc3ba445ffba479e3a976b66cdbc6a462..50db3df851fb7ec178af32c662b80aaf70dc3994 100644 (file)
@@ -192,6 +192,11 @@ private:
 
 template <typename I>
 void ObjectMapIterateRequest<I>::send() {
+  if (!m_image_ctx.data_ctx.is_valid()) {
+    this->async_complete(-ENODEV);
+    return;
+  }
+
   send_verify_objects();
 }
 
@@ -199,6 +204,12 @@ template <typename I>
 bool ObjectMapIterateRequest<I>::should_complete(int r) {
   CephContext *cct = m_image_ctx.cct;
   ldout(cct, 5) << this << " should_complete: " << " r=" << r << dendl;
+
+  if (r == -ENODEV) {
+    lderr(cct) << "missing data pool" << dendl;
+    return true;
+  }
+
   if (r < 0) {
     lderr(cct) << "object map operation encountered an error: "
               << cpp_strerror(r) << dendl;
index 763625b4ac917f2c3138e509b5314b83cca71649..c949a7958fc0ec1b298ba34f0dee8a3b47a9ca34 100644 (file)
@@ -36,6 +36,15 @@ SnapshotCreateRequest<I>::SnapshotCreateRequest(I &image_ctx,
 
 template <typename I>
 void SnapshotCreateRequest<I>::send_op() {
+  I &image_ctx = this->m_image_ctx;
+  CephContext *cct = image_ctx.cct;
+
+  if (!image_ctx.data_ctx.is_valid()) {
+    lderr(cct) << "missing data pool" << dendl;
+    this->async_complete(-ENODEV);
+    return;
+  }
+
   send_suspend_requests();
 }
 
index e120aea4ae801263886faccac7215a14ab048b6c..cbfc938760a1a62f1582f51d1f8e5ff4edbe1e6a 100644 (file)
@@ -261,6 +261,11 @@ template <typename I>
 void SnapshotRemoveRequest<I>::release_snap_id() {
   I &image_ctx = this->m_image_ctx;
 
+  if (!image_ctx.data_ctx.is_valid()) {
+    remove_snap();
+    return;
+  }
+
   CephContext *cct = image_ctx.cct;
   ldout(cct, 5) << "snap_name=" << m_snap_name << ", "
                 << "snap_id=" << m_snap_id << dendl;
index 0c5a4efab4b67e2b99c698b8ee6ab2a1a3f0012c..5d9837c3eab25669b42b5e172acb9ab686fc5f1d 100644 (file)
@@ -124,6 +124,11 @@ public:
 
     ldout(m_cct, 20) << dendl;
 
+    if (!image_ctx.data_ctx.is_valid()) {
+      lderr(m_cct) << "missing data pool" << dendl;
+      return -ENODEV;
+    }
+
     if (image_ctx.exclusive_lock != nullptr &&
         !image_ctx.exclusive_lock->is_lock_owner()) {
       ldout(m_cct, 1) << "lost exclusive lock during sparsify" << dendl;
index f1a34a5592bf00b11534c2444446ebb22d9bc6f1..3c2f58f64af827d8d67ba7fc59164c2464c955d6 100644 (file)
@@ -174,6 +174,15 @@ bool TrimRequest<I>::should_complete(int r)
 
 template <typename I>
 void TrimRequest<I>::send() {
+  I &image_ctx = this->m_image_ctx;
+  CephContext *cct = image_ctx.cct;
+
+  if (!image_ctx.data_ctx.is_valid()) {
+    lderr(cct) << "missing data pool" << dendl;
+    send_finish(-ENODEV);
+    return;
+  }
+
   send_pre_trim();
 }
 
index 8274e04b1086dd6399586121ddd850b5ac6a70bc..0f89928818c214e064151565fe158306b051e7eb 100644 (file)
@@ -375,7 +375,7 @@ public:
   }
 
   void expect_init_layout(MockRefreshImageCtx &mock_image_ctx) {
-    EXPECT_CALL(mock_image_ctx, init_layout());
+    EXPECT_CALL(mock_image_ctx, init_layout(_));
   }
 
   void expect_test_features(MockRefreshImageCtx &mock_image_ctx) {
index 0b8a9345c27ae7bb973ad419c7c72fb0ec734a29..092b90f40a49a296b7548266f1b3f2fcc7794155 100644 (file)
@@ -139,7 +139,7 @@ struct MockImageCtx {
     ctx.wait();
   }
 
-  MOCK_METHOD0(init_layout, void());
+  MOCK_METHOD1(init_layout, void(int64_t));
 
   MOCK_CONST_METHOD1(get_object_name, std::string(uint64_t));
   MOCK_CONST_METHOD0(get_object_size, uint64_t());
index 84b4d1c3c29dd4a162ff746d87933e149ed4cc1e..7e9bd9eb939ae42d3c432cfca991e00ec4b8568e 100644 (file)
@@ -1739,3 +1739,72 @@ TEST_F(TestInternal, SparsifyClone) {
   ASSERT_EQ(0, ictx->data_ctx.stat(oid, &size, NULL));
   ASSERT_EQ(0, ictx->data_ctx.read(oid, read_bl, 4096, 0));
 }
+
+TEST_F(TestInternal, MissingDataPool) {
+  REQUIRE_FORMAT_V2();
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+  ASSERT_EQ(0, snap_create(*ictx, "snap1"));
+  std::string header_oid = ictx->header_oid;
+  close_image(ictx);
+
+  // emulate non-existent data pool
+  int64_t pool_id = 1234;
+  std::string pool_name;
+  int r;
+  while ((r = _rados.pool_reverse_lookup(pool_id, &pool_name)) == 0) {
+    pool_id++;
+  }
+  ASSERT_EQ(r, -ENOENT);
+  bufferlist bl;
+  using ceph::encode;
+  encode(pool_id, bl);
+  ASSERT_EQ(0, m_ioctx.omap_set(header_oid, {{"data_pool_id", bl}}));
+
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  ASSERT_FALSE(ictx->data_ctx.is_valid());
+  ASSERT_EQ(pool_id, librbd::api::Image<>::get_data_pool_id(ictx));
+
+  librbd::image_info_t info;
+  ASSERT_EQ(0, librbd::info(ictx, info, sizeof(info)));
+
+  vector<librbd::snap_info_t> snaps;
+  EXPECT_EQ(0, librbd::snap_list(ictx, snaps));
+  EXPECT_EQ(1U, snaps.size());
+  EXPECT_EQ("snap1", snaps[0].name);
+
+  bufferptr read_ptr(256);
+  bufferlist read_bl;
+  read_bl.push_back(read_ptr);
+  librbd::io::ReadResult read_result{&read_bl};
+  ASSERT_EQ(-ENODEV,
+            ictx->io_work_queue->read(0, 256,
+                                      librbd::io::ReadResult{read_result}, 0));
+  ASSERT_EQ(-ENODEV,
+            ictx->io_work_queue->write(0, bl.length(), bufferlist{bl}, 0));
+  ASSERT_EQ(-ENODEV, ictx->io_work_queue->discard(0, 1, 256));
+  ASSERT_EQ(-ENODEV,
+            ictx->io_work_queue->writesame(0, bl.length(), bufferlist{bl}, 0));
+  uint64_t mismatch_off;
+  ASSERT_EQ(-ENODEV,
+            ictx->io_work_queue->compare_and_write(0, bl.length(),
+                                                   bufferlist{bl},
+                                                   bufferlist{bl},
+                                                   &mismatch_off, 0));
+  ASSERT_EQ(-ENODEV, ictx->io_work_queue->flush());
+
+  ASSERT_EQ(-ENODEV, snap_create(*ictx, "snap2"));
+  ASSERT_EQ(0, ictx->operations->snap_remove(cls::rbd::UserSnapshotNamespace(),
+                                             "snap1"));
+
+  librbd::NoOpProgressContext no_op;
+  ASSERT_EQ(-ENODEV, ictx->operations->resize(0, true, no_op));
+
+  close_image(ictx);
+
+  ASSERT_EQ(0, librbd::api::Image<>::remove(m_ioctx, m_image_name, no_op));
+
+  ASSERT_EQ(0, create_image_pp(m_rbd, m_ioctx, m_image_name, m_image_size));
+}