]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
librbd: new attach child state machine
authorMykola Golub <mgolub@suse.com>
Wed, 16 Jan 2019 11:58:04 +0000 (11:58 +0000)
committerMykola Golub <mgolub@suse.com>
Tue, 19 Feb 2019 08:43:09 +0000 (08:43 +0000)
Signed-off-by: Mykola Golub <mgolub@suse.com>
src/librbd/CMakeLists.txt
src/librbd/image/AttachChildRequest.cc [new file with mode: 0644]
src/librbd/image/AttachChildRequest.h [new file with mode: 0644]
src/test/librbd/CMakeLists.txt
src/test/librbd/image/test_mock_AttachChildRequest.cc [new file with mode: 0644]

index 549461c39b4511b92d8e1b581191c5bcc70c93f4..e7da59cf267f6b24ca61d0f45f74c404dae3c490 100644 (file)
@@ -50,6 +50,7 @@ set(librbd_internal_srcs
   exclusive_lock/PostAcquireRequest.cc
   exclusive_lock/PreReleaseRequest.cc
   exclusive_lock/StandardPolicy.cc
+  image/AttachChildRequest.cc
   image/AttachParentRequest.cc
   image/CloneRequest.cc
   image/CloseRequest.cc
diff --git a/src/librbd/image/AttachChildRequest.cc b/src/librbd/image/AttachChildRequest.cc
new file mode 100644 (file)
index 0000000..9c95e9f
--- /dev/null
@@ -0,0 +1,263 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/image/AttachChildRequest.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "cls/rbd/cls_rbd_client.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/Utils.h"
+#include "librbd/image/RefreshRequest.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::image::AttachChildRequest: " << this \
+                           << " " << __func__ << ": "
+
+namespace librbd {
+namespace image {
+
+using util::create_context_callback;
+using util::create_rados_callback;
+
+template <typename I>
+AttachChildRequest<I>::AttachChildRequest(
+    I *image_ctx, I *parent_image_ctx, const librados::snap_t &parent_snap_id,
+    I *old_parent_image_ctx, const librados::snap_t &old_parent_snap_id,
+    uint32_t clone_format, Context* on_finish)
+    : m_image_ctx(image_ctx), m_parent_image_ctx(parent_image_ctx),
+      m_parent_snap_id(parent_snap_id),
+      m_old_parent_image_ctx(old_parent_image_ctx),
+      m_old_parent_snap_id(old_parent_snap_id), m_clone_format(clone_format),
+      m_on_finish(on_finish), m_cct(m_image_ctx->cct) {
+}
+
+template <typename I>
+void AttachChildRequest<I>::send() {
+  if (m_clone_format == 1) {
+    v1_add_child();
+  } else {
+    v2_set_op_feature();
+  }
+}
+
+template <typename I>
+void AttachChildRequest<I>::v1_add_child() {
+  ldout(m_cct, 15) << dendl;
+
+  librados::ObjectWriteOperation op;
+  cls_client::add_child(&op, {m_parent_image_ctx->md_ctx.get_id(),
+                              m_parent_image_ctx->md_ctx.get_namespace(),
+                              m_parent_image_ctx->id,
+                              m_parent_snap_id}, m_image_ctx->id);
+
+  using klass = AttachChildRequest<I>;
+  librados::AioCompletion *comp =
+    create_rados_callback<klass, &klass::handle_v1_add_child>(this);
+  int r = m_image_ctx->md_ctx.aio_operate(RBD_CHILDREN, comp, &op);
+  ceph_assert(r == 0);
+  comp->release();
+}
+
+template <typename I>
+void AttachChildRequest<I>::handle_v1_add_child(int r) {
+  ldout(m_cct, 15) << "r=" << r << dendl;
+
+  if (r < 0) {
+    if (r == -EEXIST && m_old_parent_image_ctx != nullptr) {
+      ldout(m_cct, 5) << "child already exists" << dendl;
+    } else {
+      lderr(m_cct) << "couldn't add child: " << cpp_strerror(r) << dendl;
+      finish(r);
+      return;
+    }
+  }
+
+  v1_refresh();
+}
+
+template <typename I>
+void AttachChildRequest<I>::v1_refresh() {
+  ldout(m_cct, 15) << dendl;
+
+  using klass = AttachChildRequest<I>;
+  RefreshRequest<I> *req = RefreshRequest<I>::create(
+      *m_image_ctx, false, false,
+      create_context_callback<klass, &klass::handle_v1_refresh>(this));
+  req->send();
+}
+
+template <typename I>
+void AttachChildRequest<I>::handle_v1_refresh(int r) {
+  ldout(m_cct, 15) << "r=" << r << dendl;
+
+  bool snap_protected = false;
+  if (r == 0) {
+    m_parent_image_ctx->snap_lock.get_read();
+    r = m_parent_image_ctx->is_snap_protected(m_parent_snap_id,
+                                              &snap_protected);
+    m_parent_image_ctx->snap_lock.put_read();
+  }
+
+  if (r < 0 || !snap_protected) {
+    lderr(m_cct) << "validate protected failed" << dendl;
+    finish(-EINVAL);
+    return;
+  }
+
+  v1_remove_child_from_old_parent();
+}
+
+template <typename I>
+void AttachChildRequest<I>::v1_remove_child_from_old_parent() {
+  if (m_old_parent_image_ctx == nullptr) {
+    finish(0);
+    return;
+  }
+
+  ldout(m_cct, 15) << dendl;
+
+  librados::ObjectWriteOperation op;
+  cls_client::remove_child(&op, {m_old_parent_image_ctx->md_ctx.get_id(),
+                                 m_old_parent_image_ctx->md_ctx.get_namespace(),
+                                 m_old_parent_image_ctx->id,
+                                 m_old_parent_snap_id}, m_image_ctx->id);
+
+  using klass = AttachChildRequest<I>;
+  librados::AioCompletion *comp = create_rados_callback<
+      klass, &klass::handle_v1_remove_child_from_old_parent>(this);
+  int r = m_image_ctx->md_ctx.aio_operate(RBD_CHILDREN, comp, &op);
+  ceph_assert(r == 0);
+  comp->release();
+}
+
+template <typename I>
+void AttachChildRequest<I>::handle_v1_remove_child_from_old_parent(int r) {
+  ldout(m_cct, 15) << "r=" << r << dendl;
+
+  if (r < 0 && r != -ENOENT) {
+    lderr(m_cct) << "couldn't remove child: " << cpp_strerror(r) << dendl;
+    finish(r);
+    return;
+  }
+
+  finish(0);
+}
+
+template <typename I>
+void AttachChildRequest<I>::v2_set_op_feature() {
+  ldout(m_cct, 15) << dendl;
+
+  librados::ObjectWriteOperation op;
+  cls_client::op_features_set(&op, RBD_OPERATION_FEATURE_CLONE_CHILD,
+                              RBD_OPERATION_FEATURE_CLONE_CHILD);
+
+  using klass = AttachChildRequest<I>;
+  auto aio_comp = create_rados_callback<
+      klass, &klass::handle_v2_set_op_feature>(this);
+  int r = m_image_ctx->md_ctx.aio_operate(m_image_ctx->header_oid, aio_comp,
+                                          &op);
+  ceph_assert(r == 0);
+  aio_comp->release();
+}
+
+template <typename I>
+void AttachChildRequest<I>::handle_v2_set_op_feature(int r) {
+  ldout(m_cct, 15) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(m_cct) << "failed to enable clone v2: " << cpp_strerror(r) << dendl;
+    finish(r);
+    return;
+  }
+
+  v2_child_attach();
+}
+
+template <typename I>
+void AttachChildRequest<I>::v2_child_attach() {
+  ldout(m_cct, 15) << dendl;
+
+  librados::ObjectWriteOperation op;
+  cls_client::child_attach(&op, m_parent_snap_id,
+                           {m_image_ctx->md_ctx.get_id(),
+                            m_image_ctx->md_ctx.get_namespace(),
+                            m_image_ctx->id});
+
+  using klass = AttachChildRequest<I>;
+  auto aio_comp = create_rados_callback<
+      klass, &klass::handle_v2_child_attach>(this);
+  int r = m_parent_image_ctx->md_ctx.aio_operate(m_parent_image_ctx->header_oid,
+                                                 aio_comp, &op);
+  ceph_assert(r == 0);
+  aio_comp->release();
+}
+
+template <typename I>
+void AttachChildRequest<I>::handle_v2_child_attach(int r) {
+  ldout(m_cct, 15) << "r=" << r << dendl;
+
+  if (r < 0) {
+    if (r == -EEXIST && m_old_parent_image_ctx != nullptr) {
+      ldout(m_cct, 5) << "child already exists" << dendl;
+    } else {
+      lderr(m_cct) << "failed to attach child image: " << cpp_strerror(r)
+                   << dendl;
+      finish(r);
+      return;
+    }
+  }
+
+  v2_child_detach_from_old_parent();
+}
+
+template <typename I>
+void AttachChildRequest<I>::v2_child_detach_from_old_parent() {
+  if (m_old_parent_image_ctx == nullptr) {
+    finish(0);
+    return;
+  }
+
+  ldout(m_cct, 15) << dendl;
+
+  librados::ObjectWriteOperation op;
+  cls_client::child_detach(&op, m_old_parent_snap_id,
+                           {m_image_ctx->md_ctx.get_id(),
+                            m_image_ctx->md_ctx.get_namespace(),
+                            m_image_ctx->id});
+
+  using klass = AttachChildRequest<I>;
+  auto aio_comp = create_rados_callback<
+      klass, &klass::handle_v2_child_detach_from_old_parent>(this);
+  int r = m_old_parent_image_ctx->md_ctx.aio_operate(
+      m_old_parent_image_ctx->header_oid, aio_comp, &op);
+  ceph_assert(r == 0);
+  aio_comp->release();
+}
+
+template <typename I>
+void AttachChildRequest<I>::handle_v2_child_detach_from_old_parent(int r) {
+  ldout(m_cct, 15) << "r=" << r << dendl;
+
+  if (r < 0 && r != -ENOENT) {
+    lderr(m_cct) << "failed to detach child image: " << cpp_strerror(r)
+                 << dendl;
+    finish(r);
+    return;
+  }
+
+  finish(0);
+}
+
+template <typename I>
+void AttachChildRequest<I>::finish(int r) {
+  ldout(m_cct, 5) << "r=" << r << dendl;
+
+  m_on_finish->complete(r);
+  delete this;
+}
+
+} // namespace image
+} // namespace librbd
+
+template class librbd::image::AttachChildRequest<librbd::ImageCtx>;
diff --git a/src/librbd/image/AttachChildRequest.h b/src/librbd/image/AttachChildRequest.h
new file mode 100644 (file)
index 0000000..d1450e8
--- /dev/null
@@ -0,0 +1,105 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_IMAGE_ATTACH_CHILD_REQUEST_H
+#define CEPH_LIBRBD_IMAGE_ATTACH_CHILD_REQUEST_H
+
+#include "include/int_types.h"
+#include "include/rados/librados.hpp"
+
+class CephContext;
+class Context;
+
+namespace librbd {
+
+class ImageCtx;
+
+namespace image {
+
+template <typename ImageCtxT = ImageCtx>
+class AttachChildRequest {
+public:
+  static AttachChildRequest* create(ImageCtxT *image_ctx,
+                                    ImageCtxT *parent_image_ctx,
+                                    const librados::snap_t &parent_snap_id,
+                                    ImageCtxT *old_parent_image_ctx,
+                                    const librados::snap_t &old_parent_snap_id,
+                                    uint32_t clone_format,
+                                    Context* on_finish) {
+      return new AttachChildRequest(image_ctx, parent_image_ctx, parent_snap_id,
+                                    old_parent_image_ctx, old_parent_snap_id,
+                                    clone_format, on_finish);
+  }
+
+  AttachChildRequest(ImageCtxT *image_ctx,
+                     ImageCtxT *parent_image_ctx,
+                     const librados::snap_t &parent_snap_id,
+                     ImageCtxT *old_parent_image_ctx,
+                     const librados::snap_t &old_parent_snap_id,
+                     uint32_t clone_format,
+                     Context* on_finish);
+
+  void send();
+
+private:
+  /**
+   * @verbatim
+   *
+   *                     <start>
+   *    (clone v1)          |        (clone v2)
+   *      /----------------/ \---------------\
+   *      |                                  |
+   *      v                                  v
+   *  V1 ADD CHILD                       V2 SET CLONE
+   *      |                                  |
+   *      v                                  v
+   *  V1 VALIDATE PROTECTED              V2 ATTACH CHILD
+   *      |                                  |
+   *      |                                  v
+   *  V1 REMOVE CHILD FROM OLD PARENT    V2 DETACH CHILD FROM OLD PARENT
+   *      |                                  |
+   *      \----------------\ /---------------/
+   *                        |
+   *                        v
+   *                     <finish>
+   *
+   * @endverbatim
+   */
+
+  ImageCtxT *m_image_ctx;
+  ImageCtxT *m_parent_image_ctx;
+  librados::snap_t m_parent_snap_id;
+  ImageCtxT *m_old_parent_image_ctx;
+  librados::snap_t m_old_parent_snap_id;
+  uint32_t m_clone_format;
+  Context* m_on_finish;
+
+  CephContext *m_cct;
+
+  void v1_add_child();
+  void handle_v1_add_child(int r);
+
+  void v1_refresh();
+  void handle_v1_refresh(int r);
+
+  void v1_remove_child_from_old_parent();
+  void handle_v1_remove_child_from_old_parent(int r);
+
+  void v2_set_op_feature();
+  void handle_v2_set_op_feature(int r);
+
+  void v2_child_attach();
+  void handle_v2_child_attach(int r);
+
+  void v2_child_detach_from_old_parent();
+  void handle_v2_child_detach_from_old_parent(int r);
+
+  void finish(int r);
+};
+
+} // namespace image
+} // namespace librbd
+
+extern template class librbd::image::AttachChildRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_IMAGE_ATTACH_CHILD_REQUEST_H
index 002b0d2c0a9bb3ee761f3844207bbdb9b2beea14..53570db52634201ee076afa0ce62b9d191581b81 100644 (file)
@@ -58,6 +58,7 @@ set(unittest_librbd_srcs
   exclusive_lock/test_mock_PreAcquireRequest.cc
   exclusive_lock/test_mock_PostAcquireRequest.cc
   exclusive_lock/test_mock_PreReleaseRequest.cc
+  image/test_mock_AttachChildRequest.cc
   image/test_mock_AttachParentRequest.cc
   image/test_mock_CloneRequest.cc
   image/test_mock_DetachChildRequest.cc
diff --git a/src/test/librbd/image/test_mock_AttachChildRequest.cc b/src/test/librbd/image/test_mock_AttachChildRequest.cc
new file mode 100644 (file)
index 0000000..14f9a73
--- /dev/null
@@ -0,0 +1,272 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librbd/test_mock_fixture.h"
+#include "test/librbd/test_support.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/librbd/mock/MockContextWQ.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "librbd/ImageState.h"
+#include "librbd/Operations.h"
+#include "librbd/image/AttachChildRequest.h"
+#include "librbd/image/RefreshRequest.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace librbd {
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+  MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) {
+  }
+};
+
+} // anonymous namespace
+
+namespace image {
+
+template <>
+struct RefreshRequest<MockTestImageCtx> {
+  Context* on_finish = nullptr;
+  static RefreshRequest* s_instance;
+  static RefreshRequest* create(MockTestImageCtx &image_ctx,
+                                bool acquiring_lock, bool skip_open_parent,
+                                Context *on_finish) {
+    ceph_assert(s_instance != nullptr);
+    s_instance->on_finish = on_finish;
+    return s_instance;
+  }
+
+  MOCK_METHOD0(send, void());
+
+  RefreshRequest() {
+    s_instance = this;
+  }
+};
+
+RefreshRequest<MockTestImageCtx>* RefreshRequest<MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace image
+
+} // namespace librbd
+
+// template definitions
+#include "librbd/image/AttachChildRequest.cc"
+
+namespace librbd {
+namespace image {
+
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::WithArg;
+
+class TestMockImageAttachChildRequest : public TestMockFixture {
+public:
+  typedef AttachChildRequest<MockTestImageCtx> MockAttachChildRequest;
+  typedef RefreshRequest<MockTestImageCtx> MockRefreshRequest;
+
+  void SetUp() override {
+    TestMockFixture::SetUp();
+
+    ASSERT_EQ(0, open_image(m_image_name, &image_ctx));
+    ASSERT_EQ(0, image_ctx->operations->snap_create(
+                   cls::rbd::UserSnapshotNamespace{}, "snap"));
+    if (is_feature_enabled(RBD_FEATURE_LAYERING)) {
+      ASSERT_EQ(0, image_ctx->operations->snap_protect(
+                     cls::rbd::UserSnapshotNamespace{}, "snap"));
+
+      uint64_t snap_id = image_ctx->snap_ids[
+        {cls::rbd::UserSnapshotNamespace{}, "snap"}];
+      ASSERT_NE(CEPH_NOSNAP, snap_id);
+
+      C_SaferCond ctx;
+      image_ctx->state->snap_set(snap_id, &ctx);
+      ASSERT_EQ(0, ctx.wait());
+    }
+  }
+
+  void expect_add_child(MockImageCtx &mock_image_ctx, int r) {
+    EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+                exec(RBD_CHILDREN, _, StrEq("rbd"), StrEq("add_child"), _, _, _))
+      .WillOnce(Return(r));
+  }
+
+  void expect_refresh(MockRefreshRequest& mock_refresh_request, int r) {
+    EXPECT_CALL(mock_refresh_request, send())
+      .WillOnce(Invoke([this, &mock_refresh_request, r]() {
+                  image_ctx->op_work_queue->queue(mock_refresh_request.on_finish, r);
+                }));
+  }
+
+  void expect_is_snap_protected(MockImageCtx &mock_image_ctx, bool is_protected,
+                                int r) {
+    EXPECT_CALL(mock_image_ctx, is_snap_protected(_, _))
+      .WillOnce(WithArg<1>(Invoke([is_protected, r](bool* is_prot) {
+                             *is_prot = is_protected;
+                             return r;
+                           })));
+  }
+
+  void expect_op_features_set(MockImageCtx &mock_image_ctx, int r) {
+    bufferlist bl;
+    encode(static_cast<uint64_t>(RBD_OPERATION_FEATURE_CLONE_CHILD), bl);
+    encode(static_cast<uint64_t>(RBD_OPERATION_FEATURE_CLONE_CHILD), bl);
+
+    EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+                exec(util::header_name(mock_image_ctx.id), _, StrEq("rbd"),
+                     StrEq("op_features_set"), ContentsEqual(bl), _, _))
+      .WillOnce(Return(r));
+  }
+
+  void expect_child_attach(MockImageCtx &mock_image_ctx, int r) {
+    bufferlist bl;
+    encode(mock_image_ctx.snap_id, bl);
+    encode(cls::rbd::ChildImageSpec{m_ioctx.get_id(), "", mock_image_ctx.id},
+           bl);
+
+    EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+                exec(mock_image_ctx.header_oid, _, StrEq("rbd"),
+                     StrEq("child_attach"), ContentsEqual(bl), _, _))
+      .WillOnce(Return(r));
+  }
+
+  librbd::ImageCtx *image_ctx;
+};
+
+TEST_F(TestMockImageAttachChildRequest, SuccessV1) {
+  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+  MockTestImageCtx mock_image_ctx(*image_ctx);
+
+  InSequence seq;
+
+  expect_add_child(mock_image_ctx, 0);
+
+  MockRefreshRequest mock_refresh_request;
+  expect_refresh(mock_refresh_request, 0);
+  expect_is_snap_protected(mock_image_ctx, true, 0);
+
+  C_SaferCond ctx;
+  auto req = MockAttachChildRequest::create(&mock_image_ctx, &mock_image_ctx,
+                                            image_ctx->snap_id, nullptr, 0, 1,
+                                            &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageAttachChildRequest, SuccessV2) {
+  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+  MockTestImageCtx mock_image_ctx(*image_ctx);
+
+  InSequence seq;
+
+  expect_op_features_set(mock_image_ctx, 0);
+  expect_child_attach(mock_image_ctx, 0);
+
+  C_SaferCond ctx;
+  auto req = MockAttachChildRequest::create(&mock_image_ctx, &mock_image_ctx,
+                                            image_ctx->snap_id, nullptr, 0, 2,
+                                            &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageAttachChildRequest, AddChildError) {
+  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+  MockTestImageCtx mock_image_ctx(*image_ctx);
+
+  InSequence seq;
+
+  expect_add_child(mock_image_ctx, -EINVAL);
+
+  C_SaferCond ctx;
+  auto req = MockAttachChildRequest::create(&mock_image_ctx, &mock_image_ctx,
+                                            image_ctx->snap_id, nullptr, 0, 1,
+                                            &ctx);
+  req->send();
+  ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageAttachChildRequest, RefreshError) {
+  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+  MockTestImageCtx mock_image_ctx(*image_ctx);
+
+  InSequence seq;
+
+  expect_add_child(mock_image_ctx, 0);
+
+  MockRefreshRequest mock_refresh_request;
+  expect_refresh(mock_refresh_request, -EINVAL);
+
+  C_SaferCond ctx;
+  auto req = MockAttachChildRequest::create(&mock_image_ctx, &mock_image_ctx,
+                                            image_ctx->snap_id, nullptr, 0, 1,
+                                            &ctx);
+  req->send();
+  ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageAttachChildRequest, ValidateProtectedFailed) {
+  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+  MockTestImageCtx mock_image_ctx(*image_ctx);
+
+  InSequence seq;
+
+  expect_add_child(mock_image_ctx, 0);
+
+  MockRefreshRequest mock_refresh_request;
+  expect_refresh(mock_refresh_request, 0);
+  expect_is_snap_protected(mock_image_ctx, false, 0);
+
+  C_SaferCond ctx;
+  auto req = MockAttachChildRequest::create(&mock_image_ctx, &mock_image_ctx,
+                                            image_ctx->snap_id, nullptr, 0, 1,
+                                            &ctx);
+  req->send();
+  ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageAttachChildRequest, SetCloneError) {
+  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+  MockTestImageCtx mock_image_ctx(*image_ctx);
+
+  InSequence seq;
+
+  expect_op_features_set(mock_image_ctx, -EINVAL);
+
+  C_SaferCond ctx;
+  auto req = MockAttachChildRequest::create(&mock_image_ctx, &mock_image_ctx,
+                                            image_ctx->snap_id, nullptr, 0, 2,
+                                            &ctx);
+  req->send();
+  ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageAttachChildRequest, AttachChildError) {
+  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+  MockTestImageCtx mock_image_ctx(*image_ctx);
+
+  InSequence seq;
+
+  expect_op_features_set(mock_image_ctx, 0);
+  expect_child_attach(mock_image_ctx, -EINVAL);
+
+  C_SaferCond ctx;
+  auto req = MockAttachChildRequest::create(&mock_image_ctx, &mock_image_ctx,
+                                            image_ctx->snap_id, nullptr, 0, 2,
+                                            &ctx);
+  req->send();
+  ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+} // namespace image
+} // namespace librbd