]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
librbd: initial conversion of snapshot ops to async versions
authorJason Dillaman <dillaman@redhat.com>
Thu, 30 Jul 2015 20:25:54 +0000 (16:25 -0400)
committerJason Dillaman <dillaman@redhat.com>
Thu, 19 Nov 2015 01:34:29 +0000 (20:34 -0500)
Object map updates are not currently integrated until those methods
are converted to async state machines.

Signed-off-by: Jason Dillaman <dillaman@redhat.com>
16 files changed:
src/librbd/ImageWatcher.cc
src/librbd/Makefile.am
src/librbd/internal.cc
src/librbd/internal.h
src/librbd/operation/SnapshotCreateRequest.cc [new file with mode: 0644]
src/librbd/operation/SnapshotCreateRequest.h [new file with mode: 0644]
src/librbd/operation/SnapshotProtectRequest.cc [new file with mode: 0644]
src/librbd/operation/SnapshotProtectRequest.h [new file with mode: 0644]
src/librbd/operation/SnapshotRemoveRequest.cc [new file with mode: 0644]
src/librbd/operation/SnapshotRemoveRequest.h [new file with mode: 0644]
src/librbd/operation/SnapshotRenameRequest.cc [new file with mode: 0644]
src/librbd/operation/SnapshotRenameRequest.h [new file with mode: 0644]
src/librbd/operation/SnapshotRollbackRequest.cc [new file with mode: 0644]
src/librbd/operation/SnapshotRollbackRequest.h [new file with mode: 0644]
src/librbd/operation/SnapshotUnprotectRequest.cc [new file with mode: 0644]
src/librbd/operation/SnapshotUnprotectRequest.h [new file with mode: 0644]

