]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
librbd/api/Mirror: enhancements to group_enable API
authorN Balachandran <nithya.balachandran@ibm.com>
Mon, 2 Jun 2025 04:08:52 +0000 (09:38 +0530)
committerIlya Dryomov <idryomov@redhat.com>
Fri, 19 Sep 2025 22:01:18 +0000 (00:01 +0200)
Notable enhancements have been made to the mirror group_enable API:
* Previously, member images were enabled for mirroring synchronously
  and one after another. They are now enabled asynchronously and
  concurrently, making  the time to enable a group independent of the
  number of member images it contains.

* Intermediate steps involved in enabling an image, such as fetching
  mirror peers, acquiring an exclusive lock, creating a mirror snapshot,
  among other operations, run asynchronously but are synchronized
  across all the group member images using C_Gather callbacks. An
  asynchronous operation on an image proceeds to the next step only
  after that operation completes for all member images. This allows for
  easier cleanup of member images and the group when an intermediate
  operation fails for one or more  member images.

* The exclusive locks on member images are now held for a shorter
  duration. All member image locks are acquired just before taking
  image snapshots and released after retrieving the snapshot IDs.
  Previously, the locks were obtained earlier in the sequence of
  operations, even for steps that did not need them, and were not
  explicitly released.

Signed-off-by: N Balachandran <nithya.balachandran@ibm.com>
Co-Authored-by: Ramana Raja <rraja@redhat.com>
Resolves: rhbz#2396582

qa/workunits/rbd/rbd_mirror_group_simple.sh
src/librbd/CMakeLists.txt
src/librbd/api/Mirror.cc
src/librbd/mirror/GroupEnableRequest.cc [new file with mode: 0644]
src/librbd/mirror/GroupEnableRequest.h [new file with mode: 0644]
src/librbd/mirror/snapshot/GroupImageCreatePrimaryRequest.cc [new file with mode: 0644]
src/librbd/mirror/snapshot/GroupImageCreatePrimaryRequest.h [new file with mode: 0644]

index 234c3bf19dc3abf88968c1d1cee5af77f0ee910b..99c5b3ae1a586903415e31250c9b2115ed5c8d08 100755 (executable)
@@ -1412,9 +1412,9 @@ test_group_with_clone_image()
   group_image_add "${primary_cluster}" "${pool}/${group}" "${pool}/child_image"
 
   # command fails with the following message now
-  #  2025-01-30T16:34:25.359+0000 7fc1a79bfb40 -1 librbd::api::Mirror: image_enable: mirroring is not enabled for the parent
-  #  2025-01-30T16:34:25.359+0000 7fc1a79bfb40 -1 librbd::api::Mirror: group_enable: failed enabling image: child_image: (22) Invalid argument
-  expect_failure "failed enabling image" rbd --cluster=${primary_cluster} mirror group enable ${pool}/${group}
+  #  2025-07-29T11:09:54.666-0400 7f06696006c0 -1 librbd::mirror::GroupEnableRequest: 0x5622625a6240 validate_images: cannot enable mirroring: cloned images are not supported
+  #  2025-07-29T11:09:54.673-0400 7f0671958d00 -1 librbd::api::Mirror: group_enable: failed to mirror enable group: (22) Invalid argument
+  expect_failure "cloned images are not supported" rbd --cluster=${primary_cluster} mirror group enable ${pool}/${group}
 
   # tidy up
   group_remove "${primary_cluster}" "${pool}/${group}"
@@ -1542,8 +1542,9 @@ test_images_different_pools()
   group_image_add "${primary_cluster}" "${pool0}/${group}" "${pool1}/${image_prefix}1"
 
   # command fails with the following message now
-  #  2025-06-12T17:33:25.241+0530 7fe40ccfbd00 -1 librbd::api::Mirror: prepare_group_images: cannot enable mirroring: image is in a different pool
-  expect_failure "cannot enable mirroring: image is in a different pool" rbd --cluster=${primary_cluster} mirror group enable ${pool0}/${group}
+  # 2025-07-29T11:16:24.690-0400 7ff66b4006c0 -1 librbd::mirror::GroupEnableRequest: 0x55c172e13ec0 validate_images: cannot enable mirroring: image in a different pool
+  # 2025-07-29T11:16:24.693-0400 7ff6778b3d00 -1 librbd::api::Mirror: group_enable: failed to mirror enable group: (22) Invalid argument
+  expect_failure "image in a different pool" rbd --cluster=${primary_cluster} mirror group enable ${pool0}/${group}
 
   # tidy up
   group_remove "${primary_cluster}" "${pool0}/${group}"
