]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
librbd: optionally move parent image to trash on remove 27521/head
authorMykola Golub <mgolub@suse.com>
Mon, 15 Apr 2019 10:35:53 +0000 (11:35 +0100)
committerMykola Golub <mgolub@suse.com>
Fri, 19 Apr 2019 07:53:38 +0000 (08:53 +0100)
and auto-delete when the last clone is detached

Signed-off-by: Mykola Golub <mgolub@suse.com>
13 files changed:
qa/workunits/rbd/cli_generic.sh
src/cls/rbd/cls_rbd_types.h
src/common/options.cc
src/include/rbd/librbd.h
src/librbd/api/Image.cc
src/librbd/image/DetachChildRequest.cc
src/librbd/image/DetachChildRequest.h
src/librbd/image/PreRemoveRequest.cc
src/librbd/image/PreRemoveRequest.h
src/librbd/image/RemoveRequest.cc
src/librbd/operation/SnapshotRemoveRequest.cc
src/test/librbd/image/test_mock_DetachChildRequest.cc
src/tools/rbd/action/Trash.cc

index 1a46df10c5103874250c607a709eada7e5675a1e..b4b8f4774c85073fafcf6d17b53aa312548de6d4 100755 (executable)
@@ -576,6 +576,21 @@ test_clone_v2() {
     rbd snap list --all test1 | wc -l | grep '^0$'
     rbd rm test1
     rbd rm test2
+
+    rbd create $RBD_CREATE_ARGS -s 1 test1
+    rbd snap create test1@1
+    rbd snap create test1@2
+    rbd clone test1@1 test2 --rbd-default-clone-format 2
+    rbd clone test1@2 test3 --rbd-default-clone-format 2
+    rbd snap rm test1@1
+    rbd snap rm test1@2
+    expect_fail rbd rm test1
+    rbd rm test1 --rbd-move-parent-to-trash-on-remove=true
+    rbd trash ls -a | grep test1
+    rbd rm test2
+    rbd trash ls -a | grep test1
+    rbd rm test3
+    rbd trash ls -a | expect_fail grep test1
 }
 
 test_thick_provision() {
index bbfc85870b21b0519298fbe4f0f9d67d60774560..74dfe1d99de207ce49059abf8dc077f73870f66e 100644 (file)
@@ -560,6 +560,7 @@ enum TrashImageSource {
   TRASH_IMAGE_SOURCE_MIRRORING = 1,
   TRASH_IMAGE_SOURCE_MIGRATION = 2,
   TRASH_IMAGE_SOURCE_REMOVING = 3,
+  TRASH_IMAGE_SOURCE_USER_PARENT= 4,
 };
 
 inline std::ostream& operator<<(std::ostream& os,
index f7db2a72aaa05e5352ed429b2e3752788f328ffe..34cf35ca5356d3dbdc961dc37f773359684a79a3 100644 (file)
@@ -7153,6 +7153,10 @@ static std::vector<Option> get_rbd_options() {
     .set_default(0)
     .set_description("default number of seconds to protect deleted images in the trash"),
 
+    Option("rbd_move_parent_to_trash_on_remove", Option::TYPE_BOOL, Option::LEVEL_BASIC)
+    .set_default(false)
+    .set_description("move parent with clone format v2 children to the trash when deleted"),
+
     Option("rbd_mirroring_resync_after_disconnect", Option::TYPE_BOOL, Option::LEVEL_ADVANCED)
     .set_default(false)
     .set_description("automatically start image resync after mirroring is disconnected due to being laggy"),
index 1adffe781cf68e80e618be2e1c075007f1764881..1fa30dda750d80c9e94fdc3e8a11cbe32ca31be1 100644 (file)
@@ -238,7 +238,8 @@ typedef enum {
   RBD_TRASH_IMAGE_SOURCE_USER = 0,
   RBD_TRASH_IMAGE_SOURCE_MIRRORING = 1,
   RBD_TRASH_IMAGE_SOURCE_MIGRATION = 2,
-  RBD_TRASH_IMAGE_SOURCE_REMOVING = 3
+  RBD_TRASH_IMAGE_SOURCE_REMOVING = 3,
+  RBD_TRASH_IMAGE_SOURCE_USER_PARENT = 4,
 } rbd_trash_image_source_t;
 
 typedef struct {
index 13c5a90d8c547421945af4d0a7bd0484e646abad..8fbd7176652cebce6bd66e166bc538a5ca48f34d 100644 (file)
@@ -767,7 +767,15 @@ int Image<I>::remove(IoCtx& io_ctx, const std::string &image_name,
       // attempt to pre-validate the removal before moving to trash and
       // removing
       r = pre_remove_image<I>(io_ctx, image_id);
-      if (r < 0 && r != -ENOENT) {
+      if (r == -ECHILD) {
+        if (config.get_val<bool>("rbd_move_parent_to_trash_on_remove")) {
+          // keep the image in the trash until the last child is removed
+          trash_image_source = RBD_TRASH_IMAGE_SOURCE_USER_PARENT;
+        } else {
+          lderr(cct) << "image has snapshots - not removing" << dendl;
+          return -ENOTEMPTY;
+        }
+      } else if (r < 0 && r != -ENOENT) {
         return r;
       }
     }
index 89f5c949a90534b3d6eeedd7b146894d21e5a264..39221e1b25dc86a8b68a27a38a4ef474cdb65c78 100644 (file)
@@ -10,6 +10,7 @@
 #include "librbd/ImageState.h"
 #include "librbd/Operations.h"
 #include "librbd/Utils.h"
+#include "librbd/trash/RemoveRequest.h"
 #include <string>
 
 #define dout_subsys ceph_subsys_rbd
@@ -205,9 +206,94 @@ void DetachChildRequest<I>::handle_clone_v2_remove_snapshot(int r) {
   if (r < 0 && r != -ENOENT) {
     ldout(cct, 5) << "failed to remove trashed clone snapshot: "
                   << cpp_strerror(r) << dendl;
+    clone_v2_close_parent();
+    return;
+  }
+
+  if (m_parent_image_ctx->snaps.empty()) {
+    clone_v2_get_parent_trash_entry();
+  } else {
+    clone_v2_close_parent();
+  }
+}
+
+template<typename I>
+void DetachChildRequest<I>::clone_v2_get_parent_trash_entry() {
+  auto cct = m_image_ctx.cct;
+  ldout(cct, 5) << dendl;
+
+  librados::ObjectReadOperation op;
+  cls_client::trash_get_start(&op, m_parent_image_ctx->id);
+
+  m_out_bl.clear();
+  auto aio_comp = create_rados_callback<
+    DetachChildRequest<I>,
+    &DetachChildRequest<I>::handle_clone_v2_get_parent_trash_entry>(this);
+  int r = m_parent_io_ctx.aio_operate(RBD_TRASH, aio_comp, &op, &m_out_bl);
+  ceph_assert(r == 0);
+  aio_comp->release();
+}
+
+template<typename I>
+void DetachChildRequest<I>::handle_clone_v2_get_parent_trash_entry(int r) {
+  auto cct = m_image_ctx.cct;
+  ldout(cct, 5) << "r=" << r << dendl;
+
+  if (r < 0 && r != -ENOENT) {
+    ldout(cct, 5) << "failed to get parent trash entry: " << cpp_strerror(r)
+                  << dendl;
+    clone_v2_close_parent();
+    return;
+  }
+
+  bool in_trash = false;
+
+  if (r == 0) {
+    cls::rbd::TrashImageSpec trash_spec;
+    auto it = m_out_bl.cbegin();
+    r = cls_client::trash_get_finish(&it, &trash_spec);
+
+    if (r == 0 &&
+        trash_spec.source == cls::rbd::TRASH_IMAGE_SOURCE_USER_PARENT &&
+        trash_spec.state == cls::rbd::TRASH_IMAGE_STATE_NORMAL &&
+        trash_spec.deferment_end_time <= ceph_clock_now()) {
+      in_trash = true;
+    }
+  }
+
+  if (in_trash) {
+    clone_v2_remove_parent_from_trash();
+  } else {
+    clone_v2_close_parent();
   }
+}
 
-  clone_v2_close_parent();
+template<typename I>
+void DetachChildRequest<I>::clone_v2_remove_parent_from_trash() {
+  auto cct = m_image_ctx.cct;
+  ldout(cct, 5) << dendl;
+
+  auto ctx = create_context_callback<
+    DetachChildRequest<I>,
+    &DetachChildRequest<I>::handle_clone_v2_remove_parent_from_trash>(this);
+  auto req = librbd::trash::RemoveRequest<I>::create(
+      m_parent_io_ctx, m_parent_image_ctx, m_image_ctx.op_work_queue, false,
+      m_no_op, ctx);
+  req->send();
+}
+
+template<typename I>
+void DetachChildRequest<I>::handle_clone_v2_remove_parent_from_trash(int r) {
+  auto cct = m_image_ctx.cct;
+  ldout(cct, 5) << "r=" << r << dendl;
+
+  if (r < 0) {
+    ldout(cct, 5) << "failed to remove parent image:" << cpp_strerror(r)
+                  << dendl;
+  }
+
+  m_parent_image_ctx = nullptr;
+  finish(0);
 }
 
 template<typename I>
index bb0c81d21af851dba156570ac4634770c47f6045..646b7ec629d6e8163d269d2e774769c3800d45be 100644 (file)
@@ -8,6 +8,7 @@
 #include "include/buffer.h"
 #include "include/rados/librados.hpp"
 #include "librbd/Types.h"
+#include "librbd/internal.h"
 
 class Context;
 
@@ -50,13 +51,16 @@ private:
    *    |                               v
    *    |                           OPEN_PARENT
    *    |                               |
-   *    |                               v
-   *    |                           REMOVE_SNAPSHOT
-   *    |                               |
-   *    |                               v
-   *    |                           CLOSE_PARENT
-   *    |                               |
-   *    |/------------------------------/
+   *    |                               v           (has more children)
+   *    |                           REMOVE_SNAPSHOT ---------------\
+   *    |                               |                          |
+   *    |                               v                  (noent) |
+   *    |     (auto-delete when     GET_PARENT_TRASH_ENTRY . . . .\|
+   *    |      last child detached)     |                          |
+   *    |                               v                          v
+   *    |                           REMOVE_PARENT_FROM_TRASH   CLOSE_PARENT
+   *    |                               |                          |
+   *    |/------------------------------/--------------------------/
    *    |
    *    v
    * <finish>
@@ -77,6 +81,7 @@ private:
   ImageCtxT* m_parent_image_ctx = nullptr;
 
   ceph::bufferlist m_out_bl;
+  NoOpProgressContext m_no_op;
 
   void clone_v2_child_detach();
   void handle_clone_v2_child_detach(int r);
@@ -90,6 +95,12 @@ private:
   void clone_v2_remove_snapshot();
   void handle_clone_v2_remove_snapshot(int r);
 
+  void clone_v2_get_parent_trash_entry();
+  void handle_clone_v2_get_parent_trash_entry(int r);
+
+  void clone_v2_remove_parent_from_trash();
+  void handle_clone_v2_remove_parent_from_trash(int r);
+
   void clone_v2_close_parent();
   void handle_clone_v2_close_parent(int r);
 
index 0ed6cf946adc9a7cbe60184eb7e16a0760c3140a..48e89e5b5f2df67c971cb5de8b40168d9cfacf23 100644 (file)
@@ -143,7 +143,7 @@ void PreRemoveRequest<I>::check_image_snaps() {
     } else {
       m_image_ctx->snap_lock.put_read();
 
-      lderr(cct) << "image has snapshots - not removing" << dendl;
+      ldout(cct, 5) << "image has snapshots - not removing" << dendl;
       finish(-ENOTEMPTY);
       return;
     }
@@ -272,14 +272,15 @@ void PreRemoveRequest<I>::handle_remove_snapshot(int r) {
   auto cct = m_image_ctx->cct;
   ldout(cct, 5) << "r=" << r << dendl;
 
-  if (r < 0 && r != -ENOENT) {
+  if (r == -EBUSY) {
+    ldout(cct, 5) << "skipping attached child" << dendl;
+    if (m_ret_val == 0) {
+      m_ret_val = -ECHILD;
+    }
+  } else if (r < 0 && r != -ENOENT) {
     auto snap_id = m_snap_infos.begin()->first;
     lderr(cct) << "failed to auto-prune snapshot " << snap_id << ": "
                << cpp_strerror(r) << dendl;
-
-    if (r == -EBUSY) {
-      r = -ENOTEMPTY;
-    }
     finish(r);
     return;
   }
@@ -295,7 +296,11 @@ void PreRemoveRequest<I>::finish(int r) {
   auto cct = m_image_ctx->cct;
   ldout(cct, 5) << "r=" << r << dendl;
 
-  m_on_finish->complete(r);
+  if (m_ret_val == 0) {
+    m_ret_val = r;
+  }
+
+  m_on_finish->complete(m_ret_val);
   delete this;
 }
 
index 99981491b651dc543175a4f531aad13ca2fc59bc..3bb6ca6434d974f63db5587709f55f210f23c972 100644 (file)
@@ -69,6 +69,7 @@ private:
   std::list<obj_watch_t> m_watchers;
 
   std::map<uint64_t, SnapInfo> m_snap_infos;
+  int m_ret_val = 0;
 
   void acquire_exclusive_lock();
   void handle_exclusive_lock(int r);
index 88af466615a5e24b7bc1c15a14f06c175d0a9952..d5154b977ee58c005b18743cbd9fabfba99b36c7 100644 (file)
@@ -122,6 +122,9 @@ void RemoveRequest<I>::handle_pre_remove_image(int r) {
   ldout(m_cct, 5) << "r=" << r << dendl;
 
   if (r < 0) {
+    if (r == -ECHILD) {
+      r = -ENOTEMPTY;
+    }
     send_close_image(r);
     return;
   }
index 8c34ebf6c5342a8ad1d59bd2ed137fe6fc2b4405..072867b448640d334af731e4dc324ab392d4d95b 100644 (file)
@@ -55,7 +55,7 @@ bool SnapshotRemoveRequest<I>::should_complete(int r) {
   I &image_ctx = this->m_image_ctx;
   CephContext *cct = image_ctx.cct;
   ldout(cct, 5) << "r=" << r << dendl;
-  if (r < 0) {
+  if (r < 0 && r != -EBUSY) {
     lderr(cct) << "encountered error: " << cpp_strerror(r) << dendl;
   }
   return true;
index 37f38c63c9a81743b15979e3257421e9e0789e66..9c8618b7795794b2eb484141298a2a75f53cfa72 100644 (file)
@@ -7,7 +7,9 @@
 #include "test/librbd/mock/MockContextWQ.h"
 #include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
 #include "test/librados_test_stub/MockTestMemRadosClient.h"
+#include "librbd/image/TypeTraits.h"
 #include "librbd/image/DetachChildRequest.h"
+#include "librbd/trash/RemoveRequest.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 
@@ -32,6 +34,46 @@ struct MockTestImageCtx : public MockImageCtx {
 MockTestImageCtx* MockTestImageCtx::s_instance = nullptr;
 
 } // anonymous namespace
+
+namespace image {
+
+template <>
+struct TypeTraits<MockTestImageCtx> {
+  typedef librbd::MockContextWQ ContextWQ;
+};
+
+} // namespace image
+
+namespace trash {
+
+template <>
+class RemoveRequest<MockTestImageCtx> {
+private:
+  typedef ::librbd::image::TypeTraits<MockTestImageCtx> TypeTraits;
+  typedef typename TypeTraits::ContextWQ ContextWQ;
+public:
+  static RemoveRequest *s_instance;
+  static RemoveRequest *create(librados::IoCtx &ioctx,
+                               MockTestImageCtx *image_ctx,
+                               ContextWQ *op_work_queue, bool force,
+                               ProgressContext &prog_ctx, Context *on_finish) {
+    ceph_assert(s_instance != nullptr);
+    s_instance->on_finish = on_finish;
+    return s_instance;
+  }
+
+  Context *on_finish = nullptr;
+
+  RemoveRequest() {
+    s_instance = this;
+  }
+
+  MOCK_METHOD0(send, void());
+};
+
+RemoveRequest<MockTestImageCtx> *RemoveRequest<MockTestImageCtx>::s_instance;
+
+} // namespace trash
 } // namespace librbd
 
 // template definitions
@@ -52,6 +94,7 @@ using ::testing::WithArg;
 class TestMockImageDetachChildRequest : public TestMockFixture {
 public:
   typedef DetachChildRequest<MockTestImageCtx> MockDetachChildRequest;
+  typedef trash::RemoveRequest<MockTestImageCtx> MockTrashRemoveRequest;
 
   void SetUp() override {
     TestMockFixture::SetUp();
@@ -138,6 +181,28 @@ public:
                            })));
   }
 
+  void expect_trash_get(MockImageCtx &mock_image_ctx,
+                        librados::MockTestMemIoCtxImpl &mock_io_ctx_impl,
+                        const cls::rbd::TrashImageSpec& trash_spec,
+                        int r) {
+    using ceph::encode;
+    EXPECT_CALL(mock_io_ctx_impl,
+                exec(RBD_TRASH, _, StrEq("rbd"),
+                     StrEq("trash_get"), _, _, _))
+      .WillOnce(WithArg<5>(Invoke([trash_spec, r](bufferlist* bl) {
+                             encode(trash_spec, *bl);
+                             return r;
+                           })));
+  }
+
+  void expect_trash_remove(MockTrashRemoveRequest& mock_trash_remove_request,
+                           int r) {
+    EXPECT_CALL(mock_trash_remove_request, send())
+      .WillOnce(Invoke([&mock_trash_remove_request, r]() {
+          mock_trash_remove_request.on_finish->complete(r);
+        }));
+  }
+
   librbd::ImageCtx *image_ctx;
 };
 
@@ -198,6 +263,8 @@ TEST_F(TestMockImageDetachChildRequest, TrashedSnapshotSuccess) {
                        "snap1", 123, {}, 0}, 0);
   expect_open(mock_image_ctx, 0);
   expect_snap_remove(mock_image_ctx, "snap1", 0);
+  const cls::rbd::TrashImageSpec trash_spec;
+  expect_trash_get(mock_image_ctx, *mock_io_ctx_impl, trash_spec, -ENOENT);
   expect_close(mock_image_ctx, 0);
 
   C_SaferCond ctx;
@@ -206,6 +273,37 @@ TEST_F(TestMockImageDetachChildRequest, TrashedSnapshotSuccess) {
   ASSERT_EQ(0, ctx.wait());
 }
 
+TEST_F(TestMockImageDetachChildRequest, ParentAutoRemove) {
+  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+  MockTestImageCtx mock_image_ctx(*image_ctx);
+  mock_image_ctx.parent_md.spec = {m_ioctx.get_id(), "", "parent id", 234};
+
+  InSequence seq;
+  expect_test_op_features(mock_image_ctx, true);
+
+  librados::MockTestMemIoCtxImpl *mock_io_ctx_impl;
+  expect_create_ioctx(mock_image_ctx, &mock_io_ctx_impl);
+  expect_child_detach(mock_image_ctx, *mock_io_ctx_impl, 0);
+  expect_snapshot_get(mock_image_ctx, *mock_io_ctx_impl,
+                      "rbd_header.parent id",
+                      {234, {cls::rbd::TrashSnapshotNamespace{}},
+                       "snap1", 123, {}, 0}, 0);
+  expect_open(mock_image_ctx, 0);
+  expect_snap_remove(mock_image_ctx, "snap1", 0);
+  const cls::rbd::TrashImageSpec trash_spec =
+      {cls::rbd::TRASH_IMAGE_SOURCE_USER_PARENT, "parent", {}, {}};
+
+  expect_trash_get(mock_image_ctx, *mock_io_ctx_impl, trash_spec, 0);
+  MockTrashRemoveRequest mock_trash_remove_request;
+  expect_trash_remove(mock_trash_remove_request, 0);
+
+  C_SaferCond ctx;
+  auto req = MockDetachChildRequest::create(mock_image_ctx, &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
 TEST_F(TestMockImageDetachChildRequest, TrashedSnapshotInUse) {
   REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
 
index 327b20ba703b9a400f254c72b11c0b5a3179bfac..d9900611c2976880ed96c9e67ebe71180a9f6eac 100644 (file)
@@ -38,7 +38,8 @@ static const std::string EXPIRED_BEFORE("expired-before");
 static const std::string THRESHOLD("threshold");
 
 static bool is_not_trash_user(const librbd::trash_image_info_t &trash_info) {
-  return trash_info.source != RBD_TRASH_IMAGE_SOURCE_USER;
+  return trash_info.source != RBD_TRASH_IMAGE_SOURCE_USER &&
+    trash_info.source != RBD_TRASH_IMAGE_SOURCE_USER_PARENT;
 }
 
 void get_move_arguments(po::options_description *positional,
@@ -269,6 +270,9 @@ int do_list(librbd::RBD &rbd, librados::IoCtx& io_ctx, bool long_flag,
       case RBD_TRASH_IMAGE_SOURCE_REMOVING:
         del_source = "REMOVING";
         break;
+      case RBD_TRASH_IMAGE_SOURCE_USER_PARENT:
+        del_source = "USER_PARENT";
+        break;
     }
 
     std::string time_str = ctime(&entry.deletion_time);