index 014ce9407217ee5c7cb97df5efc69a02612e9e09..a21b6c204d5b33e4620e469664a90f94210ed6ae 100644 (file)
@@ -1036,8 +1036,12 @@ void ImageWatcher::handle_payload(const SnapCreatePayload &payload,
   if (m_lock_owner_state == LOCK_OWNER_STATE_LOCKED) {
     ldout(m_image_ctx.cct, 10) << this << " remote snap_create request: "
                               << payload.snap_name << dendl;
-    int r = librbd::snap_create_helper(&m_image_ctx, NULL,
+    C_SaferCond cond_ctx;
+    int r = librbd::snap_create_helper(&m_image_ctx, &cond_ctx,
                                        payload.snap_name.c_str());
+    if (r == 0) {
+      r = cond_ctx.wait();
+    }
 
     ::encode(ResponseMessage(r), *out);
   }
@@ -1050,9 +1054,13 @@ void ImageWatcher::handle_payload(const SnapRenamePayload &payload,
     ldout(m_image_ctx.cct, 10) << this << " remote snap_rename request: "
                               << payload.src_snap_id << " to " 
                               << payload.dst_snap_name << dendl;
-    int r = librbd::snap_rename_helper(&m_image_ctx, NULL,
+    C_SaferCond cond_ctx;
+    int r = librbd::snap_rename_helper(&m_image_ctx, &cond_ctx,
                                        payload.src_snap_id,
                                        payload.dst_snap_name.c_str());
+    if (r == 0) {
+      r = cond_ctx.wait();
+    }
 
     ::encode(ResponseMessage(r), *out);
   }
@@ -1063,8 +1071,12 @@ void ImageWatcher::handle_payload(const SnapRemovePayload &payload,
   if (m_lock_owner_state == LOCK_OWNER_STATE_LOCKED) {
     ldout(m_image_ctx.cct, 10) << this << " remote snap_remove request: "
                               << payload.snap_name << dendl;
-    int r = librbd::snap_remove_helper(&m_image_ctx, NULL,
+    C_SaferCond cond_ctx;
+    int r = librbd::snap_remove_helper(&m_image_ctx, &cond_ctx,
                                        payload.snap_name.c_str());
+    if (r == 0) {
+      r = cond_ctx.wait();
+    }
 
     ::encode(ResponseMessage(r), *out);
   }
index 90776dac70be68691697ea37e496ac90a1e1751d..246e94c11a4ce1d9791d0d9f27cfc356a125e5ac 100644 (file)
@@ -29,6 +29,12 @@ librbd_internal_la_SOURCES = \
        librbd/operation/RebuildObjectMapRequest.cc \
        librbd/operation/Request.cc \
        librbd/operation/ResizeRequest.cc \
+       librbd/operation/SnapshotCreateRequest.cc \
+       librbd/operation/SnapshotProtectRequest.cc \
+       librbd/operation/SnapshotRemoveRequest.cc \
+       librbd/operation/SnapshotRenameRequest.cc \
+       librbd/operation/SnapshotRollbackRequest.cc \
+       librbd/operation/SnapshotUnprotectRequest.cc \
        librbd/operation/TrimRequest.cc
 noinst_LTLIBRARIES += librbd_internal.la
 
@@ -81,6 +87,12 @@ noinst_HEADERS += \
        librbd/operation/RebuildObjectMapRequest.h \
        librbd/operation/Request.h \
        librbd/operation/ResizeRequest.h \
+       librbd/operation/SnapshotCreateRequest.h \
+       librbd/operation/SnapshotProtectRequest.h \
+       librbd/operation/SnapshotRemoveRequest.h \
+       librbd/operation/SnapshotRenameRequest.h \
+       librbd/operation/SnapshotRollbackRequest.h \
+       librbd/operation/SnapshotUnprotectRequest.h \
        librbd/operation/TrimRequest.h
 
 endif # WITH_RBD
index 2cd85bdc36fe2c472e700d1bbf251190b394e09b..e296068d0011ea99e7e63f7ec7d71736f9d9bacc 100644 (file)
@@ -5,6 +5,7 @@
 #include <errno.h>
 #include <limits.h>
 
+#include "include/types.h"
 #include "common/ceph_context.h"
 #include "common/dout.h"
 #include "common/errno.h"
 #include "librbd/operation/FlattenRequest.h"
 #include "librbd/operation/RebuildObjectMapRequest.h"
 #include "librbd/operation/ResizeRequest.h"
+#include "librbd/operation/SnapshotCreateRequest.h"
+#include "librbd/operation/SnapshotProtectRequest.h"
+#include "librbd/operation/SnapshotRemoveRequest.h"
+#include "librbd/operation/SnapshotRenameRequest.h"
+#include "librbd/operation/SnapshotRollbackRequest.h"
+#include "librbd/operation/SnapshotUnprotectRequest.h"
 #include "librbd/operation/TrimRequest.h"
 #include "include/util.h"
 
@@ -562,54 +569,6 @@ int invoke_async_request(ImageCtx *ictx, const std::string& request_type,
     return (*opts_)->empty();
   }
 
-  void rollback_object(ImageCtx *ictx, uint64_t snap_id, const string& oid,
-                      SimpleThrottle& throttle)
-  {
-    Context *req_comp = new C_SimpleThrottle(&throttle);
-    librados::AioCompletion *rados_completion =
-      librados::Rados::aio_create_completion(req_comp, NULL, rados_ctx_cb);
-    librados::ObjectWriteOperation op;
-    op.selfmanaged_snap_rollback(snap_id);
-    ictx->data_ctx.aio_operate(oid, rados_completion, &op);
-    ldout(ictx->cct, 10) << "scheduling selfmanaged_snap_rollback on "
-                         << oid << " to " << snap_id << dendl;
-    rados_completion->release();
-  }
-
-  int rollback_image(ImageCtx *ictx, uint64_t snap_id,
-                    ProgressContext& prog_ctx)
-  {
-    uint64_t bsize = ictx->get_object_size();
-    uint64_t numseg;
-    {
-      RWLock::RLocker l(ictx->snap_lock);
-      numseg = Striper::get_num_objects(ictx->layout, ictx->get_current_size());
-    }
-
-    int r;
-    CephContext *cct = ictx->cct;
-    SimpleThrottle throttle(ictx->concurrent_management_ops, true);
-
-    for (uint64_t i = 0; i < numseg; i++) {
-      string oid = ictx->get_object_name(i);
-      rollback_object(ictx, snap_id, ictx->get_object_name(i), throttle);
-      prog_ctx.update_progress(i * bsize, numseg * bsize);
-    }
-
-    r = throttle.wait_for_ret();
-    if (r < 0) {
-      ldout(cct, 10) << "failed to rollback at least one object: "
-                    << cpp_strerror(r) << dendl;
-      return r;
-    }
-
-    {
-      RWLock::WLocker l(ictx->snap_lock);
-      ictx->object_map.rollback(snap_id);
-    }
-    return 0;
-  }
-
   int list(IoCtx& io_ctx, vector<string>& names)
   {
     CephContext *cct = (CephContext *)io_ctx.cct();
@@ -760,8 +719,7 @@ int invoke_async_request(ImageCtx *ictx, const std::string& request_type,
     return 0;
   }
 
-  int snap_create_helper(ImageCtx* ictx, Context* ctx,
-                         const char* snap_name) {
+  int snap_create_helper(ImageCtx* ictx, Context* ctx, const char* snap_name) {
     assert(ictx->owner_lock.is_locked());
     assert(!ictx->image_watcher->is_lock_supported() ||
           ictx->image_watcher->is_lock_owner());
@@ -774,107 +732,9 @@ int invoke_async_request(ImageCtx *ictx, const std::string& request_type,
       return r;
     }
 
-    RWLock::WLocker md_locker(ictx->md_lock);
-    r = ictx->flush();
-    if (r < 0) {
-      return r;
-    }
-
-    do {
-      r = add_snap(ictx, snap_name);
-    } while (r == -ESTALE);
-
-    if (r < 0) {
-      return r;
-    }
-
-    if (ctx != NULL) {
-      ctx->complete(0);
-    }
-    return 0;
-  }
-
-  int snap_rename(ImageCtx *ictx, const char *srcname, const char *dstname)
-  {
-    ldout(ictx->cct, 20) << "snap_rename " << ictx << " from " << srcname << " to " << dstname << dendl;
-
-    snapid_t snap_id;
-    if (ictx->read_only) {
-      return -EROFS;
-    }
-
-    int r = ictx_check(ictx);
-    if (r < 0)
-      return r;
-
-    {
-      RWLock::RLocker l(ictx->snap_lock);
-      snap_id = ictx->get_snap_id(srcname);
-      if (snap_id == CEPH_NOSNAP) {
-        return -ENOENT;
-      }
-      if (ictx->get_snap_id(dstname) != CEPH_NOSNAP) {
-        return -EEXIST;
-      }
-    }
-
-    r = invoke_async_request(ictx, "snap_rename", true,
-                             boost::bind(&snap_rename_helper, ictx, _1,
-                                         snap_id, dstname),
-                             boost::bind(&ImageWatcher::notify_snap_rename,
-                                         ictx->image_watcher, snap_id,
-                                        dstname));
-    if (r < 0 && r != -EEXIST) {
-      return r;
-    }
-
-    ictx->perfcounter->inc(l_librbd_snap_rename);
-    notify_change(ictx->md_ctx, ictx->header_oid, ictx);
-    return 0;
-  }
-
-  int snap_rename_helper(ImageCtx* ictx, Context* ctx,
-                         const uint64_t src_snap_id,
-                         const char* dst_name) {
-    assert(ictx->owner_lock.is_locked());
-    assert(!ictx->image_watcher->is_lock_supported() ||
-          ictx->image_watcher->is_lock_owner());
-
-    ldout(ictx->cct, 20) << __func__ << " " << ictx << " from " 
-                        << src_snap_id << " to " << dst_name << dendl;
-
-    int r = ictx_check(ictx, ictx->owner_lock);
-    if (r < 0) {
-      return r;
-    }
-    r = rename_snap(ictx, src_snap_id, dst_name);
-
-    if (r < 0) {
-      return r;
-    }
-
-    if (ctx != NULL) {
-      ctx->complete(0);
-    }
-    return 0;
-  }
-
-  static int scan_for_parents(ImageCtx *ictx, parent_spec &pspec,
-                             snapid_t oursnap_id)
-  {
-    if (pspec.pool_id != -1) {
-      map<snap_t, SnapInfo>::iterator it;
-      for (it = ictx->snap_info.begin();
-          it != ictx->snap_info.end(); ++it) {
-       // skip our snap id (if checking base image, CEPH_NOSNAP won't match)
-       if (it->first == oursnap_id)
-         continue;
-       if (it->second.parent.spec == pspec)
-         break;
-      }
-      if (it == ictx->snap_info.end())
-       return -ENOENT;
-    }
+    operation::SnapshotCreateRequest *req =
+      new operation::SnapshotCreateRequest(*ictx, ctx, snap_name);
+    req->send();
     return 0;
   }
 
@@ -909,7 +769,13 @@ int invoke_async_request(ImageCtx *ictx, const std::string& request_type,
       }
     } else {
       RWLock::RLocker owner_lock(ictx->owner_lock);
-      r = snap_remove_helper(ictx, NULL, snap_name);
+      C_SaferCond cond_ctx;
+      r = snap_remove_helper(ictx, &cond_ctx, snap_name);
+      if (r < 0) {
+        return r;
+      }
+
+      r = cond_ctx.wait();
       if (r < 0) {
         return r;
       }
@@ -939,59 +805,101 @@ int invoke_async_request(ImageCtx *ictx, const std::string& request_type,
       return r;
     }
 
-    RWLock::RLocker md_locker(ictx->md_lock);
-    snap_t snap_id;
+    uint64_t snap_id;
     {
-      RWLock::WLocker snap_locker(ictx->snap_lock);
+      RWLock::RLocker snap_locker(ictx->snap_lock);
       snap_id = ictx->get_snap_id(snap_name);
       if (snap_id == CEPH_NOSNAP) {
+        lderr(ictx->cct) << "No such snapshot found." << dendl;
         return -ENOENT;
       }
 
-      r = ictx->object_map.snapshot_remove(snap_id);
+      bool is_protected;
+      r = ictx->is_snap_protected(snap_id, &is_protected);
       if (r < 0) {
-        lderr(ictx->cct) << "snap_remove: failed to remove snapshot object map"
-                        << dendl;
-        return r;
+        ctx->complete(r);
+        return;
+      } else if (is_protected) {
+        lderr(ictx->cct) << "snapshot is protected" << dendl;
+        ctx->complete(-EBUSY);
+        return;
       }
+    }
 
-      {
-        parent_spec our_pspec;
-        RWLock::RLocker parent_locker(ictx->parent_lock);
-        r = ictx->get_parent_spec(snap_id, &our_pspec);
-        if (r < 0) {
-         lderr(ictx->cct) << "snap_remove: can't get parent spec" << dendl;
-         return r;
-        }
+    operation::SnapshotRemoveRequest *req =
+      new operation::SnapshotRemoveRequest(*ictx, ctx, snap_name, snap_id);
+    req->send();
+    return 0;
+  }
 
-        if (ictx->parent_md.spec != our_pspec &&
-           (scan_for_parents(ictx, our_pspec, snap_id) == -ENOENT)) {
-          r = cls_client::remove_child(&ictx->md_ctx, RBD_CHILDREN,
-                                      our_pspec, ictx->id);
-         if (r < 0 && r != -ENOENT) {
-            lderr(ictx->cct) << "snap_remove: failed to deregister from parent "
-                             << "image" << dendl;
-           return r;
-          }
-        }
+  int snap_rename(ImageCtx *ictx, const char *srcname, const char *dstname)
+  {
+    ldout(ictx->cct, 20) << "snap_rename " << ictx << " from " << srcname << " to " << dstname << dendl;
+
+    snapid_t snap_id;
+    if (ictx->read_only) {
+      return -EROFS;
+    }
+
+    int r = ictx_check(ictx);
+    if (r < 0)
+      return r;
+
+    {
+      RWLock::RLocker l(ictx->snap_lock);
+      snap_id = ictx->get_snap_id(srcname);
+      if (snap_id == CEPH_NOSNAP) {
+        return -ENOENT;
       }
+      if (ictx->get_snap_id(dstname) != CEPH_NOSNAP) {
+        return -EEXIST;
+      }
+    }
+
+    if (ictx->test_features(RBD_FEATURE_JOURNALING)) {
+      r = invoke_async_request(ictx, "snap_rename", true,
+                               boost::bind(&snap_rename_helper, ictx, _1,
+                                           snap_id, dstname),
+                               boost::bind(&ImageWatcher::notify_snap_rename,
+                                           ictx->image_watcher, snap_id,
+                                           dstname));
+      if (r < 0 && r != -EEXIST) {
+        return r;
+      }
+    } else {
+      RWLock::RLocker owner_lock(ictx->owner_lock);
+      C_SaferCond cond_ctx;
+      snap_rename_helper(ictx, &cond_ctx, snap_id, dstname);
 
-      r = rm_snap(ictx, snap_name, snap_id);
+      r = cond_ctx.wait();
       if (r < 0) {
         return r;
       }
     }
 
-    r = ictx->data_ctx.selfmanaged_snap_remove(snap_id);
+    ictx->perfcounter->inc(l_librbd_snap_rename);
+    notify_change(ictx->md_ctx, ictx->header_oid, ictx);
+    return 0;
+  }
+
+  int snap_rename_helper(ImageCtx* ictx, Context* ctx,
+                         const uint64_t src_snap_id, const char* dst_name) {
+    assert(ictx->owner_lock.is_locked());
+    if ((ictx->features & RBD_FEATURE_JOURNALING) != 0) {
+      assert(!ictx->image_watcher->is_lock_supported() ||
+             ictx->image_watcher->is_lock_owner());
+    }
+    ldout(ictx->cct, 20) << __func__ << " " << ictx << " from "
+                         << src_snap_id << " to " << dst_name << dendl;
+
+    int r = ictx_check(ictx, ictx->owner_lock);
     if (r < 0) {
-      lderr(ictx->cct) << "snap_remove: failed to remove RADOS snapshot"
-                       << dendl;
       return r;
     }
 
-    if (ctx != NULL) {
-      ctx->complete(0);
-    }
+    operation::SnapshotRenameRequest *req =
+      new operation::SnapshotRenameRequest(*ictx, ctx, src_snap_id, dst_name);
+    req->send();
     return 0;
   }
 
@@ -1007,31 +915,18 @@ int invoke_async_request(ImageCtx *ictx, const std::string& request_type,
     if (r < 0)
       return r;
 
-    RWLock::RLocker l(ictx->md_lock);
-    RWLock::RLocker l2(ictx->snap_lock);
-    if ((ictx->features & RBD_FEATURE_LAYERING) == 0) {
-      lderr(ictx->cct) << "snap_protect: image must support layering"
-                      << dendl;
-      return -ENOSYS;
-    }
-    snap_t snap_id = ictx->get_snap_id(snap_name);
-    if (snap_id == CEPH_NOSNAP)
-      return -ENOENT;
+    // TODO integrate w/ watch/notify
+    RWLock::RLocker owner_locker(ictx->owner_lock);
 
-    bool is_protected;
-    r = ictx->is_snap_protected(snap_id, &is_protected);
-    if (r < 0)
+    C_SaferCond cond_ctx;
+    operation::SnapshotProtectRequest *request =
+      new operation::SnapshotProtectRequest(*ictx, &cond_ctx, snap_name);
+    request->send();
+    r = cond_ctx.wait();
+    if (r < 0) {
       return r;
+    }
 
-    if (is_protected)
-      return -EBUSY;
-
-    r = cls_client::set_protection_status(&ictx->md_ctx,
-                                         ictx->header_oid,
-                                         snap_id,
-                                         RBD_PROTECTION_STATUS_PROTECTED);
-    if (r < 0)
-      return r;
     notify_change(ictx->md_ctx, ictx->header_oid, ictx);
     return 0;
   }
@@ -1048,115 +943,20 @@ int invoke_async_request(ImageCtx *ictx, const std::string& request_type,
     if (r < 0)
       return r;
 
-    RWLock::RLocker l(ictx->md_lock);
-    RWLock::RLocker l2(ictx->snap_lock);
-    if ((ictx->features & RBD_FEATURE_LAYERING) == 0) {
-      lderr(ictx->cct) << "snap_unprotect: image must support layering"
-                      << dendl;
-      return -ENOSYS;
-    }
-    snap_t snap_id = ictx->get_snap_id(snap_name);
-    if (snap_id == CEPH_NOSNAP)
-      return -ENOENT;
+    // TODO integrate w/ watch/notify
+    RWLock::RLocker owner_locker(ictx->owner_lock);
 
-    bool is_unprotected;
-    r = ictx->is_snap_unprotected(snap_id, &is_unprotected);
-    if (r < 0)
+    C_SaferCond cond_ctx;
+    operation::SnapshotUnprotectRequest *request
+      = new operation::SnapshotUnprotectRequest(*ictx, &cond_ctx, snap_name);
+    request->send();
+    r = cond_ctx.wait();
+    if (r < 0) {
       return r;
-
-    if (is_unprotected) {
-      lderr(ictx->cct) << "snap_unprotect: snapshot is already unprotected"
-                      << dendl;
-      return -EINVAL;
     }
 
-    r = cls_client::set_protection_status(&ictx->md_ctx,
-                                         ictx->header_oid,
-                                         snap_id,
-                                         RBD_PROTECTION_STATUS_UNPROTECTING);
-    if (r < 0)
-      return r;
-    notify_change(ictx->md_ctx, ictx->header_oid, ictx);
-
-    parent_spec pspec(ictx->md_ctx.get_id(), ictx->id, snap_id);
-    // search all pools for children depending on this snapshot
-    Rados rados(ictx->md_ctx);
-    rados.wait_for_latest_osdmap();
-
-    // protect against pools being renamed/deleted
-    std::list<std::pair<int64_t, std::string> > pools;
-    rados.pool_list2(pools);
-
-    for (std::list<std::pair<int64_t, std::string> >::const_iterator it =
-         pools.begin(); it != pools.end(); ++it) {
-      int64_t base_tier;
-      r = rados.pool_get_base_tier(it->first, &base_tier);
-      if (r == -ENOENT) {
-        ldout(ictx->cct, 1) << "pool " << it->second << " no longer exists"
-                            << dendl;
-        continue;
-      } else if (r < 0) {
-        lderr(ictx->cct) << "snap_unprotect: error retrieving base tier for "
-                         << "pool " << it->second << dendl;
-        goto reprotect_and_return_err;
-      }
-      if (it->first != base_tier) {
-       // pool is a cache; skip it
-       continue;
-      }
-
-      IoCtx pool_ioctx;
-      r = rados.ioctx_create2(it->first, pool_ioctx);
-      if (r == -ENOENT) {
-        ldout(ictx->cct, 1) << "pool " << it->second << " no longer exists"
-                            << dendl;
-        continue;
-      } else if (r < 0) {
-        lderr(ictx->cct) << "snap_unprotect: can't create ioctx for pool "
-                        << it->second << dendl;
-        goto reprotect_and_return_err;
-      }
-
-      std::set<std::string> children;
-      r = cls_client::get_children(&pool_ioctx, RBD_CHILDREN, pspec, children);
-      // key should not exist for this parent if there is no entry
-      if (((r < 0) && (r != -ENOENT))) {
-        lderr(ictx->cct) << "can't get children for pool " << it->second
-                         << dendl;
-        goto reprotect_and_return_err;
-      }
-      // if we found a child, can't unprotect
-      if (r == 0) {
-        lderr(ictx->cct) << "snap_unprotect: can't unprotect; at least "
-          << children.size() << " child(ren) in pool " << it->second << dendl;
-        r = -EBUSY;
-        goto reprotect_and_return_err;
-      }
-    }
-
-    // didn't find any child in any pool, go ahead with unprotect
-    r = cls_client::set_protection_status(&ictx->md_ctx,
-                                         ictx->header_oid,
-                                         snap_id,
-                                         RBD_PROTECTION_STATUS_UNPROTECTED);
-    if (r < 0) {
-      lderr(ictx->cct) << "snap_unprotect: error setting unprotected status"
-                      << dendl;
-      goto reprotect_and_return_err;
-    }
     notify_change(ictx->md_ctx, ictx->header_oid, ictx);
     return 0;
-
-reprotect_and_return_err:
-    int proterr = cls_client::set_protection_status(&ictx->md_ctx,
-                                                   ictx->header_oid,
-                                                   snap_id,
-                                             RBD_PROTECTION_STATUS_PROTECTED);
-    if (proterr < 0) {
-      lderr(ictx->cct) << "snap_unprotect: can't reprotect image" << dendl;
-    }
-    notify_change(ictx->md_ctx, ictx->header_oid, ictx);
-    return r;
   }
 
   int snap_is_protected(ImageCtx *ictx, const char *snap_name,
@@ -2373,142 +2173,6 @@ reprotect_and_return_err:
     return ictx->get_snap_id(snap_name) != CEPH_NOSNAP;
   }
 
-
-  int add_snap(ImageCtx *ictx, const char *snap_name)
-  {
-    assert(ictx->owner_lock.is_locked());
-    assert(ictx->md_lock.is_wlocked());
-
-    bool lock_owner = ictx->image_watcher->is_lock_owner();
-    if (ictx->image_watcher->is_lock_supported()) {
-      assert(lock_owner);
-    }
-
-    uint64_t snap_id;
-    int r = ictx->md_ctx.selfmanaged_snap_create(&snap_id);
-    if (r < 0) {
-      lderr(ictx->cct) << "failed to create snap id: " << cpp_strerror(-r)
-                      << dendl;
-      return r;
-    }
-
-    if (ictx->old_format) {
-      r = cls_client::old_snapshot_add(&ictx->md_ctx, ictx->header_oid,
-                                      snap_id, snap_name);
-    } else {
-      librados::ObjectWriteOperation op;
-      if (lock_owner) {
-       ictx->image_watcher->assert_header_locked(&op);
-      }
-      cls_client::snapshot_add(&op, snap_id, snap_name);
-      r = ictx->md_ctx.operate(ictx->header_oid, &op);
-    }
-
-    if (r < 0) {
-      lderr(ictx->cct) << "adding snapshot to header failed: "
-                      << cpp_strerror(r) << dendl;
-      ictx->data_ctx.selfmanaged_snap_remove(snap_id);
-      return r;
-    }
-
-    RWLock::WLocker l(ictx->snap_lock);
-    if (!ictx->old_format) {
-      ictx->object_map.snapshot_add(snap_id);
-      if (lock_owner) {
-       // immediately start using the new snap context if we
-       // own the exclusive lock
-       std::vector<snapid_t> snaps;
-       snaps.push_back(snap_id);
-       snaps.insert(snaps.end(), ictx->snapc.snaps.begin(),
-                    ictx->snapc.snaps.end());
-
-       ictx->snapc.seq = snap_id;
-       ictx->snapc.snaps.swap(snaps);
-       ictx->data_ctx.selfmanaged_snap_set_write_ctx(ictx->snapc.seq,
-                                                     ictx->snaps);
-      }
-    }
-    return 0;
-  }
-
-  int rm_snap(ImageCtx *ictx, const char *snap_name, uint64_t snap_id)
-  {
-    assert(ictx->snap_lock.is_wlocked());
-
-    int r;
-    if (ictx->old_format) {
-      r = cls_client::old_snapshot_remove(&ictx->md_ctx,
-                                         ictx->header_oid, snap_name);
-    } else {
-      r = cls_client::snapshot_remove(&ictx->md_ctx, ictx->header_oid, snap_id);
-      if (r == 0) {
-        ictx->rm_snap(snap_name, snap_id);
-      }
-    }
-
-    if (r < 0) {
-      lderr(ictx->cct) << "removing snapshot from header failed: "
-                      << cpp_strerror(r) << dendl;
-      return r;
-    }
-
-    return 0;
-  }
-  int rename_snap(ImageCtx *ictx, uint64_t src_snap_id, const char *dst_name)
-  {
-    assert(ictx->owner_lock.is_locked());
-
-    int r;
-    map<snap_t, SnapInfo>::iterator it;
-    {
-      RWLock::RLocker(ictx->snap_lock);
-      it = ictx->snap_info.find(src_snap_id);
-      if (it == ictx->snap_info.end()) {
-        ldout(ictx->cct, 20) << __func__ << " can not find snap with snap id "
-                             << src_snap_id << dendl;
-        return -ENOENT;
-      }
-    }
-    bool lock_owner = ictx->image_watcher->is_lock_owner();
-    if (ictx->image_watcher->is_lock_supported()) {
-      assert(lock_owner);
-    }
-
-
-    if (ictx->old_format) {
-      r = cls_client::old_snapshot_rename(&ictx->md_ctx, ictx->header_oid,
-                                      src_snap_id, dst_name);
-    } else {
-      librados::ObjectWriteOperation op;
-      if (lock_owner) {
-       ictx->image_watcher->assert_header_locked(&op);
-      }
-      cls_client::snapshot_rename(&op, src_snap_id, dst_name);
-      r = ictx->md_ctx.operate(ictx->header_oid, &op);
-    }
-
-    if (r < 0) {
-      lderr(ictx->cct) << "rename snapshot name failed: "
-                      << cpp_strerror(r) << dendl;
-      return r;
-    }
-
-    RWLock::WLocker snap_locker(ictx->snap_lock);
-    if (!ictx->old_format) {
-      if (lock_owner) {
-        it = ictx->snap_info.find(src_snap_id);
-        if (it == ictx->snap_info.end())
-          return -ENOENT;
-        ictx->snap_ids.erase(it->second.name);
-        it->second.name = dst_name;
-        ictx->snap_ids.insert(make_pair(dst_name,it->first));
-        if (ictx->snap_id == src_snap_id)
-          ictx->snap_name = it->second.name;
-      }
-    }
-    return 0;
-  }
-
   int ictx_check(ImageCtx *ictx) {
     RWLock::RLocker owner_locker(ictx->owner_lock);
     return ictx_check(ictx, ictx->owner_lock);
@@ -2829,32 +2493,16 @@ reprotect_and_return_err:
       ictx->snap_lock.get_read();
       new_size = ictx->get_image_size(snap_id);
       ictx->snap_lock.put_read();
-
-      // need to flush any pending writes before resizing and rolling back -
-      // writes might create new snapshots. Rolling back will replace
-      // the current version, so we have to invalidate that too.
-      RWLock::WLocker md_locker(ictx->md_lock);
-      r = ictx->invalidate_cache();
-      if (r < 0) {
-       return r;
-      }
-    }
-
-    ldout(cct, 2) << "resizing to snapshot size..." << dendl;
-    NoOpProgressContext no_op;
-    C_SaferCond ctx;
-    async_resize_helper(ictx, &ctx, new_size, no_op);
-
-    r = ctx.wait();
-    if (r < 0) {
-      lderr(cct) << "Error resizing to snapshot size: "
-                << cpp_strerror(r) << dendl;
-      return r;
     }
 
-    r = rollback_image(ictx, snap_id, prog_ctx);
+    // TODO need to wait for journal replay to complete (if enabled)
+    C_SaferCond cond_ctx;
+    operation::SnapshotRollbackRequest *request =
+      new operation::SnapshotRollbackRequest(*ictx, &cond_ctx, snap_name,
+                                             snap_id, new_size, prog_ctx);
+    request->send();
+    r = cond_ctx.wait();
     if (r < 0) {
-      lderr(cct) << "Error rolling back image: " << cpp_strerror(-r) << dendl;
       return r;
     }
 
index 3de90a66fd03ac3c36a89ff002c1c3e760fd3e70..0bbb908fe47c3affc01c8b7b1f9d51b0471ba1b5 100644 (file)
@@ -48,6 +48,7 @@ enum {
 };
 
 class Context;
+class RWLock;
 class SimpleThrottle;
 
 namespace librbd {
@@ -132,16 +133,13 @@ namespace librbd {
                    ProgressContext& prog_ctx);
   int snap_remove(ImageCtx *ictx, const char *snap_name);
   int snap_remove_helper(ImageCtx *ictx, Context* ctx, const char *snap_name);
-  int snap_rename_helper(ImageCtx *ictx, Context* ctx, const uint64_t src_snap_id,
-                        const char *dst_name);
   int snap_rename(ImageCtx *ictx, const char *srcname, const char *dstname);
+  int snap_rename_helper(ImageCtx *ictx, Context* ctx,
+                         const uint64_t src_snap_id, const char *dst_name);
   int snap_protect(ImageCtx *ictx, const char *snap_name);
   int snap_unprotect(ImageCtx *ictx, const char *snap_name);
   int snap_is_protected(ImageCtx *ictx, const char *snap_name,
                        bool *is_protected);
-  int add_snap(ImageCtx *ictx, const char *snap_name);
-  int rm_snap(ImageCtx *ictx, const char *snap_name, uint64_t snap_id);
-  int rename_snap(ImageCtx *ictx, uint64_t src_snap_id, const char *dst_name);
   int refresh_parent(ImageCtx *ictx);
   int ictx_check(ImageCtx *ictx);
   int ictx_check(ImageCtx *ictx, const RWLock &owner_lock);
@@ -182,10 +180,6 @@ namespace librbd {
                  struct rbd_obj_header_ondisk *header, uint64_t *ver);
   int tmap_set(librados::IoCtx& io_ctx, const std::string& imgname);
   int tmap_rm(librados::IoCtx& io_ctx, const std::string& imgname);
-  void rollback_object(ImageCtx *ictx, uint64_t snap_id, const string& oid,
-                       SimpleThrottle& throttle);
-  int rollback_image(ImageCtx *ictx, uint64_t snap_id,
-                    ProgressContext& prog_ctx);
   void image_info(const ImageCtx *ictx, image_info_t& info, size_t info_size);
   uint64_t oid_to_object_no(const std::string& oid,
                            const std::string& object_prefix);
diff --git a/src/librbd/operation/SnapshotCreateRequest.cc b/src/librbd/operation/SnapshotCreateRequest.cc
new file mode 100644 (file)
index 0000000..2ee41c3
--- /dev/null
@@ -0,0 +1,282 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/operation/SnapshotCreateRequest.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "librbd/AioImageRequestWQ.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/ImageWatcher.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::SnapshotCreateRequest: "
+
+namespace librbd {
+namespace operation {
+
+namespace {
+
+std::ostream& operator<<(std::ostream& os,
+                         const SnapshotCreateRequest::State& state) {
+  switch(state) {
+  case SnapshotCreateRequest::STATE_SUSPEND_REQUESTS:
+    os << "SUSPEND_REQUESTS";
+    break;
+  case SnapshotCreateRequest::STATE_SUSPEND_AIO:
+    os << "SUSPEND_AIO";
+    break;
+  case SnapshotCreateRequest::STATE_FLUSH_AIO:
+    os << "FLUSH_AIO";
+    break;
+  case SnapshotCreateRequest::STATE_ALLOCATE_SNAP_ID:
+    os << "ALLOCATE_SNAP_ID";
+    break;
+  case SnapshotCreateRequest::STATE_CREATE_SNAP:
+    os << "CREATE_SNAP";
+    break;
+  case SnapshotCreateRequest::STATE_CREATE_OBJECT_MAP:
+    os << "CREATE_OBJECT_MAP";
+    break;
+  case SnapshotCreateRequest::STATE_RELEASE_SNAP_ID:
+    os << "RELEASE_SNAP_ID";
+    break;
+  default:
+    os << "UNKNOWN (" << static_cast<uint32_t>(state) << ")";
+    break;
+  }
+  return os;
+}
+
+} // anonymous namespace
+
+SnapshotCreateRequest::SnapshotCreateRequest(ImageCtx &image_ctx,
+                                             Context *on_finish,
+                                             const std::string &snap_name)
+  : Request(image_ctx, on_finish), m_snap_name(snap_name), m_ret_val(0),
+    m_aio_suspended(false), m_requests_suspended(false),
+    m_snap_id(CEPH_NOSNAP), m_snap_created(false) {
+}
+
+void SnapshotCreateRequest::send_op() {
+  send_suspend_requests();
+}
+
+bool SnapshotCreateRequest::should_complete(int r) {
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 5) << this << " " << __func__ << ": state=" << m_state << ", "
+                << "r=" << r << dendl;
+  r = filter_state_return_code(r);
+  if (r < 0) {
+    lderr(cct) << "encountered error: " << cpp_strerror(r) << dendl;
+    if (m_ret_val == 0) {
+      m_ret_val = r;
+    }
+  }
+
+  if (m_ret_val < 0) {
+    return should_complete_error();
+  }
+
+  RWLock::RLocker owner_lock(m_image_ctx.owner_lock);
+  bool finished = false;
+  switch (m_state) {
+  case STATE_SUSPEND_REQUESTS:
+    send_suspend_aio();
+    break;
+  case STATE_SUSPEND_AIO:
+    send_flush_aio();
+    break;
+  case STATE_FLUSH_AIO:
+    send_allocate_snap_id();
+    break;
+  case STATE_ALLOCATE_SNAP_ID:
+    send_create_snap();
+    break;
+  case STATE_CREATE_SNAP:
+    if (r == 0) {
+      m_snap_created = true;
+      finished = send_create_object_map();
+    } else {
+      assert(r == -ESTALE);
+      send_allocate_snap_id();
+    }
+    break;
+  case STATE_CREATE_OBJECT_MAP:
+    finished = true;
+    break;
+  default:
+    assert(false);
+    break;
+  }
+
+  if (finished) {
+    update_snap_context();
+    resume_aio();
+    resume_requests();
+  }
+  return finished;
+}
+
+bool SnapshotCreateRequest::should_complete_error() {
+  CephContext *cct = m_image_ctx.cct;
+  lderr(cct) << this << " " << __func__ << ": "
+             << "ret_val=" << m_ret_val << dendl;
+
+  // only valid exit points during error recovery
+  bool finished = true;
+  if (m_state != STATE_RELEASE_SNAP_ID) {
+    finished = send_release_snap_id();
+  }
+
+  if (finished) {
+    resume_aio();
+    resume_requests();
+  }
+  return finished;
+}
+
+void SnapshotCreateRequest::send_suspend_requests() {
+  assert(m_image_ctx.owner_lock.is_locked());
+
+  // TODO suspend (shrink) resize to ensure consistent RBD mirror
+  send_suspend_aio();
+}
+
+void SnapshotCreateRequest::send_suspend_aio() {
+  assert(m_image_ctx.owner_lock.is_locked());
+
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 5) << this << " " << __func__ << dendl;
+
+  m_state = STATE_SUSPEND_AIO;
+  m_aio_suspended = true;
+
+  // can issue a re-entrant callback if no IO in-progress
+  m_image_ctx.aio_work_queue->block_writes(create_async_callback_context());
+}
+
+void SnapshotCreateRequest::send_flush_aio() {
+  assert(m_image_ctx.owner_lock.is_locked());
+
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 5) << this << " " << __func__ << dendl;
+  m_state = STATE_FLUSH_AIO;
+
+  // can issue a re-entrant callback if no IO to flush
+  m_image_ctx.flush(create_async_callback_context());
+}
+
+void SnapshotCreateRequest::send_allocate_snap_id() {
+  assert(m_image_ctx.owner_lock.is_locked());
+
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 5) << this << " " << __func__ << dendl;
+  m_state = STATE_ALLOCATE_SNAP_ID;
+
+  // TODO create an async version of selfmanaged_snap_create
+  int r = m_image_ctx.md_ctx.selfmanaged_snap_create(&m_snap_id);
+  async_complete(r);
+}
+
+void SnapshotCreateRequest::send_create_snap() {
+  assert(m_image_ctx.owner_lock.is_locked());
+
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 5) << this << " " << __func__ << dendl;
+  m_state = STATE_CREATE_SNAP;
+
+  // should have been canceled prior to releasing lock
+  assert(!m_image_ctx.image_watcher->is_lock_supported() ||
+         m_image_ctx.image_watcher->is_lock_owner());
+
+  librados::ObjectWriteOperation op;
+  if (m_image_ctx.old_format) {
+    cls_client::old_snapshot_add(&op, m_snap_id, m_snap_name);
+  } else {
+    if (m_image_ctx.image_watcher->is_lock_owner()) {
+      m_image_ctx.image_watcher->assert_header_locked(&op);
+    }
+    cls_client::snapshot_add(&op, m_snap_id, m_snap_name);
+  }
+
+  librados::AioCompletion *rados_completion = create_callback_completion();
+  int r = m_image_ctx.md_ctx.aio_operate(m_image_ctx.header_oid,
+                                         rados_completion, &op);
+  assert(r == 0);
+  rados_completion->release();
+}
+
+bool SnapshotCreateRequest::send_create_object_map() {
+  // TODO add object map support
+  return true;
+}
+
+bool SnapshotCreateRequest::send_release_snap_id() {
+  assert(m_image_ctx.owner_lock.is_locked());
+  if (m_snap_id != CEPH_NOSNAP && !m_snap_created) {
+    CephContext *cct = m_image_ctx.cct;
+    ldout(cct, 5) << this << " " << __func__ << ": snap_id=" << m_snap_id
+                  << dendl;
+    m_state = STATE_RELEASE_SNAP_ID;
+
+    // TODO add async version of selfmanaged_snap_remove
+    int r = m_image_ctx.data_ctx.selfmanaged_snap_remove(m_snap_id);
+    m_snap_id = CEPH_NOSNAP;
+
+    async_complete(r);
+    return false;
+  }
+  return true;
+}
+
+void SnapshotCreateRequest::resume_aio() {
+  if (m_aio_suspended) {
+    CephContext *cct = m_image_ctx.cct;
+    ldout(cct, 5) << this << " " << __func__ << dendl;
+
+    m_image_ctx.aio_work_queue->unblock_writes();
+    m_aio_suspended = false;
+  }
+}
+
+void SnapshotCreateRequest::resume_requests() {
+  if (m_requests_suspended) {
+    CephContext *cct = m_image_ctx.cct;
+    ldout(cct, 5) << this << " " << __func__ << dendl;
+
+    // TODO
+    m_requests_suspended = false;
+  }
+}
+
+void SnapshotCreateRequest::update_snap_context() {
+  assert(m_image_ctx.owner_lock.is_locked());
+
+  RWLock::WLocker snap_locker(m_image_ctx.snap_lock);
+  if (m_image_ctx.old_format || !m_snap_created) {
+    return;
+  }
+
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 5) << this << " " << __func__ << dendl;
+
+  // should have been canceled prior to releasing lock
+  assert(!m_image_ctx.image_watcher->is_lock_supported(m_image_ctx.snap_lock) ||
+         m_image_ctx.image_watcher->is_lock_owner());
+
+  // immediately start using the new snap context if we
+  // own the exclusive lock
+  std::vector<snapid_t> snaps;
+  snaps.push_back(m_snap_id);
+  snaps.insert(snaps.end(), m_image_ctx.snapc.snaps.begin(),
+               m_image_ctx.snapc.snaps.end());
+
+  m_image_ctx.snapc.seq = m_snap_id;
+  m_image_ctx.snapc.snaps.swap(snaps);
+  m_image_ctx.data_ctx.selfmanaged_snap_set_write_ctx(
+    m_image_ctx.snapc.seq, m_image_ctx.snaps);
+}
+
+} // namespace operation
+} // namespace librbd
diff --git a/src/librbd/operation/SnapshotCreateRequest.h b/src/librbd/operation/SnapshotCreateRequest.h
new file mode 100644 (file)
index 0000000..1e9d526
--- /dev/null
@@ -0,0 +1,121 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_OPERATION_SNAPSHOT_CREATE_REQUEST_H
+#define CEPH_LIBRBD_OPERATION_SNAPSHOT_CREATE_REQUEST_H
+
+#include "librbd/operation/Request.h"
+#include <iosfwd>
+#include <string>
+
+class Context;
+
+namespace librbd {
+
+class ImageCtx;
+
+namespace operation {
+
+class SnapshotCreateRequest : public Request {
+public:
+  /**
+   * Snap Create goes through the following state machine:
+   *
+   * @verbatim
+   *
+   *            <start>
+   *               |
+   *               v
+   *           STATE_SUSPEND_REQUESTS
+   *               |
+   *               v
+   *           STATE_SUSPEND_AIO
+   *               |
+   *               v
+   *           STATE_FLUSH_AIO * * * * * * * * * * * * * *
+   *               |                                     *
+   *   (retry)     v                                     *
+   *   . . . > STATE_ALLOCATE_SNAP_ID  * *               *
+   *   .           |                     *               *
+   *   .           v                     *               *
+   *   . . . . STATE_CREATE_SNAP * * * * *               *
+   *               |                     *               *
+   *               v                     *               *
+   *           STATE_CREATE_OBJECT_MAP   *               *
+   *               |                     *               *
+   *               |                     *               *
+   *               |                     v               *
+   *               |              STATE_RELEASE_SNAP_ID  *
+   *               |                     |               *
+   *               |                     v               *
+   *               \----------------> <finish> < * * * * *
+   *
+   * @endverbatim
+   *
+   * The _CREATE_STATE state may repeat back to the _ALLOCATE_SNAP_ID state
+   * if a stale snapshot context is allocated. If the create operation needs
+   * to abort, the error path is followed to record the result in the journal
+   * (if enabled) and bubble the originating error code back to the client.
+   */
+  enum State {
+    STATE_SUSPEND_REQUESTS,
+    STATE_SUSPEND_AIO,
+    STATE_FLUSH_AIO,
+    STATE_ALLOCATE_SNAP_ID,
+    STATE_CREATE_SNAP,
+    STATE_CREATE_OBJECT_MAP,
+    STATE_RELEASE_SNAP_ID
+  };
+
+  SnapshotCreateRequest(ImageCtx &image_ctx, Context *on_finish,
+                       const std::string &snap_name);
+
+protected:
+  virtual void send_op();
+  virtual bool should_complete(int r);
+
+  virtual int filter_return_code(int r) const {
+    if (m_ret_val < 0) {
+      return m_ret_val;
+    }
+    return r;
+  }
+
+private:
+  std::string m_snap_name;
+  State m_state;
+
+  int m_ret_val;
+
+  bool m_aio_suspended;
+  bool m_requests_suspended;
+
+  uint64_t m_snap_id;
+  bool m_snap_created;
+
+  int filter_state_return_code(int r) const {
+    if (m_state == STATE_CREATE_SNAP && r == -ESTALE) {
+      return 0;
+    }
+    return r;
+  }
+
+  bool should_complete_error();
+
+  void send_suspend_requests();
+  void send_suspend_aio();
+  void send_flush_aio();
+  void send_allocate_snap_id();
+  void send_create_snap();
+  bool send_create_object_map();
+  bool send_release_snap_id();
+
+  void resume_aio();
+  void resume_requests();
+  void update_snap_context();
+};
+
+} // namespace operation
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_OPERATION_SNAPSHOT_CREATE_REQUEST_H
diff --git a/src/librbd/operation/SnapshotProtectRequest.cc b/src/librbd/operation/SnapshotProtectRequest.cc
new file mode 100644 (file)
index 0000000..3e8a396
--- /dev/null
@@ -0,0 +1,104 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/operation/SnapshotProtectRequest.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "librbd/ImageCtx.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::SnapshotProtectRequest: "
+
+namespace librbd {
+namespace operation {
+
+namespace {
+
+std::ostream& operator<<(std::ostream& os,
+                         const SnapshotProtectRequest::State& state) {
+  switch(state) {
+  case SnapshotProtectRequest::STATE_PROTECT_SNAP:
+    os << "PROTECT_SNAP";
+    break;
+  }
+  return os;
+}
+
+} // anonymous namespace
+
+SnapshotProtectRequest::SnapshotProtectRequest(ImageCtx &image_ctx,
+                                               Context *on_finish,
+                                               const std::string &snap_name)
+  : Request(image_ctx, on_finish), m_snap_name(snap_name) {
+}
+
+void SnapshotProtectRequest::send_op() {
+  send_protect_snap();
+}
+
+bool SnapshotProtectRequest::should_complete(int r) {
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 5) << this << " " << __func__ << ": state=" << m_state << ", "
+                << "r=" << r << dendl;
+  if (r < 0) {
+    lderr(cct) << "encountered error: " << cpp_strerror(r) << dendl;
+  }
+  return true;
+}
+
+void SnapshotProtectRequest::send_protect_snap() {
+  assert(m_image_ctx.owner_lock.is_locked());
+
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 5) << this << " " << __func__ << dendl;
+
+  m_state = STATE_PROTECT_SNAP;
+
+  int r = verify_and_send_protect_snap();
+  if (r < 0) {
+    async_complete(r);
+    return;
+  }
+}
+
+int SnapshotProtectRequest::verify_and_send_protect_snap() {
+  RWLock::RLocker md_locker(m_image_ctx.md_lock);
+  RWLock::RLocker snap_locker(m_image_ctx.snap_lock);
+
+  CephContext *cct = m_image_ctx.cct;
+  if ((m_image_ctx.features & RBD_FEATURE_LAYERING) == 0) {
+    lderr(cct) << "image must support layering" << dendl;
+    return -ENOSYS;
+  }
+
+  uint64_t snap_id = m_image_ctx.get_snap_id(m_snap_name);
+  if (snap_id == CEPH_NOSNAP) {
+    return -ENOENT;
+  }
+
+  bool is_protected;
+  int r = m_image_ctx.is_snap_protected(snap_id, &is_protected);
+  if (r < 0) {
+    return r;
+  }
+
+  if (is_protected) {
+    return -EBUSY;
+  }
+
+  librados::ObjectWriteOperation op;
+  cls_client::set_protection_status(&op, snap_id,
+                                    RBD_PROTECTION_STATUS_PROTECTED);
+
+  librados::AioCompletion *rados_completion = create_callback_completion();
+  r = m_image_ctx.md_ctx.aio_operate(m_image_ctx.header_oid, rados_completion,
+                                     &op);
+  assert(r == 0);
+  rados_completion->release();
+  return 0;
+}
+
+} // namespace operation
+} // namespace librbd
+
diff --git a/src/librbd/operation/SnapshotProtectRequest.h b/src/librbd/operation/SnapshotProtectRequest.h
new file mode 100644 (file)
index 0000000..dd3496b
--- /dev/null
@@ -0,0 +1,60 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_OPERATION_SNAPSHOT_PROTECT_REQUEST_H
+#define CEPH_LIBRBD_OPERATION_SNAPSHOT_PROTECT_REQUEST_H
+
+#include "librbd/operation/Request.h"
+#include <iosfwd>
+#include <string>
+
+class Context;
+
+namespace librbd {
+
+class ImageCtx;
+
+namespace operation {
+
+class SnapshotProtectRequest : public Request {
+public:
+  /**
+   * Snap Protect goes through the following state machine:
+   *
+   * @verbatim
+   *
+   * <start>
+   *    |
+   *    v
+   * STATE_PROTECT_SNAP
+   *    |
+   *    v
+   * <finish>
+   *
+   * @endverbatim
+   *
+   */
+  enum State {
+    STATE_PROTECT_SNAP
+  };
+
+  SnapshotProtectRequest(ImageCtx &image_ctx, Context *on_finish,
+                        const std::string &snap_name);
+
+protected:
+  virtual void send_op();
+  virtual bool should_complete(int r);
+
+private:
+  std::string m_snap_name;
+  State m_state;
+
+  void send_protect_snap();
+
+  int verify_and_send_protect_snap();
+};
+
+} // namespace operation
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_OPERATION_SNAPSHOT_PROTECT_REQUEST_H
diff --git a/src/librbd/operation/SnapshotRemoveRequest.cc b/src/librbd/operation/SnapshotRemoveRequest.cc
new file mode 100644 (file)
index 0000000..794a977
--- /dev/null
@@ -0,0 +1,202 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/operation/SnapshotRemoveRequest.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/ImageWatcher.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::SnapshotRemoveRequest: "
+
+namespace librbd {
+namespace operation {
+
+namespace {
+
+std::ostream& operator<<(std::ostream& os,
+                         const SnapshotRemoveRequest::State& state) {
+  switch(state) {
+  case SnapshotRemoveRequest::STATE_REMOVE_OBJECT_MAP:
+    os << "REMOVE_OBJECT_MAP";
+    break;
+  case SnapshotRemoveRequest::STATE_REMOVE_CHILD:
+    os << "REMOVE_CHILD";
+    break;
+  case SnapshotRemoveRequest::STATE_REMOVE_SNAP:
+    os << "REMOVE_SNAP";
+    break;
+  case SnapshotRemoveRequest::STATE_RELEASE_SNAP_ID:
+    os << "RELEASE_SNAP_ID";
+    break;
+  default:
+    os << "UNKNOWN (" << static_cast<uint32_t>(state) << ")";
+    break;
+  }
+  return os;
+}
+
+} // anonymous namespace
+
+SnapshotRemoveRequest::SnapshotRemoveRequest(ImageCtx &image_ctx,
+                                             Context *on_finish,
+                                             const std::string &snap_name,
+                                             uint64_t snap_id)
+  : Request(image_ctx, on_finish), m_snap_name(snap_name),
+    m_snap_id(snap_id) {
+}
+
+void SnapshotRemoveRequest::send_op() {
+  send_remove_object_map();
+}
+
+bool SnapshotRemoveRequest::should_complete(int r) {
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 5) << this << " " << __func__ << ": state=" << m_state << ", "
+                << "r=" << r << dendl;
+  r = filter_state_return_code(r);
+  if (r < 0) {
+    return true;
+  }
+
+  RWLock::RLocker owner_lock(m_image_ctx.owner_lock);
+  bool finished = false;
+  switch (m_state) {
+  case STATE_REMOVE_OBJECT_MAP:
+    send_remove_child();
+    break;
+  case STATE_REMOVE_CHILD:
+    send_remove_snap();
+    break;
+  case STATE_REMOVE_SNAP:
+    remove_snap_context();
+    send_release_snap_id();
+    break;
+  case STATE_RELEASE_SNAP_ID:
+    finished = true;
+    break;
+  default:
+    assert(false);
+    break;
+  }
+
+  return finished;
+}
+
+void SnapshotRemoveRequest::send_remove_object_map() {
+  assert(m_image_ctx.owner_lock.is_locked());
+
+  // TODO add object map support
+  send_remove_child();
+}
+
+void SnapshotRemoveRequest::send_remove_child() {
+  assert(m_image_ctx.owner_lock.is_locked());
+
+  CephContext *cct = m_image_ctx.cct;
+  {
+    RWLock::RLocker snap_locker(m_image_ctx.snap_lock);
+    RWLock::RLocker parent_locker(m_image_ctx.parent_lock);
+
+    parent_spec our_pspec;
+    int r = m_image_ctx.get_parent_spec(m_snap_id, &our_pspec);
+    if (r < 0) {
+      lderr(cct) << "failed to retrieve parent spec" << dendl;
+      async_complete(r);
+      return;
+    }
+
+    if (m_image_ctx.parent_md.spec != our_pspec &&
+        (scan_for_parents(our_pspec) == -ENOENT)) {
+      // no other references to the parent image
+      ldout(cct, 5) << this << " " << __func__ << dendl;
+      m_state = STATE_REMOVE_CHILD;
+
+      librados::ObjectWriteOperation op;
+      cls_client::remove_child(&op, our_pspec, m_image_ctx.id);
+
+      librados::AioCompletion *rados_completion = create_callback_completion();
+      r = m_image_ctx.md_ctx.aio_operate(RBD_CHILDREN, rados_completion, &op);
+      assert(r == 0);
+      rados_completion->release();
+      return;
+    }
+  }
+
+  // HEAD image or other snapshots still associated with parent
+  send_remove_snap();
+}
+
+void SnapshotRemoveRequest::send_remove_snap() {
+  assert(m_image_ctx.owner_lock.is_locked());
+
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 5) << this << " " << __func__ << dendl;
+  m_state = STATE_REMOVE_SNAP;
+
+  librados::ObjectWriteOperation op;
+  if (m_image_ctx.old_format) {
+    cls_client::old_snapshot_remove(&op, m_snap_name);
+  } else {
+    if (m_image_ctx.image_watcher->is_lock_owner()) {
+      m_image_ctx.image_watcher->assert_header_locked(&op);
+    }
+    cls_client::snapshot_remove(&op, m_snap_id);
+  }
+
+  librados::AioCompletion *rados_completion = create_callback_completion();
+  int r = m_image_ctx.md_ctx.aio_operate(m_image_ctx.header_oid,
+                                         rados_completion, &op);
+  assert(r == 0);
+  rados_completion->release();
+}
+
+void SnapshotRemoveRequest::send_release_snap_id() {
+  assert(m_image_ctx.owner_lock.is_locked());
+
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 5) << this << " " << __func__ << ": "
+                << "snap_name=" << m_snap_name << ", "
+                << "snap_id=" << m_snap_id << dendl;
+  m_state = STATE_RELEASE_SNAP_ID;
+
+  // TODO add async version of selfmanaged_snap_remove
+  m_image_ctx.data_ctx.selfmanaged_snap_remove(m_snap_id);
+  async_complete(0);
+}
+
+void SnapshotRemoveRequest::remove_snap_context() {
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 5) << this << " " << __func__ << dendl;
+
+  RWLock::WLocker snap_locker(m_image_ctx.snap_lock);
+  m_image_ctx.rm_snap(m_snap_name, m_snap_id);
+}
+
+int SnapshotRemoveRequest::scan_for_parents(parent_spec &pspec) {
+  assert(m_image_ctx.snap_lock.is_locked());
+  assert(m_image_ctx.parent_lock.is_locked());
+
+  if (pspec.pool_id != -1) {
+    map<uint64_t, SnapInfo>::iterator it;
+    for (it = m_image_ctx.snap_info.begin();
+         it != m_image_ctx.snap_info.end(); ++it) {
+      // skip our snap id (if checking base image, CEPH_NOSNAP won't match)
+      if (it->first == m_snap_id) {
+        continue;
+      }
+      if (it->second.parent.spec == pspec) {
+        break;
+      }
+    }
+    if (it == m_image_ctx.snap_info.end()) {
+      return -ENOENT;
+    }
+  }
+  return 0;
+}
+
+} // namespace operation
+} // namespace librbd
diff --git a/src/librbd/operation/SnapshotRemoveRequest.h b/src/librbd/operation/SnapshotRemoveRequest.h
new file mode 100644 (file)
index 0000000..85a3561
--- /dev/null
@@ -0,0 +1,89 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_OPERATION_SNAPSHOT_REMOVE_REQUEST_H
+#define CEPH_LIBRBD_OPERATION_SNAPSHOT_REMOVE_REQUEST_H
+
+#include "librbd/operation/Request.h"
+#include "librbd/parent_types.h"
+#include <string>
+
+class Context;
+
+namespace librbd {
+
+class ImageCtx;
+
+namespace operation {
+
+class SnapshotRemoveRequest : public Request {
+public:
+  /**
+   * Snap Remove goes through the following state machine:
+   *
+   * @verbatim
+   *
+   * <start> ------\
+   *  .            |
+   *  .            v
+   *  .     STATE_REMOVE_OBJECT_MAP
+   *  .            |            .
+   *  .            v            .
+   *  . . > STATE_REMOVE_CHILD  .
+   *  .            |            .
+   *  .            |      . . . .
+   *  .            |      .
+   *  .            v      v
+   *  . . > STATE_REMOVE_SNAP
+   *               |
+   *               v
+   *        STATE_RELEASE_SNAP_ID
+   *               |
+   *               v
+   *           <finish>
+   *
+   * @endverbatim
+   *
+   * The _REMOVE_OBJECT_MAP state is skipped if the object map is not enabled.
+   * The _REMOVE_CHILD state is skipped if the parent is still in-use.
+   */
+  enum State {
+    STATE_REMOVE_OBJECT_MAP,
+    STATE_REMOVE_CHILD,
+    STATE_REMOVE_SNAP,
+    STATE_RELEASE_SNAP_ID
+  };
+
+  SnapshotRemoveRequest(ImageCtx &image_ctx, Context *on_finish,
+                       const std::string &snap_name, uint64_t snap_id);
+
+protected:
+  virtual void send_op();
+  virtual bool should_complete(int r);
+
+private:
+  std::string m_snap_name;
+  uint64_t m_snap_id;
+  State m_state;
+
+  int filter_state_return_code(int r) const {
+    if (m_state == STATE_REMOVE_CHILD && r == -ENOENT) {
+      return 0;
+    }
+    return r;
+  }
+
+  void send_remove_object_map();
+  void send_remove_child();
+  void send_remove_snap();
+  void send_release_snap_id();
+
+  void remove_snap_context();
+  int scan_for_parents(parent_spec &pspec);
+
+};
+
+} // namespace operation
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_OPERATION_SNAPSHOT_REMOVE_REQUEST_H
diff --git a/src/librbd/operation/SnapshotRenameRequest.cc b/src/librbd/operation/SnapshotRenameRequest.cc
new file mode 100644 (file)
index 0000000..c5804af
--- /dev/null
@@ -0,0 +1,81 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/operation/SnapshotRenameRequest.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/ImageWatcher.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::SnapshotRenameRequest: "
+
+namespace librbd {
+namespace operation {
+
+namespace {
+
+std::ostream& operator<<(std::ostream& os,
+                         const SnapshotRenameRequest::State& state) {
+  switch(state) {
+  case SnapshotRenameRequest::STATE_RENAME_SNAP:
+    os << "RENAME_SNAP";
+    break;
+  }
+  return os;
+}
+
+} // anonymous namespace
+
+SnapshotRenameRequest::SnapshotRenameRequest(ImageCtx &image_ctx,
+                                             Context *on_finish,
+                                             uint64_t snap_id,
+                                             const std::string &snap_name)
+  : Request(image_ctx, on_finish), m_snap_id(snap_id), m_snap_name(snap_name) {
+}
+
+void SnapshotRenameRequest::send_op() {
+  send_rename_snap();
+}
+
+bool SnapshotRenameRequest::should_complete(int r) {
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 5) << this << " " << __func__ << ": state=" << m_state << ", "
+                << "r=" << r << dendl;
+  if (r < 0) {
+    lderr(cct) << "encountered error: " << cpp_strerror(r) << dendl;
+  }
+  return true;
+}
+
+void SnapshotRenameRequest::send_rename_snap() {
+  assert(m_image_ctx.owner_lock.is_locked());
+  RWLock::RLocker md_locker(m_image_ctx.md_lock);
+  RWLock::RLocker snap_locker(m_image_ctx.snap_lock);
+
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 5) << this << " " << __func__ << dendl;
+
+  m_state = STATE_RENAME_SNAP;
+
+  librados::ObjectWriteOperation op;
+  if (m_image_ctx.old_format) {
+    cls_client::old_snapshot_rename(&op, m_snap_id, m_snap_name);
+  } else {
+    if (m_image_ctx.image_watcher->is_lock_owner()) {
+      m_image_ctx.image_watcher->assert_header_locked(&op);
+    }
+    cls_client::snapshot_rename(&op, m_snap_id, m_snap_name);
+  }
+
+  librados::AioCompletion *rados_completion = create_callback_completion();
+  int r = m_image_ctx.md_ctx.aio_operate(m_image_ctx.header_oid,
+                                         rados_completion, &op);
+  assert(r == 0);
+  rados_completion->release();
+}
+
+} // namespace operation
+} // namespace librbd
+
diff --git a/src/librbd/operation/SnapshotRenameRequest.h b/src/librbd/operation/SnapshotRenameRequest.h
new file mode 100644 (file)
index 0000000..5857816
--- /dev/null
@@ -0,0 +1,59 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_OPERATION_SNAPSHOT_RENAME_REQUEST_H
+#define CEPH_LIBRBD_OPERATION_SNAPSHOT_RENAME_REQUEST_H
+
+#include "librbd/operation/Request.h"
+#include <iosfwd>
+#include <string>
+
+class Context;
+
+namespace librbd {
+
+class ImageCtx;
+
+namespace operation {
+
+class SnapshotRenameRequest : public Request {
+public:
+  /**
+   * Snap Rename goes through the following state machine:
+   *
+   * @verbatim
+   *
+   * <start>
+   *    |
+   *    v
+   * STATE_RENAME_SNAP
+   *    |
+   *    v
+   * <finish>
+   *
+   * @endverbatim
+   *
+   */
+  enum State {
+    STATE_RENAME_SNAP
+  };
+
+  SnapshotRenameRequest(ImageCtx &image_ctx, Context *on_finish,
+                        uint64_t snap_id, const std::string &snap_name);
+
+protected:
+  virtual void send_op();
+  virtual bool should_complete(int r);
+
+private:
+  uint64_t m_snap_id;
+  std::string m_snap_name;
+  State m_state;
+
+  void send_rename_snap();
+};
+
+} // namespace operation
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_OPERATION_SNAPSHOT_RENAME_REQUEST_H
diff --git a/src/librbd/operation/SnapshotRollbackRequest.cc b/src/librbd/operation/SnapshotRollbackRequest.cc
new file mode 100644 (file)
index 0000000..33972ef
--- /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
+
+#include "librbd/operation/SnapshotRollbackRequest.h"
+#include "include/rados/librados.hpp"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "librbd/AsyncObjectThrottle.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/operation/ResizeRequest.h"
+#include "osdc/Striper.h"
+#include <boost/lambda/bind.hpp>
+#include <boost/lambda/construct.hpp>
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::SnapshotRollbackRequest: "
+
+namespace librbd {
+namespace operation {
+
+namespace {
+
+std::ostream& operator<<(std::ostream& os,
+                         const SnapshotRollbackRequest::State& state) {
+  switch(state) {
+  case SnapshotRollbackRequest::STATE_RESIZE_IMAGE:
+    os << "RESIZE_IMAGE";
+    break;
+  case SnapshotRollbackRequest::STATE_ROLLBACK_OBJECT_MAP:
+    os << "ROLLBACK_OBJECT_MAP";
+    break;
+  case SnapshotRollbackRequest::STATE_ROLLBACK_OBJECTS:
+    os << "ROLLBACK_OBJECTS";
+    break;
+  case SnapshotRollbackRequest::STATE_INVALIDATE_CACHE:
+    os << "INVALIDATE_CACHE";
+    break;
+  default:
+    os << "UNKNOWN (" << static_cast<uint32_t>(state) << ")";
+    break;
+  }
+  return os;
+}
+
+class C_RollbackObject : public C_AsyncObjectThrottle<> {
+public:
+  C_RollbackObject(AsyncObjectThrottle<> &throttle, ImageCtx *image_ctx,
+                   uint64_t snap_id, uint64_t object_num)
+    : C_AsyncObjectThrottle(throttle, *image_ctx), m_snap_id(snap_id),
+      m_object_num(object_num) {
+  }
+
+  virtual int send() {
+    CephContext *cct = m_image_ctx.cct;
+    ldout(cct, 20) << "C_RollbackObject: " << __func__ << ": object_num="
+                   << m_object_num << dendl;
+
+    std::string oid = m_image_ctx.get_object_name(m_object_num);
+
+    librados::AioCompletion *rados_completion =
+      librados::Rados::aio_create_completion(this, NULL, rados_ctx_cb);
+    librados::ObjectWriteOperation op;
+    op.selfmanaged_snap_rollback(m_snap_id);
+    m_image_ctx.data_ctx.aio_operate(oid, rados_completion, &op);
+    rados_completion->release();
+    return 0;
+  }
+
+private:
+  uint64_t m_snap_id;
+  uint64_t m_object_num;
+};
+
+} // anonymous namespace
+
+SnapshotRollbackRequest::SnapshotRollbackRequest(ImageCtx &image_ctx,
+                                                 Context *on_finish,
+                                                 const std::string &snap_name,
+                                                 uint64_t snap_id,
+                                                 uint64_t snap_size,
+                                                 ProgressContext &prog_ctx)
+  : Request(image_ctx, on_finish), m_snap_name(snap_name), m_snap_id(snap_id),
+    m_snap_size(snap_size), m_prog_ctx(prog_ctx) {
+}
+
+void SnapshotRollbackRequest::send_op() {
+  send_resize_image();
+}
+
+bool SnapshotRollbackRequest::should_complete(int r) {
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 5) << this << " " << __func__ << ": state=" << m_state << ", "
+                << "r=" << r << dendl;
+  if (r < 0) {
+    lderr(cct) << "encountered error: " << cpp_strerror(r) << dendl;
+    return true;
+  }
+
+  RWLock::RLocker owner_lock(m_image_ctx.owner_lock);
+  bool finished = false;
+  switch (m_state) {
+  case STATE_RESIZE_IMAGE:
+    send_rollback_object_map();
+    break;
+  case STATE_ROLLBACK_OBJECT_MAP:
+    send_rollback_objects();
+    break;
+  case STATE_ROLLBACK_OBJECTS:
+    finished = send_invalidate_cache();
+    break;
+  case STATE_INVALIDATE_CACHE:
+    finished = true;
+    break;
+  default:
+    assert(false);
+    break;
+  }
+  return finished;
+}
+
+void SnapshotRollbackRequest::send_resize_image() {
+  assert(m_image_ctx.owner_lock.is_locked());
+
+  uint64_t current_size;
+  {
+    RWLock::RLocker snap_locker(m_image_ctx.snap_lock);
+    current_size = m_image_ctx.get_image_size(CEPH_NOSNAP);
+  }
+
+  if (current_size == m_snap_size) {
+    send_rollback_object_map();
+    return;
+  }
+
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 5) << this << " " << __func__ << dendl;
+  m_state = STATE_RESIZE_IMAGE;
+
+  ResizeRequest *req = new ResizeRequest(m_image_ctx, create_callback_context(),
+                                         m_snap_size, m_no_op_prog_ctx);
+  req->send();
+}
+
+void SnapshotRollbackRequest::send_rollback_object_map() {
+  assert(m_image_ctx.owner_lock.is_locked());
+
+  // TODO add object map support
+  send_rollback_objects();
+}
+
+void SnapshotRollbackRequest::send_rollback_objects() {
+  assert(m_image_ctx.owner_lock.is_locked());
+
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 5) << this << " " << __func__ << dendl;
+  m_state = STATE_ROLLBACK_OBJECTS;
+
+  uint64_t num_objects;
+  {
+    RWLock::RLocker snap_locker(m_image_ctx.snap_lock);
+    num_objects = Striper::get_num_objects(m_image_ctx.layout,
+                                           m_image_ctx.get_current_size());
+  }
+
+  Context *ctx = create_callback_context();
+  AsyncObjectThrottle<>::ContextFactory context_factory(
+    boost::lambda::bind(boost::lambda::new_ptr<C_RollbackObject>(),
+      boost::lambda::_1, &m_image_ctx, m_snap_id, boost::lambda::_2));
+  AsyncObjectThrottle<> *throttle = new AsyncObjectThrottle<>(
+    this, m_image_ctx, context_factory, ctx, &m_prog_ctx, 0, num_objects);
+  throttle->start_ops(m_image_ctx.concurrent_management_ops);
+}
+
+bool SnapshotRollbackRequest::send_invalidate_cache() {
+  assert(m_image_ctx.owner_lock.is_locked());
+
+  if (m_image_ctx.object_cacher == NULL) {
+    return true;
+  }
+
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 5) << this << " " << __func__ << dendl;
+  m_state = STATE_INVALIDATE_CACHE;
+
+  m_image_ctx.invalidate_cache(create_callback_context());
+  return false;
+}
+
+} // namespace operation
+} // namespace librbd
diff --git a/src/librbd/operation/SnapshotRollbackRequest.h b/src/librbd/operation/SnapshotRollbackRequest.h
new file mode 100644 (file)
index 0000000..972547e
--- /dev/null
@@ -0,0 +1,84 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_OPERATION_SNAPSHOT_ROLLBACK_REQUEST_H
+#define CEPH_LIBRBD_OPERATION_SNAPSHOT_ROLLBACK_REQUEST_H
+
+#include "librbd/operation/Request.h"
+#include "librbd/internal.h"
+#include <string>
+
+class Context;
+
+namespace librbd {
+
+class ImageCtx;
+class ProgressContext;
+
+namespace operation {
+
+class SnapshotRollbackRequest : public Request {
+public:
+  /**
+   * Snap Rollback goes through the following state machine:
+   *
+   * @verbatim
+   *
+   * <start> ---------\
+   *  .               |
+   *  .               v
+   *  .         STATE_RESIZE_IMAGE
+   *  .               |
+   *  . (skip path)   v
+   *  . . . . > STATE_ROLLBACK_OBJECT_MAP
+   *  .               |
+   *  .               v
+   *  . . . . > STATE_ROLLBACK_OBJECTS . . .
+   *                  |                    .
+   *                  v                    .
+   *            STATE_INVALIDATE_CACHE     .
+   *                  |                    .
+   *                  v                    .
+   *              <finish> < . . . . . . . .
+   *
+   * @endverbatim
+   *
+   * The _RESIZE_IMAGE state is skipped if the image doesn't need to be resized.
+   * The _ROLLBACK_OBJECT_MAP state is skipped if the object map isn't enabled.
+   * The _INVALIDATE_CACHE state is skipped if the cache isn't enabled.
+   */
+  enum State {
+    STATE_RESIZE_IMAGE,
+    STATE_ROLLBACK_OBJECT_MAP,
+    STATE_ROLLBACK_OBJECTS,
+    STATE_INVALIDATE_CACHE
+  };
+
+  SnapshotRollbackRequest(ImageCtx &image_ctx, Context *on_finish,
+                          const std::string &snap_name, uint64_t snap_id,
+                          uint64_t snap_size, ProgressContext &prog_ctx);
+
+protected:
+  virtual void send_op();
+  virtual bool should_complete(int r);
+
+private:
+  std::string m_snap_name;
+  uint64_t m_snap_id;
+  uint64_t m_snap_size;
+  ProgressContext &m_prog_ctx;
+
+  NoOpProgressContext m_no_op_prog_ctx;
+  State m_state;
+
+  void send_resize_image();
+  void send_rollback_object_map();
+  void send_rollback_objects();
+  bool send_invalidate_cache();
+
+};
+
+} // namespace operation
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_OPERATION_SNAPSHOT_ROLLBACK_REQUEST_H
diff --git a/src/librbd/operation/SnapshotUnprotectRequest.cc b/src/librbd/operation/SnapshotUnprotectRequest.cc
new file mode 100644 (file)
index 0000000..6d6d334
--- /dev/null
@@ -0,0 +1,317 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/operation/SnapshotUnprotectRequest.h"
+#include "include/rados/librados.hpp"
+#include "include/stringify.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "librbd/AsyncObjectThrottle.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/internal.h"
+#include "librbd/parent_types.h"
+#include <list>
+#include <set>
+#include <vector>
+#include <boost/lambda/bind.hpp>
+#include <boost/lambda/construct.hpp>
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::SnapshotUnprotectRequest: "
+
+namespace librbd {
+namespace operation {
+
+namespace {
+
+typedef std::pair<int64_t, std::string> Pool;
+typedef std::vector<Pool> Pools;
+
+std::ostream& operator<<(std::ostream& os,
+                         const SnapshotUnprotectRequest::State& state) {
+  switch(state) {
+  case SnapshotUnprotectRequest::STATE_UNPROTECT_SNAP_START:
+    os << "UNPROTECT_SNAP_START";
+    break;
+  case SnapshotUnprotectRequest::STATE_SCAN_POOL_CHILDREN:
+    os << "SCAN_POOL_CHILDREN";
+    break;
+  case SnapshotUnprotectRequest::STATE_UNPROTECT_SNAP_FINISH:
+    os << "UNPROTECT_SNAP_FINISH";
+    break;
+  case SnapshotUnprotectRequest::STATE_UNPROTECT_SNAP_ROLLBACK:
+    os << "UNPROTECT_SNAP_ROLLBACK";
+    break;
+  default:
+    os << "UNKNOWN (" << static_cast<uint32_t>(state) << ")";
+    break;
+  }
+  return os;
+}
+
+class C_ScanPoolChildren : public C_AsyncObjectThrottle<> {
+public:
+  C_ScanPoolChildren(AsyncObjectThrottle<> &throttle, ImageCtx *image_ctx,
+                     const parent_spec &pspec, const Pools &pools,
+                     size_t pool_idx)
+    : C_AsyncObjectThrottle(throttle, *image_ctx), m_pspec(pspec),
+      m_pool(pools[pool_idx]) {
+  }
+
+  virtual int send() {
+    assert(m_image_ctx.owner_lock.is_locked());
+
+    CephContext *cct = m_image_ctx.cct;
+    ldout(cct, 10) << this << " scanning pool '" << m_pool.second << "'"
+                   << dendl;
+
+    librados::Rados rados(m_image_ctx.md_ctx);
+    int64_t base_tier;
+    int r = rados.pool_get_base_tier(m_pool.first, &base_tier);
+    if (r == -ENOENT) {
+      ldout(cct, 1) << "pool '" << m_pool.second << "' no longer exists"
+                    << dendl;
+      return 1;
+    } else if (r < 0) {
+      lderr(cct) << "error retrieving base tier for pool '"
+                 << m_pool.second << "'" << dendl;
+      return r;
+    }
+    if (m_pool.first != base_tier) {
+      // pool is a cache; skip it
+      return 1;
+    }
+
+    r = rados.ioctx_create2(m_pool.first, m_pool_ioctx);
+    if (r == -ENOENT) {
+      ldout(cct, 1) << "pool '" << m_pool.second << "' no longer exists"
+                    << dendl;
+      return 1;
+    } else if (r < 0) {
+      lderr(cct) << "can't create ioctx for pool '" << m_pool.second
+                 << "'" << dendl;
+      return r;
+    }
+
+    cls_client::get_children(&m_pool_ioctx, RBD_CHILDREN, m_pspec, &m_children,
+                             this);
+    return 0;
+  }
+
+protected:
+  virtual void finish(int r) {
+    CephContext *cct = m_image_ctx.cct;
+    ldout(cct, 10) << this << " retrieved children: r=" << r << dendl;
+
+    if (r == -ENOENT) {
+      // no children -- proceed with unprotect
+      r = 0;
+    } else if (r < 0) {
+      lderr(cct) << "cannot get children for pool '" << m_pool.second << "'"
+                 << dendl;
+    } else {
+      lderr(cct) << "cannot unprotect: at least " << m_children.size() << " "
+                 << "child(ren) [" << joinify(m_children.begin(),
+                                              m_children.end(),
+                                              std::string(",")) << "] "
+                 << "in pool '" << m_pool.second << "'" << dendl;
+      r = -EBUSY;
+    }
+    C_AsyncObjectThrottle::finish(r);
+  }
+
+private:
+  parent_spec m_pspec;
+  Pool m_pool;
+
+  IoCtx m_pool_ioctx;
+  std::set<std::string> m_children;
+  bufferlist m_children_bl;
+};
+
+} // anonymous namespace
+
+SnapshotUnprotectRequest::SnapshotUnprotectRequest(ImageCtx &image_ctx,
+                                               Context *on_finish,
+                                               const std::string &snap_name)
+  : Request(image_ctx, on_finish), m_snap_name(snap_name), m_ret_val(0),
+    m_snap_id(CEPH_NOSNAP) {
+}
+
+void SnapshotUnprotectRequest::send_op() {
+  send_unprotect_snap_start();
+}
+
+bool SnapshotUnprotectRequest::should_complete(int r) {
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 5) << this << " " << __func__ << ": state=" << m_state << ", "
+                << "r=" << r << dendl;
+  if (r < 0) {
+    lderr(cct) << "encountered error: " << cpp_strerror(r) << dendl;
+    if (m_ret_val == 0) {
+      m_ret_val = r;
+    }
+  }
+
+  // use a different state machine once an error is encountered
+  if (m_ret_val < 0) {
+    return should_complete_error();
+  }
+
+  RWLock::RLocker owner_lock(m_image_ctx.owner_lock);
+  bool finished = false;
+  switch (m_state) {
+  case STATE_UNPROTECT_SNAP_START:
+    send_scan_pool_children();
+    break;
+  case STATE_SCAN_POOL_CHILDREN:
+    send_unprotect_snap_finish();
+    break;
+  case STATE_UNPROTECT_SNAP_FINISH:
+    finished = true;
+    break;
+  default:
+    assert(false);
+    break;
+  }
+  return finished;
+}
+
+bool SnapshotUnprotectRequest::should_complete_error() {
+  RWLock::RLocker owner_locker(m_image_ctx.owner_lock);
+  CephContext *cct = m_image_ctx.cct;
+  lderr(cct) << this << " " << __func__ << ": "
+             << "ret_val=" << m_ret_val << dendl;
+
+  bool finished = true;
+  if (m_state == STATE_SCAN_POOL_CHILDREN ||
+      m_state == STATE_UNPROTECT_SNAP_FINISH) {
+    send_unprotect_snap_rollback();
+    finished = false;
+  }
+  return finished;
+}
+
+void SnapshotUnprotectRequest::send_unprotect_snap_start() {
+  assert(m_image_ctx.owner_lock.is_locked());
+
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 5) << this << " " << __func__ << dendl;
+
+  m_state = STATE_UNPROTECT_SNAP_START;
+
+  int r = verify_and_send_unprotect_snap_start();
+  if (r < 0) {
+    async_complete(r);
+    return;
+  }
+}
+
+void SnapshotUnprotectRequest::send_scan_pool_children() {
+  assert(m_image_ctx.owner_lock.is_locked());
+
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 5) << this << " " << __func__ << dendl;
+  m_state = STATE_SCAN_POOL_CHILDREN;
+
+  // search all pools for children depending on this snapshot
+  // TODO add async version of wait_for_latest_osdmap
+  librados::Rados rados(m_image_ctx.md_ctx);
+  rados.wait_for_latest_osdmap();
+
+  // protect against pools being renamed/deleted
+  std::list<Pool> pool_list;
+  rados.pool_list2(pool_list);
+
+  parent_spec pspec(m_image_ctx.md_ctx.get_id(), m_image_ctx.id, m_snap_id);
+  Pools pools(pool_list.begin(), pool_list.end());
+
+  Context *ctx = create_callback_context();
+  AsyncObjectThrottle<>::ContextFactory context_factory(
+    boost::lambda::bind(boost::lambda::new_ptr<C_ScanPoolChildren>(),
+      boost::lambda::_1, &m_image_ctx, pspec, pools, boost::lambda::_2));
+  AsyncObjectThrottle<> *throttle = new AsyncObjectThrottle<>(
+    this, m_image_ctx, context_factory, ctx, NULL, 0,
+    pools.size());
+  throttle->start_ops(m_image_ctx.concurrent_management_ops);
+}
+
+void SnapshotUnprotectRequest::send_unprotect_snap_finish() {
+  assert(m_image_ctx.owner_lock.is_locked());
+
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 5) << this << " " << __func__ << dendl;
+
+  m_state = STATE_UNPROTECT_SNAP_FINISH;
+
+  librados::ObjectWriteOperation op;
+  cls_client::set_protection_status(&op, m_snap_id,
+                                    RBD_PROTECTION_STATUS_UNPROTECTED);
+
+  librados::AioCompletion *comp = create_callback_completion();
+  int r = m_image_ctx.md_ctx.aio_operate(m_image_ctx.header_oid, comp, &op);
+  assert(r == 0);
+  comp->release();
+}
+
+void SnapshotUnprotectRequest::send_unprotect_snap_rollback() {
+  assert(m_image_ctx.owner_lock.is_locked());
+
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 5) << this << " " << __func__ << dendl;
+
+  m_state = STATE_UNPROTECT_SNAP_ROLLBACK;
+
+  librados::ObjectWriteOperation op;
+  cls_client::set_protection_status(&op, m_snap_id,
+                                    RBD_PROTECTION_STATUS_PROTECTED);
+
+  librados::AioCompletion *comp = create_callback_completion();
+  int r = m_image_ctx.md_ctx.aio_operate(m_image_ctx.header_oid, comp, &op);
+  assert(r == 0);
+  comp->release();
+}
+
+int SnapshotUnprotectRequest::verify_and_send_unprotect_snap_start() {
+  RWLock::RLocker md_locker(m_image_ctx.md_lock);
+  RWLock::RLocker snap_locker(m_image_ctx.snap_lock);
+
+  CephContext *cct = m_image_ctx.cct;
+  if ((m_image_ctx.features & RBD_FEATURE_LAYERING) == 0) {
+    lderr(cct) << "image must support layering" << dendl;
+    return -ENOSYS;
+  }
+
+  m_snap_id = m_image_ctx.get_snap_id(m_snap_name);
+  if (m_snap_id == CEPH_NOSNAP) {
+    return -ENOENT;
+  }
+
+  bool is_unprotected;
+  int r = m_image_ctx.is_snap_unprotected(m_snap_id, &is_unprotected);
+  if (r < 0) {
+    return r;
+  }
+
+  if (is_unprotected) {
+    lderr(cct) << "snapshot is already unprotected" << dendl;
+    return -EINVAL;
+  }
+
+  librados::ObjectWriteOperation op;
+  cls_client::set_protection_status(&op, m_snap_id,
+                                    RBD_PROTECTION_STATUS_UNPROTECTING);
+
+  librados::AioCompletion *comp = create_callback_completion();
+  r = m_image_ctx.md_ctx.aio_operate(m_image_ctx.header_oid, comp, &op);
+  assert(r == 0);
+  comp->release();
+
+  // TODO legacy code threw a notification post UNPROTECTING update -- required?
+  return 0;
+}
+
+} // namespace operation
+} // namespace librbd
+
diff --git a/src/librbd/operation/SnapshotUnprotectRequest.h b/src/librbd/operation/SnapshotUnprotectRequest.h
new file mode 100644 (file)
index 0000000..faf1e13
--- /dev/null
@@ -0,0 +1,86 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_OPERATION_SNAPSHOT_UNPROTECT_REQUEST_H
+#define CEPH_LIBRBD_OPERATION_SNAPSHOT_UNPROTECT_REQUEST_H
+
+#include "librbd/operation/Request.h"
+#include <iosfwd>
+#include <string>
+
+class Context;
+
+namespace librbd {
+
+class ImageCtx;
+
+namespace operation {
+
+class SnapshotUnprotectRequest : public Request {
+public:
+  /**
+   * Snap Unprotect goes through the following state machine:
+   *
+   * @verbatim
+   *
+   * <start>
+   *    |
+   *    v
+   * STATE_UNPROTECT_SNAP_START
+   *    |
+   *    v
+   * STATE_SCAN_POOL_CHILDREN * * * * > STATE_UNPROTECT_SNAP_ROLLBACK
+   *    |                                  |
+   *    v                                  |
+   * STATE_UNPROTECT_SNAP_FINISH           |
+   *    |                                  |
+   *    v                                  |
+   * <finish> <----------------------------/
+   *
+   * @endverbatim
+   *
+   * If the unprotect operation needs to abort, the error path is followed
+   * to rollback the unprotect in-progress status on the image.
+   */
+  enum State {
+    STATE_UNPROTECT_SNAP_START,
+    STATE_SCAN_POOL_CHILDREN,
+    STATE_UNPROTECT_SNAP_FINISH,
+    STATE_UNPROTECT_SNAP_ROLLBACK
+  };
+
+  SnapshotUnprotectRequest(ImageCtx &image_ctx, Context *on_finish,
+                          const std::string &snap_name);
+
+protected:
+  virtual void send_op();
+  virtual bool should_complete(int r);
+
+  virtual int filter_return_code(int r) const {
+    if (m_ret_val < 0) {
+      return m_ret_val;
+    }
+    return 0;
+  }
+
+private:
+  std::string m_snap_name;
+  State m_state;
+
+  int m_ret_val;
+  uint64_t m_snap_id;
+
+  bool should_complete_error();
+
+  void send_unprotect_snap_start();
+  void send_scan_pool_children();
+  void send_unprotect_snap_finish();
+  void send_unprotect_snap_rollback();
+
+  int verify_and_send_unprotect_snap_start();
+};
+
+} // namespace operation
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_OPERATION_SNAPSHOT_UNPROTECT_REQUEST_H