index 426b84c9aa3d9f7345eaf2317106fccdd046548e..4c57d31145c4aa83711566612110925d2c945a4f 100644 (file)
@@ -153,6 +153,7 @@ set(librbd_internal_srcs
   mirror/GetInfoRequest.cc
   mirror/GetStatusRequest.cc
   mirror/GetUuidRequest.cc
+  mirror/GroupEnableRequest.cc
   mirror/GroupGetInfoRequest.cc
   mirror/ImageRemoveRequest.cc
   mirror/ImageStateUpdateRequest.cc
@@ -162,6 +163,7 @@ set(librbd_internal_srcs
   mirror/snapshot/DemoteRequest.cc
   mirror/snapshot/GetImageStateRequest.cc
   mirror/snapshot/GroupCreatePrimaryRequest.cc
+  mirror/snapshot/GroupImageCreatePrimaryRequest.cc
   mirror/snapshot/GroupUnlinkPeerRequest.cc
   mirror/snapshot/ImageMeta.cc
   mirror/snapshot/PromoteRequest.cc
index 8fc17dca77f219f4f0b24621d2dc92105be4cd9f..62bfc0028c68ce4feb7de7ac5724406c5e87cc98 100644 (file)
@@ -28,6 +28,7 @@
 #include "librbd/mirror/GetInfoRequest.h"
 #include "librbd/mirror/GetStatusRequest.h"
 #include "librbd/mirror/GetUuidRequest.h"
+#include "librbd/mirror/GroupEnableRequest.h"
 #include "librbd/mirror/GroupGetInfoRequest.h"
 #include "librbd/mirror/PromoteRequest.h"
 #include "librbd/mirror/Types.h"
@@ -2835,172 +2836,18 @@ int Mirror<I>::group_enable(IoCtx& group_ioctx, const char *group_name,
     return -EINVAL;
   }
 
-  cls::rbd::MirrorGroup mirror_group;
-  r = cls_client::mirror_group_get(&group_ioctx, group_id, &mirror_group);
-  if (r == 0) {
-    auto mode = static_cast<rbd_mirror_image_mode_t>(
-        mirror_group.mirror_image_mode);
-    if (mode != mirror_image_mode) {
-      lderr(cct) << "invalid group mirroring mode" << dendl;
-      r = -EINVAL;
-    } else if (mirror_group.state == cls::rbd::MIRROR_GROUP_STATE_ENABLING) {
-      ldout(cct, 10) << "mirroring on group is in-progress of enabling"
-                     << dendl;
-      r = -EINVAL;
-    } else if (mirror_group.state == cls::rbd::MIRROR_GROUP_STATE_ENABLED) {
-      ldout(cct, 10) << "mirroring on group is already enabled" << dendl;
-    } else if (mirror_group.state == cls::rbd::MIRROR_GROUP_STATE_DISABLING) {
-      lderr(cct) << "mirroring on group is currently disabling" << dendl;
-      r = -EINVAL;
-    } else {
-      lderr(cct) << "mirroring on group is in unexpected state: "
-                 << mirror_group.state << dendl;
-      r = -EINVAL;
-    }
-    return r;
-  } else if (r < 0) {
-    if (r == -EOPNOTSUPP) {
-      lderr(cct) << "mirroring on group is not supported by OSD" << dendl;
-      return r;
-    } else if (r != -ENOENT) {
-      lderr(cct) << "failed to retrieve mirror group metadata: "
-                 << cpp_strerror(r) << dendl;
-      return r;
-    }
-  }
-
-  uuid_d uuid_gen;
-  uuid_gen.generate_random();
-
-  mirror_group = {uuid_gen.to_string(),
-    static_cast<cls::rbd::MirrorImageMode>(mirror_image_mode),
-    cls::rbd::MIRROR_GROUP_STATE_ENABLING};
-
-  r = cls_client::mirror_group_set(&group_ioctx, group_id, mirror_group);
+  C_SaferCond cond;
+  auto req = mirror::GroupEnableRequest<>::create(
+    group_ioctx, group_id, internal_flags,
+    static_cast<cls::rbd::MirrorImageMode>(mirror_image_mode), &cond);
+  req->send();
+  r = cond.wait();
   if (r < 0) {
-    lderr(cct) << "failed to set mirroring group metadata: "
+    lderr(cct) << "failed to mirror enable group: "
                << cpp_strerror(r) << dendl;
     return r;
   }
-
-  std::string group_snap_id = librbd::util::generate_image_id(group_ioctx);
-  cls::rbd::GroupSnapshot group_snap{
-      group_snap_id,
-      cls::rbd::GroupSnapshotNamespaceMirror{},
-      prepare_primary_mirror_snap_name(cct, uuid_gen.to_string(),
-                                       group_snap_id),
-      cls::rbd::GROUP_SNAPSHOT_STATE_INCOMPLETE};
-
-  std::vector<uint64_t> quiesce_requests;
-  std::vector<I *> image_ctxs;
-  std::set<std::string> mirror_peer_uuids;
-  int ret_code = 0;
-  r = prepare_group_images(group_ioctx, group_id, mirror_group.state,
-                           &image_ctxs, &group_snap, quiesce_requests,
-                           cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY,
-                           &mirror_peer_uuids,
-                           internal_flags);
-  if (r != 0) {
-    ret_code = r;
-  }
-
-  auto image_count = image_ctxs.size();
-  std::string group_header_oid = librbd::util::group_header_name(group_id);
-  std::vector<uint64_t> snap_ids(image_ctxs.size(), CEPH_NOSNAP);
-  if (ret_code) {
-    goto cleanup;
-  }
-
-  for (size_t i = 0; i < image_ctxs.size(); i++) {
-    r = image_enable(image_ctxs[i], group_snap_id, mirror_image_mode, false,
-                     &snap_ids[i]);
-    group_snap.snaps[i].snap_id = snap_ids[i];
-    if (r < 0) {
-      lderr(cct) << "failed enabling image: "
-                 << image_ctxs[i]->name << ": " << cpp_strerror(r) << dendl;
-      if (ret_code == 0) {
-        ret_code = r;
-        break;
-      }
-    }
-  }
-
-  if (!ret_code) {
-    group_snap.state = cls::rbd::GROUP_SNAPSHOT_STATE_COMPLETE;
-    r = cls_client::group_snap_set(&group_ioctx, group_header_oid, group_snap);
-    if (r < 0) {
-      lderr(cct) << "failed to update group snapshot metadata: "
-                 << cpp_strerror(r) << dendl;
-      ret_code = r;
-      goto cleanup;
-    }
-
-    mirror_group.state = cls::rbd::MIRROR_GROUP_STATE_ENABLED;
-    r = cls_client::mirror_group_set(&group_ioctx, group_id, mirror_group);
-    if (r < 0) {
-      lderr(cct) << "failed to update mirroring group metadata: "
-                 << cpp_strerror(r) << dendl;
-      ret_code = r;
-    }
-  }
-
-cleanup:
-  if (ret_code) {
-    // undo
-    ldout(cct, 20) << "undoing group enable: " << ret_code << dendl;
-    remove_group_snap(group_ioctx, group_id, &group_snap, &image_ctxs);
-    // MirrorImage need to be removed, hence calling image disable is mandatory
-    r = 0;
-    for (size_t i = 0; i < image_ctxs.size(); i++) {
-      if (snap_ids[i] == CEPH_NOSNAP) {
-        continue;
-      }
-      r = image_disable(image_ctxs[i], false, true);
-      if (r < 0) {
-        lderr(cct) << "failed to disable mirroring on image: "
-                   << image_ctxs[i]->name << cpp_strerror(r) << dendl;
-        break;
-      }
-    }
-
-    if (r == 0) {
-      mirror_group.state = cls::rbd::MIRROR_GROUP_STATE_DISABLING;
-      r = cls_client::mirror_group_set(&group_ioctx, group_id, mirror_group);
-      if (r < 0) {
-        ret_code = r;
-        lderr(cct) << "failed to update mirroring group metadata: "
-                   << cpp_strerror(r) << dendl;
-      }
-    }
-
-    if (r == 0) {
-      r = cls_client::mirror_group_remove(&group_ioctx, group_id);
-      if (r < 0 && r != -ENOENT) {
-        ret_code = r;
-        lderr(cct) << "failed to remove mirroring group metadata: "
-                   << cpp_strerror(r) << dendl;
-      }
-    }
-  }
-
-  if (!quiesce_requests.empty()) {
-    util::notify_unquiesce(image_ctxs, quiesce_requests);
-  }
-
-  close_images(&image_ctxs);
-
-  if (!ret_code) {
-    r = MirroringWatcher<I>::notify_group_updated(
-          group_ioctx, cls::rbd::MIRROR_GROUP_STATE_ENABLED, group_id,
-          mirror_group.global_group_id, image_count);
-    if (r < 0) {
-      lderr(cct) << "failed to notify mirroring group=" << group_name
-                 << " updated: " << cpp_strerror(r) << dendl;
-      // not fatal
-    }
-  }
-
-  return ret_code;
+  return 0;
 }
 
 template <typename I>
@@ -3867,9 +3714,6 @@ template <typename I>
 int Mirror<I>::group_get_info(librados::IoCtx& io_ctx,
                               const std::string &group_name,
                               mirror_group_info_t *mirror_group_info) {
-  CephContext *cct((CephContext *)io_ctx.cct());
-  ldout(cct, 20) << "group_name=" << group_name << dendl;
-
   C_SaferCond ctx;
   group_get_info(io_ctx, group_name, mirror_group_info, &ctx);
   int r = ctx.wait();
diff --git a/src/librbd/mirror/GroupEnableRequest.cc b/src/librbd/mirror/GroupEnableRequest.cc
new file mode 100644 (file)
index 0000000..21b07a7
--- /dev/null
@@ -0,0 +1,937 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/mirror/GroupEnableRequest.h"
+#include "common/Cond.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "cls/rbd/cls_rbd_client.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/ImageState.h"
+#include "librbd/MirroringWatcher.h"
+#include "librbd/Utils.h"
+#include "librbd/mirror/ImageStateUpdateRequest.h"
+#include "librbd/mirror/ImageRemoveRequest.h"
+#include "librbd/mirror/snapshot/GroupImageCreatePrimaryRequest.h"
+#include "librbd/mirror/snapshot/RemoveGroupSnapshotRequest.h"
+
+#include <shared_mutex>
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::mirror::GroupEnableRequest: " \
+                           << this << " " << __func__ << ": "
+
+namespace librbd {
+namespace mirror {
+namespace {
+
+const uint32_t MAX_RETURN = 1024;
+
+} // anonymous namespace
+
+
+using util::create_context_callback;
+using util::create_rados_callback;
+
+
+template <typename I>
+GroupEnableRequest<I>::GroupEnableRequest(librados::IoCtx &io_ctx,
+                                          const std::string &group_id,
+                                          uint64_t group_snap_create_flags,
+                                          cls::rbd::MirrorImageMode mode,
+                                          Context *on_finish)
+  : m_group_ioctx(io_ctx), m_group_id(group_id),
+    m_group_snap_create_flags(group_snap_create_flags), m_mode(mode),
+    m_on_finish(on_finish), m_cct(reinterpret_cast<CephContext*>(io_ctx.cct())) {
+}
+
+template <typename I>
+void GroupEnableRequest<I>::send() {
+  get_mirror_group();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::get_mirror_group() {
+  ldout(m_cct, 10) << dendl;
+
+  librados::ObjectReadOperation op;
+  cls_client::mirror_group_get_start(&op, m_group_id);
+
+  using klass = GroupEnableRequest<I>;
+  librados::AioCompletion *comp =
+    create_rados_callback<klass, &klass::handle_get_mirror_group>(this);
+
+  m_out_bls.resize(1);
+  int r = m_group_ioctx.aio_operate(RBD_MIRRORING, comp, &op, &m_out_bls[0]);
+  ceph_assert(r == 0);
+  comp->release();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::handle_get_mirror_group(int r) {
+  ldout(m_cct, 10) << "r=" << r << dendl;
+
+  if (r == 0) {
+    auto iter = m_out_bls[0].cbegin();
+    r = cls_client::mirror_group_get_finish(&iter, &m_mirror_group);
+  }
+
+  m_out_bls[0].clear();
+
+  if (r == 0) {
+    if (m_mirror_group.mirror_image_mode != m_mode) {
+      lderr(m_cct) << "invalid group mirroring mode" << dendl;
+      r = -EINVAL;
+    } else if (m_mirror_group.state == cls::rbd::MIRROR_GROUP_STATE_ENABLING) {
+      lderr(m_cct) << "mirroring on group is currently enabling"
+                   << dendl;
+      r = -EINVAL;
+    } else if (m_mirror_group.state == cls::rbd::MIRROR_GROUP_STATE_ENABLED) {
+      ldout(m_cct, 10) << "mirroring on group is already enabled" << dendl;
+    } else if (
+        m_mirror_group.state == cls::rbd::MIRROR_GROUP_STATE_DISABLING) {
+      lderr(m_cct) << "mirroring on group is currently disabling" << dendl;
+      r = -EINVAL;
+    } else {
+      lderr(m_cct) << "mirroring on group is in unexpected state: "
+                   << m_mirror_group.state << dendl;
+      r = -EINVAL;
+    }
+    finish(r);
+    return;
+  } else if (r < 0) {
+    if (r == -EOPNOTSUPP) {
+      lderr(m_cct) << "mirroring on group is not supported by OSD" << dendl;
+      finish(r);
+      return;
+    } else if (r != -ENOENT) {
+      lderr(m_cct) << "failed to retrieve mirror group metadata: "
+                   << cpp_strerror(r) << dendl;
+      finish(r);
+      return;
+    }
+  }
+
+  uuid_d uuid_gen;
+  uuid_gen.generate_random();
+  m_mirror_group.global_group_id = uuid_gen.to_string();
+  m_mirror_group.mirror_image_mode = m_mode;
+
+  get_mirror_peer_list();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::get_mirror_peer_list() {
+  ldout(m_cct, 10) << dendl;
+
+  m_default_ns_ioctx.dup(m_group_ioctx);
+  m_default_ns_ioctx.set_namespace("");
+
+  librados::ObjectReadOperation op;
+  cls_client::mirror_peer_list_start(&op);
+
+  auto comp = create_rados_callback<
+      GroupEnableRequest<I>,
+      &GroupEnableRequest<I>::handle_get_mirror_peer_list>(this);
+
+  int r = m_default_ns_ioctx.aio_operate(RBD_MIRRORING, comp, &op,
+                                         &m_out_bls[0]);
+  ceph_assert(r == 0);
+  comp->release();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::handle_get_mirror_peer_list(int r) {
+  ldout(m_cct, 10) << "r=" << r << dendl;
+
+  std::vector<cls::rbd::MirrorPeer> peers;
+  if (r == 0) {
+    auto it = m_out_bls[0].cbegin();
+    r = cls_client::mirror_peer_list_finish(&it, &peers);
+  }
+
+  m_out_bls[0].clear();
+
+  if (r < 0) {
+    lderr(m_cct) << "failed to retrieve mirror peers: " << cpp_strerror(r)
+                 << dendl;
+    finish(r);
+    return;
+  }
+
+  for (auto &peer : peers) {
+    if (peer.mirror_peer_direction == cls::rbd::MIRROR_PEER_DIRECTION_RX) {
+      continue;
+    }
+    m_mirror_peer_uuids.insert(peer.uuid);
+  }
+
+  if (m_mirror_peer_uuids.empty()) {
+    lderr(m_cct) << "no mirror tx peers configured for the pool" << dendl;
+    finish(-EINVAL);
+    return;
+  }
+
+  list_group_images();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::list_group_images() {
+  ldout(m_cct, 10) << dendl;
+
+  librados::ObjectReadOperation op;
+  cls_client::group_image_list_start(&op, m_start_after, MAX_RETURN);
+
+  auto comp = create_rados_callback<
+    GroupEnableRequest<I>,
+    &GroupEnableRequest<I>::handle_list_group_images>(this);
+
+  int r = m_group_ioctx.aio_operate(
+    librbd::util::group_header_name(m_group_id), comp, &op, &m_out_bls[0]);
+  ceph_assert(r == 0);
+  comp->release();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::handle_list_group_images(int r) {
+  ldout(m_cct, 10) << "r=" << r << dendl;
+
+  std::vector<cls::rbd::GroupImageStatus> images;
+  if (r == 0) {
+    auto iter = m_out_bls[0].cbegin();
+    r = cls_client::group_image_list_finish(&iter, &images);
+  }
+
+  m_out_bls[0].clear();
+
+  if (r < 0) {
+    lderr(m_cct) << "error listing images in group: " << cpp_strerror(r)
+                 << dendl;
+    finish(r);
+    return;
+  }
+
+  auto image_count = images.size();
+  m_images.insert(m_images.end(), images.begin(), images.end());
+  if (image_count == MAX_RETURN) {
+    m_start_after = images.rbegin()->spec;
+    list_group_images();
+    return;
+  }
+
+  if (m_images.empty()) {
+    set_mirror_group_enabling();
+  } else {
+    check_mirror_images_disabled();
+  }
+}
+
+template <typename I>
+void GroupEnableRequest<I>::check_mirror_images_disabled() {
+  ldout(m_cct, 10) << dendl;
+
+  auto ctx = create_context_callback<
+    GroupEnableRequest<I>,
+    &GroupEnableRequest<I>::handle_check_mirror_images_disabled>(this);
+  auto gather_ctx = new C_Gather(m_cct, ctx);
+
+  m_mirror_images.resize(m_images.size());
+  m_out_bls.resize(m_images.size());
+  for (size_t i = 0; i < m_images.size(); i++) {
+    librados::ObjectReadOperation op;
+    cls_client::mirror_image_get_start(&op, m_images[i].spec.image_id);
+
+    auto on_mirror_image_get = new LambdaContext(
+      [this, i, new_sub_ctx=gather_ctx->new_sub()](int r) {
+        if (r == 0) {
+          auto iter = m_out_bls[i].cbegin();
+          r = cls_client::mirror_image_get_finish(&iter, &m_mirror_images[i]);
+        }
+
+        if (r == -ENOENT) {
+          // image is disabled for mirroring as required
+          r = 0;
+        } else if (r == 0) {
+          lderr(m_cct) << "image_id=" << m_images[i].spec.image_id
+                       << " is not disabled for mirroring" << dendl;
+          r = -EINVAL;
+        } else {
+          lderr(m_cct) << "failed to get mirror image info for image_id="
+                       << m_images[i].spec.image_id << dendl;
+        }
+
+        new_sub_ctx->complete(r);
+      });
+
+    auto comp = create_rados_callback(on_mirror_image_get);
+
+    int r = m_group_ioctx.aio_operate(RBD_MIRRORING, comp, &op,
+                                      &m_out_bls[i]);
+    ceph_assert(r == 0);
+    comp->release();
+  }
+
+  gather_ctx->activate();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::handle_check_mirror_images_disabled(int r) {
+  ldout(m_cct, 10) << "r=" << r <<  dendl;
+
+  m_out_bls.clear();
+
+  if (r < 0) {
+    lderr(m_cct) << "images not disabled for mirroring: "
+                 << cpp_strerror(r) << dendl;
+    finish(r);
+    return;
+  }
+
+  open_images();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::open_images() {
+  ldout(m_cct, 10) << dendl;
+
+  auto ctx = create_context_callback<
+    GroupEnableRequest<I>, &GroupEnableRequest<I>::handle_open_images>(this);
+  auto gather_ctx = new C_Gather(m_cct, ctx);
+  int r = 0;
+  std::vector<librados::IoCtx> ioctxs;
+
+  for (const auto& image: m_images) {
+    librados::IoCtx image_io_ctx;
+    r = librbd::util::create_ioctx(m_group_ioctx, "image",
+                                   image.spec.pool_id, {},
+                                   &image_io_ctx);
+    if (r < 0) {
+      finish(r);
+      return;
+    }
+
+    ioctxs.push_back(std::move(image_io_ctx));
+  }
+
+  for (size_t i = 0; i < m_images.size(); i++) {
+    m_image_ctxs.push_back(
+      new ImageCtx("", m_images[i].spec.image_id.c_str(), nullptr, ioctxs[i],
+                   false));
+
+    auto on_open = new LambdaContext(
+      [this, i, new_sub_ctx=gather_ctx->new_sub()](int r) {
+        // If asynchronous ImageState::open() fails, ImageState together with
+        // ImageCtx is destroyed. Simply NULL-out the respective image_ctxs[i]
+        // pointer to record that it's no longer valid.
+        if (r < 0) {
+          m_image_ctxs[i] = nullptr;
+        }
+        new_sub_ctx->complete(r);
+      });
+
+    // Open parent as well to check if the image is a clone
+    m_image_ctxs[i]->state->open(0, on_open);
+  }
+
+  gather_ctx->activate();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::handle_open_images(int r) {
+  ldout(m_cct, 10) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(m_cct) << "failed to open group images: " << cpp_strerror(r)
+                 << dendl;
+    m_ret_val = r;
+
+    close_images();
+    return;
+  }
+
+  validate_images();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::validate_images() {
+  ldout(m_cct, 10) << dendl;
+
+  for (auto &image_ctx : m_image_ctxs) {
+    std::shared_lock image_locker{image_ctx->image_lock};
+    if (image_ctx->parent) {
+      lderr(m_cct) << "cannot enable mirroring: cloned images are not "
+                   << "supported" << dendl;
+      m_ret_val = -EINVAL;
+
+      close_images();
+      return;
+    }
+  }
+
+  // FIXME: Once the support for mirroring cloned images is added, need to
+  // check that the parents are enabled for mirroring
+
+  // FIXME: For now images must belong to the same pool as the group
+  auto group_pool_id = m_group_ioctx.get_id();
+  for (auto &image_ctx : m_image_ctxs) {
+    std::shared_lock image_locker{image_ctx->image_lock};
+    if (image_ctx->md_ctx.get_id() != group_pool_id) {
+      lderr(m_cct) << "cannot enable mirroring: image in a different pool"
+                   << dendl;
+      m_ret_val = -EINVAL;
+
+      close_images();
+      return;
+    }
+  }
+
+  set_mirror_group_enabling();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::set_mirror_group_enabling() {
+  ldout(m_cct, 10) << dendl;
+
+  m_mirror_group.state = cls::rbd::MIRROR_GROUP_STATE_ENABLING;
+
+  librados::ObjectWriteOperation op;
+  cls_client::mirror_group_set(&op, m_group_id, m_mirror_group);
+  auto aio_comp = create_rados_callback<
+    GroupEnableRequest<I>,
+    &GroupEnableRequest<I>::handle_set_mirror_group_enabling>(this);
+  int r = m_group_ioctx.aio_operate(RBD_MIRRORING, aio_comp, &op);
+  ceph_assert(r == 0);
+  aio_comp->release();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::handle_set_mirror_group_enabling(int r) {
+  ldout(m_cct, 10) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(m_cct) << "failed to set mirror group as enabling: "
+                 << cpp_strerror(r) << dendl;
+    m_ret_val = r;
+
+    close_images();
+    return;
+  }
+
+  create_primary_group_snapshot();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::create_primary_group_snapshot() {
+  ldout(m_cct, 10) << dendl;
+
+  m_group_snap.id = librbd::util::generate_image_id(m_group_ioctx);
+
+  auto snap_name = ".mirror.primary." + m_mirror_group.global_group_id
+                + "." + m_group_snap.id;
+  m_group_snap.name = snap_name;
+
+  cls::rbd::MirrorSnapshotState state = cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY;
+
+  // Create incomplete group snap
+  m_group_snap.snapshot_namespace = cls::rbd::GroupSnapshotNamespaceMirror{
+    state, m_mirror_peer_uuids, {}, {}};
+
+  for (auto image_ctx: m_image_ctxs) {
+    m_group_snap.snaps.emplace_back(image_ctx->md_ctx.get_id(), image_ctx->id,
+                                    CEPH_NOSNAP);
+  }
+
+  librados::ObjectWriteOperation op;
+  cls_client::group_snap_set(&op, m_group_snap);
+
+  auto aio_comp = create_rados_callback<
+    GroupEnableRequest<I>,
+    &GroupEnableRequest<I>::handle_create_primary_group_snapshot>(this);
+  int r = m_group_ioctx.aio_operate(librbd::util::group_header_name(m_group_id),
+                                    aio_comp, &op);
+  ceph_assert(r == 0);
+  aio_comp->release();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::handle_create_primary_group_snapshot(int r) {
+  ldout(m_cct, 10) << "r=" << r << dendl;
+
+  m_need_to_cleanup_group_snapshot = true;
+
+  if (r < 0) {
+    lderr(m_cct) << "failed to create group snapshot: "
+                 << cpp_strerror(r) << dendl;
+    m_ret_val = r;
+
+    disable_mirror_group();
+    return;
+  }
+
+  if (m_image_ctxs.empty()) {
+    update_primary_group_snapshot();
+  } else {
+    create_primary_image_snapshots();
+  }
+}
+
+template <typename I>
+void GroupEnableRequest<I>::create_primary_image_snapshots() {
+  ldout(m_cct, 10) << dendl;
+
+  auto num_images = m_image_ctxs.size();
+  m_global_image_ids.resize(num_images);
+
+  uuid_d uuid_gen;
+  for (size_t i = 0; i < num_images; i++) {
+    uuid_gen.generate_random();
+    m_global_image_ids[i] = uuid_gen.to_string();
+    m_mirror_images[i].global_image_id = m_global_image_ids[i];
+  }
+
+  auto ctx = librbd::util::create_context_callback<
+    GroupEnableRequest<I>,
+    &GroupEnableRequest<I>::handle_create_primary_image_snapshots>(this);
+
+  m_snap_ids.resize(num_images, CEPH_NOSNAP);
+
+  // quiescing and requesting exclusive locks of images
+  auto req = snapshot::GroupImageCreatePrimaryRequest<I>::create(
+    m_cct, m_image_ctxs, m_global_image_ids, m_group_snap_create_flags,
+    snapshot::CREATE_PRIMARY_FLAG_IGNORE_EMPTY_PEERS, m_group_snap.id,
+    &m_snap_ids, ctx);
+  req->send();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::handle_create_primary_image_snapshots(int r) {
+  ldout(m_cct, 10) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(m_cct) << "failed to create primary mirror image snapshots: "
+                 << cpp_strerror(r) << dendl;
+    m_ret_val = r;
+
+    for (size_t i = 0; i < m_image_ctxs.size(); i++) {
+      m_group_snap.snaps[i].snap_id = m_snap_ids[i];
+    }
+
+    disable_mirror_group();
+    return;
+  }
+
+  update_primary_group_snapshot();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::update_primary_group_snapshot() {
+  ldout(m_cct, 10) << dendl;
+
+  for (size_t i = 0; i < m_image_ctxs.size(); i++) {
+    m_group_snap.snaps[i].snap_id = m_snap_ids[i];
+  }
+
+  m_group_snap.state = cls::rbd::GROUP_SNAPSHOT_STATE_COMPLETE;
+  librados::ObjectWriteOperation op;
+  cls_client::group_snap_set(&op, m_group_snap);
+
+  auto aio_comp = create_rados_callback<
+    GroupEnableRequest<I>,
+    &GroupEnableRequest<I>::handle_update_primary_group_snapshot>(this);
+  int r = m_group_ioctx.aio_operate(librbd::util::group_header_name(m_group_id),
+                                    aio_comp, &op);
+  ceph_assert(r == 0);
+  aio_comp->release();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::handle_update_primary_group_snapshot(int r) {
+  ldout(m_cct, 10) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(m_cct) << "failed to create group snapshot: "
+                 << cpp_strerror(r) << dendl;
+    m_ret_val = r;
+
+    disable_mirror_group();
+    return;
+  }
+
+  if (m_image_ctxs.empty()) {
+    set_mirror_group_enabled();
+  } else {
+    set_mirror_images_enabled();
+  }
+}
+
+template <typename I>
+void GroupEnableRequest<I>::set_mirror_images_enabled() {
+  ldout(m_cct, 10) << dendl;
+
+  auto ctx = create_context_callback<
+    GroupEnableRequest<I>,
+    &GroupEnableRequest<I>::handle_set_mirror_images_enabled>(this);
+
+  auto gather_ctx = new C_Gather(m_cct, ctx);
+
+  for (size_t i = 0; i < m_image_ctxs.size(); i++) {
+    auto ictx = m_image_ctxs[i];
+
+    m_mirror_images[i].type = cls::rbd::MIRROR_IMAGE_TYPE_GROUP;
+    m_mirror_images[i].mode = m_mode;
+    m_mirror_images[i].global_image_id = m_global_image_ids[i];
+
+    auto req = ImageStateUpdateRequest<I>::create(
+      ictx->md_ctx, ictx->id, cls::rbd::MIRROR_IMAGE_STATE_ENABLED,
+      m_mirror_images[i], gather_ctx->new_sub());
+
+    req->send();
+  }
+
+  gather_ctx->activate();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::handle_set_mirror_images_enabled(int r) {
+  ldout(m_cct, 10) << "r=" << r << dendl;
+
+  m_need_to_cleanup_mirror_images = true;
+
+  if (r < 0) {
+    lderr(m_cct) << "failed to enabled mirror images: " << cpp_strerror(r)
+                 << dendl;
+    m_ret_val = r;
+
+    disable_mirror_group();
+    return;
+  }
+
+  set_mirror_group_enabled();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::set_mirror_group_enabled() {
+  ldout(m_cct, 10) << dendl;
+
+  m_mirror_group.state = cls::rbd::MIRROR_GROUP_STATE_ENABLED;
+
+  librados::ObjectWriteOperation op;
+  cls_client::mirror_group_set(&op, m_group_id, m_mirror_group);
+  auto aio_comp = create_rados_callback<
+    GroupEnableRequest<I>,
+    &GroupEnableRequest<I>::handle_set_mirror_group_enabled>(this);
+  int r = m_group_ioctx.aio_operate(RBD_MIRRORING, aio_comp, &op);
+  ceph_assert(r == 0);
+  aio_comp->release();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::handle_set_mirror_group_enabled(int r) {
+  ldout(m_cct, 10) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(m_cct) << "failed to set mirror group as enabled: "
+                 << cpp_strerror(r) << dendl;
+    m_ret_val = r;
+
+    disable_mirror_group();
+    return;
+  }
+
+  notify_mirroring_watcher();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::notify_mirroring_watcher() {
+  ldout(m_cct, 10) << dendl;
+
+  auto ctx = util::create_context_callback<
+    GroupEnableRequest<I>,
+    &GroupEnableRequest<I>::handle_notify_mirroring_watcher>(this);
+
+  MirroringWatcher<I>::notify_group_updated(
+          m_group_ioctx, cls::rbd::MIRROR_GROUP_STATE_ENABLED, m_group_id,
+          m_mirror_group.global_group_id, m_image_ctxs.size(), ctx);
+}
+
+template <typename I>
+void GroupEnableRequest<I>::handle_notify_mirroring_watcher(int r) {
+  ldout(m_cct, 10) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(m_cct) << "failed to notify mirror group update: " << cpp_strerror(r)
+                 << dendl;
+    m_ret_val = r;
+  }
+
+  close_images();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::close_images() {
+  ldout(m_cct, 10) << dendl;
+
+  auto ctx = create_context_callback<
+    GroupEnableRequest<I>, &GroupEnableRequest<I>::handle_close_images>(this);
+
+  auto gather_ctx = new C_Gather(m_cct, ctx);
+
+  for (auto ictx: m_image_ctxs) {
+    if (ictx != nullptr) {
+      ictx->state->close(gather_ctx->new_sub());
+    }
+  }
+
+  gather_ctx->activate();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::handle_close_images(int r) {
+  ldout(m_cct, 10) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(m_cct) << "failed to close images: " << cpp_strerror(r) << dendl;
+    if (m_ret_val == 0) {
+      m_ret_val = r;
+    }
+  }
+
+  finish(m_ret_val);
+}
+
+template <typename I>
+void GroupEnableRequest<I>::disable_mirror_group() {
+  ldout(m_cct, 10) << dendl;
+
+  librados::ObjectWriteOperation op;
+  m_mirror_group.state = cls::rbd::MIRROR_GROUP_STATE_DISABLING;
+
+  cls_client::mirror_group_set(&op, m_group_id, m_mirror_group);
+  auto aio_comp = create_rados_callback<
+    GroupEnableRequest<I>,
+    &GroupEnableRequest<I>::handle_disable_mirror_group>(this);
+  int r = m_group_ioctx.aio_operate(RBD_MIRRORING, aio_comp, &op);
+  ceph_assert(r == 0);
+  aio_comp->release();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::handle_disable_mirror_group(int r) {
+  ldout(m_cct, 10) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(m_cct) << "failed to disable mirror group: " << cpp_strerror(r)
+                 << dendl;
+    close_images();
+    return;
+  }
+
+  if (m_need_to_cleanup_mirror_images) {
+    get_mirror_images_for_cleanup();
+  } else if (m_need_to_cleanup_group_snapshot) {
+    remove_primary_group_snapshot();
+  } else {
+    remove_mirror_group();
+  }
+}
+
+template <typename I>
+void GroupEnableRequest<I>::get_mirror_images_for_cleanup() {
+  ldout(m_cct, 10) << dendl;
+
+  m_mirror_images.clear();
+  m_mirror_images.resize(m_images.size());
+  m_out_bls.resize(m_images.size());
+
+  auto ctx = create_context_callback<
+    GroupEnableRequest<I>,
+    &GroupEnableRequest<I>::handle_get_mirror_images_for_cleanup>(this);
+  auto gather_ctx = new C_Gather(m_cct, ctx);
+
+  for (size_t i = 0; i < m_images.size(); i++) {
+    librados::ObjectReadOperation op;
+    cls_client::mirror_image_get_start(&op, m_images[i].spec.image_id);
+
+    auto on_mirror_image_get = new LambdaContext(
+      [this, i, new_sub_ctx=gather_ctx->new_sub()](int r) {
+        if (r == 0) {
+          auto iter = m_out_bls[i].cbegin();
+          r = cls_client::mirror_image_get_finish(&iter, &m_mirror_images[i]);
+        }
+
+        if (r == -ENOENT) {
+          r = 0;
+          m_mirror_images[i].state = cls::rbd::MIRROR_IMAGE_STATE_DISABLED;
+        } else if (r < 0) {
+          lderr(m_cct) << "failed to get mirror image info for image_id="
+                       << m_images[i].spec.image_id << dendl;
+        }
+
+        new_sub_ctx->complete(r);
+      });
+
+    auto comp = create_rados_callback(on_mirror_image_get);
+
+    int r = m_group_ioctx.aio_operate(RBD_MIRRORING, comp, &op, &m_out_bls[i]);
+    ceph_assert(r == 0);
+    comp->release();
+  }
+
+  gather_ctx->activate();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::handle_get_mirror_images_for_cleanup(int r) {
+  ldout(m_cct, 10) << "r=" << r <<  dendl;
+
+  m_out_bls.clear();
+
+  if (r < 0) {
+    ldout(m_cct, 10) << "failed to get mirror image info for cleanup" << dendl;
+    close_images();
+    return;
+  }
+
+  disable_mirror_images();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::disable_mirror_images() {
+  ldout(m_cct, 10) << dendl;
+
+  auto ctx = create_context_callback<
+    GroupEnableRequest<I>,
+    &GroupEnableRequest<I>::handle_disable_mirror_images>(this);
+
+  auto gather_ctx = new C_Gather(m_cct, ctx);
+  for (size_t i = 0; i < m_images.size(); i++) {
+    if (m_mirror_images[i].state != cls::rbd::MIRROR_IMAGE_STATE_DISABLED) {
+      auto req = ImageStateUpdateRequest<I>::create(
+        m_image_ctxs[i]->md_ctx, m_image_ctxs[i]->id,
+        cls::rbd::MIRROR_IMAGE_STATE_DISABLING, m_mirror_images[i],
+        gather_ctx->new_sub());
+      req->send();
+    }
+  }
+  gather_ctx->activate();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::handle_disable_mirror_images(int r) {
+  ldout(m_cct, 10) << "r=" << dendl;
+
+  if (r < 0) {
+    lderr(m_cct) << "failed to disable mirror images: " << cpp_strerror(r)
+                 << dendl;
+    close_images();
+    return;
+  }
+
+  remove_primary_group_snapshot();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::remove_primary_group_snapshot() {
+  ldout(m_cct, 10) << dendl;
+
+  auto ctx = create_context_callback<
+    GroupEnableRequest<I>,
+    &GroupEnableRequest<I>::handle_remove_primary_group_snapshot>(this);
+
+  auto req = snapshot::RemoveGroupSnapshotRequest<I>::create(m_group_ioctx,
+     m_group_id, &m_group_snap, &m_image_ctxs, ctx);
+
+  req->send();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::handle_remove_primary_group_snapshot(int r) {
+  ldout(m_cct, 10) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(m_cct) << "failed to remove mirror group snapshot: " << cpp_strerror(r)
+                 << dendl;
+    close_images();
+    return;
+  }
+
+  if (m_need_to_cleanup_mirror_images) {
+    remove_mirror_images();
+  } else {
+    remove_mirror_group();
+  }
+}
+
+template <typename I>
+void GroupEnableRequest<I>::remove_mirror_images() {
+  ldout(m_cct, 10) << dendl;
+
+  auto ctx = create_context_callback<
+    GroupEnableRequest<I>,
+    &GroupEnableRequest<I>::handle_remove_mirror_images>(this);
+
+  auto gather_ctx = new C_Gather(m_cct, ctx);
+  for (size_t i = 0; i < m_images.size(); i++) {
+    auto req = ImageRemoveRequest<I>::create(
+      m_image_ctxs[i]->md_ctx, m_global_image_ids[i], m_image_ctxs[i]->id,
+      gather_ctx->new_sub());
+    req->send();
+  }
+  gather_ctx->activate();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::handle_remove_mirror_images(int r) {
+  ldout(m_cct, 10) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(m_cct) << "failed to remove mirror images: " << cpp_strerror(r)
+                 << dendl;
+    close_images();
+    return;
+  }
+
+  remove_mirror_group();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::remove_mirror_group() {
+  ldout(m_cct, 10) << dendl;
+
+  librados::ObjectWriteOperation op;
+  cls_client::mirror_group_remove(&op, m_group_id);
+
+  auto comp = create_rados_callback<
+    GroupEnableRequest<I>,
+    &GroupEnableRequest<I>::handle_remove_mirror_group>(this);
+  int r = m_group_ioctx.aio_operate(RBD_MIRRORING, comp, &op);
+  ceph_assert(r == 0);
+  comp->release();
+
+}
+
+template <typename I>
+void GroupEnableRequest<I>::handle_remove_mirror_group(int r) {
+  ldout(m_cct, 10) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(m_cct) << "failed to remove mirror group: " << cpp_strerror(r)
+                 << dendl;
+  }
+
+  close_images();
+}
+
+template <typename I>
+void GroupEnableRequest<I>::finish(int r) {
+  ldout(m_cct, 10) << "r=" << r << dendl;
+
+  m_on_finish->complete(r);
+  delete this;
+}
+
+} // namespace mirror
+} // namespace librbd
+
+template class librbd::mirror::GroupEnableRequest<librbd::ImageCtx>;
diff --git a/src/librbd/mirror/GroupEnableRequest.h b/src/librbd/mirror/GroupEnableRequest.h
new file mode 100644 (file)
index 0000000..5e9fa93
--- /dev/null
@@ -0,0 +1,191 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_MIRROR_GROUP_ENABLE_REQUEST_H
+#define CEPH_LIBRBD_MIRROR_GROUP_ENABLE_REQUEST_H
+
+#include "include/buffer_fwd.h"
+#include "include/rados/librados_fwd.hpp"
+#include "include/rbd/librbd.hpp"
+#include "cls/rbd/cls_rbd_types.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/mirror/Types.h"
+#include <map>
+#include <string>
+
+class Context;
+
+namespace librbd {
+
+namespace asio { struct ContextWQ; }
+
+namespace mirror {
+
+template <typename ImageCtxT = ImageCtx>
+class GroupEnableRequest {
+public:
+  static GroupEnableRequest *create(librados::IoCtx &group_io_ctx,
+                                    const std::string &group_id,
+                                    uint64_t group_snap_create_flags,
+                                    cls::rbd::MirrorImageMode mode,
+                                    Context *on_finish) {
+    return new GroupEnableRequest(group_io_ctx, group_id,
+                                  group_snap_create_flags, mode, on_finish);
+  }
+
+  void send();
+
+private:
+  /**
+   * @verbatim
+   *
+   * <start>
+   *    |
+   *    v                         (on error)
+   * GET_MIRROR_GROUP  * * * * * * * * * * *
+   *    |                                  *
+   *    v                                  *
+   * GET_MIRROR_PEERS  * * * * * * * * * * *
+   *    |                                  *
+   *    v                                  *
+   * LIST_GROUP_IMAGES * * * * * * * * * * *
+   *    |                                  *
+   *    v  (skip if not needed)            *
+   * CHECK_MIRROR_IMAGES_DISABLED  * * * * *
+   *    |                                  *
+   *    v  (skip if not needed)            *
+   * OPEN_IMAGES   * * * * * * * * * * * * *
+   *    |                                  *
+   *    v  (skip if not needed)            *
+   * VALIDATE_IMAGES   * * * * * * * * * * *
+   *    |                                  *
+   *    v                                  *
+   * SET_MIRROR_GROUP_ENABLING * * * * * * *
+   *    |                                  *
+   *    v (incomplete)                     *
+   * CREATE_PRIMARY_GROUP_SNAP * * * * * * *
+   *    |                                  *
+   *    v (skip if not needed)             *
+   * CREATE_PRIMARY_IMAGE_SNAPS            *
+   *    |                                  *
+   *    v (complete)                       *
+   * UPDATE_PRIMARY_GROUP_SNAP * * * * * * *
+   *    |                                  *
+   *    v (skip if not needed)             *
+   * SET_MIRROR_IMAGES_ENABLED * * * * * * *
+   *    |                                  *
+   *    v                                  *
+   * SET_MIRROR_GROUP_ENABLED  * * * * * * *
+   *    |                                  *
+   *    v                            (if required)
+   * NOTIFY_MIRRORING_WATCHER           cleanup
+   *    |                                  *
+   *    v (skip if not needed)             *
+   * CLOSE_IMAGE < * * * * * * * * * * * * *
+   *    |
+   *    v
+   * <finish>
+   *
+   * @endverbatim
+   */
+
+  GroupEnableRequest(librados::IoCtx &io_ctx, const std::string &group_id,
+                     uint64_t group_snap_create_flags,
+                     cls::rbd::MirrorImageMode mode, Context *on_finish);
+
+  librados::IoCtx &m_group_ioctx;
+  const std::string m_group_id;
+  uint64_t m_group_snap_create_flags;
+  const cls::rbd::MirrorImageMode m_mode;
+  Context *m_on_finish;
+
+  CephContext *m_cct = nullptr;
+  std::vector<bufferlist> m_out_bls;
+  cls::rbd::MirrorGroup m_mirror_group;
+
+  int m_ret_val = 0;
+  librados::IoCtx m_default_ns_ioctx;
+
+  std::vector<ImageCtxT *> m_image_ctxs;
+  std::vector<cls::rbd::MirrorImage> m_mirror_images;
+
+  std::set<std::string> m_mirror_peer_uuids;
+  cls::rbd::GroupImageSpec m_start_after;
+  std::vector<cls::rbd::GroupImageStatus> m_images;
+
+  cls::rbd::GroupSnapshot m_group_snap;
+  std::vector<uint64_t> m_snap_ids;
+  std::vector<std::string> m_global_image_ids;
+
+  bool m_need_to_cleanup_group_snapshot = false;
+  bool m_need_to_cleanup_mirror_images = false;
+
+  void get_mirror_group();
+  void handle_get_mirror_group(int r);
+
+  void get_mirror_peer_list();
+  void handle_get_mirror_peer_list(int r);
+
+  void list_group_images();
+  void handle_list_group_images(int r);
+
+  void check_mirror_images_disabled();
+  void handle_check_mirror_images_disabled(int r);
+
+  void open_images();
+  void handle_open_images(int r);
+
+  void validate_images();
+
+  void create_primary_group_snapshot();
+  void handle_create_primary_group_snapshot(int r);
+
+  void set_mirror_group_enabling();
+  void handle_set_mirror_group_enabling(int r);
+
+  void create_primary_image_snapshots();
+  void handle_create_primary_image_snapshots(int r);
+
+  void update_primary_group_snapshot();
+  void handle_update_primary_group_snapshot(int r);
+
+  void set_mirror_images_enabled();
+  void handle_set_mirror_images_enabled(int r);
+
+  void set_mirror_group_enabled();
+  void handle_set_mirror_group_enabled(int r);
+
+  void notify_mirroring_watcher();
+  void handle_notify_mirroring_watcher(int r);
+
+  void close_images();
+  void handle_close_images(int r);
+
+  // Cleanup
+  void disable_mirror_group();
+  void handle_disable_mirror_group(int r);
+
+  void get_mirror_images_for_cleanup();
+  void handle_get_mirror_images_for_cleanup(int r);
+
+  void disable_mirror_images();
+  void handle_disable_mirror_images(int r);
+
+  void remove_primary_group_snapshot();
+  void handle_remove_primary_group_snapshot(int r);
+
+  void remove_mirror_images();
+  void handle_remove_mirror_images(int r);
+
+  void remove_mirror_group();
+  void handle_remove_mirror_group(int r);
+
+  void finish(int r);
+};
+
+} // namespace mirror
+} // namespace librbd
+
+extern template class librbd::mirror::GroupEnableRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_MIRROR_GROUP_ENABLE_REQUEST_H
diff --git a/src/librbd/mirror/snapshot/GroupImageCreatePrimaryRequest.cc b/src/librbd/mirror/snapshot/GroupImageCreatePrimaryRequest.cc
new file mode 100644 (file)
index 0000000..ab25a74
--- /dev/null
@@ -0,0 +1,431 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/mirror/snapshot/GroupImageCreatePrimaryRequest.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "cls/rbd/cls_rbd_client.h"
+#include "librbd/ExclusiveLock.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/ImageState.h"
+#include "librbd/ImageWatcher.h"
+#include "librbd/Operations.h"
+#include "librbd/Utils.h"
+#include "librbd/mirror/snapshot/Utils.h"
+
+#include <shared_mutex> // for std::shared_lock
+
+#define dout_subsys ceph_subsys_rbd
+
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::mirror::snapshot::GroupImageCreatePrimaryRequest: " \
+                           << this << " " << __func__ << ": "
+
+namespace librbd {
+namespace mirror {
+namespace snapshot {
+
+using librbd::util::create_context_callback;
+using librbd::util::create_rados_callback;
+using librbd::util::snap_create_flags_api_to_internal;
+using librbd::util::get_default_snap_create_flags;
+
+template <typename I>
+GroupImageCreatePrimaryRequest<I>::GroupImageCreatePrimaryRequest(
+    CephContext* cct, const std::vector<I *> &image_ctxs,
+    const std::vector<std::string> &global_image_ids,
+    uint64_t group_snap_create_flags, uint32_t flags,
+    const std::string &group_snap_id, std::vector<uint64_t> *snap_ids,
+    Context *on_finish)
+  : m_cct(cct), m_image_ctxs(image_ctxs), m_global_image_ids(global_image_ids),
+    m_group_snap_create_flags(group_snap_create_flags), m_flags(flags),
+    m_group_snap_id(group_snap_id), m_snap_ids(snap_ids),
+    m_on_finish(on_finish) {
+  ceph_assert(!m_image_ctxs.empty());
+  ceph_assert(!m_group_snap_id.empty());
+  ceph_assert(m_global_image_ids.size() == m_image_ctxs.size());
+  ceph_assert((*m_snap_ids).size() == m_image_ctxs.size());
+}
+
+template <typename I>
+void GroupImageCreatePrimaryRequest<I>::send() {
+  ldout(m_cct, 15) << dendl;
+
+  size_t i = 0;
+  for (; i < m_image_ctxs.size(); i++) {
+    if (!util::can_create_primary_snapshot(
+          m_image_ctxs[i],
+          ((m_flags & CREATE_PRIMARY_FLAG_DEMOTED) != 0),
+          ((m_flags & CREATE_PRIMARY_FLAG_FORCE) != 0), nullptr, nullptr)) {
+      lderr(m_cct) << "cannot create primary snapshot for "
+                   << m_image_ctxs[i]->id << dendl;
+      finish(-EINVAL);
+      return;
+    }
+  }
+
+  m_snap_names.resize(m_image_ctxs.size());
+
+  for (i = 0; i < m_image_ctxs.size(); i++) {
+    std::stringstream ss;
+    ss << ".mirror.primary." << m_global_image_ids[i] << "."
+       << m_image_ctxs[i]->group_spec.pool_id << "_"
+       << m_image_ctxs[i]->group_spec.group_id << "_"
+       << m_group_snap_id;
+    m_snap_names[i] = ss.str();
+  }
+
+  get_mirror_peers();
+}
+
+template <typename I>
+void GroupImageCreatePrimaryRequest<I>::get_mirror_peers() {
+  ldout(m_cct, 15) << dendl;
+
+  auto ctx = create_context_callback<
+    GroupImageCreatePrimaryRequest<I>,
+    &GroupImageCreatePrimaryRequest<I>::handle_get_mirror_peers>(this);
+  auto gather_ctx = new C_Gather(m_cct, ctx);
+
+  m_default_ns_ctxs.resize(m_image_ctxs.size());
+  m_mirror_peers_uuids.resize(m_image_ctxs.size());
+  m_out_bls.resize(m_image_ctxs.size());
+
+  for (size_t i = 0; i < m_image_ctxs.size(); i++) {
+    m_default_ns_ctxs[i].dup(m_image_ctxs[i]->md_ctx);
+    m_default_ns_ctxs[i].set_namespace("");
+
+    librados::ObjectReadOperation op;
+    cls_client::mirror_peer_list_start(&op);
+
+    auto on_mirror_peer_list = new LambdaContext(
+      [this, i, new_sub_ctx = gather_ctx->new_sub()](int r) {
+        std::vector<cls::rbd::MirrorPeer> peers;
+        if (r == 0) {
+          auto iter = m_out_bls[i].cbegin();
+          r = cls_client::mirror_peer_list_finish(&iter, &peers);
+        }
+
+        if (r < 0) {
+          lderr(m_image_ctxs[i]->cct) << "failed to retrieve mirror peers: "
+                                      << cpp_strerror(r) << dendl;
+        } else {
+          for (auto &peer : peers) {
+            if (peer.mirror_peer_direction ==
+                cls::rbd::MIRROR_PEER_DIRECTION_RX) {
+              continue;
+            }
+            m_mirror_peers_uuids[i].insert(peer.uuid);
+          }
+
+          if (m_mirror_peers_uuids[i].empty() &&
+              ((m_flags & CREATE_PRIMARY_FLAG_IGNORE_EMPTY_PEERS) == 0)) {
+            lderr(m_image_ctxs[i]->cct) << "no mirror tx peers configured "
+                                        << "for the pool" << dendl;
+            r = -EINVAL;
+          }
+        }
+
+        new_sub_ctx->complete(r);
+      });
+
+    auto comp = create_rados_callback(on_mirror_peer_list);
+
+    int r = m_default_ns_ctxs[i].aio_operate(RBD_MIRRORING, comp, &op,
+                                             &m_out_bls[i]);
+    ceph_assert(r == 0);
+    comp->release();
+  }
+
+  gather_ctx->activate();
+}
+
+template <typename I>
+void GroupImageCreatePrimaryRequest<I>::handle_get_mirror_peers(int r) {
+  ldout(m_cct, 15) << "r=" << r << dendl;
+
+  m_default_ns_ctxs.clear();
+  m_out_bls.clear();
+
+  if (r < 0) {
+    lderr(m_cct) << "failed to retrieve mirror peers for images: "
+                 << cpp_strerror(r) << dendl;
+    finish(r);
+    return;
+  }
+
+  if ((m_group_snap_create_flags &
+       SNAP_CREATE_FLAG_SKIP_NOTIFY_QUIESCE) != 0) {
+    acquire_exclusive_locks();
+    return;
+  }
+
+  notify_quiesce();
+}
+
+template <typename I>
+void GroupImageCreatePrimaryRequest<I>::notify_quiesce() {
+  ldout(m_cct, 15) << dendl;
+
+  auto ctx = create_context_callback<
+    GroupImageCreatePrimaryRequest<I>,
+    &GroupImageCreatePrimaryRequest<I>::handle_notify_quiesce>(this);
+  auto gather_ctx = new C_Gather(m_cct, ctx);
+
+  m_quiesce_requests.resize(m_image_ctxs.size());
+
+  for (size_t i = 0; i < m_image_ctxs.size(); ++i) {
+    auto ictx = m_image_ctxs[i];
+    ictx->image_watcher->notify_quiesce(&(m_quiesce_requests)[i], m_prog_ctx,
+                                        gather_ctx->new_sub());
+  }
+
+  gather_ctx->activate();
+}
+
+template <typename I>
+void GroupImageCreatePrimaryRequest<I>::handle_notify_quiesce(int r) {
+  ldout(m_cct, 15) << "r=" << r << dendl;
+
+  if (r < 0 &&
+      (m_group_snap_create_flags & SNAP_CREATE_FLAG_IGNORE_NOTIFY_QUIESCE_ERROR) == 0) {
+    m_ret_code = r;
+    notify_unquiesce();
+    return;
+  }
+
+  acquire_exclusive_locks();
+}
+
+template <typename I>
+void GroupImageCreatePrimaryRequest<I>::acquire_exclusive_locks() {
+  ldout(m_cct, 15) << dendl;
+
+  m_release_locks = true;
+
+  auto ctx = librbd::util::create_context_callback<
+    GroupImageCreatePrimaryRequest<I>,
+    &GroupImageCreatePrimaryRequest<I>::handle_acquire_exclusive_locks>(this);
+  auto gather_ctx = new C_Gather(m_cct, ctx);
+
+  for (auto ictx: m_image_ctxs) {
+    std::shared_lock owner_lock{ictx->owner_lock};
+    if (ictx->exclusive_lock != nullptr) {
+      ictx->exclusive_lock->block_requests(-EBUSY);
+      ictx->exclusive_lock->acquire_lock(gather_ctx->new_sub());
+    }
+  }
+
+  gather_ctx->activate();
+}
+
+template <typename I>
+void GroupImageCreatePrimaryRequest<I>::handle_acquire_exclusive_locks(int r) {
+  ldout(m_cct, 15) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(m_cct) << "failed to acquire image exclusive locks: "
+                 << cpp_strerror(r) << dendl;
+    m_ret_code = r;
+    // release locks in case some of the lock acquisitions succeeded
+    release_exclusive_locks();
+    return;
+  }
+
+  create_snapshots();
+}
+
+template <typename I>
+void GroupImageCreatePrimaryRequest<I>::create_snapshots() {
+  ldout(m_cct, 15) << dendl;
+
+  auto ctx = create_context_callback<
+    GroupImageCreatePrimaryRequest<I>,
+    &GroupImageCreatePrimaryRequest<I>::handle_create_snapshots>(this);
+
+  auto gather_ctx = new C_Gather(m_cct, ctx);
+
+  for (size_t i = 0; i < m_image_ctxs.size(); i++) {
+    cls::rbd::MirrorSnapshotNamespace ns{
+      ((m_flags & CREATE_PRIMARY_FLAG_DEMOTED) != 0 ?
+        cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY_DEMOTED :
+        cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY),
+      m_mirror_peers_uuids[i], "", CEPH_NOSNAP};
+    ns.group_spec = m_image_ctxs[i]->group_spec;
+    ns.group_snap_id = m_group_snap_id;
+
+    ldout(m_cct, 15) << "creating snapshot: image_id=" << m_image_ctxs[i]->id
+                     << ", snap_name=" << m_snap_names[i]
+                     << ", snap_ns=" << ns << dendl;
+
+    uint64_t snap_create_flags;
+    int r = snap_create_flags_api_to_internal(
+      m_cct, get_default_snap_create_flags(m_image_ctxs[i]),
+      &snap_create_flags);
+    ceph_assert(r == 0);
+
+    m_image_ctxs[i]->operations->snap_create(ns, m_snap_names[i],
+                                             snap_create_flags, m_prog_ctx,
+                                             gather_ctx->new_sub());
+  }
+
+  gather_ctx->activate();
+}
+
+template <typename I>
+void GroupImageCreatePrimaryRequest<I>::handle_create_snapshots(int r) {
+  ldout(m_cct, 15) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(m_cct) << "failed to create image snapshots: " << cpp_strerror(r)
+                 << dendl;
+    m_ret_code = r;
+    // Refresh the images anyway so we can return any available snap_ids
+  }
+
+  refresh_images();
+}
+
+template <typename I>
+void GroupImageCreatePrimaryRequest<I>::refresh_images() {
+  // Refresh is required to retrieve the snapshot id (if snapshot
+  // created via remote RPC) and complete flag (regardless)
+  ldout(m_cct, 15) << dendl;
+
+  auto ctx = create_context_callback<
+    GroupImageCreatePrimaryRequest<I>,
+    &GroupImageCreatePrimaryRequest<I>::handle_refresh_images>(this);
+
+  auto gather_ctx = new C_Gather(m_cct, ctx);
+  for (size_t i = 0; i < m_image_ctxs.size(); i++) {
+    auto on_refresh = new LambdaContext(
+      [this, i, new_sub_ctx = gather_ctx->new_sub()](int r) {
+        auto cct = m_image_ctxs[i]->cct;
+        if (r < 0) {
+          lderr(cct) << "failed to refresh image: " << cpp_strerror(r)
+                     << dendl;
+        } else {
+          ldout(cct, 15) << "snap_name=" << m_snap_names[i] << dendl;
+
+          std::shared_lock image_locker{m_image_ctxs[i]->image_lock};
+
+          auto snap_id = m_image_ctxs[i]->get_snap_id(
+            cls::rbd::MirrorSnapshotNamespace{}, m_snap_names[i]);
+          (*m_snap_ids)[i] = snap_id;
+          ldout(cct, 15) << "image_id: " <<  m_image_ctxs[i]->id
+                         << ", snap_id=" << snap_id << dendl;
+        }
+
+        new_sub_ctx->complete(r);
+      });
+
+    m_image_ctxs[i]->state->refresh(on_refresh);
+  }
+  gather_ctx->activate();
+}
+
+template <typename I>
+void GroupImageCreatePrimaryRequest<I>::handle_refresh_images(int r) {
+  ldout(m_cct, 15) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(m_cct) << "failed to refresh images: " << cpp_strerror(r) << dendl;
+    if (m_ret_code == 0) {
+      m_ret_code = r;
+    }
+  }
+
+  if (m_release_locks) {
+    release_exclusive_locks();
+  } else {
+    notify_unquiesce();
+  }
+}
+
+template <typename I>
+void GroupImageCreatePrimaryRequest<I>::release_exclusive_locks() {
+  ldout(m_cct, 15) << dendl;
+
+  auto ctx = librbd::util::create_context_callback<
+    GroupImageCreatePrimaryRequest<I>,
+    &GroupImageCreatePrimaryRequest<I>::handle_release_exclusive_locks>(this);
+  auto gather_ctx = new C_Gather(m_cct, ctx);
+
+  for (auto ictx: m_image_ctxs) {
+    std::shared_lock owner_lock{ictx->owner_lock};
+    if (ictx->exclusive_lock != nullptr) {
+      ictx->exclusive_lock->release_lock(gather_ctx->new_sub());
+    }
+  }
+
+  gather_ctx->activate();
+}
+
+template <typename I>
+void GroupImageCreatePrimaryRequest<I>::handle_release_exclusive_locks(int r) {
+  ldout(m_cct, 15) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(m_cct) << "failed to release exclusive locks for images: "
+                 << cpp_strerror(r) << dendl;
+    if (m_ret_code == 0) {
+      m_ret_code = r;
+    }
+  }
+
+  notify_unquiesce();
+}
+
+template <typename I>
+void GroupImageCreatePrimaryRequest<I>::notify_unquiesce() {
+  if (m_quiesce_requests.empty()) {
+    finish(m_ret_code);
+    return;
+  }
+
+  ldout(m_cct, 15) << dendl;
+
+  ceph_assert(m_quiesce_requests.size() == m_image_ctxs.size());
+
+  auto ctx = librbd::util::create_context_callback<
+    GroupImageCreatePrimaryRequest<I>,
+    &GroupImageCreatePrimaryRequest<I>::handle_notify_unquiesce>(this);
+  auto gather_ctx = new C_Gather(m_cct, ctx);
+
+  for (size_t i = 0; i < m_image_ctxs.size(); ++i) {
+    auto ictx = m_image_ctxs[i];
+    ictx->image_watcher->notify_unquiesce(m_quiesce_requests[i],
+                                          gather_ctx->new_sub());
+  }
+
+  gather_ctx->activate();
+}
+
+template <typename I>
+void GroupImageCreatePrimaryRequest<I>::handle_notify_unquiesce(int r) {
+  ldout(m_cct, 15) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(m_cct) << "failed to unquiesce requests: "
+                 << cpp_strerror(r) << dendl;
+    if (m_ret_code == 0) {
+      m_ret_code = r;
+    }
+  }
+
+  finish(m_ret_code);
+}
+
+template <typename I>
+void GroupImageCreatePrimaryRequest<I>::finish(int r) {
+  ldout(m_cct, 15) << "r=" << r << dendl;
+
+  m_on_finish->complete(r);
+  delete this;
+}
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
+template class librbd::mirror::snapshot::GroupImageCreatePrimaryRequest<librbd::ImageCtx>;
diff --git a/src/librbd/mirror/snapshot/GroupImageCreatePrimaryRequest.h b/src/librbd/mirror/snapshot/GroupImageCreatePrimaryRequest.h
new file mode 100644 (file)
index 0000000..cb26d13
--- /dev/null
@@ -0,0 +1,131 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_MIRROR_SNAPSHOT_GROUP_IMAGE_CREATE_PRIMARY_REQUEST_H
+#define CEPH_LIBRBD_MIRROR_SNAPSHOT_GROUP_IMAGE_CREATE_PRIMARY_REQUEST_H
+
+#include "include/buffer.h"
+#include "include/rados/librados.hpp"
+#include "cls/rbd/cls_rbd_types.h"
+#include "librbd/internal.h"
+#include "librbd/mirror/snapshot/Types.h"
+
+#include <string>
+#include <set>
+
+struct Context;
+
+namespace librbd {
+
+struct ImageCtx;
+
+namespace mirror {
+namespace snapshot {
+
+template <typename ImageCtxT = librbd::ImageCtx>
+class GroupImageCreatePrimaryRequest {
+public:
+  static GroupImageCreatePrimaryRequest *create(
+      CephContext* cct, const std::vector<ImageCtxT *> &image_ctxs,
+      const std::vector<std::string> &global_image_ids,
+      uint64_t group_snap_create_flags, uint32_t flags,
+      const std::string &group_snap_id, std::vector<uint64_t> *snap_ids,
+      Context *on_finish) {
+    return new GroupImageCreatePrimaryRequest(
+      cct, image_ctxs, global_image_ids, group_snap_create_flags, flags,
+      group_snap_id, snap_ids, on_finish);
+  }
+
+  GroupImageCreatePrimaryRequest(
+    CephContext* cct, const std::vector<ImageCtxT *> &image_ctxs,
+    const std::vector<std::string> &global_image_ids,
+    uint64_t group_snap_create_flags, uint32_t flags,
+    const std::string &group_snap_id, std::vector<uint64_t> *snap_ids,
+    Context *on_finish);
+
+  void send();
+
+private:
+  /**
+   * @verbatim
+   *
+   * <start>
+   *    |
+   *    v                    (on-error)
+   * GET_MIRROR_PEERS . . . . . . > . . . . . . . .
+   *    |                                         .
+   *    v                    (on-error)           .
+   * NOTIFY_QUIESCE  . . . . . . > . . . .        .
+   *    |                                .        .
+   *    v                    (on-error)  .        .
+   * ACQUIRE_EXCLUSIVE_LOCKS . . .       .        .
+   *    |                        .       .        .
+   *    v                        .       .        .
+   * CREATE_SNAPSHOTS            .       .        .
+   *    |                        v       .        .
+   *    v                        .       v        .
+   * REFRESH_IMAGES              .       .        .
+   *    |                        .       .        .
+   *    v                        .       .        .
+   * RELEASE_EXCLUSIVE_LOCKS . . .       .        .
+   *    |                                .        .
+   *    v                                .        .
+   * NOTIFY_UNQUIESCE  . . . . < . . . . .        .
+   *    |                                         .
+   *    v                                         .
+   * <finish> . . . . . . . . . . < . . . . . . . .
+   *
+   * @endverbatim
+   */
+
+  CephContext *m_cct;
+  const std::vector<ImageCtxT *> &m_image_ctxs;
+  const std::vector<std::string> &m_global_image_ids;
+  uint64_t m_group_snap_create_flags;
+  const uint32_t m_flags;
+  const std::string m_group_snap_id;
+  std::vector<uint64_t> *m_snap_ids;
+  Context *m_on_finish;
+
+  std::vector<std::set<std::string>> m_mirror_peers_uuids;
+  std::vector<std::string> m_snap_names;
+  std::vector<librados::IoCtx> m_default_ns_ctxs;
+  std::vector<bufferlist> m_out_bls;
+
+  std::vector<uint64_t> m_quiesce_requests;
+  bool m_release_locks = false;
+  int m_ret_code = 0;
+
+  NoOpProgressContext m_prog_ctx;
+
+  void get_mirror_peers();
+  void handle_get_mirror_peers(int r);
+
+  void create_snapshots();
+  void handle_create_snapshots(int r);
+
+  void refresh_images();
+  void handle_refresh_images(int r);
+
+  void notify_quiesce();
+  void handle_notify_quiesce(int r);
+
+  void notify_unquiesce();
+  void handle_notify_unquiesce(int r);
+
+  void acquire_exclusive_locks();
+  void handle_acquire_exclusive_locks(int r);
+
+  void release_exclusive_locks();
+  void handle_release_exclusive_locks(int r);
+
+  void finish(int r);
+};
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
+extern template class librbd::mirror::snapshot::GroupImageCreatePrimaryRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_MIRROR_SNAPSHOT_GROUP_IMAGE_CREATE_PRIMARY_REQUEST_H