[Pkg-ceph-commits] [ceph] 01/03: Imported Upstream version 10.2.1

James Downing Page jamespage at moszumanska.debian.org
Mon Jun 6 08:25:15 UTC 2016


This is an automated email from the git hooks/post-receive script.

jamespage pushed a commit to branch ubuntu-xenial
in repository ceph.

commit d14300eff4b1ebce8ab29a62ceec82a05a0d5f5b
Author: James Page <james.page at ubuntu.com>
Date:   Wed May 25 15:08:27 2016 +0100

    Imported Upstream version 10.2.1
---
 AUTHORS                                            |   6 +
 ChangeLog                                          | 152 ++++++-
 ceph.spec                                          | 305 +++++++++----
 ceph.spec.in                                       | 303 +++++++++----
 configure                                          |  20 +-
 configure.ac                                       |   2 +-
 etc/default/ceph                                   |   3 +
 install-deps.sh                                    |   4 +-
 man/ceph-authtool.8                                |   2 +-
 man/ceph-clsinfo.8                                 |   2 +-
 man/ceph-conf.8                                    |   2 +-
 man/ceph-create-keys.8                             |   2 +-
 man/ceph-debugpack.8                               |   2 +-
 man/ceph-dencoder.8                                |   2 +-
 man/ceph-deploy.8                                  |   2 +-
 man/ceph-detect-init.8                             |   2 +-
 man/ceph-disk.8                                    |   2 +-
 man/ceph-fuse.8                                    |   2 +-
 man/ceph-mds.8                                     |   2 +-
 man/ceph-mon.8                                     |   2 +-
 man/ceph-osd.8                                     |   2 +-
 man/ceph-post-file.8                               |   2 +-
 man/ceph-rbdnamer.8                                |   2 +-
 man/ceph-rest-api.8                                |   2 +-
 man/ceph-run.8                                     |   2 +-
 man/ceph-syn.8                                     |   2 +-
 man/ceph.8                                         |   2 +-
 man/cephfs.8                                       |   2 +-
 man/crushtool.8                                    |   2 +-
 man/librados-config.8                              |   2 +-
 man/monmaptool.8                                   |   2 +-
 man/mount.ceph.8                                   |   2 +-
 man/osdmaptool.8                                   |   2 +-
 man/rados.8                                        |   2 +-
 man/radosgw-admin.8                                |   2 +-
 man/radosgw.8                                      |   2 +-
 man/rbd-fuse.8                                     |   2 +-
 man/rbd-mirror.8                                   |   2 +-
 man/rbd-nbd.8                                      |   2 +-
 man/rbd-replay-many.8                              |   2 +-
 man/rbd-replay-prep.8                              |   2 +-
 man/rbd-replay.8                                   |   2 +-
 man/rbd.8                                          |   2 +-
 man/rbdmap.8                                       |   2 +-
 src/.git_version                                   |   4 +-
 src/Makefile.in                                    | 210 ++++++---
 src/ceph_mds.cc                                    |  31 +-
 src/cls/rbd/cls_rbd.cc                             | 476 ++++++++++++++++++-
 src/cls/rbd/cls_rbd_client.cc                      | 158 +++++++
 src/cls/rbd/cls_rbd_client.h                       |  34 ++
 src/cls/rbd/cls_rbd_types.cc                       |  84 ++++
 src/cls/rbd/cls_rbd_types.h                        |  54 +++
 src/cls/rgw/cls_rgw.cc                             |   1 +
 src/common/Readahead.cc                            |   4 +-
 src/include/ceph_fs.h                              |   2 +-
 src/include/rbd/librbd.h                           |  33 ++
 src/include/rbd/librbd.hpp                         |  18 +
 src/init-ceph.in                                   |   5 +-
 src/journal/Entry.cc                               |   9 +-
 src/journal/Entry.h                                |   2 +
 src/journal/JournalMetadata.h                      |   3 +
 src/journal/JournalPlayer.cc                       |   7 +-
 src/journal/JournalRecorder.cc                     |   2 +
 src/journal/Journaler.cc                           |   4 +
 src/journal/Journaler.h                            |   1 +
 src/librbd/AioImageRequest.cc                      |   8 +-
 src/librbd/AioImageRequestWQ.cc                    |   4 +-
 src/librbd/AioImageRequestWQ.h                     |   2 +-
 src/librbd/ExclusiveLock.cc                        |  22 +-
 src/librbd/ExclusiveLock.h                         |   5 +
 src/librbd/ImageState.cc                           |   7 +-
 src/librbd/ImageWatcher.cc                         |   2 +-
 src/librbd/Journal.cc                              |  86 +++-
 src/librbd/Journal.h                               |  20 +-
 src/librbd/ObjectMap.cc                            |   9 +-
 src/librbd/ObjectMap.h                             |   3 +
 src/librbd/Operations.cc                           | 263 ++++++-----
 src/librbd/Operations.h                            |   2 +-
 src/librbd/WatchNotifyTypes.h                      |   6 +-
 src/librbd/exclusive_lock/AcquireRequest.cc        |  56 ++-
 src/librbd/exclusive_lock/AcquireRequest.h         |  34 +-
 src/librbd/image/OpenRequest.cc                    |   4 +-
 src/librbd/image/RefreshParentRequest.cc           |  13 +-
 src/librbd/image/RefreshParentRequest.h            |  26 +-
 src/librbd/image/RefreshRequest.cc                 | 140 ++++--
 src/librbd/image/RefreshRequest.h                  |  20 +-
 src/librbd/image/SetSnapRequest.cc                 |  10 +-
 src/librbd/image_watcher/NotifyLockOwner.cc        |   2 +-
 src/librbd/internal.cc                             | 237 ++++++++--
 src/librbd/internal.h                              |   8 +
 src/librbd/journal/Replay.cc                       |  21 +-
 src/librbd/journal/Replay.h                        |   3 +-
 src/librbd/journal/Types.h                         |   6 +-
 src/librbd/librbd.cc                               | 125 ++++-
 src/librbd/object_map/InvalidateRequest.cc         |   1 +
 src/librbd/object_map/RefreshRequest.cc            |  42 +-
 src/librbd/object_map/RefreshRequest.h             |  40 +-
 src/librbd/object_map/Request.cc                   |   2 +-
 src/librbd/object_map/ResizeRequest.cc             |   5 +-
 src/librbd/object_map/UpdateRequest.cc             |   6 +-
 src/librbd/operation/FlattenRequest.cc             |   5 +-
 src/librbd/operation/RebuildObjectMapRequest.cc    |   5 +-
 src/librbd/operation/ResizeRequest.cc              |   5 +-
 src/librbd/operation/SnapshotCreateRequest.cc      |   7 +-
 src/librbd/operation/SnapshotCreateRequest.h       |   4 +-
 src/librbd/operation/SnapshotRollbackRequest.cc    |   5 +-
 src/librbd/operation/SnapshotUnprotectRequest.cc   |   2 +-
 src/librbd/operation/TrimRequest.cc                |   5 +-
 src/mds/Beacon.cc                                  |  19 +-
 src/mds/Beacon.h                                   |   5 +-
 src/mds/FSMap.cc                                   |  36 +-
 src/mds/MDSAuthCaps.cc                             |   2 +-
 src/mds/MDSDaemon.cc                               |  62 +--
 src/mds/MDSDaemon.h                                |   7 +-
 src/mds/MDSMap.cc                                  |  23 +-
 src/mds/MDSMap.h                                   |  22 +-
 src/mds/MDSRank.cc                                 |  13 +-
 src/mds/MDSRank.h                                  |   4 +-
 src/messages/MMDSBeacon.h                          |  18 +-
 src/mon/MDSMonitor.cc                              |  89 +---
 src/mon/OSDMonitor.cc                              |  34 +-
 src/objclass/class_api.cc                          |  23 +
 src/objclass/objclass.h                            |   5 +
 src/osd/OSDMap.cc                                  |   4 +-
 src/osdc/Objecter.cc                               |  16 +-
 src/pybind/ceph_volume_client.py                   |  15 +-
 src/rgw/rgw_admin.cc                               |  83 +++-
 src/rgw/rgw_civetweb_frontend.cc                   |  13 +-
 src/rgw/rgw_cr_rados.cc                            |   3 +
 src/rgw/rgw_cr_rados.h                             |  55 ++-
 src/rgw/rgw_cr_rest.h                              |  14 +
 src/rgw/rgw_data_sync.cc                           |  80 ++--
 src/rgw/rgw_data_sync.h                            |   3 +-
 src/rgw/rgw_http_client.cc                         | 283 +++++++++---
 src/rgw/rgw_http_client.h                          |  22 +-
 src/rgw/rgw_http_errors.h                          |   2 +
 src/rgw/rgw_op.cc                                  |  27 +-
 src/rgw/rgw_period_pusher.cc                       |   1 +
 src/rgw/rgw_rados.cc                               | 210 +++++----
 src/rgw/rgw_rados.h                                |   5 +-
 src/rgw/rgw_rest_client.cc                         |   2 +-
 src/rgw/rgw_rest_conn.cc                           |   4 -
 src/rgw/rgw_rest_conn.h                            |   4 -
 src/rgw/rgw_rest_s3.cc                             |   8 +
 src/rgw/rgw_swift_auth.cc                          |   2 +-
 src/rgw/rgw_sync.cc                                |  32 +-
 src/test/Makefile-client.am                        |  21 +-
 src/test/centos-6/ceph.spec.in                     | 303 +++++++++----
 src/test/centos-6/install-deps.sh                  |   4 +-
 src/test/centos-7/ceph.spec.in                     | 303 +++++++++----
 src/test/centos-7/install-deps.sh                  |   4 +-
 src/test/ceph_objectstore_tool.py                  |  23 +-
 src/test/cli/crushtool/help.t                      |   2 +-
 src/test/cli/radosgw-admin/help.t                  |  10 +-
 src/test/cli/rbd/help.t                            |  37 ++
 src/test/cls_rbd/test_cls_rbd.cc                   | 204 +++++++++
 src/test/debian-jessie/install-deps.sh             |   4 +-
 src/test/fedora-21/ceph.spec.in                    | 303 +++++++++----
 src/test/fedora-21/install-deps.sh                 |   4 +-
 .../{rbd_mirror => journal}/mock/MockJournaler.cc  |   1 +
 src/test/journal/mock/MockJournaler.h              | 267 +++++++++++
 src/test/journal/test_JournalPlayer.cc             |  49 ++
 src/test/journal/test_JournalRecorder.cc           |   9 +-
 src/test/librados_test_stub/LibradosTestStub.cc    |  24 +
 .../exclusive_lock/test_mock_AcquireRequest.cc     |  57 ++-
 src/test/librbd/image/test_mock_RefreshRequest.cc  |  45 +-
 src/test/librbd/journal/test_Entries.cc            |   3 +
 src/test/librbd/journal/test_mock_Replay.cc        |   2 +-
 src/test/librbd/mock/MockImageCtx.h                |   4 +
 src/test/librbd/mock/MockImageState.h              |   2 +
 src/test/librbd/mock/MockJournal.cc                |  10 +
 src/test/librbd/mock/MockJournal.h                 |  18 +
 src/test/librbd/mock/MockOperations.h              |   5 +-
 .../librbd/object_map/test_mock_RefreshRequest.cc  |  41 ++
 .../operation/test_mock_SnapshotCreateRequest.cc   |  46 +-
 src/test/librbd/test_librbd.cc                     |  11 +
 src/test/librbd/test_mirroring.cc                  |  75 +++
 src/test/librbd/test_mock_Journal.cc               | 258 +++--------
 src/test/mds/TestMDSAuthCaps.cc                    |  22 +
 src/test/objectstore/test_kv.cc                    |  39 +-
 src/test/opensuse-13.2/ceph.spec.in                | 303 +++++++++----
 src/test/opensuse-13.2/install-deps.sh             |   4 +-
 .../image_replayer/test_mock_BootstrapRequest.cc   | 126 +++++
 .../image_sync/test_mock_ImageCopyRequest.cc       |  14 +-
 .../image_sync/test_mock_ObjectCopyRequest.cc      |   2 -
 .../image_sync/test_mock_SnapshotCopyRequest.cc    | 308 ++++++++++++-
 .../image_sync/test_mock_SnapshotCreateRequest.cc  | 189 ++++++++
 .../image_sync/test_mock_SyncPointCreateRequest.cc |  14 +-
 .../image_sync/test_mock_SyncPointPruneRequest.cc  |  14 +-
 src/test/rbd_mirror/mock/MockJournaler.h           | 149 ------
 src/test/rbd_mirror/test_ImageSync.cc              |  82 +++-
 src/test/rbd_mirror/test_fixture.cc                |   8 +
 src/test/rbd_mirror/test_mock_ImageReplayer.cc     |  26 +-
 src/test/rbd_mirror/test_mock_ImageSync.cc         |  17 +-
 src/test/ubuntu-12.04/install-deps.sh              |   4 +-
 src/test/ubuntu-14.04/install-deps.sh              |   4 +-
 src/tools/Makefile-client.am                       |   5 +
 src/tools/crushtool.cc                             |   2 +-
 src/tools/rbd/Shell.cc                             |  27 +-
 src/tools/rbd/Shell.h                              |   4 +-
 src/tools/rbd/Utils.cc                             |  49 +-
 src/tools/rbd/Utils.h                              |   6 +-
 src/tools/rbd/action/DiskUsage.cc                  |  32 +-
 src/tools/rbd/action/Kernel.cc                     |  43 +-
 src/tools/rbd/action/MirrorImage.cc                |  68 +++
 src/tools/rbd/action/MirrorPool.cc                 | 146 ++++++
 src/tools/rbd_mirror/ImageReplayer.cc              | 505 ++++++++++++++++++---
 src/tools/rbd_mirror/ImageReplayer.h               |  55 ++-
 src/tools/rbd_mirror/ImageSync.cc                  |  42 +-
 src/tools/rbd_mirror/ImageSync.h                   |  13 +-
 src/tools/rbd_mirror/Mirror.cc                     | 117 ++++-
 src/tools/rbd_mirror/Mirror.h                      |   4 +
 src/tools/rbd_mirror/ProgressContext.h             |  21 +
 src/tools/rbd_mirror/Replayer.cc                   | 234 +++++++++-
 src/tools/rbd_mirror/Replayer.h                    |   9 +
 .../rbd_mirror/image_replayer/BootstrapRequest.cc  |  55 ++-
 .../rbd_mirror/image_replayer/BootstrapRequest.h   |  16 +-
 .../image_replayer/ReplayStatusFormatter.cc        | 240 ++++++++++
 .../image_replayer/ReplayStatusFormatter.h         |  56 +++
 .../rbd_mirror/image_sync/ImageCopyRequest.cc      |  27 +-
 src/tools/rbd_mirror/image_sync/ImageCopyRequest.h |  14 +-
 .../rbd_mirror/image_sync/ObjectCopyRequest.cc     |  79 ++--
 .../rbd_mirror/image_sync/SnapshotCopyRequest.cc   | 313 ++++++++++---
 .../rbd_mirror/image_sync/SnapshotCopyRequest.h    |  33 +-
 .../rbd_mirror/image_sync/SnapshotCreateRequest.cc | 221 +++++++++
 .../rbd_mirror/image_sync/SnapshotCreateRequest.h  |  95 ++++
 .../rbd_mirror/image_sync/SyncPointPruneRequest.cc |   2 +-
 src/tools/rbd_nbd/rbd-nbd.cc                       |  12 +-
 src/tools/setup-virtualenv.sh                      |   2 +-
 systemd/50-ceph.preset                             |   5 +
 systemd/Makefile.am                                |   3 +-
 systemd/Makefile.in                                |   3 +-
 systemd/ceph-osd at .service                          |   2 +-
 233 files changed, 8398 insertions(+), 2173 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 880a127..99d7569 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -16,6 +16,7 @@ Ailing Zhang <zhangal1992 at gmail.com>
 Alan Grosskurth <code at alan.grosskurth.ca>
 Alan Somers <asomers at gmail.com>
 Alexander Chuzhoy <achuzhoy at redhat.com>
+Alexandre Derumier <aderumier at odiso.com>
 Alexandre Marangone <alexandre.marangone at inktank.com>
 Alexandre Marangone <amarango at redhat.com>
 Alexandre Oliva <oliva at gnu.org>
@@ -108,6 +109,7 @@ Claire Massot <claire.massot93 at gmail.com>
 Clement Lebrun <clement.lebrun.31 at gmail.com>
 Colin Mattson <colinmattson at gmail.com>
 Colin P. McCabe <colinm at hq.newdream.net>
+cy.l at inwinstack.com <cy.l at inwinstack.com>
 Dan Chai <tengweicai at gmail.com>
 Daniel Gollub <d.gollub at telekom.de>
 Daniel Gryniewicz <dang at redhat.com>
@@ -229,6 +231,7 @@ Jean-Rémi Deveaux <jeanremi.deveaux at gmail.com>
 Jeff Epstein <jepst79 at gmail.com>
 Jeffrey Lu <lzhng2000 at aliyun.com>
 Jeff Weber <jweber at cofront.net>
+Jenkins Build Slave User <jenkins-build at trusty-huge--11a52675-9585-4db4-a514-798db40d6da2.localdomain>
 Jenkins Build Slave User <jenkins-build at trusty-small-unique--68a2c286-dc75-4669-822d-28cd109dc3c5.localdomain>
 Jenkins <jenkins at ceph.com>
 Jens-Christian Fischer <jens-christian.fischer at switch.ch>
@@ -475,7 +478,9 @@ Sylvain Munaut <s.munaut at whatever-company.com>
 Takanori Nakao <nakao.takanori at jp.fujitsu.com>
 Takeshi Miyamae <miyamae.takeshi at jp.fujitsu.com>
 Takuya ASADA <syuu at dokukino.com>
+Tamil Muthamizhan <tamil at magna002.ceph.redhat.com>
 Tamil Muthamizhan <tamil.muthamizhan at inktank.com>
+Tamil Muthamizhan <tmuthamizhan at MacBook-Air.local>
 Tao Chang <changtao at hihuron.com>
 Thomas Bechtold <t.bechtold at telekom.de>
 Thomas Cantin <thomas.cantin at telecom-bretagne.eu>
@@ -554,6 +559,7 @@ Yehuda Sadeh <yehuda at inktank.com>
 Yehuda Sadeh <ysadehwe at redhat.com>
 YiQiang Chen <cyqsign at 163.com>
 Yongqiang He <he.yongqiang at h3c.com>
+YongQiang <he.yongqiang at h3c.com>
 Yongyue Sun <abioy.sun at gmail.com>
 You Ji <jiyou09 at gmail.com>
 You Ji <youji at ebay.com>
diff --git a/ChangeLog b/ChangeLog
index 7bdd64d..b328d9a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,154 @@
-3a9fba2 (HEAD, tag: v10.2.0, origin/jewel) 10.2.0
+3a66dd4 (HEAD, tag: v10.2.1) 10.2.1
+a496b70 cmake: fix rbd compile errors
+9a46e13 cmake: add library cls_journal for target unittest_librbd
+48b732f tests: enable make check on ext4 (part 2)
+e1c67d4 qa/workunits/ceph-helpers.sh: make ceph-osd behave on ext4
+867f798 radosgw-admin: 'period commit' sends to new master zone by default
+a6a6c13 radosgw-admin: allow --remote to specify zone or zonegroup id
+dccb5ef radosgw-admin: 'zonegroup add' prints updated zonegroup
+bd6b5fc radosgw-admin: 'zonegroup modify' prints updated zonegroup
+c1e92df radosgw-admin: 'zone modify' prints updated zone
+f071d8c rgw: handle stripe transition when flushing final pending_data_bl
+10b393b cmake: add missing source file to rbd_mirror/image_replayer
+8bb4c7f ceph_test_keyvaluedb: clean up test dir
+108cdfd ceph_test_keyvaluedb: fixup unit test for keyvaluedb
+815967a test: add missing header dependency for MockJournaler
+3218957 rbd-mirror: image sync object copy was not properly mapping snapshots
+d3dc919 rbd-mirror: clean up image sync debug messages
+58e0612 librbd: readahead should not read past end of image
+7dadecb test: randomize rbd-mirror test case IO
+7aef297 rbd-mirror: don't set object map to nonexistent during image sync
+58e90f1 librbd: improve object map debug messages
+6c1af50 test: snapshot stress test rbd-mirror image sync
+0f84aaf rbd-mirror: allocate empty object map during snapshot create
+fa8a61d librbd: optionally skip object map creation upon snapshot
+a140753 rbd-mirror: refactor snapshot create into its own state machine
+ef1e4c8 rbd-mirror: sync snapshot protection status during bootstrap
+c790da5 test: stub test cases for rbd-mirror image bootstrap
+7a78895 test: share journal::MockJournaler between librbd and rbd-mirror
+8605040 librbd: guard object map against incompatible image sizes
+7448d29 librbd: properly handle object map open returning error code
+764b3bd librbd: add client-side memory constraint when loading object map
+5ec1f79 librbd: split large AioWrite journal events
+f1a2e6a journal: prohibit journal entries larger than the soft object size
+2d906e6 rbd-mirror: fixup to get/list mirror image status API
+fb7eb4e rbd-mirror: admin socket commands to start/stop/restart mirroring
+8e46e43 qa/workunits/rbd: fix rbd-mirror log file name
+d73073a rbd-mirror: make image replayer asok commands available when not started
+24ea574 qa/workunits/rbd: add env vars to adapt rbd_mirror.sh to teuthology
+21220a1 qa: rbd_mirror.sh: change parameters to cluster rather than daemon name
+a73b9dd librbd: possible race condition leads to use-after-free
+9bb17db rgw: fix for duplicates in list_periods()
+7ea6e78 rgw: 'period delete' cleans up all period objects
+451246d librbd: disable automatic refresh of image upon lock message
+9e058fc librbd: update_features should release lock if acquired
+e653a15 librbd: avoid applying refreshed image config within librados callback
+a5996e5 rbd-mirror: don't use temporary string for snap operation
+a891919 librbd: avoid recursive locking within operation state machine
+357ad37 librbd: put the validation of image snap context earlier
+a2eb187 librbd: does not crash if image header is too short
+b6ebb25 rbd: helpful error message on map failure
+f221fed librbd: assertion to ensure no concurrent processing of replay events
+37f08e6 journal: suppress notifications if client still in try_pop_front loop
+82c04c5 librbd: delay processing of next journal entry until flush in-progress
+f18b14b journal: incorrectly computed object offset within set
+466b7fe tools/crushtool: add straw2 support for help message
+d6692ed install-deps: remove distribute requirement
+3011eda tools: remove installation for distribute. It is no longer maintained and breaks builds
+45a1f01 set 128MB tcmalloc cache size by bytes
+bb1aeb7 etc/default/ceph: set 128MB tcmalloc cache size
+617004e test: update rbd integration cram test to remove format 1 warning
+7029b9c rgw: fix suffix in RGWZoneParams::fix_pool_names()
+f37318a rgw: code cleanup
+a8b800b rgw: upgrade default zonegroup and set correct zone[group] id
+5fdca84 rgw: RGWHTTPClient requests can unregister themselves early
+5609eb1 rgw: move around sync_env.init()
+faf6b2d rgw: rados crs, explicit cleanup
+b55514c rgw: RGWHTTPManager, avoid referring to req_data->client when completing
+2d0cd8a rgw: civetweb_callback() refer to store inside lock
+2f65b31 rgw: RGWPeriodPusher, stop http manager explicitly
+e5312b1 rgw: RGWHTTPManager, can call stop() more than once
+cbea993 rgw: RGWReadRESTResourceCR, fix refcounting
+93a65f3 rgw: RGWReadRemoteDataLogShardCR fix destructor
+2de1669 rgw: RGWDataSyncStatusManager, cleanup if failing init
+57266e6 rgw: rest crs, explicitly call cleanup
+6b86332 rgw: more leaks fixes
+0fb4854 rgw: drop a reference to http op
+d93fa13 rgw: fix printing wrong X-Storage-Url in Swift's TempAuth.
+a28fe02 radosgw-admin: add missing --zonegroup-id to usage
+c08e90e rgw: don't allow any concurrent sync requests on the same key
+6c61341 rgw: collect children after waiting for them
+4a5f33d rgw: don't pass object context into async coroutines
+1d12f82 rgw: check for status >= 0
+a288a05 rgw: don't try to delete object if does not exist
+308b9f5 rgw, cls/rgw: store removed object mtime in bi log
+1d28aac rgw: add AWS4 completion support for RGW_OP_SET_BUCKET_WEBSITE
+e69bfa1 radosgw-admin: update usage for zone[group] modify
+11d599c test/rgw: add test_zonegroup_remove
+928ccf4 test/rgw: index zones by name instead of insertion order
+5edacdc radosgw-admin: add missing 'zonegroup remove'
+6b1d9eb rgw: RGWZoneGroup::remove_zone() takes zone id
+0e95ee4 librbd: unlock image if journal error encountered during lock
+c1960de rbd-nbd: Fix aio_callback error handling
+4e87c1c Fix RBD-NBD aio_callback error handling
+28ac027 rbd:make a distinction of help message between ''rbd snap rollback" and "rbd snap revert"
+039554d librbd: reduce log level for image format 1 warning
+030883f osdc/Objecter: fix race condition for sortbitwise flag detection
+82838e3 librbd: default clone operation to image format 2
+ca13a95 librbd: add rbd_image_options_is_set helper method to API
+e6aa453 mds: fix upgrades with replay daemons in map
+6c1b792 doc: add cephfs daemon management docs
+bf44c6d mds: omit fscid in single-filesystem status output
+be5274c mds: simplify standby/standby-replay logic
+ae3ce5b messages: add MMDSBeacon::standby_replay
+02e3edd mds: remove ONESHOT_REPLAY mode
+52ca195 mds: remove stale comments
+c1279d8 mds: remove inc array from mdsmap
+9d5162f test/mds: add test for symbols in paths
+af3a4e4 mds: fix auth caps with hyphen in path
+075ee03 rgw_op: pass delete bucket op to master zone first
+1527b56 rgw: add errno entry for -ENOTEMPTY
+791eba8 fix deb package /etc/default/ceph location
+d985135 mon/OSDMonitor: improve reweight_by_utilization() logic
+b0543fd mon/OSDMonitor: avoid potential expensive grace calculation
+53686df mon/OSDMonitor: max_osds must be > 0 for reweight-by-*
+0de6345 OSDMonitor: avoid underflow in reweight-by-utilization if max_change=1
+63738d4 rbd: disk usage now includes all snapshots by default
+895c975 librbd: block RPC requests while updating features
+1f0056a librbd: return -ESHUTDOWN when a refresh is requested on a closed image
+6da1a84 librbd: synchronous block_writes should return possible error result
+04ef40e librbd: refresh image before executing maint op
+64cb645 librbd: reduce log level when lock owner not detected
+49cfb0e librbd: reduce log level for interrupted maint ops
+32c0901 qa/workunits/rbd: test mirror status in pool directory
+9a30a89 rbd-mirror: in replay status store number of entries behind master
+15a9131 librbd: API to retrieve image mirroring status
+98ca7f3 test: MockJournaler: add get_cached_client method
+e57c4d8 rbd: CLI to retrieve rbd mirror state for a pool / specific image
+6111a25 rbd-mirror: store replay status in mirroring object
+f225142 test: cls_rbd: methods to store/retrieve mirroring status
+4a3f0d2 cls::rbd:: methods to store/retrieve mirroring status
+62c6014 objclass: add method to list watchers
+125aab8 librbd: propagate any image refresh errors back to caller
+5fe4bea librbd: avoid second close attempt of invalid parent image
+af1c0bc rbd-mirror: interrupting image replay startup can cause crash
+9b2c173 librbd: fix potential assertion for object map invalidation
+6e5091e systemd: fix typo in preset file
+d302617 systemd: fix typo in preset file
+8cae07c librbd: fix potential double free of SetSnapRequest instance
+37ccacf rpm: Add rpm scripts for ceph-rbd-mirror
+c729bdd rpm: Start all the targets in %post
+c72f0bc rpm: implement scriptlets for the post-split daemon packages
+81f4073 systemd: enable all the ceph .target services by default
+f4d63af python_cephfs: rule out empty/None volume_id
+b609017 python-cephfs: use rados namespace for data isolation.
+96b3726 Signed-off-by: Tamil Muthamizhan <tmuthami at redhat.com>
+77fdbf1 Signed-off-by: Tamil Muthamizhan <tmuthami at redhat.com>
+1fa533e fix ceph init script
+7acbefa test: Fix ceph-objectstore-tool test to run manually from src non-cmake
+af4b31c Drop --setuser/--setgroup from osd prestart
+8a86d08 osd: fix backwards min/max osd utilization
+3a9fba2 (tag: v10.2.0) 10.2.0
 f86f73f rgw: drop rest op reference in error path
 88369e2 doc: rgw multisite, add pools section & minor cosmetic improvements
 7ca01d4 rgw_admin: improve period update errors
diff --git a/ceph.spec b/ceph.spec
index b452084..d08855e 100644
--- a/ceph.spec
+++ b/ceph.spec
@@ -74,7 +74,7 @@ restorecon -R /var/log/radosgw > /dev/null 2>&1;
 # common
 #################################################################################
 Name:		ceph
-Version:	10.2.0
+Version:	10.2.1
 Release:	0%{?dist}
 Epoch:		1
 Summary:	User space components of the Ceph file system
@@ -773,6 +773,7 @@ install -m 0644 -D etc/sysconfig/ceph $RPM_BUILD_ROOT%{_localstatedir}/adm/fillu
   install -m 0644 -D systemd/ceph-rbd-mirror.target $RPM_BUILD_ROOT%{_unitdir}/ceph-rbd-mirror.target
   install -m 0644 -D systemd/ceph-disk at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-disk at .service
   install -m 0755 -D systemd/ceph $RPM_BUILD_ROOT%{_sbindir}/rcceph
+  install -m 0644 -D systemd/50-ceph.preset $RPM_BUILD_ROOT%{_libexecdir}/systemd/system-preset/50-ceph.preset
 %else
   install -D src/init-rbdmap $RPM_BUILD_ROOT%{_initrddir}/rbdmap
   install -D src/init-ceph $RPM_BUILD_ROOT%{_initrddir}/ceph
@@ -845,6 +846,7 @@ rm -rf $RPM_BUILD_ROOT
 %{_bindir}/cephfs
 %if 0%{?_with_systemd}
 %{_unitdir}/ceph-create-keys at .service
+%{_libexecdir}/systemd/system-preset/50-ceph.preset
 %else
 %{_initrddir}/ceph
 %endif
@@ -899,70 +901,35 @@ rm -rf $RPM_BUILD_ROOT
 %attr(770,ceph,ceph) %dir %{_localstatedir}/run/ceph
 %endif
 
-%pre base
-%if 0%{?_with_systemd}
-  %if 0%{?suse_version}
-    # service_add_pre and friends don't work with parameterized systemd service
-    # instances, only with single services or targets, so we always pass
-    # ceph.target to these macros
-    %service_add_pre ceph.target
-  %endif
-%endif
-
 %post base
 /sbin/ldconfig
-%if 0%{?_with_systemd}
-  %if 0%{?suse_version}
-    %fillup_only
-    %service_add_post ceph.target
-  %endif
-%else
-  /sbin/chkconfig --add ceph
+%if 0%{?suse_version}
+%fillup_only
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph.target >/dev/null 2>&1 || :
+fi
 %endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph.target
+%endif
+/usr/bin/systemctl start ceph.target >/dev/null 2>&1 || :
 
 %preun base
-%if 0%{?_with_systemd}
-  %if 0%{?suse_version}
-    %service_del_preun ceph.target
-  %endif
-  # Disable and stop on removal.
-  if [ $1 = 0 ] ; then
-    SERVICE_LIST=$(systemctl | grep -E '^ceph-mon@|^ceph-create-keys@|^ceph-osd@|^ceph-mds@|^ceph-disk-'  | cut -d' ' -f1)
-    if [ -n "$SERVICE_LIST" ]; then
-      for SERVICE in $SERVICE_LIST; do
-        /usr/bin/systemctl --no-reload disable $SERVICE > /dev/null 2>&1 || :
-        /usr/bin/systemctl stop $SERVICE > /dev/null 2>&1 || :
-      done
-    fi
-  fi
-%else
-  %if 0%{?rhel} || 0%{?fedora}
-    if [ $1 = 0 ] ; then
-      /sbin/service ceph stop >/dev/null 2>&1
-      /sbin/chkconfig --del ceph
-    fi
-  %endif
+%if 0%{?suse_version}
+%service_del_preun ceph.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph.target
 %endif
 
 %postun base
 /sbin/ldconfig
-%if 0%{?_with_systemd}
-  if [ $1 = 1 ] ; then
-    # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
-    # "yes". In any case: if units are not running, do not touch them.
-    SYSCONF_CEPH=/etc/sysconfig/ceph
-    if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
-      source $SYSCONF_CEPH
-    fi
-    if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
-      SERVICE_LIST=$(systemctl | grep -E '^ceph-mon@|^ceph-create-keys@|^ceph-osd@|^ceph-mds@|^ceph-disk-'  | cut -d' ' -f1)
-      if [ -n "$SERVICE_LIST" ]; then
-        for SERVICE in $SERVICE_LIST; do
-          /usr/bin/systemctl try-restart $SERVICE > /dev/null 2>&1 || :
-        done
-      fi
-    fi
-  fi
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph.target
 %endif
 
 #################################################################################
@@ -1068,6 +1035,46 @@ fi
 %endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/mds
 
+%post mds
+%if 0%{?suse_version}
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-mds@\*.service ceph-mds.target >/dev/null 2>&1 || :
+fi
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-mds@\*.service ceph-mds.target
+%endif
+/usr/bin/systemctl start ceph-mds.target >/dev/null 2>&1 || :
+
+%preun mds
+%if 0%{?suse_version}
+%service_del_preun ceph-mds@\*.service ceph-mds.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-mds@\*.service ceph-mds.target
+%endif
+
+%postun mds
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-mds@\*.service ceph-mds.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-mds@\*.service ceph-mds.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-mds@\*.service > /dev/null 2>&1 || :
+  fi
+fi
+
 #################################################################################
 %files mon
 %{_bindir}/ceph-mon
@@ -1083,6 +1090,46 @@ fi
 %endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/mon
 
+%post mon
+%if 0%{?suse_version}
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target >/dev/null 2>&1 || :
+fi
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+/usr/bin/systemctl start ceph-mon.target >/dev/null 2>&1 || :
+
+%preun mon
+%if 0%{?suse_version}
+%service_del_preun ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+
+%postun mon
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-create-keys@\*.service ceph-mon@\*.service > /dev/null 2>&1 || :
+  fi
+fi
+
 #################################################################################
 %files fuse
 %defattr(-,root,root,-)
@@ -1110,6 +1157,46 @@ fi
 %{_unitdir}/ceph-rbd-mirror.target
 %endif
 
+%post -n rbd-mirror
+%if 0%{?suse_version}
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-rbd-mirror@\*.service ceph-rbd-mirror.target >/dev/null 2>&1 || :
+fi
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+/usr/bin/systemctl start ceph-rbd-mirror.target >/dev/null 2>&1 || :
+
+%preun -n rbd-mirror
+%if 0%{?suse_version}
+%service_del_preun ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+
+%postun -n rbd-mirror
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-rbd-mirror@\*.service > /dev/null 2>&1 || :
+  fi
+fi
+
 #################################################################################
 %files -n rbd-nbd
 %defattr(-,root,root,-)
@@ -1136,48 +1223,44 @@ fi
 %endif
 
 %post radosgw
-/sbin/ldconfig
 %if 0%{?suse_version}
-  # explicit systemctl daemon-reload (that's the only relevant bit of
-  # service_add_post; the rest is all sysvinit --> systemd migration which
-  # isn't applicable in this context (see above comment).
-  /usr/bin/systemctl daemon-reload >/dev/null 2>&1 || :
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-radosgw@\*.service ceph-radosgw.target >/dev/null 2>&1 || :
+fi
 %endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-radosgw@\*.service ceph-radosgw.target
+%endif
+/usr/bin/systemctl start ceph-radosgw.target >/dev/null 2>&1 || :
 
 %preun radosgw
-%if 0%{?_with_systemd}
-  # Disable and stop on removal.
-  if [ $1 = 0 ] ; then
-    SERVICE_LIST=$(systemctl | grep -E '^ceph-radosgw@'  | cut -d' ' -f1)
-    if [ -n "$SERVICE_LIST" ]; then
-      for SERVICE in $SERVICE_LIST; do
-        /usr/bin/systemctl --no-reload disable $SERVICE > /dev/null 2>&1 || :
-        /usr/bin/systemctl stop $SERVICE > /dev/null 2>&1 || :
-      done
-    fi
-  fi
+%if 0%{?suse_version}
+%service_del_preun ceph-radosgw@\*.service ceph-radosgw.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-radosgw@\*.service ceph-radosgw.target
 %endif
 
 %postun radosgw
-/sbin/ldconfig
-%if 0%{?_with_systemd}
-  if [ $1 = 1 ] ; then
-    # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
-    # "yes". In any case: if units are not running, do not touch them.
-    SYSCONF_CEPH=/etc/sysconfig/ceph
-    if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
-      source $SYSCONF_CEPH
-    fi
-    if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
-      SERVICE_LIST=$(systemctl | grep -E '^ceph-radosgw@'  | cut -d' ' -f1)
-      if [ -n "$SERVICE_LIST" ]; then
-        for SERVICE in $SERVICE_LIST; do
-          /usr/bin/systemctl try-restart $SERVICE > /dev/null 2>&1 || :
-        done
-      fi
-    fi
-  fi
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-radosgw@\*.service ceph-radosgw.target
 %endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-radosgw@\*.service ceph-radosgw.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-radosgw@\*.service > /dev/null 2>&1 || :
+  fi
+fi
 
 #################################################################################
 %files osd
@@ -1205,6 +1288,46 @@ fi
 %endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/osd
 
+%post osd
+%if 0%{?suse_version}
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target >/dev/null 2>&1 || :
+fi
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+/usr/bin/systemctl start ceph-osd.target >/dev/null 2>&1 || :
+
+%preun osd
+%if 0%{?suse_version}
+%service_del_preun ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+
+%postun osd
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-disk@\*.service ceph-osd@\*.service > /dev/null 2>&1 || :
+  fi
+fi
+
 #################################################################################
 %if %{with ocf}
 
@@ -1450,7 +1573,7 @@ else
     exit 0
 fi
 
-if test "$OLD_POLVER" == "$NEW_POLVER"; then
+if test "$OLD_POLVER" = "$NEW_POLVER"; then
    # Do not relabel if policy version did not change
    exit 0
 fi
diff --git a/ceph.spec.in b/ceph.spec.in
index 26928f7..34e4caa 100644
--- a/ceph.spec.in
+++ b/ceph.spec.in
@@ -773,6 +773,7 @@ install -m 0644 -D etc/sysconfig/ceph $RPM_BUILD_ROOT%{_localstatedir}/adm/fillu
   install -m 0644 -D systemd/ceph-rbd-mirror.target $RPM_BUILD_ROOT%{_unitdir}/ceph-rbd-mirror.target
   install -m 0644 -D systemd/ceph-disk at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-disk at .service
   install -m 0755 -D systemd/ceph $RPM_BUILD_ROOT%{_sbindir}/rcceph
+  install -m 0644 -D systemd/50-ceph.preset $RPM_BUILD_ROOT%{_libexecdir}/systemd/system-preset/50-ceph.preset
 %else
   install -D src/init-rbdmap $RPM_BUILD_ROOT%{_initrddir}/rbdmap
   install -D src/init-ceph $RPM_BUILD_ROOT%{_initrddir}/ceph
@@ -845,6 +846,7 @@ rm -rf $RPM_BUILD_ROOT
 %{_bindir}/cephfs
 %if 0%{?_with_systemd}
 %{_unitdir}/ceph-create-keys at .service
+%{_libexecdir}/systemd/system-preset/50-ceph.preset
 %else
 %{_initrddir}/ceph
 %endif
@@ -899,70 +901,35 @@ rm -rf $RPM_BUILD_ROOT
 %attr(770,ceph,ceph) %dir %{_localstatedir}/run/ceph
 %endif
 
-%pre base
-%if 0%{?_with_systemd}
-  %if 0%{?suse_version}
-    # service_add_pre and friends don't work with parameterized systemd service
-    # instances, only with single services or targets, so we always pass
-    # ceph.target to these macros
-    %service_add_pre ceph.target
-  %endif
-%endif
-
 %post base
 /sbin/ldconfig
-%if 0%{?_with_systemd}
-  %if 0%{?suse_version}
-    %fillup_only
-    %service_add_post ceph.target
-  %endif
-%else
-  /sbin/chkconfig --add ceph
+%if 0%{?suse_version}
+%fillup_only
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph.target >/dev/null 2>&1 || :
+fi
 %endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph.target
+%endif
+/usr/bin/systemctl start ceph.target >/dev/null 2>&1 || :
 
 %preun base
-%if 0%{?_with_systemd}
-  %if 0%{?suse_version}
-    %service_del_preun ceph.target
-  %endif
-  # Disable and stop on removal.
-  if [ $1 = 0 ] ; then
-    SERVICE_LIST=$(systemctl | grep -E '^ceph-mon@|^ceph-create-keys@|^ceph-osd@|^ceph-mds@|^ceph-disk-'  | cut -d' ' -f1)
-    if [ -n "$SERVICE_LIST" ]; then
-      for SERVICE in $SERVICE_LIST; do
-        /usr/bin/systemctl --no-reload disable $SERVICE > /dev/null 2>&1 || :
-        /usr/bin/systemctl stop $SERVICE > /dev/null 2>&1 || :
-      done
-    fi
-  fi
-%else
-  %if 0%{?rhel} || 0%{?fedora}
-    if [ $1 = 0 ] ; then
-      /sbin/service ceph stop >/dev/null 2>&1
-      /sbin/chkconfig --del ceph
-    fi
-  %endif
+%if 0%{?suse_version}
+%service_del_preun ceph.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph.target
 %endif
 
 %postun base
 /sbin/ldconfig
-%if 0%{?_with_systemd}
-  if [ $1 = 1 ] ; then
-    # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
-    # "yes". In any case: if units are not running, do not touch them.
-    SYSCONF_CEPH=/etc/sysconfig/ceph
-    if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
-      source $SYSCONF_CEPH
-    fi
-    if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
-      SERVICE_LIST=$(systemctl | grep -E '^ceph-mon@|^ceph-create-keys@|^ceph-osd@|^ceph-mds@|^ceph-disk-'  | cut -d' ' -f1)
-      if [ -n "$SERVICE_LIST" ]; then
-        for SERVICE in $SERVICE_LIST; do
-          /usr/bin/systemctl try-restart $SERVICE > /dev/null 2>&1 || :
-        done
-      fi
-    fi
-  fi
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph.target
 %endif
 
 #################################################################################
@@ -1068,6 +1035,46 @@ fi
 %endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/mds
 
+%post mds
+%if 0%{?suse_version}
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-mds@\*.service ceph-mds.target >/dev/null 2>&1 || :
+fi
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-mds@\*.service ceph-mds.target
+%endif
+/usr/bin/systemctl start ceph-mds.target >/dev/null 2>&1 || :
+
+%preun mds
+%if 0%{?suse_version}
+%service_del_preun ceph-mds@\*.service ceph-mds.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-mds@\*.service ceph-mds.target
+%endif
+
+%postun mds
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-mds@\*.service ceph-mds.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-mds@\*.service ceph-mds.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-mds@\*.service > /dev/null 2>&1 || :
+  fi
+fi
+
 #################################################################################
 %files mon
 %{_bindir}/ceph-mon
@@ -1083,6 +1090,46 @@ fi
 %endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/mon
 
+%post mon
+%if 0%{?suse_version}
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target >/dev/null 2>&1 || :
+fi
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+/usr/bin/systemctl start ceph-mon.target >/dev/null 2>&1 || :
+
+%preun mon
+%if 0%{?suse_version}
+%service_del_preun ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+
+%postun mon
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-create-keys@\*.service ceph-mon@\*.service > /dev/null 2>&1 || :
+  fi
+fi
+
 #################################################################################
 %files fuse
 %defattr(-,root,root,-)
@@ -1110,6 +1157,46 @@ fi
 %{_unitdir}/ceph-rbd-mirror.target
 %endif
 
+%post -n rbd-mirror
+%if 0%{?suse_version}
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-rbd-mirror@\*.service ceph-rbd-mirror.target >/dev/null 2>&1 || :
+fi
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+/usr/bin/systemctl start ceph-rbd-mirror.target >/dev/null 2>&1 || :
+
+%preun -n rbd-mirror
+%if 0%{?suse_version}
+%service_del_preun ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+
+%postun -n rbd-mirror
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-rbd-mirror@\*.service > /dev/null 2>&1 || :
+  fi
+fi
+
 #################################################################################
 %files -n rbd-nbd
 %defattr(-,root,root,-)
@@ -1136,48 +1223,44 @@ fi
 %endif
 
 %post radosgw
-/sbin/ldconfig
 %if 0%{?suse_version}
-  # explicit systemctl daemon-reload (that's the only relevant bit of
-  # service_add_post; the rest is all sysvinit --> systemd migration which
-  # isn't applicable in this context (see above comment).
-  /usr/bin/systemctl daemon-reload >/dev/null 2>&1 || :
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-radosgw@\*.service ceph-radosgw.target >/dev/null 2>&1 || :
+fi
 %endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-radosgw@\*.service ceph-radosgw.target
+%endif
+/usr/bin/systemctl start ceph-radosgw.target >/dev/null 2>&1 || :
 
 %preun radosgw
-%if 0%{?_with_systemd}
-  # Disable and stop on removal.
-  if [ $1 = 0 ] ; then
-    SERVICE_LIST=$(systemctl | grep -E '^ceph-radosgw@'  | cut -d' ' -f1)
-    if [ -n "$SERVICE_LIST" ]; then
-      for SERVICE in $SERVICE_LIST; do
-        /usr/bin/systemctl --no-reload disable $SERVICE > /dev/null 2>&1 || :
-        /usr/bin/systemctl stop $SERVICE > /dev/null 2>&1 || :
-      done
-    fi
-  fi
+%if 0%{?suse_version}
+%service_del_preun ceph-radosgw@\*.service ceph-radosgw.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-radosgw@\*.service ceph-radosgw.target
 %endif
 
 %postun radosgw
-/sbin/ldconfig
-%if 0%{?_with_systemd}
-  if [ $1 = 1 ] ; then
-    # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
-    # "yes". In any case: if units are not running, do not touch them.
-    SYSCONF_CEPH=/etc/sysconfig/ceph
-    if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
-      source $SYSCONF_CEPH
-    fi
-    if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
-      SERVICE_LIST=$(systemctl | grep -E '^ceph-radosgw@'  | cut -d' ' -f1)
-      if [ -n "$SERVICE_LIST" ]; then
-        for SERVICE in $SERVICE_LIST; do
-          /usr/bin/systemctl try-restart $SERVICE > /dev/null 2>&1 || :
-        done
-      fi
-    fi
-  fi
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-radosgw@\*.service ceph-radosgw.target
 %endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-radosgw@\*.service ceph-radosgw.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-radosgw@\*.service > /dev/null 2>&1 || :
+  fi
+fi
 
 #################################################################################
 %files osd
@@ -1205,6 +1288,46 @@ fi
 %endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/osd
 
+%post osd
+%if 0%{?suse_version}
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target >/dev/null 2>&1 || :
+fi
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+/usr/bin/systemctl start ceph-osd.target >/dev/null 2>&1 || :
+
+%preun osd
+%if 0%{?suse_version}
+%service_del_preun ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+
+%postun osd
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-disk@\*.service ceph-osd@\*.service > /dev/null 2>&1 || :
+  fi
+fi
+
 #################################################################################
 %if %{with ocf}
 
@@ -1450,7 +1573,7 @@ else
     exit 0
 fi
 
-if test "$OLD_POLVER" == "$NEW_POLVER"; then
+if test "$OLD_POLVER" = "$NEW_POLVER"; then
    # Do not relabel if policy version did not change
    exit 0
 fi
diff --git a/configure b/configure
index d2d7dc2..1e4e171 100755
--- a/configure
+++ b/configure
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for ceph 10.2.0.
+# Generated by GNU Autoconf 2.69 for ceph 10.2.1.
 #
 # Report bugs to <ceph-devel at vger.kernel.org>.
 #
@@ -590,8 +590,8 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='ceph'
 PACKAGE_TARNAME='ceph'
-PACKAGE_VERSION='10.2.0'
-PACKAGE_STRING='ceph 10.2.0'
+PACKAGE_VERSION='10.2.1'
+PACKAGE_STRING='ceph 10.2.1'
 PACKAGE_BUGREPORT='ceph-devel at vger.kernel.org'
 PACKAGE_URL=''
 
@@ -1582,7 +1582,7 @@ if test "$ac_init_help" = "long"; then
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures ceph 10.2.0 to adapt to many kinds of systems.
+\`configure' configures ceph 10.2.1 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1653,7 +1653,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of ceph 10.2.0:";;
+     short | recursive ) echo "Configuration of ceph 10.2.1:";;
    esac
   cat <<\_ACEOF
 
@@ -1837,7 +1837,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-ceph configure 10.2.0
+ceph configure 10.2.1
 generated by GNU Autoconf 2.69
 
 Copyright (C) 2012 Free Software Foundation, Inc.
@@ -2913,7 +2913,7 @@ cat >config.log <<_ACEOF
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by ceph $as_me 10.2.0, which was
+It was created by ceph $as_me 10.2.1, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   $ $0 $@
@@ -16408,7 +16408,7 @@ fi
 
 # Define the identity of the package.
  PACKAGE='ceph'
- VERSION='10.2.0'
+ VERSION='10.2.1'
 
 
 cat >>confdefs.h <<_ACEOF
@@ -26100,7 +26100,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by ceph $as_me 10.2.0, which was
+This file was extended by ceph $as_me 10.2.1, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -26166,7 +26166,7 @@ _ACEOF
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
 ac_cs_version="\\
-ceph config.status 10.2.0
+ceph config.status 10.2.1
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 
diff --git a/configure.ac b/configure.ac
index bfcf4b8..cd0c91c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -8,7 +8,7 @@ AC_PREREQ(2.59)
 # VERSION define is not used by the code.  It gets a version string
 # from 'git describe'; see src/ceph_ver.[ch]
 
-AC_INIT([ceph], [10.2.0], [ceph-devel at vger.kernel.org])
+AC_INIT([ceph], [10.2.1], [ceph-devel at vger.kernel.org])
 
 AX_CXX_COMPILE_STDCXX_11(, mandatory)
 
diff --git a/etc/default/ceph b/etc/default/ceph
index 6d6f40e..f272207 100644
--- a/etc/default/ceph
+++ b/etc/default/ceph
@@ -3,6 +3,9 @@
 # Environment file for ceph daemon systemd unit files.
 #
 
+# Increase tcmalloc cache size
+TCMALLOC_MAX_TOTAL_THREAD_CACHE_BYTES=134217728
+
 ## use jemalloc instead of tcmalloc
 #
 # jemalloc is generally faster for small IO workloads and when
diff --git a/install-deps.sh b/install-deps.sh
index 8249ea3..21e71ee 100755
--- a/install-deps.sh
+++ b/install-deps.sh
@@ -66,7 +66,7 @@ CentOS|Fedora|RedHatEnterpriseServer)
                     $SUDO yum install subscription-manager
                     $SUDO subscription-manager repos --enable=rhel-$MAJOR_VERSION-server-optional-rpms
                 fi
-                $SUDO yum-config-manager --add-repo https://dl.fedoraproject.org/pub/epel/$MAJOR_VERSION/x86_64/ 
+                $SUDO yum-config-manager --add-repo https://dl.fedoraproject.org/pub/epel/$MAJOR_VERSION/x86_64/
                 $SUDO yum install --nogpgcheck -y epel-release
                 $SUDO rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-$MAJOR_VERSION
                 $SUDO rm -f /etc/yum.repos.d/dl.fedoraproject.org*
@@ -92,8 +92,6 @@ function populate_wheelhouse() {
     local install=$1
     shift
 
-    # Ubuntu-12.04 and Python 2.7.3 require this line
-    pip --timeout 300 $install 'distribute >= 0.7.3' || return 1
     # although pip comes with virtualenv, having a recent version
     # of pip matters when it comes to using wheel packages
     pip --timeout 300 $install 'setuptools >= 0.8' 'pip >= 7.0' 'wheel >= 0.24' || return 1
diff --git a/man/ceph-authtool.8 b/man/ceph-authtool.8
index a723c18..7179110 100644
--- a/man/ceph-authtool.8
+++ b/man/ceph-authtool.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-AUTHTOOL" "8" "April 20, 2016" "dev" "Ceph"
+.TH "CEPH-AUTHTOOL" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 ceph-authtool \- ceph keyring manipulation tool
 .
diff --git a/man/ceph-clsinfo.8 b/man/ceph-clsinfo.8
index b52a33f..66168bc 100644
--- a/man/ceph-clsinfo.8
+++ b/man/ceph-clsinfo.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-CLSINFO" "8" "April 20, 2016" "dev" "Ceph"
+.TH "CEPH-CLSINFO" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 ceph-clsinfo \- show class object information
 .
diff --git a/man/ceph-conf.8 b/man/ceph-conf.8
index 02d3b0c..274c94b 100644
--- a/man/ceph-conf.8
+++ b/man/ceph-conf.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-CONF" "8" "April 20, 2016" "dev" "Ceph"
+.TH "CEPH-CONF" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 ceph-conf \- ceph conf file tool
 .
diff --git a/man/ceph-create-keys.8 b/man/ceph-create-keys.8
index 03bbd84..25c4ac1 100644
--- a/man/ceph-create-keys.8
+++ b/man/ceph-create-keys.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-CREATE-KEYS" "8" "April 20, 2016" "dev" "Ceph"
+.TH "CEPH-CREATE-KEYS" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 ceph-create-keys \- ceph keyring generate tool
 .
diff --git a/man/ceph-debugpack.8 b/man/ceph-debugpack.8
index 4a7e50d..277a94a 100644
--- a/man/ceph-debugpack.8
+++ b/man/ceph-debugpack.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-DEBUGPACK" "8" "April 20, 2016" "dev" "Ceph"
+.TH "CEPH-DEBUGPACK" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 ceph-debugpack \- ceph debug packer utility
 .
diff --git a/man/ceph-dencoder.8 b/man/ceph-dencoder.8
index 10ab1d3..d5ebf27 100644
--- a/man/ceph-dencoder.8
+++ b/man/ceph-dencoder.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-DENCODER" "8" "April 20, 2016" "dev" "Ceph"
+.TH "CEPH-DENCODER" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 ceph-dencoder \- ceph encoder/decoder utility
 .
diff --git a/man/ceph-deploy.8 b/man/ceph-deploy.8
index 4c12db4..d65e1af 100644
--- a/man/ceph-deploy.8
+++ b/man/ceph-deploy.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-DEPLOY" "8" "April 20, 2016" "dev" "Ceph"
+.TH "CEPH-DEPLOY" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 ceph-deploy \- Ceph deployment tool
 .
diff --git a/man/ceph-detect-init.8 b/man/ceph-detect-init.8
index 142ca72..a4fff0d 100644
--- a/man/ceph-detect-init.8
+++ b/man/ceph-detect-init.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-DETECT-INIT" "8" "April 20, 2016" "dev" "Ceph"
+.TH "CEPH-DETECT-INIT" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 ceph-detect-init \- display the init system Ceph should use
 .
diff --git a/man/ceph-disk.8 b/man/ceph-disk.8
index b892d88..9eff08a 100644
--- a/man/ceph-disk.8
+++ b/man/ceph-disk.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-DISK" "8" "April 20, 2016" "dev" "Ceph"
+.TH "CEPH-DISK" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 ceph-disk \- Ceph disk utility for OSD
 .
diff --git a/man/ceph-fuse.8 b/man/ceph-fuse.8
index 381ad29..5090e8b 100644
--- a/man/ceph-fuse.8
+++ b/man/ceph-fuse.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-FUSE" "8" "April 20, 2016" "dev" "Ceph"
+.TH "CEPH-FUSE" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 ceph-fuse \- FUSE-based client for ceph
 .
diff --git a/man/ceph-mds.8 b/man/ceph-mds.8
index 19dce91..7f93717 100644
--- a/man/ceph-mds.8
+++ b/man/ceph-mds.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-MDS" "8" "April 20, 2016" "dev" "Ceph"
+.TH "CEPH-MDS" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 ceph-mds \- ceph metadata server daemon
 .
diff --git a/man/ceph-mon.8 b/man/ceph-mon.8
index 750c0b3..fab207c 100644
--- a/man/ceph-mon.8
+++ b/man/ceph-mon.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-MON" "8" "April 20, 2016" "dev" "Ceph"
+.TH "CEPH-MON" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 ceph-mon \- ceph monitor daemon
 .
diff --git a/man/ceph-osd.8 b/man/ceph-osd.8
index 380f766..8ecaa25 100644
--- a/man/ceph-osd.8
+++ b/man/ceph-osd.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-OSD" "8" "April 20, 2016" "dev" "Ceph"
+.TH "CEPH-OSD" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 ceph-osd \- ceph object storage daemon
 .
diff --git a/man/ceph-post-file.8 b/man/ceph-post-file.8
index cc7261b..4707583 100644
--- a/man/ceph-post-file.8
+++ b/man/ceph-post-file.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-POST-FILE" "8" "April 20, 2016" "dev" "Ceph"
+.TH "CEPH-POST-FILE" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 ceph-post-file \- post files for ceph developers
 .
diff --git a/man/ceph-rbdnamer.8 b/man/ceph-rbdnamer.8
index 0ff5b9a..3441426 100644
--- a/man/ceph-rbdnamer.8
+++ b/man/ceph-rbdnamer.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-RBDNAMER" "8" "April 20, 2016" "dev" "Ceph"
+.TH "CEPH-RBDNAMER" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 ceph-rbdnamer \- udev helper to name RBD devices
 .
diff --git a/man/ceph-rest-api.8 b/man/ceph-rest-api.8
index 99bc4b5..2cd75b6 100644
--- a/man/ceph-rest-api.8
+++ b/man/ceph-rest-api.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-REST-API" "8" "April 20, 2016" "dev" "Ceph"
+.TH "CEPH-REST-API" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 ceph-rest-api \- ceph RESTlike administration server
 .
diff --git a/man/ceph-run.8 b/man/ceph-run.8
index 0a3c42e..9dae47d 100644
--- a/man/ceph-run.8
+++ b/man/ceph-run.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-RUN" "8" "April 20, 2016" "dev" "Ceph"
+.TH "CEPH-RUN" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 ceph-run \- restart daemon on core dump
 .
diff --git a/man/ceph-syn.8 b/man/ceph-syn.8
index 6976fc6..edb791f 100644
--- a/man/ceph-syn.8
+++ b/man/ceph-syn.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-SYN" "8" "April 20, 2016" "dev" "Ceph"
+.TH "CEPH-SYN" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 ceph-syn \- ceph synthetic workload generator
 .
diff --git a/man/ceph.8 b/man/ceph.8
index 07475ea..9ed382e 100644
--- a/man/ceph.8
+++ b/man/ceph.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH" "8" "April 20, 2016" "dev" "Ceph"
+.TH "CEPH" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 ceph \- ceph administration tool
 .
diff --git a/man/cephfs.8 b/man/cephfs.8
index 9ee3d4f..5dde2fa 100644
--- a/man/cephfs.8
+++ b/man/cephfs.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPHFS" "8" "April 20, 2016" "dev" "Ceph"
+.TH "CEPHFS" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 cephfs \- ceph file system options utility
 .
diff --git a/man/crushtool.8 b/man/crushtool.8
index 09a2785..93dd210 100644
--- a/man/crushtool.8
+++ b/man/crushtool.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CRUSHTOOL" "8" "April 20, 2016" "dev" "Ceph"
+.TH "CRUSHTOOL" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 crushtool \- CRUSH map manipulation tool
 .
diff --git a/man/librados-config.8 b/man/librados-config.8
index b1ea8f9..443ae2b 100644
--- a/man/librados-config.8
+++ b/man/librados-config.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "LIBRADOS-CONFIG" "8" "April 20, 2016" "dev" "Ceph"
+.TH "LIBRADOS-CONFIG" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 librados-config \- display information about librados
 .
diff --git a/man/monmaptool.8 b/man/monmaptool.8
index e7ca5c4..b2157f5 100644
--- a/man/monmaptool.8
+++ b/man/monmaptool.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "MONMAPTOOL" "8" "April 20, 2016" "dev" "Ceph"
+.TH "MONMAPTOOL" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 monmaptool \- ceph monitor cluster map manipulation tool
 .
diff --git a/man/mount.ceph.8 b/man/mount.ceph.8
index dd62bb7..a0915a8 100644
--- a/man/mount.ceph.8
+++ b/man/mount.ceph.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "MOUNT.CEPH" "8" "April 20, 2016" "dev" "Ceph"
+.TH "MOUNT.CEPH" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 mount.ceph \- mount a ceph file system
 .
diff --git a/man/osdmaptool.8 b/man/osdmaptool.8
index eb6ea9e..aa346c4 100644
--- a/man/osdmaptool.8
+++ b/man/osdmaptool.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "OSDMAPTOOL" "8" "April 20, 2016" "dev" "Ceph"
+.TH "OSDMAPTOOL" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 osdmaptool \- ceph osd cluster map manipulation tool
 .
diff --git a/man/rados.8 b/man/rados.8
index 93e0ff9..2c80dc1 100644
--- a/man/rados.8
+++ b/man/rados.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "RADOS" "8" "April 20, 2016" "dev" "Ceph"
+.TH "RADOS" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 rados \- rados object storage utility
 .
diff --git a/man/radosgw-admin.8 b/man/radosgw-admin.8
index 3faee65..03aa989 100644
--- a/man/radosgw-admin.8
+++ b/man/radosgw-admin.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "RADOSGW-ADMIN" "8" "April 20, 2016" "dev" "Ceph"
+.TH "RADOSGW-ADMIN" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 radosgw-admin \- rados REST gateway user administration utility
 .
diff --git a/man/radosgw.8 b/man/radosgw.8
index 4350688..80f59a2 100644
--- a/man/radosgw.8
+++ b/man/radosgw.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "RADOSGW" "8" "April 20, 2016" "dev" "Ceph"
+.TH "RADOSGW" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 radosgw \- rados REST gateway
 .
diff --git a/man/rbd-fuse.8 b/man/rbd-fuse.8
index a063a36..1805707 100644
--- a/man/rbd-fuse.8
+++ b/man/rbd-fuse.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "RBD-FUSE" "8" "April 20, 2016" "dev" "Ceph"
+.TH "RBD-FUSE" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 rbd-fuse \- expose rbd images as files
 .
diff --git a/man/rbd-mirror.8 b/man/rbd-mirror.8
index 1671173..0ea21a6 100644
--- a/man/rbd-mirror.8
+++ b/man/rbd-mirror.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "RBD-MIRROR" "8" "April 20, 2016" "dev" "Ceph"
+.TH "RBD-MIRROR" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 rbd-mirror \- Ceph daemon for mirroring RBD images
 .
diff --git a/man/rbd-nbd.8 b/man/rbd-nbd.8
index 15ec3b4..002739a 100644
--- a/man/rbd-nbd.8
+++ b/man/rbd-nbd.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "RBD-NBD" "8" "April 20, 2016" "dev" "Ceph"
+.TH "RBD-NBD" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 rbd-nbd \- map rbd images to nbd device
 .
diff --git a/man/rbd-replay-many.8 b/man/rbd-replay-many.8
index 138778c..7d27441 100644
--- a/man/rbd-replay-many.8
+++ b/man/rbd-replay-many.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "RBD-REPLAY-MANY" "8" "April 20, 2016" "dev" "Ceph"
+.TH "RBD-REPLAY-MANY" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 rbd-replay-many \- replay a rados block device (RBD) workload on several clients
 .
diff --git a/man/rbd-replay-prep.8 b/man/rbd-replay-prep.8
index 67311b6..0042271 100644
--- a/man/rbd-replay-prep.8
+++ b/man/rbd-replay-prep.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "RBD-REPLAY-PREP" "8" "April 20, 2016" "dev" "Ceph"
+.TH "RBD-REPLAY-PREP" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 rbd-replay-prep \- prepare captured rados block device (RBD) workloads for replay
 .
diff --git a/man/rbd-replay.8 b/man/rbd-replay.8
index 8ee7a16..8b65dfa 100644
--- a/man/rbd-replay.8
+++ b/man/rbd-replay.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "RBD-REPLAY" "8" "April 20, 2016" "dev" "Ceph"
+.TH "RBD-REPLAY" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 rbd-replay \- replay rados block device (RBD) workloads
 .
diff --git a/man/rbd.8 b/man/rbd.8
index eca8906..ba08591 100644
--- a/man/rbd.8
+++ b/man/rbd.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "RBD" "8" "April 20, 2016" "dev" "Ceph"
+.TH "RBD" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 rbd \- manage rados block device (RBD) images
 .
diff --git a/man/rbdmap.8 b/man/rbdmap.8
index 2c340d0..3d17a37 100644
--- a/man/rbdmap.8
+++ b/man/rbdmap.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "RBDMAP" "8" "April 20, 2016" "dev" "Ceph"
+.TH "RBDMAP" "8" "May 13, 2016" "dev" "Ceph"
 .SH NAME
 rbdmap \- map RBD devices at boot time
 .
diff --git a/src/.git_version b/src/.git_version
index 43fe43a..188316b 100644
--- a/src/.git_version
+++ b/src/.git_version
@@ -1,2 +1,2 @@
-3a9fba20ec743699b69bd0181dd6c54dc01c64b9
-v10.2.0
+3a66dd4f30852819c1bdaa8ec23c795d4ad77269
+v10.2.1
diff --git a/src/Makefile.in b/src/Makefile.in
index 9c1a7cf..bc76a8a 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -956,7 +956,8 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @ENABLE_CLIENT_TRUE@	test/encoding/test_ceph_time.h
 
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_218 = libradostest.la \
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	librados_test_stub.la
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	librados_test_stub.la \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	libjournal_test_mock.la
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_219 = ceph_test_rados \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	ceph_test_mutate
 @ENABLE_CLIENT_TRUE@@WITH_BUILD_TESTS_TRUE@@WITH_RADOS_TRUE at am__append_220 = test_build_librados
@@ -1002,8 +1003,7 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	ceph_test_rados_api_lock \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	ceph_test_rados_api_tmap_migrate \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	ceph_test_stress_watch
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_225 = \
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	test/librados_test_stub/LibradosTestStub.h \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_225 = test/librados_test_stub/LibradosTestStub.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	test/librados_test_stub/MockTestMemIoCtxImpl.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	test/librados_test_stub/MockTestMemRadosClient.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	test/librados_test_stub/TestClassHandler.h \
@@ -1011,8 +1011,8 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	test/librados_test_stub/TestMemRadosClient.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	test/librados_test_stub/TestWatchNotify.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	test/librados_test_stub/TestMemIoCtxImpl.h \
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	test/librados_test_stub/TestIoCtxImpl.h
-
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	test/librados_test_stub/TestIoCtxImpl.h \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	test/journal/mock/MockJournaler.h
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_226 = ceph_smalliobenchrbd \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	ceph_test_librbd \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	ceph_test_librbd_api \
@@ -1041,8 +1041,7 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/librbd/mock/MockReadahead.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/librbd/object_map/mock/MockInvalidateRequest.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/test_fixture.h \
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/test_mock_fixture.h \
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/mock/MockJournaler.h
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/test_mock_fixture.h
 @ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_232 = ceph_test_librbd_fsx
 @ENABLE_CLIENT_TRUE@@WITH_RADOSSTRIPER_TRUE@@WITH_RADOS_TRUE at am__append_233 = libradosstripertest.la
 @ENABLE_CLIENT_TRUE@@WITH_RADOSSTRIPER_TRUE@@WITH_RADOS_TRUE at am__append_234 = ceph_test_rados_striper_api_io \
@@ -1143,15 +1142,18 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/ImageSync.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/Mirror.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/PoolWatcher.h \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/ProgressContext.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/Replayer.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/Threads.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/types.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_replayer/BootstrapRequest.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_replayer/CloseImageRequest.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_replayer/OpenLocalImageRequest.h \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_replayer/ReplayStatusFormatter.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_sync/ImageCopyRequest.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_sync/ObjectCopyRequest.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_sync/SnapshotCopyRequest.h \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_sync/SnapshotCreateRequest.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_sync/SyncPointCreateRequest.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_sync/SyncPointPruneRequest.h
 @ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_269 = $(LIBKRBD)
@@ -2633,6 +2635,17 @@ am__libjournal_la_SOURCES_DIST = journal/AsyncOpTracker.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	journal/Utils.lo
 libjournal_la_OBJECTS = $(am_libjournal_la_OBJECTS)
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am_libjournal_la_rpath =
+libjournal_test_mock_la_LIBADD =
+am__libjournal_test_mock_la_SOURCES_DIST =  \
+	test/journal/mock/MockJournaler.cc
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am_libjournal_test_mock_la_OBJECTS = test/journal/mock/libjournal_test_mock_la-MockJournaler.lo
+libjournal_test_mock_la_OBJECTS =  \
+	$(am_libjournal_test_mock_la_OBJECTS)
+libjournal_test_mock_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+	$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+	$(libjournal_test_mock_la_CXXFLAGS) $(CXXFLAGS) $(AM_LDFLAGS) \
+	$(LDFLAGS) -o $@
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am_libjournal_test_mock_la_rpath =
 libjson_spirit_la_DEPENDENCIES = $(am__DEPENDENCIES_1)
 am_libjson_spirit_la_OBJECTS = json_spirit/json_spirit_reader.lo \
 	json_spirit/json_spirit_writer.lo
@@ -3009,9 +3022,11 @@ am__librbd_mirror_internal_la_SOURCES_DIST =  \
 	tools/rbd_mirror/image_replayer/BootstrapRequest.cc \
 	tools/rbd_mirror/image_replayer/CloseImageRequest.cc \
 	tools/rbd_mirror/image_replayer/OpenLocalImageRequest.cc \
+	tools/rbd_mirror/image_replayer/ReplayStatusFormatter.cc \
 	tools/rbd_mirror/image_sync/ImageCopyRequest.cc \
 	tools/rbd_mirror/image_sync/ObjectCopyRequest.cc \
 	tools/rbd_mirror/image_sync/SnapshotCopyRequest.cc \
+	tools/rbd_mirror/image_sync/SnapshotCreateRequest.cc \
 	tools/rbd_mirror/image_sync/SyncPointCreateRequest.cc \
 	tools/rbd_mirror/image_sync/SyncPointPruneRequest.cc
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am_librbd_mirror_internal_la_OBJECTS = tools/rbd_mirror/ClusterWatcher.lo \
@@ -3025,9 +3040,11 @@ am__librbd_mirror_internal_la_SOURCES_DIST =  \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_replayer/BootstrapRequest.lo \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_replayer/CloseImageRequest.lo \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_replayer/OpenLocalImageRequest.lo \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_replayer/ReplayStatusFormatter.lo \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_sync/ImageCopyRequest.lo \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_sync/ObjectCopyRequest.lo \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_sync/SnapshotCopyRequest.lo \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_sync/SnapshotCreateRequest.lo \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_sync/SyncPointCreateRequest.lo \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_sync/SyncPointPruneRequest.lo
 librbd_mirror_internal_la_OBJECTS =  \
@@ -3111,8 +3128,10 @@ librbd_test_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am_librbd_test_la_rpath =
 librbd_test_mock_la_LIBADD =
 am__librbd_test_mock_la_SOURCES_DIST =  \
-	test/librbd/mock/MockImageCtx.cc
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am_librbd_test_mock_la_OBJECTS = test/librbd/mock/librbd_test_mock_la-MockImageCtx.lo
+	test/librbd/mock/MockImageCtx.cc \
+	test/librbd/mock/MockJournal.cc
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am_librbd_test_mock_la_OBJECTS = test/librbd/mock/librbd_test_mock_la-MockImageCtx.lo \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/librbd/mock/librbd_test_mock_la-MockJournal.lo
 librbd_test_mock_la_OBJECTS = $(am_librbd_test_mock_la_OBJECTS)
 librbd_test_mock_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
 	$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
@@ -6409,6 +6428,7 @@ am__unittest_librbd_SOURCES_DIST = test/librbd/test_main.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/librbd/operation/unittest_librbd-test_mock_SnapshotUnprotectRequest.$(OBJEXT)
 unittest_librbd_OBJECTS = $(am_unittest_librbd_OBJECTS)
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at unittest_librbd_DEPENDENCIES = librbd_test.la \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	libjournal_test_mock.la \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	librbd_test_mock.la \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	librbd_api.la \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	librbd_internal.la \
@@ -6597,24 +6617,27 @@ am__unittest_rbd_mirror_SOURCES_DIST = test/rbd_mirror/test_main.cc \
 	test/rbd_mirror/test_mock_fixture.cc \
 	test/rbd_mirror/test_mock_ImageReplayer.cc \
 	test/rbd_mirror/test_mock_ImageSync.cc \
+	test/rbd_mirror/image_replayer/test_mock_BootstrapRequest.cc \
 	test/rbd_mirror/image_sync/test_mock_ImageCopyRequest.cc \
 	test/rbd_mirror/image_sync/test_mock_ObjectCopyRequest.cc \
 	test/rbd_mirror/image_sync/test_mock_SnapshotCopyRequest.cc \
+	test/rbd_mirror/image_sync/test_mock_SnapshotCreateRequest.cc \
 	test/rbd_mirror/image_sync/test_mock_SyncPointCreateRequest.cc \
-	test/rbd_mirror/image_sync/test_mock_SyncPointPruneRequest.cc \
-	test/rbd_mirror/mock/MockJournaler.cc
+	test/rbd_mirror/image_sync/test_mock_SyncPointPruneRequest.cc
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am_unittest_rbd_mirror_OBJECTS = test/rbd_mirror/unittest_rbd_mirror-test_main.$(OBJEXT) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/unittest_rbd_mirror-test_mock_fixture.$(OBJEXT) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/unittest_rbd_mirror-test_mock_ImageReplayer.$(OBJEXT) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/unittest_rbd_mirror-test_mock_ImageSync.$(OBJEXT) \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/image_replayer/unittest_rbd_mirror-test_mock_BootstrapRequest.$(OBJEXT) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_ImageCopyRequest.$(OBJEXT) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_ObjectCopyRequest.$(OBJEXT) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_SnapshotCopyRequest.$(OBJEXT) \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_SnapshotCreateRequest.$(OBJEXT) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_SyncPointCreateRequest.$(OBJEXT) \
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_SyncPointPruneRequest.$(OBJEXT) \
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/mock/unittest_rbd_mirror-MockJournaler.$(OBJEXT)
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_SyncPointPruneRequest.$(OBJEXT)
 unittest_rbd_mirror_OBJECTS = $(am_unittest_rbd_mirror_OBJECTS)
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at unittest_rbd_mirror_DEPENDENCIES = librbd_mirror_test.la \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	libjournal_test_mock.la \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	librbd_test_mock.la \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	librados_test_stub.la \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	librbd_mirror_internal.la \
@@ -7046,6 +7069,7 @@ SOURCES = $(libkv_a_SOURCES) $(libmon_a_SOURCES) $(libos_a_SOURCES) \
 	$(libec_test_shec_sse4_la_SOURCES) \
 	$(liberasure_code_la_SOURCES) $(libglobal_la_SOURCES) \
 	$(libisa_la_SOURCES) $(libjournal_la_SOURCES) \
+	$(libjournal_test_mock_la_SOURCES) \
 	$(libjson_spirit_la_SOURCES) $(libkrbd_la_SOURCES) \
 	$(liblog_la_SOURCES) $(libmds_la_SOURCES) \
 	$(libmon_types_la_SOURCES) $(libmsg_la_SOURCES) \
@@ -7308,10 +7332,12 @@ DIST_SOURCES = $(am__libkv_a_SOURCES_DIST) \
 	$(am__libec_test_shec_sse4_la_SOURCES_DIST) \
 	$(liberasure_code_la_SOURCES) $(libglobal_la_SOURCES) \
 	$(am__libisa_la_SOURCES_DIST) \
-	$(am__libjournal_la_SOURCES_DIST) $(libjson_spirit_la_SOURCES) \
-	$(am__libkrbd_la_SOURCES_DIST) $(liblog_la_SOURCES) \
-	$(am__libmds_la_SOURCES_DIST) $(libmon_types_la_SOURCES) \
-	$(am__libmsg_la_SOURCES_DIST) $(am__libos_tp_la_SOURCES_DIST) \
+	$(am__libjournal_la_SOURCES_DIST) \
+	$(am__libjournal_test_mock_la_SOURCES_DIST) \
+	$(libjson_spirit_la_SOURCES) $(am__libkrbd_la_SOURCES_DIST) \
+	$(liblog_la_SOURCES) $(am__libmds_la_SOURCES_DIST) \
+	$(libmon_types_la_SOURCES) $(am__libmsg_la_SOURCES_DIST) \
+	$(am__libos_tp_la_SOURCES_DIST) \
 	$(am__libosd_tp_la_SOURCES_DIST) $(libosd_types_la_SOURCES) \
 	$(libosdc_la_SOURCES) $(am__libperfglue_la_SOURCES_DIST) \
 	$(am__librados_la_SOURCES_DIST) \
@@ -7992,8 +8018,8 @@ am__noinst_HEADERS_DIST = arch/intel.h arch/arm.h arch/probe.h \
 	test/librados_test_stub/TestWatchNotify.h \
 	test/librados_test_stub/TestMemIoCtxImpl.h \
 	test/librados_test_stub/TestIoCtxImpl.h \
-	test/librbd/test_fixture.h test/librbd/test_mock_fixture.h \
-	test/librbd/test_support.h \
+	test/journal/mock/MockJournaler.h test/librbd/test_fixture.h \
+	test/librbd/test_mock_fixture.h test/librbd/test_support.h \
 	test/librbd/mock/MockAioImageRequestWQ.h \
 	test/librbd/mock/MockContextWQ.h \
 	test/librbd/mock/MockExclusiveLock.h \
@@ -8007,8 +8033,7 @@ am__noinst_HEADERS_DIST = arch/intel.h arch/arm.h arch/probe.h \
 	test/librbd/mock/MockReadahead.h \
 	test/librbd/object_map/mock/MockInvalidateRequest.h \
 	test/rbd_mirror/test_fixture.h \
-	test/rbd_mirror/test_mock_fixture.h \
-	test/rbd_mirror/mock/MockJournaler.h test/perf_helper.h \
+	test/rbd_mirror/test_mock_fixture.h test/perf_helper.h \
 	test/bench/backend.h test/bench/bencher.h \
 	test/bench/detailed_stat_collector.h test/bench/distribution.h \
 	test/bench/dumb_backend.h test/bench/rados_backend.h \
@@ -8038,14 +8063,16 @@ am__noinst_HEADERS_DIST = arch/intel.h arch/arm.h arch/probe.h \
 	tools/rbd_mirror/ClusterWatcher.h \
 	tools/rbd_mirror/ImageReplayer.h tools/rbd_mirror/ImageSync.h \
 	tools/rbd_mirror/Mirror.h tools/rbd_mirror/PoolWatcher.h \
-	tools/rbd_mirror/Replayer.h tools/rbd_mirror/Threads.h \
-	tools/rbd_mirror/types.h \
+	tools/rbd_mirror/ProgressContext.h tools/rbd_mirror/Replayer.h \
+	tools/rbd_mirror/Threads.h tools/rbd_mirror/types.h \
 	tools/rbd_mirror/image_replayer/BootstrapRequest.h \
 	tools/rbd_mirror/image_replayer/CloseImageRequest.h \
 	tools/rbd_mirror/image_replayer/OpenLocalImageRequest.h \
+	tools/rbd_mirror/image_replayer/ReplayStatusFormatter.h \
 	tools/rbd_mirror/image_sync/ImageCopyRequest.h \
 	tools/rbd_mirror/image_sync/ObjectCopyRequest.h \
 	tools/rbd_mirror/image_sync/SnapshotCopyRequest.h \
+	tools/rbd_mirror/image_sync/SnapshotCreateRequest.h \
 	tools/rbd_mirror/image_sync/SyncPointCreateRequest.h \
 	tools/rbd_mirror/image_sync/SyncPointPruneRequest.h \
 	tools/cephfs/JournalTool.h tools/cephfs/JournalScanner.h \
@@ -11458,6 +11485,10 @@ librbd_types_la_SOURCES = \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	test/librados_test_stub/TestRadosClient.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	test/librados_test_stub/TestWatchNotify.cc
 
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at libjournal_test_mock_la_SOURCES = \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	test/journal/mock/MockJournaler.cc
+
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at libjournal_test_mock_la_CXXFLAGS = $(UNITTEST_CXXFLAGS)
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at unittest_journal_SOURCES = \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	test/journal/test_main.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@        test/journal/test_Entry.cc \
@@ -11507,7 +11538,8 @@ librbd_types_la_SOURCES = \
 
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at librbd_test_la_CXXFLAGS = $(UNITTEST_CXXFLAGS)
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at librbd_test_mock_la_SOURCES = \
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/librbd/mock/MockImageCtx.cc
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/librbd/mock/MockImageCtx.cc \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/librbd/mock/MockJournal.cc
 
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at librbd_test_mock_la_CXXFLAGS = $(UNITTEST_CXXFLAGS)
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at unittest_librbd_SOURCES = \
@@ -11538,7 +11570,7 @@ librbd_types_la_SOURCES = \
 
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at unittest_librbd_CXXFLAGS = $(UNITTEST_CXXFLAGS) -DTEST_LIBRBD_INTERNALS
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at unittest_librbd_LDADD = \
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	librbd_test.la librbd_test_mock.la \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	librbd_test.la libjournal_test_mock.la librbd_test_mock.la \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	librbd_api.la librbd_internal.la $(LIBRBD_TYPES) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	libcls_rbd_client.la libcls_lock_client.la \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	libjournal.la libcls_journal_client.la \
@@ -11579,16 +11611,18 @@ librbd_types_la_SOURCES = \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/test_mock_fixture.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/test_mock_ImageReplayer.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/test_mock_ImageSync.cc \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/image_replayer/test_mock_BootstrapRequest.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/image_sync/test_mock_ImageCopyRequest.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/image_sync/test_mock_ObjectCopyRequest.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/image_sync/test_mock_SnapshotCopyRequest.cc \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/image_sync/test_mock_SnapshotCreateRequest.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/image_sync/test_mock_SyncPointCreateRequest.cc \
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/image_sync/test_mock_SyncPointPruneRequest.cc \
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/mock/MockJournaler.cc
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/image_sync/test_mock_SyncPointPruneRequest.cc
 
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at unittest_rbd_mirror_CXXFLAGS = $(UNITTEST_CXXFLAGS)
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at unittest_rbd_mirror_LDADD = \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	librbd_mirror_test.la \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	libjournal_test_mock.la \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	librbd_test_mock.la \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	librados_test_stub.la \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	librbd_mirror_internal.la \
@@ -12289,9 +12323,11 @@ ceph_test_cfuse_cache_invalidate_SOURCES = test/test_cfuse_cache_invalidate.cc
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_replayer/BootstrapRequest.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_replayer/CloseImageRequest.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_replayer/OpenLocalImageRequest.cc \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_replayer/ReplayStatusFormatter.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_sync/ImageCopyRequest.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_sync/ObjectCopyRequest.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_sync/SnapshotCopyRequest.cc \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_sync/SnapshotCreateRequest.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_sync/SyncPointCreateRequest.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_sync/SyncPointPruneRequest.cc
 
@@ -14582,6 +14618,18 @@ journal/Utils.lo: journal/$(am__dirstamp) \
 
 libjournal.la: $(libjournal_la_OBJECTS) $(libjournal_la_DEPENDENCIES) $(EXTRA_libjournal_la_DEPENDENCIES) 
 	$(AM_V_CXXLD)$(CXXLINK) $(am_libjournal_la_rpath) $(libjournal_la_OBJECTS) $(libjournal_la_LIBADD) $(LIBS)
+test/journal/mock/$(am__dirstamp):
+	@$(MKDIR_P) test/journal/mock
+	@: > test/journal/mock/$(am__dirstamp)
+test/journal/mock/$(DEPDIR)/$(am__dirstamp):
+	@$(MKDIR_P) test/journal/mock/$(DEPDIR)
+	@: > test/journal/mock/$(DEPDIR)/$(am__dirstamp)
+test/journal/mock/libjournal_test_mock_la-MockJournaler.lo:  \
+	test/journal/mock/$(am__dirstamp) \
+	test/journal/mock/$(DEPDIR)/$(am__dirstamp)
+
+libjournal_test_mock.la: $(libjournal_test_mock_la_OBJECTS) $(libjournal_test_mock_la_DEPENDENCIES) $(EXTRA_libjournal_test_mock_la_DEPENDENCIES) 
+	$(AM_V_CXXLD)$(libjournal_test_mock_la_LINK) $(am_libjournal_test_mock_la_rpath) $(libjournal_test_mock_la_OBJECTS) $(libjournal_test_mock_la_LIBADD) $(LIBS)
 json_spirit/$(am__dirstamp):
 	@$(MKDIR_P) json_spirit
 	@: > json_spirit/$(am__dirstamp)
@@ -15109,6 +15157,9 @@ tools/rbd_mirror/image_replayer/CloseImageRequest.lo:  \
 tools/rbd_mirror/image_replayer/OpenLocalImageRequest.lo:  \
 	tools/rbd_mirror/image_replayer/$(am__dirstamp) \
 	tools/rbd_mirror/image_replayer/$(DEPDIR)/$(am__dirstamp)
+tools/rbd_mirror/image_replayer/ReplayStatusFormatter.lo:  \
+	tools/rbd_mirror/image_replayer/$(am__dirstamp) \
+	tools/rbd_mirror/image_replayer/$(DEPDIR)/$(am__dirstamp)
 tools/rbd_mirror/image_sync/$(am__dirstamp):
 	@$(MKDIR_P) tools/rbd_mirror/image_sync
 	@: > tools/rbd_mirror/image_sync/$(am__dirstamp)
@@ -15124,6 +15175,9 @@ tools/rbd_mirror/image_sync/ObjectCopyRequest.lo:  \
 tools/rbd_mirror/image_sync/SnapshotCopyRequest.lo:  \
 	tools/rbd_mirror/image_sync/$(am__dirstamp) \
 	tools/rbd_mirror/image_sync/$(DEPDIR)/$(am__dirstamp)
+tools/rbd_mirror/image_sync/SnapshotCreateRequest.lo:  \
+	tools/rbd_mirror/image_sync/$(am__dirstamp) \
+	tools/rbd_mirror/image_sync/$(DEPDIR)/$(am__dirstamp)
 tools/rbd_mirror/image_sync/SyncPointCreateRequest.lo:  \
 	tools/rbd_mirror/image_sync/$(am__dirstamp) \
 	tools/rbd_mirror/image_sync/$(DEPDIR)/$(am__dirstamp)
@@ -15242,6 +15296,9 @@ test/librbd/mock/$(DEPDIR)/$(am__dirstamp):
 test/librbd/mock/librbd_test_mock_la-MockImageCtx.lo:  \
 	test/librbd/mock/$(am__dirstamp) \
 	test/librbd/mock/$(DEPDIR)/$(am__dirstamp)
+test/librbd/mock/librbd_test_mock_la-MockJournal.lo:  \
+	test/librbd/mock/$(am__dirstamp) \
+	test/librbd/mock/$(DEPDIR)/$(am__dirstamp)
 
 librbd_test_mock.la: $(librbd_test_mock_la_OBJECTS) $(librbd_test_mock_la_DEPENDENCIES) $(EXTRA_librbd_test_mock_la_DEPENDENCIES) 
 	$(AM_V_CXXLD)$(librbd_test_mock_la_LINK) $(am_librbd_test_mock_la_rpath) $(librbd_test_mock_la_OBJECTS) $(librbd_test_mock_la_LIBADD) $(LIBS)
@@ -18222,6 +18279,15 @@ test/rbd_mirror/unittest_rbd_mirror-test_mock_ImageReplayer.$(OBJEXT):  \
 test/rbd_mirror/unittest_rbd_mirror-test_mock_ImageSync.$(OBJEXT):  \
 	test/rbd_mirror/$(am__dirstamp) \
 	test/rbd_mirror/$(DEPDIR)/$(am__dirstamp)
+test/rbd_mirror/image_replayer/$(am__dirstamp):
+	@$(MKDIR_P) test/rbd_mirror/image_replayer
+	@: > test/rbd_mirror/image_replayer/$(am__dirstamp)
+test/rbd_mirror/image_replayer/$(DEPDIR)/$(am__dirstamp):
+	@$(MKDIR_P) test/rbd_mirror/image_replayer/$(DEPDIR)
+	@: > test/rbd_mirror/image_replayer/$(DEPDIR)/$(am__dirstamp)
+test/rbd_mirror/image_replayer/unittest_rbd_mirror-test_mock_BootstrapRequest.$(OBJEXT):  \
+	test/rbd_mirror/image_replayer/$(am__dirstamp) \
+	test/rbd_mirror/image_replayer/$(DEPDIR)/$(am__dirstamp)
 test/rbd_mirror/image_sync/$(am__dirstamp):
 	@$(MKDIR_P) test/rbd_mirror/image_sync
 	@: > test/rbd_mirror/image_sync/$(am__dirstamp)
@@ -18237,21 +18303,15 @@ test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_ObjectCopyRequest.$(OBJ
 test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_SnapshotCopyRequest.$(OBJEXT):  \
 	test/rbd_mirror/image_sync/$(am__dirstamp) \
 	test/rbd_mirror/image_sync/$(DEPDIR)/$(am__dirstamp)
+test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_SnapshotCreateRequest.$(OBJEXT):  \
+	test/rbd_mirror/image_sync/$(am__dirstamp) \
+	test/rbd_mirror/image_sync/$(DEPDIR)/$(am__dirstamp)
 test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_SyncPointCreateRequest.$(OBJEXT):  \
 	test/rbd_mirror/image_sync/$(am__dirstamp) \
 	test/rbd_mirror/image_sync/$(DEPDIR)/$(am__dirstamp)
 test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_SyncPointPruneRequest.$(OBJEXT):  \
 	test/rbd_mirror/image_sync/$(am__dirstamp) \
 	test/rbd_mirror/image_sync/$(DEPDIR)/$(am__dirstamp)
-test/rbd_mirror/mock/$(am__dirstamp):
-	@$(MKDIR_P) test/rbd_mirror/mock
-	@: > test/rbd_mirror/mock/$(am__dirstamp)
-test/rbd_mirror/mock/$(DEPDIR)/$(am__dirstamp):
-	@$(MKDIR_P) test/rbd_mirror/mock/$(DEPDIR)
-	@: > test/rbd_mirror/mock/$(DEPDIR)/$(am__dirstamp)
-test/rbd_mirror/mock/unittest_rbd_mirror-MockJournaler.$(OBJEXT):  \
-	test/rbd_mirror/mock/$(am__dirstamp) \
-	test/rbd_mirror/mock/$(DEPDIR)/$(am__dirstamp)
 
 unittest_rbd_mirror$(EXEEXT): $(unittest_rbd_mirror_OBJECTS) $(unittest_rbd_mirror_DEPENDENCIES) $(EXTRA_unittest_rbd_mirror_DEPENDENCIES) 
 	@rm -f unittest_rbd_mirror$(EXEEXT)
@@ -18859,6 +18919,8 @@ mostlyclean-compile:
 	-rm -f test/filestore/*.$(OBJEXT)
 	-rm -f test/fs/*.$(OBJEXT)
 	-rm -f test/journal/*.$(OBJEXT)
+	-rm -f test/journal/mock/*.$(OBJEXT)
+	-rm -f test/journal/mock/*.lo
 	-rm -f test/libcephfs/*.$(OBJEXT)
 	-rm -f test/librados/*.$(OBJEXT)
 	-rm -f test/librados/*.lo
@@ -18886,8 +18948,8 @@ mostlyclean-compile:
 	-rm -f test/osdc/*.$(OBJEXT)
 	-rm -f test/rbd_mirror/*.$(OBJEXT)
 	-rm -f test/rbd_mirror/*.lo
+	-rm -f test/rbd_mirror/image_replayer/*.$(OBJEXT)
 	-rm -f test/rbd_mirror/image_sync/*.$(OBJEXT)
-	-rm -f test/rbd_mirror/mock/*.$(OBJEXT)
 	-rm -f test/rgw/*.$(OBJEXT)
 	-rm -f test/system/*.$(OBJEXT)
 	-rm -f test/system/*.lo
@@ -20053,6 +20115,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote at test/journal/$(DEPDIR)/unittest_journal-test_ObjectPlayer.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/journal/$(DEPDIR)/unittest_journal-test_ObjectRecorder.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/journal/$(DEPDIR)/unittest_journal-test_main.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at test/journal/mock/$(DEPDIR)/libjournal_test_mock_la-MockJournaler.Plo at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/libcephfs/$(DEPDIR)/ceph_test_libcephfs-access.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/libcephfs/$(DEPDIR)/ceph_test_libcephfs-acl.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/libcephfs/$(DEPDIR)/ceph_test_libcephfs-caps.Po at am__quote@
@@ -20116,6 +20179,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote at test/librbd/journal/$(DEPDIR)/librbd_test_la-test_Replay.Plo at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/librbd/journal/$(DEPDIR)/unittest_librbd-test_mock_Replay.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/librbd/mock/$(DEPDIR)/librbd_test_mock_la-MockImageCtx.Plo at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at test/librbd/mock/$(DEPDIR)/librbd_test_mock_la-MockJournal.Plo at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/librbd/object_map/$(DEPDIR)/unittest_librbd-test_mock_InvalidateRequest.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/librbd/object_map/$(DEPDIR)/unittest_librbd-test_mock_LockRequest.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/librbd/object_map/$(DEPDIR)/unittest_librbd-test_mock_RefreshRequest.Po at am__quote@
@@ -20189,12 +20253,13 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote at test/rbd_mirror/$(DEPDIR)/unittest_rbd_mirror-test_mock_ImageReplayer.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/rbd_mirror/$(DEPDIR)/unittest_rbd_mirror-test_mock_ImageSync.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/rbd_mirror/$(DEPDIR)/unittest_rbd_mirror-test_mock_fixture.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at test/rbd_mirror/image_replayer/$(DEPDIR)/unittest_rbd_mirror-test_mock_BootstrapRequest.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/rbd_mirror/image_sync/$(DEPDIR)/unittest_rbd_mirror-test_mock_ImageCopyRequest.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/rbd_mirror/image_sync/$(DEPDIR)/unittest_rbd_mirror-test_mock_ObjectCopyRequest.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/rbd_mirror/image_sync/$(DEPDIR)/unittest_rbd_mirror-test_mock_SnapshotCopyRequest.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at test/rbd_mirror/image_sync/$(DEPDIR)/unittest_rbd_mirror-test_mock_SnapshotCreateRequest.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/rbd_mirror/image_sync/$(DEPDIR)/unittest_rbd_mirror-test_mock_SyncPointCreateRequest.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/rbd_mirror/image_sync/$(DEPDIR)/unittest_rbd_mirror-test_mock_SyncPointPruneRequest.Po at am__quote@
- at AMDEP_TRUE@@am__include@ @am__quote at test/rbd_mirror/mock/$(DEPDIR)/unittest_rbd_mirror-MockJournaler.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/rgw/$(DEPDIR)/ceph_test_rgw_manifest-test_rgw_manifest.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/rgw/$(DEPDIR)/ceph_test_rgw_obj-test_rgw_obj.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/rgw/$(DEPDIR)/ceph_test_rgw_period_history-test_rgw_period_history.Po at am__quote@
@@ -20292,9 +20357,11 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote at tools/rbd_mirror/image_replayer/$(DEPDIR)/BootstrapRequest.Plo at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at tools/rbd_mirror/image_replayer/$(DEPDIR)/CloseImageRequest.Plo at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at tools/rbd_mirror/image_replayer/$(DEPDIR)/OpenLocalImageRequest.Plo at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at tools/rbd_mirror/image_replayer/$(DEPDIR)/ReplayStatusFormatter.Plo at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at tools/rbd_mirror/image_sync/$(DEPDIR)/ImageCopyRequest.Plo at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at tools/rbd_mirror/image_sync/$(DEPDIR)/ObjectCopyRequest.Plo at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at tools/rbd_mirror/image_sync/$(DEPDIR)/SnapshotCopyRequest.Plo at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at tools/rbd_mirror/image_sync/$(DEPDIR)/SnapshotCreateRequest.Plo at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at tools/rbd_mirror/image_sync/$(DEPDIR)/SyncPointCreateRequest.Plo at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at tools/rbd_mirror/image_sync/$(DEPDIR)/SyncPointPruneRequest.Plo at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at tools/rbd_nbd/$(DEPDIR)/rbd_nbd-rbd-nbd.Po at am__quote@
@@ -23948,6 +24015,13 @@ erasure-code/isa/libisa_la-xor_op.lo: erasure-code/isa/xor_op.cc
 @AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
 @am__fastdepCXX_FALSE@	$(AM_V_CXX at am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(libisa_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libisa_la_CXXFLAGS) $(CXXFLAGS) -c -o erasure-code/isa/libisa_la-xor_op.lo `test -f 'erasure-code/isa/xor_op.cc' || echo '$(srcdir)/'`erasure-code/isa/xor_op.cc
 
+test/journal/mock/libjournal_test_mock_la-MockJournaler.lo: test/journal/mock/MockJournaler.cc
+ at am__fastdepCXX_TRUE@	$(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libjournal_test_mock_la_CXXFLAGS) $(CXXFLAGS) -MT test/journal/mock/libjournal_test_mock_la-MockJournaler.lo -MD -MP -MF test/journal/mock/$(DEPDIR)/libjournal_test_mock_la-MockJournaler.Tpo -c -o test/journal/mock/libjournal_test_mock_la-MockJournaler.lo `test -f 'test/journal/mock/MockJournaler.cc' [...]
+ at am__fastdepCXX_TRUE@	$(AM_V_at)$(am__mv) test/journal/mock/$(DEPDIR)/libjournal_test_mock_la-MockJournaler.Tpo test/journal/mock/$(DEPDIR)/libjournal_test_mock_la-MockJournaler.Plo
+ at AMDEP_TRUE@@am__fastdepCXX_FALSE@	$(AM_V_CXX)source='test/journal/mock/MockJournaler.cc' object='test/journal/mock/libjournal_test_mock_la-MockJournaler.lo' libtool=yes @AMDEPBACKSLASH@
+ at AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+ at am__fastdepCXX_FALSE@	$(AM_V_CXX at am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libjournal_test_mock_la_CXXFLAGS) $(CXXFLAGS) -c -o test/journal/mock/libjournal_test_mock_la-MockJournaler.lo `test -f 'test/journal/mock/MockJournaler.cc' || echo '$(srcdir)/'`test/journal/mock/MockJournaler.cc
+
 osd/libosd_types_la-PGLog.lo: osd/PGLog.cc
 @am__fastdepCXX_TRUE@	$(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libosd_types_la_CXXFLAGS) $(CXXFLAGS) -MT osd/libosd_types_la-PGLog.lo -MD -MP -MF osd/$(DEPDIR)/libosd_types_la-PGLog.Tpo -c -o osd/libosd_types_la-PGLog.lo `test -f 'osd/PGLog.cc' || echo '$(srcdir)/'`osd/PGLog.cc
 @am__fastdepCXX_TRUE@	$(AM_V_at)$(am__mv) osd/$(DEPDIR)/libosd_types_la-PGLog.Tpo osd/$(DEPDIR)/libosd_types_la-PGLog.Plo
@@ -24144,6 +24218,13 @@ test/librbd/mock/librbd_test_mock_la-MockImageCtx.lo: test/librbd/mock/MockImage
 @AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
 @am__fastdepCXX_FALSE@	$(AM_V_CXX at am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(librbd_test_mock_la_CXXFLAGS) $(CXXFLAGS) -c -o test/librbd/mock/librbd_test_mock_la-MockImageCtx.lo `test -f 'test/librbd/mock/MockImageCtx.cc' || echo '$(srcdir)/'`test/librbd/mock/MockImageCtx.cc
 
+test/librbd/mock/librbd_test_mock_la-MockJournal.lo: test/librbd/mock/MockJournal.cc
+ at am__fastdepCXX_TRUE@	$(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(librbd_test_mock_la_CXXFLAGS) $(CXXFLAGS) -MT test/librbd/mock/librbd_test_mock_la-MockJournal.lo -MD -MP -MF test/librbd/mock/$(DEPDIR)/librbd_test_mock_la-MockJournal.Tpo -c -o test/librbd/mock/librbd_test_mock_la-MockJournal.lo `test -f 'test/librbd/mock/MockJournal.cc' || echo '$(srcdir)/'`test/l [...]
+ at am__fastdepCXX_TRUE@	$(AM_V_at)$(am__mv) test/librbd/mock/$(DEPDIR)/librbd_test_mock_la-MockJournal.Tpo test/librbd/mock/$(DEPDIR)/librbd_test_mock_la-MockJournal.Plo
+ at AMDEP_TRUE@@am__fastdepCXX_FALSE@	$(AM_V_CXX)source='test/librbd/mock/MockJournal.cc' object='test/librbd/mock/librbd_test_mock_la-MockJournal.lo' libtool=yes @AMDEPBACKSLASH@
+ at AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+ at am__fastdepCXX_FALSE@	$(AM_V_CXX at am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(librbd_test_mock_la_CXXFLAGS) $(CXXFLAGS) -c -o test/librbd/mock/librbd_test_mock_la-MockJournal.lo `test -f 'test/librbd/mock/MockJournal.cc' || echo '$(srcdir)/'`test/librbd/mock/MockJournal.cc
+
 rgw/librgw_la-rgw_acl.lo: rgw/rgw_acl.cc
 @am__fastdepCXX_TRUE@	$(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(librgw_la_CXXFLAGS) $(CXXFLAGS) -MT rgw/librgw_la-rgw_acl.lo -MD -MP -MF rgw/$(DEPDIR)/librgw_la-rgw_acl.Tpo -c -o rgw/librgw_la-rgw_acl.lo `test -f 'rgw/rgw_acl.cc' || echo '$(srcdir)/'`rgw/rgw_acl.cc
 @am__fastdepCXX_TRUE@	$(AM_V_at)$(am__mv) rgw/$(DEPDIR)/librgw_la-rgw_acl.Tpo rgw/$(DEPDIR)/librgw_la-rgw_acl.Plo
@@ -29681,6 +29762,20 @@ test/rbd_mirror/unittest_rbd_mirror-test_mock_ImageSync.obj: test/rbd_mirror/tes
 @AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
 @am__fastdepCXX_FALSE@	$(AM_V_CXX at am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(unittest_rbd_mirror_CXXFLAGS) $(CXXFLAGS) -c -o test/rbd_mirror/unittest_rbd_mirror-test_mock_ImageSync.obj `if test -f 'test/rbd_mirror/test_mock_ImageSync.cc'; then $(CYGPATH_W) 'test/rbd_mirror/test_mock_ImageSync.cc'; else $(CYGPATH_W) '$(srcdir)/test/rbd_mirror/test_mock_ImageSync.cc'; fi`
 
+test/rbd_mirror/image_replayer/unittest_rbd_mirror-test_mock_BootstrapRequest.o: test/rbd_mirror/image_replayer/test_mock_BootstrapRequest.cc
+ at am__fastdepCXX_TRUE@	$(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(unittest_rbd_mirror_CXXFLAGS) $(CXXFLAGS) -MT test/rbd_mirror/image_replayer/unittest_rbd_mirror-test_mock_BootstrapRequest.o -MD -MP -MF test/rbd_mirror/image_replayer/$(DEPDIR)/unittest_rbd_mirror-test_mock_BootstrapRequest.Tpo -c -o test/rbd_mirror/image_replayer/unittest_rbd_mirror-test_mock_BootstrapRequest.o `test -f 'test/rbd_mirror/image_replayer/test_mock_BootstrapRequest [...]
+ at am__fastdepCXX_TRUE@	$(AM_V_at)$(am__mv) test/rbd_mirror/image_replayer/$(DEPDIR)/unittest_rbd_mirror-test_mock_BootstrapRequest.Tpo test/rbd_mirror/image_replayer/$(DEPDIR)/unittest_rbd_mirror-test_mock_BootstrapRequest.Po
+ at AMDEP_TRUE@@am__fastdepCXX_FALSE@	$(AM_V_CXX)source='test/rbd_mirror/image_replayer/test_mock_BootstrapRequest.cc' object='test/rbd_mirror/image_replayer/unittest_rbd_mirror-test_mock_BootstrapRequest.o' libtool=no @AMDEPBACKSLASH@
+ at AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+ at am__fastdepCXX_FALSE@	$(AM_V_CXX at am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(unittest_rbd_mirror_CXXFLAGS) $(CXXFLAGS) -c -o test/rbd_mirror/image_replayer/unittest_rbd_mirror-test_mock_BootstrapRequest.o `test -f 'test/rbd_mirror/image_replayer/test_mock_BootstrapRequest.cc' || echo '$(srcdir)/'`test/rbd_mirror/image_replayer/test_mock_BootstrapRequest.cc
+
+test/rbd_mirror/image_replayer/unittest_rbd_mirror-test_mock_BootstrapRequest.obj: test/rbd_mirror/image_replayer/test_mock_BootstrapRequest.cc
+ at am__fastdepCXX_TRUE@	$(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(unittest_rbd_mirror_CXXFLAGS) $(CXXFLAGS) -MT test/rbd_mirror/image_replayer/unittest_rbd_mirror-test_mock_BootstrapRequest.obj -MD -MP -MF test/rbd_mirror/image_replayer/$(DEPDIR)/unittest_rbd_mirror-test_mock_BootstrapRequest.Tpo -c -o test/rbd_mirror/image_replayer/unittest_rbd_mirror-test_mock_BootstrapRequest.obj `if test -f 'test/rbd_mirror/image_replayer/test_mock_Bootstrap [...]
+ at am__fastdepCXX_TRUE@	$(AM_V_at)$(am__mv) test/rbd_mirror/image_replayer/$(DEPDIR)/unittest_rbd_mirror-test_mock_BootstrapRequest.Tpo test/rbd_mirror/image_replayer/$(DEPDIR)/unittest_rbd_mirror-test_mock_BootstrapRequest.Po
+ at AMDEP_TRUE@@am__fastdepCXX_FALSE@	$(AM_V_CXX)source='test/rbd_mirror/image_replayer/test_mock_BootstrapRequest.cc' object='test/rbd_mirror/image_replayer/unittest_rbd_mirror-test_mock_BootstrapRequest.obj' libtool=no @AMDEPBACKSLASH@
+ at AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+ at am__fastdepCXX_FALSE@	$(AM_V_CXX at am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(unittest_rbd_mirror_CXXFLAGS) $(CXXFLAGS) -c -o test/rbd_mirror/image_replayer/unittest_rbd_mirror-test_mock_BootstrapRequest.obj `if test -f 'test/rbd_mirror/image_replayer/test_mock_BootstrapRequest.cc'; then $(CYGPATH_W) 'test/rbd_mirror/image_replayer/test_mock_BootstrapRequest.cc'; else $(CYGPATH_W) '$(srcdir)/test/rbd_mirror/image_replayer/test_mock_BootstrapRequ [...]
+
 test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_ImageCopyRequest.o: test/rbd_mirror/image_sync/test_mock_ImageCopyRequest.cc
 @am__fastdepCXX_TRUE@	$(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(unittest_rbd_mirror_CXXFLAGS) $(CXXFLAGS) -MT test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_ImageCopyRequest.o -MD -MP -MF test/rbd_mirror/image_sync/$(DEPDIR)/unittest_rbd_mirror-test_mock_ImageCopyRequest.Tpo -c -o test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_ImageCopyRequest.o `test -f 'test/rbd_mirror/image_sync/test_mock_ImageCopyRequest.cc' || echo '$( [...]
 @am__fastdepCXX_TRUE@	$(AM_V_at)$(am__mv) test/rbd_mirror/image_sync/$(DEPDIR)/unittest_rbd_mirror-test_mock_ImageCopyRequest.Tpo test/rbd_mirror/image_sync/$(DEPDIR)/unittest_rbd_mirror-test_mock_ImageCopyRequest.Po
@@ -29723,6 +29818,20 @@ test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_SnapshotCopyRequest.obj
 @AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
 @am__fastdepCXX_FALSE@	$(AM_V_CXX at am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(unittest_rbd_mirror_CXXFLAGS) $(CXXFLAGS) -c -o test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_SnapshotCopyRequest.obj `if test -f 'test/rbd_mirror/image_sync/test_mock_SnapshotCopyRequest.cc'; then $(CYGPATH_W) 'test/rbd_mirror/image_sync/test_mock_SnapshotCopyRequest.cc'; else $(CYGPATH_W) '$(srcdir)/test/rbd_mirror/image_sync/test_mock_SnapshotCopyRequest. [...]
 
+test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_SnapshotCreateRequest.o: test/rbd_mirror/image_sync/test_mock_SnapshotCreateRequest.cc
+ at am__fastdepCXX_TRUE@	$(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(unittest_rbd_mirror_CXXFLAGS) $(CXXFLAGS) -MT test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_SnapshotCreateRequest.o -MD -MP -MF test/rbd_mirror/image_sync/$(DEPDIR)/unittest_rbd_mirror-test_mock_SnapshotCreateRequest.Tpo -c -o test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_SnapshotCreateRequest.o `test -f 'test/rbd_mirror/image_sync/test_mock_SnapshotCreateReq [...]
+ at am__fastdepCXX_TRUE@	$(AM_V_at)$(am__mv) test/rbd_mirror/image_sync/$(DEPDIR)/unittest_rbd_mirror-test_mock_SnapshotCreateRequest.Tpo test/rbd_mirror/image_sync/$(DEPDIR)/unittest_rbd_mirror-test_mock_SnapshotCreateRequest.Po
+ at AMDEP_TRUE@@am__fastdepCXX_FALSE@	$(AM_V_CXX)source='test/rbd_mirror/image_sync/test_mock_SnapshotCreateRequest.cc' object='test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_SnapshotCreateRequest.o' libtool=no @AMDEPBACKSLASH@
+ at AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+ at am__fastdepCXX_FALSE@	$(AM_V_CXX at am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(unittest_rbd_mirror_CXXFLAGS) $(CXXFLAGS) -c -o test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_SnapshotCreateRequest.o `test -f 'test/rbd_mirror/image_sync/test_mock_SnapshotCreateRequest.cc' || echo '$(srcdir)/'`test/rbd_mirror/image_sync/test_mock_SnapshotCreateRequest.cc
+
+test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_SnapshotCreateRequest.obj: test/rbd_mirror/image_sync/test_mock_SnapshotCreateRequest.cc
+ at am__fastdepCXX_TRUE@	$(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(unittest_rbd_mirror_CXXFLAGS) $(CXXFLAGS) -MT test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_SnapshotCreateRequest.obj -MD -MP -MF test/rbd_mirror/image_sync/$(DEPDIR)/unittest_rbd_mirror-test_mock_SnapshotCreateRequest.Tpo -c -o test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_SnapshotCreateRequest.obj `if test -f 'test/rbd_mirror/image_sync/test_mock_SnapshotCr [...]
+ at am__fastdepCXX_TRUE@	$(AM_V_at)$(am__mv) test/rbd_mirror/image_sync/$(DEPDIR)/unittest_rbd_mirror-test_mock_SnapshotCreateRequest.Tpo test/rbd_mirror/image_sync/$(DEPDIR)/unittest_rbd_mirror-test_mock_SnapshotCreateRequest.Po
+ at AMDEP_TRUE@@am__fastdepCXX_FALSE@	$(AM_V_CXX)source='test/rbd_mirror/image_sync/test_mock_SnapshotCreateRequest.cc' object='test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_SnapshotCreateRequest.obj' libtool=no @AMDEPBACKSLASH@
+ at AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+ at am__fastdepCXX_FALSE@	$(AM_V_CXX at am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(unittest_rbd_mirror_CXXFLAGS) $(CXXFLAGS) -c -o test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_SnapshotCreateRequest.obj `if test -f 'test/rbd_mirror/image_sync/test_mock_SnapshotCreateRequest.cc'; then $(CYGPATH_W) 'test/rbd_mirror/image_sync/test_mock_SnapshotCreateRequest.cc'; else $(CYGPATH_W) '$(srcdir)/test/rbd_mirror/image_sync/test_mock_SnapshotCreate [...]
+
 test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_SyncPointCreateRequest.o: test/rbd_mirror/image_sync/test_mock_SyncPointCreateRequest.cc
 @am__fastdepCXX_TRUE@	$(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(unittest_rbd_mirror_CXXFLAGS) $(CXXFLAGS) -MT test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_SyncPointCreateRequest.o -MD -MP -MF test/rbd_mirror/image_sync/$(DEPDIR)/unittest_rbd_mirror-test_mock_SyncPointCreateRequest.Tpo -c -o test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_SyncPointCreateRequest.o `test -f 'test/rbd_mirror/image_sync/test_mock_SyncPointCreat [...]
 @am__fastdepCXX_TRUE@	$(AM_V_at)$(am__mv) test/rbd_mirror/image_sync/$(DEPDIR)/unittest_rbd_mirror-test_mock_SyncPointCreateRequest.Tpo test/rbd_mirror/image_sync/$(DEPDIR)/unittest_rbd_mirror-test_mock_SyncPointCreateRequest.Po
@@ -29751,20 +29860,6 @@ test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_SyncPointPruneRequest.o
 @AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
 @am__fastdepCXX_FALSE@	$(AM_V_CXX at am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(unittest_rbd_mirror_CXXFLAGS) $(CXXFLAGS) -c -o test/rbd_mirror/image_sync/unittest_rbd_mirror-test_mock_SyncPointPruneRequest.obj `if test -f 'test/rbd_mirror/image_sync/test_mock_SyncPointPruneRequest.cc'; then $(CYGPATH_W) 'test/rbd_mirror/image_sync/test_mock_SyncPointPruneRequest.cc'; else $(CYGPATH_W) '$(srcdir)/test/rbd_mirror/image_sync/test_mock_SyncPointPrune [...]
 
-test/rbd_mirror/mock/unittest_rbd_mirror-MockJournaler.o: test/rbd_mirror/mock/MockJournaler.cc
- at am__fastdepCXX_TRUE@	$(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(unittest_rbd_mirror_CXXFLAGS) $(CXXFLAGS) -MT test/rbd_mirror/mock/unittest_rbd_mirror-MockJournaler.o -MD -MP -MF test/rbd_mirror/mock/$(DEPDIR)/unittest_rbd_mirror-MockJournaler.Tpo -c -o test/rbd_mirror/mock/unittest_rbd_mirror-MockJournaler.o `test -f 'test/rbd_mirror/mock/MockJournaler.cc' || echo '$(srcdir)/'`test/rbd_mirror/mock/MockJournaler.cc
- at am__fastdepCXX_TRUE@	$(AM_V_at)$(am__mv) test/rbd_mirror/mock/$(DEPDIR)/unittest_rbd_mirror-MockJournaler.Tpo test/rbd_mirror/mock/$(DEPDIR)/unittest_rbd_mirror-MockJournaler.Po
- at AMDEP_TRUE@@am__fastdepCXX_FALSE@	$(AM_V_CXX)source='test/rbd_mirror/mock/MockJournaler.cc' object='test/rbd_mirror/mock/unittest_rbd_mirror-MockJournaler.o' libtool=no @AMDEPBACKSLASH@
- at AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
- at am__fastdepCXX_FALSE@	$(AM_V_CXX at am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(unittest_rbd_mirror_CXXFLAGS) $(CXXFLAGS) -c -o test/rbd_mirror/mock/unittest_rbd_mirror-MockJournaler.o `test -f 'test/rbd_mirror/mock/MockJournaler.cc' || echo '$(srcdir)/'`test/rbd_mirror/mock/MockJournaler.cc
-
-test/rbd_mirror/mock/unittest_rbd_mirror-MockJournaler.obj: test/rbd_mirror/mock/MockJournaler.cc
- at am__fastdepCXX_TRUE@	$(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(unittest_rbd_mirror_CXXFLAGS) $(CXXFLAGS) -MT test/rbd_mirror/mock/unittest_rbd_mirror-MockJournaler.obj -MD -MP -MF test/rbd_mirror/mock/$(DEPDIR)/unittest_rbd_mirror-MockJournaler.Tpo -c -o test/rbd_mirror/mock/unittest_rbd_mirror-MockJournaler.obj `if test -f 'test/rbd_mirror/mock/MockJournaler.cc'; then $(CYGPATH_W) 'test/rbd_mirror/mock/MockJournaler.cc'; else $(CYGPATH_W) '$ [...]
- at am__fastdepCXX_TRUE@	$(AM_V_at)$(am__mv) test/rbd_mirror/mock/$(DEPDIR)/unittest_rbd_mirror-MockJournaler.Tpo test/rbd_mirror/mock/$(DEPDIR)/unittest_rbd_mirror-MockJournaler.Po
- at AMDEP_TRUE@@am__fastdepCXX_FALSE@	$(AM_V_CXX)source='test/rbd_mirror/mock/MockJournaler.cc' object='test/rbd_mirror/mock/unittest_rbd_mirror-MockJournaler.obj' libtool=no @AMDEPBACKSLASH@
- at AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
- at am__fastdepCXX_FALSE@	$(AM_V_CXX at am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(unittest_rbd_mirror_CXXFLAGS) $(CXXFLAGS) -c -o test/rbd_mirror/mock/unittest_rbd_mirror-MockJournaler.obj `if test -f 'test/rbd_mirror/mock/MockJournaler.cc'; then $(CYGPATH_W) 'test/rbd_mirror/mock/MockJournaler.cc'; else $(CYGPATH_W) '$(srcdir)/test/rbd_mirror/mock/MockJournaler.cc'; fi`
-
 test/unittest_rbd_replay-test_rbd_replay.o: test/test_rbd_replay.cc
 @am__fastdepCXX_TRUE@	$(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(unittest_rbd_replay_CXXFLAGS) $(CXXFLAGS) -MT test/unittest_rbd_replay-test_rbd_replay.o -MD -MP -MF test/$(DEPDIR)/unittest_rbd_replay-test_rbd_replay.Tpo -c -o test/unittest_rbd_replay-test_rbd_replay.o `test -f 'test/test_rbd_replay.cc' || echo '$(srcdir)/'`test/test_rbd_replay.cc
 @am__fastdepCXX_TRUE@	$(AM_V_at)$(am__mv) test/$(DEPDIR)/unittest_rbd_replay-test_rbd_replay.Tpo test/$(DEPDIR)/unittest_rbd_replay-test_rbd_replay.Po
@@ -30418,6 +30513,7 @@ clean-libtool:
 	-rm -rf rgw/.libs rgw/_libs
 	-rm -rf test/compressor/.libs test/compressor/_libs
 	-rm -rf test/erasure-code/.libs test/erasure-code/_libs
+	-rm -rf test/journal/mock/.libs test/journal/mock/_libs
 	-rm -rf test/librados/.libs test/librados/_libs
 	-rm -rf test/librados_test_stub/.libs test/librados_test_stub/_libs
 	-rm -rf test/libradosstriper/.libs test/libradosstriper/_libs
@@ -32160,6 +32256,8 @@ distclean-generic:
 	-rm -f test/fs/$(am__dirstamp)
 	-rm -f test/journal/$(DEPDIR)/$(am__dirstamp)
 	-rm -f test/journal/$(am__dirstamp)
+	-rm -f test/journal/mock/$(DEPDIR)/$(am__dirstamp)
+	-rm -f test/journal/mock/$(am__dirstamp)
 	-rm -f test/libcephfs/$(DEPDIR)/$(am__dirstamp)
 	-rm -f test/libcephfs/$(am__dirstamp)
 	-rm -f test/librados/$(DEPDIR)/$(am__dirstamp)
@@ -32200,10 +32298,10 @@ distclean-generic:
 	-rm -f test/osdc/$(am__dirstamp)
 	-rm -f test/rbd_mirror/$(DEPDIR)/$(am__dirstamp)
 	-rm -f test/rbd_mirror/$(am__dirstamp)
+	-rm -f test/rbd_mirror/image_replayer/$(DEPDIR)/$(am__dirstamp)
+	-rm -f test/rbd_mirror/image_replayer/$(am__dirstamp)
 	-rm -f test/rbd_mirror/image_sync/$(DEPDIR)/$(am__dirstamp)
 	-rm -f test/rbd_mirror/image_sync/$(am__dirstamp)
-	-rm -f test/rbd_mirror/mock/$(DEPDIR)/$(am__dirstamp)
-	-rm -f test/rbd_mirror/mock/$(am__dirstamp)
 	-rm -f test/rgw/$(DEPDIR)/$(am__dirstamp)
 	-rm -f test/rgw/$(am__dirstamp)
 	-rm -f test/system/$(DEPDIR)/$(am__dirstamp)
@@ -32246,7 +32344,7 @@ clean-am: clean-binPROGRAMS clean-checkPROGRAMS \
 	clean-sbinPROGRAMS clean-su_sbinPROGRAMS mostlyclean-am
 
 distclean: distclean-recursive
-	-rm -rf ./$(DEPDIR) arch/$(DEPDIR) auth/$(DEPDIR) auth/cephx/$(DEPDIR) auth/none/$(DEPDIR) auth/unknown/$(DEPDIR) civetweb/src/$(DEPDIR) client/$(DEPDIR) cls/cephfs/$(DEPDIR) cls/hello/$(DEPDIR) cls/journal/$(DEPDIR) cls/lock/$(DEPDIR) cls/log/$(DEPDIR) cls/numops/$(DEPDIR) cls/rbd/$(DEPDIR) cls/refcount/$(DEPDIR) cls/replica_log/$(DEPDIR) cls/rgw/$(DEPDIR) cls/statelog/$(DEPDIR) cls/timeindex/$(DEPDIR) cls/user/$(DEPDIR) cls/version/$(DEPDIR) common/$(DEPDIR) compressor/$(DEPDIR) compr [...]
+	-rm -rf ./$(DEPDIR) arch/$(DEPDIR) auth/$(DEPDIR) auth/cephx/$(DEPDIR) auth/none/$(DEPDIR) auth/unknown/$(DEPDIR) civetweb/src/$(DEPDIR) client/$(DEPDIR) cls/cephfs/$(DEPDIR) cls/hello/$(DEPDIR) cls/journal/$(DEPDIR) cls/lock/$(DEPDIR) cls/log/$(DEPDIR) cls/numops/$(DEPDIR) cls/rbd/$(DEPDIR) cls/refcount/$(DEPDIR) cls/replica_log/$(DEPDIR) cls/rgw/$(DEPDIR) cls/statelog/$(DEPDIR) cls/timeindex/$(DEPDIR) cls/user/$(DEPDIR) cls/version/$(DEPDIR) common/$(DEPDIR) compressor/$(DEPDIR) compr [...]
 	-rm -f Makefile
 distclean-am: clean-am distclean-compile distclean-generic \
 	distclean-hdr distclean-tags
@@ -32304,7 +32402,7 @@ install-ps-am:
 installcheck-am:
 
 maintainer-clean: maintainer-clean-recursive
-	-rm -rf ./$(DEPDIR) arch/$(DEPDIR) auth/$(DEPDIR) auth/cephx/$(DEPDIR) auth/none/$(DEPDIR) auth/unknown/$(DEPDIR) civetweb/src/$(DEPDIR) client/$(DEPDIR) cls/cephfs/$(DEPDIR) cls/hello/$(DEPDIR) cls/journal/$(DEPDIR) cls/lock/$(DEPDIR) cls/log/$(DEPDIR) cls/numops/$(DEPDIR) cls/rbd/$(DEPDIR) cls/refcount/$(DEPDIR) cls/replica_log/$(DEPDIR) cls/rgw/$(DEPDIR) cls/statelog/$(DEPDIR) cls/timeindex/$(DEPDIR) cls/user/$(DEPDIR) cls/version/$(DEPDIR) common/$(DEPDIR) compressor/$(DEPDIR) compr [...]
+	-rm -rf ./$(DEPDIR) arch/$(DEPDIR) auth/$(DEPDIR) auth/cephx/$(DEPDIR) auth/none/$(DEPDIR) auth/unknown/$(DEPDIR) civetweb/src/$(DEPDIR) client/$(DEPDIR) cls/cephfs/$(DEPDIR) cls/hello/$(DEPDIR) cls/journal/$(DEPDIR) cls/lock/$(DEPDIR) cls/log/$(DEPDIR) cls/numops/$(DEPDIR) cls/rbd/$(DEPDIR) cls/refcount/$(DEPDIR) cls/replica_log/$(DEPDIR) cls/rgw/$(DEPDIR) cls/statelog/$(DEPDIR) cls/timeindex/$(DEPDIR) cls/user/$(DEPDIR) cls/version/$(DEPDIR) common/$(DEPDIR) compressor/$(DEPDIR) compr [...]
 	-rm -f Makefile
 maintainer-clean-am: distclean-am maintainer-clean-generic
 
diff --git a/src/ceph_mds.cc b/src/ceph_mds.cc
index fda5a37..efe22e6 100644
--- a/src/ceph_mds.cc
+++ b/src/ceph_mds.cc
@@ -97,10 +97,6 @@ int main(int argc, const char **argv)
 	      0, "mds_data");
   ceph_heap_profiler_init();
 
-  // mds specific args
-  MDSMap::DaemonState shadow = MDSMap::STATE_NULL;
-  std::string dump_file;
-
   std::string val, action;
   for (std::vector<const char*>::iterator i = args.begin(); i != args.end(); ) {
     if (ceph_argparse_double_dash(args, i)) {
@@ -110,30 +106,13 @@ int main(int argc, const char **argv)
       usage();
       break;
     }
-    else if (ceph_argparse_witharg(args, i, &val, "--journal-check", (char*)NULL)) {
-      int r = parse_rank("journal-check", val);
-      if (shadow != MDSMap::STATE_NULL) {
-        dout(0) << "Error: can only select one standby state" << dendl;
-        return -1;
-      }
-      dout(0) << "requesting oneshot_replay for mds." << r << dendl;
-      shadow = MDSMap::STATE_ONESHOT_REPLAY;
-      char rb[32];
-      snprintf(rb, sizeof(rb), "%d", r);
-      g_conf->set_val("mds_standby_for_rank", rb);
-      g_conf->apply_changes(NULL);
-    }
     else if (ceph_argparse_witharg(args, i, &val, "--hot-standby", (char*)NULL)) {
       int r = parse_rank("hot-standby", val);
-      if (shadow != MDSMap::STATE_NULL) {
-        dout(0) << "Error: can only select one standby state" << dendl;
-        return -1;
-      }
       dout(0) << "requesting standby_replay for mds." << r << dendl;
-      shadow = MDSMap::STATE_STANDBY_REPLAY;
       char rb[32];
       snprintf(rb, sizeof(rb), "%d", r);
       g_conf->set_val("mds_standby_for_rank", rb);
+      g_conf->set_val("mds_standby_replay", "true");
       g_conf->apply_changes(NULL);
     }
     else {
@@ -193,8 +172,7 @@ int main(int argc, const char **argv)
   if (r < 0)
     exit(1);
 
-  if (shadow != MDSMap::STATE_ONESHOT_REPLAY)
-    global_init_daemonize(g_ceph_context);
+  global_init_daemonize(g_ceph_context);
   common_init_finish(g_ceph_context);
 
   // get monmap
@@ -212,10 +190,7 @@ int main(int argc, const char **argv)
   mds->orig_argc = argc;
   mds->orig_argv = argv;
 
-  if (shadow != MDSMap::STATE_NULL)
-    r = mds->init(shadow);
-  else
-    r = mds->init();
+  r = mds->init();
   if (r < 0) {
     msgr->wait();
     goto shutdown;
diff --git a/src/cls/rbd/cls_rbd.cc b/src/cls/rbd/cls_rbd.cc
index ad7d6b1..f56e4e0 100644
--- a/src/cls/rbd/cls_rbd.cc
+++ b/src/cls/rbd/cls_rbd.cc
@@ -40,6 +40,7 @@
 #include "common/bit_vector.hpp"
 #include "common/errno.h"
 #include "objclass/objclass.h"
+#include "osd/osd_types.h"
 #include "include/rbd_types.h"
 #include "include/rbd/object_map_types.h"
 
@@ -125,13 +126,18 @@ cls_method_handle_t h_mirror_image_get_image_id;
 cls_method_handle_t h_mirror_image_get;
 cls_method_handle_t h_mirror_image_set;
 cls_method_handle_t h_mirror_image_remove;
+cls_method_handle_t h_mirror_image_status_set;
+cls_method_handle_t h_mirror_image_status_remove;
+cls_method_handle_t h_mirror_image_status_get;
+cls_method_handle_t h_mirror_image_status_list;
+cls_method_handle_t h_mirror_image_status_get_summary;
+cls_method_handle_t h_mirror_image_status_remove_down;
 
 #define RBD_MAX_KEYS_READ 64
 #define RBD_SNAP_KEY_PREFIX "snapshot_"
 #define RBD_DIR_ID_KEY_PREFIX "id_"
 #define RBD_DIR_NAME_KEY_PREFIX "name_"
 #define RBD_METADATA_KEY_PREFIX "metadata_"
-#define RBD_MAX_OBJECT_MAP_OBJECT_COUNT 256000000
 
 static int snap_read_header(cls_method_context_t hctx, bufferlist& bl)
 {
@@ -2261,7 +2267,7 @@ int object_map_resize(cls_method_context_t hctx, bufferlist *in, bufferlist *out
   }
 
   // protect against excessive memory requirements
-  if (object_count > RBD_MAX_OBJECT_MAP_OBJECT_COUNT) {
+  if (object_count > cls::rbd::MAX_OBJECT_MAP_OBJECT_COUNT) {
     CLS_ERR("object map too large: %" PRIu64, object_count);
     return -EINVAL;
   }
@@ -2965,6 +2971,7 @@ static const std::string MODE("mirror_mode");
 static const std::string PEER_KEY_PREFIX("mirror_peer_");
 static const std::string IMAGE_KEY_PREFIX("image_");
 static const std::string GLOBAL_KEY_PREFIX("global_");
+static const std::string STATUS_GLOBAL_KEY_PREFIX("status_global_");
 
 std::string peer_key(const std::string &uuid) {
   return PEER_KEY_PREFIX + uuid;
@@ -2978,6 +2985,10 @@ std::string global_key(const string &global_id) {
   return GLOBAL_KEY_PREFIX + global_id;
 }
 
+std::string status_global_key(const string &global_id) {
+  return STATUS_GLOBAL_KEY_PREFIX + global_id;
+}
+
 int uuid_get(cls_method_context_t hctx, std::string *mirror_uuid) {
   bufferlist mirror_uuid_bl;
   int r = cls_cxx_map_get_val(hctx, mirror::UUID, &mirror_uuid_bl);
@@ -3162,6 +3173,296 @@ int image_remove(cls_method_context_t hctx, const string &image_id) {
   return 0;
 }
 
+struct MirrorImageStatusOnDisk : cls::rbd::MirrorImageStatus {
+  entity_inst_t origin;
+
+  MirrorImageStatusOnDisk() {
+  }
+  MirrorImageStatusOnDisk(const cls::rbd::MirrorImageStatus &status) :
+    cls::rbd::MirrorImageStatus(status) {
+  }
+
+  void encode_meta(bufferlist &bl) const {
+    ENCODE_START(1, 1, bl);
+    ::encode(origin, bl);
+    ENCODE_FINISH(bl);
+  }
+
+  void encode(bufferlist &bl) const {
+    encode_meta(bl);
+    cls::rbd::MirrorImageStatus::encode(bl);
+  }
+
+  void decode_meta(bufferlist::iterator &it) {
+    DECODE_START(1, it);
+    ::decode(origin, it);
+    DECODE_FINISH(it);
+  }
+
+  void decode(bufferlist::iterator &it) {
+    decode_meta(it);
+    cls::rbd::MirrorImageStatus::decode(it);
+  }
+};
+WRITE_CLASS_ENCODER(MirrorImageStatusOnDisk)
+
+int image_status_set(cls_method_context_t hctx, const string &global_image_id,
+		     const cls::rbd::MirrorImageStatus &status) {
+  MirrorImageStatusOnDisk ondisk_status(status);
+  ondisk_status.up = false;
+  ondisk_status.last_update = ceph_clock_now(g_ceph_context);
+
+  int r = cls_get_request_origin(hctx, &ondisk_status.origin);
+  assert(r == 0);
+
+  bufferlist bl;
+  encode(ondisk_status, bl);
+
+  r = cls_cxx_map_set_val(hctx, status_global_key(global_image_id), &bl);
+  if (r < 0) {
+    CLS_ERR("error setting status for mirrored image, global id '%s': %s",
+	    global_image_id.c_str(), cpp_strerror(r).c_str());
+    return r;
+  }
+  return 0;
+}
+
+int image_status_remove(cls_method_context_t hctx,
+			const string &global_image_id) {
+
+  int r = cls_cxx_map_remove_key(hctx, status_global_key(global_image_id));
+  if (r < 0) {
+    CLS_ERR("error removing status for mirrored image, global id '%s': %s",
+	    global_image_id.c_str(), cpp_strerror(r).c_str());
+    return r;
+  }
+  return 0;
+}
+
+int image_status_get(cls_method_context_t hctx, const string &global_image_id,
+		     cls::rbd::MirrorImageStatus *status) {
+
+  bufferlist bl;
+  int r = cls_cxx_map_get_val(hctx, status_global_key(global_image_id), &bl);
+  if (r < 0) {
+    if (r != -ENOENT) {
+      CLS_ERR("error reading status for mirrored image, global id '%s': '%s'",
+	      global_image_id.c_str(), cpp_strerror(r).c_str());
+    }
+    return r;
+  }
+
+  MirrorImageStatusOnDisk ondisk_status;
+  try {
+    bufferlist::iterator it = bl.begin();
+    decode(ondisk_status, it);
+  } catch (const buffer::error &err) {
+    CLS_ERR("could not decode status for mirrored image, global id '%s'",
+	    global_image_id.c_str());
+    return -EIO;
+  }
+
+  obj_list_watch_response_t watchers;
+  r = cls_cxx_list_watchers(hctx, &watchers);
+  if (r < 0 && r != -ENOENT) {
+    CLS_ERR("error listing watchers: '%s'", cpp_strerror(r).c_str());
+    return r;
+  }
+
+  *status = static_cast<cls::rbd::MirrorImageStatus>(ondisk_status);
+  status->up = false;
+  for (auto &w : watchers.entries) {
+    if (w.name == ondisk_status.origin.name &&
+	w.addr == ondisk_status.origin.addr) {
+      status->up = true;
+      break;
+    }
+  }
+
+  return 0;
+}
+
+int image_status_list(cls_method_context_t hctx,
+	const std::string &start_after, uint64_t max_return,
+	map<std::string, cls::rbd::MirrorImage> *mirror_images,
+        map<std::string, cls::rbd::MirrorImageStatus> *mirror_statuses) {
+  std::string last_read = image_key(start_after);
+  int max_read = RBD_MAX_KEYS_READ;
+  int r = max_read;
+
+  while (r == max_read && mirror_images->size() < max_return) {
+    std::map<std::string, bufferlist> vals;
+    CLS_LOG(20, "last_read = '%s'", last_read.c_str());
+    r = cls_cxx_map_get_vals(hctx, last_read, IMAGE_KEY_PREFIX, max_read,
+			     &vals);
+    if (r < 0) {
+      CLS_ERR("error reading mirror image directory by name: %s",
+              cpp_strerror(r).c_str());
+      return r;
+    }
+
+    for (auto it = vals.begin(); it != vals.end() &&
+	   mirror_images->size() < max_return; ++it) {
+      const std::string &image_id = it->first.substr(IMAGE_KEY_PREFIX.size());
+      cls::rbd::MirrorImage mirror_image;
+      bufferlist::iterator iter = it->second.begin();
+      try {
+	::decode(mirror_image, iter);
+      } catch (const buffer::error &err) {
+	CLS_ERR("could not decode mirror image payload of image '%s'",
+                image_id.c_str());
+	return -EIO;
+      }
+
+      (*mirror_images)[image_id] = mirror_image;
+
+      cls::rbd::MirrorImageStatus status;
+      r = image_status_get(hctx, mirror_image.global_image_id, &status);
+      if (r < 0) {
+	continue;
+      }
+
+      (*mirror_statuses)[image_id] = status;
+    }
+    if (!vals.empty()) {
+      last_read = image_key(mirror_images->rbegin()->first);
+    }
+  }
+
+  return 0;
+}
+
+int image_status_get_summary(cls_method_context_t hctx,
+	std::map<cls::rbd::MirrorImageStatusState, int> *states) {
+  obj_list_watch_response_t watchers_;
+  int r = cls_cxx_list_watchers(hctx, &watchers_);
+  if (r < 0) {
+    if (r != -ENOENT) {
+      CLS_ERR("error listing watchers: '%s'", cpp_strerror(r).c_str());
+    }
+    return r;
+  }
+
+  set<entity_inst_t> watchers;
+  for (auto &w : watchers_.entries) {
+    watchers.insert(entity_inst_t(w.name, w.addr));
+  }
+
+  states->clear();
+
+  string last_read = IMAGE_KEY_PREFIX;
+  int max_read = RBD_MAX_KEYS_READ;
+  r = max_read;
+  while (r == max_read) {
+    map<string, bufferlist> vals;
+    r = cls_cxx_map_get_vals(hctx, last_read, IMAGE_KEY_PREFIX,
+			     max_read, &vals);
+    if (r < 0) {
+      CLS_ERR("error reading mirrored images: %s", cpp_strerror(r).c_str());
+      return r;
+    }
+
+    for (auto &list_it : vals) {
+      const string &key = list_it.first;
+
+      if (0 != key.compare(0, IMAGE_KEY_PREFIX.size(), IMAGE_KEY_PREFIX)) {
+	break;
+      }
+
+      cls::rbd::MirrorImage mirror_image;
+      bufferlist::iterator iter = list_it.second.begin();
+      try {
+	::decode(mirror_image, iter);
+      } catch (const buffer::error &err) {
+	CLS_ERR("could not decode mirror image payload for key '%s'",
+                key.c_str());
+	return -EIO;
+      }
+
+      cls::rbd::MirrorImageStatus status;
+      r = image_status_get(hctx, mirror_image.global_image_id, &status);
+      if (r < 0) {
+	// Ignore.
+      }
+
+      cls::rbd::MirrorImageStatusState state = status.up ? status.state :
+	cls::rbd::MIRROR_IMAGE_STATUS_STATE_UNKNOWN;
+      (*states)[state]++;
+    }
+
+    if (!vals.empty()) {
+      last_read = vals.rbegin()->first;
+    }
+  }
+
+  return 0;
+}
+
+int image_status_remove_down(cls_method_context_t hctx) {
+  obj_list_watch_response_t watchers_;
+  int r = cls_cxx_list_watchers(hctx, &watchers_);
+  if (r < 0) {
+    if (r != -ENOENT) {
+      CLS_ERR("error listing watchers: '%s'", cpp_strerror(r).c_str());
+    }
+    return r;
+  }
+
+  set<entity_inst_t> watchers;
+  for (auto &w : watchers_.entries) {
+    watchers.insert(entity_inst_t(w.name, w.addr));
+  }
+
+  string last_read = STATUS_GLOBAL_KEY_PREFIX;
+  int max_read = RBD_MAX_KEYS_READ;
+  r = max_read;
+  while (r == max_read) {
+    map<string, bufferlist> vals;
+    r = cls_cxx_map_get_vals(hctx, last_read, STATUS_GLOBAL_KEY_PREFIX,
+			     max_read, &vals);
+    if (r < 0) {
+      CLS_ERR("error reading mirrored images: %s", cpp_strerror(r).c_str());
+      return r;
+    }
+
+    for (auto &list_it : vals) {
+      const string &key = list_it.first;
+
+      if (0 != key.compare(0, STATUS_GLOBAL_KEY_PREFIX.size(),
+			   STATUS_GLOBAL_KEY_PREFIX)) {
+	break;
+      }
+
+      MirrorImageStatusOnDisk status;
+      try {
+	bufferlist::iterator it = list_it.second.begin();
+	status.decode_meta(it);
+      } catch (const buffer::error &err) {
+	CLS_ERR("could not decode status metadata for mirrored image '%s'",
+		key.c_str());
+	return -EIO;
+      }
+
+      if (watchers.find(status.origin) == watchers.end()) {
+	CLS_LOG(20, "removing stale status object for key %s",
+		key.c_str());
+	int r1 = cls_cxx_map_remove_key(hctx, key);
+	if (r1 < 0) {
+	  CLS_ERR("error removing stale status for key '%s': %s",
+		  key.c_str(), cpp_strerror(r1).c_str());
+	  return r1;
+	}
+      }
+    }
+
+    if (!vals.empty()) {
+      last_read = vals.rbegin()->first;
+    }
+  }
+
+  return 0;
+}
+
 } // namespace mirror
 
 /**
@@ -3676,6 +3977,158 @@ int mirror_image_remove(cls_method_context_t hctx, bufferlist *in,
   return 0;
 }
 
+/**
+ * Input:
+ * @param global_image_id (std::string)
+ * @param status (cls::rbd::MirrorImageStatus)
+ *
+ * Output:
+ * @returns 0 on success, negative error code on failure
+ */
+int mirror_image_status_set(cls_method_context_t hctx, bufferlist *in,
+			    bufferlist *out) {
+  string global_image_id;
+  cls::rbd::MirrorImageStatus status;
+  try {
+    bufferlist::iterator it = in->begin();
+    ::decode(global_image_id, it);
+    ::decode(status, it);
+  } catch (const buffer::error &err) {
+    return -EINVAL;
+  }
+
+  int r = mirror::image_status_set(hctx, global_image_id, status);
+  if (r < 0) {
+    return r;
+  }
+  return 0;
+}
+
+/**
+ * Input:
+ * @param global_image_id (std::string)
+ *
+ * Output:
+ * @returns 0 on success, negative error code on failure
+ */
+int mirror_image_status_remove(cls_method_context_t hctx, bufferlist *in,
+			       bufferlist *out) {
+  string global_image_id;
+  try {
+    bufferlist::iterator it = in->begin();
+    ::decode(global_image_id, it);
+  } catch (const buffer::error &err) {
+    return -EINVAL;
+  }
+
+  int r = mirror::image_status_remove(hctx, global_image_id);
+  if (r < 0) {
+    return r;
+  }
+  return 0;
+}
+
+/**
+ * Input:
+ * @param global_image_id (std::string)
+ *
+ * Output:
+ * @param cls::rbd::MirrorImageStatus - metadata associated with the global_image_id
+ * @returns 0 on success, negative error code on failure
+ */
+int mirror_image_status_get(cls_method_context_t hctx, bufferlist *in,
+			    bufferlist *out) {
+  string global_image_id;
+  try {
+    bufferlist::iterator it = in->begin();
+    ::decode(global_image_id, it);
+  } catch (const buffer::error &err) {
+    return -EINVAL;
+  }
+
+  cls::rbd::MirrorImageStatus status;
+  int r = mirror::image_status_get(hctx, global_image_id, &status);
+  if (r < 0) {
+    return r;
+  }
+
+  ::encode(status, *out);
+  return 0;
+}
+
+/**
+ * Input:
+ * @param start_after which name to begin listing after
+ *        (use the empty string to start at the beginning)
+ * @param max_return the maximum number of names to list
+ *
+ * Output:
+ * @param std::map<std::string, cls::rbd::MirrorImage>: image id to image map
+ * @param std::map<std::string, cls::rbd::MirrorImageStatus>: image it to status map
+ * @returns 0 on success, negative error code on failure
+ */
+int mirror_image_status_list(cls_method_context_t hctx, bufferlist *in,
+			     bufferlist *out) {
+  std::string start_after;
+  uint64_t max_return;
+  try {
+    bufferlist::iterator iter = in->begin();
+    ::decode(start_after, iter);
+    ::decode(max_return, iter);
+  } catch (const buffer::error &err) {
+    return -EINVAL;
+  }
+
+  map<std::string, cls::rbd::MirrorImage> images;
+  map<std::string, cls::rbd::MirrorImageStatus> statuses;
+  int r = mirror::image_status_list(hctx, start_after, max_return, &images,
+				    &statuses);
+  if (r < 0) {
+    return r;
+  }
+
+  ::encode(images, *out);
+  ::encode(statuses, *out);
+  return 0;
+}
+
+/**
+ * Input:
+ * none
+ *
+ * Output:
+ * @param std::map<cls::rbd::MirrorImageStatusState, int>: states counts
+ * @returns 0 on success, negative error code on failure
+ */
+int mirror_image_status_get_summary(cls_method_context_t hctx, bufferlist *in,
+				    bufferlist *out) {
+  std::map<cls::rbd::MirrorImageStatusState, int> states;
+
+  int r = mirror::image_status_get_summary(hctx, &states);
+  if (r < 0) {
+    return r;
+  }
+
+  ::encode(states, *out);
+  return 0;
+}
+
+/**
+ * Input:
+ * none
+ *
+ * Output:
+ * @returns 0 on success, negative error code on failure
+ */
+int mirror_image_status_remove_down(cls_method_context_t hctx, bufferlist *in,
+				    bufferlist *out) {
+  int r = mirror::image_status_remove_down(hctx);
+  if (r < 0) {
+    return r;
+  }
+  return 0;
+}
+
 void __cls_init()
 {
   CLS_LOG(20, "Loaded rbd class!");
@@ -3871,5 +4324,24 @@ void __cls_init()
   cls_register_cxx_method(h_class, "mirror_image_remove",
                           CLS_METHOD_RD | CLS_METHOD_WR,
                           mirror_image_remove, &h_mirror_image_remove);
+  cls_register_cxx_method(h_class, "mirror_image_status_set",
+                          CLS_METHOD_RD | CLS_METHOD_WR | CLS_METHOD_PROMOTE,
+                          mirror_image_status_set, &h_mirror_image_status_set);
+  cls_register_cxx_method(h_class, "mirror_image_status_remove",
+                          CLS_METHOD_RD | CLS_METHOD_WR,
+                          mirror_image_status_remove,
+			  &h_mirror_image_status_remove);
+  cls_register_cxx_method(h_class, "mirror_image_status_get", CLS_METHOD_RD,
+                          mirror_image_status_get, &h_mirror_image_status_get);
+  cls_register_cxx_method(h_class, "mirror_image_status_list", CLS_METHOD_RD,
+                          mirror_image_status_list,
+			  &h_mirror_image_status_list);
+  cls_register_cxx_method(h_class, "mirror_image_status_get_summary",
+			  CLS_METHOD_RD, mirror_image_status_get_summary,
+			  &h_mirror_image_status_get_summary);
+  cls_register_cxx_method(h_class, "mirror_image_status_remove_down",
+                          CLS_METHOD_RD | CLS_METHOD_WR,
+                          mirror_image_status_remove_down,
+			  &h_mirror_image_status_remove_down);
   return;
 }
diff --git a/src/cls/rbd/cls_rbd_client.cc b/src/cls/rbd/cls_rbd_client.cc
index 5fe8452..ec57f7f 100644
--- a/src/cls/rbd/cls_rbd_client.cc
+++ b/src/cls/rbd/cls_rbd_client.cc
@@ -1236,5 +1236,163 @@ namespace librbd {
       return 0;
     }
 
+    int mirror_image_status_set(librados::IoCtx *ioctx,
+				const std::string &global_image_id,
+				const cls::rbd::MirrorImageStatus &status) {
+      librados::ObjectWriteOperation op;
+      mirror_image_status_set(&op, global_image_id, status);
+      return ioctx->operate(RBD_MIRRORING, &op);
+    }
+
+    void mirror_image_status_set(librados::ObjectWriteOperation *op,
+				 const std::string &global_image_id,
+				 const cls::rbd::MirrorImageStatus &status) {
+      bufferlist bl;
+      ::encode(global_image_id, bl);
+      ::encode(status, bl);
+      op->exec("rbd", "mirror_image_status_set", bl);
+    }
+
+    int mirror_image_status_remove(librados::IoCtx *ioctx,
+				   const std::string &global_image_id) {
+      librados::ObjectWriteOperation op;
+      mirror_image_status_remove(&op, global_image_id);
+      return ioctx->operate(RBD_MIRRORING, &op);
+    }
+
+    void mirror_image_status_remove(librados::ObjectWriteOperation *op,
+				    const std::string &global_image_id) {
+      bufferlist bl;
+      ::encode(global_image_id, bl);
+      op->exec("rbd", "mirror_image_status_remove", bl);
+    }
+
+    int mirror_image_status_get(librados::IoCtx *ioctx,
+				const std::string &global_image_id,
+				cls::rbd::MirrorImageStatus *status) {
+      librados::ObjectReadOperation op;
+      mirror_image_status_get_start(&op, global_image_id);
+
+      bufferlist out_bl;
+      int r = ioctx->operate(RBD_MIRRORING, &op, &out_bl);
+      if (r < 0) {
+	return r;
+      }
+
+      bufferlist::iterator iter = out_bl.begin();
+      r = mirror_image_status_get_finish(&iter, status);
+      if (r < 0) {
+	return r;
+      }
+      return 0;
+    }
+
+    void mirror_image_status_get_start(librados::ObjectReadOperation *op,
+				       const std::string &global_image_id) {
+      bufferlist bl;
+      ::encode(global_image_id, bl);
+      op->exec("rbd", "mirror_image_status_get", bl);
+    }
+
+    int mirror_image_status_get_finish(bufferlist::iterator *iter,
+				       cls::rbd::MirrorImageStatus *status) {
+      try {
+	::decode(*status, *iter);
+      } catch (const buffer::error &err) {
+	return -EBADMSG;
+      }
+      return 0;
+    }
+
+    int mirror_image_status_list(librados::IoCtx *ioctx,
+	const std::string &start, uint64_t max_return,
+	std::map<std::string, cls::rbd::MirrorImage> *images,
+	std::map<std::string, cls::rbd::MirrorImageStatus> *statuses) {
+      librados::ObjectReadOperation op;
+      mirror_image_status_list_start(&op, start, max_return);
+
+      bufferlist out_bl;
+      int r = ioctx->operate(RBD_MIRRORING, &op, &out_bl);
+      if (r < 0) {
+	return r;
+      }
+
+      bufferlist::iterator iter = out_bl.begin();
+      r = mirror_image_status_list_finish(&iter, images, statuses);
+      if (r < 0) {
+	return r;
+      }
+      return 0;
+    }
+
+    void mirror_image_status_list_start(librados::ObjectReadOperation *op,
+					const std::string &start,
+					uint64_t max_return) {
+      bufferlist bl;
+      ::encode(start, bl);
+      ::encode(max_return, bl);
+      op->exec("rbd", "mirror_image_status_list", bl);
+    }
+
+    int mirror_image_status_list_finish(bufferlist::iterator *iter,
+	std::map<std::string, cls::rbd::MirrorImage> *images,
+	std::map<std::string, cls::rbd::MirrorImageStatus> *statuses) {
+      images->clear();
+      statuses->clear();
+      try {
+	::decode(*images, *iter);
+	::decode(*statuses, *iter);
+      } catch (const buffer::error &err) {
+	return -EBADMSG;
+      }
+      return 0;
+    }
+
+    int mirror_image_status_get_summary(librados::IoCtx *ioctx,
+	std::map<cls::rbd::MirrorImageStatusState, int> *states) {
+      librados::ObjectReadOperation op;
+      mirror_image_status_get_summary_start(&op);
+
+      bufferlist out_bl;
+      int r = ioctx->operate(RBD_MIRRORING, &op, &out_bl);
+      if (r < 0) {
+	return r;
+      }
+
+      bufferlist::iterator iter = out_bl.begin();
+      r = mirror_image_status_get_summary_finish(&iter, states);
+      if (r < 0) {
+	return r;
+      }
+      return 0;
+    }
+
+    void mirror_image_status_get_summary_start(
+      librados::ObjectReadOperation *op) {
+      bufferlist bl;
+      op->exec("rbd", "mirror_image_status_get_summary", bl);
+    }
+
+    int mirror_image_status_get_summary_finish(bufferlist::iterator *iter,
+	std::map<cls::rbd::MirrorImageStatusState, int> *states) {
+      try {
+	::decode(*states, *iter);
+      } catch (const buffer::error &err) {
+	return -EBADMSG;
+      }
+      return 0;
+    }
+
+    int mirror_image_status_remove_down(librados::IoCtx *ioctx) {
+      librados::ObjectWriteOperation op;
+      mirror_image_status_remove_down(&op);
+      return ioctx->operate(RBD_MIRRORING, &op);
+    }
+
+    void mirror_image_status_remove_down(librados::ObjectWriteOperation *op) {
+      bufferlist bl;
+      op->exec("rbd", "mirror_image_status_remove_down", bl);
+    }
+
   } // namespace cls_client
 } // namespace librbd
diff --git a/src/cls/rbd/cls_rbd_client.h b/src/cls/rbd/cls_rbd_client.h
index 8bc8ee2..b3dd22e 100644
--- a/src/cls/rbd/cls_rbd_client.h
+++ b/src/cls/rbd/cls_rbd_client.h
@@ -243,6 +243,40 @@ namespace librbd {
 			 const cls::rbd::MirrorImage &mirror_image);
     int mirror_image_remove(librados::IoCtx *ioctx,
 			    const std::string &image_id);
+    int mirror_image_status_set(librados::IoCtx *ioctx,
+				const std::string &global_image_id,
+				const cls::rbd::MirrorImageStatus &status);
+    void mirror_image_status_set(librados::ObjectWriteOperation *op,
+				 const std::string &global_image_id,
+				 const cls::rbd::MirrorImageStatus &status);
+    int mirror_image_status_remove(librados::IoCtx *ioctx,
+				   const std::string &global_image_id);
+    void mirror_image_status_remove(librados::ObjectWriteOperation *op,
+				    const std::string &global_image_id);
+    int mirror_image_status_get(librados::IoCtx *ioctx,
+				const std::string &global_image_id,
+				cls::rbd::MirrorImageStatus *status);
+    void mirror_image_status_get_start(librados::ObjectReadOperation *op,
+				       const std::string &global_image_id);
+    int mirror_image_status_get_finish(bufferlist::iterator *iter,
+				       cls::rbd::MirrorImageStatus *status);
+    int mirror_image_status_list(librados::IoCtx *ioctx,
+	const std::string &start, uint64_t max_return,
+	std::map<std::string, cls::rbd::MirrorImage> *images,
+	std::map<std::string, cls::rbd::MirrorImageStatus> *statuses);
+    void mirror_image_status_list_start(librados::ObjectReadOperation *op,
+					const std::string &start,
+					uint64_t max_return);
+    int mirror_image_status_list_finish(bufferlist::iterator *iter,
+	std::map<std::string, cls::rbd::MirrorImage> *images,
+	std::map<std::string, cls::rbd::MirrorImageStatus> *statuses);
+    int mirror_image_status_get_summary(librados::IoCtx *ioctx,
+	std::map<cls::rbd::MirrorImageStatusState, int> *states);
+    void mirror_image_status_get_summary_start(librados::ObjectReadOperation *op);
+    int mirror_image_status_get_summary_finish(bufferlist::iterator *iter,
+	std::map<cls::rbd::MirrorImageStatusState, int> *states);
+    int mirror_image_status_remove_down(librados::IoCtx *ioctx);
+    void mirror_image_status_remove_down(librados::ObjectWriteOperation *op);
 
   } // namespace cls_client
 } // namespace librbd
diff --git a/src/cls/rbd/cls_rbd_types.cc b/src/cls/rbd/cls_rbd_types.cc
index 3f68522..5891e56 100644
--- a/src/cls/rbd/cls_rbd_types.cc
+++ b/src/cls/rbd/cls_rbd_types.cc
@@ -105,6 +105,11 @@ bool MirrorImage::operator==(const MirrorImage &rhs) const {
   return global_image_id == rhs.global_image_id && state == rhs.state;
 }
 
+bool MirrorImage::operator<(const MirrorImage &rhs) const {
+  return global_image_id < rhs.global_image_id ||
+	(global_image_id == rhs.global_image_id  && state < rhs.state);
+}
+
 std::ostream& operator<<(std::ostream& os, const MirrorImageState& mirror_state) {
   switch (mirror_state) {
   case MIRROR_IMAGE_STATE_DISABLING:
@@ -127,5 +132,84 @@ std::ostream& operator<<(std::ostream& os, const MirrorImage& mirror_image) {
   return os;
 }
 
+void MirrorImageStatus::encode(bufferlist &bl) const {
+  ENCODE_START(1, 1, bl);
+  ::encode(state, bl);
+  ::encode(description, bl);
+  ::encode(last_update, bl);
+  ::encode(up, bl);
+  ENCODE_FINISH(bl);
+}
+
+void MirrorImageStatus::decode(bufferlist::iterator &it) {
+  DECODE_START(1, it);
+  ::decode(state, it);
+  ::decode(description, it);
+  ::decode(last_update, it);
+  ::decode(up, it);
+  DECODE_FINISH(it);
+}
+
+void MirrorImageStatus::dump(Formatter *f) const {
+  f->dump_string("state", state_to_string());
+  f->dump_string("description", description);
+  f->dump_stream("last_update") << last_update;
+}
+
+std::string MirrorImageStatus::state_to_string() const {
+  std::stringstream ss;
+  ss << (up ? "up+" : "down+") << state;
+  return ss.str();
+}
+
+void MirrorImageStatus::generate_test_instances(
+  std::list<MirrorImageStatus*> &o) {
+  o.push_back(new MirrorImageStatus());
+  o.push_back(new MirrorImageStatus(MIRROR_IMAGE_STATUS_STATE_REPLAYING));
+  o.push_back(new MirrorImageStatus(MIRROR_IMAGE_STATUS_STATE_ERROR, "error"));
+}
+
+bool MirrorImageStatus::operator==(const MirrorImageStatus &rhs) const {
+  return state == rhs.state && description == rhs.description && up == rhs.up;
+}
+
+std::ostream& operator<<(std::ostream& os, const MirrorImageStatusState& state) {
+  switch (state) {
+  case MIRROR_IMAGE_STATUS_STATE_UNKNOWN:
+    os << "unknown";
+    break;
+  case MIRROR_IMAGE_STATUS_STATE_ERROR:
+    os << "error";
+    break;
+  case MIRROR_IMAGE_STATUS_STATE_SYNCING:
+    os << "syncing";
+    break;
+  case MIRROR_IMAGE_STATUS_STATE_STARTING_REPLAY:
+    os << "starting_replay";
+    break;
+  case MIRROR_IMAGE_STATUS_STATE_REPLAYING:
+    os << "replaying";
+    break;
+  case MIRROR_IMAGE_STATUS_STATE_STOPPING_REPLAY:
+    os << "stopping_replay";
+    break;
+  case MIRROR_IMAGE_STATUS_STATE_STOPPED:
+    os << "stopped";
+    break;
+  default:
+    os << "unknown (" << static_cast<uint32_t>(state) << ")";
+    break;
+  }
+  return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const MirrorImageStatus& status) {
+  os << "["
+     << "state=" << status.state_to_string() << ", "
+     << "description=" << status.description << ", "
+     << "last_update=" << status.last_update << "]";
+  return os;
+}
+
 } // namespace rbd
 } // namespace cls
diff --git a/src/cls/rbd/cls_rbd_types.h b/src/cls/rbd/cls_rbd_types.h
index e318914..f98a942 100644
--- a/src/cls/rbd/cls_rbd_types.h
+++ b/src/cls/rbd/cls_rbd_types.h
@@ -7,6 +7,7 @@
 #include "include/int_types.h"
 #include "include/buffer.h"
 #include "include/encoding.h"
+#include "include/utime.h"
 #include <iosfwd>
 #include <string>
 
@@ -15,6 +16,8 @@ namespace ceph { class Formatter; }
 namespace cls {
 namespace rbd {
 
+static const uint32_t MAX_OBJECT_MAP_OBJECT_COUNT = 256000000;
+
 enum MirrorMode {
   MIRROR_MODE_DISABLED = 0,
   MIRROR_MODE_IMAGE    = 1,
@@ -73,6 +76,7 @@ struct MirrorImage {
   static void generate_test_instances(std::list<MirrorImage*> &o);
 
   bool operator==(const MirrorImage &rhs) const;
+  bool operator<(const MirrorImage &rhs) const;
 };
 
 std::ostream& operator<<(std::ostream& os, const MirrorImageState& mirror_state);
@@ -80,6 +84,56 @@ std::ostream& operator<<(std::ostream& os, const MirrorImage& mirror_image);
 
 WRITE_CLASS_ENCODER(MirrorImage);
 
+enum MirrorImageStatusState {
+  MIRROR_IMAGE_STATUS_STATE_UNKNOWN         = 0,
+  MIRROR_IMAGE_STATUS_STATE_ERROR           = 1,
+  MIRROR_IMAGE_STATUS_STATE_SYNCING         = 2,
+  MIRROR_IMAGE_STATUS_STATE_STARTING_REPLAY = 3,
+  MIRROR_IMAGE_STATUS_STATE_REPLAYING       = 4,
+  MIRROR_IMAGE_STATUS_STATE_STOPPING_REPLAY = 5,
+  MIRROR_IMAGE_STATUS_STATE_STOPPED         = 6,
+};
+
+inline void encode(const MirrorImageStatusState &state, bufferlist& bl,
+		   uint64_t features=0)
+{
+  ::encode(static_cast<uint8_t>(state), bl);
+}
+
+inline void decode(MirrorImageStatusState &state, bufferlist::iterator& it)
+{
+  uint8_t int_state;
+  ::decode(int_state, it);
+  state = static_cast<MirrorImageStatusState>(int_state);
+}
+
+struct MirrorImageStatus {
+  MirrorImageStatus() {}
+  MirrorImageStatus(MirrorImageStatusState state,
+		    const std::string &description = "")
+    : state(state), description(description) {}
+
+  MirrorImageStatusState state = MIRROR_IMAGE_STATUS_STATE_UNKNOWN;
+  std::string description;
+  utime_t last_update;
+  bool up = false;
+
+  void encode(bufferlist &bl) const;
+  void decode(bufferlist::iterator &it);
+  void dump(Formatter *f) const;
+
+  std::string state_to_string() const;
+
+  static void generate_test_instances(std::list<MirrorImageStatus*> &o);
+
+  bool operator==(const MirrorImageStatus &rhs) const;
+};
+
+std::ostream& operator<<(std::ostream& os, const MirrorImageStatus& status);
+std::ostream& operator<<(std::ostream& os, const MirrorImageStatusState& state);
+
+WRITE_CLASS_ENCODER(MirrorImageStatus);
+
 } // namespace rbd
 } // namespace cls
 
diff --git a/src/cls/rgw/cls_rgw.cc b/src/cls/rgw/cls_rgw.cc
index f3432da..a29ad7b 100644
--- a/src/cls/rgw/cls_rgw.cc
+++ b/src/cls/rgw/cls_rgw.cc
@@ -870,6 +870,7 @@ int rgw_bucket_complete_op(cls_method_context_t hctx, bufferlist *in, bufferlist
   entry.ver = op.ver;
   switch ((int)op.op) {
   case CLS_RGW_OP_DEL:
+    entry.meta = op.meta;
     if (ondisk) {
       if (!entry.pending_map.size()) {
 	int ret = cls_cxx_map_remove_key(hctx, idx);
diff --git a/src/common/Readahead.cc b/src/common/Readahead.cc
index 55f74db..34f37da 100644
--- a/src/common/Readahead.cc
+++ b/src/common/Readahead.cc
@@ -29,7 +29,7 @@ Readahead::extent_t Readahead::update(const vector<extent_t>& extents, uint64_t
   for (vector<extent_t>::const_iterator p = extents.begin(); p != extents.end(); ++p) {
     _observe_read(p->first, p->second);
   }
-  if (m_readahead_pos >= limit) {
+  if (m_readahead_pos >= limit|| m_last_pos >= limit) {
     m_lock.Unlock();
     return extent_t(0, 0);
   }
@@ -41,7 +41,7 @@ Readahead::extent_t Readahead::update(const vector<extent_t>& extents, uint64_t
 Readahead::extent_t Readahead::update(uint64_t offset, uint64_t length, uint64_t limit) {
   m_lock.Lock();
   _observe_read(offset, length);
-  if (m_readahead_pos >= limit) {
+  if (m_readahead_pos >= limit || m_last_pos >= limit) {
     m_lock.Unlock();
     return extent_t(0, 0);
   }
diff --git a/src/include/ceph_fs.h b/src/include/ceph_fs.h
index fe0a8d5..70f83e3 100644
--- a/src/include/ceph_fs.h
+++ b/src/include/ceph_fs.h
@@ -253,7 +253,7 @@ struct ceph_mon_subscribe_ack {
 #define CEPH_MDS_STATE_CREATING    -6  /* up, creating MDS instance. */
 #define CEPH_MDS_STATE_STARTING    -7  /* up, starting previously stopped mds */
 #define CEPH_MDS_STATE_STANDBY_REPLAY -8 /* up, tailing active node's journal */
-#define CEPH_MDS_STATE_REPLAYONCE   -9 /* up, replaying an active node's journal */
+#define CEPH_MDS_STATE_REPLAYONCE   -9 /* Legacy, unused */
 #define CEPH_MDS_STATE_NULL         -10
 
 #define CEPH_MDS_STATE_REPLAY       8  /* up, replaying journal. */
diff --git a/src/include/rbd/librbd.h b/src/include/rbd/librbd.h
index 49ed98e..fb61b8f 100644
--- a/src/include/rbd/librbd.h
+++ b/src/include/rbd/librbd.h
@@ -113,6 +113,25 @@ typedef struct {
   bool primary;
 } rbd_mirror_image_info_t;
 
+typedef enum {
+  MIRROR_IMAGE_STATUS_STATE_UNKNOWN         = 0,
+  MIRROR_IMAGE_STATUS_STATE_ERROR           = 1,
+  MIRROR_IMAGE_STATUS_STATE_SYNCING         = 2,
+  MIRROR_IMAGE_STATUS_STATE_STARTING_REPLAY = 3,
+  MIRROR_IMAGE_STATUS_STATE_REPLAYING       = 4,
+  MIRROR_IMAGE_STATUS_STATE_STOPPING_REPLAY = 5,
+  MIRROR_IMAGE_STATUS_STATE_STOPPED         = 6,
+} rbd_mirror_image_status_state_t;
+
+typedef struct {
+  char *name;
+  rbd_mirror_image_info_t info;
+  rbd_mirror_image_status_state_t state;
+  char *description;
+  time_t last_update;
+  bool up;
+} rbd_mirror_image_status_t;
+
 CEPH_RBD_API void rbd_version(int *major, int *minor, int *extra);
 
 /* image options */
@@ -138,6 +157,8 @@ CEPH_RBD_API int rbd_image_options_get_string(rbd_image_options_t opts,
 					      size_t maxlen);
 CEPH_RBD_API int rbd_image_options_get_uint64(rbd_image_options_t opts,
 					      int optname, uint64_t* optval);
+CEPH_RBD_API int rbd_image_options_is_set(rbd_image_options_t opts,
+                                          int optname, bool* is_set);
 CEPH_RBD_API int rbd_image_options_unset(rbd_image_options_t opts, int optname);
 CEPH_RBD_API void rbd_image_options_clear(rbd_image_options_t opts);
 CEPH_RBD_API int rbd_image_options_is_empty(rbd_image_options_t opts);
@@ -208,6 +229,15 @@ CEPH_RBD_API int rbd_mirror_peer_set_client(rados_ioctx_t io_ctx,
 CEPH_RBD_API int rbd_mirror_peer_set_cluster(rados_ioctx_t io_ctx,
                                              const char *uuid,
                                              const char *cluster_name);
+CEPH_RBD_API int rbd_mirror_image_status_list(rados_ioctx_t io_ctx,
+					      const char *start_id, size_t max,
+					      char **image_ids,
+					      rbd_mirror_image_status_t *images,
+					      size_t *len);
+CEPH_RBD_API void rbd_mirror_image_status_list_cleanup(char **image_ids,
+    rbd_mirror_image_status_t *images, size_t len);
+CEPH_RBD_API int rbd_mirror_image_status_summary(rados_ioctx_t io_ctx,
+    rbd_mirror_image_status_state_t *states, int *counts, size_t *maxlen);
 
 CEPH_RBD_API int rbd_open(rados_ioctx_t io, const char *name,
                           rbd_image_t *image, const char *snap_name);
@@ -607,6 +637,9 @@ CEPH_RBD_API int rbd_mirror_image_resync(rbd_image_t image);
 CEPH_RBD_API int rbd_mirror_image_get_info(rbd_image_t image,
                                            rbd_mirror_image_info_t *mirror_image_info,
                                            size_t info_size);
+CEPH_RBD_API int rbd_mirror_image_get_status(rbd_image_t image,
+                                             rbd_mirror_image_status_t *mirror_image_status,
+                                             size_t status_size);
 
 #ifdef __cplusplus
 }
diff --git a/src/include/rbd/librbd.hpp b/src/include/rbd/librbd.hpp
index f328c42..e4c434a 100644
--- a/src/include/rbd/librbd.hpp
+++ b/src/include/rbd/librbd.hpp
@@ -60,6 +60,17 @@ namespace librbd {
     bool primary;
   } mirror_image_info_t;
 
+  typedef rbd_mirror_image_status_state_t mirror_image_status_state_t;
+
+  typedef struct {
+    std::string name;
+    mirror_image_info_t info;
+    mirror_image_status_state_t state;
+    std::string description;
+    time_t last_update;
+    bool up;
+  } mirror_image_status_t;
+
   typedef rbd_image_info_t image_info_t;
 
   class CEPH_RBD_API ProgressContext
@@ -132,6 +143,10 @@ public:
                              const std::string &client_name);
   int mirror_peer_set_cluster(IoCtx& io_ctx, const std::string &uuid,
                               const std::string &cluster_name);
+  int mirror_image_status_list(IoCtx& io_ctx, const std::string &start_id,
+      size_t max, std::map<std::string, mirror_image_status_t> *images);
+  int mirror_image_status_summary(IoCtx& io_ctx,
+      std::map<mirror_image_status_state_t, int> *states);
 
 private:
   /* We don't allow assignment or copying */
@@ -149,6 +164,7 @@ public:
   int set(int optname, uint64_t optval);
   int get(int optname, std::string* optval) const;
   int get(int optname, uint64_t* optval) const;
+  int is_set(int optname, bool* is_set);
   int unset(int optname);
   void clear();
   bool empty() const;
@@ -337,6 +353,8 @@ public:
   int mirror_image_resync();
   int mirror_image_get_info(mirror_image_info_t *mirror_image_info,
                             size_t info_size);
+  int mirror_image_get_status(mirror_image_status_t *mirror_image_status,
+			      size_t status_size);
 
 private:
   friend class RBD;
diff --git a/src/init-ceph.in b/src/init-ceph.in
index 8b6d314..e74fe74 100755
--- a/src/init-ceph.in
+++ b/src/init-ceph.in
@@ -38,11 +38,10 @@ else
     ASSUME_DEV=0
 fi
 
-if [ -n $CEPH_BIN ] && [ -n $CEPH_ROOT ] && [ -n $CEPH_BUILD_DIR ]; then
-  #need second look at all variables, especially ETCDIR
+if [ -n "$CEPH_BIN" ] && [ -n "$CEPH_ROOT" ] && [ -n "$CEPH_BUILD_DIR" ]; then
   BINDIR=$CEPH_BIN
   SBINDIR=$CEPH_ROOT/src
-  ETCDIR=$CEPH_BUILD_DIR
+  ETCDIR=$CEPH_BIN
   LIBEXECDIR=$CEPH_ROOT/src
   SYSTEMD_RUN=""
   ASSUME_DEV=1
diff --git a/src/journal/Entry.cc b/src/journal/Entry.cc
index af6c699..f88dea8 100644
--- a/src/journal/Entry.cc
+++ b/src/journal/Entry.cc
@@ -16,22 +16,27 @@ namespace journal {
 namespace {
 
 const uint32_t HEADER_FIXED_SIZE = 25; /// preamble, version, entry tid, tag id
+const uint32_t REMAINDER_FIXED_SIZE = 8; /// data size, crc
 
 } // anonymous namespace
 
+uint32_t Entry::get_fixed_size() {
+  return HEADER_FIXED_SIZE + REMAINDER_FIXED_SIZE;
+}
+
 void Entry::encode(bufferlist &bl) const {
   bufferlist data_bl;
   ::encode(preamble, data_bl);
   ::encode(static_cast<uint8_t>(1), data_bl);
   ::encode(m_entry_tid, data_bl);
   ::encode(m_tag_tid, data_bl);
-  assert(HEADER_FIXED_SIZE == data_bl.length());
-
   ::encode(m_data, data_bl);
 
   uint32_t crc = data_bl.crc32c(0);
+  uint32_t bl_offset = bl.length();
   bl.claim_append(data_bl);
   ::encode(crc, bl);
+  assert(get_fixed_size() + m_data.length() + bl_offset == bl.length());
 }
 
 void Entry::decode(bufferlist::iterator &iter) {
diff --git a/src/journal/Entry.h b/src/journal/Entry.h
index 3d0a0d0..7867a19 100644
--- a/src/journal/Entry.h
+++ b/src/journal/Entry.h
@@ -24,6 +24,8 @@ public:
   {
   }
 
+  static uint32_t get_fixed_size();
+
   inline uint64_t get_tag_tid() const {
     return m_tag_tid;
   }
diff --git a/src/journal/JournalMetadata.h b/src/journal/JournalMetadata.h
index d95f1ed..c9e6d93 100644
--- a/src/journal/JournalMetadata.h
+++ b/src/journal/JournalMetadata.h
@@ -83,6 +83,9 @@ public:
   inline uint8_t get_order() const {
     return m_order;
   }
+  inline uint64_t get_object_size() const {
+    return 1 << m_order;
+  }
   inline uint8_t get_splay_width() const {
     return m_splay_width;
   }
diff --git a/src/journal/JournalPlayer.cc b/src/journal/JournalPlayer.cc
index aee8716..2f1d96d 100644
--- a/src/journal/JournalPlayer.cc
+++ b/src/journal/JournalPlayer.cc
@@ -154,12 +154,13 @@ bool JournalPlayer::try_pop_front(Entry *entry, uint64_t *commit_tid) {
   ldout(m_cct, 20) << __func__ << dendl;
   Mutex::Locker locker(m_lock);
 
-  m_handler_notified = false;
   if (m_state != STATE_PLAYBACK) {
+    m_handler_notified = false;
     return false;
   }
 
   if (!is_object_set_ready()) {
+    m_handler_notified = false;
     return false;
   }
 
@@ -167,6 +168,7 @@ bool JournalPlayer::try_pop_front(Entry *entry, uint64_t *commit_tid) {
     if (!m_watch_enabled) {
       notify_complete(0);
     } else if (!m_watch_scheduled) {
+      m_handler_notified = false;
       schedule_watch();
     }
     return false;
@@ -396,7 +398,6 @@ bool JournalPlayer::verify_playback_ready() {
   // set of data before advancing to a new tag
   if (m_watch_enabled && m_watch_required) {
     m_watch_required = false;
-    schedule_watch();
     return false;
   }
 
@@ -410,7 +411,7 @@ bool JournalPlayer::verify_playback_ready() {
         entry.get_entry_tid() == 0) {
       uint8_t splay_width = m_journal_metadata->get_splay_width();
       m_active_tag_tid = entry.get_tag_tid();
-      m_splay_offset = object_player->get_object_number() / splay_width;
+      m_splay_offset = object_player->get_object_number() % splay_width;
 
       ldout(m_cct, 20) << __func__ << ": new tag " << entry.get_tag_tid() << " "
                        << "detected, adjusting offset to "
diff --git a/src/journal/JournalRecorder.cc b/src/journal/JournalRecorder.cc
index b730b26..f78f0c8 100644
--- a/src/journal/JournalRecorder.cc
+++ b/src/journal/JournalRecorder.cc
@@ -71,6 +71,7 @@ JournalRecorder::~JournalRecorder() {
 
 Future JournalRecorder::append(uint64_t tag_tid,
                                const bufferlist &payload_bl) {
+
   Mutex::Locker locker(m_lock);
 
   uint64_t entry_tid = m_journal_metadata->allocate_entry_tid(tag_tid);
@@ -87,6 +88,7 @@ Future JournalRecorder::append(uint64_t tag_tid,
   bufferlist entry_bl;
   ::encode(Entry(future->get_tag_tid(), future->get_entry_tid(), payload_bl),
            entry_bl);
+  assert(entry_bl.length() <= m_journal_metadata->get_object_size());
 
   AppendBuffers append_buffers;
   append_buffers.push_back(std::make_pair(future, entry_bl));
diff --git a/src/journal/Journaler.cc b/src/journal/Journaler.cc
index 0981db8..2a02f60 100644
--- a/src/journal/Journaler.cc
+++ b/src/journal/Journaler.cc
@@ -349,6 +349,10 @@ void Journaler::stop_append(Context *on_safe) {
   m_recorder = NULL;
 }
 
+uint64_t Journaler::get_max_append_size() const {
+  return m_metadata->get_object_size() - Entry::get_fixed_size();
+}
+
 Future Journaler::append(uint64_t tag_tid, const bufferlist &payload_bl) {
   return m_recorder->append(tag_tid, payload_bl);
 }
diff --git a/src/journal/Journaler.h b/src/journal/Journaler.h
index 055b8bf..f74f7ab 100644
--- a/src/journal/Journaler.h
+++ b/src/journal/Journaler.h
@@ -96,6 +96,7 @@ public:
   bool try_pop_front(ReplayEntry *replay_entry, uint64_t *tag_tid = nullptr);
   void stop_replay();
 
+  uint64_t get_max_append_size() const;
   void start_append(int flush_interval, uint64_t flush_bytes, double flush_age);
   Future append(uint64_t tag_tid, const bufferlist &bl);
   void flush_append(Context *on_safe);
diff --git a/src/librbd/AioImageRequest.cc b/src/librbd/AioImageRequest.cc
index e7cfd39..5a1904b 100644
--- a/src/librbd/AioImageRequest.cc
+++ b/src/librbd/AioImageRequest.cc
@@ -318,11 +318,9 @@ uint64_t AioImageWrite::append_journal_event(
   bufferlist bl;
   bl.append(m_buf, m_len);
 
-  journal::EventEntry event_entry(journal::AioWriteEvent(m_off, m_len, bl));
-  uint64_t tid = m_image_ctx.journal->append_io_event(m_aio_comp,
-                                                      std::move(event_entry),
-                                                      requests, m_off, m_len,
-                                                      synchronous);
+  uint64_t tid = m_image_ctx.journal->append_write_event(m_aio_comp, m_off,
+                                                         m_len, bl, requests,
+                                                         synchronous);
   if (m_image_ctx.object_cacher == NULL) {
     m_aio_comp->associate_journal_event(tid);
   }
diff --git a/src/librbd/AioImageRequestWQ.cc b/src/librbd/AioImageRequestWQ.cc
index 97c6fa4..5ed3e2e 100644
--- a/src/librbd/AioImageRequestWQ.cc
+++ b/src/librbd/AioImageRequestWQ.cc
@@ -231,10 +231,10 @@ bool AioImageRequestWQ::is_lock_request_needed() const {
           (m_require_lock_on_read && m_queued_reads.read() > 0));
 }
 
-void AioImageRequestWQ::block_writes() {
+int AioImageRequestWQ::block_writes() {
   C_SaferCond cond_ctx;
   block_writes(&cond_ctx);
-  cond_ctx.wait();
+  return cond_ctx.wait();
 }
 
 void AioImageRequestWQ::block_writes(Context *on_blocked) {
diff --git a/src/librbd/AioImageRequestWQ.h b/src/librbd/AioImageRequestWQ.h
index e21aa86..74b8438 100644
--- a/src/librbd/AioImageRequestWQ.h
+++ b/src/librbd/AioImageRequestWQ.h
@@ -46,7 +46,7 @@ public:
     return (m_write_blockers > 0);
   }
 
-  void block_writes();
+  int block_writes();
   void block_writes(Context *on_blocked);
   void unblock_writes();
 
diff --git a/src/librbd/ExclusiveLock.cc b/src/librbd/ExclusiveLock.cc
index 01043e5..932fe04 100644
--- a/src/librbd/ExclusiveLock.cc
+++ b/src/librbd/ExclusiveLock.cc
@@ -78,13 +78,33 @@ template <typename I>
 bool ExclusiveLock<I>::accept_requests() const {
   Mutex::Locker locker(m_lock);
 
-  bool accept_requests = (!is_shutdown() && m_state == STATE_LOCKED);
+  bool accept_requests = (!is_shutdown() && m_state == STATE_LOCKED &&
+                          m_request_blockers == 0);
   ldout(m_image_ctx.cct, 20) << this << " " << __func__ << "="
                              << accept_requests << dendl;
   return accept_requests;
 }
 
 template <typename I>
+void ExclusiveLock<I>::block_requests() {
+  Mutex::Locker locker(m_lock);
+  ++m_request_blockers;
+
+  ldout(m_image_ctx.cct, 20) << this << " " << __func__ << "="
+                             << m_request_blockers << dendl;
+}
+
+template <typename I>
+void ExclusiveLock<I>::unblock_requests() {
+  Mutex::Locker locker(m_lock);
+  assert(m_request_blockers > 0);
+  --m_request_blockers;
+
+  ldout(m_image_ctx.cct, 20) << this << " " << __func__ << "="
+                             << m_request_blockers << dendl;
+}
+
+template <typename I>
 void ExclusiveLock<I>::init(uint64_t features, Context *on_init) {
   assert(m_image_ctx.owner_lock.is_locked());
   ldout(m_image_ctx.cct, 10) << this << " " << __func__ << dendl;
diff --git a/src/librbd/ExclusiveLock.h b/src/librbd/ExclusiveLock.h
index e2e1416..6268c80 100644
--- a/src/librbd/ExclusiveLock.h
+++ b/src/librbd/ExclusiveLock.h
@@ -32,6 +32,9 @@ public:
   bool is_lock_owner() const;
   bool accept_requests() const;
 
+  void block_requests();
+  void unblock_requests();
+
   void init(uint64_t features, Context *on_init);
   void shut_down(Context *on_shutdown);
 
@@ -127,6 +130,8 @@ private:
 
   ActionsContexts m_actions_contexts;
 
+  uint32_t m_request_blockers = 0;
+
   std::string encode_lock_cookie() const;
 
   bool is_transition_state() const;
diff --git a/src/librbd/ImageState.cc b/src/librbd/ImageState.cc
index 329ad3a..0bf2e45 100644
--- a/src/librbd/ImageState.cc
+++ b/src/librbd/ImageState.cc
@@ -109,7 +109,7 @@ void ImageState<I>::refresh(Context *on_finish) {
   m_lock.Lock();
   if (is_closed()) {
     m_lock.Unlock();
-    on_finish->complete(0);
+    on_finish->complete(-ESHUTDOWN);
     return;
   }
 
@@ -123,9 +123,12 @@ int ImageState<I>::refresh_if_required() {
   C_SaferCond ctx;
   {
     m_lock.Lock();
-    if (m_last_refresh == m_refresh_seq || is_closed()) {
+    if (m_last_refresh == m_refresh_seq) {
       m_lock.Unlock();
       return 0;
+    } else if (is_closed()) {
+      m_lock.Unlock();
+      return -ESHUTDOWN;
     }
 
     Action action(ACTION_TYPE_REFRESH);
diff --git a/src/librbd/ImageWatcher.cc b/src/librbd/ImageWatcher.cc
index b48974e..d3d4e70 100644
--- a/src/librbd/ImageWatcher.cc
+++ b/src/librbd/ImageWatcher.cc
@@ -714,7 +714,7 @@ bool ImageWatcher::handle_payload(const SnapCreatePayload &payload,
 
     m_image_ctx.operations->execute_snap_create(payload.snap_name.c_str(),
                                                 new C_ResponseMessage(ack_ctx),
-                                                0);
+                                                0, false);
     return false;
   }
   return true;
diff --git a/src/librbd/Journal.cc b/src/librbd/Journal.cc
index 265a869..92b360d 100644
--- a/src/librbd/Journal.cc
+++ b/src/librbd/Journal.cc
@@ -795,6 +795,42 @@ void Journal<I>::flush_commit_position(Context *on_finish) {
 }
 
 template <typename I>
+uint64_t Journal<I>::append_write_event(AioCompletion *aio_comp,
+                                        uint64_t offset, size_t length,
+                                        const bufferlist &bl,
+                                        const AioObjectRequests &requests,
+                                        bool flush_entry) {
+  assert(m_image_ctx.owner_lock.is_locked());
+
+  assert(m_max_append_size > journal::AioWriteEvent::get_fixed_size());
+  uint64_t max_write_data_size =
+    m_max_append_size - journal::AioWriteEvent::get_fixed_size();
+
+  // ensure that the write event fits within the journal entry
+  Bufferlists bufferlists;
+  uint64_t bytes_remaining = length;
+  uint64_t event_offset = 0;
+  do {
+    uint64_t event_length = MIN(bytes_remaining, max_write_data_size);
+
+    bufferlist event_bl;
+    event_bl.substr_of(bl, event_offset, event_length);
+    journal::EventEntry event_entry(journal::AioWriteEvent(offset + event_offset,
+                                                           event_length,
+                                                           event_bl));
+
+    bufferlists.emplace_back();
+    ::encode(event_entry, bufferlists.back());
+
+    event_offset += event_length;
+    bytes_remaining -= event_length;
+  } while (bytes_remaining > 0);
+
+  return append_io_events(aio_comp, journal::EVENT_TYPE_AIO_WRITE, bufferlists,
+                          requests, offset, length, flush_entry);
+}
+
+template <typename I>
 uint64_t Journal<I>::append_io_event(AioCompletion *aio_comp,
                                      journal::EventEntry &&event_entry,
                                      const AioObjectRequests &requests,
@@ -804,8 +840,21 @@ uint64_t Journal<I>::append_io_event(AioCompletion *aio_comp,
 
   bufferlist bl;
   ::encode(event_entry, bl);
+  return append_io_events(aio_comp, event_entry.get_event_type(), {bl},
+                          requests, offset, length, flush_entry);
+}
 
-  Future future;
+template <typename I>
+uint64_t Journal<I>::append_io_events(AioCompletion *aio_comp,
+                                      journal::EventType event_type,
+                                      const Bufferlists &bufferlists,
+                                      const AioObjectRequests &requests,
+                                      uint64_t offset, size_t length,
+                                      bool flush_entry) {
+  assert(m_image_ctx.owner_lock.is_locked());
+  assert(!bufferlists.empty());
+
+  Futures futures;
   uint64_t tid;
   {
     Mutex::Locker locker(m_lock);
@@ -815,13 +864,16 @@ uint64_t Journal<I>::append_io_event(AioCompletion *aio_comp,
     tid = ++m_event_tid;
     assert(tid != 0);
 
-    future = m_journaler->append(m_tag_tid, bl);
-    m_events[tid] = Event(future, aio_comp, requests, offset, length);
+    for (auto &bl : bufferlists) {
+      assert(bl.length() <= m_max_append_size);
+      futures.push_back(m_journaler->append(m_tag_tid, bl));
+    }
+    m_events[tid] = Event(futures, aio_comp, requests, offset, length);
   }
 
   CephContext *cct = m_image_ctx.cct;
   ldout(cct, 20) << this << " " << __func__ << ": "
-                 << "event=" << event_entry.get_event_type() << ", "
+                 << "event=" << event_type << ", "
                  << "new_reqs=" << requests.size() << ", "
                  << "offset=" << offset << ", "
                  << "length=" << length << ", "
@@ -830,9 +882,9 @@ uint64_t Journal<I>::append_io_event(AioCompletion *aio_comp,
   Context *on_safe = create_async_context_callback(
     m_image_ctx, new C_IOEventSafe(this, tid));
   if (flush_entry) {
-    future.flush(on_safe);
+    futures.back().flush(on_safe);
   } else {
-    future.wait(on_safe);
+    futures.back().wait(on_safe);
   }
   return tid;
 }
@@ -1006,7 +1058,7 @@ typename Journal<I>::Future Journal<I>::wait_event(Mutex &lock, uint64_t tid,
 
   event.on_safe_contexts.push_back(create_async_context_callback(m_image_ctx,
                                                                  on_safe));
-  return event.future;
+  return event.futures.back();
 }
 
 template <typename I>
@@ -1107,7 +1159,9 @@ void Journal<I>::complete_event(typename Events::iterator it, int r) {
   event.committed_io = true;
   if (event.safe) {
     if (r >= 0) {
-      m_journaler->committed(event.future);
+      for (auto &future : event.futures) {
+        m_journaler->committed(future);
+      }
     }
     m_events.erase(it);
   }
@@ -1129,6 +1183,9 @@ void Journal<I>::handle_initialized(int r) {
     return;
   }
 
+  m_max_append_size = m_journaler->get_max_append_size();
+  ldout(cct, 20) << this << " max_append_size=" << m_max_append_size << dendl;
+
   // locate the master image client record
   cls::journal::Client client;
   r = m_journaler->get_cached_client(Journal<ImageCtx>::IMAGE_CLIENT_ID,
@@ -1201,6 +1258,10 @@ void Journal<I>::handle_replay_ready() {
     if (!m_journaler->try_pop_front(&replay_entry)) {
       return;
     }
+
+    // only one entry should be in-flight at a time
+    assert(!m_processing_entry);
+    m_processing_entry = true;
   }
 
   bufferlist data = replay_entry.get_data();
@@ -1246,6 +1307,11 @@ void Journal<I>::handle_replay_process_ready(int r) {
   ldout(cct, 20) << this << " " << __func__ << dendl;
 
   assert(r == 0);
+  {
+    Mutex::Locker locker(m_lock);
+    assert(m_processing_entry);
+    m_processing_entry = false;
+  }
   handle_replay_ready();
 }
 
@@ -1387,7 +1453,9 @@ void Journal<I>::handle_io_event_safe(int r, uint64_t tid) {
       // failed journal write so IO won't be sent -- or IO extent was
       // overwritten by future IO operations so this was a no-op IO event
       event.ret_val = r;
-      m_journaler->committed(event.future);
+      for (auto &future : event.futures) {
+        m_journaler->committed(future);
+      }
     }
 
     if (event.committed_io) {
diff --git a/src/librbd/Journal.h b/src/librbd/Journal.h
index 424ef0e..e738d02 100644
--- a/src/librbd/Journal.h
+++ b/src/librbd/Journal.h
@@ -125,6 +125,11 @@ public:
 
   void flush_commit_position(Context *on_finish);
 
+  uint64_t append_write_event(AioCompletion *aio_comp,
+                              uint64_t offset, size_t length,
+                              const bufferlist &bl,
+                              const AioObjectRequests &requests,
+                              bool flush_entry);
   uint64_t append_io_event(AioCompletion *aio_comp,
                            journal::EventEntry &&event_entry,
                            const AioObjectRequests &requests,
@@ -160,11 +165,13 @@ private:
   typedef typename TypeTraits::Future Future;
   typedef typename TypeTraits::ReplayEntry ReplayEntry;
 
+  typedef std::list<bufferlist> Bufferlists;
   typedef std::list<Context *> Contexts;
+  typedef std::list<Future> Futures;
   typedef interval_set<uint64_t> ExtentInterval;
 
   struct Event {
-    Future future;
+    Futures futures;
     AioCompletion *aio_comp = nullptr;
     AioObjectRequests aio_object_requests;
     Contexts on_safe_contexts;
@@ -175,9 +182,9 @@ private:
 
     Event() {
     }
-    Event(const Future &_future, AioCompletion *_aio_comp,
+    Event(const Futures &_futures, AioCompletion *_aio_comp,
           const AioObjectRequests &_requests, uint64_t offset, size_t length)
-      : future(_future), aio_comp(_aio_comp), aio_object_requests(_requests) {
+      : futures(_futures), aio_comp(_aio_comp), aio_object_requests(_requests) {
       if (length > 0) {
         pending_extents.insert(offset, length);
       }
@@ -256,6 +263,7 @@ private:
   Journaler *m_journaler;
   mutable Mutex m_lock;
   State m_state;
+  uint64_t m_max_append_size = 0;
   uint64_t m_tag_class = 0;
   uint64_t m_tag_tid = 0;
   journal::TagData m_tag_data;
@@ -273,10 +281,16 @@ private:
   atomic_t m_op_tid;
   TidToFutures m_op_futures;
 
+  bool m_processing_entry = false;
   bool m_blocking_writes;
 
   journal::Replay<ImageCtxT> *m_journal_replay;
 
+  uint64_t append_io_events(AioCompletion *aio_comp,
+                            journal::EventType event_type,
+                            const Bufferlists &bufferlists,
+                            const AioObjectRequests &requests,
+                            uint64_t offset, size_t length, bool flush_entry);
   Future wait_event(Mutex &lock, uint64_t tid, Context *on_safe);
 
   void create_journaler();
diff --git a/src/librbd/ObjectMap.cc b/src/librbd/ObjectMap.cc
index 59d3a36..9f7d1d4 100644
--- a/src/librbd/ObjectMap.cc
+++ b/src/librbd/ObjectMap.cc
@@ -17,8 +17,10 @@
 #include "common/dout.h"
 #include "common/errno.h"
 #include "common/WorkQueue.h"
-#include "include/stringify.h"
 #include "cls/lock/cls_lock_client.h"
+#include "cls/rbd/cls_rbd_types.h"
+#include "include/stringify.h"
+#include "osdc/Striper.h"
 #include <sstream>
 
 #define dout_subsys ceph_subsys_rbd
@@ -48,6 +50,11 @@ std::string ObjectMap::object_map_name(const std::string &image_id,
   return oid;
 }
 
+bool ObjectMap::is_compatible(const file_layout_t& layout, uint64_t size) {
+  uint64_t object_count = Striper::get_num_objects(layout, size);
+  return (object_count <= cls::rbd::MAX_OBJECT_MAP_OBJECT_COUNT);
+}
+
 ceph::BitVector<2u>::Reference ObjectMap::operator[](uint64_t object_no)
 {
   assert(m_image_ctx.object_map_lock.is_wlocked());
diff --git a/src/librbd/ObjectMap.h b/src/librbd/ObjectMap.h
index a2868aa..f285296 100644
--- a/src/librbd/ObjectMap.h
+++ b/src/librbd/ObjectMap.h
@@ -4,6 +4,7 @@
 #define CEPH_LIBRBD_OBJECT_MAP_H
 
 #include "include/int_types.h"
+#include "include/fs_types.h"
 #include "include/rados/librados.hpp"
 #include "include/rbd/object_map_types.h"
 #include "common/bit_vector.hpp"
@@ -24,6 +25,8 @@ public:
   static std::string object_map_name(const std::string &image_id,
 				     uint64_t snap_id);
 
+  static bool is_compatible(const file_layout_t& layout, uint64_t size);
+
   ceph::BitVector<2u>::Reference operator[](uint64_t object_no);
   uint8_t operator[](uint64_t object_no) const;
   inline uint64_t size() const {
diff --git a/src/librbd/Operations.cc b/src/librbd/Operations.cc
index 81219d4..c523b91 100644
--- a/src/librbd/Operations.cc
+++ b/src/librbd/Operations.cc
@@ -4,10 +4,12 @@
 #include "librbd/Operations.h"
 #include "common/dout.h"
 #include "common/errno.h"
+#include "common/WorkQueue.h"
 #include "librbd/ExclusiveLock.h"
 #include "librbd/ImageCtx.h"
 #include "librbd/ImageState.h"
 #include "librbd/ImageWatcher.h"
+#include "librbd/ObjectMap.h"
 #include "librbd/Utils.h"
 #include "librbd/operation/FlattenRequest.h"
 #include "librbd/operation/RebuildObjectMapRequest.h"
@@ -79,6 +81,9 @@ struct C_InvokeAsyncRequest : public Context {
    *    . . . . . .   |   . . . . . . . . . . . . . . . . . .
    *    .         .   |   .                                 .
    *    .         v   v   v                                 .
+   *    .       REFRESH_IMAGE (skip if not needed)          .
+   *    .             |                                     .
+   *    .             v                                     .
    *    .       ACQUIRE_LOCK (skip if exclusive lock        .
    *    .             |       disabled or has lock)         .
    *    .             |                                     .
@@ -116,24 +121,57 @@ struct C_InvokeAsyncRequest : public Context {
   }
 
   void send() {
+    send_refresh_image();
+  }
+
+  void send_refresh_image() {
+    if (!image_ctx.state->is_refresh_required()) {
+      send_acquire_exclusive_lock();
+      return;
+    }
+
+    CephContext *cct = image_ctx.cct;
+    ldout(cct, 20) << __func__ << dendl;
+
+    Context *ctx = util::create_context_callback<
+      C_InvokeAsyncRequest<I>,
+      &C_InvokeAsyncRequest<I>::handle_refresh_image>(this);
+    image_ctx.state->refresh(ctx);
+  }
+
+  void handle_refresh_image(int r) {
+    CephContext *cct = image_ctx.cct;
+    ldout(cct, 20) << __func__ << ": r=" << r << dendl;
+
+    if (r < 0) {
+      lderr(cct) << "failed to refresh image: " << cpp_strerror(r) << dendl;
+      complete(r);
+      return;
+    }
+
     send_acquire_exclusive_lock();
   }
 
   void send_acquire_exclusive_lock() {
-    RWLock::RLocker owner_locker(image_ctx.owner_lock);
-    {
-      RWLock::RLocker snap_locker(image_ctx.snap_lock);
-      if (image_ctx.read_only ||
-          (!permit_snapshot && image_ctx.snap_id != CEPH_NOSNAP)) {
-        complete(-EROFS);
-        return;
-      }
+    // context can complete before owner_lock is unlocked
+    RWLock &owner_lock(image_ctx.owner_lock);
+    owner_lock.get_read();
+    image_ctx.snap_lock.get_read();
+    if (image_ctx.read_only ||
+        (!permit_snapshot && image_ctx.snap_id != CEPH_NOSNAP)) {
+      image_ctx.snap_lock.put_read();
+      owner_lock.put_read();
+      complete(-EROFS);
+      return;
     }
+    image_ctx.snap_lock.put_read();
 
     if (image_ctx.exclusive_lock == nullptr) {
       send_local_request();
+      owner_lock.put_read();
       return;
     } else if (image_ctx.image_watcher == nullptr) {
+      owner_lock.put_read();
       complete(-EROFS);
       return;
     }
@@ -141,6 +179,7 @@ struct C_InvokeAsyncRequest : public Context {
     if (image_ctx.exclusive_lock->is_lock_owner() &&
         image_ctx.exclusive_lock->accept_requests()) {
       send_local_request();
+      owner_lock.put_read();
       return;
     }
 
@@ -152,22 +191,29 @@ struct C_InvokeAsyncRequest : public Context {
       &C_InvokeAsyncRequest<I>::handle_acquire_exclusive_lock>(
         this);
     image_ctx.exclusive_lock->try_lock(ctx);
+    owner_lock.put_read();
   }
 
   void handle_acquire_exclusive_lock(int r) {
     CephContext *cct = image_ctx.cct;
     ldout(cct, 20) << __func__ << ": r=" << r << dendl;
 
-    RWLock::RLocker owner_locker(image_ctx.owner_lock);
     if (r < 0) {
       complete(-EROFS);
       return;
-    } else if (image_ctx.exclusive_lock->is_lock_owner()) {
+    }
+
+    // context can complete before owner_lock is unlocked
+    RWLock &owner_lock(image_ctx.owner_lock);
+    owner_lock.get_read();
+    if (image_ctx.exclusive_lock->is_lock_owner()) {
       send_local_request();
+      owner_lock.put_read();
       return;
     }
 
     send_remote_request();
+    owner_lock.put_read();
   }
 
   void send_remote_request() {
@@ -193,7 +239,7 @@ struct C_InvokeAsyncRequest : public Context {
 
     ldout(cct, 5) << request_type << " timed out notifying lock owner"
                   << dendl;
-    send_acquire_exclusive_lock();
+    send_refresh_image();
   }
 
   void send_local_request() {
@@ -202,9 +248,10 @@ struct C_InvokeAsyncRequest : public Context {
     CephContext *cct = image_ctx.cct;
     ldout(cct, 20) << __func__ << dendl;
 
-    Context *ctx = util::create_context_callback<
-      C_InvokeAsyncRequest<I>, &C_InvokeAsyncRequest<I>::handle_local_request>(
-        this);
+    Context *ctx = util::create_async_context_callback(
+      image_ctx, util::create_context_callback<
+        C_InvokeAsyncRequest<I>,
+        &C_InvokeAsyncRequest<I>::handle_local_request>(this));
     local(ctx);
   }
 
@@ -213,7 +260,7 @@ struct C_InvokeAsyncRequest : public Context {
     ldout(cct, 20) << __func__ << ": r=" << r << dendl;
 
     if (r == -ERESTART) {
-      send_acquire_exclusive_lock();
+      send_refresh_image();
       return;
     }
     complete(r);
@@ -281,41 +328,44 @@ void Operations<I>::execute_flatten(ProgressContext &prog_ctx,
   CephContext *cct = m_image_ctx.cct;
   ldout(cct, 20) << "flatten" << dendl;
 
-  uint64_t object_size;
-  uint64_t overlap_objects;
-  ::SnapContext snapc;
+  if (m_image_ctx.read_only) {
+    on_finish->complete(-EROFS);
+    return;
+  }
 
-  {
-    uint64_t overlap;
-    RWLock::RLocker l(m_image_ctx.snap_lock);
-    RWLock::RLocker l2(m_image_ctx.parent_lock);
+  m_image_ctx.snap_lock.get_read();
+  m_image_ctx.parent_lock.get_read();
 
-    if (m_image_ctx.read_only) {
-      on_finish->complete(-EROFS);
-      return;
-    }
+  // can't flatten a non-clone
+  if (m_image_ctx.parent_md.spec.pool_id == -1) {
+    lderr(cct) << "image has no parent" << dendl;
+    m_image_ctx.parent_lock.put_read();
+    m_image_ctx.snap_lock.put_read();
+    on_finish->complete(-EINVAL);
+    return;
+  }
+  if (m_image_ctx.snap_id != CEPH_NOSNAP) {
+    lderr(cct) << "snapshots cannot be flattened" << dendl;
+    m_image_ctx.parent_lock.put_read();
+    m_image_ctx.snap_lock.put_read();
+    on_finish->complete(-EROFS);
+    return;
+  }
 
-    // can't flatten a non-clone
-    if (m_image_ctx.parent_md.spec.pool_id == -1) {
-      lderr(cct) << "image has no parent" << dendl;
-      on_finish->complete(-EINVAL);
-      return;
-    }
-    if (m_image_ctx.snap_id != CEPH_NOSNAP) {
-      lderr(cct) << "snapshots cannot be flattened" << dendl;
-      on_finish->complete(-EROFS);
-      return;
-    }
+  ::SnapContext snapc = m_image_ctx.snapc;
+  assert(m_image_ctx.parent != NULL);
 
-    snapc = m_image_ctx.snapc;
-    assert(m_image_ctx.parent != NULL);
-    int r = m_image_ctx.get_parent_overlap(CEPH_NOSNAP, &overlap);
-    assert(r == 0);
-    assert(overlap <= m_image_ctx.size);
+  uint64_t overlap;
+  int r = m_image_ctx.get_parent_overlap(CEPH_NOSNAP, &overlap);
+  assert(r == 0);
+  assert(overlap <= m_image_ctx.size);
 
-    object_size = m_image_ctx.get_object_size();
-    overlap_objects = Striper::get_num_objects(m_image_ctx.layout, overlap);
-  }
+  uint64_t object_size = m_image_ctx.get_object_size();
+  uint64_t  overlap_objects = Striper::get_num_objects(m_image_ctx.layout,
+                                                       overlap);
+
+  m_image_ctx.parent_lock.put_read();
+  m_image_ctx.snap_lock.put_read();
 
   operation::FlattenRequest<I> *req = new operation::FlattenRequest<I>(
     m_image_ctx, new C_NotifyUpdate<I>(m_image_ctx, on_finish), object_size,
@@ -450,6 +500,12 @@ int Operations<I>::resize(uint64_t size, ProgressContext& prog_ctx) {
     return r;
   }
 
+  if (m_image_ctx.test_features(RBD_FEATURE_OBJECT_MAP) &&
+      !ObjectMap::is_compatible(m_image_ctx.layout, size)) {
+    lderr(cct) << "New size not compatible with object map" << dendl;
+    return -EINVAL;
+  }
+
   uint64_t request_id = ++m_async_request_seq;
   r = invoke_async_request("resize", false,
                            boost::bind(&Operations<I>::execute_resize, this,
@@ -476,15 +532,19 @@ void Operations<I>::execute_resize(uint64_t size, ProgressContext &prog_ctx,
   ldout(cct, 5) << this << " " << __func__ << ": "
                 << "size=" << m_image_ctx.size << ", "
                 << "new_size=" << size << dendl;
-  m_image_ctx.snap_lock.put_read();
 
-  {
-    RWLock::RLocker snap_locker(m_image_ctx.snap_lock);
-    if (m_image_ctx.snap_id != CEPH_NOSNAP || m_image_ctx.read_only) {
-      on_finish->complete(-EROFS);
-      return;
-    }
+  if (m_image_ctx.snap_id != CEPH_NOSNAP || m_image_ctx.read_only) {
+    m_image_ctx.snap_lock.put_read();
+    on_finish->complete(-EROFS);
+    return;
+  } else if (m_image_ctx.test_features(RBD_FEATURE_OBJECT_MAP,
+                                       m_image_ctx.snap_lock) &&
+             !ObjectMap::is_compatible(m_image_ctx.layout, size)) {
+    m_image_ctx.snap_lock.put_read();
+    on_finish->complete(-EINVAL);
+    return;
   }
+  m_image_ctx.snap_lock.put_read();
 
   operation::ResizeRequest<I> *req = new operation::ResizeRequest<I>(
     m_image_ctx, new C_NotifyUpdate<I>(m_image_ctx, on_finish), size, prog_ctx,
@@ -526,17 +586,18 @@ void Operations<I>::snap_create(const char *snap_name, Context *on_finish) {
     return;
   }
 
-  {
-    RWLock::RLocker snap_locker(m_image_ctx.snap_lock);
-    if (m_image_ctx.get_snap_id(snap_name) != CEPH_NOSNAP) {
-      on_finish->complete(-EEXIST);
-      return;
-    }
+  m_image_ctx.snap_lock.get_read();
+  if (m_image_ctx.get_snap_id(snap_name) != CEPH_NOSNAP) {
+    m_image_ctx.snap_lock.put_read();
+    on_finish->complete(-EEXIST);
+    return;
   }
+  m_image_ctx.snap_lock.put_read();
 
   C_InvokeAsyncRequest<I> *req = new C_InvokeAsyncRequest<I>(
     m_image_ctx, "snap_create", true,
-    boost::bind(&Operations<I>::execute_snap_create, this, snap_name, _1, 0),
+    boost::bind(&Operations<I>::execute_snap_create, this, snap_name, _1, 0,
+                false),
     boost::bind(&ImageWatcher::notify_snap_create, m_image_ctx.image_watcher,
                 snap_name, _1),
     {-EEXIST}, on_finish);
@@ -546,7 +607,8 @@ void Operations<I>::snap_create(const char *snap_name, Context *on_finish) {
 template <typename I>
 void Operations<I>::execute_snap_create(const char *snap_name,
                                         Context *on_finish,
-                                        uint64_t journal_op_tid) {
+                                        uint64_t journal_op_tid,
+                                        bool skip_object_map) {
   assert(m_image_ctx.owner_lock.is_locked());
   assert(m_image_ctx.exclusive_lock == nullptr ||
          m_image_ctx.exclusive_lock->is_lock_owner());
@@ -558,7 +620,7 @@ void Operations<I>::execute_snap_create(const char *snap_name,
   operation::SnapshotCreateRequest<I> *req =
     new operation::SnapshotCreateRequest<I>(
       m_image_ctx, new C_NotifyUpdate<I>(m_image_ctx, on_finish), snap_name,
-      journal_op_tid);
+      journal_op_tid, skip_object_map);
   req->send();
 }
 
@@ -621,20 +683,18 @@ void Operations<I>::execute_snap_rollback(const char *snap_name,
   ldout(cct, 5) << this << " " << __func__ << ": snap_name=" << snap_name
                 << dendl;
 
-  uint64_t snap_id;
-  uint64_t new_size;
-  {
-    RWLock::RLocker snap_locker(m_image_ctx.snap_lock);
-    snap_id = m_image_ctx.get_snap_id(snap_name);
-    if (snap_id == CEPH_NOSNAP) {
-      lderr(cct) << "No such snapshot found." << dendl;
-      on_finish->complete(-ENOENT);
-      return;
-    }
-
-    new_size = m_image_ctx.get_image_size(snap_id);
+  m_image_ctx.snap_lock.get_read();
+  uint64_t snap_id = m_image_ctx.get_snap_id(snap_name);
+  if (snap_id == CEPH_NOSNAP) {
+    lderr(cct) << "No such snapshot found." << dendl;
+    m_image_ctx.snap_lock.put_read();
+    on_finish->complete(-ENOENT);
+    return;
   }
 
+  uint64_t new_size = m_image_ctx.get_image_size(snap_id);
+  m_image_ctx.snap_lock.put_read();
+
   // async mode used for journal replay
   operation::SnapshotRollbackRequest<I> *request =
     new operation::SnapshotRollbackRequest<I>(
@@ -677,17 +737,17 @@ void Operations<I>::snap_remove(const char *snap_name, Context *on_finish) {
     return;
   }
 
-  bool proxy_op = false;
-  {
-    RWLock::RLocker snap_locker(m_image_ctx.snap_lock);
-    if (m_image_ctx.get_snap_id(snap_name) == CEPH_NOSNAP) {
-      on_finish->complete(-ENOENT);
-      return;
-    }
-    proxy_op = ((m_image_ctx.features & RBD_FEATURE_FAST_DIFF) != 0 ||
-                (m_image_ctx.features & RBD_FEATURE_JOURNALING) != 0);
+  m_image_ctx.snap_lock.get_read();
+  if (m_image_ctx.get_snap_id(snap_name) == CEPH_NOSNAP) {
+    m_image_ctx.snap_lock.put_read();
+    on_finish->complete(-ENOENT);
+    return;
   }
 
+  bool proxy_op = ((m_image_ctx.features & RBD_FEATURE_FAST_DIFF) != 0 ||
+                   (m_image_ctx.features & RBD_FEATURE_JOURNALING) != 0);
+  m_image_ctx.snap_lock.put_read();
+
   if (proxy_op) {
     C_InvokeAsyncRequest<I> *req = new C_InvokeAsyncRequest<I>(
       m_image_ctx, "snap_remove", true,
@@ -717,27 +777,28 @@ void Operations<I>::execute_snap_remove(const char *snap_name,
   ldout(cct, 5) << this << " " << __func__ << ": snap_name=" << snap_name
                 << dendl;
 
-  uint64_t snap_id;
-  {
-    RWLock::RLocker snap_locker(m_image_ctx.snap_lock);
-    snap_id = m_image_ctx.get_snap_id(snap_name);
-    if (snap_id == CEPH_NOSNAP) {
-      lderr(m_image_ctx.cct) << "No such snapshot found." << dendl;
-      on_finish->complete(-ENOENT);
-      return;
-    }
+  m_image_ctx.snap_lock.get_read();
+  uint64_t snap_id = m_image_ctx.get_snap_id(snap_name);
+  if (snap_id == CEPH_NOSNAP) {
+    lderr(m_image_ctx.cct) << "No such snapshot found." << dendl;
+    m_image_ctx.snap_lock.put_read();
+    on_finish->complete(-ENOENT);
+    return;
+  }
 
-    bool is_protected;
-    int r = m_image_ctx.is_snap_protected(snap_id, &is_protected);
-    if (r < 0) {
-      on_finish->complete(r);
-      return;
-    } else if (is_protected) {
-      lderr(m_image_ctx.cct) << "snapshot is protected" << dendl;
-      on_finish->complete(-EBUSY);
-      return;
-    }
+  bool is_protected;
+  int r = m_image_ctx.is_snap_protected(snap_id, &is_protected);
+  if (r < 0) {
+    m_image_ctx.snap_lock.put_read();
+    on_finish->complete(r);
+    return;
+  } else if (is_protected) {
+    lderr(m_image_ctx.cct) << "snapshot is protected" << dendl;
+    m_image_ctx.snap_lock.put_read();
+    on_finish->complete(-EBUSY);
+    return;
   }
+  m_image_ctx.snap_lock.put_read();
 
   operation::SnapshotRemoveRequest<I> *req =
     new operation::SnapshotRemoveRequest<I>(
diff --git a/src/librbd/Operations.h b/src/librbd/Operations.h
index a32116a..95af4dc 100644
--- a/src/librbd/Operations.h
+++ b/src/librbd/Operations.h
@@ -38,7 +38,7 @@ public:
   int snap_create(const char *snap_name);
   void snap_create(const char *snap_name, Context *on_finish);
   void execute_snap_create(const char *snap_name, Context *on_finish,
-                           uint64_t journal_op_tid);
+                           uint64_t journal_op_tid, bool skip_object_map);
 
   int snap_rollback(const char *snap_name, ProgressContext& prog_ctx);
   void execute_snap_rollback(const char *snap_name, ProgressContext& prog_ctx,
diff --git a/src/librbd/WatchNotifyTypes.h b/src/librbd/WatchNotifyTypes.h
index 813ff5f..f5ec621 100644
--- a/src/librbd/WatchNotifyTypes.h
+++ b/src/librbd/WatchNotifyTypes.h
@@ -92,7 +92,7 @@ enum NotifyOp {
 
 struct AcquiredLockPayload {
   static const NotifyOp NOTIFY_OP = NOTIFY_OP_ACQUIRED_LOCK;
-  static const bool CHECK_FOR_REFRESH = true;
+  static const bool CHECK_FOR_REFRESH = false;
 
   ClientId client_id;
 
@@ -106,7 +106,7 @@ struct AcquiredLockPayload {
 
 struct ReleasedLockPayload {
   static const NotifyOp NOTIFY_OP = NOTIFY_OP_RELEASED_LOCK;
-  static const bool CHECK_FOR_REFRESH = true;
+  static const bool CHECK_FOR_REFRESH = false;
 
   ClientId client_id;
 
@@ -120,7 +120,7 @@ struct ReleasedLockPayload {
 
 struct RequestLockPayload {
   static const NotifyOp NOTIFY_OP = NOTIFY_OP_REQUEST_LOCK;
-  static const bool CHECK_FOR_REFRESH = true;
+  static const bool CHECK_FOR_REFRESH = false;
 
   ClientId client_id;
   bool force = false;
diff --git a/src/librbd/exclusive_lock/AcquireRequest.cc b/src/librbd/exclusive_lock/AcquireRequest.cc
index 6ec148e..4a4ee6d 100644
--- a/src/librbd/exclusive_lock/AcquireRequest.cc
+++ b/src/librbd/exclusive_lock/AcquireRequest.cc
@@ -218,7 +218,13 @@ Context *AcquireRequest<I>::handle_close_journal(int *ret_val) {
   CephContext *cct = m_image_ctx.cct;
   ldout(cct, 10) << __func__ << ": r=" << *ret_val << dendl;
 
-  return send_close_object_map(ret_val);
+  if (*ret_val < 0) {
+    lderr(cct) << "failed to close journal: " << cpp_strerror(*ret_val)
+               << dendl;
+  }
+
+  send_close_object_map();
+  return nullptr;
 }
 
 template <typename I>
@@ -244,16 +250,23 @@ Context *AcquireRequest<I>::handle_open_object_map(int *ret_val) {
   CephContext *cct = m_image_ctx.cct;
   ldout(cct, 10) << __func__ << ": r=" << *ret_val << dendl;
 
-  // object map should never result in an error
-  assert(*ret_val == 0);
+  if (*ret_val < 0) {
+    lderr(cct) << "failed to open object map: " << cpp_strerror(*ret_val)
+               << dendl;
+
+    *ret_val = 0;
+    delete m_object_map;
+    m_object_map = nullptr;
+  }
+
   return send_open_journal();
 }
 
 template <typename I>
-Context *AcquireRequest<I>::send_close_object_map(int *ret_val) {
+void AcquireRequest<I>::send_close_object_map() {
   if (m_object_map == nullptr) {
-    revert(ret_val);
-    return m_on_finish;
+    send_unlock();
+    return;
   }
 
   CephContext *cct = m_image_ctx.cct;
@@ -263,7 +276,6 @@ Context *AcquireRequest<I>::send_close_object_map(int *ret_val) {
   Context *ctx = create_context_callback<
     klass, &klass::handle_close_object_map>(this);
   m_object_map->close(ctx);
-  return nullptr;
 }
 
 template <typename I>
@@ -273,6 +285,36 @@ Context *AcquireRequest<I>::handle_close_object_map(int *ret_val) {
 
   // object map should never result in an error
   assert(*ret_val == 0);
+  send_unlock();
+  return nullptr;
+}
+
+template <typename I>
+void AcquireRequest<I>::send_unlock() {
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 10) << __func__ << dendl;
+
+  librados::ObjectWriteOperation op;
+  rados::cls::lock::unlock(&op, RBD_LOCK_NAME, m_cookie);
+
+  using klass = AcquireRequest<I>;
+  librados::AioCompletion *rados_completion =
+    create_rados_safe_callback<klass, &klass::handle_unlock>(this);
+  int r = m_image_ctx.md_ctx.aio_operate(m_image_ctx.header_oid,
+                                         rados_completion, &op);
+  assert(r == 0);
+  rados_completion->release();
+}
+
+template <typename I>
+Context *AcquireRequest<I>::handle_unlock(int *ret_val) {
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 10) << __func__ << ": r=" << *ret_val << dendl;
+
+  if (*ret_val < 0) {
+    lderr(cct) << "failed to unlock image: " << cpp_strerror(*ret_val) << dendl;
+  }
+
   revert(ret_val);
   return m_on_finish;
 }
diff --git a/src/librbd/exclusive_lock/AcquireRequest.h b/src/librbd/exclusive_lock/AcquireRequest.h
index 2c4e05b..9a16fc5 100644
--- a/src/librbd/exclusive_lock/AcquireRequest.h
+++ b/src/librbd/exclusive_lock/AcquireRequest.h
@@ -49,21 +49,26 @@ private:
    *          .   |                         |                             |
    *    . . . .   |                         |                             |
    *    .         v                         v                             |
-   *    .     OPEN_OBJECT_MAP             GET_WATCHERS . . .              |
-   *    .         |                         |              .              |
+   *    .     OPEN_OBJECT_MAP (skip if    GET_WATCHERS . . .              |
+   *    .         |            disabled)    |              .              |
    *    .         v                         v              .              |
-   *    . . > OPEN_JOURNAL * * * * * *    BLACKLIST        . (blacklist   |
-   *    .         |                  *      |              .  disabled)   |
-   *    .         v                  *      v              .              |
-   *    .     ALLOCATE_JOURNAL_TAG   *    BREAK_LOCK < . . .              |
-   *    .         |            *     *      |                             |
-   *    .         |            *     *      \-----------------------------/
-   *    .         |            v     v
+   *    . . > OPEN_JOURNAL (skip if       BLACKLIST        . (blacklist   |
+   *    .         |   *     disabled)       |              .  disabled)   |
+   *    .         |   *                     v              .              |
+   *    .         |   * * * * * * * *     BREAK_LOCK < . . .              |
+   *    .         v                 *       |                             |
+   *    .     ALLOCATE_JOURNAL_TAG  *       \-----------------------------/
+   *    .         |            *    *
+   *    .         |            *    *
+   *    .         |            v    v
    *    .         |         CLOSE_JOURNAL
    *    .         |               |
    *    .         |               v
    *    .         |         CLOSE_OBJECT_MAP
    *    .         |               |
+   *    .         |               v
+   *    .         |         UNLOCK_IMAGE
+   *    .         |               |
    *    .         v               |
    *    . . > <finish> <----------/
    *
@@ -105,15 +110,18 @@ private:
   void send_allocate_journal_tag();
   Context *handle_allocate_journal_tag(int *ret_val);
 
-  void send_close_journal();
-  Context *handle_close_journal(int *ret_val);
-
   Context *send_open_object_map();
   Context *handle_open_object_map(int *ret_val);
 
-  Context *send_close_object_map(int *ret_val);
+  void send_close_journal();
+  Context *handle_close_journal(int *ret_val);
+
+  void send_close_object_map();
   Context *handle_close_object_map(int *ret_val);
 
+  void send_unlock();
+  Context *handle_unlock(int *ret_val);
+
   void send_get_lockers();
   Context *handle_get_lockers(int *ret_val);
 
diff --git a/src/librbd/image/OpenRequest.cc b/src/librbd/image/OpenRequest.cc
index c491039..3027eca 100644
--- a/src/librbd/image/OpenRequest.cc
+++ b/src/librbd/image/OpenRequest.cc
@@ -58,8 +58,8 @@ Context *OpenRequest<I>::handle_v1_detect_header(int *result) {
     }
     send_close_image(*result);
   } else {
-    lderr(cct) << "RBD image format 1 is deprecated. "
-               << "Please copy this image to image format 2." << dendl;
+    ldout(cct, 1) << "RBD image format 1 is deprecated. "
+                  << "Please copy this image to image format 2." << dendl;
 
     m_image_ctx->old_format = true;
     m_image_ctx->header_oid = util::old_header_name(m_image_ctx->name);
diff --git a/src/librbd/image/RefreshParentRequest.cc b/src/librbd/image/RefreshParentRequest.cc
index f9ce4f1..a44124f 100644
--- a/src/librbd/image/RefreshParentRequest.cc
+++ b/src/librbd/image/RefreshParentRequest.cc
@@ -117,8 +117,9 @@ void RefreshParentRequest<I>::send_open_parent() {
   }
 
   using klass = RefreshParentRequest<I>;
-  Context *ctx = create_context_callback<
-    klass, &klass::handle_open_parent, false>(this);
+  Context *ctx = create_async_context_callback(
+    m_child_image_ctx, create_context_callback<
+      klass, &klass::handle_open_parent, false>(this));
   OpenRequest<I> *req = OpenRequest<I>::create(m_parent_image_ctx, ctx);
   req->send();
 }
@@ -132,8 +133,12 @@ Context *RefreshParentRequest<I>::handle_open_parent(int *result) {
   if (*result < 0) {
     lderr(cct) << "failed to open parent image: " << cpp_strerror(*result)
                << dendl;
-    send_close_parent();
-    return nullptr;
+
+    // image already closed by open state machine
+    delete m_parent_image_ctx;
+    m_parent_image_ctx = nullptr;
+
+    return m_on_finish;
   }
 
   send_set_parent_snap();
diff --git a/src/librbd/image/RefreshParentRequest.h b/src/librbd/image/RefreshParentRequest.h
index e51d24f..3fe6539 100644
--- a/src/librbd/image/RefreshParentRequest.h
+++ b/src/librbd/image/RefreshParentRequest.h
@@ -38,19 +38,19 @@ private:
    * <start>
    *    |
    *    | (open required)
-   *    |----------------> OPEN_PARENT * * * * * * * *
-   *    |                     |                      *
-   *    |                     v                      * (on error)
-   *    |                  SET_PARENT_SNAP * * * * * *
-   *    |                     |                      *
-   *    |                     v                      *
-   *    \----------------> <apply>                   *
-   *                          |                      *
-   *                          | (close required)     v
-   *                          |-----------------> CLOSE_PARENT
-   *                          |                      |
-   *                          |                      v
-   *                          \-----------------> <finish>
+   *    |----------------> OPEN_PARENT * * * * * * * * * * * * * * *
+   *    |                     |                                    *
+   *    |                     v                                    *
+   *    |                  SET_PARENT_SNAP * * * * * *             *
+   *    |                     |                      *             *
+   *    |                     v                      * (on error)  *
+   *    \----------------> <apply>                   *             *
+   *                          |                      *             *
+   *                          | (close required)     v             *
+   *                          |-----------------> CLOSE_PARENT     *
+   *                          |                      |             *
+   *                          |                      v             *
+   *                          \-----------------> <finish> < * * * *
    *
    * @endverbatim
    */
diff --git a/src/librbd/image/RefreshRequest.cc b/src/librbd/image/RefreshRequest.cc
index ecb7cae..dedf307 100644
--- a/src/librbd/image/RefreshRequest.cc
+++ b/src/librbd/image/RefreshRequest.cc
@@ -187,8 +187,28 @@ Context *RefreshRequest<I>::handle_v1_get_locks(int *result) {
     return m_on_finish;
   }
 
-  apply();
+  send_v1_apply();
+  return nullptr;
+}
+
+template <typename I>
+void RefreshRequest<I>::send_v1_apply() {
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 10) << this << " " << __func__ << dendl;
 
+  // ensure we are not in a rados callback when applying updates
+  using klass = RefreshRequest<I>;
+  Context *ctx = create_context_callback<
+    klass, &klass::handle_v1_apply>(this);
+  m_image_ctx.op_work_queue->queue(ctx, 0);
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_v1_apply(int *result) {
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 10) << this << " " << __func__ << dendl;
+
+  apply();
   return send_flush_aio();
 }
 
@@ -245,6 +265,12 @@ Context *RefreshRequest<I>::handle_v2_get_mutable_metadata(int *result) {
     return m_on_finish;
   }
 
+  if (!m_snapc.is_valid()) {
+    lderr(cct) << "image snap context is invalid!" << dendl;
+    *result = -EIO;
+    return m_on_finish;
+  }
+
   send_v2_get_flags();
   return nullptr;
 }
@@ -299,17 +325,19 @@ Context *RefreshRequest<I>::handle_v2_get_flags(int *result) {
     return m_on_finish;
   }
 
-  return send_v2_get_snapshots();
+  send_v2_get_snapshots();
+  return nullptr;
 }
 
 template <typename I>
-Context *RefreshRequest<I>::send_v2_get_snapshots() {
+void RefreshRequest<I>::send_v2_get_snapshots() {
   if (m_snapc.snaps.empty()) {
     m_snap_names.clear();
     m_snap_sizes.clear();
     m_snap_parents.clear();
     m_snap_protection.clear();
-    return send_v2_refresh_parent();
+    send_v2_refresh_parent();
+    return;
   }
 
   CephContext *cct = m_image_ctx.cct;
@@ -326,7 +354,6 @@ Context *RefreshRequest<I>::send_v2_get_snapshots() {
                                          &m_out_bl);
   assert(r == 0);
   comp->release();
-  return nullptr;
 }
 
 template <typename I>
@@ -352,17 +379,12 @@ Context *RefreshRequest<I>::handle_v2_get_snapshots(int *result) {
     return m_on_finish;
   }
 
-  if (!m_snapc.is_valid()) {
-    lderr(cct) << "image snap context is invalid!" << dendl;
-    *result = -EIO;
-    return m_on_finish;
-  }
-
-  return send_v2_refresh_parent();
+  send_v2_refresh_parent();
+  return nullptr;
 }
 
 template <typename I>
-Context *RefreshRequest<I>::send_v2_refresh_parent() {
+void RefreshRequest<I>::send_v2_refresh_parent() {
   {
     RWLock::RLocker snap_locker(m_image_ctx.snap_lock);
     RWLock::RLocker parent_locker(m_image_ctx.parent_lock);
@@ -384,9 +406,8 @@ Context *RefreshRequest<I>::send_v2_refresh_parent() {
 
   if (m_refresh_parent != nullptr) {
     m_refresh_parent->send();
-    return nullptr;
   } else {
-    return send_v2_init_exclusive_lock();
+    send_v2_init_exclusive_lock();
   }
 }
 
@@ -399,18 +420,21 @@ Context *RefreshRequest<I>::handle_v2_refresh_parent(int *result) {
     lderr(cct) << "failed to refresh parent image: " << cpp_strerror(*result)
                << dendl;
     save_result(result);
-    return send_v2_finalize_refresh_parent();
+    send_v2_apply();
+    return nullptr;
   }
 
-  return send_v2_init_exclusive_lock();
+  send_v2_init_exclusive_lock();
+  return nullptr;
 }
 
 template <typename I>
-Context *RefreshRequest<I>::send_v2_init_exclusive_lock() {
+void RefreshRequest<I>::send_v2_init_exclusive_lock() {
   if ((m_features & RBD_FEATURE_EXCLUSIVE_LOCK) == 0 ||
       m_image_ctx.read_only || !m_image_ctx.snap_name.empty() ||
       m_image_ctx.exclusive_lock != nullptr) {
-    return send_v2_open_object_map();
+    send_v2_open_object_map();
+    return;
   }
 
   // implies exclusive lock dynamically enabled or image open in-progress
@@ -426,7 +450,6 @@ Context *RefreshRequest<I>::send_v2_init_exclusive_lock() {
 
   RWLock::RLocker owner_locker(m_image_ctx.owner_lock);
   m_exclusive_lock->init(m_features, ctx);
-  return nullptr;
 }
 
 template <typename I>
@@ -442,11 +465,12 @@ Context *RefreshRequest<I>::handle_v2_init_exclusive_lock(int *result) {
 
   // object map and journal will be opened when exclusive lock is
   // acquired (if features are enabled)
-  return send_v2_finalize_refresh_parent();
+  send_v2_apply();
+  return nullptr;
 }
 
 template <typename I>
-Context *RefreshRequest<I>::send_v2_open_journal() {
+void RefreshRequest<I>::send_v2_open_journal() {
   if ((m_features & RBD_FEATURE_JOURNALING) == 0 ||
       m_image_ctx.read_only ||
       !m_image_ctx.snap_name.empty() ||
@@ -460,7 +484,8 @@ Context *RefreshRequest<I>::send_v2_open_journal() {
         m_image_ctx.journal == nullptr) {
       m_image_ctx.aio_work_queue->set_require_lock_on_read();
     }
-    return send_v2_block_writes();
+    send_v2_block_writes();
+    return;
   }
 
   // implies journal dynamically enabled since ExclusiveLock will init
@@ -475,7 +500,6 @@ Context *RefreshRequest<I>::send_v2_open_journal() {
   // TODO need safe close
   m_journal = m_image_ctx.create_journal();
   m_journal->open(ctx);
-  return nullptr;
 }
 
 template <typename I>
@@ -489,11 +513,12 @@ Context *RefreshRequest<I>::handle_v2_open_journal(int *result) {
     save_result(result);
   }
 
-  return send_v2_block_writes();
+  send_v2_block_writes();
+  return nullptr;
 }
 
 template <typename I>
-Context *RefreshRequest<I>::send_v2_block_writes() {
+void RefreshRequest<I>::send_v2_block_writes() {
   bool disabled_journaling = false;
   {
     RWLock::RLocker snap_locker(m_image_ctx.snap_lock);
@@ -503,7 +528,8 @@ Context *RefreshRequest<I>::send_v2_block_writes() {
   }
 
   if (!disabled_journaling) {
-    return send_v2_finalize_refresh_parent();
+    send_v2_apply();
+    return;
   }
 
   CephContext *cct = m_image_ctx.cct;
@@ -517,7 +543,6 @@ Context *RefreshRequest<I>::send_v2_block_writes() {
 
   RWLock::RLocker owner_locker(m_image_ctx.owner_lock);
   m_image_ctx.aio_work_queue->block_writes(ctx);
-  return nullptr;
 }
 
 template <typename I>
@@ -530,18 +555,20 @@ Context *RefreshRequest<I>::handle_v2_block_writes(int *result) {
                << dendl;
     save_result(result);
   }
-  return send_v2_finalize_refresh_parent();
+  send_v2_apply();
+  return nullptr;
 }
 
 template <typename I>
-Context *RefreshRequest<I>::send_v2_open_object_map() {
+void RefreshRequest<I>::send_v2_open_object_map() {
   if ((m_features & RBD_FEATURE_OBJECT_MAP) == 0 ||
       m_image_ctx.object_map != nullptr ||
       (m_image_ctx.snap_name.empty() &&
        (m_image_ctx.read_only ||
         m_image_ctx.exclusive_lock == nullptr ||
         !m_image_ctx.exclusive_lock->is_lock_owner()))) {
-    return send_v2_open_journal();
+    send_v2_open_journal();
+    return;
   }
 
   // implies object map dynamically enabled or image open in-progress
@@ -564,7 +591,8 @@ Context *RefreshRequest<I>::send_v2_open_object_map() {
     if (m_object_map == nullptr) {
       lderr(cct) << "failed to locate snapshot: " << m_image_ctx.snap_name
                  << dendl;
-      return send_v2_open_journal();
+      send_v2_open_journal();
+      return;
     }
   }
 
@@ -572,7 +600,6 @@ Context *RefreshRequest<I>::send_v2_open_object_map() {
   Context *ctx = create_context_callback<
     klass, &klass::handle_v2_open_object_map>(this);
   m_object_map->open(ctx);
-  return nullptr;
 }
 
 template <typename I>
@@ -580,14 +607,40 @@ Context *RefreshRequest<I>::handle_v2_open_object_map(int *result) {
   CephContext *cct = m_image_ctx.cct;
   ldout(cct, 10) << this << " " << __func__ << ": r=" << *result << dendl;
 
-  assert(*result == 0);
-  return send_v2_open_journal();
+  if (*result < 0) {
+    lderr(cct) << "failed to open object map: " << cpp_strerror(*result)
+               << dendl;
+    delete m_object_map;
+    m_object_map = nullptr;
+  }
+
+  send_v2_open_journal();
+  return nullptr;
 }
 
 template <typename I>
-Context *RefreshRequest<I>::send_v2_finalize_refresh_parent() {
+void RefreshRequest<I>::send_v2_apply() {
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 10) << this << " " << __func__ << dendl;
+
+  // ensure we are not in a rados callback when applying updates
+  using klass = RefreshRequest<I>;
+  Context *ctx = create_context_callback<
+    klass, &klass::handle_v2_apply>(this);
+  m_image_ctx.op_work_queue->queue(ctx, 0);
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_v2_apply(int *result) {
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 10) << this << " " << __func__ << dendl;
+
   apply();
+  return send_v2_finalize_refresh_parent();
+}
 
+template <typename I>
+Context *RefreshRequest<I>::send_v2_finalize_refresh_parent() {
   if (m_refresh_parent == nullptr) {
     return send_v2_shut_down_exclusive_lock();
   }
@@ -736,7 +789,14 @@ Context *RefreshRequest<I>::send_flush_aio() {
       klass, &klass::handle_flush_aio>(this);
     m_image_ctx.flush(ctx);
     return nullptr;
+  } else if (m_error_result < 0) {
+    // propagate saved error back to caller
+    Context *ctx = create_context_callback<
+      RefreshRequest<I>, &RefreshRequest<I>::handle_error>(this);
+    m_image_ctx.op_work_queue->queue(ctx, 0);
+    return nullptr;
   }
+
   return m_on_finish;
 }
 
@@ -750,8 +810,16 @@ Context *RefreshRequest<I>::handle_flush_aio(int *result) {
                << dendl;
   }
 
+  return handle_error(result);
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_error(int *result) {
   if (m_error_result < 0) {
     *result = m_error_result;
+
+    CephContext *cct = m_image_ctx.cct;
+    ldout(cct, 10) << this << " " << __func__ << ": r=" << *result << dendl;
   }
   return m_on_finish;
 }
diff --git a/src/librbd/image/RefreshRequest.h b/src/librbd/image/RefreshRequest.h
index 71fe509..63c858b 100644
--- a/src/librbd/image/RefreshRequest.h
+++ b/src/librbd/image/RefreshRequest.h
@@ -139,30 +139,36 @@ private:
   void send_v1_get_locks();
   Context *handle_v1_get_locks(int *result);
 
+  void send_v1_apply();
+  Context *handle_v1_apply(int *result);
+
   void send_v2_get_mutable_metadata();
   Context *handle_v2_get_mutable_metadata(int *result);
 
   void send_v2_get_flags();
   Context *handle_v2_get_flags(int *result);
 
-  Context *send_v2_get_snapshots();
+  void send_v2_get_snapshots();
   Context *handle_v2_get_snapshots(int *result);
 
-  Context *send_v2_refresh_parent();
+  void send_v2_refresh_parent();
   Context *handle_v2_refresh_parent(int *result);
 
-  Context *send_v2_init_exclusive_lock();
+  void send_v2_init_exclusive_lock();
   Context *handle_v2_init_exclusive_lock(int *result);
 
-  Context *send_v2_open_journal();
+  void send_v2_open_journal();
   Context *handle_v2_open_journal(int *result);
 
-  Context *send_v2_block_writes();
+  void send_v2_block_writes();
   Context *handle_v2_block_writes(int *result);
 
-  Context *send_v2_open_object_map();
+  void send_v2_open_object_map();
   Context *handle_v2_open_object_map(int *result);
 
+  void send_v2_apply();
+  Context *handle_v2_apply(int *result);
+
   Context *send_v2_finalize_refresh_parent();
   Context *handle_v2_finalize_refresh_parent(int *result);
 
@@ -178,6 +184,8 @@ private:
   Context *send_flush_aio();
   Context *handle_flush_aio(int *result);
 
+  Context *handle_error(int *result);
+
   void save_result(int *result) {
     if (m_error_result == 0 && *result < 0) {
       m_error_result = *result;
diff --git a/src/librbd/image/SetSnapRequest.cc b/src/librbd/image/SetSnapRequest.cc
index ca10e45..44da673 100644
--- a/src/librbd/image/SetSnapRequest.cc
+++ b/src/librbd/image/SetSnapRequest.cc
@@ -60,8 +60,8 @@ void SetSnapRequest<I>::send_init_exclusive_lock() {
     int r = 0;
     if (send_refresh_parent(&r) != nullptr) {
       send_complete();
-      return;
     }
+    return;
   }
 
   CephContext *cct = m_image_ctx.cct;
@@ -272,8 +272,12 @@ Context *SetSnapRequest<I>::handle_open_object_map(int *result) {
   CephContext *cct = m_image_ctx.cct;
   ldout(cct, 10) << __func__ << ": r=" << *result << dendl;
 
-  // object map should never report errors
-  assert(*result == 0);
+  if (*result < 0) {
+    lderr(cct) << "failed to open object map: " << cpp_strerror(*result)
+               << dendl;
+    delete m_object_map;
+    m_object_map = nullptr;
+  }
 
   *result = apply();
   if (*result < 0) {
diff --git a/src/librbd/image_watcher/NotifyLockOwner.cc b/src/librbd/image_watcher/NotifyLockOwner.cc
index 9ee0ebe..09a3e43 100644
--- a/src/librbd/image_watcher/NotifyLockOwner.cc
+++ b/src/librbd/image_watcher/NotifyLockOwner.cc
@@ -78,7 +78,7 @@ void NotifyLockOwner::handle_notify(int r) {
   }
 
   if (!lock_owner_responded) {
-    lderr(cct) << ": no lock owners detected" << dendl;
+    ldout(cct, 1) << ": no lock owners detected" << dendl;
     finish(-ETIMEDOUT);
     return;
   }
diff --git a/src/librbd/internal.cc b/src/librbd/internal.cc
index 5276052..92fab7f 100644
--- a/src/librbd/internal.cc
+++ b/src/librbd/internal.cc
@@ -95,18 +95,26 @@ int remove_object_map(ImageCtx *ictx) {
   }
   return 0;
 }
+
 int create_object_map(ImageCtx *ictx) {
   assert(ictx->snap_lock.is_locked());
   CephContext *cct = ictx->cct;
 
   int r;
+  uint64_t max_size = ictx->size;
   std::vector<uint64_t> snap_ids;
   snap_ids.push_back(CEPH_NOSNAP);
   for (std::map<snap_t, SnapInfo>::iterator it = ictx->snap_info.begin();
        it != ictx->snap_info.end(); ++it) {
+    max_size = MAX(max_size, it->second.size);
     snap_ids.push_back(it->first);
   }
 
+  if (!ObjectMap::is_compatible(ictx->layout, max_size)) {
+    lderr(cct) << "image size not compatible with object map" << dendl;
+    return -EINVAL;
+  }
+
   for (std::vector<uint64_t>::iterator it = snap_ids.begin();
     it != snap_ids.end(); ++it) {
     librados::ObjectWriteOperation op;
@@ -523,7 +531,8 @@ remove_mirroring_image:
       off += r;
     } while (r == READ_SIZE);
 
-    if (memcmp(RBD_HEADER_TEXT, header.c_str(), sizeof(RBD_HEADER_TEXT))) {
+    if (header.length() < sizeof(RBD_HEADER_TEXT) ||
+	memcmp(RBD_HEADER_TEXT, header.c_str(), sizeof(RBD_HEADER_TEXT))) {
       CephContext *cct = (CephContext *)io_ctx.cct();
       lderr(cct) << "unrecognized header format" << dendl;
       return -ENXIO;
@@ -749,6 +758,19 @@ remove_mirroring_image:
     return 0;
   }
 
+  int image_options_is_set(rbd_image_options_t opts, int optname,
+                           bool* is_set)
+  {
+    if (IMAGE_OPTIONS_TYPE_MAPPING.find(optname) ==
+          IMAGE_OPTIONS_TYPE_MAPPING.end()) {
+      return -EINVAL;
+    }
+
+    image_options_ref* opts_ = static_cast<image_options_ref*>(opts);
+    *is_set = ((*opts_)->find(optname) != (*opts_)->end());
+    return 0;
+  }
+
   int image_options_unset(rbd_image_options_t opts, int optname)
   {
     image_options_ref* opts_ = static_cast<image_options_ref*>(opts);
@@ -1084,6 +1106,11 @@ remove_mirroring_image:
         layout.stripe_count = stripe_count;
       }
 
+      if (!ObjectMap::is_compatible(layout, size)) {
+        lderr(cct) << "image size not compatible with object map" << dendl;
+        goto err_remove_header;
+      }
+
       librados::ObjectWriteOperation op;
       cls_client::object_map_resize(&op, Striper::get_num_objects(layout, size),
                                     OBJECT_NONEXISTENT);
@@ -1220,6 +1247,8 @@ remove_mirroring_image:
 	     ImageOptions& opts)
   {
     CephContext *cct = (CephContext *)io_ctx.cct();
+    ldout(cct, 10) << __func__ << " name=" << imgname << ", "
+                   << "size=" << size << ", opts=" << opts << dendl;
 
     uint64_t format = cct->_conf->rbd_default_format;
     opts.get(RBD_IMAGE_OPTION_FORMAT, &format);
@@ -1330,7 +1359,6 @@ remove_mirroring_image:
     uint64_t order = *c_order;
 
     ImageOptions opts;
-    opts.set(RBD_IMAGE_OPTION_FORMAT, static_cast<uint64_t>(2));
     opts.set(RBD_IMAGE_OPTION_FEATURES, features);
     opts.set(RBD_IMAGE_OPTION_ORDER, order);
     opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit);
@@ -1347,10 +1375,16 @@ remove_mirroring_image:
   {
     CephContext *cct = (CephContext *)p_ioctx.cct();
     ldout(cct, 20) << "clone " << &p_ioctx << " name " << p_name << " snap "
-		   << p_snap_name << "to child " << &c_ioctx << " name "
+		   << p_snap_name << " to child " << &c_ioctx << " name "
 		   << c_name << " opts = " << c_opts << dendl;
 
-    uint64_t format = cct->_conf->rbd_default_format;
+    bool default_format_set;
+    c_opts.is_set(RBD_IMAGE_OPTION_FORMAT, &default_format_set);
+    if (!default_format_set) {
+      c_opts.set(RBD_IMAGE_OPTION_FORMAT, static_cast<uint64_t>(2));
+    }
+
+    uint64_t format = 0;
     c_opts.get(RBD_IMAGE_OPTION_FORMAT, &format);
     if (format < 2) {
       lderr(cct) << "format 2 or later required for clone" << dendl;
@@ -1589,40 +1623,57 @@ remove_mirroring_image:
       return -EINVAL;
     }
 
-    {
-      RWLock::RLocker owner_locker(ictx->owner_lock);
-      RWLock::WLocker md_locker(ictx->md_lock);
-      r = ictx->flush();
-      if (r < 0) {
-        return r;
-      }
+    RWLock::RLocker owner_locker(ictx->owner_lock);
+    r = ictx->aio_work_queue->block_writes();
+    BOOST_SCOPE_EXIT_ALL( (ictx) ) {
+      ictx->aio_work_queue->unblock_writes();
+    };
+    if (r < 0) {
+      return r;
+    }
+
+    uint64_t disable_mask = (RBD_FEATURES_MUTABLE |
+                             RBD_FEATURES_DISABLE_ONLY);
+    if ((enabled && (features & RBD_FEATURES_MUTABLE) != features) ||
+        (!enabled && (features & disable_mask) != features)) {
+      lderr(cct) << "cannot update immutable features" << dendl;
+      return -EINVAL;
+    } else if (features == 0) {
+      lderr(cct) << "update requires at least one feature" << dendl;
+      return -EINVAL;
+    }
 
-      uint64_t disable_mask = (RBD_FEATURES_MUTABLE |
-                               RBD_FEATURES_DISABLE_ONLY);
-      if ((enabled && (features & RBD_FEATURES_MUTABLE) != features) ||
-          (!enabled && (features & disable_mask) != features)) {
-        lderr(cct) << "cannot update immutable features" << dendl;
-        return -EINVAL;
-      } else if (features == 0) {
-        lderr(cct) << "update requires at least one feature" << dendl;
-        return -EINVAL;
+    // avoid accepting new requests from peers while we manipulate
+    // the image features
+    if (ictx->exclusive_lock != nullptr) {
+      ictx->exclusive_lock->block_requests();
+    }
+    BOOST_SCOPE_EXIT_ALL( (ictx) ) {
+      if (ictx->exclusive_lock != nullptr) {
+        ictx->exclusive_lock->unblock_requests();
       }
+    };
 
-      // if disabling features w/ exclusive lock supported, we need to
-      // acquire the lock to temporarily block IO against the image
-      if (ictx->exclusive_lock != nullptr && !enabled) {
-        C_SaferCond lock_ctx;
-        ictx->exclusive_lock->request_lock(&lock_ctx);
-        r = lock_ctx.wait();
-        if (r < 0) {
-          lderr(cct) << "failed to lock image: " << cpp_strerror(r) << dendl;
-          return r;
-        } else if (!ictx->exclusive_lock->is_lock_owner()) {
-          lderr(cct) << "failed to acquire exclusive lock" << dendl;
-          return -EROFS;
-        }
+    // if disabling features w/ exclusive lock supported, we need to
+    // acquire the lock to temporarily block IO against the image
+    bool acquired_lock = false;
+    if (ictx->exclusive_lock != nullptr &&
+        !ictx->exclusive_lock->is_lock_owner() && !enabled) {
+      acquired_lock = true;
+
+      C_SaferCond lock_ctx;
+      ictx->exclusive_lock->request_lock(&lock_ctx);
+      r = lock_ctx.wait();
+      if (r < 0) {
+        lderr(cct) << "failed to lock image: " << cpp_strerror(r) << dendl;
+        return r;
+      } else if (!ictx->exclusive_lock->is_lock_owner()) {
+        lderr(cct) << "failed to acquire exclusive lock" << dendl;
+        return -EROFS;
       }
+    }
 
+    {
       RWLock::WLocker snap_locker(ictx->snap_lock);
       uint64_t new_features;
       if (enabled) {
@@ -1799,7 +1850,17 @@ remove_mirroring_image:
           img_ctx->state->close();
         }
       }
-   }
+    }
+
+    if (ictx->exclusive_lock != nullptr && acquired_lock) {
+      C_SaferCond lock_ctx;
+      ictx->exclusive_lock->release_lock(&lock_ctx);
+      r = lock_ctx.wait();
+      if (r < 0) {
+        lderr(cct) << "failed to unlock image: " << cpp_strerror(r) << dendl;
+        return r;
+      }
+    }
 
     ictx->notify_update();
     return 0;
@@ -2877,6 +2938,40 @@ remove_mirroring_image:
     return 0;
   }
 
+  int mirror_image_get_status(ImageCtx *ictx, mirror_image_status_t *status,
+			      size_t status_size) {
+    CephContext *cct = ictx->cct;
+    ldout(cct, 20) << __func__ << ": ictx=" << ictx << dendl;
+    if (status_size < sizeof(mirror_image_status_t)) {
+      return -ERANGE;
+    }
+
+    mirror_image_info_t info;
+    int r = mirror_image_get_info(ictx, &info, sizeof(info));
+    if (r < 0) {
+      return r;
+    }
+
+    cls::rbd::MirrorImageStatus
+      s(cls::rbd::MIRROR_IMAGE_STATUS_STATE_UNKNOWN, "status not found");
+
+    r = cls_client::mirror_image_status_get(&ictx->md_ctx, info.global_id, &s);
+    if (r < 0 && r != -ENOENT) {
+      lderr(cct) << "failed to retrieve image mirror status: "
+		 << cpp_strerror(r) << dendl;
+      return r;
+    }
+
+    *status = mirror_image_status_t{
+      ictx->name,
+      info,
+      static_cast<mirror_image_status_state_t>(s.state),
+      s.description,
+      s.last_update.sec(),
+      s.up};
+    return 0;
+  }
+
   int mirror_mode_get(IoCtx& io_ctx, rbd_mirror_mode_t *mirror_mode) {
     CephContext *cct = reinterpret_cast<CephContext *>(io_ctx.cct());
     ldout(cct, 20) << __func__ << dendl;
@@ -3242,6 +3337,80 @@ remove_mirroring_image:
     return 0;
   }
 
+  int mirror_image_status_list(IoCtx& io_ctx, const std::string &start_id,
+      size_t max, std::map<std::string, mirror_image_status_t> *images) {
+    CephContext *cct = reinterpret_cast<CephContext *>(io_ctx.cct());
+    int r;
+
+    map<string, string> id_to_name;
+    {
+      map<string, string> name_to_id;
+      r = list_images_v2(io_ctx, name_to_id);
+      if (r < 0) {
+	return r;
+      }
+      for (auto it : name_to_id) {
+	id_to_name[it.second] = it.first;
+      }
+    }
+
+    map<std::string, cls::rbd::MirrorImage> images_;
+    map<std::string, cls::rbd::MirrorImageStatus> statuses_;
+
+    r = librbd::cls_client::mirror_image_status_list(&io_ctx, start_id, max,
+						     &images_, &statuses_);
+    if (r < 0) {
+      lderr(cct) << "Failed to list mirror image statuses: "
+                 << cpp_strerror(r) << dendl;
+      return r;
+    }
+
+    cls::rbd::MirrorImageStatus unknown_status(
+      cls::rbd::MIRROR_IMAGE_STATUS_STATE_UNKNOWN, "status not found");
+
+    for (auto it = images_.begin(); it != images_.end(); ++it) {
+      auto &image_id = it->first;
+      auto &info = it->second;
+      auto &image_name = id_to_name[image_id];
+      if (image_name.empty()) {
+	lderr(cct) << "Failed to find image name for image " << image_id
+		   << ", using image id as name" << dendl;
+	image_name = image_id;
+      }
+      auto s_it = statuses_.find(image_id);
+      auto &s = s_it != statuses_.end() ? s_it->second : unknown_status;
+      (*images)[image_id] = mirror_image_status_t{
+	image_name,
+	mirror_image_info_t{
+	  info.global_image_id,
+	  static_cast<mirror_image_state_t>(info.state),
+	  false}, // XXX: To set "primary" right would require an additional call.
+	static_cast<mirror_image_status_state_t>(s.state),
+	s.description,
+	s.last_update.sec(),
+	s.up};
+    }
+
+    return 0;
+  }
+
+  int mirror_image_status_summary(IoCtx& io_ctx,
+    std::map<mirror_image_status_state_t, int> *states) {
+    CephContext *cct = reinterpret_cast<CephContext *>(io_ctx.cct());
+
+    std::map<cls::rbd::MirrorImageStatusState, int> states_;
+    int r = cls_client::mirror_image_status_get_summary(&io_ctx, &states_);
+    if (r < 0) {
+      lderr(cct) << "Failed to get mirror status summary: "
+                 << cpp_strerror(r) << dendl;
+      return r;
+    }
+    for (auto &s : states_) {
+      (*states)[static_cast<mirror_image_status_state_t>(s.first)] = s.second;
+    }
+    return 0;
+  }
+
   void rbd_req_cb(completion_t cb, void *arg)
   {
     AioObjectRequest *req = reinterpret_cast<AioObjectRequest *>(arg);
diff --git a/src/librbd/internal.h b/src/librbd/internal.h
index c2413f4..dcb0350 100644
--- a/src/librbd/internal.h
+++ b/src/librbd/internal.h
@@ -81,6 +81,8 @@ namespace librbd {
 			std::string* optval);
   int image_options_get(rbd_image_options_t opts, int optname,
 			uint64_t* optval);
+  int image_options_is_set(rbd_image_options_t opts, int optname,
+                           bool* is_set);
   int image_options_unset(rbd_image_options_t opts, int optname);
   void image_options_clear(rbd_image_options_t opts);
   bool image_options_is_empty(rbd_image_options_t opts);
@@ -189,6 +191,10 @@ namespace librbd {
                              const std::string &client_name);
   int mirror_peer_set_cluster(IoCtx& io_ctx, const std::string &uuid,
                               const std::string &cluster_name);
+  int mirror_image_status_list(IoCtx& io_ctx, const std::string &start_id,
+      size_t max, std::map<std::string, mirror_image_status_t> *images);
+  int mirror_image_status_summary(IoCtx& io_ctx,
+      std::map<mirror_image_status_state_t, int> *states);
 
   int mirror_image_enable(ImageCtx *ictx);
   int mirror_image_disable(ImageCtx *ictx, bool force);
@@ -197,6 +203,8 @@ namespace librbd {
   int mirror_image_resync(ImageCtx *ictx);
   int mirror_image_get_info(ImageCtx *ictx, mirror_image_info_t *mirror_image_info,
                             size_t info_size);
+  int mirror_image_get_status(ImageCtx *ictx, mirror_image_status_t *status,
+			      size_t status_size);
 }
 
 #endif
diff --git a/src/librbd/journal/Replay.cc b/src/librbd/journal/Replay.cc
index aca64d9..c6c4f55 100644
--- a/src/librbd/journal/Replay.cc
+++ b/src/librbd/journal/Replay.cc
@@ -41,7 +41,7 @@ struct ExecuteOp : public Context {
   void execute(const journal::SnapCreateEvent &_) {
     image_ctx.operations->execute_snap_create(event.snap_name.c_str(),
                                               on_op_complete,
-                                              event.op_tid);
+                                              event.op_tid, false);
   }
 
   void execute(const journal::SnapRemoveEvent &_) {
@@ -166,7 +166,7 @@ void Replay<I>::shut_down(bool cancel_ops, Context *on_finish) {
 
     // safely commit any remaining AIO modify operations
     if ((m_in_flight_aio_flush + m_in_flight_aio_modify) != 0) {
-      flush_comp = create_aio_flush_completion(nullptr, nullptr);;
+      flush_comp = create_aio_flush_completion(nullptr);
     }
 
     for (auto &op_event_pair : m_op_events) {
@@ -214,7 +214,7 @@ void Replay<I>::flush(Context *on_finish) {
   {
     Mutex::Locker locker(m_lock);
     aio_comp = create_aio_flush_completion(
-      nullptr, util::create_async_context_callback(m_image_ctx, on_finish));
+      util::create_async_context_callback(m_image_ctx, on_finish));
   }
 
   RWLock::RLocker owner_locker(m_image_ctx.owner_lock);
@@ -273,7 +273,7 @@ void Replay<I>::handle_event(const journal::AioDiscardEvent &event,
                                   event.length);
   if (flush_required) {
     m_lock.Lock();
-    AioCompletion *flush_comp = create_aio_flush_completion(nullptr, nullptr);
+    AioCompletion *flush_comp = create_aio_flush_completion(nullptr);
     m_lock.Unlock();
 
     AioImageRequest<I>::aio_flush(&m_image_ctx, flush_comp);
@@ -294,7 +294,7 @@ void Replay<I>::handle_event(const journal::AioWriteEvent &event,
                                 event.length, data.c_str(), 0);
   if (flush_required) {
     m_lock.Lock();
-    AioCompletion *flush_comp = create_aio_flush_completion(nullptr, nullptr);
+    AioCompletion *flush_comp = create_aio_flush_completion(nullptr);
     m_lock.Unlock();
 
     AioImageRequest<I>::aio_flush(&m_image_ctx, flush_comp);
@@ -310,9 +310,11 @@ void Replay<I>::handle_event(const journal::AioFlushEvent &event,
   AioCompletion *aio_comp;
   {
     Mutex::Locker locker(m_lock);
-    aio_comp = create_aio_flush_completion(on_ready, on_safe);
+    aio_comp = create_aio_flush_completion(on_safe);
   }
   AioImageRequest<I>::aio_flush(&m_image_ctx, aio_comp);
+
+  on_ready->complete(0);
 }
 
 template <typename I>
@@ -808,8 +810,7 @@ AioCompletion *Replay<I>::create_aio_modify_completion(Context *on_ready,
 }
 
 template <typename I>
-AioCompletion *Replay<I>::create_aio_flush_completion(Context *on_ready,
-                                                      Context *on_safe) {
+AioCompletion *Replay<I>::create_aio_flush_completion(Context *on_safe) {
   assert(m_lock.is_locked());
 
   ++m_in_flight_aio_flush;
@@ -819,10 +820,6 @@ AioCompletion *Replay<I>::create_aio_flush_completion(Context *on_ready,
       new C_AioFlushComplete(this, on_safe,
                              std::move(m_aio_modify_unsafe_contexts)));
   m_aio_modify_unsafe_contexts.clear();
-
-  if (on_ready != nullptr) {
-    on_ready->complete(0);
-  }
   return aio_comp;
 }
 
diff --git a/src/librbd/journal/Replay.h b/src/librbd/journal/Replay.h
index a9d73c0..aeca5ba 100644
--- a/src/librbd/journal/Replay.h
+++ b/src/librbd/journal/Replay.h
@@ -168,8 +168,7 @@ private:
   AioCompletion *create_aio_modify_completion(Context *on_ready,
                                               Context *on_safe,
                                               bool *flush_required);
-  AioCompletion *create_aio_flush_completion(Context *on_ready,
-                                             Context *on_safe);
+  AioCompletion *create_aio_flush_completion(Context *on_safe);
   void handle_aio_completion(AioCompletion *aio_comp);
 
 };
diff --git a/src/librbd/journal/Types.h b/src/librbd/journal/Types.h
index 0f25766..4008a0f 100644
--- a/src/librbd/journal/Types.h
+++ b/src/librbd/journal/Types.h
@@ -59,9 +59,13 @@ struct AioWriteEvent {
   static const EventType TYPE = EVENT_TYPE_AIO_WRITE;
 
   uint64_t offset;
-  size_t length;
+  uint64_t length;
   bufferlist data;
 
+  static uint32_t get_fixed_size() {
+    return 30; /// version encoding, type, offset, length
+  }
+
   AioWriteEvent() : offset(0), length(0) {
   }
   AioWriteEvent(uint64_t _offset, size_t _length, const bufferlist &_data)
diff --git a/src/librbd/librbd.cc b/src/librbd/librbd.cc
index 0cc3f95..2cb5132 100644
--- a/src/librbd/librbd.cc
+++ b/src/librbd/librbd.cc
@@ -132,6 +132,23 @@ struct C_CloseComplete : public Context {
   }
 };
 
+void mirror_image_info_cpp_to_c(const librbd::mirror_image_info_t &cpp_info,
+				rbd_mirror_image_info_t *c_info) {
+  c_info->global_id = strdup(cpp_info.global_id.c_str());
+  c_info->state = cpp_info.state;
+  c_info->primary = cpp_info.primary;
+}
+
+void mirror_image_status_cpp_to_c(const librbd::mirror_image_status_t &cpp_status,
+				  rbd_mirror_image_status_t *c_status) {
+  c_status->name = strdup(cpp_status.name.c_str());
+  mirror_image_info_cpp_to_c(cpp_status.info, &c_status->info);
+  c_status->state = cpp_status.state;
+  c_status->description = strdup(cpp_status.description.c_str());
+  c_status->last_update = cpp_status.last_update;
+  c_status->up = cpp_status.up;
+}
+
 } // anonymous namespace
 
 namespace librbd {
@@ -411,6 +428,16 @@ namespace librbd {
     return librbd::mirror_peer_set_cluster(io_ctx, uuid, cluster_name);
   }
 
+  int RBD::mirror_image_status_list(IoCtx& io_ctx, const std::string &start_id,
+      size_t max, std::map<std::string, mirror_image_status_t> *images) {
+    return librbd::mirror_image_status_list(io_ctx, start_id, max, images);
+  }
+
+  int RBD::mirror_image_status_summary(IoCtx& io_ctx,
+      std::map<mirror_image_status_state_t, int> *states) {
+    return librbd::mirror_image_status_summary(io_ctx, states);
+  }
+
   RBD::AioCompletion::AioCompletion(void *cb_arg, callback_t complete_cb)
   {
     pc = reinterpret_cast<void*>(librbd::AioCompletion::create(
@@ -487,6 +514,11 @@ namespace librbd {
     return librbd::image_options_get(opts, optname, optval);
   }
 
+  int ImageOptions::is_set(int optname, bool* is_set)
+  {
+    return librbd::image_options_is_set(opts, optname, is_set);
+  }
+
   int ImageOptions::unset(int optname)
   {
     return librbd::image_options_unset(opts, optname);
@@ -1258,6 +1290,13 @@ namespace librbd {
     return librbd::mirror_image_get_info(ictx, mirror_image_info, info_size);
   }
 
+  int Image::mirror_image_get_status(mirror_image_status_t *mirror_image_status,
+				     size_t status_size) {
+    ImageCtx *ictx = (ImageCtx *)ctx;
+    return librbd::mirror_image_get_status(ictx, mirror_image_status,
+					   status_size);
+  }
+
 } // namespace librbd
 
 extern "C" void rbd_version(int *major, int *minor, int *extra)
@@ -1318,6 +1357,12 @@ extern "C" int rbd_image_options_get_uint64(rbd_image_options_t opts, int optnam
   return librbd::image_options_get(opts, optname, optval);
 }
 
+extern "C" int rbd_image_options_is_set(rbd_image_options_t opts, int optname,
+                                        bool* is_set)
+{
+  return librbd::image_options_is_set(opts, optname, is_set);
+}
+
 extern "C" int rbd_image_options_unset(rbd_image_options_t opts, int optname)
 {
   return librbd::image_options_unset(opts, optname);
@@ -1425,6 +1470,65 @@ extern "C" int rbd_mirror_peer_set_cluster(rados_ioctx_t p, const char *uuid,
   return librbd::mirror_peer_set_cluster(io_ctx, uuid, cluster_name);
 }
 
+extern "C" int rbd_mirror_image_status_list(rados_ioctx_t p,
+    const char *start_id, size_t max, char **image_ids,
+    rbd_mirror_image_status_t *images, size_t *len) {
+  librados::IoCtx io_ctx;
+  librados::IoCtx::from_rados_ioctx_t(p, io_ctx);
+  std::map<std::string, librbd::mirror_image_status_t> cpp_images;
+
+  int r = librbd::mirror_image_status_list(io_ctx, start_id, max, &cpp_images);
+  if (r < 0) {
+    return r;
+  }
+
+  size_t i = 0;
+  for (auto &it : cpp_images) {
+    assert(i < max);
+    const std::string &image_id = it.first;
+    image_ids[i] = strdup(image_id.c_str());
+    mirror_image_status_cpp_to_c(it.second, &images[i]);
+    i++;
+  }
+  *len = i;
+  return 0;
+}
+
+extern "C" void rbd_mirror_image_status_list_cleanup(char **image_ids,
+    rbd_mirror_image_status_t *images, size_t len) {
+  for (size_t i = 0; i < len; i++) {
+    free(image_ids[i]);
+    free(images[i].name);
+    free(images[i].info.global_id);
+    free(images[i].description);
+  }
+}
+
+extern "C" int rbd_mirror_image_status_summary(rados_ioctx_t p,
+    rbd_mirror_image_status_state_t *states, int *counts, size_t *maxlen) {
+
+  librados::IoCtx io_ctx;
+  librados::IoCtx::from_rados_ioctx_t(p, io_ctx);
+
+  std::map<librbd::mirror_image_status_state_t, int> states_;
+  int r = librbd::mirror_image_status_summary(io_ctx, &states_);
+  if (r < 0) {
+    return r;
+  }
+
+  size_t i = 0;
+  for (auto &it : states_) {
+    if (i == *maxlen) {
+      return -ERANGE;
+    }
+    states[i] = it.first;
+    counts[i] = it.second;
+    i++;
+  }
+  *maxlen = i;
+  return 0;
+}
+
 /* images */
 extern "C" int rbd_list(rados_ioctx_t p, char *names, size_t *size)
 {
@@ -2628,9 +2732,24 @@ extern "C" int rbd_mirror_image_get_info(rbd_image_t image,
     return r;
   }
 
-  mirror_image_info->global_id = strdup(cpp_mirror_image.global_id.c_str());
-  mirror_image_info->state = cpp_mirror_image.state;
-  mirror_image_info->primary = cpp_mirror_image.primary;
+  mirror_image_info_cpp_to_c(cpp_mirror_image, mirror_image_info);
+  return 0;
+}
+
+extern "C" int rbd_mirror_image_get_status(rbd_image_t image,
+					   rbd_mirror_image_status_t *status,
+					   size_t status_size)
+{
+  librbd::ImageCtx *ictx = (librbd::ImageCtx *)image;
+
+  librbd::mirror_image_status_t cpp_status;
+  int r = librbd::mirror_image_get_status(ictx, &cpp_status,
+					  sizeof(cpp_status));
+  if (r < 0) {
+    return r;
+  }
+
+  mirror_image_status_cpp_to_c(cpp_status, status);
   return 0;
 }
 
diff --git a/src/librbd/object_map/InvalidateRequest.cc b/src/librbd/object_map/InvalidateRequest.cc
index 3566fdd..41ef46c 100644
--- a/src/librbd/object_map/InvalidateRequest.cc
+++ b/src/librbd/object_map/InvalidateRequest.cc
@@ -47,6 +47,7 @@ void InvalidateRequest<I>::send() {
   r = image_ctx.update_flags(m_snap_id, flags, true);
   if (r < 0) {
     this->async_complete(r);
+    return;
   }
 
   // do not update on-disk flags if not image owner
diff --git a/src/librbd/object_map/RefreshRequest.cc b/src/librbd/object_map/RefreshRequest.cc
index 048f72f..1bd465e 100644
--- a/src/librbd/object_map/RefreshRequest.cc
+++ b/src/librbd/object_map/RefreshRequest.cc
@@ -3,9 +3,11 @@
 
 #include "librbd/object_map/RefreshRequest.h"
 #include "cls/rbd/cls_rbd_client.h"
+#include "cls/rbd/cls_rbd_types.h"
 #include "cls/lock/cls_lock_client.h"
 #include "common/dout.h"
 #include "common/errno.h"
+#include "common/WorkQueue.h"
 #include "librbd/ImageCtx.h"
 #include "librbd/ObjectMap.h"
 #include "librbd/object_map/InvalidateRequest.h"
@@ -42,6 +44,10 @@ void RefreshRequest<I>::send() {
       m_image_ctx.layout, m_image_ctx.get_image_size(m_snap_id));
   }
 
+
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 20) << this << " " << __func__ << ": "
+                 << "object_count=" << m_object_count << dendl;
   send_lock();
 }
 
@@ -60,12 +66,15 @@ void RefreshRequest<I>::apply() {
 
 template <typename I>
 void RefreshRequest<I>::send_lock() {
-  if (m_snap_id != CEPH_NOSNAP) {
+  CephContext *cct = m_image_ctx.cct;
+  if (m_object_count > cls::rbd::MAX_OBJECT_MAP_OBJECT_COUNT) {
+    send_invalidate_and_close();
+    return;
+  } else if (m_snap_id != CEPH_NOSNAP) {
     send_load();
     return;
   }
 
-  CephContext *cct = m_image_ctx.cct;
   std::string oid(ObjectMap::object_map_name(m_image_ctx.id, m_snap_id));
   ldout(cct, 10) << this << " " << __func__ << ": oid=" << oid << dendl;
 
@@ -248,6 +257,35 @@ Context *RefreshRequest<I>::handle_resize(int *ret_val) {
   return m_on_finish;
 }
 
+template <typename I>
+void RefreshRequest<I>::send_invalidate_and_close() {
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 10) << this << " " << __func__ << dendl;
+
+  using klass = RefreshRequest<I>;
+  Context *ctx = create_context_callback<
+    klass, &klass::handle_invalidate_and_close>(this);
+  InvalidateRequest<I> *req = InvalidateRequest<I>::create(
+    m_image_ctx, m_snap_id, false, ctx);
+
+  lderr(cct) << "object map too large: " << m_object_count << dendl;
+  RWLock::RLocker owner_locker(m_image_ctx.owner_lock);
+  RWLock::WLocker snap_locker(m_image_ctx.snap_lock);
+  req->send();
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_invalidate_and_close(int *ret_val) {
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 10) << this << " " << __func__ << ": r=" << *ret_val << dendl;
+
+  assert(*ret_val == 0);
+
+  *ret_val = -EFBIG;
+  m_object_map->clear();
+  return m_on_finish;
+}
+
 } // namespace object_map
 } // namespace librbd
 
diff --git a/src/librbd/object_map/RefreshRequest.h b/src/librbd/object_map/RefreshRequest.h
index 2c94d1e..9ae1f27 100644
--- a/src/librbd/object_map/RefreshRequest.h
+++ b/src/librbd/object_map/RefreshRequest.h
@@ -28,24 +28,25 @@ private:
    * @verbatim
    *
    * <start> -----> LOCK (skip if snapshot)
-   *                  |
-   *                  v  (other errors)
-   *                LOAD * * * * * * * > INVALIDATE ------------\
-   *                  |    *                                    |
-   *                  |    * (-EINVAL or too small)             |
-   *                  |    * * * * * * > INVALIDATE_AND_RESIZE  |
-   *                  |                      |              *   |
-   *                  |                      |              *   |
-   *                  |                      v              *   |
-   *                  |                    RESIZE           *   |
-   *                  |                      |              *   |
-   *                  |                      |  * * * * * * *   |
-   *                  |                      |  *               |
-   *                  |                      v  v               |
-   *                  \--------------------> LOCK <-------------/
-   *                                          |
-   *                                          v
-   *                                      <finish>
+   *    *             |
+   *    *             v  (other errors)
+   *    *           LOAD * * * * * * * > INVALIDATE ------------\
+   *    *             |    *                                    |
+   *    *             |    * (-EINVAL or too small)             |
+   *    *             |    * * * * * * > INVALIDATE_AND_RESIZE  |
+   *    *             |                      |              *   |
+   *    *             |                      |              *   |
+   *    *             |                      v              *   |
+   *    *             |                    RESIZE           *   |
+   *    *             |                      |              *   |
+   *    *             |                      |  * * * * * * *   |
+   *    *             |                      |  *               |
+   *    *             |                      v  v               |
+   *    *             \--------------------> LOCK <-------------/
+   *    *                                     |
+   *    v                                     v
+   * INVALIDATE_AND_CLOSE ---------------> <finish>
+   *
    * @endverbatim
    */
 
@@ -74,6 +75,9 @@ private:
   void send_resize();
   Context *handle_resize(int *ret_val);
 
+  void send_invalidate_and_close();
+  Context *handle_invalidate_and_close(int *ret_val);
+
   void apply();
 };
 
diff --git a/src/librbd/object_map/Request.cc b/src/librbd/object_map/Request.cc
index e95c22a..48cd99f 100644
--- a/src/librbd/object_map/Request.cc
+++ b/src/librbd/object_map/Request.cc
@@ -20,7 +20,7 @@ namespace object_map {
 
 bool Request::should_complete(int r) {
   CephContext *cct = m_image_ctx.cct;
-  ldout(cct, 20) << &m_image_ctx << " should_complete: r=" << r << dendl;
+  ldout(cct, 20) << this << " should_complete: r=" << r << dendl;
 
   switch (m_state)
   {
diff --git a/src/librbd/object_map/ResizeRequest.cc b/src/librbd/object_map/ResizeRequest.cc
index fcc6ec6..e574a18 100644
--- a/src/librbd/object_map/ResizeRequest.cc
+++ b/src/librbd/object_map/ResizeRequest.cc
@@ -31,7 +31,8 @@ void ResizeRequest::send() {
   m_num_objs = Striper::get_num_objects(m_image_ctx.layout, m_new_size);
 
   std::string oid(ObjectMap::object_map_name(m_image_ctx.id, m_snap_id));
-  ldout(cct, 5) << &m_image_ctx << " resizing on-disk object map: "
+  ldout(cct, 5) << this << " resizing on-disk object map: "
+                << "ictx=" << &m_image_ctx << ", "
                 << "oid=" << oid << ", num_objs=" << m_num_objs << dendl;
 
   librados::ObjectWriteOperation op;
@@ -49,7 +50,7 @@ void ResizeRequest::send() {
 void ResizeRequest::finish_request() {
   CephContext *cct = m_image_ctx.cct;
 
-  ldout(cct, 5) << &m_image_ctx << " resizing in-memory object map: "
+  ldout(cct, 5) << this << " resizing in-memory object map: "
 		<< m_num_objs << dendl;
   resize(m_object_map, m_num_objs, m_default_object_state);
 }
diff --git a/src/librbd/object_map/UpdateRequest.cc b/src/librbd/object_map/UpdateRequest.cc
index e8a5f47..5dd1e53 100644
--- a/src/librbd/object_map/UpdateRequest.cc
+++ b/src/librbd/object_map/UpdateRequest.cc
@@ -26,8 +26,8 @@ void UpdateRequest::send() {
   // safe to update in-memory state first without handling rollback since any
   // failures will invalidate the object map
   std::string oid(ObjectMap::object_map_name(m_image_ctx.id, m_snap_id));
-  ldout(cct, 20) << &m_image_ctx << " updating object map"
-                 << ": oid=" << oid << ", ["
+  ldout(cct, 20) << this << " updating object map"
+                 << ": ictx=" << &m_image_ctx << ", oid=" << oid << ", ["
 		 << m_start_object_no << "," << m_end_object_no << ") = "
 		 << (m_current_state ?
 		       stringify(static_cast<uint32_t>(*m_current_state)) : "")
@@ -62,7 +62,7 @@ void UpdateRequest::send() {
 }
 
 void UpdateRequest::finish_request() {
-  ldout(m_image_ctx.cct, 20) << &m_image_ctx << " on-disk object map updated"
+  ldout(m_image_ctx.cct, 20) << this << " on-disk object map updated"
                              << dendl;
 }
 
diff --git a/src/librbd/operation/FlattenRequest.cc b/src/librbd/operation/FlattenRequest.cc
index cc4842e..59bdb29 100644
--- a/src/librbd/operation/FlattenRequest.cc
+++ b/src/librbd/operation/FlattenRequest.cc
@@ -67,7 +67,10 @@ bool FlattenRequest<I>::should_complete(int r) {
   I &image_ctx = this->m_image_ctx;
   CephContext *cct = image_ctx.cct;
   ldout(cct, 5) << this << " should_complete: " << " r=" << r << dendl;
-  if (r < 0 && !(r == -ENOENT && m_ignore_enoent) ) {
+  if (r == -ERESTART) {
+    ldout(cct, 5) << "flatten operation interrupted" << dendl;
+    return true;
+  } else if (r < 0 && !(r == -ENOENT && m_ignore_enoent) ) {
     lderr(cct) << "flatten encountered an error: " << cpp_strerror(r) << dendl;
     return true;
   }
diff --git a/src/librbd/operation/RebuildObjectMapRequest.cc b/src/librbd/operation/RebuildObjectMapRequest.cc
index 9b607fe..da2e744 100644
--- a/src/librbd/operation/RebuildObjectMapRequest.cc
+++ b/src/librbd/operation/RebuildObjectMapRequest.cc
@@ -225,7 +225,10 @@ bool RebuildObjectMapRequest<I>::should_complete(int r) {
     break;
   }
 
-  if (r < 0) {
+  if (r == -ERESTART) {
+    ldout(cct, 5) << "rebuild object map operation interrupted" << dendl;
+    return true;
+  } else if (r < 0) {
     lderr(cct) << "rebuild object map encountered an error: " << cpp_strerror(r)
                << dendl;
     return true;
diff --git a/src/librbd/operation/ResizeRequest.cc b/src/librbd/operation/ResizeRequest.cc
index 300fa8b..a2ee7b0 100644
--- a/src/librbd/operation/ResizeRequest.cc
+++ b/src/librbd/operation/ResizeRequest.cc
@@ -161,7 +161,10 @@ Context *ResizeRequest<I>::handle_trim_image(int *result) {
   CephContext *cct = image_ctx.cct;
   ldout(cct, 5) << this << " " << __func__ << ": r=" << *result << dendl;
 
-  if (*result < 0) {
+  if (*result == -ERESTART) {
+    ldout(cct, 5) << "resize operation interrupted" << dendl;
+    return this->create_context_finisher();
+  } else if (*result < 0) {
     lderr(cct) << "failed to trim image: " << cpp_strerror(*result) << dendl;
     return this->create_context_finisher();
   }
diff --git a/src/librbd/operation/SnapshotCreateRequest.cc b/src/librbd/operation/SnapshotCreateRequest.cc
index e4a1fc3..cb92c6c 100644
--- a/src/librbd/operation/SnapshotCreateRequest.cc
+++ b/src/librbd/operation/SnapshotCreateRequest.cc
@@ -62,9 +62,10 @@ template <typename I>
 SnapshotCreateRequest<I>::SnapshotCreateRequest(I &image_ctx,
                                                 Context *on_finish,
                                                 const std::string &snap_name,
-                                                uint64_t journal_op_tid)
+                                                uint64_t journal_op_tid,
+                                                bool skip_object_map)
   : Request<I>(image_ctx, on_finish, journal_op_tid), m_snap_name(snap_name),
-    m_ret_val(0), m_snap_id(CEPH_NOSNAP) {
+    m_skip_object_map(skip_object_map), m_ret_val(0), m_snap_id(CEPH_NOSNAP) {
 }
 
 template <typename I>
@@ -247,7 +248,7 @@ Context *SnapshotCreateRequest<I>::send_create_object_map() {
   update_snap_context();
 
   image_ctx.snap_lock.get_read();
-  if (image_ctx.object_map == nullptr) {
+  if (image_ctx.object_map == nullptr || m_skip_object_map) {
     image_ctx.snap_lock.put_read();
 
     finalize(0);
diff --git a/src/librbd/operation/SnapshotCreateRequest.h b/src/librbd/operation/SnapshotCreateRequest.h
index 6b52681..35f8b53 100644
--- a/src/librbd/operation/SnapshotCreateRequest.h
+++ b/src/librbd/operation/SnapshotCreateRequest.h
@@ -60,7 +60,8 @@ public:
    * (if enabled) and bubble the originating error code back to the client.
    */
   SnapshotCreateRequest(ImageCtxT &image_ctx, Context *on_finish,
-		        const std::string &snap_name, uint64_t journal_op_tid);
+		        const std::string &snap_name, uint64_t journal_op_tid,
+                        bool skip_object_map);
 
 protected:
   virtual void send_op();
@@ -76,6 +77,7 @@ protected:
 
 private:
   std::string m_snap_name;
+  bool m_skip_object_map;
 
   int m_ret_val;
 
diff --git a/src/librbd/operation/SnapshotRollbackRequest.cc b/src/librbd/operation/SnapshotRollbackRequest.cc
index 8bc5b33..6dcf3a7 100644
--- a/src/librbd/operation/SnapshotRollbackRequest.cc
+++ b/src/librbd/operation/SnapshotRollbackRequest.cc
@@ -222,7 +222,10 @@ Context *SnapshotRollbackRequest<I>::handle_rollback_objects(int *result) {
   CephContext *cct = image_ctx.cct;
   ldout(cct, 5) << this << " " << __func__ << ": r=" << *result << dendl;
 
-  if (*result < 0) {
+  if (*result == -ERESTART) {
+    ldout(cct, 5) << "snapshot rollback operation interrupted" << dendl;
+    return this->create_context_finisher();
+  } else if (*result < 0) {
     lderr(cct) << "failed to rollback objects: " << cpp_strerror(*result)
                << dendl;
     return this->create_context_finisher();
diff --git a/src/librbd/operation/SnapshotUnprotectRequest.cc b/src/librbd/operation/SnapshotUnprotectRequest.cc
index 65b7167..5ca98f9 100644
--- a/src/librbd/operation/SnapshotUnprotectRequest.cc
+++ b/src/librbd/operation/SnapshotUnprotectRequest.cc
@@ -259,7 +259,7 @@ void SnapshotUnprotectRequest<I>::send_scan_pool_children() {
     boost::lambda::bind(boost::lambda::new_ptr<C_ScanPoolChildren<I> >(),
       boost::lambda::_1, &image_ctx, pspec, pools, boost::lambda::_2));
   AsyncObjectThrottle<I> *throttle = new AsyncObjectThrottle<I>(
-    this, image_ctx, context_factory, ctx, NULL, 0, pools.size());
+    nullptr, image_ctx, context_factory, ctx, NULL, 0, pools.size());
   throttle->start_ops(image_ctx.concurrent_management_ops);
 }
 
diff --git a/src/librbd/operation/TrimRequest.cc b/src/librbd/operation/TrimRequest.cc
index 8e32f3e..3ed96f7 100644
--- a/src/librbd/operation/TrimRequest.cc
+++ b/src/librbd/operation/TrimRequest.cc
@@ -122,7 +122,10 @@ bool TrimRequest<I>::should_complete(int r)
   I &image_ctx = this->m_image_ctx;
   CephContext *cct = image_ctx.cct;
   ldout(cct, 5) << this << " should_complete: r=" << r << dendl;
-  if (r < 0) {
+  if (r == -ERESTART) {
+    ldout(cct, 5) << "trim operation interrupted" << dendl;
+    return true;
+  } else if (r < 0) {
     lderr(cct) << "trim encountered an error: " << cpp_strerror(r) << dendl;
     return true;
   }
diff --git a/src/mds/Beacon.cc b/src/mds/Beacon.cc
index 9a07b91..06020af 100644
--- a/src/mds/Beacon.cc
+++ b/src/mds/Beacon.cc
@@ -34,10 +34,10 @@
 
 Beacon::Beacon(CephContext *cct_, MonClient *monc_, std::string name_) :
   Dispatcher(cct_), lock("Beacon"), monc(monc_), timer(g_ceph_context, lock),
-  name(name_), standby_for_rank(MDSMap::MDS_NO_STANDBY_PREF),
-  standby_for_fscid(FS_CLUSTER_ID_NONE), awaiting_seq(-1)
+  name(name_), standby_for_rank(MDS_RANK_NONE),
+  standby_for_fscid(FS_CLUSTER_ID_NONE), want_state(MDSMap::STATE_BOOT),
+  awaiting_seq(-1)
 {
-  want_state = MDSMap::STATE_NULL;
   last_seq = 0;
   sender = NULL;
   was_laggy = false;
@@ -51,18 +51,16 @@ Beacon::~Beacon()
 }
 
 
-void Beacon::init(MDSMap const *mdsmap, MDSMap::DaemonState want_state_,
-    mds_rank_t standby_rank_, std::string const & standby_name_,
-    fs_cluster_id_t standby_fscid_)
+void Beacon::init(MDSMap const *mdsmap)
 {
   Mutex::Locker l(lock);
   assert(mdsmap != NULL);
 
-  want_state = want_state_;
   _notify_mdsmap(mdsmap);
-  standby_for_rank = standby_rank_;
-  standby_for_name = standby_name_;
-  standby_for_fscid = standby_fscid_;
+  standby_for_rank = mds_rank_t(g_conf->mds_standby_for_rank);
+  standby_for_name = g_conf->mds_standby_for_name;
+  standby_for_fscid = fs_cluster_id_t(g_conf->mds_standby_for_fscid);
+  standby_replay = g_conf->mds_standby_replay;
 
   // Spawn threads and start messaging
   timer.init();
@@ -210,6 +208,7 @@ void Beacon::_send()
   beacon->set_standby_for_rank(standby_for_rank);
   beacon->set_standby_for_name(standby_for_name);
   beacon->set_standby_for_fscid(standby_for_fscid);
+  beacon->set_standby_replay(standby_replay);
   beacon->set_health(health);
   beacon->set_compat(compat);
   // piggyback the sys info on beacon msg
diff --git a/src/mds/Beacon.h b/src/mds/Beacon.h
index e8368cf..1a29f24 100644
--- a/src/mds/Beacon.h
+++ b/src/mds/Beacon.h
@@ -51,6 +51,7 @@ class Beacon : public Dispatcher
   mds_rank_t standby_for_rank;
   std::string standby_for_name;
   fs_cluster_id_t standby_for_fscid;
+  bool standby_replay;
   MDSMap::DaemonState want_state;
 
   // Internal beacon state
@@ -86,9 +87,7 @@ public:
   Beacon(CephContext *cct_, MonClient *monc_, std::string name);
   ~Beacon();
 
-  void init(MDSMap const *mdsmap, MDSMap::DaemonState want_state_,
-      mds_rank_t standby_rank_, std::string const &standby_name_,
-      fs_cluster_id_t standby_fscid_);
+  void init(MDSMap const *mdsmap);
   void shutdown();
 
   bool ms_dispatch(Message *m); 
diff --git a/src/mds/FSMap.cc b/src/mds/FSMap.cc
index a20bb09..32b04db 100644
--- a/src/mds/FSMap.cc
+++ b/src/mds/FSMap.cc
@@ -182,7 +182,12 @@ void FSMap::print_summary(Formatter *f, ostream *out)
         }
         *out << " " << pretty;
       } else {
-        *out << " " << by_rank;
+        // Omit FSCID in output when only one filesystem exists
+        std::map<mds_rank_t, std::string> shortened;
+        for (auto i : by_rank) {
+          shortened[i.first.rank] = i.second;
+        }
+        *out << " " << shortened;
       }
     }
   }
@@ -271,6 +276,15 @@ void FSMap::encode(bufferlist& bl, uint64_t features) const
       for (const auto p : standby_daemons) {
         full_mdsmap.mds_info[p.first] = p.second;
       }
+
+      // Old MDSMaps don't set rank on standby replay daemons
+      for (auto &i : full_mdsmap.mds_info) {
+        auto &info = i.second;
+        if (info.state == MDSMap::STATE_STANDBY_REPLAY) {
+          info.rank = MDS_RANK_NONE;
+        }
+      }
+
       full_mdsmap.encode(bl, features);
     }
   }
@@ -334,7 +348,8 @@ void FSMap::decode(bufferlist::iterator& p)
     ::decode(legacy_mds_map.modified, p);
     ::decode(legacy_mds_map.tableserver, p);
     ::decode(legacy_mds_map.in, p);
-    ::decode(legacy_mds_map.inc, p);
+    std::map<mds_rank_t,int32_t> inc;  // Legacy field, parse and drop
+    ::decode(inc, p);
     ::decode(legacy_mds_map.up, p);
     ::decode(legacy_mds_map.failed, p);
     ::decode(legacy_mds_map.stopped, p);
@@ -407,7 +422,12 @@ void FSMap::decode(bufferlist::iterator& p)
 
       // Construct mds_roles, standby_daemons, and remove
       // standbys from the MDSMap in the Filesystem.
-      for (const auto &p : migrate_fs->mds_map.mds_info) {
+      for (auto &p : migrate_fs->mds_map.mds_info) {
+        if (p.second.state == MDSMap::STATE_STANDBY_REPLAY) {
+          // In legacy MDSMap, standby replay daemons don't have
+          // rank set, but since FSMap they do.
+          p.second.rank = p.second.standby_for_rank;
+        }
         if (p.second.rank == MDS_RANK_NONE) {
           standby_daemons[p.first] = p.second;
           standby_epochs[p.first] = epoch;
@@ -553,10 +573,10 @@ mds_gid_t FSMap::find_unused(fs_cluster_id_t fscid,
         info.standby_for_fscid != fscid)
       continue;
 
-    if ((info.standby_for_rank == MDSMap::MDS_NO_STANDBY_PREF ||
-         info.standby_for_rank == MDSMap::MDS_MATCHED_ACTIVE ||
-         (info.standby_for_rank == MDSMap::MDS_STANDBY_ANY
-          && force_standby_active))) {
+    // To be considered 'unused' a daemon must either not
+    // be selected for standby-replay or the force_standby_active
+    // setting must be enabled to use replay daemons anyway.
+    if (!info.standby_replay || force_standby_active) {
       return gid;
     }
   }
@@ -663,7 +683,7 @@ void FSMap::promote(
     mds_map.failed.erase(assigned_rank);
   }
   info.rank = assigned_rank;
-  info.inc = ++mds_map.inc[assigned_rank];
+  info.inc = epoch;
   mds_roles[standby_gid] = filesystem->fscid;
 
   // Update the rank state in Filesystem
diff --git a/src/mds/MDSAuthCaps.cc b/src/mds/MDSAuthCaps.cc
index c888780..c774d3f 100644
--- a/src/mds/MDSAuthCaps.cc
+++ b/src/mds/MDSAuthCaps.cc
@@ -56,7 +56,7 @@ struct MDSCapParser : qi::grammar<Iterator, MDSAuthCaps()>
     quoted_path %=
       lexeme[lit("\"") >> *(char_ - '"') >> '"'] | 
       lexeme[lit("'") >> *(char_ - '\'') >> '\''];
-    unquoted_path %= +char_("a-zA-Z0-9_.-/");
+    unquoted_path %= +char_("a-zA-Z0-9_./-");
 
     // match := [path=<path>] [uid=<uid> [gids=<gid>[,<gid>...]]
     path %= (spaces >> lit("path") >> lit('=') >> (quoted_path | unquoted_path));
diff --git a/src/mds/MDSDaemon.cc b/src/mds/MDSDaemon.cc
index a950b0b..e7d9fe5 100644
--- a/src/mds/MDSDaemon.cc
+++ b/src/mds/MDSDaemon.cc
@@ -120,8 +120,6 @@ MDSDaemon::MDSDaemon(const std::string &n, Messenger *m, MonClient *mc) :
   log_client(m->cct, messenger, &mc->monmap, LogClient::NO_FLAGS),
   mds_rank(NULL),
   tick_event(0),
-  standby_for_rank(MDSMap::MDS_NO_STANDBY_PREF),
-  standby_type(MDSMap::STATE_NULL),
   asok_hook(NULL)
 {
   orig_argc = 0;
@@ -438,7 +436,7 @@ void MDSDaemon::handle_conf_change(const struct md_config_t *conf,
 }
 
 
-int MDSDaemon::init(MDSMap::DaemonState wanted_state)
+int MDSDaemon::init()
 {
   dout(10) << sizeof(MDSCacheObject) << "\tMDSCacheObject" << dendl;
   dout(10) << sizeof(CInode) << "\tCInode" << dendl;
@@ -545,44 +543,7 @@ int MDSDaemon::init(MDSMap::DaemonState wanted_state)
 
   timer.init();
 
-  if (wanted_state==MDSMap::STATE_BOOT && g_conf->mds_standby_replay) {
-    wanted_state = MDSMap::STATE_STANDBY_REPLAY;
-  }
-
-  // starting beacon.  this will induce an MDSMap from the monitor
-  if (wanted_state==MDSMap::STATE_STANDBY_REPLAY ||
-      wanted_state==MDSMap::STATE_ONESHOT_REPLAY) {
-    g_conf->set_val_or_die("mds_standby_replay", "true");
-    g_conf->apply_changes(NULL);
-    if ( wanted_state == MDSMap::STATE_ONESHOT_REPLAY &&
-        (g_conf->mds_standby_for_rank == -1) &&
-        g_conf->mds_standby_for_name.empty()) {
-      // uh-oh, must specify one or the other!
-      dout(0) << "Specified oneshot replay mode but not an MDS!" << dendl;
-      suicide();
-    }
-    standby_type = wanted_state;
-    wanted_state = MDSMap::STATE_BOOT;
-  }
-
-  standby_for_rank = mds_rank_t(g_conf->mds_standby_for_rank);
-  standby_for_name.assign(g_conf->mds_standby_for_name);
-
-  if (standby_type == MDSMap::STATE_STANDBY_REPLAY &&
-      standby_for_rank == -1) {
-    if (standby_for_name.empty())
-      standby_for_rank = MDSMap::MDS_STANDBY_ANY;
-    else
-      standby_for_rank = MDSMap::MDS_STANDBY_NAME;
-  } else if (standby_type == MDSMap::STATE_NULL && !standby_for_name.empty())
-    standby_for_rank = MDSMap::MDS_MATCHED_ACTIVE;
-
-  if (wanted_state == MDSMap::STATE_NULL) {
-    wanted_state = MDSMap::STATE_BOOT;
-  }
-  beacon.init(mdsmap, wanted_state,
-    standby_for_rank, standby_for_name,
-    fs_cluster_id_t(g_conf->mds_standby_for_fscid));
+  beacon.init(mdsmap);
   messenger->set_myname(entity_name_t::MDS(MDS_RANK_NONE));
 
   // schedule tick
@@ -974,19 +935,8 @@ void MDSDaemon::handle_mds_map(MMDSMap *m)
     }
   }
 
-  // If I was put into standby replay, but I am configured for a different standby
-  // type, ignore the map's state and request my standby type (only used
-  // for oneshot replay?)
-  if (new_state == MDSMap::STATE_STANDBY_REPLAY) {
-    if (standby_type != MDSMap::STATE_NULL && standby_type != MDSMap::STATE_STANDBY_REPLAY) {
-      beacon.set_want_state(mdsmap, standby_type);
-      beacon.send();
-      goto out;
-    }
-  }
-
-  if (whoami == MDS_RANK_NONE && (
-      new_state == MDSMap::STATE_STANDBY_REPLAY || new_state == MDSMap::STATE_ONESHOT_REPLAY)) {
+  if (whoami == MDS_RANK_NONE && 
+      new_state == MDSMap::STATE_STANDBY_REPLAY) {
     whoami = mdsmap->get_mds_info_gid(mds_gid_t(monc->get_global_id())).standby_for_rank;
   }
 
@@ -1066,10 +1016,6 @@ void MDSDaemon::_handle_mds_map(MDSMap *oldmap)
     beacon.set_want_state(mdsmap, new_state);
     dout(1) << "handle_mds_map standby" << dendl;
 
-    if (standby_type != MDSMap::STATE_NULL) {// we want to be in standby_replay or oneshot_replay!
-      beacon.set_want_state(mdsmap, standby_type);
-      beacon.send();
-    }
     return;
   }
 
diff --git a/src/mds/MDSDaemon.h b/src/mds/MDSDaemon.h
index 783f37e..4d6296c 100644
--- a/src/mds/MDSDaemon.h
+++ b/src/mds/MDSDaemon.h
@@ -109,8 +109,7 @@ class MDSDaemon : public Dispatcher, public md_config_obs_t {
   // handle a signal (e.g., SIGTERM)
   void handle_signal(int signum);
 
-  // start up, shutdown
-  int init(MDSMap::DaemonState wanted_state=MDSMap::STATE_BOOT);
+  int init();
 
   /**
    * Hint at whether we were shutdown gracefully (i.e. we were only
@@ -142,10 +141,6 @@ class MDSDaemon : public Dispatcher, public md_config_obs_t {
 
   void wait_for_omap_osds();
 
-  mds_rank_t standby_for_rank;
-  string standby_for_name;
-  MDSMap::DaemonState standby_type;  // one of STANDBY_REPLAY, ONESHOT_REPLAY
-
  private:
   bool ms_dispatch(Message *m);
   bool ms_get_authorizer(int dest_type, AuthAuthorizer **authorizer, bool force_new);
diff --git a/src/mds/MDSMap.cc b/src/mds/MDSMap.cc
index f66fc7a..f08e78e 100644
--- a/src/mds/MDSMap.cc
+++ b/src/mds/MDSMap.cc
@@ -19,11 +19,6 @@
 using std::stringstream;
 
 
-const mds_rank_t MDSMap::MDS_NO_STANDBY_PREF(-1);
-const mds_rank_t MDSMap::MDS_STANDBY_ANY(-2);
-const mds_rank_t MDSMap::MDS_STANDBY_NAME(-3);
-const mds_rank_t MDSMap::MDS_MATCHED_ACTIVE(-4);
-
 // features
 CompatSet get_mdsmap_compat_set_all() {
   CompatSet::FeatureSet feature_compat;
@@ -83,6 +78,7 @@ void MDSMap::mds_info_t::dump(Formatter *f) const
   f->dump_int("standby_for_rank", standby_for_rank);
   f->dump_int("standby_for_fscid", standby_for_fscid);
   f->dump_string("standby_for_name", standby_for_name);
+  f->dump_bool("standby_replay", standby_replay);
   f->open_array_section("export_targets");
   for (set<mds_rank_t>::iterator p = export_targets.begin();
        p != export_targets.end(); ++p) {
@@ -408,7 +404,7 @@ void MDSMap::get_health(list<pair<health_status_t,string> >& summary,
 
 void MDSMap::mds_info_t::encode_versioned(bufferlist& bl, uint64_t features) const
 {
-  ENCODE_START(6, 4, bl);
+  ENCODE_START(7, 4, bl);
   ::encode(global_id, bl);
   ::encode(name, bl);
   ::encode(rank, bl);
@@ -422,6 +418,7 @@ void MDSMap::mds_info_t::encode_versioned(bufferlist& bl, uint64_t features) con
   ::encode(export_targets, bl);
   ::encode(mds_features, bl);
   ::encode(standby_for_fscid, bl);
+  ::encode(standby_replay, bl);
   ENCODE_FINISH(bl);
 }
 
@@ -444,7 +441,7 @@ void MDSMap::mds_info_t::encode_unversioned(bufferlist& bl) const
 
 void MDSMap::mds_info_t::decode(bufferlist::iterator& bl)
 {
-  DECODE_START_LEGACY_COMPAT_LEN(6, 4, 4, bl);
+  DECODE_START_LEGACY_COMPAT_LEN(7, 4, 4, bl);
   ::decode(global_id, bl);
   ::decode(name, bl);
   ::decode(rank, bl);
@@ -462,6 +459,9 @@ void MDSMap::mds_info_t::decode(bufferlist::iterator& bl)
   if (struct_v >= 6) {
     ::decode(standby_for_fscid, bl);
   }
+  if (struct_v >= 7) {
+    ::decode(standby_replay, bl);
+  }
   DECODE_FINISH(bl);
 }
 
@@ -469,6 +469,13 @@ void MDSMap::mds_info_t::decode(bufferlist::iterator& bl)
 
 void MDSMap::encode(bufferlist& bl, uint64_t features) const
 {
+  std::map<mds_rank_t,int32_t> inc;  // Legacy field, fake it so that
+                                     // old-mon peers have something sane
+                                     // during upgrade
+  for (const auto rank : in) {
+    inc.insert(std::make_pair(rank, epoch));
+  }
+
   if ((features & CEPH_FEATURE_PGID64) == 0) {
     __u16 v = 2;
     ::encode(v, bl);
@@ -573,6 +580,8 @@ void MDSMap::encode(bufferlist& bl, uint64_t features) const
 
 void MDSMap::decode(bufferlist::iterator& p)
 {
+  std::map<mds_rank_t,int32_t> inc;  // Legacy field, parse and drop
+
   cached_up_features = 0;
   DECODE_START_LEGACY_COMPAT_LEN_16(5, 4, 4, p);
   ::decode(epoch, p);
diff --git a/src/mds/MDSMap.h b/src/mds/MDSMap.h
index bb69a75..60aaf00 100644
--- a/src/mds/MDSMap.h
+++ b/src/mds/MDSMap.h
@@ -89,7 +89,6 @@ public:
     // States of an MDS rank, and of any MDS daemon holding that rank
     // ==============================================================
     STATE_STOPPED  =   CEPH_MDS_STATE_STOPPED,        // down, once existed, but no subtrees. empty log.  may not be held by a daemon.
-    STATE_ONESHOT_REPLAY = CEPH_MDS_STATE_REPLAYONCE, // up, replaying active node journal to verify it, then shutting down
 
     STATE_CREATING  =  CEPH_MDS_STATE_CREATING,       // up, creating MDS instance (new journal, idalloc..).
     STATE_STARTING  =  CEPH_MDS_STATE_STARTING,       // up, starting prior stopped MDS instance.
@@ -119,16 +118,6 @@ public:
      */
   } DaemonState;
 
-  // indicate startup standby preferences for MDS
-  // of course, if they have a specific rank to follow, they just set that!
-  static const mds_rank_t MDS_NO_STANDBY_PREF; // doesn't have instructions to do anything
-  static const mds_rank_t MDS_STANDBY_ANY;     // is instructed to be standby-replay, may
-                                               // or may not have specific name to follow
-  static const mds_rank_t MDS_STANDBY_NAME;    // standby for a named MDS
-  static const mds_rank_t MDS_MATCHED_ACTIVE;  // has a matched standby, which if up
-                                               // it should follow, but otherwise should
-                                               // be assigned a rank
-
   struct mds_info_t {
     mds_gid_t global_id;
     std::string name;
@@ -141,12 +130,15 @@ public:
     mds_rank_t standby_for_rank;
     std::string standby_for_name;
     fs_cluster_id_t standby_for_fscid;
+    bool standby_replay;
     std::set<mds_rank_t> export_targets;
     uint64_t mds_features;
 
-    mds_info_t() : global_id(MDS_GID_NONE), rank(MDS_RANK_NONE), inc(0), state(STATE_STANDBY), state_seq(0),
-		   standby_for_rank(MDS_NO_STANDBY_PREF),
-                   standby_for_fscid(FS_CLUSTER_ID_NONE)
+    mds_info_t() : global_id(MDS_GID_NONE), rank(MDS_RANK_NONE), inc(0),
+                   state(STATE_STANDBY), state_seq(0),
+                   standby_for_rank(MDS_RANK_NONE),
+                   standby_for_fscid(FS_CLUSTER_ID_NONE),
+                   standby_replay(false)
     { }
 
     bool laggy() const { return !(laggy_since == utime_t()); }
@@ -203,7 +195,7 @@ protected:
   mds_rank_t max_mds; /* The maximum number of active MDSes. Also, the maximum rank. */
 
   std::set<mds_rank_t> in;              // currently defined cluster
-  std::map<mds_rank_t,int32_t> inc;     // most recent incarnation.
+
   // which ranks are failed, stopped, damaged (i.e. not held by a daemon)
   std::set<mds_rank_t> failed, stopped, damaged;
   std::map<mds_rank_t, mds_gid_t> up;        // who is in those roles
diff --git a/src/mds/MDSRank.cc b/src/mds/MDSRank.cc
index feb4897..188750a 100644
--- a/src/mds/MDSRank.cc
+++ b/src/mds/MDSRank.cc
@@ -867,9 +867,6 @@ bool MDSRank::is_daemon_stopping() const
   return stopping;
 }
 
-// FIXME>> this fns are state-machiney, not dispatchy
-// >>>>>
-
 void MDSRank::request_state(MDSMap::DaemonState s)
 {
   dout(3) << "request_state " << ceph_mds_state_name(s) << dendl;
@@ -1093,12 +1090,6 @@ void MDSRank::replay_done()
 {
   dout(1) << "replay_done" << (standby_replaying ? " (as standby)" : "") << dendl;
 
-  if (is_oneshot_replay()) {
-    dout(2) << "hack.  journal looks ok.  shutting down." << dendl;
-    suicide();
-    return;
-  }
-
   if (is_standby_replay()) {
     // The replay was done in standby state, and we are still in that state
     assert(standby_replaying);
@@ -1376,8 +1367,6 @@ void MDSRank::stopping_done()
   request_state(MDSMap::STATE_STOPPED);
 }
 
-// <<<<<<<<
-
 void MDSRankDispatcher::handle_mds_map(
     MMDSMap *m,
     MDSMap *oldmap)
@@ -1434,7 +1423,7 @@ void MDSRankDispatcher::handle_mds_map(
 
   if (oldstate != state) {
     // update messenger.
-    if (state == MDSMap::STATE_STANDBY_REPLAY || state == MDSMap::STATE_ONESHOT_REPLAY) {
+    if (state == MDSMap::STATE_STANDBY_REPLAY) {
       dout(1) << "handle_mds_map i am now mds." << mds_gid << "." << incarnation
 	      << " replaying mds." << whoami << "." << incarnation << dendl;
       messenger->set_myname(entity_name_t::MDS(mds_gid));
diff --git a/src/mds/MDSRank.h b/src/mds/MDSRank.h
index 4da260d..fe65c56 100644
--- a/src/mds/MDSRank.h
+++ b/src/mds/MDSRank.h
@@ -191,9 +191,7 @@ class MDSRank {
     bool is_clientreplay() const { return state == MDSMap::STATE_CLIENTREPLAY; }
     bool is_active() const { return state == MDSMap::STATE_ACTIVE; }
     bool is_stopping() const { return state == MDSMap::STATE_STOPPING; }
-    bool is_oneshot_replay() const { return state == MDSMap::STATE_ONESHOT_REPLAY; }
-    bool is_any_replay() const { return (is_replay() || is_standby_replay() ||
-        is_oneshot_replay()); }
+    bool is_any_replay() const { return (is_replay() || is_standby_replay()); }
     bool is_stopped() const { return mdsmap->is_stopped(whoami); }
 
     void handle_write_error(int err);
diff --git a/src/messages/MMDSBeacon.h b/src/messages/MMDSBeacon.h
index 727aaad..a4d9d9b 100644
--- a/src/messages/MMDSBeacon.h
+++ b/src/messages/MMDSBeacon.h
@@ -122,7 +122,7 @@ WRITE_CLASS_ENCODER(MDSHealth)
 
 class MMDSBeacon : public PaxosServiceMessage {
 
-  static const int HEAD_VERSION = 6;
+  static const int HEAD_VERSION = 7;
   static const int COMPAT_VERSION = 2;
 
   uuid_d fsid;
@@ -135,6 +135,7 @@ class MMDSBeacon : public PaxosServiceMessage {
   mds_rank_t      standby_for_rank;
   string          standby_for_name;
   fs_cluster_id_t standby_for_fscid;
+  bool            standby_replay;
 
   CompatSet compat;
 
@@ -150,7 +151,7 @@ class MMDSBeacon : public PaxosServiceMessage {
     PaxosServiceMessage(MSG_MDS_BEACON, les, HEAD_VERSION, COMPAT_VERSION),
     fsid(f), global_id(g), name(n), state(st), seq(se),
     standby_for_rank(MDS_RANK_NONE), standby_for_fscid(FS_CLUSTER_ID_NONE),
-    mds_features(feat) {
+    standby_replay(false), mds_features(feat) {
   }
 private:
   ~MMDSBeacon() {}
@@ -166,6 +167,7 @@ public:
   mds_rank_t get_standby_for_rank() { return standby_for_rank; }
   const string& get_standby_for_name() { return standby_for_name; }
   const fs_cluster_id_t& get_standby_for_fscid() { return standby_for_fscid; }
+  bool get_standby_replay() const { return standby_replay; }
   uint64_t get_mds_features() const { return mds_features; }
 
   CompatSet const& get_compat() const { return compat; }
@@ -178,6 +180,7 @@ public:
   void set_standby_for_name(string& n) { standby_for_name = n; }
   void set_standby_for_name(const char* c) { standby_for_name.assign(c); }
   void set_standby_for_fscid(fs_cluster_id_t f) { standby_for_fscid = f; }
+  void set_standby_replay(bool r) { standby_replay = r; }
 
   const map<string, string>& get_sys_info() const { return sys_info; }
   void set_sys_info(const map<string, string>& i) { sys_info = i; }
@@ -203,6 +206,7 @@ public:
     }
     ::encode(mds_features, payload);
     ::encode(standby_for_fscid, payload);
+    ::encode(standby_replay, payload);
   }
   void decode_payload() {
     bufferlist::iterator p = payload.begin();
@@ -229,6 +233,16 @@ public:
     if (header.version >= 6) {
       ::decode(standby_for_fscid, p);
     }
+    if (header.version >= 7) {
+      ::decode(standby_replay, p);
+    }
+
+    if (header.version < 7  && state == MDSMap::STATE_STANDBY_REPLAY) {
+      // Old MDS daemons request the state, instead of explicitly
+      // advertising that they are configured as a replay daemon.
+      standby_replay = true;
+      state = MDSMap::STATE_STANDBY;
+    }
   }
 };
 
diff --git a/src/mon/MDSMonitor.cc b/src/mon/MDSMonitor.cc
index 00f06a0..9d72a0f 100644
--- a/src/mon/MDSMonitor.cc
+++ b/src/mon/MDSMonitor.cc
@@ -386,8 +386,7 @@ bool MDSMonitor::preprocess_beacon(MonOpRequestRef op)
   if (info.state != state) {
     // legal state change?
     if ((info.state == MDSMap::STATE_STANDBY ||
-	 info.state == MDSMap::STATE_STANDBY_REPLAY ||
-	 info.state == MDSMap::STATE_ONESHOT_REPLAY) && state > 0) {
+	 info.state == MDSMap::STATE_STANDBY_REPLAY) && state > 0) {
       dout(10) << "mds_beacon mds can't activate itself (" << ceph_mds_state_name(info.state)
 	       << " -> " << ceph_mds_state_name(state) << ")" << dendl;
       goto reply;
@@ -530,6 +529,7 @@ bool MDSMonitor::prepare_beacon(MonOpRequestRef op)
       new_info.standby_for_rank = m->get_standby_for_rank();
       new_info.standby_for_name = m->get_standby_for_name();
       new_info.standby_for_fscid = m->get_standby_for_fscid();
+      new_info.standby_replay = m->get_standby_replay();
       pending_fsmap.insert(new_info);
     }
 
@@ -568,6 +568,15 @@ bool MDSMonitor::prepare_beacon(MonOpRequestRef op)
   } else {
     // state update
     const MDSMap::mds_info_t &info = pending_fsmap.get_info_gid(gid);
+    // Old MDS daemons don't mention that they're standby replay until
+    // after they've sent their boot beacon, so update this field.
+    if (info.standby_replay != m->get_standby_replay()) {
+      pending_fsmap.modify_daemon(info.global_id, [&m](
+            MDSMap::mds_info_t *i)
+        {
+          i->standby_replay = m->get_standby_replay();
+        });
+    }
 
     if (info.state == MDSMap::STATE_STOPPING && state != MDSMap::STATE_STOPPED ) {
       // we can't transition to any other states from STOPPING
@@ -594,67 +603,6 @@ bool MDSMonitor::prepare_beacon(MonOpRequestRef op)
     if (state == MDSMap::STATE_STOPPED) {
       pending_fsmap.stop(gid);
       last_beacon.erase(gid);
-    } else if (state == MDSMap::STATE_STANDBY_REPLAY) {
-      if (m->get_standby_for_rank() == MDSMap::MDS_STANDBY_NAME) {
-        dout(20) << "looking for mds " << m->get_standby_for_name()
-                  << " to STANDBY_REPLAY for" << dendl;
-        auto target_info = pending_fsmap.find_by_name(m->get_standby_for_name());
-        if (target_info == nullptr) {
-          // This name is unknown, do nothing, stay in standby
-          return false;
-        }
-
-        auto target_ns = pending_fsmap.mds_roles.at(target_info->global_id);
-        if (target_ns == FS_CLUSTER_ID_NONE) {
-          // The named daemon is not in a Filesystem, do nothing.
-          return false;
-        }
-
-        auto target_fs = pending_fsmap.get_filesystem(target_ns);
-        if (target_fs->mds_map.is_followable(info.rank)) {
-          dout(10) <<" found mds " << m->get_standby_for_name()
-                       << "; it has rank " << target_info->rank << dendl;
-          pending_fsmap.modify_daemon(info.global_id,
-              [target_info, target_ns, seq](MDSMap::mds_info_t *info) {
-            info->standby_for_rank = target_info->rank;
-            info->standby_for_fscid = target_ns;
-            info->state = MDSMap::STATE_STANDBY_REPLAY;
-            info->state_seq = seq;
-          });
-        } else {
-          // The named daemon has a rank but isn't followable, do nothing
-          return false;
-        }
-      } else if (m->get_standby_for_rank() >= 0) {
-        fs_cluster_id_t target_ns = m->get_standby_for_fscid();
-
-        mds_role_t target_role = {
-          target_ns == FS_CLUSTER_ID_NONE ?
-            pending_fsmap.legacy_client_fscid : info.standby_for_fscid,
-          m->get_standby_for_rank()};
-
-        if (target_role.fscid != FS_CLUSTER_ID_NONE) {
-          auto fs = pending_fsmap.get_filesystem(target_role.fscid);
-          if (fs->mds_map.is_followable(target_role.rank)) {
-            pending_fsmap.modify_daemon(info.global_id,
-                [target_role, seq](MDSMap::mds_info_t *info) {
-              info->standby_for_rank = target_role.rank;
-              info->standby_for_fscid = target_role.fscid;
-              info->state = MDSMap::STATE_STANDBY_REPLAY;
-              info->state_seq = seq;
-            });
-          } else {
-            // We know who they want to follow, but he's not in a suitable state
-            return false;
-          }
-        } else {
-          // Couldn't resolve to a particular filesystem
-          return false;
-        }
-      } else { //it's a standby for anybody, and is already in the list
-        assert(pending_fsmap.get_mds_info().count(info.global_id));
-        return false;
-      }
     } else if (state == MDSMap::STATE_DAMAGED) {
       if (!mon->osdmon()->is_writeable()) {
         dout(4) << __func__ << ": DAMAGED from rank " << info.rank
@@ -1732,7 +1680,6 @@ int MDSMonitor::management_command(
 
     // Carry forward what makes sense
     new_fs->fscid = fs->fscid;
-    new_fs->mds_map.inc = fs->mds_map.inc;
     new_fs->mds_map.inline_data_enabled = fs->mds_map.inline_data_enabled;
     new_fs->mds_map.max_mds = g_conf->max_mds;
     new_fs->mds_map.data_pools = fs->mds_map.data_pools;
@@ -2723,8 +2670,8 @@ bool MDSMonitor::maybe_expand_cluster(std::shared_ptr<Filesystem> fs)
     while (fs->mds_map.is_in(mds)) {
       mds++;
     }
-    mds_gid_t newgid = pending_fsmap.find_replacement_for({fs->fscid, mds}, name,
-                         g_conf->mon_force_standby_active);
+    mds_gid_t newgid = pending_fsmap.find_replacement_for({fs->fscid, mds},
+                         name, g_conf->mon_force_standby_active);
     if (newgid == MDS_GID_NONE) {
       break;
     }
@@ -2767,7 +2714,8 @@ void MDSMonitor::maybe_replace_gid(mds_gid_t gid,
       info.state != MDSMap::STATE_STANDBY_REPLAY &&
       !pending_fsmap.get_filesystem(fscid)->mds_map.test_flag(CEPH_MDSMAP_DOWN) &&
       (sgid = pending_fsmap.find_replacement_for({fscid, info.rank}, info.name,
-                g_conf->mon_force_standby_active)) != MDS_GID_NONE) {
+                g_conf->mon_force_standby_active)) != MDS_GID_NONE)
+  {
     
     MDSMap::mds_info_t si = pending_fsmap.get_info_gid(sgid);
     dout(10) << " replacing " << gid << " " << info.addr << " mds."
@@ -2847,6 +2795,10 @@ bool MDSMonitor::maybe_promote_standby(std::shared_ptr<Filesystem> fs)
       const auto &info = j.second;
       assert(info.state == MDSMap::STATE_STANDBY);
 
+      if (!info.standby_replay) {
+        continue;
+      }
+
       /*
        * This mds is standby but has no rank assigned.
        * See if we can find it somebody to shadow
@@ -2887,8 +2839,7 @@ bool MDSMonitor::maybe_promote_standby(std::shared_ptr<Filesystem> fs)
               continue;   // we're supposed to follow someone else
             }
 
-            if (info.standby_for_rank == MDSMap::MDS_STANDBY_ANY &&
-                try_standby_replay(info, *(fs_i.second), cand_info)) {
+            if (try_standby_replay(info, *(fs_i.second), cand_info)) {
               do_propose = true;
               break;
             }
diff --git a/src/mon/OSDMonitor.cc b/src/mon/OSDMonitor.cc
index f9cf0fd..68bcc97 100644
--- a/src/mon/OSDMonitor.cc
+++ b/src/mon/OSDMonitor.cc
@@ -599,10 +599,13 @@ int OSDMonitor::reweight_by_utilization(int oload,
     util_by_osd.push_back(osd_util);
   }
 
-  // sort and iterate from most to least utilized
-  std::sort(util_by_osd.begin(), util_by_osd.end(), [](std::pair<int, float> l, std::pair<int, float> r) {
-    return l.second > r.second;
-  });
+  // sort by absolute deviation from the mean utilization,
+  // in descending order.
+  std::sort(util_by_osd.begin(), util_by_osd.end(),
+    [average_util](std::pair<int, float> l, std::pair<int, float> r) {
+      return abs(l.second - average_util) > abs(r.second - average_util);
+    }
+  );
 
   OSDMap::Incremental newinc;
 
@@ -621,7 +624,8 @@ int OSDMonitor::reweight_by_utilization(int oload,
       // to represent e.g. differing storage capacities
       unsigned weight = osdmap.get_weight(p->first);
       unsigned new_weight = (unsigned)((average_util / util) * (float)weight);
-      new_weight = MAX(new_weight, weight - max_change);
+      if (weight > max_change)
+	new_weight = MAX(new_weight, weight - max_change);
       newinc.new_weight[p->first] = new_weight;
       if (!dry_run) {
 	pending_inc.new_weight[p->first] = new_weight;
@@ -1707,6 +1711,13 @@ void OSDMonitor::check_failures(utime_t now)
 
 bool OSDMonitor::check_failure(utime_t now, int target_osd, failure_info_t& fi)
 {
+  // already pending failure?
+  if (pending_inc.new_state.count(target_osd) &&
+      pending_inc.new_state[target_osd] & CEPH_OSD_UP) {
+    dout(10) << " already pending failure" << dendl;
+    return true;
+  }
+
   set<string> reporters_by_subtree;
   string reporter_subtree_level = g_conf->mon_osd_reporter_subtree_level;
   utime_t orig_grace(g_conf->osd_heartbeat_grace, 0);
@@ -1762,14 +1773,6 @@ bool OSDMonitor::check_failure(utime_t now, int target_osd, failure_info_t& fi)
 	   << " + " << peer_grace << "), max_failed_since " << max_failed_since
 	   << dendl;
 
-  // already pending failure?
-  if (pending_inc.new_state.count(target_osd) &&
-      pending_inc.new_state[target_osd] & CEPH_OSD_UP) {
-    dout(10) << " already pending failure" << dendl;
-    return true;
-  }
-
-
   if (failed_for >= grace &&
       (int)reporters_by_subtree.size() >= g_conf->mon_osd_min_down_reporters) {
     dout(1) << " we have enough reporters to mark osd." << target_osd
@@ -7598,6 +7601,11 @@ done:
     }
     int64_t max_osds = g_conf->mon_reweight_max_osds;
     cmd_getval(g_ceph_context, cmdmap, "max_osds", max_osds);
+    if (max_osds <= 0) {
+      ss << "max_osds " << max_osds << " must be positive";
+      err = -EINVAL;
+      goto reply;
+    }
     string no_increasing;
     cmd_getval(g_ceph_context, cmdmap, "no_increasing", no_increasing);
     string out_str;
diff --git a/src/objclass/class_api.cc b/src/objclass/class_api.cc
index d0d0827..6752e51 100644
--- a/src/objclass/class_api.cc
+++ b/src/objclass/class_api.cc
@@ -6,6 +6,7 @@
 
 #include "objclass/objclass.h"
 #include "osd/ReplicatedPG.h"
+#include "osd/osd_types.h"
 
 #include "osd/ClassHandler.h"
 
@@ -606,6 +607,28 @@ int cls_cxx_map_remove_key(cls_method_context_t hctx, const string &key)
   return (*pctx)->pg->do_osd_ops(*pctx, ops);
 }
 
+int cls_cxx_list_watchers(cls_method_context_t hctx,
+			  obj_list_watch_response_t *watchers)
+{
+  ReplicatedPG::OpContext **pctx = (ReplicatedPG::OpContext **)hctx;
+  vector<OSDOp> nops(1);
+  OSDOp& op = nops[0];
+  int r;
+
+  op.op.op = CEPH_OSD_OP_LIST_WATCHERS;
+  r = (*pctx)->pg->do_osd_ops(*pctx, nops);
+  if (r < 0)
+    return r;
+
+  bufferlist::iterator iter = op.outdata.begin();
+  try {
+    ::decode(*watchers, iter);
+  } catch (buffer::error& err) {
+    return -EIO;
+  }
+  return 0;
+}
+
 int cls_gen_random_bytes(char *buf, int size)
 {
   return get_random_bytes(buf, size);
diff --git a/src/objclass/objclass.h b/src/objclass/objclass.h
index 8bc3a0a..49a97dd 100644
--- a/src/objclass/objclass.h
+++ b/src/objclass/objclass.h
@@ -11,6 +11,8 @@
 #include "common/hobject.h"
 #include "common/ceph_time.h"
 
+struct obj_list_watch_response_t;
+
 extern "C" {
 #endif
 
@@ -181,6 +183,9 @@ extern int cls_cxx_map_write_header(cls_method_context_t hctx, bufferlist *inbl)
 extern int cls_cxx_map_remove_key(cls_method_context_t hctx, const string &key);
 extern int cls_cxx_map_update(cls_method_context_t hctx, bufferlist *inbl);
 
+extern int cls_cxx_list_watchers(cls_method_context_t hctx,
+				 obj_list_watch_response_t *watchers);
+
 /* utility functions */
 extern int cls_gen_random_bytes(char *buf, int size);
 extern int cls_gen_rand_base64(char *dest, int size); /* size should be the required string size + 1 */
diff --git a/src/osd/OSDMap.cc b/src/osd/OSDMap.cc
index 3d06df6..7961d2f 100644
--- a/src/osd/OSDMap.cc
+++ b/src/osd/OSDMap.cc
@@ -2953,12 +2953,12 @@ int OSDMap::summarize_mapping_stats(
       base_stddev += base_diff * base_diff;
       float new_diff = (float)new_by_osd[osd] - avg_pg;
       new_stddev += new_diff * new_diff;
-      if (min < 0 || min_base_pg < base_by_osd[osd]) {
+      if (min < 0 || base_by_osd[osd] < min_base_pg) {
 	min = osd;
 	min_base_pg = base_by_osd[osd];
 	min_new_pg = new_by_osd[osd];
       }
-      if (max < 0 || max_base_pg > base_by_osd[osd]) {
+      if (max < 0 || base_by_osd[osd] > max_base_pg) {
 	max = osd;
 	max_base_pg = base_by_osd[osd];
 	max_new_pg = new_by_osd[osd];
diff --git a/src/osdc/Objecter.cc b/src/osdc/Objecter.cc
index 92a8698..c1f4e91 100644
--- a/src/osdc/Objecter.cc
+++ b/src/osdc/Objecter.cc
@@ -3410,19 +3410,19 @@ void Objecter::list_nobjects(NListContext *list_context, Context *onfinish)
     return;
   }
   int pg_num = pool->get_pg_num();
+  bool sort_bitwise = osdmap->test_flag(CEPH_OSDMAP_SORTBITWISE);
   rl.unlock();
 
   if (list_context->starting_pg_num == 0) {     // there can't be zero pgs!
     list_context->starting_pg_num = pg_num;
-    list_context->sort_bitwise = osdmap->test_flag(CEPH_OSDMAP_SORTBITWISE);
+    list_context->sort_bitwise = sort_bitwise;
     ldout(cct, 20) << pg_num << " placement groups" << dendl;
   }
-  if (list_context->sort_bitwise !=
-      osdmap->test_flag(CEPH_OSDMAP_SORTBITWISE)) {
+  if (list_context->sort_bitwise != sort_bitwise) {
     ldout(cct, 10) << " hobject sort order changed, restarting this pg"
 		   << dendl;
     list_context->cookie = collection_list_handle_t();
-    list_context->sort_bitwise = osdmap->test_flag(CEPH_OSDMAP_SORTBITWISE);
+    list_context->sort_bitwise = sort_bitwise;
   }
   if (list_context->starting_pg_num != pg_num) {
     // start reading from the beginning; the pgs have changed
@@ -3566,19 +3566,19 @@ void Objecter::list_objects(ListContext *list_context, Context *onfinish)
     return;
   }
   int pg_num = pool->get_pg_num();
+  bool sort_bitwise = osdmap->test_flag(CEPH_OSDMAP_SORTBITWISE);
   rl.unlock();
 
   if (list_context->starting_pg_num == 0) {     // there can't be zero pgs!
     list_context->starting_pg_num = pg_num;
-    list_context->sort_bitwise = osdmap->test_flag(CEPH_OSDMAP_SORTBITWISE);
+    list_context->sort_bitwise = sort_bitwise;
     ldout(cct, 20) << pg_num << " placement groups" << dendl;
   }
-  if (list_context->sort_bitwise !=
-      osdmap->test_flag(CEPH_OSDMAP_SORTBITWISE)) {
+  if (list_context->sort_bitwise != sort_bitwise) {
     ldout(cct, 10) << " hobject sort order changed, restarting this pg"
 		   << dendl;
     list_context->cookie = collection_list_handle_t();
-    list_context->sort_bitwise = osdmap->test_flag(CEPH_OSDMAP_SORTBITWISE);
+    list_context->sort_bitwise = sort_bitwise;
   }
   if (list_context->starting_pg_num != pg_num) {
     // start reading from the beginning; the pgs have changed
diff --git a/src/pybind/ceph_volume_client.py b/src/pybind/ceph_volume_client.py
index 8add311..d5748f1 100644
--- a/src/pybind/ceph_volume_client.py
+++ b/src/pybind/ceph_volume_client.py
@@ -44,6 +44,7 @@ class VolumePath(object):
         self.group_id = group_id
         self.volume_id = volume_id
         assert self.group_id != NO_GROUP_NAME
+        assert self.volume_id != "" and self.volume_id is not None
 
     def __str__(self):
         return "{0}/{1}".format(self.group_id, self.volume_id)
@@ -204,6 +205,7 @@ class CephFSVolumeClient(object):
     # Where shall we create our volumes?
     VOLUME_PREFIX = "/volumes"
     POOL_PREFIX = "fsvolume_"
+    POOL_NS_PREFIX = "fsvolumens_"
 
     def __init__(self, auth_id, conf_path, cluster_name):
         self.fs = None
@@ -443,8 +445,10 @@ class CephFSVolumeClient(object):
         if size is not None:
             self.fs.setxattr(path, 'ceph.quota.max_bytes', size.__str__(), 0)
 
+        # data_isolated means create a seperate pool for this volume
         if data_isolated:
             pool_name = "{0}{1}".format(self.POOL_PREFIX, volume_path.volume_id)
+            log.info("create_volume: {0}, create pool {1} as data_isolated =True.".format(volume_path, pool_name))
             pool_id = self._create_volume_pool(pool_name)
             mds_map = self._rados_command("mds dump", {})
             if pool_id not in mds_map['data_pools']:
@@ -453,6 +457,11 @@ class CephFSVolumeClient(object):
                 })
             self.fs.setxattr(path, 'ceph.dir.layout.pool', pool_name, 0)
 
+        # enforce security isolation, use seperate namespace for this volume
+        namespace = "{0}{1}".format(self.POOL_NS_PREFIX, volume_path.volume_id)
+        log.info("create_volume: {0}, using rados namespace {1} to isolate data.".format(volume_path, namespace))
+        self.fs.setxattr(path, 'ceph.dir.layout.pool_namespace', namespace, 0)
+
         return {
             'mount_path': path
         }
@@ -568,12 +577,13 @@ class CephFSVolumeClient(object):
         # read the layout
         path = self._get_path(volume_path)
         pool_name = self._get_ancestor_xattr(path, "ceph.dir.layout.pool")
+        namespace = self.fs.getxattr(path, "ceph.dir.layout.pool_namespace")
 
         # Now construct auth capabilities that give the guest just enough
         # permissions to access the share
         client_entity = "client.{0}".format(auth_id)
         want_mds_cap = 'allow rw path={0}'.format(path)
-        want_osd_cap = 'allow rw pool={0}'.format(pool_name)
+        want_osd_cap = 'allow rw pool={0} namespace={1}'.format(pool_name, namespace)
         try:
             existing = self._rados_command(
                 'auth get',
@@ -648,9 +658,10 @@ class CephFSVolumeClient(object):
         client_entity = "client.{0}".format(auth_id)
         path = self._get_path(volume_path)
         pool_name = self._get_ancestor_xattr(path, "ceph.dir.layout.pool")
+        namespace = self.fs.getxattr(path, "ceph.dir.layout.pool_namespace")
 
         want_mds_cap = 'allow rw path={0}'.format(path)
-        want_osd_cap = 'allow rw pool={0}'.format(pool_name)
+        want_osd_cap = 'allow rw pool={0} namespace={1}'.format(pool_name, namespace)
 
         try:
             existing = self._rados_command(
diff --git a/src/rgw/rgw_admin.cc b/src/rgw/rgw_admin.cc
index 3e4bcba..657adcd 100644
--- a/src/rgw/rgw_admin.cc
+++ b/src/rgw/rgw_admin.cc
@@ -101,8 +101,9 @@ void _usage()
   cout << "  zonegroup default          set default zone group\n";
   cout << "  zonegroup delete           delete a zone group info\n";
   cout << "  zonegroup get              show zone group info\n";
-  cout << "  zonegroup modify           set/clear zonegroup master status\n";
+  cout << "  zonegroup modify           modify an existing zonegroup\n";
   cout << "  zonegroup set              set zone group info (requires infile)\n";
+  cout << "  zonegroup remove           remove a zone from a zonegroup\n";
   cout << "  zonegroup rename           rename a zone group\n";
   cout << "  zonegroup list             list all zone groups set on this cluster\n";
   cout << "  zonegroup-map get          show zonegroup-map\n";
@@ -110,7 +111,7 @@ void _usage()
   cout << "  zone create                create a new zone\n";
   cout << "  zone delete                delete a zone\n";
   cout << "  zone get                   show zone cluster params\n";
-  cout << "  zone modify                set/clear zone master status\n";
+  cout << "  zone modify                modify an existing zone\n";
   cout << "  zone set                   set zone cluster params (requires infile)\n";
   cout << "  zone list                  list all zones set on this cluster\n";
   cout << "  pool add                   add an existing pool for data placement\n";
@@ -183,7 +184,7 @@ void _usage()
   cout << "                               replica mdlog get/delete\n";
   cout << "                               replica datalog get/delete\n";
   cout << "   --metadata-key=<key>      key to retrieve metadata from with metadata get\n";
-  cout << "   --remote=<remote>         remote to pull period\n";
+  cout << "   --remote=<remote>         zone or zonegroup id of remote gateway\n";
   cout << "   --period=<id>             period id\n";
   cout << "   --epoch=<number>          period epoch\n";
   cout << "   --commit                  commit the period during 'period update'\n";
@@ -195,7 +196,8 @@ void _usage()
   cout << "   --realm-id=<realm id>     realm id\n";
   cout << "   --realm-new-name=<realm new name> realm new name\n";
   cout << "   --rgw-zonegroup=<zonegroup>   zonegroup name\n";
-  cout << "   --rgw-zone=<zone>         zone in which radosgw is running\n";
+  cout << "   --zonegroup-id=<zonegroup id> zonegroup id\n";
+  cout << "   --rgw-zone=<zone>         name of zone in which radosgw is running\n";
   cout << "   --zone-id=<zone id>       zone id\n";
   cout << "   --zone-new-name=<zone>    zone new name\n";
   cout << "   --source-zone             specify the source zone (for data sync)\n";
@@ -308,7 +310,8 @@ enum {
   OPT_ZONEGROUP_MODIFY,
   OPT_ZONEGROUP_SET,
   OPT_ZONEGROUP_LIST,
-  OPT_ZONEGROUP_RENAME ,  
+  OPT_ZONEGROUP_REMOVE,
+  OPT_ZONEGROUP_RENAME,
   OPT_ZONEGROUPMAP_GET,
   OPT_ZONEGROUPMAP_SET,
   OPT_ZONEGROUPMAP_UPDATE,
@@ -595,6 +598,8 @@ static int get_cmd(const char *cmd, const char *prev_cmd, const char *prev_prev_
       return OPT_ZONEGROUP_LIST;
     if (strcmp(cmd, "set") == 0)
       return OPT_ZONEGROUP_SET;
+    if (strcmp(cmd, "remove") == 0)
+      return OPT_ZONEGROUP_REMOVE;
     if (strcmp(cmd, "rename") == 0)
       return OPT_ZONEGROUP_RENAME;
   } else if (strcmp(prev_cmd, "quota") == 0) {
@@ -1315,10 +1320,16 @@ static int send_to_remote_gateway(const string& remote, req_info& info,
     }
     conn = store->rest_master_conn;
   } else {
+    // check zonegroups
     auto iter = store->zonegroup_conn_map.find(remote);
     if (iter == store->zonegroup_conn_map.end()) {
-      cerr << "could not find connection to: " << remote << std::endl;
-      return -ENOENT;
+      // check zones
+      iter = store->zone_conn_map.find(remote);
+      if (iter == store->zone_conn_map.end()) {
+        cerr << "could not find connection for zone or zonegroup id: "
+            << remote << std::endl;
+        return -ENOENT;
+      }
     }
     conn = iter->second;
   }
@@ -1371,7 +1382,7 @@ static int send_to_remote_or_url(const string& remote, const string& url,
 }
 
 static int commit_period(RGWRealm& realm, RGWPeriod& period,
-                         const string& remote, const string& url,
+                         string remote, const string& url,
                          const string& access, const string& secret)
 {
   const string& master_zone = period.get_master_zone();
@@ -1397,6 +1408,12 @@ static int commit_period(RGWRealm& realm, RGWPeriod& period,
     return ret;
   }
 
+  if (remote.empty() && url.empty()) {
+    // use the new master zone's connection
+    remote = master_zone;
+    cout << "Sending period to new master zone " << remote << std::endl;
+  }
+
   // push period to the master with an empty period id
   period.set_id("");
 
@@ -2394,6 +2411,7 @@ int main(int argc, char **argv)
 			 opt_cmd == OPT_ZONEGROUP_GET || opt_cmd == OPT_ZONEGROUP_LIST ||  
                          opt_cmd == OPT_ZONEGROUP_SET || opt_cmd == OPT_ZONEGROUP_DEFAULT ||
 			 opt_cmd == OPT_ZONEGROUP_RENAME || opt_cmd == OPT_ZONEGROUP_MODIFY ||
+			 opt_cmd == OPT_ZONEGROUP_REMOVE ||
                          opt_cmd == OPT_ZONEGROUPMAP_GET || opt_cmd == OPT_ZONEGROUPMAP_SET ||
                          opt_cmd == OPT_ZONEGROUPMAP_UPDATE ||
 			 opt_cmd == OPT_ZONE_CREATE || opt_cmd == OPT_ZONE_DELETE ||
@@ -2862,6 +2880,9 @@ int main(int argc, char **argv)
 	       << cpp_strerror(-ret) << std::endl;
 	  return ret;
 	}
+
+        encode_json("zonegroup", zonegroup, formatter);
+        formatter->flush(cout);
       }
       break;
     case OPT_ZONEGROUP_CREATE:
@@ -3033,6 +3054,9 @@ int main(int argc, char **argv)
             cerr << "failed to set zonegroup " << zonegroup_name << " as default: " << cpp_strerror(-ret) << std::endl;
           }
         }
+
+        encode_json("zonegroup", zonegroup, formatter);
+        formatter->flush(cout);
       }
       break;
     case OPT_ZONEGROUP_SET:
@@ -3080,6 +3104,44 @@ int main(int argc, char **argv)
 	formatter->flush(cout);
       }
       break;
+    case OPT_ZONEGROUP_REMOVE:
+      {
+        RGWZoneGroup zonegroup(zonegroup_id, zonegroup_name);
+        int ret = zonegroup.init(g_ceph_context, store);
+        if (ret < 0) {
+          cerr << "failed to init zonegroup: " << cpp_strerror(-ret) << std::endl;
+          return -ret;
+        }
+
+        if (zone_id.empty()) {
+          if (zone_name.empty()) {
+            cerr << "no --zone-id or --rgw-zone name provided" << std::endl;
+            return EINVAL;
+          }
+          // look up zone id by name
+          for (auto& z : zonegroup.zones) {
+            if (zone_name == z.second.name) {
+              zone_id = z.second.id;
+              break;
+            }
+          }
+          if (zone_id.empty()) {
+            cerr << "zone name " << zone_name << " not found in zonegroup "
+                << zonegroup.get_name() << std::endl;
+            return ENOENT;
+          }
+        }
+
+        ret = zonegroup.remove_zone(zone_id);
+        if (ret < 0) {
+          cerr << "failed to remove zone: " << cpp_strerror(-ret) << std::endl;
+          return -ret;
+        }
+
+        encode_json("zonegroup", zonegroup, formatter);
+        formatter->flush(cout);
+      }
+      break;
     case OPT_ZONEGROUP_RENAME:
       {
 	if (zonegroup_new_name.empty()) {
@@ -3293,7 +3355,7 @@ int main(int argc, char **argv)
             cerr << "WARNING: failed to initialize zonegroup " << zonegroup_name << std::endl;
             continue;
           }
-          ret = zonegroup.remove_zone(zone);
+          ret = zonegroup.remove_zone(zone.get_id());
           if (ret < 0 && ret != -ENOENT) {
             cerr << "failed to remove zone " << zone_name << " from zonegroup " << zonegroup.get_name() << ": "
               << cpp_strerror(-ret) << std::endl;
@@ -3493,6 +3555,9 @@ int main(int argc, char **argv)
             cerr << "failed to set zone " << zone_name << " as default: " << cpp_strerror(-ret) << std::endl;
           }
         }
+
+        encode_json("zone", zone, formatter);
+        formatter->flush(cout);
       }
       break;
     case OPT_ZONE_RENAME:
diff --git a/src/rgw/rgw_civetweb_frontend.cc b/src/rgw/rgw_civetweb_frontend.cc
index 557bb5b..e3df80c 100644
--- a/src/rgw/rgw_civetweb_frontend.cc
+++ b/src/rgw/rgw_civetweb_frontend.cc
@@ -8,17 +8,18 @@
 static int civetweb_callback(struct mg_connection* conn) {
   struct mg_request_info* req_info = mg_get_request_info(conn);
   RGWMongooseEnv* pe = static_cast<RGWMongooseEnv *>(req_info->user_data);
-  RGWRados* store = pe->store;
-  RGWREST* rest = pe->rest;
-  OpsLogSocket* olog = pe->olog;
-
-  RGWRequest req(store->get_new_req_id());
-  RGWMongoose client_io(conn, pe->port);
 
   {
     // hold a read lock over access to pe->store for reconfiguration
     RWLock::RLocker lock(pe->mutex);
 
+    RGWRados* store = pe->store;
+    RGWREST* rest = pe->rest;
+    OpsLogSocket* olog = pe->olog;
+
+    RGWRequest req(store->get_new_req_id());
+    RGWMongoose client_io(conn, pe->port);
+
     int ret = process_request(pe->store, rest, &req, &client_io, olog);
     if (ret < 0) {
       /* we don't really care about return code */
diff --git a/src/rgw/rgw_cr_rados.cc b/src/rgw/rgw_cr_rados.cc
index 398433c..e812347 100644
--- a/src/rgw/rgw_cr_rados.cc
+++ b/src/rgw/rgw_cr_rados.cc
@@ -309,6 +309,7 @@ void RGWSimpleRadosLockCR::request_cleanup()
 {
   if (req) {
     req->finish();
+    req = NULL;
   }
 }
 
@@ -345,6 +346,7 @@ void RGWSimpleRadosUnlockCR::request_cleanup()
 {
   if (req) {
     req->finish();
+    req = NULL;
   }
 }
 
@@ -639,6 +641,7 @@ void RGWStatObjCR::request_cleanup()
 {
   if (req) {
     req->finish();
+    req = NULL;
   }
 }
 
diff --git a/src/rgw/rgw_cr_rados.h b/src/rgw/rgw_cr_rados.h
index 2459e78..af0481c 100644
--- a/src/rgw/rgw_cr_rados.h
+++ b/src/rgw/rgw_cr_rados.h
@@ -174,7 +174,7 @@ template <class T>
 class RGWSimpleRadosReadCR : public RGWSimpleCoroutine {
   RGWAsyncRadosProcessor *async_rados;
   RGWRados *store;
-  RGWObjectCtx& obj_ctx;
+  RGWObjectCtx obj_ctx;
   bufferlist bl;
 
   rgw_bucket pool;
@@ -188,19 +188,22 @@ class RGWSimpleRadosReadCR : public RGWSimpleCoroutine {
 
 public:
   RGWSimpleRadosReadCR(RGWAsyncRadosProcessor *_async_rados, RGWRados *_store,
-		      RGWObjectCtx& _obj_ctx,
 		      const rgw_bucket& _pool, const string& _oid,
 		      T *_result) : RGWSimpleCoroutine(_store->ctx()),
                                                 async_rados(_async_rados), store(_store),
-                                                obj_ctx(_obj_ctx),
+                                                obj_ctx(store),
 						pool(_pool), oid(_oid),
                                                 pattrs(NULL),
 						result(_result),
                                                 req(NULL) { }
+  ~RGWSimpleRadosReadCR() {
+    request_cleanup();
+  }
                                                          
   void request_cleanup() {
     if (req) {
       req->finish();
+      req = NULL;
     }
   }
 
@@ -252,7 +255,7 @@ int RGWSimpleRadosReadCR<T>::request_complete()
 class RGWSimpleRadosReadAttrsCR : public RGWSimpleCoroutine {
   RGWAsyncRadosProcessor *async_rados;
   RGWRados *store;
-  RGWObjectCtx& obj_ctx;
+  RGWObjectCtx obj_ctx;
   bufferlist bl;
 
   rgw_bucket pool;
@@ -264,18 +267,21 @@ class RGWSimpleRadosReadAttrsCR : public RGWSimpleCoroutine {
 
 public:
   RGWSimpleRadosReadAttrsCR(RGWAsyncRadosProcessor *_async_rados, RGWRados *_store,
-		      RGWObjectCtx& _obj_ctx,
 		      rgw_bucket& _pool, const string& _oid,
 		      map<string, bufferlist> *_pattrs) : RGWSimpleCoroutine(_store->ctx()),
                                                 async_rados(_async_rados), store(_store),
-                                                obj_ctx(_obj_ctx),
+                                                obj_ctx(store),
 						pool(_pool), oid(_oid),
                                                 pattrs(_pattrs),
                                                 req(NULL) { }
+  ~RGWSimpleRadosReadAttrsCR() {
+    request_cleanup();
+  }
                                                          
   void request_cleanup() {
     if (req) {
       req->finish();
+      req = NULL;
     }
   }
 
@@ -305,9 +311,14 @@ public:
     ::encode(_data, bl);
   }
 
+  ~RGWSimpleRadosWriteCR() {
+    request_cleanup();
+  }
+
   void request_cleanup() {
     if (req) {
       req->finish();
+      req = NULL;
     }
   }
 
@@ -344,10 +355,14 @@ public:
 						pool(_pool), oid(_oid),
                                                 attrs(_attrs), req(NULL) {
   }
+  ~RGWSimpleRadosWriteAttrsCR() {
+    request_cleanup();
+  }
 
   void request_cleanup() {
     if (req) {
       req->finish();
+      req = NULL;
     }
   }
 
@@ -431,6 +446,9 @@ public:
 		      const rgw_bucket& _pool, const string& _oid, const string& _lock_name,
 		      const string& _cookie,
 		      uint32_t _duration);
+  ~RGWSimpleRadosLockCR() {
+    request_cleanup();
+  }
   void request_cleanup();
 
   int send_request();
@@ -452,6 +470,9 @@ public:
   RGWSimpleRadosUnlockCR(RGWAsyncRadosProcessor *_async_rados, RGWRados *_store,
 		      const rgw_bucket& _pool, const string& _oid, const string& _lock_name,
 		      const string& _cookie);
+  ~RGWSimpleRadosUnlockCR() {
+    request_cleanup();
+  }
   void request_cleanup();
 
   int send_request();
@@ -522,11 +543,15 @@ public:
             int _secs) : RGWSimpleCoroutine(cct), cct(_cct),
                          async_rados(_async_rados), lock(_lock), cond(_cond), secs(_secs), req(NULL) {
   }
+  ~RGWWaitCR() {
+    request_cleanup();
+  }
 
   void request_cleanup() {
-    wakeup();
     if (req) {
+      wakeup();
       req->finish();
+      req = NULL;
     }
   }
 
@@ -621,9 +646,13 @@ public:
                         RGWBucketInfo *_bucket_info) : RGWSimpleCoroutine(_store->ctx()), async_rados(_async_rados), store(_store),
                                                        bucket_name(_bucket_name), bucket_id(_bucket_id),
                                                        bucket_info(_bucket_info), req(NULL) {}
+  ~RGWGetBucketInstanceInfoCR() {
+    request_cleanup();
+  }
   void request_cleanup() {
     if (req) {
       req->finish();
+      req = NULL;
     }
   }
 
@@ -698,9 +727,14 @@ public:
                                        copy_if_newer(_if_newer), req(NULL) {}
 
 
+  ~RGWFetchRemoteObjCR() {
+    request_cleanup();
+  }
+
   void request_cleanup() {
     if (req) {
       req->finish();
+      req = NULL;
     }
   }
 
@@ -812,10 +846,14 @@ public:
       owner_display_name = *_owner_display_name;
     }
   }
+  ~RGWRemoveObjCR() {
+    request_cleanup();
+  }
 
   void request_cleanup() {
     if (req) {
       req->finish();
+      req = NULL;
     }
   }
 
@@ -936,6 +974,9 @@ class RGWStatObjCR : public RGWSimpleCoroutine {
 	  const rgw_obj& obj, uint64_t *psize = nullptr,
 	  real_time* pmtime = nullptr, uint64_t *pepoch = nullptr,
 	  RGWObjVersionTracker *objv_tracker = nullptr);
+  ~RGWStatObjCR() {
+    request_cleanup();
+  }
   void request_cleanup();
 
   int send_request() override;
diff --git a/src/rgw/rgw_cr_rest.h b/src/rgw/rgw_cr_rest.h
index 147784f..4958c45 100644
--- a/src/rgw/rgw_cr_rest.h
+++ b/src/rgw/rgw_cr_rest.h
@@ -25,6 +25,10 @@ public:
       path(_path), params(make_param_list(params)), result(_result)
   {}
 
+  ~RGWReadRESTResourceCR() {
+    request_cleanup();
+  }
+
   int send_request() {
     auto op = boost::intrusive_ptr<RGWRESTReadResource>(
         new RGWRESTReadResource(conn, path, params, NULL, http_manager));
@@ -35,6 +39,7 @@ public:
     if (ret < 0) {
       log_error() << "failed to send http operation: " << op->to_str()
           << " ret=" << ret << std::endl;
+      op->put();
       return ret;
     }
     std::swap(http_op, op); // store reference in http_op on success
@@ -47,14 +52,17 @@ public:
     if (ret < 0) {
       error_stream << "http operation failed: " << op->to_str()
           << " status=" << op->get_http_status() << std::endl;
+      op->put();
       return ret;
     }
+    op->put();
     return 0;
   }
 
   void request_cleanup() {
     if (http_op) {
       http_op->put();
+      http_op = NULL;
     }
   }
 };
@@ -79,6 +87,10 @@ public:
       input(_input)
   {}
 
+  ~RGWPostRESTResourceCR() {
+    request_cleanup();
+  }
+
   int send_request() {
     auto op = boost::intrusive_ptr<RGWRESTPostResource>(
         new RGWRESTPostResource(conn, path, params, NULL, http_manager));
@@ -119,12 +131,14 @@ public:
       op->put();
       return ret;
     }
+    op->put();
     return 0;
   }
 
   void request_cleanup() {
     if (http_op) {
       http_op->put();
+      http_op = NULL;
     }
   }
 };
diff --git a/src/rgw/rgw_data_sync.cc b/src/rgw/rgw_data_sync.cc
index 6d73b73..ef4e434 100644
--- a/src/rgw/rgw_data_sync.cc
+++ b/src/rgw/rgw_data_sync.cc
@@ -46,18 +46,15 @@ void rgw_datalog_shard_data::decode_json(JSONObj *obj) {
 class RGWReadDataSyncStatusCoroutine : public RGWSimpleRadosReadCR<rgw_data_sync_info> {
   RGWDataSyncEnv *sync_env;
 
-  RGWObjectCtx& obj_ctx;
-
   rgw_data_sync_status *sync_status;
 
 public:
-  RGWReadDataSyncStatusCoroutine(RGWDataSyncEnv *_sync_env, RGWObjectCtx& _obj_ctx,
-		      rgw_data_sync_status *_status) : RGWSimpleRadosReadCR(_sync_env->async_rados, _sync_env->store, _obj_ctx,
+  RGWReadDataSyncStatusCoroutine(RGWDataSyncEnv *_sync_env,
+		      rgw_data_sync_status *_status) : RGWSimpleRadosReadCR(_sync_env->async_rados, _sync_env->store,
 									    _sync_env->store->get_zone_params().log_pool,
 									    RGWDataSyncStatusManager::sync_status_oid(_sync_env->source_zone),
 									    &_status->sync_info),
                                                                             sync_env(_sync_env),
-                                                                            obj_ctx(_obj_ctx),
 									    sync_status(_status) {}
 
   int handle_data(rgw_data_sync_info& data);
@@ -72,7 +69,7 @@ int RGWReadDataSyncStatusCoroutine::handle_data(rgw_data_sync_info& data)
   map<uint32_t, rgw_data_sync_marker>& markers = sync_status->sync_markers;
   RGWRados *store = sync_env->store;
   for (int i = 0; i < (int)data.num_shards; i++) {
-    spawn(new RGWSimpleRadosReadCR<rgw_data_sync_marker>(sync_env->async_rados, store, obj_ctx, store->get_zone_params().log_pool,
+    spawn(new RGWSimpleRadosReadCR<rgw_data_sync_marker>(sync_env->async_rados, store, store->get_zone_params().log_pool,
                                                     RGWDataSyncStatusManager::shard_obj_name(sync_env->source_zone, i), &markers[i]), true);
   }
   return 0;
@@ -173,6 +170,8 @@ public:
                                                       pmarker(_pmarker),
                                                       entries(_entries),
                                                       truncated(_truncated) {
+  }
+  ~RGWReadRemoteDataLogShardCR() {
     if (http_op) {
       http_op->put();
     }
@@ -344,7 +343,6 @@ class RGWInitDataSyncStatusCoroutine : public RGWCoroutine {
   RGWDataSyncEnv *sync_env;
 
   RGWRados *store;
-  RGWObjectCtx& obj_ctx;
 
   string sync_status_oid;
 
@@ -354,9 +352,8 @@ class RGWInitDataSyncStatusCoroutine : public RGWCoroutine {
   map<int, RGWDataChangesLogInfo> shards_info;
 public:
   RGWInitDataSyncStatusCoroutine(RGWDataSyncEnv *_sync_env,
-		      RGWObjectCtx& _obj_ctx, uint32_t _num_shards) : RGWCoroutine(_sync_env->cct),
-                                                sync_env(_sync_env), store(sync_env->store),
-                                                obj_ctx(_obj_ctx) {
+		      uint32_t _num_shards) : RGWCoroutine(_sync_env->cct),
+                                                sync_env(_sync_env), store(sync_env->store) {
     lock_name = "sync_lock";
     status.num_shards = _num_shards;
 
@@ -481,12 +478,12 @@ int RGWRemoteDataLog::read_source_log_shards_next(map<int, string> shard_markers
 
 int RGWRemoteDataLog::init(const string& _source_zone, RGWRESTConn *_conn, RGWSyncErrorLogger *_error_logger)
 {
+  sync_env.init(store->ctx(), store, _conn, async_rados, &http_manager, _error_logger, _source_zone);
+
   if (initialized) {
     return 0;
   }
 
-  sync_env.init(store->ctx(), store, _conn, async_rados, &http_manager, _error_logger, _source_zone);
-
   int ret = http_manager.set_threaded();
   if (ret < 0) {
     ldout(store->ctx(), 0) << "failed in http_manager.set_threaded() ret=" << ret << dendl;
@@ -527,8 +524,7 @@ int RGWRemoteDataLog::get_shard_info(int shard_id)
 
 int RGWRemoteDataLog::read_sync_status(rgw_data_sync_status *sync_status)
 {
-  RGWObjectCtx obj_ctx(store, NULL);
-  int r = run(new RGWReadDataSyncStatusCoroutine(&sync_env, obj_ctx, sync_status));
+  int r = run(new RGWReadDataSyncStatusCoroutine(&sync_env, sync_status));
   if (r == -ENOENT) {
     r = 0;
   }
@@ -537,8 +533,7 @@ int RGWRemoteDataLog::read_sync_status(rgw_data_sync_status *sync_status)
 
 int RGWRemoteDataLog::init_sync_status(int num_shards)
 {
-  RGWObjectCtx obj_ctx(store, NULL);
-  return run(new RGWInitDataSyncStatusCoroutine(&sync_env, obj_ctx, num_shards));
+  return run(new RGWInitDataSyncStatusCoroutine(&sync_env, num_shards));
 }
 
 static string full_data_sync_index_shard_oid(const string& source_zone, int shard_id)
@@ -1139,8 +1134,7 @@ public:
 
   RGWCoroutine *alloc_finisher_cr() {
     RGWRados *store = sync_env->store;
-    RGWObjectCtx obj_ctx(store, NULL);
-    return new RGWSimpleRadosReadCR<rgw_data_sync_marker>(sync_env->async_rados, store, obj_ctx, store->get_zone_params().log_pool,
+    return new RGWSimpleRadosReadCR<rgw_data_sync_marker>(sync_env->async_rados, store, store->get_zone_params().log_pool,
                                                     RGWDataSyncStatusManager::shard_obj_name(sync_env->source_zone, shard_id), &sync_marker);
   }
 
@@ -1160,8 +1154,6 @@ class RGWDataSyncCR : public RGWCoroutine {
   RGWDataSyncEnv *sync_env;
   uint32_t num_shards;
 
-  RGWObjectCtx obj_ctx;
-
   rgw_data_sync_status sync_status;
 
   RGWDataSyncShardMarkerTrack *marker_tracker;
@@ -1175,7 +1167,6 @@ public:
   RGWDataSyncCR(RGWDataSyncEnv *_sync_env, uint32_t _num_shards, bool *_reset_backoff) : RGWCoroutine(_sync_env->cct),
                                                       sync_env(_sync_env),
                                                       num_shards(_num_shards),
-                                                      obj_ctx(sync_env->store),
                                                       marker_tracker(NULL),
                                                       shard_crs_lock("RGWDataSyncCR::shard_crs_lock"),
                                                       reset_backoff(_reset_backoff) {
@@ -1191,7 +1182,7 @@ public:
     reenter(this) {
 
       /* read sync status */
-      yield call(new RGWReadDataSyncStatusCoroutine(sync_env, obj_ctx, &sync_status));
+      yield call(new RGWReadDataSyncStatusCoroutine(sync_env, &sync_status));
 
       if (retcode == -ENOENT) {
         sync_status.sync_info.num_shards = num_shards;
@@ -1203,7 +1194,7 @@ public:
       /* state: init status */
       if ((rgw_data_sync_info::SyncState)sync_status.sync_info.state == rgw_data_sync_info::StateInit) {
         ldout(sync_env->cct, 20) << __func__ << "(): init" << dendl;
-        yield call(new RGWInitDataSyncStatusCoroutine(sync_env, obj_ctx, sync_status.sync_info.num_shards));
+        yield call(new RGWInitDataSyncStatusCoroutine(sync_env, sync_status.sync_info.num_shards));
         if (retcode < 0) {
           ldout(sync_env->cct, 0) << "ERROR: failed to init sync, retcode=" << retcode << dendl;
           return set_cr_error(retcode);
@@ -1320,9 +1311,7 @@ void RGWRemoteDataLog::wakeup(int shard_id, set<string>& keys) {
 
 int RGWRemoteDataLog::run_sync(int num_shards, rgw_data_sync_status& sync_status)
 {
-  RGWObjectCtx obj_ctx(store, NULL);
-
-  int r = run(new RGWReadDataSyncStatusCoroutine(&sync_env, obj_ctx, &sync_status));
+  int r = run(new RGWReadDataSyncStatusCoroutine(&sync_env, &sync_status));
   if (r < 0 && r != -ENOENT) {
     ldout(store->ctx(), 0) << "ERROR: failed to read sync status from source_zone=" << sync_env.source_zone << " r=" << r << dendl;
     return r;
@@ -1367,6 +1356,7 @@ int RGWDataSyncStatusManager::init()
   r = source_log.init(source_zone, conn, error_logger);
   if (r < 0) {
     lderr(store->ctx()) << "ERROR: failed to init remote log, r=" << r << dendl;
+    finalize();
     return r;
   }
 
@@ -1374,6 +1364,7 @@ int RGWDataSyncStatusManager::init()
   r = source_log.read_log_info(&datalog_info);
   if (r < 0) {
     ldout(store->ctx(), 5) << "ERROR: master.read_log_info() returned r=" << r << dendl;
+    finalize();
     return r;
   }
 
@@ -1386,6 +1377,13 @@ int RGWDataSyncStatusManager::init()
   return 0;
 }
 
+void RGWDataSyncStatusManager::finalize()
+{
+  delete error_logger;
+  error_logger = nullptr;
+  ioctx.close();
+}
+
 string RGWDataSyncStatusManager::sync_status_oid(const string& source_zone)
 {
   char buf[datalog_sync_status_oid_prefix.size() + source_zone.size() + 16];
@@ -1604,7 +1602,6 @@ void rgw_bucket_shard_inc_sync_marker::encode_attr(map<string, bufferlist>& attr
 
 class RGWReadBucketSyncStatusCoroutine : public RGWCoroutine {
   RGWDataSyncEnv *sync_env;
-  RGWObjectCtx obj_ctx;
   string oid;
   rgw_bucket_shard_sync_info *status;
 
@@ -1614,7 +1611,6 @@ public:
                       const string& _bucket_name, const string _bucket_id, int _shard_id,
 		      rgw_bucket_shard_sync_info *_status) : RGWCoroutine(_sync_env->cct),
                                                             sync_env(_sync_env),
-                                                            obj_ctx(sync_env->store),
                                                             oid(RGWBucketSyncStatusManager::status_oid(sync_env->source_zone, _bucket_name, _bucket_id, _shard_id)),
                                                             status(_status) {}
   int operate();
@@ -1623,7 +1619,7 @@ public:
 int RGWReadBucketSyncStatusCoroutine::operate()
 {
   reenter(this) {
-    yield call(new RGWSimpleRadosReadAttrsCR(sync_env->async_rados, sync_env->store, obj_ctx,
+    yield call(new RGWSimpleRadosReadAttrsCR(sync_env->async_rados, sync_env->store,
                                                    sync_env->store->get_zone_params().log_pool,
                                                    oid,
                                                    &attrs));
@@ -1863,7 +1859,7 @@ class RGWBucketIncSyncShardMarkerTrack : public RGWSyncShardMarkerTrack<string,
   string marker_oid;
   rgw_bucket_shard_inc_sync_marker sync_marker;
 
-  map<rgw_obj_key, pair<RGWModifyOp, string> > key_to_marker;
+  map<rgw_obj_key, string> key_to_marker;
   map<string, rgw_obj_key> marker_to_key;
 
   void handle_finish(const string& marker) {
@@ -1906,23 +1902,18 @@ public:
    * Also, we should make sure that we don't run concurrent operations on the same key with
    * different ops.
    */
-  bool index_key_to_marker(const rgw_obj_key& key, RGWModifyOp op, const string& marker) {
+  bool index_key_to_marker(const rgw_obj_key& key, const string& marker) {
     if (key_to_marker.find(key) != key_to_marker.end()) {
       set_need_retry(key);
       return false;
     }
-    key_to_marker[key] = make_pair<>(op, marker);
+    key_to_marker[key] = marker;
     marker_to_key[marker] = key;
     return true;
   }
 
-  bool can_do_op(const rgw_obj_key& key, RGWModifyOp op) {
-    auto i = key_to_marker.find(key);
-    if (i == key_to_marker.end()) {
-      return true;
-    }
-
-    return (i->second.first == op);
+  bool can_do_op(const rgw_obj_key& key) {
+    return (key_to_marker.find(key) == key_to_marker.end());
   }
 };
 
@@ -2313,16 +2304,21 @@ int RGWBucketShardIncrementalSyncCR::operate()
         }
         ldout(sync_env->cct, 20) << "[inc sync] syncing object: " << bucket_name << ":" << bucket_id << ":" << shard_id << "/" << key << dendl;
         updated_status = false;
-        while (!marker_tracker->can_do_op(key, entry->op)) {
+        while (!marker_tracker->can_do_op(key)) {
           if (!updated_status) {
             set_status() << "can't do op, conflicting inflight operation";
             updated_status = true;
           }
           ldout(sync_env->cct, 5) << *this << ": [inc sync] can't do op on key=" << key << " need to wait for conflicting operation to complete" << dendl;
           yield wait_for_child();
-          
+          while (collect(&ret)) {
+            if (ret < 0) {
+              ldout(sync_env->cct, 0) << "ERROR: a child operation returned error (ret=" << ret << ")" << dendl;
+              /* we have reported this error */
+            }
+          }
         }
-        if (!marker_tracker->index_key_to_marker(key, entry->op, cur_id)) {
+        if (!marker_tracker->index_key_to_marker(key, cur_id)) {
           set_status() << "can't do op, sync already in progress for object";
           ldout(sync_env->cct, 20) << __func__ << ": skipping sync of entry: " << cur_id << ":" << key << " sync already in progress for object" << dendl;
           marker_tracker->try_update_high_marker(cur_id, 0, entry->timestamp);
diff --git a/src/rgw/rgw_data_sync.h b/src/rgw/rgw_data_sync.h
index a2b2451..33b723a 100644
--- a/src/rgw/rgw_data_sync.h
+++ b/src/rgw/rgw_data_sync.h
@@ -242,9 +242,10 @@ public:
     : store(_store), source_zone(_source_zone), conn(NULL), error_logger(NULL),
       source_log(store, async_rados), num_shards(0) {}
   ~RGWDataSyncStatusManager() {
-    delete error_logger;
+    finalize();
   }
   int init();
+  void finalize();
 
   rgw_data_sync_status& get_sync_status() { return sync_status; }
 
diff --git a/src/rgw/rgw_http_client.cc b/src/rgw/rgw_http_client.cc
index b15ab66..05d9acd 100644
--- a/src/rgw/rgw_http_client.cc
+++ b/src/rgw/rgw_http_client.cc
@@ -14,7 +14,62 @@
 
 #define dout_subsys ceph_subsys_rgw
 
-static size_t receive_http_header(void *ptr, size_t size, size_t nmemb, void *_info)
+struct rgw_http_req_data : public RefCountedObject {
+  CURL *easy_handle;
+  curl_slist *h;
+  uint64_t id;
+  int ret;
+  atomic_t done;
+  RGWHTTPClient *client;
+  void *user_info;
+  bool registered;
+  RGWHTTPManager *mgr;
+  char error_buf[CURL_ERROR_SIZE];
+
+  Mutex lock;
+  Cond cond;
+
+  rgw_http_req_data() : easy_handle(NULL), h(NULL), id(-1), ret(0),
+                        client(nullptr), user_info(nullptr), registered(false),
+                        mgr(NULL), lock("rgw_http_req_data::lock") {
+    memset(error_buf, 0, sizeof(error_buf));
+  }
+
+  int wait() {
+    Mutex::Locker l(lock);
+    cond.Wait(lock);
+    return ret;
+  }
+
+  void finish(int r) {
+    Mutex::Locker l(lock);
+    ret = r;
+    if (easy_handle)
+      curl_easy_cleanup(easy_handle);
+
+    if (h)
+      curl_slist_free_all(h);
+
+    easy_handle = NULL;
+    h = NULL;
+    done.set(1);
+    cond.Signal();
+  }
+
+  bool is_done() {
+    return done.read() != 0;
+  }
+
+  int get_retcode() {
+    Mutex::Locker l(lock);
+    return ret;
+  }
+};
+
+/*
+ * the simple set of callbacks will be called on RGWHTTPClient::process()
+ */
+static size_t simple_receive_http_header(void *ptr, size_t size, size_t nmemb, void *_info)
 {
   RGWHTTPClient *client = static_cast<RGWHTTPClient *>(_info);
   size_t len = size * nmemb;
@@ -26,7 +81,7 @@ static size_t receive_http_header(void *ptr, size_t size, size_t nmemb, void *_i
   return len;
 }
 
-static size_t receive_http_data(void *ptr, size_t size, size_t nmemb, void *_info)
+static size_t simple_receive_http_data(void *ptr, size_t size, size_t nmemb, void *_info)
 {
   RGWHTTPClient *client = static_cast<RGWHTTPClient *>(_info);
   size_t len = size * nmemb;
@@ -38,7 +93,7 @@ static size_t receive_http_data(void *ptr, size_t size, size_t nmemb, void *_inf
   return len;
 }
 
-static size_t send_http_data(void *ptr, size_t size, size_t nmemb, void *_info)
+static size_t simple_send_http_data(void *ptr, size_t size, size_t nmemb, void *_info)
 {
   RGWHTTPClient *client = static_cast<RGWHTTPClient *>(_info);
   int ret = client->send_data(ptr, size * nmemb);
@@ -49,6 +104,66 @@ static size_t send_http_data(void *ptr, size_t size, size_t nmemb, void *_info)
   return ret;
 }
 
+/*
+ * the following set of callbacks will be called either on RGWHTTPManager::process(),
+ * or via the RGWHTTPManager async processing.
+ */
+static size_t receive_http_header(void *ptr, size_t size, size_t nmemb, void *_info)
+{
+  rgw_http_req_data *req_data = static_cast<rgw_http_req_data *>(_info);
+  size_t len = size * nmemb;
+
+  Mutex::Locker l(req_data->lock);
+  
+  if (!req_data->registered) {
+    return len;
+  }
+
+  int ret = req_data->client->receive_header(ptr, size * nmemb);
+  if (ret < 0) {
+    dout(0) << "WARNING: client->receive_header() returned ret=" << ret << dendl;
+  }
+
+  return len;
+}
+
+static size_t receive_http_data(void *ptr, size_t size, size_t nmemb, void *_info)
+{
+  rgw_http_req_data *req_data = static_cast<rgw_http_req_data *>(_info);
+  size_t len = size * nmemb;
+
+  Mutex::Locker l(req_data->lock);
+  
+  if (!req_data->registered) {
+    return len;
+  }
+  
+  int ret = req_data->client->receive_data(ptr, size * nmemb);
+  if (ret < 0) {
+    dout(0) << "WARNING: client->receive_data() returned ret=" << ret << dendl;
+  }
+
+  return len;
+}
+
+static size_t send_http_data(void *ptr, size_t size, size_t nmemb, void *_info)
+{
+  rgw_http_req_data *req_data = static_cast<rgw_http_req_data *>(_info);
+
+  Mutex::Locker l(req_data->lock);
+  
+  if (!req_data->registered) {
+    return 0;
+  }
+
+  int ret = req_data->client->send_data(ptr, size * nmemb);
+  if (ret < 0) {
+    dout(0) << "WARNING: client->receive_data() returned ret=" << ret << dendl;
+  }
+
+  return ret;
+}
+
 static curl_slist *headers_to_slist(list<pair<string, string> >& headers)
 {
   curl_slist *h = NULL;
@@ -79,6 +194,10 @@ static curl_slist *headers_to_slist(list<pair<string, string> >& headers)
   return h;
 }
 
+/*
+ * process a single simple one off request, not going through RGWHTTPManager. Not using
+ * req_data.
+ */
 int RGWHTTPClient::process(const char *method, const char *url)
 {
   int ret = 0;
@@ -99,15 +218,15 @@ int RGWHTTPClient::process(const char *method, const char *url)
   curl_easy_setopt(curl_handle, CURLOPT_URL, url);
   curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1L);
   curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1L);
-  curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, receive_http_header);
+  curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, simple_receive_http_header);
   curl_easy_setopt(curl_handle, CURLOPT_WRITEHEADER, (void *)this);
-  curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, receive_http_data);
+  curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, simple_receive_http_data);
   curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)this);
   curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, (void *)error_buf);
   if (h) {
     curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, (void *)h);
   }
-  curl_easy_setopt(curl_handle, CURLOPT_READFUNCTION, send_http_data);
+  curl_easy_setopt(curl_handle, CURLOPT_READFUNCTION, simple_send_http_data);
   curl_easy_setopt(curl_handle, CURLOPT_READDATA, (void *)this);
   curl_easy_setopt(curl_handle, CURLOPT_UPLOAD, 1L); 
   if (has_send_len) {
@@ -131,55 +250,6 @@ int RGWHTTPClient::process(const char *method, const char *url)
   return ret;
 }
 
-struct rgw_http_req_data : public RefCountedObject {
-  CURL *easy_handle;
-  curl_slist *h;
-  uint64_t id;
-  int ret;
-  atomic_t done;
-  RGWHTTPClient *client;
-  RGWHTTPManager *mgr;
-  char error_buf[CURL_ERROR_SIZE];
-
-  Mutex lock;
-  Cond cond;
-
-  rgw_http_req_data() : easy_handle(NULL), h(NULL), id(-1), ret(0), client(NULL),
-                        mgr(NULL), lock("rgw_http_req_data::lock") {
-    memset(error_buf, 0, sizeof(error_buf));
-  }
-
-  int wait() {
-    Mutex::Locker l(lock);
-    cond.Wait(lock);
-    return ret;
-  }
-
-  void finish(int r) {
-    Mutex::Locker l(lock);
-    ret = r;
-    cond.Signal();
-    done.set(1);
-    if (easy_handle)
-      curl_easy_cleanup(easy_handle);
-
-    if (h)
-      curl_slist_free_all(h);
-
-    easy_handle = NULL;
-    h = NULL;
-  }
-
-  bool is_done() {
-    return done.read() != 0;
-  }
-
-  int get_retcode() {
-    Mutex::Locker l(lock);
-    return ret;
-  }
-};
-
 string RGWHTTPClient::to_str()
 {
   string method_str = (last_method.empty() ? "<no-method>" : last_method);
@@ -196,6 +266,9 @@ int RGWHTTPClient::get_req_retcode()
   return req_data->get_retcode();
 }
 
+/*
+ * init request, will be used later with RGWHTTPManager
+ */
 int RGWHTTPClient::init_request(const char *method, const char *url, rgw_http_req_data *_req_data)
 {
   assert(!req_data);
@@ -222,15 +295,15 @@ int RGWHTTPClient::init_request(const char *method, const char *url, rgw_http_re
   curl_easy_setopt(easy_handle, CURLOPT_NOPROGRESS, 1L);
   curl_easy_setopt(easy_handle, CURLOPT_NOSIGNAL, 1L);
   curl_easy_setopt(easy_handle, CURLOPT_HEADERFUNCTION, receive_http_header);
-  curl_easy_setopt(easy_handle, CURLOPT_WRITEHEADER, (void *)this);
+  curl_easy_setopt(easy_handle, CURLOPT_WRITEHEADER, (void *)req_data);
   curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, receive_http_data);
-  curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, (void *)this);
+  curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, (void *)req_data);
   curl_easy_setopt(easy_handle, CURLOPT_ERRORBUFFER, (void *)req_data->error_buf);
   if (h) {
     curl_easy_setopt(easy_handle, CURLOPT_HTTPHEADER, (void *)h);
   }
   curl_easy_setopt(easy_handle, CURLOPT_READFUNCTION, send_http_data);
-  curl_easy_setopt(easy_handle, CURLOPT_READDATA, (void *)this);
+  curl_easy_setopt(easy_handle, CURLOPT_READDATA, (void *)req_data);
   curl_easy_setopt(easy_handle, CURLOPT_UPLOAD, 1L); 
   if (has_send_len) {
     curl_easy_setopt(easy_handle, CURLOPT_INFILESIZE, (void *)send_len); 
@@ -240,6 +313,9 @@ int RGWHTTPClient::init_request(const char *method, const char *url, rgw_http_re
   return 0;
 }
 
+/*
+ * wait for async request to complete
+ */
 int RGWHTTPClient::wait()
 {
   if (!req_data->is_done()) {
@@ -252,6 +328,8 @@ int RGWHTTPClient::wait()
 RGWHTTPClient::~RGWHTTPClient()
 {
   if (req_data) {
+    req_data->mgr->remove_request(this);
+
     req_data->put();
   }
 }
@@ -347,6 +425,9 @@ void *RGWHTTPManager::ReqsThread::entry()
   return NULL;
 }
 
+/*
+ * RGWHTTPManager has two modes of operation: threaded and non-threaded.
+ */
 RGWHTTPManager::RGWHTTPManager(CephContext *_cct, RGWCompletionManager *_cm) : cct(_cct),
                                                     completion_mgr(_cm), is_threaded(false),
                                                     reqs_lock("RGWHTTPManager::reqs_lock"), num_reqs(0), max_threaded_req(0),
@@ -367,11 +448,21 @@ void RGWHTTPManager::register_request(rgw_http_req_data *req_data)
 {
   RWLock::WLocker rl(reqs_lock);
   req_data->id = num_reqs;
+  req_data->registered = true;
   reqs[num_reqs] = req_data;
   num_reqs++;
   ldout(cct, 20) << __func__ << " mgr=" << this << " req_data->id=" << req_data->id << ", easy_handle=" << req_data->easy_handle << dendl;
 }
 
+void RGWHTTPManager::unregister_request(rgw_http_req_data *req_data)
+{
+  RWLock::WLocker rl(reqs_lock);
+  req_data->get();
+  req_data->registered = false;
+  unregistered_reqs.push_back(req_data);
+  ldout(cct, 20) << __func__ << " mgr=" << this << " req_data->id=" << req_data->id << ", easy_handle=" << req_data->easy_handle << dendl;
+}
+
 void RGWHTTPManager::complete_request(rgw_http_req_data *req_data)
 {
   RWLock::WLocker rl(reqs_lock);
@@ -385,8 +476,9 @@ void RGWHTTPManager::_complete_request(rgw_http_req_data *req_data)
     reqs.erase(iter);
   }
   if (completion_mgr) {
-    completion_mgr->complete(NULL, req_data->client->get_user_info());
+    completion_mgr->complete(NULL, req_data->user_info);
   }
+
   req_data->put();
 }
 
@@ -402,6 +494,9 @@ void RGWHTTPManager::_finish_request(rgw_http_req_data *req_data, int ret)
   _complete_request(req_data);
 }
 
+/*
+ * hook request to the curl multi handle
+ */
 int RGWHTTPManager::link_request(rgw_http_req_data *req_data)
 {
   ldout(cct, 20) << __func__ << " req_data=" << req_data << " req_data->id=" << req_data->id << ", easy_handle=" << req_data->easy_handle << dendl;
@@ -413,10 +508,30 @@ int RGWHTTPManager::link_request(rgw_http_req_data *req_data)
   return 0;
 }
 
-void RGWHTTPManager::link_pending_requests()
+/*
+ * unhook request from the curl multi handle, and finish request if it wasn't finished yet as
+ * there will be no more processing on this request
+ */
+void RGWHTTPManager::_unlink_request(rgw_http_req_data *req_data)
+{
+  if (req_data->easy_handle) {
+    curl_multi_remove_handle((CURLM *)multi_handle, req_data->easy_handle);
+  }
+  if (!req_data->is_done()) {
+    _finish_request(req_data, -ECANCELED);
+  }
+}
+
+void RGWHTTPManager::unlink_request(rgw_http_req_data *req_data)
+{
+  RWLock::WLocker wl(reqs_lock);
+  _unlink_request(req_data);
+}
+
+void RGWHTTPManager::manage_pending_requests()
 {
   reqs_lock.get_read();
-  if (max_threaded_req == num_reqs) {
+  if (max_threaded_req == num_reqs && unregistered_reqs.empty()) {
     reqs_lock.unlock();
     return;
   }
@@ -424,6 +539,15 @@ void RGWHTTPManager::link_pending_requests()
 
   RWLock::WLocker wl(reqs_lock);
 
+  if (!unregistered_reqs.empty()) {
+    for (auto& r : unregistered_reqs) {
+      _unlink_request(r);
+      r->put();
+    }
+
+    unregistered_reqs.clear();
+  }
+
   map<uint64_t, rgw_http_req_data *>::iterator iter = reqs.find(max_threaded_req);
 
   list<std::pair<rgw_http_req_data *, int> > remove_reqs;
@@ -454,16 +578,22 @@ int RGWHTTPManager::add_request(RGWHTTPClient *client, const char *method, const
   int ret = client->init_request(method, url, req_data);
   if (ret < 0) {
     req_data->put();
+    req_data = NULL;
     return ret;
   }
 
   req_data->mgr = this;
   req_data->client = client;
+  req_data->user_info = client->get_user_info();
 
   register_request(req_data);
 
   if (!is_threaded) {
     ret = link_request(req_data);
+    if (ret < 0) {
+      req_data->put();
+      req_data = NULL;
+    }
     return ret;
   }
   ret = signal_thread();
@@ -474,6 +604,26 @@ int RGWHTTPManager::add_request(RGWHTTPClient *client, const char *method, const
   return ret;
 }
 
+int RGWHTTPManager::remove_request(RGWHTTPClient *client)
+{
+  rgw_http_req_data *req_data = client->get_req_data();
+
+  if (!is_threaded) {
+    unlink_request(req_data);
+    return 0;
+  }
+  unregister_request(req_data);
+  int ret = signal_thread();
+  if (ret < 0) {
+    return ret;
+  }
+
+  return 0;
+}
+
+/*
+ * the synchronous, non-threaded request processing method.
+ */
 int RGWHTTPManager::process_requests(bool wait_for_data, bool *done)
 {
   assert(!is_threaded);
@@ -528,6 +678,9 @@ int RGWHTTPManager::process_requests(bool wait_for_data, bool *done)
   return 0;
 }
 
+/*
+ * the synchronous, non-threaded request processing completion method.
+ */
 int RGWHTTPManager::complete_requests()
 {
   bool done;
@@ -556,6 +709,12 @@ int RGWHTTPManager::set_threaded()
 
 void RGWHTTPManager::stop()
 {
+  if (is_stopped.read()) {
+    return;
+  }
+
+  is_stopped.set(1);
+
   if (is_threaded) {
     going_down.set(1);
     signal_thread();
@@ -590,7 +749,7 @@ void *RGWHTTPManager::reqs_thread_entry()
       return NULL;
     }
 
-    link_pending_requests();
+    manage_pending_requests();
 
     mstatus = curl_multi_perform((CURLM *)multi_handle, &still_running);
     switch (mstatus) {
diff --git a/src/rgw/rgw_http_client.h b/src/rgw/rgw_http_client.h
index d2105cb..6283d31 100644
--- a/src/rgw/rgw_http_client.h
+++ b/src/rgw/rgw_http_client.h
@@ -29,6 +29,8 @@ class RGWHTTPClient
   string last_url;
   bool verify_ssl; // Do not validate self signed certificates, default to false
 
+  atomic_t stopped;
+
 protected:
   CephContext *cct;
 
@@ -62,9 +64,15 @@ public:
     headers.push_back(pair<string, string>(name, val));
   }
 
-  virtual int receive_header(void *ptr, size_t len) = 0;
-  virtual int receive_data(void *ptr, size_t len) = 0;
-  virtual int send_data(void *ptr, size_t len) = 0;
+  virtual int receive_header(void *ptr, size_t len) {
+    return 0;
+  }
+  virtual int receive_data(void *ptr, size_t len) {
+    return 0;
+  }
+  virtual int send_data(void *ptr, size_t len) {
+    return 0;
+  }
 
   void set_send_length(size_t len) {
     send_len = len;
@@ -99,9 +107,11 @@ class RGWHTTPManager {
   void *multi_handle;
   bool is_threaded;
   atomic_t going_down;
+  atomic_t is_stopped;
 
   RWLock reqs_lock;
   map<uint64_t, rgw_http_req_data *> reqs;
+  list<rgw_http_req_data *> unregistered_reqs;
   map<uint64_t, rgw_http_req_data *> complete_reqs;
   int64_t num_reqs;
   int64_t max_threaded_req;
@@ -110,11 +120,14 @@ class RGWHTTPManager {
   void register_request(rgw_http_req_data *req_data);
   void complete_request(rgw_http_req_data *req_data);
   void _complete_request(rgw_http_req_data *req_data);
+  void unregister_request(rgw_http_req_data *req_data);
+  void _unlink_request(rgw_http_req_data *req_data);
+  void unlink_request(rgw_http_req_data *req_data);
   void finish_request(rgw_http_req_data *req_data, int r);
   void _finish_request(rgw_http_req_data *req_data, int r);
   int link_request(rgw_http_req_data *req_data);
 
-  void link_pending_requests();
+  void manage_pending_requests();
 
   class ReqsThread : public Thread {
     RGWHTTPManager *manager;
@@ -138,6 +151,7 @@ public:
   void stop();
 
   int add_request(RGWHTTPClient *client, const char *method, const char *url);
+  int remove_request(RGWHTTPClient *client);
 
   /* only for non threaded case */
   int process_requests(bool wait_for_data, bool *done);
diff --git a/src/rgw/rgw_http_errors.h b/src/rgw/rgw_http_errors.h
index 04500ee..aebf801 100644
--- a/src/rgw/rgw_http_errors.h
+++ b/src/rgw/rgw_http_errors.h
@@ -153,6 +153,8 @@ static inline int rgw_http_error_to_errno(int http_err)
         return -EACCES;
     case 404:
         return -ENOENT;
+    case 409:
+        return -ENOTEMPTY;
     default:
         return -EIO;
   }
diff --git a/src/rgw/rgw_op.cc b/src/rgw/rgw_op.cc
index d050821..a2202ca 100644
--- a/src/rgw/rgw_op.cc
+++ b/src/rgw/rgw_op.cc
@@ -2113,6 +2113,20 @@ void RGWDeleteBucket::execute()
      ldout(s->cct, 1) << "WARNING: failed to sync user stats before bucket delete: op_ret= " << op_ret << dendl;
   }
 
+  if (!store->is_meta_master()) {
+    bufferlist in_data;
+    op_ret = forward_request_to_master(s, &ot.read_version, store, in_data,
+				       NULL);
+    if (op_ret < 0) {
+      if (op_ret == -ENOENT) {
+        /* adjust error, we want to return with NoSuchBucket and not
+	 * NoSuchKey */
+        op_ret = -ERR_NO_SUCH_BUCKET;
+      }
+      return;
+    }
+  }
+
   op_ret = store->delete_bucket(s->bucket, ot);
   if (op_ret == 0) {
     op_ret = rgw_unlink_bucket(store, s->user->user_id, s->bucket.tenant,
@@ -2127,19 +2141,6 @@ void RGWDeleteBucket::execute()
     return;
   }
 
-  if (!store->is_meta_master()) {
-    bufferlist in_data;
-    op_ret = forward_request_to_master(s, &ot.read_version, store, in_data,
-				       NULL);
-    if (op_ret < 0) {
-      if (op_ret == -ENOENT) {
-        /* adjust error, we want to return with NoSuchBucket and not
-	 * NoSuchKey */
-        op_ret = -ERR_NO_SUCH_BUCKET;
-      }
-      return;
-    }
-  }
 
 }
 
diff --git a/src/rgw/rgw_period_pusher.cc b/src/rgw/rgw_period_pusher.cc
index 10bf86e..d6b2eab 100644
--- a/src/rgw/rgw_period_pusher.cc
+++ b/src/rgw/rgw_period_pusher.cc
@@ -144,6 +144,7 @@ class RGWPeriodPusher::CRThread {
   {
     push_all.reset();
     coroutines.stop();
+    http.stop();
     if (thread.joinable())
       thread.join();
   }
diff --git a/src/rgw/rgw_rados.cc b/src/rgw/rgw_rados.cc
index 47ba962..8a3420c 100644
--- a/src/rgw/rgw_rados.cc
+++ b/src/rgw/rgw_rados.cc
@@ -310,12 +310,12 @@ void RGWZoneGroup::post_process_params()
   }
 }
 
-int RGWZoneGroup::remove_zone(const RGWZoneParams& zone_params)
+int RGWZoneGroup::remove_zone(const std::string& zone_id)
 {
-  map<string, RGWZone>::iterator iter = zones.find(zone_params.get_id());
-
+  map<string, RGWZone>::iterator iter = zones.find(zone_id);
   if (iter == zones.end()) {
-    ldout(cct, 0) << "zone " << zone_params.get_name() << " " << zone_params.get_id() << "is not a part of zonegroup "<< name << dendl;
+    ldout(cct, 0) << "zone id " << zone_id << " is not a part of zonegroup "
+        << name << dendl;
     return -ENOENT;
   }
 
@@ -1009,15 +1009,26 @@ int RGWPeriod::set_latest_epoch(epoch_t epoch, bool exclusive)
 
 int RGWPeriod::delete_obj()
 {
-  string pool_name = get_pool_name(cct);
-  rgw_bucket pool(pool_name.c_str());
+  rgw_bucket pool(get_pool_name(cct));
 
-  rgw_obj object_id(pool, get_period_oid());
-  int ret = store->delete_system_obj(object_id);
-  if (ret < 0) {
-    ldout(cct, 0) << "Error delete object id " << id << ": " << cpp_strerror(-ret) << dendl;
+  // delete the object for each period epoch
+  for (epoch_t e = 1; e <= epoch; e++) {
+    RGWPeriod p{get_id(), e};
+    rgw_obj oid{pool, p.get_period_oid()};
+    int ret = store->delete_system_obj(oid);
+    if (ret < 0) {
+      ldout(cct, 0) << "WARNING: failed to delete period object " << oid
+          << ": " << cpp_strerror(-ret) << dendl;
+    }
   }
 
+  // delete the .latest_epoch object
+  rgw_obj oid{pool, get_period_oid_prefix() + get_latest_epoch_oid()};
+  int ret = store->delete_system_obj(oid);
+  if (ret < 0) {
+    ldout(cct, 0) << "WARNING: failed to delete period object " << oid
+        << ": " << cpp_strerror(-ret) << dendl;
+  }
   return ret;
 }
 
@@ -1416,7 +1427,7 @@ string fix_zone_pool_name(set<string> pool_names,
 
   if (!suggested_name.empty()) {
     prefix = suggested_name.substr(0,suggested_name.find("."));
-    suffix = suggested_name.substr(prefix.length(), suggested_name.length() - 1);
+    suffix = suggested_name.substr(prefix.length());
   }
 
   string name = prefix + suffix;
@@ -2409,18 +2420,33 @@ int RGWPutObjProcessor_Atomic::complete_writing_data()
     }
     obj_len = (uint64_t)first_chunk.length();
   }
-  if (pending_data_bl.length()) {
+  while (pending_data_bl.length()) {
     void *handle;
-    int r = write_data(pending_data_bl, data_ofs, &handle, false);
+    uint64_t max_write_size = MIN(max_chunk_size, (uint64_t)next_part_ofs - data_ofs);
+    if (max_write_size > pending_data_bl.length()) {
+      max_write_size = pending_data_bl.length();
+    }
+    bufferlist bl;
+    pending_data_bl.splice(0, max_write_size, &bl);
+    int r = write_data(bl, data_ofs, &handle, false);
     if (r < 0) {
       ldout(store->ctx(), 0) << "ERROR: write_data() returned " << r << dendl;
       return r;
     }
+    data_ofs += bl.length();
     r = throttle_data(handle, false);
     if (r < 0) {
       ldout(store->ctx(), 0) << "ERROR: throttle_data() returned " << r << dendl;
       return r;
     }
+
+    if (data_ofs >= next_part_ofs) {
+      r = prepare_next_part(data_ofs);
+      if (r < 0) {
+        ldout(store->ctx(), 0) << "ERROR: prepare_next_part() returned " << r << dendl;
+        return r;
+      }
+    }
   }
   int r = complete_parts();
   if (r < 0) {
@@ -2947,7 +2973,10 @@ public:
   }
 
   int process() {
-    while (!going_down()) {
+    while (!initialized) {
+      if (going_down()) {
+        return 0;
+      }
       int ret = sync.init();
       if (ret >= 0) {
         initialized = true;
@@ -3013,6 +3042,9 @@ int RGWRados::get_required_alignment(rgw_bucket& bucket, uint64_t *alignment)
       << r << dendl;
     return r;
   }
+  if (align != 0) {
+    ldout(cct, 20) << "required alignment=" << align << dendl;
+  }
   *alignment = align;
   return 0;
 }
@@ -3039,6 +3071,8 @@ int RGWRados::get_max_chunk_size(rgw_bucket& bucket, uint64_t *max_chunk_size)
 
   *max_chunk_size = config_chunk_size - (config_chunk_size % alignment);
 
+  ldout(cct, 20) << "max_chunk_size=" << *max_chunk_size << dendl;
+
   return 0;
 }
 
@@ -3361,76 +3395,75 @@ int RGWRados::replace_region_with_zonegroup()
   /* create zonegroups */
   for (iter = regions.begin(); iter != regions.end(); ++iter)
   {
-    /* read region info default has no data */
-    if (*iter != default_zonegroup_name){
-      RGWZoneGroup zonegroup(*iter);
-      int ret = zonegroup.init(cct, this, true, true);
+    RGWZoneGroup zonegroup(*iter);
+    zonegroup.set_id(*iter);
+    int ret = zonegroup.init(cct, this, true, true);
+    if (ret < 0) {
+      ldout(cct, 0) << "failed init zonegroup: ret "<< ret << " " << cpp_strerror(-ret) << dendl;
+      return ret;
+    }
+    zonegroup.realm_id = realm.get_id();
+    ret = zonegroup.update();
+    if (ret < 0 && ret != -EEXIST) {
+      ldout(cct, 0) << "failed to update zonegroup " << *iter << ": ret "<< ret << " " << cpp_strerror(-ret)
+        << dendl;
+      return ret;
+    }
+    ret = zonegroup.update_name();
+    if (ret < 0 && ret != -EEXIST) {
+      ldout(cct, 0) << "failed to update_name for zonegroup " << *iter << ": ret "<< ret << " " << cpp_strerror(-ret)
+        << dendl;
+      return ret;
+    }
+    if (zonegroup.get_name() == default_region) {
+      ret = zonegroup.set_as_default();
       if (ret < 0) {
-	ldout(cct, 0) << "failed init zonegroup: ret "<< ret << " " << cpp_strerror(-ret) << dendl;
-	return ret;
+        ldout(cct, 0) << "failed to set_as_default " << *iter << ": ret "<< ret << " " << cpp_strerror(-ret)
+          << dendl;
+        return ret;
+      }
+    }
+    for (map<string, RGWZone>::const_iterator iter = zonegroup.zones.begin(); iter != zonegroup.zones.end();
+         iter ++) {
+      RGWZoneParams zoneparams(iter->first, iter->first);
+      zoneparams.set_id(iter->first);
+      ret = zoneparams.init(cct, this);
+      if (ret < 0) {
+        ldout(cct, 0) << "failed to init zoneparams  " << iter->first <<  ": " << cpp_strerror(-ret) << dendl;
+        return ret;
       }
       zonegroup.realm_id = realm.get_id();
-      ret = zonegroup.update();
+      ret = zoneparams.update();
       if (ret < 0 && ret != -EEXIST) {
-	ldout(cct, 0) << "failed to update zonegroup " << *iter << ": ret "<< ret << " " << cpp_strerror(-ret)
-		   << dendl;
-	return ret;
+        ldout(cct, 0) << "failed to update zoneparams " << iter->first <<  ": " << cpp_strerror(-ret) << dendl;
+        return ret;
       }
-      ret = zonegroup.update_name();
+      ret = zoneparams.update_name();
       if (ret < 0 && ret != -EEXIST) {
-	ldout(cct, 0) << "failed to update_name for zonegroup " << *iter << ": ret "<< ret << " " << cpp_strerror(-ret)
-		   << dendl;
-	return ret;
-      }
-      if (zonegroup.get_name() == default_region) {
-	ret = zonegroup.set_as_default();
-	if (ret < 0) {
-	  ldout(cct, 0) << "failed to set_as_default " << *iter << ": ret "<< ret << " " << cpp_strerror(-ret)
-		     << dendl;
-	  return ret;
-	}
-      }
-      for (map<string, RGWZone>::const_iterator iter = zonegroup.zones.begin(); iter != zonegroup.zones.end();
-	   iter ++) {
-	RGWZoneParams zoneparams(iter->first, iter->first);
-	ret = zoneparams.init(cct, this);
-	if (ret < 0) {
-	  ldout(cct, 0) << "failed to init zoneparams  " << iter->first <<  ": " << cpp_strerror(-ret) << dendl;
-	  return ret;
-	}
-	zonegroup.realm_id = realm.get_id();
-	ret = zoneparams.update();
-	if (ret < 0 && ret != -EEXIST) {
-	  ldout(cct, 0) << "failed to update zoneparams " << iter->first <<  ": " << cpp_strerror(-ret) << dendl;
-	  return ret;
-	}
-	ret = zoneparams.update_name();
-	if (ret < 0 && ret != -EEXIST) {
-	  ldout(cct, 0) << "failed to init zoneparams " << iter->first <<  ": " << cpp_strerror(-ret) << dendl;
-	  return ret;
-	}
+        ldout(cct, 0) << "failed to init zoneparams " << iter->first <<  ": " << cpp_strerror(-ret) << dendl;
+        return ret;
       }
+    }
 
-      if (!current_period.get_id().empty()) {
-	ret = current_period.add_zonegroup(zonegroup);
-	if (ret < 0) {
-	  ldout(cct, 0) << "failed to add zonegroup to current_period: " << cpp_strerror(-ret) << dendl;
-	  return ret;
-	}
-	ret = current_period.update();
-	if (ret < 0) {
-	  ldout(cct, 0) << "failed to update current_period: " << cpp_strerror(-ret) << dendl;
-	  return ret;
-	}
+    if (!current_period.get_id().empty()) {
+      ret = current_period.add_zonegroup(zonegroup);
+      if (ret < 0) {
+        ldout(cct, 0) << "failed to add zonegroup to current_period: " << cpp_strerror(-ret) << dendl;
+        return ret;
       }
-
-      ret = zonegroup.delete_obj(true);
-      if (ret < 0 && ret != -ENOENT) {
-	ldout(cct, 0) << "failed to delete region " << *iter << ": ret "<< ret << " " << cpp_strerror(-ret)
-		   << dendl;
-	return ret;
+      ret = current_period.update();
+      if (ret < 0) {
+        ldout(cct, 0) << "failed to update current_period: " << cpp_strerror(-ret) << dendl;
+        return ret;
       }
     }
+
+    ret = zonegroup.delete_obj(true);
+    if (ret < 0 && ret != -ENOENT) {
+      ldout(cct, 0) << "failed to delete region " << *iter << ": ret "<< ret << " " << cpp_strerror(-ret)
+        << dendl;
+      return ret;
+    }
   }
 
   return 0;
@@ -3815,14 +3848,15 @@ int RGWRados::list_periods(list<string>& periods)
   if (ret < 0) {
     return ret;
   }
-  for(list<string>::iterator iter = raw_periods.begin(); iter != raw_periods.end(); iter++) {
-    size_t pos = iter->find(".");
-    if ( pos != std::string::npos) {
-      periods.push_back(iter->substr(0, pos));
+  for (const auto& oid : raw_periods) {
+    size_t pos = oid.find(".");
+    if (pos != std::string::npos) {
+      periods.push_back(oid.substr(0, pos));
     } else {
-      periods.push_back(*iter);
+      periods.push_back(oid);
     }
   }
+  periods.sort(); // unique() only detects duplicates if they're adjacent
   periods.unique();
   return 0;
 }
@@ -7491,12 +7525,15 @@ int RGWRados::Object::Delete::delete_obj()
     }
   }
 
+  if (!state->exists) {
+    target->invalidate_state();
+    return -ENOENT;
+  }
+
   r = target->prepare_atomic_modification(op, false, NULL, NULL, NULL, true);
   if (r < 0)
     return r;
 
-  bool ret_not_existed = (!state->exists);
-
   RGWBucketInfo& bucket_info = target->get_bucket_info();
 
   RGWRados::Bucket bop(store, bucket_info);
@@ -7525,7 +7562,7 @@ int RGWRados::Object::Delete::delete_obj()
 
   int64_t poolid = ref.ioctx.get_id();
   if (r >= 0) {
-    r = index_op.complete_del(poolid, ref.ioctx.get_last_version(), params.remove_objs);
+    r = index_op.complete_del(poolid, ref.ioctx.get_last_version(), state->mtime, params.remove_objs);
   } else {
     int ret = index_op.cancel();
     if (ret < 0) {
@@ -7547,9 +7584,6 @@ int RGWRados::Object::Delete::delete_obj()
   if (r < 0)
     return r;
 
-  if (ret_not_existed)
-    return -ENOENT;
-
   /* update quota cache */
   store->quota_handler->update_stats(params.bucket_owner, bucket, -1, 0, obj_size);
 
@@ -7615,7 +7649,8 @@ int RGWRados::delete_obj_index(rgw_obj& obj)
   RGWRados::Bucket bop(this, bucket_info);
   RGWRados::Bucket::UpdateIndex index_op(&bop, obj, NULL);
 
-  int r = index_op.complete_del(-1 /* pool */, 0, NULL);
+  real_time removed_mtime;
+  int r = index_op.complete_del(-1 /* pool */, 0, removed_mtime, NULL);
 
   return r;
 }
@@ -8555,6 +8590,7 @@ int RGWRados::Bucket::UpdateIndex::complete(int64_t poolid, uint64_t epoch, uint
 }
 
 int RGWRados::Bucket::UpdateIndex::complete_del(int64_t poolid, uint64_t epoch,
+                                                real_time& removed_mtime,
                                                 list<rgw_obj_key> *remove_objs)
 {
   if (blind) {
@@ -8568,7 +8604,7 @@ int RGWRados::Bucket::UpdateIndex::complete_del(int64_t poolid, uint64_t epoch,
     return ret;
   }
 
-  ret = store->cls_obj_complete_del(*bs, optag, poolid, epoch, obj, remove_objs, bilog_flags);
+  ret = store->cls_obj_complete_del(*bs, optag, poolid, epoch, obj, removed_mtime, remove_objs, bilog_flags);
 
   int r = store->data_log->add_entry(bs->bucket, bs->shard_id);
   if (r < 0) {
@@ -11039,10 +11075,12 @@ int RGWRados::cls_obj_complete_add(BucketShard& bs, string& tag,
 int RGWRados::cls_obj_complete_del(BucketShard& bs, string& tag,
                                    int64_t pool, uint64_t epoch,
                                    rgw_obj& obj,
+                                   real_time& removed_mtime,
                                    list<rgw_obj_key> *remove_objs,
                                    uint16_t bilog_flags)
 {
   RGWObjEnt ent;
+  ent.mtime = removed_mtime;
   obj.get_index_key(&ent.key);
   return cls_obj_complete_op(bs, CLS_RGW_OP_DEL, tag, pool, epoch, ent, RGW_OBJ_CATEGORY_NONE, remove_objs, bilog_flags);
 }
diff --git a/src/rgw/rgw_rados.h b/src/rgw/rgw_rados.h
index 612a523..0ced215 100644
--- a/src/rgw/rgw_rados.h
+++ b/src/rgw/rgw_rados.h
@@ -1155,7 +1155,7 @@ struct RGWZoneGroup : public RGWSystemMetaObj {
   int create_default(bool old_format = false);
   int equals(const string& other_zonegroup) const;
   int add_zone(const RGWZoneParams& zone_params, bool *is_master, bool *read_only, const list<string>& endpoints);
-  int remove_zone(const RGWZoneParams& zone_params);
+  int remove_zone(const std::string& zone_id);
   int rename_zone(const RGWZoneParams& zone_params);
   const string& get_pool_name(CephContext *cct);
   const string get_default_oid(bool old_region_format = false);
@@ -2374,6 +2374,7 @@ public:
                    bufferlist *acl_bl, RGWObjCategory category,
 		   list<rgw_obj_key> *remove_objs);
       int complete_del(int64_t poolid, uint64_t epoch,
+                       ceph::real_time& removed_mtime, /* mtime of removed object */
                        list<rgw_obj_key> *remove_objs);
       int cancel();
     };
@@ -2753,7 +2754,7 @@ public:
   int cls_obj_complete_add(BucketShard& bs, string& tag, int64_t pool, uint64_t epoch, RGWObjEnt& ent,
                            RGWObjCategory category, list<rgw_obj_key> *remove_objs, uint16_t bilog_flags);
   int cls_obj_complete_del(BucketShard& bs, string& tag, int64_t pool, uint64_t epoch, rgw_obj& obj,
-                           list<rgw_obj_key> *remove_objs, uint16_t bilog_flags);
+                           ceph::real_time& removed_mtime, list<rgw_obj_key> *remove_objs, uint16_t bilog_flags);
   int cls_obj_complete_cancel(BucketShard& bs, string& tag, rgw_obj& obj, uint16_t bilog_flags);
   int cls_obj_set_bucket_tag_timeout(rgw_bucket& bucket, uint64_t timeout);
   int cls_bucket_list(rgw_bucket& bucket, int shard_id, rgw_obj_key& start, const string& prefix,
diff --git a/src/rgw/rgw_rest_client.cc b/src/rgw/rgw_rest_client.cc
index 73cc129..b219e6f 100644
--- a/src/rgw/rgw_rest_client.cc
+++ b/src/rgw/rgw_rest_client.cc
@@ -692,7 +692,7 @@ int RGWRESTStreamRWRequest::get_resource(RGWAccessKey& key, map<string, string>&
 int RGWRESTStreamRWRequest::complete(string& etag, real_time *mtime, map<string, string>& attrs)
 {
   set_str_from_headers(out_headers, "ETAG", etag);
-  if (status > 0 && mtime) {
+  if (status >= 0 && mtime) {
     string mtime_str;
     set_str_from_headers(out_headers, "RGWX_MTIME", mtime_str);
     if (!mtime_str.empty()) {
diff --git a/src/rgw/rgw_rest_conn.cc b/src/rgw/rgw_rest_conn.cc
index 88bfbc6..64c07d2 100644
--- a/src/rgw/rgw_rest_conn.cc
+++ b/src/rgw/rgw_rest_conn.cc
@@ -262,10 +262,8 @@ int RGWRESTReadResource::read()
 
 int RGWRESTReadResource::aio_read()
 {
-  get();
   int ret = req.get_resource(conn->get_key(), headers, resource, mgr);
   if (ret < 0) {
-    put();
     ldout(cct, 5) << __func__ << ": get_resource() resource=" << resource << " returned ret=" << ret << dendl;
     return ret;
   }
@@ -324,10 +322,8 @@ int RGWRESTPostResource::send(bufferlist& outbl)
 int RGWRESTPostResource::aio_send(bufferlist& outbl)
 {
   req.set_outbl(outbl);
-  get();
   int ret = req.get_resource(conn->get_key(), headers, resource, mgr);
   if (ret < 0) {
-    put();
     ldout(cct, 5) << __func__ << ": get_resource() resource=" << resource << " returned ret=" << ret << dendl;
     return ret;
   }
diff --git a/src/rgw/rgw_rest_conn.h b/src/rgw/rgw_rest_conn.h
index ef33b1f..415622f 100644
--- a/src/rgw/rgw_rest_conn.h
+++ b/src/rgw/rgw_rest_conn.h
@@ -193,7 +193,6 @@ public:
 
   int wait_bl(bufferlist *pbl) {
     int ret = req.wait();
-    put();
     if (ret < 0) {
       return ret;
     }
@@ -246,7 +245,6 @@ template <class T>
 int RGWRESTReadResource::wait(T *dest)
 {
   int ret = req.wait();
-  put();
   if (ret < 0) {
     return ret;
   }
@@ -309,7 +307,6 @@ public:
 
   int wait_bl(bufferlist *pbl) {
     int ret = req.wait();
-    put();
     if (ret < 0) {
       return ret;
     }
@@ -343,7 +340,6 @@ template <class T>
 int RGWRESTPostResource::wait(T *dest)
 {
   int ret = req.wait();
-  put();
   if (ret < 0) {
     return ret;
   }
diff --git a/src/rgw/rgw_rest_s3.cc b/src/rgw/rgw_rest_s3.cc
index 2ef10a5..dc4e970 100644
--- a/src/rgw/rgw_rest_s3.cc
+++ b/src/rgw/rgw_rest_s3.cc
@@ -761,6 +761,13 @@ int RGWSetBucketWebsite_ObjStore_S3::get_params()
     return r;
   }
 
+  if (s->aws4_auth_needs_complete) {
+      int ret_auth = do_aws4_auth_completion();
+      if (ret_auth < 0) {
+        return ret_auth;
+      }
+  }
+
   bufferlist bl;
   bl.append(data, len);
 
@@ -3576,6 +3583,7 @@ int RGW_Auth_S3::authorize_v4(RGWRados *store, struct req_state *s)
       case RGW_OP_SET_BUCKET_VERSIONING:
       case RGW_OP_DELETE_MULTI_OBJ:
       case RGW_OP_ADMIN_SET_METADATA:
+      case RGW_OP_SET_BUCKET_WEBSITE:
         break;
       default:
         dout(10) << "ERROR: AWS4 completion for this operation NOT IMPLEMENTED" << dendl;
diff --git a/src/rgw/rgw_swift_auth.cc b/src/rgw/rgw_swift_auth.cc
index 32234d7..8c7f032 100644
--- a/src/rgw/rgw_swift_auth.cc
+++ b/src/rgw/rgw_swift_auth.cc
@@ -215,7 +215,7 @@ void RGW_SWIFT_Auth_Get::execute()
     tenant_path.append(g_conf->rgw_swift_tenant_name);
   } else if (g_conf->rgw_swift_account_in_url) {
     tenant_path = "/AUTH_";
-    tenant_path.append(user_str);
+    tenant_path.append(info.user_id.to_str());
   }
 
   STREAM_IO(s)->print("X-Storage-Url: %s/%s/v1%s\r\n", swift_url.c_str(),
diff --git a/src/rgw/rgw_sync.cc b/src/rgw/rgw_sync.cc
index 922a904..dab98fc 100644
--- a/src/rgw/rgw_sync.cc
+++ b/src/rgw/rgw_sync.cc
@@ -587,17 +587,15 @@ bool RGWListRemoteMDLogCR::spawn_next() {
 
 class RGWInitSyncStatusCoroutine : public RGWCoroutine {
   RGWMetaSyncEnv *sync_env;
-  RGWObjectCtx& obj_ctx;
 
   rgw_meta_sync_info status;
   vector<RGWMetadataLogInfo> shards_info;
   RGWContinuousLeaseCR *lease_cr;
 public:
   RGWInitSyncStatusCoroutine(RGWMetaSyncEnv *_sync_env,
-                             RGWObjectCtx& _obj_ctx,
                              const rgw_meta_sync_info &status)
     : RGWCoroutine(_sync_env->store->ctx()), sync_env(_sync_env),
-      obj_ctx(_obj_ctx), status(status), shards_info(status.num_shards),
+      status(status), shards_info(status.num_shards),
       lease_cr(NULL) {}
 
   ~RGWInitSyncStatusCoroutine() {
@@ -689,19 +687,16 @@ public:
 
 class RGWReadSyncStatusCoroutine : public RGWSimpleRadosReadCR<rgw_meta_sync_info> {
   RGWMetaSyncEnv *sync_env;
-  RGWObjectCtx& obj_ctx;
 
   rgw_meta_sync_status *sync_status;
 
 public:
   RGWReadSyncStatusCoroutine(RGWMetaSyncEnv *_sync_env,
-		      RGWObjectCtx& _obj_ctx,
-		      rgw_meta_sync_status *_status) : RGWSimpleRadosReadCR(_sync_env->async_rados, _sync_env->store, _obj_ctx,
+		      rgw_meta_sync_status *_status) : RGWSimpleRadosReadCR(_sync_env->async_rados, _sync_env->store,
 									    _sync_env->store->get_zone_params().log_pool,
 									    _sync_env->status_oid(),
 									    &_status->sync_info),
                                                                             sync_env(_sync_env),
-                                                                            obj_ctx(_obj_ctx),
 									    sync_status(_status) {
 
   }
@@ -718,7 +713,7 @@ int RGWReadSyncStatusCoroutine::handle_data(rgw_meta_sync_info& data)
   RGWRados *store = sync_env->store;
   map<uint32_t, rgw_meta_sync_marker>& markers = sync_status->sync_markers;
   for (int i = 0; i < (int)data.num_shards; i++) {
-    spawn(new RGWSimpleRadosReadCR<rgw_meta_sync_marker>(sync_env->async_rados, store, obj_ctx, store->get_zone_params().log_pool,
+    spawn(new RGWSimpleRadosReadCR<rgw_meta_sync_marker>(sync_env->async_rados, store, store->get_zone_params().log_pool,
 				                    sync_env->shard_obj_name(i), &markers[i]), true);
   }
   return 0;
@@ -1636,8 +1631,6 @@ class RGWMetaSyncShardControlCR : public RGWBackoffControlCR
   rgw_meta_sync_marker sync_marker;
   const std::string period_marker;
 
-  RGWObjectCtx obj_ctx;
-
 public:
   RGWMetaSyncShardControlCR(RGWMetaSyncEnv *_sync_env, const rgw_bucket& _pool,
                             const std::string& period, RGWMetadataLog* mdlog,
@@ -1645,8 +1638,7 @@ public:
                             std::string&& period_marker)
     : RGWBackoffControlCR(_sync_env->cct, true), sync_env(_sync_env),
       pool(_pool), period(period), mdlog(mdlog), shard_id(_shard_id),
-      sync_marker(_marker), period_marker(std::move(period_marker)),
-      obj_ctx(sync_env->store) {}
+      sync_marker(_marker), period_marker(std::move(period_marker)) {}
 
   RGWCoroutine *alloc_cr() {
     return new RGWMetaSyncShardCR(sync_env, pool, period, mdlog, shard_id,
@@ -1655,7 +1647,7 @@ public:
 
   RGWCoroutine *alloc_finisher_cr() {
     RGWRados *store = sync_env->store;
-    return new RGWSimpleRadosReadCR<rgw_meta_sync_marker>(sync_env->async_rados, store, obj_ctx, pool,
+    return new RGWSimpleRadosReadCR<rgw_meta_sync_marker>(sync_env->async_rados, store, pool,
                                                                sync_env->shard_obj_name(shard_id), &sync_marker);
   }
 };
@@ -1784,8 +1776,7 @@ int RGWRemoteMetaLog::read_sync_status()
     return 0;
   }
 
-  RGWObjectCtx obj_ctx(store, NULL);
-  return run(new RGWReadSyncStatusCoroutine(&sync_env, obj_ctx, &sync_status));
+  return run(new RGWReadSyncStatusCoroutine(&sync_env, &sync_status));
 }
 
 int RGWRemoteMetaLog::init_sync_status()
@@ -1810,8 +1801,7 @@ int RGWRemoteMetaLog::init_sync_status()
     }
   }
 
-  RGWObjectCtx obj_ctx(store, NULL);
-  return run(new RGWInitSyncStatusCoroutine(&sync_env, obj_ctx, sync_info));
+  return run(new RGWInitSyncStatusCoroutine(&sync_env, sync_info));
 }
 
 int RGWRemoteMetaLog::store_sync_info()
@@ -1867,7 +1857,6 @@ int RGWRemoteMetaLog::run_sync()
     return 0;
   }
 
-  RGWObjectCtx obj_ctx(store, NULL);
   int r = 0;
 
   // get shard count and oldest log period from master
@@ -1897,7 +1886,7 @@ int RGWRemoteMetaLog::run_sync()
       ldout(store->ctx(), 1) << __func__ << "(): going down" << dendl;
       return 0;
     }
-    r = run(new RGWReadSyncStatusCoroutine(&sync_env, obj_ctx, &sync_status));
+    r = run(new RGWReadSyncStatusCoroutine(&sync_env, &sync_status));
     if (r < 0 && r != -ENOENT) {
       ldout(store->ctx(), 0) << "ERROR: failed to fetch sync status r=" << r << dendl;
       return r;
@@ -1922,8 +1911,7 @@ int RGWRemoteMetaLog::run_sync()
         sync_status.sync_info.period = cursor.get_period().get_id();
         sync_status.sync_info.realm_epoch = cursor.get_epoch();
       }
-      r = run(new RGWInitSyncStatusCoroutine(&sync_env, obj_ctx,
-                                             sync_status.sync_info));
+      r = run(new RGWInitSyncStatusCoroutine(&sync_env, sync_status.sync_info));
       if (r == -EBUSY) {
         backoff.backoff_sleep();
         continue;
@@ -1944,7 +1932,7 @@ int RGWRemoteMetaLog::run_sync()
 
   RGWPeriodHistory::Cursor cursor;
   do {
-    r = run(new RGWReadSyncStatusCoroutine(&sync_env, obj_ctx, &sync_status));
+    r = run(new RGWReadSyncStatusCoroutine(&sync_env, &sync_status));
     if (r < 0 && r != -ENOENT) {
       ldout(store->ctx(), 0) << "ERROR: failed to fetch sync status r=" << r << dendl;
       return r;
diff --git a/src/test/Makefile-client.am b/src/test/Makefile-client.am
index 243c2b0..71be16e 100644
--- a/src/test/Makefile-client.am
+++ b/src/test/Makefile-client.am
@@ -317,6 +317,13 @@ noinst_HEADERS += \
 	test/librados_test_stub/TestIoCtxImpl.h
 noinst_LTLIBRARIES += librados_test_stub.la
 
+libjournal_test_mock_la_SOURCES = \
+	test/journal/mock/MockJournaler.cc
+noinst_HEADERS += \
+	test/journal/mock/MockJournaler.h
+libjournal_test_mock_la_CXXFLAGS = $(UNITTEST_CXXFLAGS)
+noinst_LTLIBRARIES += libjournal_test_mock.la
+
 unittest_journal_SOURCES = \
 	test/journal/test_main.cc \
         test/journal/test_Entry.cc \
@@ -370,7 +377,8 @@ librbd_test_la_CXXFLAGS = $(UNITTEST_CXXFLAGS)
 noinst_LTLIBRARIES += librbd_test.la
 
 librbd_test_mock_la_SOURCES = \
-	test/librbd/mock/MockImageCtx.cc
+	test/librbd/mock/MockImageCtx.cc \
+	test/librbd/mock/MockJournal.cc
 librbd_test_mock_la_CXXFLAGS = $(UNITTEST_CXXFLAGS)
 noinst_LTLIBRARIES += librbd_test_mock.la
 
@@ -401,7 +409,7 @@ unittest_librbd_SOURCES = \
 	test/librbd/operation/test_mock_SnapshotUnprotectRequest.cc
 unittest_librbd_CXXFLAGS = $(UNITTEST_CXXFLAGS) -DTEST_LIBRBD_INTERNALS
 unittest_librbd_LDADD = \
-	librbd_test.la librbd_test_mock.la \
+	librbd_test.la libjournal_test_mock.la librbd_test_mock.la \
 	librbd_api.la librbd_internal.la $(LIBRBD_TYPES) \
 	libcls_rbd_client.la libcls_lock_client.la \
 	libjournal.la libcls_journal_client.la \
@@ -457,8 +465,7 @@ librbd_mirror_test_la_SOURCES = \
 
 noinst_HEADERS += \
 	test/rbd_mirror/test_fixture.h \
-	test/rbd_mirror/test_mock_fixture.h \
-	test/rbd_mirror/mock/MockJournaler.h
+	test/rbd_mirror/test_mock_fixture.h
 
 librbd_mirror_test_la_CXXFLAGS = $(UNITTEST_CXXFLAGS)
 noinst_LTLIBRARIES += librbd_mirror_test.la
@@ -468,15 +475,17 @@ unittest_rbd_mirror_SOURCES = \
 	test/rbd_mirror/test_mock_fixture.cc \
 	test/rbd_mirror/test_mock_ImageReplayer.cc \
 	test/rbd_mirror/test_mock_ImageSync.cc \
+	test/rbd_mirror/image_replayer/test_mock_BootstrapRequest.cc \
 	test/rbd_mirror/image_sync/test_mock_ImageCopyRequest.cc \
 	test/rbd_mirror/image_sync/test_mock_ObjectCopyRequest.cc \
 	test/rbd_mirror/image_sync/test_mock_SnapshotCopyRequest.cc \
+	test/rbd_mirror/image_sync/test_mock_SnapshotCreateRequest.cc \
 	test/rbd_mirror/image_sync/test_mock_SyncPointCreateRequest.cc \
-	test/rbd_mirror/image_sync/test_mock_SyncPointPruneRequest.cc \
-	test/rbd_mirror/mock/MockJournaler.cc
+	test/rbd_mirror/image_sync/test_mock_SyncPointPruneRequest.cc
 unittest_rbd_mirror_CXXFLAGS = $(UNITTEST_CXXFLAGS)
 unittest_rbd_mirror_LDADD = \
 	librbd_mirror_test.la \
+	libjournal_test_mock.la \
 	librbd_test_mock.la \
 	librados_test_stub.la \
 	librbd_mirror_internal.la \
diff --git a/src/test/centos-6/ceph.spec.in b/src/test/centos-6/ceph.spec.in
index 26928f7..34e4caa 100644
--- a/src/test/centos-6/ceph.spec.in
+++ b/src/test/centos-6/ceph.spec.in
@@ -773,6 +773,7 @@ install -m 0644 -D etc/sysconfig/ceph $RPM_BUILD_ROOT%{_localstatedir}/adm/fillu
   install -m 0644 -D systemd/ceph-rbd-mirror.target $RPM_BUILD_ROOT%{_unitdir}/ceph-rbd-mirror.target
   install -m 0644 -D systemd/ceph-disk at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-disk at .service
   install -m 0755 -D systemd/ceph $RPM_BUILD_ROOT%{_sbindir}/rcceph
+  install -m 0644 -D systemd/50-ceph.preset $RPM_BUILD_ROOT%{_libexecdir}/systemd/system-preset/50-ceph.preset
 %else
   install -D src/init-rbdmap $RPM_BUILD_ROOT%{_initrddir}/rbdmap
   install -D src/init-ceph $RPM_BUILD_ROOT%{_initrddir}/ceph
@@ -845,6 +846,7 @@ rm -rf $RPM_BUILD_ROOT
 %{_bindir}/cephfs
 %if 0%{?_with_systemd}
 %{_unitdir}/ceph-create-keys at .service
+%{_libexecdir}/systemd/system-preset/50-ceph.preset
 %else
 %{_initrddir}/ceph
 %endif
@@ -899,70 +901,35 @@ rm -rf $RPM_BUILD_ROOT
 %attr(770,ceph,ceph) %dir %{_localstatedir}/run/ceph
 %endif
 
-%pre base
-%if 0%{?_with_systemd}
-  %if 0%{?suse_version}
-    # service_add_pre and friends don't work with parameterized systemd service
-    # instances, only with single services or targets, so we always pass
-    # ceph.target to these macros
-    %service_add_pre ceph.target
-  %endif
-%endif
-
 %post base
 /sbin/ldconfig
-%if 0%{?_with_systemd}
-  %if 0%{?suse_version}
-    %fillup_only
-    %service_add_post ceph.target
-  %endif
-%else
-  /sbin/chkconfig --add ceph
+%if 0%{?suse_version}
+%fillup_only
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph.target >/dev/null 2>&1 || :
+fi
 %endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph.target
+%endif
+/usr/bin/systemctl start ceph.target >/dev/null 2>&1 || :
 
 %preun base
-%if 0%{?_with_systemd}
-  %if 0%{?suse_version}
-    %service_del_preun ceph.target
-  %endif
-  # Disable and stop on removal.
-  if [ $1 = 0 ] ; then
-    SERVICE_LIST=$(systemctl | grep -E '^ceph-mon@|^ceph-create-keys@|^ceph-osd@|^ceph-mds@|^ceph-disk-'  | cut -d' ' -f1)
-    if [ -n "$SERVICE_LIST" ]; then
-      for SERVICE in $SERVICE_LIST; do
-        /usr/bin/systemctl --no-reload disable $SERVICE > /dev/null 2>&1 || :
-        /usr/bin/systemctl stop $SERVICE > /dev/null 2>&1 || :
-      done
-    fi
-  fi
-%else
-  %if 0%{?rhel} || 0%{?fedora}
-    if [ $1 = 0 ] ; then
-      /sbin/service ceph stop >/dev/null 2>&1
-      /sbin/chkconfig --del ceph
-    fi
-  %endif
+%if 0%{?suse_version}
+%service_del_preun ceph.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph.target
 %endif
 
 %postun base
 /sbin/ldconfig
-%if 0%{?_with_systemd}
-  if [ $1 = 1 ] ; then
-    # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
-    # "yes". In any case: if units are not running, do not touch them.
-    SYSCONF_CEPH=/etc/sysconfig/ceph
-    if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
-      source $SYSCONF_CEPH
-    fi
-    if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
-      SERVICE_LIST=$(systemctl | grep -E '^ceph-mon@|^ceph-create-keys@|^ceph-osd@|^ceph-mds@|^ceph-disk-'  | cut -d' ' -f1)
-      if [ -n "$SERVICE_LIST" ]; then
-        for SERVICE in $SERVICE_LIST; do
-          /usr/bin/systemctl try-restart $SERVICE > /dev/null 2>&1 || :
-        done
-      fi
-    fi
-  fi
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph.target
 %endif
 
 #################################################################################
@@ -1068,6 +1035,46 @@ fi
 %endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/mds
 
+%post mds
+%if 0%{?suse_version}
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-mds@\*.service ceph-mds.target >/dev/null 2>&1 || :
+fi
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-mds@\*.service ceph-mds.target
+%endif
+/usr/bin/systemctl start ceph-mds.target >/dev/null 2>&1 || :
+
+%preun mds
+%if 0%{?suse_version}
+%service_del_preun ceph-mds@\*.service ceph-mds.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-mds@\*.service ceph-mds.target
+%endif
+
+%postun mds
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-mds@\*.service ceph-mds.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-mds@\*.service ceph-mds.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-mds@\*.service > /dev/null 2>&1 || :
+  fi
+fi
+
 #################################################################################
 %files mon
 %{_bindir}/ceph-mon
@@ -1083,6 +1090,46 @@ fi
 %endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/mon
 
+%post mon
+%if 0%{?suse_version}
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target >/dev/null 2>&1 || :
+fi
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+/usr/bin/systemctl start ceph-mon.target >/dev/null 2>&1 || :
+
+%preun mon
+%if 0%{?suse_version}
+%service_del_preun ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+
+%postun mon
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-create-keys@\*.service ceph-mon@\*.service > /dev/null 2>&1 || :
+  fi
+fi
+
 #################################################################################
 %files fuse
 %defattr(-,root,root,-)
@@ -1110,6 +1157,46 @@ fi
 %{_unitdir}/ceph-rbd-mirror.target
 %endif
 
+%post -n rbd-mirror
+%if 0%{?suse_version}
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-rbd-mirror@\*.service ceph-rbd-mirror.target >/dev/null 2>&1 || :
+fi
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+/usr/bin/systemctl start ceph-rbd-mirror.target >/dev/null 2>&1 || :
+
+%preun -n rbd-mirror
+%if 0%{?suse_version}
+%service_del_preun ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+
+%postun -n rbd-mirror
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-rbd-mirror@\*.service > /dev/null 2>&1 || :
+  fi
+fi
+
 #################################################################################
 %files -n rbd-nbd
 %defattr(-,root,root,-)
@@ -1136,48 +1223,44 @@ fi
 %endif
 
 %post radosgw
-/sbin/ldconfig
 %if 0%{?suse_version}
-  # explicit systemctl daemon-reload (that's the only relevant bit of
-  # service_add_post; the rest is all sysvinit --> systemd migration which
-  # isn't applicable in this context (see above comment).
-  /usr/bin/systemctl daemon-reload >/dev/null 2>&1 || :
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-radosgw@\*.service ceph-radosgw.target >/dev/null 2>&1 || :
+fi
 %endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-radosgw@\*.service ceph-radosgw.target
+%endif
+/usr/bin/systemctl start ceph-radosgw.target >/dev/null 2>&1 || :
 
 %preun radosgw
-%if 0%{?_with_systemd}
-  # Disable and stop on removal.
-  if [ $1 = 0 ] ; then
-    SERVICE_LIST=$(systemctl | grep -E '^ceph-radosgw@'  | cut -d' ' -f1)
-    if [ -n "$SERVICE_LIST" ]; then
-      for SERVICE in $SERVICE_LIST; do
-        /usr/bin/systemctl --no-reload disable $SERVICE > /dev/null 2>&1 || :
-        /usr/bin/systemctl stop $SERVICE > /dev/null 2>&1 || :
-      done
-    fi
-  fi
+%if 0%{?suse_version}
+%service_del_preun ceph-radosgw@\*.service ceph-radosgw.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-radosgw@\*.service ceph-radosgw.target
 %endif
 
 %postun radosgw
-/sbin/ldconfig
-%if 0%{?_with_systemd}
-  if [ $1 = 1 ] ; then
-    # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
-    # "yes". In any case: if units are not running, do not touch them.
-    SYSCONF_CEPH=/etc/sysconfig/ceph
-    if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
-      source $SYSCONF_CEPH
-    fi
-    if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
-      SERVICE_LIST=$(systemctl | grep -E '^ceph-radosgw@'  | cut -d' ' -f1)
-      if [ -n "$SERVICE_LIST" ]; then
-        for SERVICE in $SERVICE_LIST; do
-          /usr/bin/systemctl try-restart $SERVICE > /dev/null 2>&1 || :
-        done
-      fi
-    fi
-  fi
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-radosgw@\*.service ceph-radosgw.target
 %endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-radosgw@\*.service ceph-radosgw.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-radosgw@\*.service > /dev/null 2>&1 || :
+  fi
+fi
 
 #################################################################################
 %files osd
@@ -1205,6 +1288,46 @@ fi
 %endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/osd
 
+%post osd
+%if 0%{?suse_version}
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target >/dev/null 2>&1 || :
+fi
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+/usr/bin/systemctl start ceph-osd.target >/dev/null 2>&1 || :
+
+%preun osd
+%if 0%{?suse_version}
+%service_del_preun ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+
+%postun osd
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-disk@\*.service ceph-osd@\*.service > /dev/null 2>&1 || :
+  fi
+fi
+
 #################################################################################
 %if %{with ocf}
 
@@ -1450,7 +1573,7 @@ else
     exit 0
 fi
 
-if test "$OLD_POLVER" == "$NEW_POLVER"; then
+if test "$OLD_POLVER" = "$NEW_POLVER"; then
    # Do not relabel if policy version did not change
    exit 0
 fi
diff --git a/src/test/centos-6/install-deps.sh b/src/test/centos-6/install-deps.sh
index 8249ea3..21e71ee 100755
--- a/src/test/centos-6/install-deps.sh
+++ b/src/test/centos-6/install-deps.sh
@@ -66,7 +66,7 @@ CentOS|Fedora|RedHatEnterpriseServer)
                     $SUDO yum install subscription-manager
                     $SUDO subscription-manager repos --enable=rhel-$MAJOR_VERSION-server-optional-rpms
                 fi
-                $SUDO yum-config-manager --add-repo https://dl.fedoraproject.org/pub/epel/$MAJOR_VERSION/x86_64/ 
+                $SUDO yum-config-manager --add-repo https://dl.fedoraproject.org/pub/epel/$MAJOR_VERSION/x86_64/
                 $SUDO yum install --nogpgcheck -y epel-release
                 $SUDO rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-$MAJOR_VERSION
                 $SUDO rm -f /etc/yum.repos.d/dl.fedoraproject.org*
@@ -92,8 +92,6 @@ function populate_wheelhouse() {
     local install=$1
     shift
 
-    # Ubuntu-12.04 and Python 2.7.3 require this line
-    pip --timeout 300 $install 'distribute >= 0.7.3' || return 1
     # although pip comes with virtualenv, having a recent version
     # of pip matters when it comes to using wheel packages
     pip --timeout 300 $install 'setuptools >= 0.8' 'pip >= 7.0' 'wheel >= 0.24' || return 1
diff --git a/src/test/centos-7/ceph.spec.in b/src/test/centos-7/ceph.spec.in
index 26928f7..34e4caa 100644
--- a/src/test/centos-7/ceph.spec.in
+++ b/src/test/centos-7/ceph.spec.in
@@ -773,6 +773,7 @@ install -m 0644 -D etc/sysconfig/ceph $RPM_BUILD_ROOT%{_localstatedir}/adm/fillu
   install -m 0644 -D systemd/ceph-rbd-mirror.target $RPM_BUILD_ROOT%{_unitdir}/ceph-rbd-mirror.target
   install -m 0644 -D systemd/ceph-disk at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-disk at .service
   install -m 0755 -D systemd/ceph $RPM_BUILD_ROOT%{_sbindir}/rcceph
+  install -m 0644 -D systemd/50-ceph.preset $RPM_BUILD_ROOT%{_libexecdir}/systemd/system-preset/50-ceph.preset
 %else
   install -D src/init-rbdmap $RPM_BUILD_ROOT%{_initrddir}/rbdmap
   install -D src/init-ceph $RPM_BUILD_ROOT%{_initrddir}/ceph
@@ -845,6 +846,7 @@ rm -rf $RPM_BUILD_ROOT
 %{_bindir}/cephfs
 %if 0%{?_with_systemd}
 %{_unitdir}/ceph-create-keys at .service
+%{_libexecdir}/systemd/system-preset/50-ceph.preset
 %else
 %{_initrddir}/ceph
 %endif
@@ -899,70 +901,35 @@ rm -rf $RPM_BUILD_ROOT
 %attr(770,ceph,ceph) %dir %{_localstatedir}/run/ceph
 %endif
 
-%pre base
-%if 0%{?_with_systemd}
-  %if 0%{?suse_version}
-    # service_add_pre and friends don't work with parameterized systemd service
-    # instances, only with single services or targets, so we always pass
-    # ceph.target to these macros
-    %service_add_pre ceph.target
-  %endif
-%endif
-
 %post base
 /sbin/ldconfig
-%if 0%{?_with_systemd}
-  %if 0%{?suse_version}
-    %fillup_only
-    %service_add_post ceph.target
-  %endif
-%else
-  /sbin/chkconfig --add ceph
+%if 0%{?suse_version}
+%fillup_only
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph.target >/dev/null 2>&1 || :
+fi
 %endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph.target
+%endif
+/usr/bin/systemctl start ceph.target >/dev/null 2>&1 || :
 
 %preun base
-%if 0%{?_with_systemd}
-  %if 0%{?suse_version}
-    %service_del_preun ceph.target
-  %endif
-  # Disable and stop on removal.
-  if [ $1 = 0 ] ; then
-    SERVICE_LIST=$(systemctl | grep -E '^ceph-mon@|^ceph-create-keys@|^ceph-osd@|^ceph-mds@|^ceph-disk-'  | cut -d' ' -f1)
-    if [ -n "$SERVICE_LIST" ]; then
-      for SERVICE in $SERVICE_LIST; do
-        /usr/bin/systemctl --no-reload disable $SERVICE > /dev/null 2>&1 || :
-        /usr/bin/systemctl stop $SERVICE > /dev/null 2>&1 || :
-      done
-    fi
-  fi
-%else
-  %if 0%{?rhel} || 0%{?fedora}
-    if [ $1 = 0 ] ; then
-      /sbin/service ceph stop >/dev/null 2>&1
-      /sbin/chkconfig --del ceph
-    fi
-  %endif
+%if 0%{?suse_version}
+%service_del_preun ceph.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph.target
 %endif
 
 %postun base
 /sbin/ldconfig
-%if 0%{?_with_systemd}
-  if [ $1 = 1 ] ; then
-    # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
-    # "yes". In any case: if units are not running, do not touch them.
-    SYSCONF_CEPH=/etc/sysconfig/ceph
-    if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
-      source $SYSCONF_CEPH
-    fi
-    if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
-      SERVICE_LIST=$(systemctl | grep -E '^ceph-mon@|^ceph-create-keys@|^ceph-osd@|^ceph-mds@|^ceph-disk-'  | cut -d' ' -f1)
-      if [ -n "$SERVICE_LIST" ]; then
-        for SERVICE in $SERVICE_LIST; do
-          /usr/bin/systemctl try-restart $SERVICE > /dev/null 2>&1 || :
-        done
-      fi
-    fi
-  fi
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph.target
 %endif
 
 #################################################################################
@@ -1068,6 +1035,46 @@ fi
 %endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/mds
 
+%post mds
+%if 0%{?suse_version}
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-mds@\*.service ceph-mds.target >/dev/null 2>&1 || :
+fi
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-mds@\*.service ceph-mds.target
+%endif
+/usr/bin/systemctl start ceph-mds.target >/dev/null 2>&1 || :
+
+%preun mds
+%if 0%{?suse_version}
+%service_del_preun ceph-mds@\*.service ceph-mds.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-mds@\*.service ceph-mds.target
+%endif
+
+%postun mds
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-mds@\*.service ceph-mds.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-mds@\*.service ceph-mds.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-mds@\*.service > /dev/null 2>&1 || :
+  fi
+fi
+
 #################################################################################
 %files mon
 %{_bindir}/ceph-mon
@@ -1083,6 +1090,46 @@ fi
 %endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/mon
 
+%post mon
+%if 0%{?suse_version}
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target >/dev/null 2>&1 || :
+fi
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+/usr/bin/systemctl start ceph-mon.target >/dev/null 2>&1 || :
+
+%preun mon
+%if 0%{?suse_version}
+%service_del_preun ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+
+%postun mon
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-create-keys@\*.service ceph-mon@\*.service > /dev/null 2>&1 || :
+  fi
+fi
+
 #################################################################################
 %files fuse
 %defattr(-,root,root,-)
@@ -1110,6 +1157,46 @@ fi
 %{_unitdir}/ceph-rbd-mirror.target
 %endif
 
+%post -n rbd-mirror
+%if 0%{?suse_version}
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-rbd-mirror@\*.service ceph-rbd-mirror.target >/dev/null 2>&1 || :
+fi
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+/usr/bin/systemctl start ceph-rbd-mirror.target >/dev/null 2>&1 || :
+
+%preun -n rbd-mirror
+%if 0%{?suse_version}
+%service_del_preun ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+
+%postun -n rbd-mirror
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-rbd-mirror@\*.service > /dev/null 2>&1 || :
+  fi
+fi
+
 #################################################################################
 %files -n rbd-nbd
 %defattr(-,root,root,-)
@@ -1136,48 +1223,44 @@ fi
 %endif
 
 %post radosgw
-/sbin/ldconfig
 %if 0%{?suse_version}
-  # explicit systemctl daemon-reload (that's the only relevant bit of
-  # service_add_post; the rest is all sysvinit --> systemd migration which
-  # isn't applicable in this context (see above comment).
-  /usr/bin/systemctl daemon-reload >/dev/null 2>&1 || :
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-radosgw@\*.service ceph-radosgw.target >/dev/null 2>&1 || :
+fi
 %endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-radosgw@\*.service ceph-radosgw.target
+%endif
+/usr/bin/systemctl start ceph-radosgw.target >/dev/null 2>&1 || :
 
 %preun radosgw
-%if 0%{?_with_systemd}
-  # Disable and stop on removal.
-  if [ $1 = 0 ] ; then
-    SERVICE_LIST=$(systemctl | grep -E '^ceph-radosgw@'  | cut -d' ' -f1)
-    if [ -n "$SERVICE_LIST" ]; then
-      for SERVICE in $SERVICE_LIST; do
-        /usr/bin/systemctl --no-reload disable $SERVICE > /dev/null 2>&1 || :
-        /usr/bin/systemctl stop $SERVICE > /dev/null 2>&1 || :
-      done
-    fi
-  fi
+%if 0%{?suse_version}
+%service_del_preun ceph-radosgw@\*.service ceph-radosgw.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-radosgw@\*.service ceph-radosgw.target
 %endif
 
 %postun radosgw
-/sbin/ldconfig
-%if 0%{?_with_systemd}
-  if [ $1 = 1 ] ; then
-    # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
-    # "yes". In any case: if units are not running, do not touch them.
-    SYSCONF_CEPH=/etc/sysconfig/ceph
-    if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
-      source $SYSCONF_CEPH
-    fi
-    if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
-      SERVICE_LIST=$(systemctl | grep -E '^ceph-radosgw@'  | cut -d' ' -f1)
-      if [ -n "$SERVICE_LIST" ]; then
-        for SERVICE in $SERVICE_LIST; do
-          /usr/bin/systemctl try-restart $SERVICE > /dev/null 2>&1 || :
-        done
-      fi
-    fi
-  fi
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-radosgw@\*.service ceph-radosgw.target
 %endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-radosgw@\*.service ceph-radosgw.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-radosgw@\*.service > /dev/null 2>&1 || :
+  fi
+fi
 
 #################################################################################
 %files osd
@@ -1205,6 +1288,46 @@ fi
 %endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/osd
 
+%post osd
+%if 0%{?suse_version}
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target >/dev/null 2>&1 || :
+fi
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+/usr/bin/systemctl start ceph-osd.target >/dev/null 2>&1 || :
+
+%preun osd
+%if 0%{?suse_version}
+%service_del_preun ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+
+%postun osd
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-disk@\*.service ceph-osd@\*.service > /dev/null 2>&1 || :
+  fi
+fi
+
 #################################################################################
 %if %{with ocf}
 
@@ -1450,7 +1573,7 @@ else
     exit 0
 fi
 
-if test "$OLD_POLVER" == "$NEW_POLVER"; then
+if test "$OLD_POLVER" = "$NEW_POLVER"; then
    # Do not relabel if policy version did not change
    exit 0
 fi
diff --git a/src/test/centos-7/install-deps.sh b/src/test/centos-7/install-deps.sh
index 8249ea3..21e71ee 100755
--- a/src/test/centos-7/install-deps.sh
+++ b/src/test/centos-7/install-deps.sh
@@ -66,7 +66,7 @@ CentOS|Fedora|RedHatEnterpriseServer)
                     $SUDO yum install subscription-manager
                     $SUDO subscription-manager repos --enable=rhel-$MAJOR_VERSION-server-optional-rpms
                 fi
-                $SUDO yum-config-manager --add-repo https://dl.fedoraproject.org/pub/epel/$MAJOR_VERSION/x86_64/ 
+                $SUDO yum-config-manager --add-repo https://dl.fedoraproject.org/pub/epel/$MAJOR_VERSION/x86_64/
                 $SUDO yum install --nogpgcheck -y epel-release
                 $SUDO rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-$MAJOR_VERSION
                 $SUDO rm -f /etc/yum.repos.d/dl.fedoraproject.org*
@@ -92,8 +92,6 @@ function populate_wheelhouse() {
     local install=$1
     shift
 
-    # Ubuntu-12.04 and Python 2.7.3 require this line
-    pip --timeout 300 $install 'distribute >= 0.7.3' || return 1
     # although pip comes with virtualenv, having a recent version
     # of pip matters when it comes to using wheel packages
     pip --timeout 300 $install 'setuptools >= 0.8' 'pip >= 7.0' 'wheel >= 0.24' || return 1
diff --git a/src/test/ceph_objectstore_tool.py b/src/test/ceph_objectstore_tool.py
index 4182103..ed79286 100755
--- a/src/test/ceph_objectstore_tool.py
+++ b/src/test/ceph_objectstore_tool.py
@@ -43,7 +43,7 @@ logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.WARNING)
 def wait_for_health():
     print "Wait for health_ok...",
     tries = 0
-    while call("./ceph health 2> /dev/null | grep -v 'HEALTH_OK\|HEALTH_WARN' > /dev/null", shell=True) == 0:
+    while call("{path}/ceph health 2> /dev/null | grep -v 'HEALTH_OK\|HEALTH_WARN' > /dev/null".format(path=CEPH_BIN), shell=True) == 0:
         tries += 1
         if tries == 150:
             raise Exception("Time exceeded to go to health")
@@ -139,7 +139,7 @@ def cat_file(level, filename):
 def vstart(new, opt=""):
     print "vstarting....",
     NEW = new and "-n" or ""
-    call("MON=1 OSD=4 CEPH_PORT=7400 {path}/src/vstart.sh -l {new} -d mon osd {opt} > /dev/null 2>&1".format(new=NEW, opt=opt, path=CEPH_ROOT), shell=True)
+    call("MON=1 OSD=4 CEPH_PORT=7400 {path}/src/vstart.sh --short -l {new} -d mon osd {opt} > /dev/null 2>&1".format(new=NEW, opt=opt, path=CEPH_ROOT), shell=True)
     print "DONE"
 
 
@@ -367,11 +367,22 @@ def test_dump_journal(CFSD_PREFIX, osds):
 
     return ERRORS
 
-
-CEPH_DIR = os.environ['CEPH_BUILD_DIR'] + "/ceph_objectstore_tool_dir"
+CEPH_BUILD_DIR = os.environ.get('CEPH_BUILD_DIR')
+CEPH_BIN = os.environ.get('CEPH_BIN')
+CEPH_ROOT = os.environ.get('CEPH_ROOT')
+
+if not CEPH_BUILD_DIR:
+    CEPH_BUILD_DIR=os.getcwd()
+    os.putenv('CEPH_BUILD_DIR', CEPH_BUILD_DIR)
+    CEPH_BIN=CEPH_BUILD_DIR
+    os.putenv('CEPH_BIN', CEPH_BIN)
+    CEPH_ROOT=os.path.dirname(CEPH_BUILD_DIR)
+    os.putenv('CEPH_ROOT', CEPH_ROOT)
+    CEPH_LIB=os.path.join(CEPH_BIN, '.libs')
+    os.putenv('CEPH_LIB', CEPH_LIB)
+
+CEPH_DIR = CEPH_BUILD_DIR + "/ceph_objectstore_tool_dir"
 CEPH_CONF = os.path.join(CEPH_DIR, 'ceph.conf')
-CEPH_BIN = os.environ['CEPH_BIN']
-CEPH_ROOT = os.environ['CEPH_ROOT']
 
 def kill_daemons():
     call("{path}/init-ceph -c {conf} stop osd mon > /dev/null 2>&1".format(conf=CEPH_CONF, path=CEPH_BIN), shell=True)
diff --git a/src/test/cli/crushtool/help.t b/src/test/cli/crushtool/help.t
index e5ead08..7edb3af 100755
--- a/src/test/cli/crushtool/help.t
+++ b/src/test/cli/crushtool/help.t
@@ -26,7 +26,7 @@
                            compile with unsafe tunables
      --build --num_osds N layer1 ...
                            build a new map, where each 'layer' is
-                           'name (uniform|straw|list|tree) size'
+                           'name (uniform|straw2|straw|list|tree) size'
   
   Options for the tunables adjustments stage
   
diff --git a/src/test/cli/radosgw-admin/help.t b/src/test/cli/radosgw-admin/help.t
index 5867f00..978d45e 100644
--- a/src/test/cli/radosgw-admin/help.t
+++ b/src/test/cli/radosgw-admin/help.t
@@ -53,8 +53,9 @@
     zonegroup default          set default zone group
     zonegroup delete           delete a zone group info
     zonegroup get              show zone group info
-    zonegroup modify           set/clear zonegroup master status
+    zonegroup modify           modify an existing zonegroup
     zonegroup set              set zone group info (requires infile)
+    zonegroup remove           remove a zone from a zonegroup
     zonegroup rename           rename a zone group
     zonegroup list             list all zone groups set on this cluster
     zonegroup-map get          show zonegroup-map
@@ -62,7 +63,7 @@
     zone create                create a new zone
     zone delete                delete a zone
     zone get                   show zone cluster params
-    zone modify                set/clear zone master status
+    zone modify                modify an existing zone
     zone set                   set zone cluster params (requires infile)
     zone list                  list all zones set on this cluster
     pool add                   add an existing pool for data placement
@@ -135,7 +136,7 @@
                                  replica mdlog get/delete
                                  replica datalog get/delete
      --metadata-key=<key>      key to retrieve metadata from with metadata get
-     --remote=<remote>         remote to pull period
+     --remote=<remote>         zone or zonegroup id of remote gateway
      --period=<id>             period id
      --epoch=<number>          period epoch
      --commit                  commit the period during 'period update'
@@ -147,7 +148,8 @@
      --realm-id=<realm id>     realm id
      --realm-new-name=<realm new name> realm new name
      --rgw-zonegroup=<zonegroup>   zonegroup name
-     --rgw-zone=<zone>         zone in which radosgw is running
+     --zonegroup-id=<zonegroup id> zonegroup id
+     --rgw-zone=<zone>         name of zone in which radosgw is running
      --zone-id=<zone id>       zone id
      --zone-new-name=<zone>    zone new name
      --source-zone             specify the source zone (for data sync)
diff --git a/src/test/cli/rbd/help.t b/src/test/cli/rbd/help.t
index f68a486..4848370 100644
--- a/src/test/cli/rbd/help.t
+++ b/src/test/cli/rbd/help.t
@@ -48,6 +48,7 @@
       mirror image enable         Enable RBD mirroring for an image.
       mirror image promote        Promote an image to primary for RBD mirroring.
       mirror image resync         Force resync to primary image for RBD mirroring.
+      mirror image status         Show RDB mirroring status for an image.
       mirror pool disable         Disable RBD mirroring by default within a pool.
       mirror pool enable          Enable RBD mirroring by default within a pool.
       mirror pool info            Show information about the pool mirroring
@@ -55,6 +56,7 @@
       mirror pool peer add        Add a mirroring peer to a pool.
       mirror pool peer remove     Remove a mirroring peer from a pool.
       mirror pool peer set        Update mirroring peer settings.
+      mirror pool status          Show status for all mirrored images in the pool.
       nbd list (nbd ls)           List the nbd devices already used.
       nbd map                     Map image to a nbd device.
       nbd unmap                   Unmap a nbd device.
@@ -282,6 +284,7 @@
   rbd help disk-usage
   usage: rbd disk-usage [--pool <pool>] [--image <image>] [--snap <snap>] 
                         [--format <format>] [--pretty-format] 
+                        [--from-snap <from-snap>] 
                         <image-or-snap-spec> 
   
   Show disk usage stats for pool, image or snapshot
@@ -296,6 +299,7 @@
     --snap arg            snapshot name
     --format arg          output format [plain, json, or xml]
     --pretty-format       pretty formatting (json and xml)
+    --from-snap arg       snapshot starting point
   
   rbd help export
   usage: rbd export [--pool <pool>] [--image <image>] [--snap <snap>] 
@@ -824,6 +828,23 @@
     -p [ --pool ] arg    pool name
     --image arg          image name
   
+  rbd help mirror image status
+  usage: rbd mirror image status [--pool <pool>] [--image <image>] 
+                                 [--format <format>] [--pretty-format] 
+                                 <image-spec> 
+  
+  Show RDB mirroring status for an image.
+  
+  Positional arguments
+    <image-spec>         image specification
+                         (example: [<pool-name>/]<image-name>)
+  
+  Optional arguments
+    -p [ --pool ] arg    pool name
+    --image arg          image name
+    --format arg         output format [plain, json, or xml]
+    --pretty-format      pretty formatting (json and xml)
+  
   rbd help mirror pool disable
   usage: rbd mirror pool disable [--pool <pool>] 
                                  <pool-name> 
@@ -910,6 +931,22 @@
   Optional arguments
     -p [ --pool ] arg    pool name
   
+  rbd help mirror pool status
+  usage: rbd mirror pool status [--pool <pool>] [--format <format>] 
+                                [--pretty-format] [--verbose] 
+                                <pool-name> 
+  
+  Show status for all mirrored images in the pool.
+  
+  Positional arguments
+    <pool-name>          pool name
+  
+  Optional arguments
+    -p [ --pool ] arg    pool name
+    --format arg         output format [plain, json, or xml]
+    --pretty-format      pretty formatting (json and xml)
+    --verbose            be verbose
+  
   rbd help nbd list
   usage: rbd nbd list 
   
diff --git a/src/test/cls_rbd/test_cls_rbd.cc b/src/test/cls_rbd/test_cls_rbd.cc
index 64f5b54..132af04 100644
--- a/src/test/cls_rbd/test_cls_rbd.cc
+++ b/src/test/cls_rbd/test_cls_rbd.cc
@@ -1,6 +1,8 @@
 // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
 // vim: ts=8 sw=2 smarttab
 
+#include "common/ceph_context.h"
+#include "common/config.h"
 #include "common/snap_types.h"
 #include "include/encoding.h"
 #include "include/types.h"
@@ -1428,3 +1430,205 @@ TEST_F(TestClsRbd, mirror_image) {
   expected_mirror_image_ids = {};
   ASSERT_EQ(expected_mirror_image_ids, mirror_image_ids);
 }
+
+TEST_F(TestClsRbd, mirror_image_status) {
+  struct WatchCtx : public librados::WatchCtx2 {
+    librados::IoCtx *m_ioctx;
+
+    WatchCtx(librados::IoCtx *ioctx) : m_ioctx(ioctx) {}
+    virtual void handle_notify(uint64_t notify_id, uint64_t cookie,
+			     uint64_t notifier_id, bufferlist& bl_) {
+      bufferlist bl;
+      m_ioctx->notify_ack(RBD_MIRRORING, notify_id, cookie, bl);
+    }
+    virtual void handle_error(uint64_t cookie, int err) {}
+  };
+
+  map<std::string, cls::rbd::MirrorImage> images;
+  map<std::string, cls::rbd::MirrorImageStatus> statuses;
+  std::map<cls::rbd::MirrorImageStatusState, int> states;
+  cls::rbd::MirrorImageStatus read_status;
+  uint64_t watch_handle;
+  librados::IoCtx ioctx;
+
+  ASSERT_EQ(0, _rados.ioctx_create(_pool_name.c_str(), ioctx));
+  ioctx.remove(RBD_MIRRORING);
+
+  // Test list fails on nonexistent RBD_MIRRORING object
+
+  ASSERT_EQ(-ENOENT, mirror_image_status_list(&ioctx, "", 1024, &images,
+	  &statuses));
+
+  // Test status set
+
+  cls::rbd::MirrorImage image1("uuid1", cls::rbd::MIRROR_IMAGE_STATE_ENABLED);
+  cls::rbd::MirrorImage image2("uuid2", cls::rbd::MIRROR_IMAGE_STATE_ENABLED);
+  cls::rbd::MirrorImage image3("uuid3", cls::rbd::MIRROR_IMAGE_STATE_ENABLED);
+
+  ASSERT_EQ(0, mirror_image_set(&ioctx, "image_id1", image1));
+  ASSERT_EQ(0, mirror_image_set(&ioctx, "image_id2", image2));
+  ASSERT_EQ(0, mirror_image_set(&ioctx, "image_id3", image3));
+
+  cls::rbd::MirrorImageStatus status1(cls::rbd::MIRROR_IMAGE_STATUS_STATE_UNKNOWN);
+  cls::rbd::MirrorImageStatus status2(cls::rbd::MIRROR_IMAGE_STATUS_STATE_REPLAYING);
+  cls::rbd::MirrorImageStatus status3(cls::rbd::MIRROR_IMAGE_STATUS_STATE_ERROR);
+
+  ASSERT_EQ(0, mirror_image_status_set(&ioctx, "uuid1", status1));
+  images.clear();
+  statuses.clear();
+  ASSERT_EQ(0, mirror_image_status_list(&ioctx, "", 1024, &images, &statuses));
+  ASSERT_EQ(3U, images.size());
+  ASSERT_EQ(1U, statuses.size());
+
+  // Test status is down due to RBD_MIRRORING is not watched
+
+  status1.up = false;
+  ASSERT_EQ(statuses["image_id1"], status1);
+  ASSERT_EQ(0, mirror_image_status_get(&ioctx, "uuid1", &read_status));
+  ASSERT_EQ(read_status, status1);
+
+  // Test status summary. All statuses are unknown due to down.
+  states.clear();
+  ASSERT_EQ(0, mirror_image_status_get_summary(&ioctx, &states));
+  ASSERT_EQ(1U, states.size());
+  ASSERT_EQ(3, states[cls::rbd::MIRROR_IMAGE_STATUS_STATE_UNKNOWN]);
+
+  // Test remove_down removes stale statuses
+
+  ASSERT_EQ(0, mirror_image_status_remove_down(&ioctx));
+  ASSERT_EQ(-ENOENT, mirror_image_status_get(&ioctx, "uuid1", &read_status));
+  ASSERT_EQ(0, mirror_image_status_list(&ioctx, "", 1024, &images, &statuses));
+  ASSERT_EQ(3U, images.size());
+  ASSERT_TRUE(statuses.empty());
+  ASSERT_EQ(0, mirror_image_status_get_summary(&ioctx, &states));
+  ASSERT_EQ(1U, states.size());
+  ASSERT_EQ(3, states[cls::rbd::MIRROR_IMAGE_STATUS_STATE_UNKNOWN]);
+
+  // Test statuses are not down after watcher is started
+
+  ASSERT_EQ(0, mirror_image_status_set(&ioctx, "uuid1", status1));
+
+  WatchCtx watch_ctx(&ioctx);
+  ASSERT_EQ(0, ioctx.watch2(RBD_MIRRORING, &watch_handle, &watch_ctx));
+
+  ASSERT_EQ(0, mirror_image_status_set(&ioctx, "uuid2", status2));
+  ASSERT_EQ(0, mirror_image_status_set(&ioctx, "uuid3", status3));
+
+  ASSERT_EQ(0, mirror_image_status_get(&ioctx, "uuid1", &read_status));
+  status1.up = true;
+  ASSERT_EQ(read_status, status1);
+  ASSERT_EQ(0, mirror_image_status_get(&ioctx, "uuid2", &read_status));
+  status2.up = true;
+  ASSERT_EQ(read_status, status2);
+  ASSERT_EQ(0, mirror_image_status_get(&ioctx, "uuid3", &read_status));
+  status3.up = true;
+  ASSERT_EQ(read_status, status3);
+
+  images.clear();
+  statuses.clear();
+  ASSERT_EQ(0, mirror_image_status_list(&ioctx, "", 1024, &images, &statuses));
+  ASSERT_EQ(3U, images.size());
+  ASSERT_EQ(3U, statuses.size());
+  ASSERT_EQ(statuses["image_id1"], status1);
+  ASSERT_EQ(statuses["image_id2"], status2);
+  ASSERT_EQ(statuses["image_id3"], status3);
+
+  ASSERT_EQ(0, mirror_image_status_remove_down(&ioctx));
+  ASSERT_EQ(0, mirror_image_status_get(&ioctx, "uuid1", &read_status));
+  ASSERT_EQ(read_status, status1);
+  images.clear();
+  statuses.clear();
+  ASSERT_EQ(0, mirror_image_status_list(&ioctx, "", 1024, &images, &statuses));
+  ASSERT_EQ(3U, images.size());
+  ASSERT_EQ(3U, statuses.size());
+  ASSERT_EQ(statuses["image_id1"], status1);
+  ASSERT_EQ(statuses["image_id2"], status2);
+  ASSERT_EQ(statuses["image_id3"], status3);
+
+  states.clear();
+  ASSERT_EQ(0, mirror_image_status_get_summary(&ioctx, &states));
+  ASSERT_EQ(3U, states.size());
+  ASSERT_EQ(1, states[cls::rbd::MIRROR_IMAGE_STATUS_STATE_UNKNOWN]);
+  ASSERT_EQ(1, states[cls::rbd::MIRROR_IMAGE_STATUS_STATE_REPLAYING]);
+  ASSERT_EQ(1, states[cls::rbd::MIRROR_IMAGE_STATUS_STATE_ERROR]);
+
+  // Test update
+
+  status1.state = status3.state = cls::rbd::MIRROR_IMAGE_STATUS_STATE_REPLAYING;
+  ASSERT_EQ(0, mirror_image_status_set(&ioctx, "uuid1", status1));
+  ASSERT_EQ(0, mirror_image_status_set(&ioctx, "uuid3", status3));
+  ASSERT_EQ(0, mirror_image_status_get(&ioctx, "uuid3", &read_status));
+  ASSERT_EQ(read_status, status3);
+
+  states.clear();
+  ASSERT_EQ(0, mirror_image_status_get_summary(&ioctx, &states));
+  ASSERT_EQ(1U, states.size());
+  ASSERT_EQ(3, states[cls::rbd::MIRROR_IMAGE_STATUS_STATE_REPLAYING]);
+
+  // Test remove
+
+  ASSERT_EQ(0, mirror_image_status_remove(&ioctx, "uuid3"));
+  ASSERT_EQ(-ENOENT, mirror_image_status_get(&ioctx, "uuid3", &read_status));
+  images.clear();
+  statuses.clear();
+  ASSERT_EQ(0, mirror_image_status_list(&ioctx, "", 1024, &images, &statuses));
+  ASSERT_EQ(3U, images.size());
+  ASSERT_EQ(2U, statuses.size());
+  ASSERT_EQ(statuses["image_id1"], status1);
+  ASSERT_EQ(statuses["image_id2"], status2);
+
+  states.clear();
+  ASSERT_EQ(0, mirror_image_status_get_summary(&ioctx, &states));
+  ASSERT_EQ(2U, states.size());
+  ASSERT_EQ(1, states[cls::rbd::MIRROR_IMAGE_STATUS_STATE_UNKNOWN]);
+  ASSERT_EQ(2, states[cls::rbd::MIRROR_IMAGE_STATUS_STATE_REPLAYING]);
+
+  // Test statuses are down after removing watcher
+
+  ASSERT_EQ(0, mirror_image_status_set(&ioctx, "uuid1", status1));
+  ASSERT_EQ(0, mirror_image_status_set(&ioctx, "uuid2", status2));
+  ASSERT_EQ(0, mirror_image_status_set(&ioctx, "uuid3", status3));
+
+  images.clear();
+  statuses.clear();
+  ASSERT_EQ(0, mirror_image_status_list(&ioctx, "", 1024, &images, &statuses));
+  ASSERT_EQ(3U, images.size());
+  ASSERT_EQ(3U, statuses.size());
+  ASSERT_EQ(statuses["image_id1"], status1);
+  ASSERT_EQ(statuses["image_id2"], status2);
+  ASSERT_EQ(statuses["image_id3"], status3);
+
+  ioctx.unwatch2(watch_handle);
+
+  ASSERT_EQ(0, mirror_image_status_list(&ioctx, "", 1024, &images, &statuses));
+  ASSERT_EQ(3U, images.size());
+  ASSERT_EQ(3U, statuses.size());
+  status1.up = false;
+  ASSERT_EQ(statuses["image_id1"], status1);
+  status2.up = false;
+  ASSERT_EQ(statuses["image_id2"], status2);
+  status3.up = false;
+  ASSERT_EQ(statuses["image_id3"], status3);
+
+  ASSERT_EQ(0, mirror_image_status_get(&ioctx, "uuid1", &read_status));
+  ASSERT_EQ(read_status, status1);
+
+  states.clear();
+  ASSERT_EQ(0, mirror_image_status_get_summary(&ioctx, &states));
+  ASSERT_EQ(1U, states.size());
+  ASSERT_EQ(3, states[cls::rbd::MIRROR_IMAGE_STATUS_STATE_UNKNOWN]);
+
+  ASSERT_EQ(0, mirror_image_status_remove_down(&ioctx));
+  ASSERT_EQ(-ENOENT, mirror_image_status_get(&ioctx, "uuid1", &read_status));
+
+  images.clear();
+  statuses.clear();
+  ASSERT_EQ(0, mirror_image_status_list(&ioctx, "", 1024, &images, &statuses));
+  ASSERT_EQ(3U, images.size());
+  ASSERT_TRUE(statuses.empty());
+
+  states.clear();
+  ASSERT_EQ(0, mirror_image_status_get_summary(&ioctx, &states));
+  ASSERT_EQ(1U, states.size());
+  ASSERT_EQ(3, states[cls::rbd::MIRROR_IMAGE_STATUS_STATE_UNKNOWN]);
+}
diff --git a/src/test/debian-jessie/install-deps.sh b/src/test/debian-jessie/install-deps.sh
index 8249ea3..21e71ee 100755
--- a/src/test/debian-jessie/install-deps.sh
+++ b/src/test/debian-jessie/install-deps.sh
@@ -66,7 +66,7 @@ CentOS|Fedora|RedHatEnterpriseServer)
                     $SUDO yum install subscription-manager
                     $SUDO subscription-manager repos --enable=rhel-$MAJOR_VERSION-server-optional-rpms
                 fi
-                $SUDO yum-config-manager --add-repo https://dl.fedoraproject.org/pub/epel/$MAJOR_VERSION/x86_64/ 
+                $SUDO yum-config-manager --add-repo https://dl.fedoraproject.org/pub/epel/$MAJOR_VERSION/x86_64/
                 $SUDO yum install --nogpgcheck -y epel-release
                 $SUDO rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-$MAJOR_VERSION
                 $SUDO rm -f /etc/yum.repos.d/dl.fedoraproject.org*
@@ -92,8 +92,6 @@ function populate_wheelhouse() {
     local install=$1
     shift
 
-    # Ubuntu-12.04 and Python 2.7.3 require this line
-    pip --timeout 300 $install 'distribute >= 0.7.3' || return 1
     # although pip comes with virtualenv, having a recent version
     # of pip matters when it comes to using wheel packages
     pip --timeout 300 $install 'setuptools >= 0.8' 'pip >= 7.0' 'wheel >= 0.24' || return 1
diff --git a/src/test/fedora-21/ceph.spec.in b/src/test/fedora-21/ceph.spec.in
index 26928f7..34e4caa 100644
--- a/src/test/fedora-21/ceph.spec.in
+++ b/src/test/fedora-21/ceph.spec.in
@@ -773,6 +773,7 @@ install -m 0644 -D etc/sysconfig/ceph $RPM_BUILD_ROOT%{_localstatedir}/adm/fillu
   install -m 0644 -D systemd/ceph-rbd-mirror.target $RPM_BUILD_ROOT%{_unitdir}/ceph-rbd-mirror.target
   install -m 0644 -D systemd/ceph-disk at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-disk at .service
   install -m 0755 -D systemd/ceph $RPM_BUILD_ROOT%{_sbindir}/rcceph
+  install -m 0644 -D systemd/50-ceph.preset $RPM_BUILD_ROOT%{_libexecdir}/systemd/system-preset/50-ceph.preset
 %else
   install -D src/init-rbdmap $RPM_BUILD_ROOT%{_initrddir}/rbdmap
   install -D src/init-ceph $RPM_BUILD_ROOT%{_initrddir}/ceph
@@ -845,6 +846,7 @@ rm -rf $RPM_BUILD_ROOT
 %{_bindir}/cephfs
 %if 0%{?_with_systemd}
 %{_unitdir}/ceph-create-keys at .service
+%{_libexecdir}/systemd/system-preset/50-ceph.preset
 %else
 %{_initrddir}/ceph
 %endif
@@ -899,70 +901,35 @@ rm -rf $RPM_BUILD_ROOT
 %attr(770,ceph,ceph) %dir %{_localstatedir}/run/ceph
 %endif
 
-%pre base
-%if 0%{?_with_systemd}
-  %if 0%{?suse_version}
-    # service_add_pre and friends don't work with parameterized systemd service
-    # instances, only with single services or targets, so we always pass
-    # ceph.target to these macros
-    %service_add_pre ceph.target
-  %endif
-%endif
-
 %post base
 /sbin/ldconfig
-%if 0%{?_with_systemd}
-  %if 0%{?suse_version}
-    %fillup_only
-    %service_add_post ceph.target
-  %endif
-%else
-  /sbin/chkconfig --add ceph
+%if 0%{?suse_version}
+%fillup_only
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph.target >/dev/null 2>&1 || :
+fi
 %endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph.target
+%endif
+/usr/bin/systemctl start ceph.target >/dev/null 2>&1 || :
 
 %preun base
-%if 0%{?_with_systemd}
-  %if 0%{?suse_version}
-    %service_del_preun ceph.target
-  %endif
-  # Disable and stop on removal.
-  if [ $1 = 0 ] ; then
-    SERVICE_LIST=$(systemctl | grep -E '^ceph-mon@|^ceph-create-keys@|^ceph-osd@|^ceph-mds@|^ceph-disk-'  | cut -d' ' -f1)
-    if [ -n "$SERVICE_LIST" ]; then
-      for SERVICE in $SERVICE_LIST; do
-        /usr/bin/systemctl --no-reload disable $SERVICE > /dev/null 2>&1 || :
-        /usr/bin/systemctl stop $SERVICE > /dev/null 2>&1 || :
-      done
-    fi
-  fi
-%else
-  %if 0%{?rhel} || 0%{?fedora}
-    if [ $1 = 0 ] ; then
-      /sbin/service ceph stop >/dev/null 2>&1
-      /sbin/chkconfig --del ceph
-    fi
-  %endif
+%if 0%{?suse_version}
+%service_del_preun ceph.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph.target
 %endif
 
 %postun base
 /sbin/ldconfig
-%if 0%{?_with_systemd}
-  if [ $1 = 1 ] ; then
-    # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
-    # "yes". In any case: if units are not running, do not touch them.
-    SYSCONF_CEPH=/etc/sysconfig/ceph
-    if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
-      source $SYSCONF_CEPH
-    fi
-    if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
-      SERVICE_LIST=$(systemctl | grep -E '^ceph-mon@|^ceph-create-keys@|^ceph-osd@|^ceph-mds@|^ceph-disk-'  | cut -d' ' -f1)
-      if [ -n "$SERVICE_LIST" ]; then
-        for SERVICE in $SERVICE_LIST; do
-          /usr/bin/systemctl try-restart $SERVICE > /dev/null 2>&1 || :
-        done
-      fi
-    fi
-  fi
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph.target
 %endif
 
 #################################################################################
@@ -1068,6 +1035,46 @@ fi
 %endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/mds
 
+%post mds
+%if 0%{?suse_version}
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-mds@\*.service ceph-mds.target >/dev/null 2>&1 || :
+fi
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-mds@\*.service ceph-mds.target
+%endif
+/usr/bin/systemctl start ceph-mds.target >/dev/null 2>&1 || :
+
+%preun mds
+%if 0%{?suse_version}
+%service_del_preun ceph-mds@\*.service ceph-mds.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-mds@\*.service ceph-mds.target
+%endif
+
+%postun mds
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-mds@\*.service ceph-mds.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-mds@\*.service ceph-mds.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-mds@\*.service > /dev/null 2>&1 || :
+  fi
+fi
+
 #################################################################################
 %files mon
 %{_bindir}/ceph-mon
@@ -1083,6 +1090,46 @@ fi
 %endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/mon
 
+%post mon
+%if 0%{?suse_version}
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target >/dev/null 2>&1 || :
+fi
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+/usr/bin/systemctl start ceph-mon.target >/dev/null 2>&1 || :
+
+%preun mon
+%if 0%{?suse_version}
+%service_del_preun ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+
+%postun mon
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-create-keys@\*.service ceph-mon@\*.service > /dev/null 2>&1 || :
+  fi
+fi
+
 #################################################################################
 %files fuse
 %defattr(-,root,root,-)
@@ -1110,6 +1157,46 @@ fi
 %{_unitdir}/ceph-rbd-mirror.target
 %endif
 
+%post -n rbd-mirror
+%if 0%{?suse_version}
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-rbd-mirror@\*.service ceph-rbd-mirror.target >/dev/null 2>&1 || :
+fi
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+/usr/bin/systemctl start ceph-rbd-mirror.target >/dev/null 2>&1 || :
+
+%preun -n rbd-mirror
+%if 0%{?suse_version}
+%service_del_preun ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+
+%postun -n rbd-mirror
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-rbd-mirror@\*.service > /dev/null 2>&1 || :
+  fi
+fi
+
 #################################################################################
 %files -n rbd-nbd
 %defattr(-,root,root,-)
@@ -1136,48 +1223,44 @@ fi
 %endif
 
 %post radosgw
-/sbin/ldconfig
 %if 0%{?suse_version}
-  # explicit systemctl daemon-reload (that's the only relevant bit of
-  # service_add_post; the rest is all sysvinit --> systemd migration which
-  # isn't applicable in this context (see above comment).
-  /usr/bin/systemctl daemon-reload >/dev/null 2>&1 || :
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-radosgw@\*.service ceph-radosgw.target >/dev/null 2>&1 || :
+fi
 %endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-radosgw@\*.service ceph-radosgw.target
+%endif
+/usr/bin/systemctl start ceph-radosgw.target >/dev/null 2>&1 || :
 
 %preun radosgw
-%if 0%{?_with_systemd}
-  # Disable and stop on removal.
-  if [ $1 = 0 ] ; then
-    SERVICE_LIST=$(systemctl | grep -E '^ceph-radosgw@'  | cut -d' ' -f1)
-    if [ -n "$SERVICE_LIST" ]; then
-      for SERVICE in $SERVICE_LIST; do
-        /usr/bin/systemctl --no-reload disable $SERVICE > /dev/null 2>&1 || :
-        /usr/bin/systemctl stop $SERVICE > /dev/null 2>&1 || :
-      done
-    fi
-  fi
+%if 0%{?suse_version}
+%service_del_preun ceph-radosgw@\*.service ceph-radosgw.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-radosgw@\*.service ceph-radosgw.target
 %endif
 
 %postun radosgw
-/sbin/ldconfig
-%if 0%{?_with_systemd}
-  if [ $1 = 1 ] ; then
-    # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
-    # "yes". In any case: if units are not running, do not touch them.
-    SYSCONF_CEPH=/etc/sysconfig/ceph
-    if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
-      source $SYSCONF_CEPH
-    fi
-    if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
-      SERVICE_LIST=$(systemctl | grep -E '^ceph-radosgw@'  | cut -d' ' -f1)
-      if [ -n "$SERVICE_LIST" ]; then
-        for SERVICE in $SERVICE_LIST; do
-          /usr/bin/systemctl try-restart $SERVICE > /dev/null 2>&1 || :
-        done
-      fi
-    fi
-  fi
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-radosgw@\*.service ceph-radosgw.target
 %endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-radosgw@\*.service ceph-radosgw.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-radosgw@\*.service > /dev/null 2>&1 || :
+  fi
+fi
 
 #################################################################################
 %files osd
@@ -1205,6 +1288,46 @@ fi
 %endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/osd
 
+%post osd
+%if 0%{?suse_version}
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target >/dev/null 2>&1 || :
+fi
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+/usr/bin/systemctl start ceph-osd.target >/dev/null 2>&1 || :
+
+%preun osd
+%if 0%{?suse_version}
+%service_del_preun ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+
+%postun osd
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-disk@\*.service ceph-osd@\*.service > /dev/null 2>&1 || :
+  fi
+fi
+
 #################################################################################
 %if %{with ocf}
 
@@ -1450,7 +1573,7 @@ else
     exit 0
 fi
 
-if test "$OLD_POLVER" == "$NEW_POLVER"; then
+if test "$OLD_POLVER" = "$NEW_POLVER"; then
    # Do not relabel if policy version did not change
    exit 0
 fi
diff --git a/src/test/fedora-21/install-deps.sh b/src/test/fedora-21/install-deps.sh
index 8249ea3..21e71ee 100755
--- a/src/test/fedora-21/install-deps.sh
+++ b/src/test/fedora-21/install-deps.sh
@@ -66,7 +66,7 @@ CentOS|Fedora|RedHatEnterpriseServer)
                     $SUDO yum install subscription-manager
                     $SUDO subscription-manager repos --enable=rhel-$MAJOR_VERSION-server-optional-rpms
                 fi
-                $SUDO yum-config-manager --add-repo https://dl.fedoraproject.org/pub/epel/$MAJOR_VERSION/x86_64/ 
+                $SUDO yum-config-manager --add-repo https://dl.fedoraproject.org/pub/epel/$MAJOR_VERSION/x86_64/
                 $SUDO yum install --nogpgcheck -y epel-release
                 $SUDO rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-$MAJOR_VERSION
                 $SUDO rm -f /etc/yum.repos.d/dl.fedoraproject.org*
@@ -92,8 +92,6 @@ function populate_wheelhouse() {
     local install=$1
     shift
 
-    # Ubuntu-12.04 and Python 2.7.3 require this line
-    pip --timeout 300 $install 'distribute >= 0.7.3' || return 1
     # although pip comes with virtualenv, having a recent version
     # of pip matters when it comes to using wheel packages
     pip --timeout 300 $install 'setuptools >= 0.8' 'pip >= 7.0' 'wheel >= 0.24' || return 1
diff --git a/src/test/rbd_mirror/mock/MockJournaler.cc b/src/test/journal/mock/MockJournaler.cc
similarity index 88%
rename from src/test/rbd_mirror/mock/MockJournaler.cc
rename to src/test/journal/mock/MockJournaler.cc
index 047dd2f..9064944 100644
--- a/src/test/rbd_mirror/mock/MockJournaler.cc
+++ b/src/test/journal/mock/MockJournaler.cc
@@ -5,6 +5,7 @@
 
 namespace journal {
 
+MockFuture *MockFuture::s_instance = nullptr;
 MockReplayEntry *MockReplayEntry::s_instance = nullptr;
 MockJournaler *MockJournaler::s_instance = nullptr;
 
diff --git a/src/test/journal/mock/MockJournaler.h b/src/test/journal/mock/MockJournaler.h
new file mode 100644
index 0000000..4d9053f
--- /dev/null
+++ b/src/test/journal/mock/MockJournaler.h
@@ -0,0 +1,267 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef TEST_RBD_MIRROR_MOCK_JOURNALER_H
+#define TEST_RBD_MIRROR_MOCK_JOURNALER_H
+
+#include <gmock/gmock.h>
+#include "include/int_types.h"
+#include "include/rados/librados.hpp"
+#include "cls/journal/cls_journal_types.h"
+#include "journal/Journaler.h"
+#include <iosfwd>
+#include <string>
+
+class Context;
+class ContextWQ;
+class Mutex;
+class SafeTimer;
+
+namespace journal {
+
+struct ReplayHandler;
+
+struct MockFuture {
+  static MockFuture *s_instance;
+  static MockFuture &get_instance() {
+    assert(s_instance != nullptr);
+    return *s_instance;
+  }
+
+  MockFuture() {
+    s_instance = this;
+  }
+
+  MOCK_CONST_METHOD0(is_valid, bool());
+  MOCK_METHOD1(flush, void(Context *));
+  MOCK_METHOD1(wait, void(Context *));
+};
+
+struct MockFutureProxy {
+  bool is_valid() const {
+    return MockFuture::get_instance().is_valid();
+  }
+
+  void flush(Context *on_safe) {
+    MockFuture::get_instance().flush(on_safe);
+  }
+
+  void wait(Context *on_safe) {
+    MockFuture::get_instance().wait(on_safe);
+  }
+};
+
+struct MockReplayEntry {
+  static MockReplayEntry *s_instance;
+  static MockReplayEntry &get_instance() {
+    assert(s_instance != nullptr);
+    return *s_instance;
+  }
+
+  MockReplayEntry() {
+    s_instance = this;
+  }
+
+  MOCK_CONST_METHOD0(get_commit_tid, uint64_t());
+  MOCK_METHOD0(get_data, bufferlist());
+};
+
+struct MockReplayEntryProxy {
+  uint64_t get_commit_tid() const {
+    return MockReplayEntry::get_instance().get_commit_tid();
+  }
+
+  bufferlist get_data() {
+    return MockReplayEntry::get_instance().get_data();
+  }
+};
+
+struct MockJournaler {
+  static MockJournaler *s_instance;
+  static MockJournaler &get_instance() {
+    assert(s_instance != nullptr);
+    return *s_instance;
+  }
+
+  MockJournaler() {
+    s_instance = this;
+  }
+
+  MOCK_METHOD0(construct, void());
+
+  MOCK_METHOD1(init, void(Context *));
+  MOCK_METHOD0(shut_down, void());
+  MOCK_CONST_METHOD0(is_initialized, bool());
+
+  MOCK_METHOD3(get_metadata, void(uint8_t *order, uint8_t *splay_width,
+                                  int64_t *pool_id));
+  MOCK_METHOD4(get_mutable_metadata, void(uint64_t*, uint64_t*,
+                                          std::set<cls::journal::Client> *,
+                                          Context*));
+
+  MOCK_METHOD2(register_client, void(const bufferlist &, Context *));
+  MOCK_METHOD3(get_client, void(const std::string &, cls::journal::Client *,
+                                Context *));
+  MOCK_METHOD2(get_cached_client, int(const std::string&, cls::journal::Client*));
+  MOCK_METHOD2(update_client, void(const bufferlist &, Context *));
+
+  MOCK_METHOD3(get_tag, void(uint64_t, cls::journal::Tag *, Context *));
+  MOCK_METHOD3(get_tags, void(uint64_t, journal::Journaler::Tags*, Context*));
+
+  MOCK_METHOD1(start_replay, void(::journal::ReplayHandler *replay_handler));
+  MOCK_METHOD2(start_live_replay, void(ReplayHandler *, double));
+  MOCK_METHOD1(try_pop_front, bool(MockReplayEntryProxy *));
+  MOCK_METHOD2(try_pop_front, bool(MockReplayEntryProxy *, uint64_t *));
+  MOCK_METHOD0(stop_replay, void());
+
+  MOCK_METHOD3(start_append, void(int flush_interval, uint64_t flush_bytes,
+                                  double flush_age));
+  MOCK_CONST_METHOD0(get_max_append_size, uint64_t());
+  MOCK_METHOD2(append, MockFutureProxy(uint64_t tag_id,
+                                       const bufferlist &bl));
+  MOCK_METHOD1(flush, void(Context *on_safe));
+  MOCK_METHOD1(stop_append, void(Context *on_safe));
+
+  MOCK_METHOD1(committed, void(const MockReplayEntryProxy &));
+  MOCK_METHOD1(committed, void(const MockFutureProxy &future));
+  MOCK_METHOD1(flush_commit_position, void(Context*));
+
+};
+
+struct MockJournalerProxy {
+  template <typename IoCtxT>
+  MockJournalerProxy(IoCtxT &header_ioctx, const std::string &,
+                     const std::string &, double) {
+    MockJournaler::get_instance().construct();
+  }
+
+  MockJournalerProxy(ContextWQ *work_queue, SafeTimer *timer, Mutex *timer_lock,
+                     librados::IoCtx &header_ioctx, const std::string &journal_id,
+                     const std::string &client_id, double commit_interval) {
+    MockJournaler::get_instance().construct();
+  }
+
+  int exists(bool *header_exists) const {
+    return -EINVAL;
+  }
+  int create(uint8_t order, uint8_t splay_width, int64_t pool_id) {
+    return -EINVAL;
+  }
+  int remove(bool force) {
+    return -EINVAL;
+  }
+  int register_client(const bufferlist &data) {
+    return -EINVAL;
+  }
+  void allocate_tag(uint64_t, const bufferlist &,
+                    cls::journal::Tag*, Context *on_finish) {
+    on_finish->complete(-EINVAL);
+  }
+
+  void init(Context *on_finish) {
+    MockJournaler::get_instance().init(on_finish);
+  }
+  void shut_down() {
+    MockJournaler::get_instance().shut_down();
+  }
+  bool is_initialized() const {
+    return MockJournaler::get_instance().is_initialized();
+  }
+
+  void get_metadata(uint8_t *order, uint8_t *splay_width, int64_t *pool_id) {
+    MockJournaler::get_instance().get_metadata(order, splay_width, pool_id);
+  }
+
+  void get_mutable_metadata(uint64_t *min, uint64_t *active,
+                            std::set<cls::journal::Client> *clients,
+                            Context *on_finish) {
+    MockJournaler::get_instance().get_mutable_metadata(min, active, clients,
+                                                       on_finish);
+  }
+
+  void register_client(const bufferlist &data, Context *on_finish) {
+    MockJournaler::get_instance().register_client(data, on_finish);
+  }
+
+  void get_client(const std::string &client_id, cls::journal::Client *client,
+                  Context *on_finish) {
+    MockJournaler::get_instance().get_client(client_id, client, on_finish);
+  }
+
+  int get_cached_client(const std::string& client_id,
+                        cls::journal::Client* client) {
+    return MockJournaler::get_instance().get_cached_client(client_id, client);
+  }
+
+  void update_client(const bufferlist &client_data, Context *on_finish) {
+    MockJournaler::get_instance().update_client(client_data, on_finish);
+  }
+
+  void get_tag(uint64_t tag_tid, cls::journal::Tag *tag, Context *on_finish) {
+    MockJournaler::get_instance().get_tag(tag_tid, tag, on_finish);
+  }
+
+  void get_tags(uint64_t tag_class, journal::Journaler::Tags *tags,
+                Context *on_finish) {
+    MockJournaler::get_instance().get_tags(tag_class, tags, on_finish);
+  }
+
+  void start_replay(::journal::ReplayHandler *replay_handler) {
+    MockJournaler::get_instance().start_replay(replay_handler);
+  }
+
+  void start_live_replay(ReplayHandler *handler, double interval) {
+    MockJournaler::get_instance().start_live_replay(handler, interval);
+  }
+
+  bool try_pop_front(MockReplayEntryProxy *replay_entry) {
+    return MockJournaler::get_instance().try_pop_front(replay_entry);
+  }
+
+  bool try_pop_front(MockReplayEntryProxy *entry, uint64_t *tag_tid) {
+    return MockJournaler::get_instance().try_pop_front(entry, tag_tid);
+  }
+
+  void stop_replay() {
+    MockJournaler::get_instance().stop_replay();
+  }
+
+  void start_append(int flush_interval, uint64_t flush_bytes, double flush_age) { 
+    MockJournaler::get_instance().start_append(flush_interval, flush_bytes,
+                                               flush_age);
+  }
+
+  uint64_t get_max_append_size() const {
+    return MockJournaler::get_instance().get_max_append_size();
+  }
+
+  MockFutureProxy append(uint64_t tag_id, const bufferlist &bl) {
+    return MockJournaler::get_instance().append(tag_id, bl);
+  }
+
+  void flush(Context *on_safe) {
+    MockJournaler::get_instance().flush(on_safe);
+  }
+
+  void stop_append(Context *on_safe) {
+    MockJournaler::get_instance().stop_append(on_safe);
+  }
+
+  void committed(const MockReplayEntryProxy &entry) {
+    MockJournaler::get_instance().committed(entry);
+  }
+
+  void committed(const MockFutureProxy &future) {
+    MockJournaler::get_instance().committed(future);
+  }
+
+  void flush_commit_position(Context *on_finish) {
+    MockJournaler::get_instance().flush_commit_position(on_finish);
+  }
+};
+
+std::ostream &operator<<(std::ostream &os, const MockJournalerProxy &);
+
+} // namespace journal
+
+#endif // TEST_RBD_MIRROR_MOCK_JOURNALER_H
diff --git a/src/test/journal/test_JournalPlayer.cc b/src/test/journal/test_JournalPlayer.cc
index 3f992cb..f989dd5 100644
--- a/src/test/journal/test_JournalPlayer.cc
+++ b/src/test/journal/test_JournalPlayer.cc
@@ -403,3 +403,52 @@ TEST_F(TestJournalPlayer, PrefetchSkippedObject) {
   ASSERT_TRUE(metadata->get_last_allocated_entry_tid(234, &last_tid));
   ASSERT_EQ(126U, last_tid);
 }
+
+TEST_F(TestJournalPlayer, ImbalancedJournal) {
+  std::string oid = get_temp_oid();
+
+  journal::JournalPlayer::ObjectPositions positions = {
+    cls::journal::ObjectPosition(9, 300, 1),
+    cls::journal::ObjectPosition(8, 300, 0),
+    cls::journal::ObjectPosition(10, 200, 4334),
+    cls::journal::ObjectPosition(11, 200, 4331) };
+  cls::journal::ObjectSetPosition commit_position(positions);
+
+  ASSERT_EQ(0, create(oid, 14, 4));
+  ASSERT_EQ(0, client_register(oid));
+  ASSERT_EQ(0, client_commit(oid, commit_position));
+
+  journal::JournalMetadataPtr metadata = create_metadata(oid);
+  ASSERT_EQ(0, init_metadata(metadata));
+  metadata->set_active_set(2);
+  metadata->set_minimum_set(2);
+
+  journal::JournalPlayer *player = create_player(oid, metadata);
+
+  ASSERT_EQ(0, write_entry(oid, 8, 300, 0));
+  ASSERT_EQ(0, write_entry(oid, 8, 301, 0));
+  ASSERT_EQ(0, write_entry(oid, 9, 300, 1));
+  ASSERT_EQ(0, write_entry(oid, 9, 301, 1));
+  ASSERT_EQ(0, write_entry(oid, 10, 200, 4334));
+  ASSERT_EQ(0, write_entry(oid, 10, 301, 2));
+  ASSERT_EQ(0, write_entry(oid, 11, 200, 4331));
+  ASSERT_EQ(0, write_entry(oid, 11, 301, 3));
+
+  player->prefetch();
+
+  Entries entries;
+  ASSERT_TRUE(wait_for_entries(player, 4, &entries));
+  ASSERT_TRUE(wait_for_complete(player));
+
+  Entries expected_entries;
+  expected_entries = {
+    create_entry(301, 0),
+    create_entry(301, 1),
+    create_entry(301, 2),
+    create_entry(301, 3)};
+  ASSERT_EQ(expected_entries, entries);
+
+  uint64_t last_tid;
+  ASSERT_TRUE(metadata->get_last_allocated_entry_tid(301, &last_tid));
+  ASSERT_EQ(3U, last_tid);
+}
diff --git a/src/test/journal/test_JournalRecorder.cc b/src/test/journal/test_JournalRecorder.cc
index d384e7f..149e63b 100644
--- a/src/test/journal/test_JournalRecorder.cc
+++ b/src/test/journal/test_JournalRecorder.cc
@@ -2,6 +2,7 @@
 // vim: ts=8 sw=2 smarttab
 
 #include "journal/JournalRecorder.h"
+#include "journal/Entry.h"
 #include "journal/JournalMetadata.h"
 #include "test/journal/RadosTestFixture.h"
 #include <limits>
@@ -58,7 +59,8 @@ TEST_F(TestJournalRecorder, AppendKnownOverflow) {
 
   journal::JournalRecorder *recorder = create_recorder(oid, metadata);
 
-  recorder->append(123, create_payload(std::string(1 << 12, '1')));
+  recorder->append(123, create_payload(std::string(metadata->get_object_size() -
+                                                   journal::Entry::get_fixed_size(), '1')));
   journal::Future future2 = recorder->append(123, create_payload(std::string(1, '2')));
 
   C_SaferCond cond;
@@ -80,8 +82,9 @@ TEST_F(TestJournalRecorder, AppendDelayedOverflow) {
   journal::JournalRecorder *recorder1 = create_recorder(oid, metadata);
   journal::JournalRecorder *recorder2 = create_recorder(oid, metadata);
 
-  recorder1->append(123, create_payload(std::string(1, '1')));
-  recorder2->append(234, create_payload(std::string(1 << 12, '2')));
+  recorder1->append(234, create_payload(std::string(1, '1')));
+  recorder2->append(123, create_payload(std::string(metadata->get_object_size() -
+                                                    journal::Entry::get_fixed_size(), '2')));
 
   journal::Future future = recorder2->append(123, create_payload(std::string(1, '3')));
 
diff --git a/src/test/librados_test_stub/LibradosTestStub.cc b/src/test/librados_test_stub/LibradosTestStub.cc
index 7176ba4..dac71e9 100644
--- a/src/test/librados_test_stub/LibradosTestStub.cc
+++ b/src/test/librados_test_stub/LibradosTestStub.cc
@@ -15,6 +15,7 @@
 #include "test/librados_test_stub/TestRadosClient.h"
 #include "test/librados_test_stub/TestMemRadosClient.h"
 #include "objclass/objclass.h"
+#include "osd/osd_types.h"
 #include <boost/bind.hpp>
 #include <boost/shared_ptr.hpp>
 #include <deque>
@@ -1159,6 +1160,29 @@ int cls_cxx_write_full(cls_method_context_t hctx, bufferlist *inbl) {
   return ctx->io_ctx_impl->write_full(ctx->oid, *inbl, ctx->snapc);
 }
 
+int cls_cxx_list_watchers(cls_method_context_t hctx,
+			  obj_list_watch_response_t *watchers) {
+  librados::TestClassHandler::MethodContext *ctx =
+    reinterpret_cast<librados::TestClassHandler::MethodContext*>(hctx);
+
+  std::list<obj_watch_t> obj_watchers;
+  int r = ctx->io_ctx_impl->list_watchers(ctx->oid, &obj_watchers);
+  if (r < 0) {
+    return r;
+  }
+
+  for (auto &w : obj_watchers) {
+    watch_item_t watcher;
+    watcher.name = entity_name_t::CLIENT(w.watcher_id);
+    watcher.cookie = w.cookie;
+    watcher.timeout_seconds = w.timeout_seconds;
+    watcher.addr.parse(w.addr, 0);
+    watchers->entries.push_back(watcher);
+  }
+
+  return 0;
+}
+
 int cls_log(int level, const char *format, ...) {
   int size = 256;
   va_list ap;
diff --git a/src/test/librbd/exclusive_lock/test_mock_AcquireRequest.cc b/src/test/librbd/exclusive_lock/test_mock_AcquireRequest.cc
index c7c9428..e4ba0ec 100644
--- a/src/test/librbd/exclusive_lock/test_mock_AcquireRequest.cc
+++ b/src/test/librbd/exclusive_lock/test_mock_AcquireRequest.cc
@@ -52,6 +52,12 @@ public:
                   .WillOnce(Return(r));
   }
 
+  void expect_unlock(MockImageCtx &mock_image_ctx, int r) {
+    EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+                exec(mock_image_ctx.header_oid, _, StrEq("lock"), StrEq("unlock"), _, _, _))
+                  .WillOnce(Return(r));
+  }
+
   void expect_create_object_map(MockImageCtx &mock_image_ctx,
                                 MockObjectMap *mock_object_map) {
     EXPECT_CALL(mock_image_ctx, create_object_map(_))
@@ -59,9 +65,9 @@ public:
   }
 
   void expect_open_object_map(MockImageCtx &mock_image_ctx,
-                              MockObjectMap &mock_object_map) {
+                              MockObjectMap &mock_object_map, int r) {
     EXPECT_CALL(mock_object_map, open(_))
-                  .WillOnce(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue));
+                  .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue));
   }
 
   void expect_close_object_map(MockImageCtx &mock_image_ctx,
@@ -186,7 +192,7 @@ TEST_F(TestMockExclusiveLockAcquireRequest, Success) {
   MockObjectMap mock_object_map;
   expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, true);
   expect_create_object_map(mock_image_ctx, &mock_object_map);
-  expect_open_object_map(mock_image_ctx, mock_object_map);
+  expect_open_object_map(mock_image_ctx, mock_object_map, 0);
 
   MockJournal mock_journal;
   MockJournalPolicy mock_journal_policy;
@@ -222,7 +228,7 @@ TEST_F(TestMockExclusiveLockAcquireRequest, SuccessJournalDisabled) {
   MockObjectMap mock_object_map;
   expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, true);
   expect_create_object_map(mock_image_ctx, &mock_object_map);
-  expect_open_object_map(mock_image_ctx, mock_object_map);
+  expect_open_object_map(mock_image_ctx, mock_object_map, 0);
 
   expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, false);
 
@@ -285,7 +291,7 @@ TEST_F(TestMockExclusiveLockAcquireRequest, JournalError) {
   MockObjectMap *mock_object_map = new MockObjectMap();
   expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, true);
   expect_create_object_map(mock_image_ctx, mock_object_map);
-  expect_open_object_map(mock_image_ctx, *mock_object_map);
+  expect_open_object_map(mock_image_ctx, *mock_object_map, 0);
 
   MockJournal *mock_journal = new MockJournal();
   expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, true);
@@ -293,6 +299,7 @@ TEST_F(TestMockExclusiveLockAcquireRequest, JournalError) {
   expect_open_journal(mock_image_ctx, *mock_journal, -EINVAL);
   expect_close_journal(mock_image_ctx, *mock_journal);
   expect_close_object_map(mock_image_ctx, *mock_object_map);
+  expect_unlock(mock_image_ctx, 0);
 
   C_SaferCond acquire_ctx;
   C_SaferCond ctx;
@@ -319,7 +326,7 @@ TEST_F(TestMockExclusiveLockAcquireRequest, AllocateJournalTagError) {
   MockObjectMap *mock_object_map = new MockObjectMap();
   expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, true);
   expect_create_object_map(mock_image_ctx, mock_object_map);
-  expect_open_object_map(mock_image_ctx, *mock_object_map);
+  expect_open_object_map(mock_image_ctx, *mock_object_map, 0);
 
   MockJournal *mock_journal = new MockJournal();
   MockJournalPolicy mock_journal_policy;
@@ -330,6 +337,7 @@ TEST_F(TestMockExclusiveLockAcquireRequest, AllocateJournalTagError) {
   expect_allocate_journal_tag(mock_image_ctx, mock_journal_policy, -EPERM);
   expect_close_journal(mock_image_ctx, *mock_journal);
   expect_close_object_map(mock_image_ctx, *mock_object_map);
+  expect_unlock(mock_image_ctx, 0);
 
   C_SaferCond acquire_ctx;
   C_SaferCond ctx;
@@ -645,5 +653,42 @@ TEST_F(TestMockExclusiveLockAcquireRequest, BreakLockError) {
   ASSERT_EQ(-EINVAL, ctx.wait());
 }
 
+TEST_F(TestMockExclusiveLockAcquireRequest, OpenObjectMapError) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockImageCtx mock_image_ctx(*ictx);
+  expect_op_work_queue(mock_image_ctx);
+
+  InSequence seq;
+  expect_flush_notifies(mock_image_ctx);
+  expect_lock(mock_image_ctx, 0);
+
+  MockObjectMap *mock_object_map = new MockObjectMap();
+  expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, true);
+  expect_create_object_map(mock_image_ctx, mock_object_map);
+  expect_open_object_map(mock_image_ctx, *mock_object_map, -EFBIG);
+
+  MockJournal mock_journal;
+  MockJournalPolicy mock_journal_policy;
+  expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, true);
+  expect_create_journal(mock_image_ctx, &mock_journal);
+  expect_open_journal(mock_image_ctx, mock_journal, 0);
+  expect_get_journal_policy(mock_image_ctx, mock_journal_policy);
+  expect_allocate_journal_tag(mock_image_ctx, mock_journal_policy, 0);
+
+  C_SaferCond acquire_ctx;
+  C_SaferCond ctx;
+  MockAcquireRequest *req = MockAcquireRequest::create(mock_image_ctx,
+                                                       TEST_COOKIE,
+                                                       &acquire_ctx, &ctx);
+  req->send();
+  ASSERT_EQ(0, acquire_ctx.wait());
+  ASSERT_EQ(0, ctx.wait());
+  ASSERT_EQ(nullptr, mock_image_ctx.object_map);
+}
+
 } // namespace exclusive_lock
 } // namespace librbd
diff --git a/src/test/librbd/image/test_mock_RefreshRequest.cc b/src/test/librbd/image/test_mock_RefreshRequest.cc
index 6af5f46..01a7381 100644
--- a/src/test/librbd/image/test_mock_RefreshRequest.cc
+++ b/src/test/librbd/image/test_mock_RefreshRequest.cc
@@ -261,10 +261,10 @@ public:
   }
 
   void expect_open_object_map(MockRefreshImageCtx &mock_image_ctx,
-                              MockObjectMap &mock_object_map, int r) {
+                              MockObjectMap *mock_object_map, int r) {
     EXPECT_CALL(mock_image_ctx, create_object_map(_))
-                  .WillOnce(Return(&mock_object_map));
-    EXPECT_CALL(mock_object_map, open(_))
+                  .WillOnce(Return(mock_object_map));
+    EXPECT_CALL(*mock_object_map, open(_))
                   .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue));
   }
 
@@ -419,7 +419,7 @@ TEST_F(TestMockImageRefreshRequest, SuccessSetSnapshotV2) {
   expect_get_snapshots(mock_image_ctx, 0);
   expect_refresh_parent_is_required(mock_refresh_parent_request, false);
   if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) {
-    expect_open_object_map(mock_image_ctx, mock_object_map, 0);
+    expect_open_object_map(mock_image_ctx, &mock_object_map, 0);
   }
   expect_add_snap(mock_image_ctx, "snap", ictx->snap_ids.begin()->second);
   expect_get_snap_id(mock_image_ctx, "snap", ictx->snap_ids.begin()->second);
@@ -664,7 +664,7 @@ TEST_F(TestMockImageRefreshRequest, EnableObjectMapWithExclusiveLock) {
   expect_get_mutable_metadata(mock_image_ctx, 0);
   expect_get_flags(mock_image_ctx, 0);
   expect_refresh_parent_is_required(mock_refresh_parent_request, false);
-  expect_open_object_map(mock_image_ctx, mock_object_map, 0);
+  expect_open_object_map(mock_image_ctx, &mock_object_map, 0);
 
   C_SaferCond ctx;
   MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &ctx);
@@ -745,5 +745,40 @@ TEST_F(TestMockImageRefreshRequest, DisableObjectMap) {
   ASSERT_EQ(0, ctx.wait());
 }
 
+TEST_F(TestMockImageRefreshRequest, OpenObjectMapError) {
+  REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  ASSERT_EQ(0, update_features(ictx, RBD_FEATURE_JOURNALING, false));
+
+  MockRefreshImageCtx mock_image_ctx(*ictx);
+  MockRefreshParentRequest mock_refresh_parent_request;
+
+  MockExclusiveLock mock_exclusive_lock;
+  mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+
+  MockObjectMap *mock_object_map = new MockObjectMap();
+
+  expect_op_work_queue(mock_image_ctx);
+  expect_test_features(mock_image_ctx);
+  expect_is_exclusive_lock_owner(mock_exclusive_lock, true);
+
+  // object map should be immediately opened if exclusive lock owned
+  InSequence seq;
+  expect_get_mutable_metadata(mock_image_ctx, 0);
+  expect_get_flags(mock_image_ctx, 0);
+  expect_refresh_parent_is_required(mock_refresh_parent_request, false);
+  expect_open_object_map(mock_image_ctx, mock_object_map, -EFBIG);
+
+  C_SaferCond ctx;
+  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &ctx);
+  req->send();
+
+  ASSERT_EQ(0, ctx.wait());
+  ASSERT_EQ(nullptr, mock_image_ctx.object_map);
+}
+
 } // namespace image
 } // namespace librbd
diff --git a/src/test/librbd/journal/test_Entries.cc b/src/test/librbd/journal/test_Entries.cc
index 91c5c78..ec6c689 100644
--- a/src/test/librbd/journal/test_Entries.cc
+++ b/src/test/librbd/journal/test_Entries.cc
@@ -150,6 +150,9 @@ TEST_F(TestJournalEntries, AioWrite) {
   bufferlist buffer_bl;
   buffer_bl.append(buffer);
   ASSERT_TRUE(aio_write_event.data.contents_equal(buffer_bl));
+
+  ASSERT_EQ(librbd::journal::AioWriteEvent::get_fixed_size() +
+              aio_write_event.data.length(), replay_entry.get_data().length());
 }
 
 TEST_F(TestJournalEntries, AioDiscard) {
diff --git a/src/test/librbd/journal/test_mock_Replay.cc b/src/test/librbd/journal/test_mock_Replay.cc
index 2fc4344..f4145e1 100644
--- a/src/test/librbd/journal/test_mock_Replay.cc
+++ b/src/test/librbd/journal/test_mock_Replay.cc
@@ -147,7 +147,7 @@ public:
                           Context **on_finish, const char *snap_name,
                           uint64_t op_tid) {
     EXPECT_CALL(*mock_image_ctx.operations, execute_snap_create(CStrEq(snap_name), _,
-                                                                op_tid))
+                                                                op_tid, false))
                   .WillOnce(DoAll(SaveArg<1>(on_finish),
                                   NotifyInvoke(&m_invoke_lock, &m_invoke_cond)));
   }
diff --git a/src/test/librbd/mock/MockImageCtx.h b/src/test/librbd/mock/MockImageCtx.h
index 170e9a1..034db1c 100644
--- a/src/test/librbd/mock/MockImageCtx.h
+++ b/src/test/librbd/mock/MockImageCtx.h
@@ -63,6 +63,8 @@ struct MockImageCtx {
       size(image_ctx.size),
       features(image_ctx.features),
       flags(image_ctx.flags),
+      stripe_unit(image_ctx.stripe_unit),
+      stripe_count(image_ctx.stripe_count),
       object_prefix(image_ctx.object_prefix),
       header_oid(image_ctx.header_oid),
       id(image_ctx.id),
@@ -199,6 +201,8 @@ struct MockImageCtx {
   uint64_t size;
   uint64_t features;
   uint64_t flags;
+  uint64_t stripe_unit;
+  uint64_t stripe_count;
   std::string object_prefix;
   std::string header_oid;
   std::string id;
diff --git a/src/test/librbd/mock/MockImageState.h b/src/test/librbd/mock/MockImageState.h
index 8f5f206..8d2ad11 100644
--- a/src/test/librbd/mock/MockImageState.h
+++ b/src/test/librbd/mock/MockImageState.h
@@ -14,6 +14,8 @@ struct MockImageState {
   MOCK_CONST_METHOD0(is_refresh_required, bool());
   MOCK_METHOD1(refresh, void(Context*));
 
+  MOCK_METHOD1(open, void(Context*));
+
   MOCK_METHOD0(close, int());
   MOCK_METHOD1(close, void(Context*));
 };
diff --git a/src/test/librbd/mock/MockJournal.cc b/src/test/librbd/mock/MockJournal.cc
new file mode 100644
index 0000000..a9e9886
--- /dev/null
+++ b/src/test/librbd/mock/MockJournal.cc
@@ -0,0 +1,10 @@
+// -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librbd/mock/MockJournal.h"
+
+namespace librbd {
+
+MockJournal *MockJournal::s_instance = nullptr;
+
+} // namespace librbd
diff --git a/src/test/librbd/mock/MockJournal.h b/src/test/librbd/mock/MockJournal.h
index a4637a4..a80eead 100644
--- a/src/test/librbd/mock/MockJournal.h
+++ b/src/test/librbd/mock/MockJournal.h
@@ -11,12 +11,28 @@
 namespace librbd {
 
 struct MockJournal {
+  static MockJournal *s_instance;
+  static MockJournal *get_instance() {
+    assert(s_instance != nullptr);
+    return s_instance;
+  }
+
+  template <typename ImageCtxT>
+  static int is_tag_owner(ImageCtxT *image_ctx, bool *is_tag_owner) {
+    return get_instance()->is_tag_owner(is_tag_owner);
+  }
+
+  MockJournal() {
+    s_instance = this;
+  }
+
   MOCK_CONST_METHOD0(is_journal_ready, bool());
   MOCK_CONST_METHOD0(is_journal_replaying, bool());
 
   MOCK_METHOD1(wait_for_journal_ready, void(Context *));
 
   MOCK_CONST_METHOD0(is_tag_owner, bool());
+  MOCK_CONST_METHOD1(is_tag_owner, int(bool *));
   MOCK_METHOD6(allocate_tag, void(const std::string &mirror_uuid,
                                   const std::string &predecessor_mirror_uuid,
                                   bool predecessor_commit_valid,
@@ -27,6 +43,8 @@ struct MockJournal {
   MOCK_METHOD1(open, void(Context *));
   MOCK_METHOD1(close, void(Context *));
 
+  MOCK_CONST_METHOD0(get_tag_data, journal::TagData());
+
   MOCK_METHOD0(allocate_op_tid, uint64_t());
 
   MOCK_METHOD3(append_op_event_mock, void(uint64_t, const journal::EventEntry&,
diff --git a/src/test/librbd/mock/MockOperations.h b/src/test/librbd/mock/MockOperations.h
index c92578a..5bbd9b3 100644
--- a/src/test/librbd/mock/MockOperations.h
+++ b/src/test/librbd/mock/MockOperations.h
@@ -22,9 +22,10 @@ struct MockOperations {
                                     Context *on_finish,
                                     uint64_t journal_op_tid));
   MOCK_METHOD2(snap_create, void(const char *snap_name, Context *on_finish));
-  MOCK_METHOD3(execute_snap_create, void(const char *snap_name,
+  MOCK_METHOD4(execute_snap_create, void(const char *snap_name,
                                          Context *on_finish,
-                                         uint64_t journal_op_tid));
+                                         uint64_t journal_op_tid,
+                                         bool skip_object_map));
   MOCK_METHOD2(snap_remove, void(const char *snap_name, Context *on_finish));
   MOCK_METHOD2(execute_snap_remove, void(const char *snap_name,
                                          Context *on_finish));
diff --git a/src/test/librbd/object_map/test_mock_RefreshRequest.cc b/src/test/librbd/object_map/test_mock_RefreshRequest.cc
index 0dfbb4c..be982ce 100644
--- a/src/test/librbd/object_map/test_mock_RefreshRequest.cc
+++ b/src/test/librbd/object_map/test_mock_RefreshRequest.cc
@@ -142,6 +142,8 @@ public:
 };
 
 TEST_F(TestMockObjectMapRefreshRequest, SuccessHead) {
+  REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP);
+
   librbd::ImageCtx *ictx;
   ASSERT_EQ(0, open_image(m_image_name, &ictx));
 
@@ -170,6 +172,8 @@ TEST_F(TestMockObjectMapRefreshRequest, SuccessHead) {
 }
 
 TEST_F(TestMockObjectMapRefreshRequest, SuccessSnapshot) {
+  REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP);
+
   librbd::ImageCtx *ictx;
   ASSERT_EQ(0, open_image(m_image_name, &ictx));
 
@@ -196,6 +200,8 @@ TEST_F(TestMockObjectMapRefreshRequest, SuccessSnapshot) {
 }
 
 TEST_F(TestMockObjectMapRefreshRequest, LoadError) {
+  REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP);
+
   librbd::ImageCtx *ictx;
   ASSERT_EQ(0, open_image(m_image_name, &ictx));
 
@@ -224,6 +230,8 @@ TEST_F(TestMockObjectMapRefreshRequest, LoadError) {
 }
 
 TEST_F(TestMockObjectMapRefreshRequest, LoadCorrupt) {
+  REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP);
+
   librbd::ImageCtx *ictx;
   ASSERT_EQ(0, open_image(m_image_name, &ictx));
 
@@ -254,6 +262,8 @@ TEST_F(TestMockObjectMapRefreshRequest, LoadCorrupt) {
 }
 
 TEST_F(TestMockObjectMapRefreshRequest, TooSmall) {
+  REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP);
+
   librbd::ImageCtx *ictx;
   ASSERT_EQ(0, open_image(m_image_name, &ictx));
 
@@ -285,6 +295,8 @@ TEST_F(TestMockObjectMapRefreshRequest, TooSmall) {
 }
 
 TEST_F(TestMockObjectMapRefreshRequest, TooLarge) {
+  REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP);
+
   librbd::ImageCtx *ictx;
   ASSERT_EQ(0, open_image(m_image_name, &ictx));
 
@@ -312,6 +324,8 @@ TEST_F(TestMockObjectMapRefreshRequest, TooLarge) {
 }
 
 TEST_F(TestMockObjectMapRefreshRequest, ResizeError) {
+  REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP);
+
   librbd::ImageCtx *ictx;
   ASSERT_EQ(0, open_image(m_image_name, &ictx));
 
@@ -342,6 +356,33 @@ TEST_F(TestMockObjectMapRefreshRequest, ResizeError) {
   ASSERT_EQ(0, ctx.wait());
 }
 
+TEST_F(TestMockObjectMapRefreshRequest, LargeImageError) {
+  REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockObjectMapImageCtx mock_image_ctx(*ictx);
+
+  ceph::BitVector<2> on_disk_object_map;
+  init_object_map(mock_image_ctx, &on_disk_object_map);
+
+  C_SaferCond ctx;
+  ceph::BitVector<2> object_map;
+  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &object_map,
+                                                   TEST_SNAP_ID, &ctx);
+
+  InSequence seq;
+  expect_get_image_size(mock_image_ctx, TEST_SNAP_ID,
+                        std::numeric_limits<int64_t>::max());
+
+  MockInvalidateRequest invalidate_request;
+  expect_invalidate_request(mock_image_ctx, invalidate_request);
+
+  req->send();
+  ASSERT_EQ(-EFBIG, ctx.wait());
+}
+
 } // namespace object_map
 } // namespace librbd
 
diff --git a/src/test/librbd/operation/test_mock_SnapshotCreateRequest.cc b/src/test/librbd/operation/test_mock_SnapshotCreateRequest.cc
index 8cc5fda..1b94b7e 100644
--- a/src/test/librbd/operation/test_mock_SnapshotCreateRequest.cc
+++ b/src/test/librbd/operation/test_mock_SnapshotCreateRequest.cc
@@ -138,7 +138,7 @@ TEST_F(TestMockOperationSnapshotCreateRequest, Success) {
 
   C_SaferCond cond_ctx;
   MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest(
-    mock_image_ctx, &cond_ctx, "snap1", 0);
+    mock_image_ctx, &cond_ctx, "snap1", 0, false);
   {
     RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
     req->send();
@@ -167,7 +167,7 @@ TEST_F(TestMockOperationSnapshotCreateRequest, AllocateSnapIdError) {
 
   C_SaferCond cond_ctx;
   MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest(
-    mock_image_ctx, &cond_ctx, "snap1", 0);
+    mock_image_ctx, &cond_ctx, "snap1", 0, false);
   {
     RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
     req->send();
@@ -205,7 +205,7 @@ TEST_F(TestMockOperationSnapshotCreateRequest, CreateSnapStale) {
 
   C_SaferCond cond_ctx;
   MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest(
-    mock_image_ctx, &cond_ctx, "snap1", 0);
+    mock_image_ctx, &cond_ctx, "snap1", 0, false);
   {
     RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
     req->send();
@@ -235,7 +235,7 @@ TEST_F(TestMockOperationSnapshotCreateRequest, CreateSnapError) {
 
   C_SaferCond cond_ctx;
   MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest(
-    mock_image_ctx, &cond_ctx, "snap1", 0);
+    mock_image_ctx, &cond_ctx, "snap1", 0, false);
   {
     RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
     req->send();
@@ -265,7 +265,7 @@ TEST_F(TestMockOperationSnapshotCreateRequest, ReleaseSnapIdError) {
 
   C_SaferCond cond_ctx;
   MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest(
-    mock_image_ctx, &cond_ctx, "snap1", 0);
+    mock_image_ctx, &cond_ctx, "snap1", 0, false);
   {
     RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
     req->send();
@@ -273,5 +273,41 @@ TEST_F(TestMockOperationSnapshotCreateRequest, ReleaseSnapIdError) {
   ASSERT_EQ(-EINVAL, cond_ctx.wait());
 }
 
+TEST_F(TestMockOperationSnapshotCreateRequest, SkipObjectMap) {
+  REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockImageCtx mock_image_ctx(*ictx);
+
+  MockExclusiveLock mock_exclusive_lock;
+  if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+    mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+  }
+
+  MockObjectMap mock_object_map;
+  mock_image_ctx.object_map = &mock_object_map;
+
+  expect_verify_lock_ownership(mock_image_ctx);
+  expect_op_work_queue(mock_image_ctx);
+
+  ::testing::InSequence seq;
+  expect_block_writes(mock_image_ctx);
+  expect_allocate_snap_id(mock_image_ctx, 0);
+  expect_snap_create(mock_image_ctx, 0);
+  expect_update_snap_context(mock_image_ctx);
+  expect_unblock_writes(mock_image_ctx);
+
+  C_SaferCond cond_ctx;
+  MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest(
+    mock_image_ctx, &cond_ctx, "snap1", 0, true);
+  {
+    RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+    req->send();
+  }
+  ASSERT_EQ(0, cond_ctx.wait());
+}
+
 } // namespace operation
 } // namespace librbd
diff --git a/src/test/librbd/test_librbd.cc b/src/test/librbd/test_librbd.cc
index cd9decf..52b5d45 100644
--- a/src/test/librbd/test_librbd.cc
+++ b/src/test/librbd/test_librbd.cc
@@ -3935,6 +3935,13 @@ TEST_F(TestLibRBD, TestImageOptions)
   uint64_t stripe_count = 16;
   rbd_image_options_t opts;
   rbd_image_options_create(&opts);
+
+  bool is_set;
+  ASSERT_EQ(-EINVAL, rbd_image_options_is_set(opts, 12345, &is_set));
+  ASSERT_EQ(0, rbd_image_options_is_set(opts, RBD_IMAGE_OPTION_FORMAT,
+                                        &is_set));
+  ASSERT_FALSE(is_set);
+
   ASSERT_EQ(0, rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_FORMAT,
 	  2));
   ASSERT_EQ(0, rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_FEATURES,
@@ -3946,6 +3953,10 @@ TEST_F(TestLibRBD, TestImageOptions)
   ASSERT_EQ(0, rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_STRIPE_COUNT,
 	  stripe_count));
 
+  ASSERT_EQ(0, rbd_image_options_is_set(opts, RBD_IMAGE_OPTION_FORMAT,
+                                        &is_set));
+  ASSERT_TRUE(is_set);
+
   std::string parent_name = get_temp_image_name();
 
   // make parent
diff --git a/src/test/librbd/test_mirroring.cc b/src/test/librbd/test_mirroring.cc
index e7ccf3f..ece07b1 100644
--- a/src/test/librbd/test_mirroring.cc
+++ b/src/test/librbd/test_mirroring.cc
@@ -68,6 +68,10 @@ public:
     ASSERT_EQ(0, image.mirror_image_get_info(&mirror_image, sizeof(mirror_image)));
     ASSERT_EQ(mirror_state, mirror_image.state);
 
+    librbd::mirror_image_status_t status;
+    ASSERT_EQ(0, image.mirror_image_get_status(&status, sizeof(status)));
+    ASSERT_EQ(MIRROR_IMAGE_STATUS_STATE_UNKNOWN, status.state);
+
     ASSERT_EQ(0, image.close());
     ASSERT_EQ(0, m_rbd.remove(m_ioctx, image_name.c_str()));
     ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_DISABLED));
@@ -93,17 +97,39 @@ public:
     ASSERT_EQ(0, image.mirror_image_get_info(&mirror_image, sizeof(mirror_image)));
     ASSERT_EQ(mirror_state, mirror_image.state);
 
+    librbd::mirror_image_status_t status;
+    ASSERT_EQ(0, image.mirror_image_get_status(&status, sizeof(status)));
+    ASSERT_EQ(MIRROR_IMAGE_STATUS_STATE_UNKNOWN, status.state);
+
     ASSERT_EQ(0, image.close());
     ASSERT_EQ(0, m_rbd.remove(m_ioctx, image_name.c_str()));
     ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_DISABLED));
   }
 
+  void check_mirroring_status(size_t *images_count) {
+    std::map<std::string, librbd::mirror_image_status_t> images;
+    ASSERT_EQ(0, m_rbd.mirror_image_status_list(m_ioctx, "", 4096, &images));
+
+    std::map<librbd::mirror_image_status_state_t, int> states;
+    ASSERT_EQ(0, m_rbd.mirror_image_status_summary(m_ioctx, &states));
+    size_t states_count = 0;
+    for (auto &s : states) {
+      states_count += s.second;
+    }
+    ASSERT_EQ(images.size(), states_count);
+
+    *images_count = images.size();
+  }
+
   void check_mirroring_on_create(uint64_t features,
                                  rbd_mirror_mode_t mirror_mode,
                                  rbd_mirror_image_state_t mirror_state) {
 
     ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, mirror_mode));
 
+    size_t mirror_images_count = 0;
+    check_mirroring_status(&mirror_images_count);
+
     int order = 20;
     ASSERT_EQ(0, m_rbd.create2(m_ioctx, image_name.c_str(), 4096, features, &order));
     librbd::Image image;
@@ -113,9 +139,25 @@ public:
     ASSERT_EQ(0, image.mirror_image_get_info(&mirror_image, sizeof(mirror_image)));
     ASSERT_EQ(mirror_state, mirror_image.state);
 
+    librbd::mirror_image_status_t status;
+    ASSERT_EQ(0, image.mirror_image_get_status(&status, sizeof(status)));
+    ASSERT_EQ(MIRROR_IMAGE_STATUS_STATE_UNKNOWN, status.state);
+
+    size_t mirror_images_new_count = 0;
+    check_mirroring_status(&mirror_images_new_count);
+    if (mirror_mode == RBD_MIRROR_MODE_POOL &&
+	mirror_state == RBD_MIRROR_IMAGE_ENABLED) {
+      ASSERT_EQ(mirror_images_new_count, mirror_images_count + 1);
+    } else {
+      ASSERT_EQ(mirror_images_new_count, mirror_images_count);
+    }
+
     ASSERT_EQ(0, image.close());
     ASSERT_EQ(0, m_rbd.remove(m_ioctx, image_name.c_str()));
     ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_DISABLED));
+
+    check_mirroring_status(&mirror_images_new_count);
+    ASSERT_EQ(mirror_images_new_count, mirror_images_count);
   }
 
   void check_mirroring_on_update_features(uint64_t init_features,
@@ -141,6 +183,10 @@ public:
     ASSERT_EQ(0, image.mirror_image_get_info(&mirror_image, sizeof(mirror_image)));
     ASSERT_EQ(mirror_state, mirror_image.state);
 
+    librbd::mirror_image_status_t status;
+    ASSERT_EQ(0, image.mirror_image_get_status(&status, sizeof(status)));
+    ASSERT_EQ(MIRROR_IMAGE_STATUS_STATE_UNKNOWN, status.state);
+
     ASSERT_EQ(0, image.close());
     ASSERT_EQ(0, m_rbd.remove(m_ioctx, image_name.c_str()));
     ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_DISABLED));
@@ -180,6 +226,10 @@ public:
       ASSERT_EQ(0, image.mirror_image_get_info(&mirror_image, sizeof(mirror_image)));
       ASSERT_EQ(mirror_state, mirror_image.state);
 
+      librbd::mirror_image_status_t status;
+      ASSERT_EQ(0, image.mirror_image_get_status(&status, sizeof(status)));
+      ASSERT_EQ(MIRROR_IMAGE_STATUS_STATE_UNKNOWN, status.state);
+
       ASSERT_EQ(0, image.close());
       ASSERT_EQ(0, m_rbd.remove(m_ioctx, img_name_str.c_str()));
     }
@@ -460,3 +510,28 @@ TEST_F(TestMirroring, RemoveImage_With_ImageWithoutJournal) {
                      false);
 }
 
+TEST_F(TestMirroring, MirrorStatusList) {
+  std::vector<uint64_t>
+      features_vec(5, RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_JOURNALING);
+  setup_images_with_mirror_mode(RBD_MIRROR_MODE_POOL, features_vec);
+
+  std::string last_read = "";
+  std::map<std::string, librbd::mirror_image_status_t> images;
+  ASSERT_EQ(0, m_rbd.mirror_image_status_list(m_ioctx, last_read, 2, &images));
+  ASSERT_EQ(2U, images.size());
+
+  last_read = images.rbegin()->first;
+  images.clear();
+  ASSERT_EQ(0, m_rbd.mirror_image_status_list(m_ioctx, last_read, 2, &images));
+  ASSERT_EQ(2U, images.size());
+
+  last_read = images.rbegin()->first;
+  images.clear();
+  ASSERT_EQ(0, m_rbd.mirror_image_status_list(m_ioctx, last_read, 4096, &images));
+  ASSERT_EQ(1U, images.size());
+
+  last_read = images.rbegin()->first;
+  images.clear();
+  ASSERT_EQ(0, m_rbd.mirror_image_status_list(m_ioctx, last_read, 4096, &images));
+  ASSERT_EQ(0U, images.size());
+}
diff --git a/src/test/librbd/test_mock_Journal.cc b/src/test/librbd/test_mock_Journal.cc
index 9ebea8f..d8c5451 100644
--- a/src/test/librbd/test_mock_Journal.cc
+++ b/src/test/librbd/test_mock_Journal.cc
@@ -2,6 +2,7 @@
 // vim: ts=8 sw=2 smarttab
 
 #include "test/librbd/test_mock_fixture.h"
+#include "test/journal/mock/MockJournaler.h"
 #include "test/librbd/test_support.h"
 #include "test/librbd/mock/MockImageCtx.h"
 #include "common/Cond.h"
@@ -19,199 +20,6 @@
 #include <list>
 #include <boost/scope_exit.hpp>
 
-namespace journal {
-
-struct MockFuture {
-  static MockFuture *s_instance;
-  static MockFuture &get_instance() {
-    assert(s_instance != nullptr);
-    return *s_instance;
-  }
-
-  MockFuture() {
-    s_instance = this;
-  }
-
-  MOCK_CONST_METHOD0(is_valid, bool());
-  MOCK_METHOD1(flush, void(Context *));
-  MOCK_METHOD1(wait, void(Context *));
-};
-
-struct MockFutureProxy {
-  bool is_valid() const {
-    return MockFuture::get_instance().is_valid();
-  }
-
-  void flush(Context *on_safe) {
-    MockFuture::get_instance().flush(on_safe);
-  }
-
-  void wait(Context *on_safe) {
-    MockFuture::get_instance().wait(on_safe);
-  }
-};
-
-struct MockReplayEntry {
-  static MockReplayEntry *s_instance;
-  static MockReplayEntry &get_instance() {
-    assert(s_instance != nullptr);
-    return *s_instance;
-  }
-
-  MockReplayEntry() {
-    s_instance = this;
-  }
-
-  MOCK_METHOD0(get_data, bufferlist());
-};
-
-struct MockReplayEntryProxy {
-  bufferlist get_data() {
-    return MockReplayEntry::get_instance().get_data();
-  }
-};
-
-struct MockJournaler {
-  static MockJournaler *s_instance;
-  static MockJournaler &get_instance() {
-    assert(s_instance != nullptr);
-    return *s_instance;
-  }
-
-  MockJournaler() {
-    s_instance = this;
-  }
-
-  MOCK_METHOD0(construct, void());
-
-  MOCK_METHOD3(get_metadata, void(uint8_t *order, uint8_t *splay_width,
-                                  int64_t *pool_id));
-  MOCK_METHOD1(init, void(Context*));
-  MOCK_METHOD0(shut_down, void());
-  MOCK_METHOD1(flush_commit_position, void(Context*));
-
-  MOCK_METHOD2(get_cached_client, int(const std::string&, cls::journal::Client*));
-  MOCK_METHOD2(update_client, void(const bufferlist &, Context *));
-  MOCK_METHOD3(get_tags, void(uint64_t, journal::Journaler::Tags*, Context*));
-
-  MOCK_METHOD1(start_replay, void(::journal::ReplayHandler *replay_handler));
-  MOCK_METHOD1(try_pop_front, bool(MockReplayEntryProxy *replay_entry));
-  MOCK_METHOD0(stop_replay, void());
-
-  MOCK_METHOD3(start_append, void(int flush_interval, uint64_t flush_bytes,
-                                  double flush_age));
-  MOCK_METHOD2(append, MockFutureProxy(uint64_t tag_id,
-                                       const bufferlist &bl));
-  MOCK_METHOD1(flush, void(Context *on_safe));
-  MOCK_METHOD1(stop_append, void(Context *on_safe));
-
-  MOCK_METHOD1(committed, void(const MockReplayEntryProxy &replay_entry));
-  MOCK_METHOD1(committed, void(const MockFutureProxy &future));
-};
-
-struct MockJournalerProxy {
-  template <typename IoCtxT>
-  MockJournalerProxy(IoCtxT &header_ioctx, const std::string &,
-                     const std::string &, double) {
-    MockJournaler::get_instance().construct();
-  }
-
-  template <typename IoCtxT>
-  MockJournalerProxy(ContextWQ *work_queue, SafeTimer *safe_timer,
-                     Mutex *timer_lock, IoCtxT &header_ioctx,
-                     const std::string &, const std::string &, double) {
-    MockJournaler::get_instance().construct();
-  }
-
-  int exists(bool *header_exists) const {
-    return -EINVAL;
-  }
-  int create(uint8_t order, uint8_t splay_width, int64_t pool_id) {
-    return -EINVAL;
-  }
-  int remove(bool force) {
-    return -EINVAL;
-  }
-  int register_client(const bufferlist &data) {
-    return -EINVAL;
-  }
-  void allocate_tag(uint64_t, const bufferlist &,
-                    cls::journal::Tag*, Context *on_finish) {
-    on_finish->complete(-EINVAL);
-  }
-
-  void get_metadata(uint8_t *order, uint8_t *splay_width, int64_t *pool_id) {
-    MockJournaler::get_instance().get_metadata(order, splay_width, pool_id);
-  }
-
-  void init(Context *on_finish) {
-    MockJournaler::get_instance().init(on_finish);
-  }
-  void shut_down() {
-    MockJournaler::get_instance().shut_down();
-  }
-
-  int get_cached_client(const std::string& client_id,
-                        cls::journal::Client* client) {
-    return MockJournaler::get_instance().get_cached_client(client_id, client);
-  }
-  void update_client(const bufferlist &client_data, Context *on_finish) {
-    MockJournaler::get_instance().update_client(client_data, on_finish);
-  }
-
-  void get_tags(uint64_t tag_class, journal::Journaler::Tags *tags,
-                Context *on_finish) {
-    MockJournaler::get_instance().get_tags(tag_class, tags, on_finish);
-  }
-
-  void flush_commit_position(Context *on_finish) {
-    MockJournaler::get_instance().flush_commit_position(on_finish);
-  }
-
-  void start_replay(::journal::ReplayHandler *replay_handler) {
-    MockJournaler::get_instance().start_replay(replay_handler);
-  }
-
-  bool try_pop_front(MockReplayEntryProxy *replay_entry) {
-    return MockJournaler::get_instance().try_pop_front(replay_entry);
-  }
-
-  void stop_replay() {
-    MockJournaler::get_instance().stop_replay();
-  }
-
-  void start_append(int flush_interval, uint64_t flush_bytes, double flush_age) {
-    MockJournaler::get_instance().start_append(flush_interval, flush_bytes,
-                                               flush_age);
-  }
-
-  MockFutureProxy append(uint64_t tag_id, const bufferlist &bl) {
-    return MockJournaler::get_instance().append(tag_id, bl);
-  }
-
-  void flush(Context *on_safe) {
-    MockJournaler::get_instance().flush(on_safe);
-  }
-
-  void stop_append(Context *on_safe) {
-    MockJournaler::get_instance().stop_append(on_safe);
-  }
-
-  void committed(const MockReplayEntryProxy &replay_entry) {
-    MockJournaler::get_instance().committed(replay_entry);
-  }
-
-  void committed(const MockFutureProxy &future) {
-    MockJournaler::get_instance().committed(future);
-  }
-};
-
-MockFuture *MockFuture::s_instance = nullptr;
-MockReplayEntry *MockReplayEntry::s_instance = nullptr;
-MockJournaler *MockJournaler::s_instance = nullptr;
-
-} // namespace journal
-
 namespace librbd {
 
 namespace {
@@ -340,6 +148,12 @@ public:
                   .WillOnce(CompleteContext(r, NULL));
   }
 
+  void expect_get_max_append_size(::journal::MockJournaler &mock_journaler,
+                                  uint32_t max_size) {
+    EXPECT_CALL(mock_journaler, get_max_append_size())
+                  .WillOnce(Return(max_size));
+  }
+
   void expect_get_journaler_cached_client(::journal::MockJournaler &mock_journaler, int r) {
 
     journal::ImageClientMeta image_client_meta;
@@ -463,6 +277,15 @@ public:
     return ctx.wait();
   }
 
+  uint64_t when_append_write_event(MockJournalImageCtx &mock_image_ctx,
+                                   MockJournal &mock_journal, uint64_t length) {
+    bufferlist bl;
+    bl.append_zero(length);
+
+    RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+    return mock_journal.append_write_event(nullptr, 0, length, bl, {}, false);
+  }
+
   uint64_t when_append_io_event(MockJournalImageCtx &mock_image_ctx,
                                 MockJournal &mock_journal,
                                 AioCompletion *aio_comp = nullptr) {
@@ -501,6 +324,7 @@ public:
     InSequence seq;
     expect_construct_journaler(mock_journaler);
     expect_init_journaler(mock_journaler, 0);
+    expect_get_max_append_size(mock_journaler, 1 << 16);
     expect_get_journaler_cached_client(mock_journaler, 0);
     expect_get_journaler_tags(mock_image_ctx, mock_journaler, 0);
     expect_start_replay(
@@ -546,6 +370,7 @@ TEST_F(TestMockJournal, StateTransitions) {
   ::journal::MockJournaler mock_journaler;
   expect_construct_journaler(mock_journaler);
   expect_init_journaler(mock_journaler, 0);
+  expect_get_max_append_size(mock_journaler, 1 << 16);
   expect_get_journaler_cached_client(mock_journaler, 0);
   expect_get_journaler_tags(mock_image_ctx, mock_journaler, 0);
   expect_start_replay(
@@ -611,6 +436,7 @@ TEST_F(TestMockJournal, GetCachedClientError) {
   ::journal::MockJournaler mock_journaler;
   expect_construct_journaler(mock_journaler);
   expect_init_journaler(mock_journaler, 0);
+  expect_get_max_append_size(mock_journaler, 1 << 16);
   expect_get_journaler_cached_client(mock_journaler, -ENOENT);
   ASSERT_EQ(-ENOENT, when_open(mock_journal));
 }
@@ -630,6 +456,7 @@ TEST_F(TestMockJournal, GetTagsError) {
   ::journal::MockJournaler mock_journaler;
   expect_construct_journaler(mock_journaler);
   expect_init_journaler(mock_journaler, 0);
+  expect_get_max_append_size(mock_journaler, 1 << 16);
   expect_get_journaler_cached_client(mock_journaler, 0);
   expect_get_journaler_tags(mock_image_ctx, mock_journaler, -EBADMSG);
   ASSERT_EQ(-EBADMSG, when_open(mock_journal));
@@ -650,6 +477,7 @@ TEST_F(TestMockJournal, ReplayCompleteError) {
   ::journal::MockJournaler mock_journaler;
   expect_construct_journaler(mock_journaler);
   expect_init_journaler(mock_journaler, 0);
+  expect_get_max_append_size(mock_journaler, 1 << 16);
   expect_get_journaler_cached_client(mock_journaler, 0);
   expect_get_journaler_tags(mock_image_ctx, mock_journaler, 0);
   expect_start_replay(
@@ -664,6 +492,7 @@ TEST_F(TestMockJournal, ReplayCompleteError) {
   // replay failure should result in replay-restart
   expect_construct_journaler(mock_journaler);
   expect_init_journaler(mock_journaler, 0);
+  expect_get_max_append_size(mock_journaler, 1 << 16);
   expect_get_journaler_cached_client(mock_journaler, 0);
   expect_get_journaler_tags(mock_image_ctx, mock_journaler, 0);
   expect_start_replay(
@@ -695,6 +524,7 @@ TEST_F(TestMockJournal, FlushReplayError) {
   ::journal::MockJournaler mock_journaler;
   expect_construct_journaler(mock_journaler);
   expect_init_journaler(mock_journaler, 0);
+  expect_get_max_append_size(mock_journaler, 1 << 16);
   expect_get_journaler_cached_client(mock_journaler, 0);
   expect_get_journaler_tags(mock_image_ctx, mock_journaler, 0);
   expect_start_replay(
@@ -714,6 +544,7 @@ TEST_F(TestMockJournal, FlushReplayError) {
   // replay flush failure should result in replay-restart
   expect_construct_journaler(mock_journaler);
   expect_init_journaler(mock_journaler, 0);
+  expect_get_max_append_size(mock_journaler, 1 << 16);
   expect_get_journaler_cached_client(mock_journaler, 0);
   expect_get_journaler_tags(mock_image_ctx, mock_journaler, 0);
   expect_start_replay(
@@ -745,6 +576,7 @@ TEST_F(TestMockJournal, StopError) {
   ::journal::MockJournaler mock_journaler;
   expect_construct_journaler(mock_journaler);
   expect_init_journaler(mock_journaler, 0);
+  expect_get_max_append_size(mock_journaler, 1 << 16);
   expect_get_journaler_cached_client(mock_journaler, 0);
   expect_get_journaler_tags(mock_image_ctx, mock_journaler, 0);
   expect_start_replay(
@@ -776,6 +608,7 @@ TEST_F(TestMockJournal, ReplayOnDiskPreFlushError) {
   ::journal::MockJournaler mock_journaler;
   expect_construct_journaler(mock_journaler);
   expect_init_journaler(mock_journaler, 0);
+  expect_get_max_append_size(mock_journaler, 1 << 16);
   expect_get_journaler_cached_client(mock_journaler, 0);
   expect_get_journaler_tags(mock_image_ctx, mock_journaler, 0);
 
@@ -802,6 +635,7 @@ TEST_F(TestMockJournal, ReplayOnDiskPreFlushError) {
   // replay write-to-disk failure should result in replay-restart
   expect_construct_journaler(mock_journaler);
   expect_init_journaler(mock_journaler, 0);
+  expect_get_max_append_size(mock_journaler, 1 << 16);
   expect_get_journaler_cached_client(mock_journaler, 0);
   expect_get_journaler_tags(mock_image_ctx, mock_journaler, 0);
   expect_start_replay(
@@ -854,6 +688,7 @@ TEST_F(TestMockJournal, ReplayOnDiskPostFlushError) {
   ::journal::MockJournaler mock_journaler;
   expect_construct_journaler(mock_journaler);
   expect_init_journaler(mock_journaler, 0);
+  expect_get_max_append_size(mock_journaler, 1 << 16);
   expect_get_journaler_cached_client(mock_journaler, 0);
   expect_get_journaler_tags(mock_image_ctx, mock_journaler, 0);
   expect_start_replay(
@@ -877,6 +712,7 @@ TEST_F(TestMockJournal, ReplayOnDiskPostFlushError) {
   // replay write-to-disk failure should result in replay-restart
   expect_construct_journaler(mock_journaler);
   expect_init_journaler(mock_journaler, 0);
+  expect_get_max_append_size(mock_journaler, 1 << 16);
   expect_get_journaler_cached_client(mock_journaler, 0);
   expect_get_journaler_tags(mock_image_ctx, mock_journaler, 0);
   expect_start_replay(
@@ -959,6 +795,42 @@ TEST_F(TestMockJournal, EventAndIOCommitOrder) {
   ASSERT_EQ(0, event_ctx.wait());
 }
 
+TEST_F(TestMockJournal, AppendWriteEvent) {
+  REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockJournalImageCtx mock_image_ctx(*ictx);
+  MockJournal mock_journal(mock_image_ctx);
+  ::journal::MockJournaler mock_journaler;
+  open_journal(mock_image_ctx, mock_journal, mock_journaler);
+  BOOST_SCOPE_EXIT_ALL(&) {
+    close_journal(mock_journal, mock_journaler);
+  };
+
+  InSequence seq;
+
+  ::journal::MockFuture mock_future;
+  Context *on_journal_safe = nullptr;
+  expect_append_journaler(mock_journaler);
+  expect_append_journaler(mock_journaler);
+  expect_append_journaler(mock_journaler);
+  expect_wait_future(mock_future, &on_journal_safe);
+  ASSERT_EQ(1U, when_append_write_event(mock_image_ctx, mock_journal, 1 << 17));
+
+  on_journal_safe->complete(0);
+  C_SaferCond event_ctx;
+  mock_journal.wait_event(1U, &event_ctx);
+  ASSERT_EQ(0, event_ctx.wait());
+
+  expect_future_committed(mock_journaler);
+  expect_future_committed(mock_journaler);
+  expect_future_committed(mock_journaler);
+  mock_journal.commit_io_event(1U, 0);
+  ictx->op_work_queue->drain();
+}
+
 TEST_F(TestMockJournal, EventCommitError) {
   REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
 
diff --git a/src/test/mds/TestMDSAuthCaps.cc b/src/test/mds/TestMDSAuthCaps.cc
index 0c821e8..919095d 100644
--- a/src/test/mds/TestMDSAuthCaps.cc
+++ b/src/test/mds/TestMDSAuthCaps.cc
@@ -29,6 +29,7 @@ const char *parse_good[] = {
   "allow rw uid=1 gids=1",
   "allow * path=\"/foo\"",
   "allow * path=/foo",
+  "allow * path=/foo-bar_baz",
   "allow * path=\"/foo bar/baz\"",
   "allow * uid=1",
   "allow * path=\"/foo\" uid=1",
@@ -183,6 +184,27 @@ TEST(MDSAuthCaps, AllowPath) {
   ASSERT_FALSE(cap.is_capable("foo", 0, 0, 0777, 0, 0, MAY_READ | MAY_WRITE, 0, 0));
 }
 
+TEST(MDSAuthCaps, AllowPathChars) {
+  MDSAuthCaps unquo_cap;
+  ASSERT_TRUE(unquo_cap.parse(g_ceph_context, "allow * path=/sandbox-._foo", NULL));
+  ASSERT_FALSE(unquo_cap.allow_all());
+  ASSERT_TRUE(unquo_cap.is_capable("sandbox-._foo/foo", 0, 0, 0777, 0, 0, MAY_READ | MAY_WRITE, 0, 0));
+  ASSERT_FALSE(unquo_cap.is_capable("sandbox", 0, 0, 0777, 0, 0, MAY_READ | MAY_WRITE, 0, 0));
+  ASSERT_FALSE(unquo_cap.is_capable("sandbox-._food", 0, 0, 0777, 0, 0, MAY_READ | MAY_WRITE, 0, 0));
+  ASSERT_FALSE(unquo_cap.is_capable("foo", 0, 0, 0777, 0, 0, MAY_READ | MAY_WRITE, 0, 0));
+}
+
+
+TEST(MDSAuthCaps, AllowPathCharsQuoted) {
+  MDSAuthCaps quo_cap;
+  ASSERT_TRUE(quo_cap.parse(g_ceph_context, "allow * path=\"/sandbox-._foo\"", NULL));
+  ASSERT_FALSE(quo_cap.allow_all());
+  ASSERT_TRUE(quo_cap.is_capable("sandbox-._foo/foo", 0, 0, 0777, 0, 0, MAY_READ | MAY_WRITE, 0, 0));
+  ASSERT_FALSE(quo_cap.is_capable("sandbox", 0, 0, 0777, 0, 0, MAY_READ | MAY_WRITE, 0, 0));
+  ASSERT_FALSE(quo_cap.is_capable("sandbox-._food", 0, 0, 0777, 0, 0, MAY_READ | MAY_WRITE, 0, 0));
+  ASSERT_FALSE(quo_cap.is_capable("foo", 0, 0, 0777, 0, 0, MAY_READ | MAY_WRITE, 0, 0));
+}
+
 TEST(MDSAuthCaps, OutputParsed) {
   struct CapsTest {
     const char *input;
diff --git a/src/test/objectstore/test_kv.cc b/src/test/objectstore/test_kv.cc
index b561650..06c45e0 100644
--- a/src/test/objectstore/test_kv.cc
+++ b/src/test/objectstore/test_kv.cc
@@ -35,9 +35,20 @@ public:
 
   KVTest() : db(0) {}
 
+  void rm_r(string path) {
+    string cmd = string("rm -r ") + path;
+    cout << "==> " << cmd << std::endl;
+    int r = ::system(cmd.c_str());
+    if (r) {
+      cerr << "failed with exit code " << r
+	   << ", continuing anyway" << std::endl;
+    }
+  }
+
   void init() {
+    cout << "Creating " << string(GetParam()) << "\n";
     db.reset(KeyValueDB::create(g_ceph_context, string(GetParam()),
-				string("kv_test_temp_dir")));
+				"kv_test_temp_dir"));
   }
   void fini() {
     db.reset(NULL);
@@ -47,14 +58,15 @@ public:
     int r = ::mkdir("kv_test_temp_dir", 0777);
     if (r < 0 && errno != EEXIST) {
       r = -errno;
-      cerr << __func__ << ": unable to create kv_test_temp_dir"
-	   << ": " << cpp_strerror(r) << std::endl;
+      cerr << __func__ << ": unable to create kv_test_temp_dir: "
+	   << cpp_strerror(r) << std::endl;
       return;
     }
     init();
   }
   virtual void TearDown() {
     fini();
+    rm_r("kv_test_temp_dir");
   }
 };
 
@@ -87,11 +99,11 @@ TEST_P(KVTest, PutReopen) {
   init();
   ASSERT_EQ(0, db->open(cout));
   {
-    bufferlist v;
-    ASSERT_EQ(0, db->get("prefix", "key", &v));
-    ASSERT_EQ(v.length(), 5u);
-    ASSERT_EQ(0, db->get("prefix", "key2", &v));
-    ASSERT_EQ(v.length(), 5u);
+    bufferlist v1, v2;
+    ASSERT_EQ(0, db->get("prefix", "key", &v1));
+    ASSERT_EQ(v1.length(), 5u);
+    ASSERT_EQ(0, db->get("prefix", "key2", &v2));
+    ASSERT_EQ(v2.length(), 5u);
   }
   {
     KeyValueDB::Transaction t = db->get_transaction();
@@ -104,11 +116,11 @@ TEST_P(KVTest, PutReopen) {
   init();
   ASSERT_EQ(0, db->open(cout));
   {
-    bufferlist v;
-    ASSERT_EQ(-ENOENT, db->get("prefix", "key", &v));
-    ASSERT_EQ(0, db->get("prefix", "key2", &v));
-    ASSERT_EQ(v.length(), 5u);
-    ASSERT_EQ(-ENOENT, db->get("prefix", "key3", &v));
+    bufferlist v1, v2, v3;
+    ASSERT_EQ(-ENOENT, db->get("prefix", "key", &v1));
+    ASSERT_EQ(0, db->get("prefix", "key2", &v2));
+    ASSERT_EQ(v2.length(), 5u);
+    ASSERT_EQ(-ENOENT, db->get("prefix", "key3", &v3));
   }
   fini();
 }
@@ -144,6 +156,7 @@ TEST_P(KVTest, BenchCommit) {
   utime_t dur = end - start;
   cout << n << " commits in " << dur << ", avg latency " << (dur / (double)n)
        << std::endl;
+  fini();
 }
 
 
diff --git a/src/test/opensuse-13.2/ceph.spec.in b/src/test/opensuse-13.2/ceph.spec.in
index 26928f7..34e4caa 100644
--- a/src/test/opensuse-13.2/ceph.spec.in
+++ b/src/test/opensuse-13.2/ceph.spec.in
@@ -773,6 +773,7 @@ install -m 0644 -D etc/sysconfig/ceph $RPM_BUILD_ROOT%{_localstatedir}/adm/fillu
   install -m 0644 -D systemd/ceph-rbd-mirror.target $RPM_BUILD_ROOT%{_unitdir}/ceph-rbd-mirror.target
   install -m 0644 -D systemd/ceph-disk at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-disk at .service
   install -m 0755 -D systemd/ceph $RPM_BUILD_ROOT%{_sbindir}/rcceph
+  install -m 0644 -D systemd/50-ceph.preset $RPM_BUILD_ROOT%{_libexecdir}/systemd/system-preset/50-ceph.preset
 %else
   install -D src/init-rbdmap $RPM_BUILD_ROOT%{_initrddir}/rbdmap
   install -D src/init-ceph $RPM_BUILD_ROOT%{_initrddir}/ceph
@@ -845,6 +846,7 @@ rm -rf $RPM_BUILD_ROOT
 %{_bindir}/cephfs
 %if 0%{?_with_systemd}
 %{_unitdir}/ceph-create-keys at .service
+%{_libexecdir}/systemd/system-preset/50-ceph.preset
 %else
 %{_initrddir}/ceph
 %endif
@@ -899,70 +901,35 @@ rm -rf $RPM_BUILD_ROOT
 %attr(770,ceph,ceph) %dir %{_localstatedir}/run/ceph
 %endif
 
-%pre base
-%if 0%{?_with_systemd}
-  %if 0%{?suse_version}
-    # service_add_pre and friends don't work with parameterized systemd service
-    # instances, only with single services or targets, so we always pass
-    # ceph.target to these macros
-    %service_add_pre ceph.target
-  %endif
-%endif
-
 %post base
 /sbin/ldconfig
-%if 0%{?_with_systemd}
-  %if 0%{?suse_version}
-    %fillup_only
-    %service_add_post ceph.target
-  %endif
-%else
-  /sbin/chkconfig --add ceph
+%if 0%{?suse_version}
+%fillup_only
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph.target >/dev/null 2>&1 || :
+fi
 %endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph.target
+%endif
+/usr/bin/systemctl start ceph.target >/dev/null 2>&1 || :
 
 %preun base
-%if 0%{?_with_systemd}
-  %if 0%{?suse_version}
-    %service_del_preun ceph.target
-  %endif
-  # Disable and stop on removal.
-  if [ $1 = 0 ] ; then
-    SERVICE_LIST=$(systemctl | grep -E '^ceph-mon@|^ceph-create-keys@|^ceph-osd@|^ceph-mds@|^ceph-disk-'  | cut -d' ' -f1)
-    if [ -n "$SERVICE_LIST" ]; then
-      for SERVICE in $SERVICE_LIST; do
-        /usr/bin/systemctl --no-reload disable $SERVICE > /dev/null 2>&1 || :
-        /usr/bin/systemctl stop $SERVICE > /dev/null 2>&1 || :
-      done
-    fi
-  fi
-%else
-  %if 0%{?rhel} || 0%{?fedora}
-    if [ $1 = 0 ] ; then
-      /sbin/service ceph stop >/dev/null 2>&1
-      /sbin/chkconfig --del ceph
-    fi
-  %endif
+%if 0%{?suse_version}
+%service_del_preun ceph.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph.target
 %endif
 
 %postun base
 /sbin/ldconfig
-%if 0%{?_with_systemd}
-  if [ $1 = 1 ] ; then
-    # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
-    # "yes". In any case: if units are not running, do not touch them.
-    SYSCONF_CEPH=/etc/sysconfig/ceph
-    if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
-      source $SYSCONF_CEPH
-    fi
-    if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
-      SERVICE_LIST=$(systemctl | grep -E '^ceph-mon@|^ceph-create-keys@|^ceph-osd@|^ceph-mds@|^ceph-disk-'  | cut -d' ' -f1)
-      if [ -n "$SERVICE_LIST" ]; then
-        for SERVICE in $SERVICE_LIST; do
-          /usr/bin/systemctl try-restart $SERVICE > /dev/null 2>&1 || :
-        done
-      fi
-    fi
-  fi
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph.target
 %endif
 
 #################################################################################
@@ -1068,6 +1035,46 @@ fi
 %endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/mds
 
+%post mds
+%if 0%{?suse_version}
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-mds@\*.service ceph-mds.target >/dev/null 2>&1 || :
+fi
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-mds@\*.service ceph-mds.target
+%endif
+/usr/bin/systemctl start ceph-mds.target >/dev/null 2>&1 || :
+
+%preun mds
+%if 0%{?suse_version}
+%service_del_preun ceph-mds@\*.service ceph-mds.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-mds@\*.service ceph-mds.target
+%endif
+
+%postun mds
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-mds@\*.service ceph-mds.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-mds@\*.service ceph-mds.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-mds@\*.service > /dev/null 2>&1 || :
+  fi
+fi
+
 #################################################################################
 %files mon
 %{_bindir}/ceph-mon
@@ -1083,6 +1090,46 @@ fi
 %endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/mon
 
+%post mon
+%if 0%{?suse_version}
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target >/dev/null 2>&1 || :
+fi
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+/usr/bin/systemctl start ceph-mon.target >/dev/null 2>&1 || :
+
+%preun mon
+%if 0%{?suse_version}
+%service_del_preun ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+
+%postun mon
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-create-keys@\*.service ceph-mon@\*.service ceph-mon.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-create-keys@\*.service ceph-mon@\*.service > /dev/null 2>&1 || :
+  fi
+fi
+
 #################################################################################
 %files fuse
 %defattr(-,root,root,-)
@@ -1110,6 +1157,46 @@ fi
 %{_unitdir}/ceph-rbd-mirror.target
 %endif
 
+%post -n rbd-mirror
+%if 0%{?suse_version}
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-rbd-mirror@\*.service ceph-rbd-mirror.target >/dev/null 2>&1 || :
+fi
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+/usr/bin/systemctl start ceph-rbd-mirror.target >/dev/null 2>&1 || :
+
+%preun -n rbd-mirror
+%if 0%{?suse_version}
+%service_del_preun ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+
+%postun -n rbd-mirror
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-rbd-mirror@\*.service ceph-rbd-mirror.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-rbd-mirror@\*.service > /dev/null 2>&1 || :
+  fi
+fi
+
 #################################################################################
 %files -n rbd-nbd
 %defattr(-,root,root,-)
@@ -1136,48 +1223,44 @@ fi
 %endif
 
 %post radosgw
-/sbin/ldconfig
 %if 0%{?suse_version}
-  # explicit systemctl daemon-reload (that's the only relevant bit of
-  # service_add_post; the rest is all sysvinit --> systemd migration which
-  # isn't applicable in this context (see above comment).
-  /usr/bin/systemctl daemon-reload >/dev/null 2>&1 || :
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-radosgw@\*.service ceph-radosgw.target >/dev/null 2>&1 || :
+fi
 %endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-radosgw@\*.service ceph-radosgw.target
+%endif
+/usr/bin/systemctl start ceph-radosgw.target >/dev/null 2>&1 || :
 
 %preun radosgw
-%if 0%{?_with_systemd}
-  # Disable and stop on removal.
-  if [ $1 = 0 ] ; then
-    SERVICE_LIST=$(systemctl | grep -E '^ceph-radosgw@'  | cut -d' ' -f1)
-    if [ -n "$SERVICE_LIST" ]; then
-      for SERVICE in $SERVICE_LIST; do
-        /usr/bin/systemctl --no-reload disable $SERVICE > /dev/null 2>&1 || :
-        /usr/bin/systemctl stop $SERVICE > /dev/null 2>&1 || :
-      done
-    fi
-  fi
+%if 0%{?suse_version}
+%service_del_preun ceph-radosgw@\*.service ceph-radosgw.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-radosgw@\*.service ceph-radosgw.target
 %endif
 
 %postun radosgw
-/sbin/ldconfig
-%if 0%{?_with_systemd}
-  if [ $1 = 1 ] ; then
-    # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
-    # "yes". In any case: if units are not running, do not touch them.
-    SYSCONF_CEPH=/etc/sysconfig/ceph
-    if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
-      source $SYSCONF_CEPH
-    fi
-    if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
-      SERVICE_LIST=$(systemctl | grep -E '^ceph-radosgw@'  | cut -d' ' -f1)
-      if [ -n "$SERVICE_LIST" ]; then
-        for SERVICE in $SERVICE_LIST; do
-          /usr/bin/systemctl try-restart $SERVICE > /dev/null 2>&1 || :
-        done
-      fi
-    fi
-  fi
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-radosgw@\*.service ceph-radosgw.target
 %endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-radosgw@\*.service ceph-radosgw.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-radosgw@\*.service > /dev/null 2>&1 || :
+  fi
+fi
 
 #################################################################################
 %files osd
@@ -1205,6 +1288,46 @@ fi
 %endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/osd
 
+%post osd
+%if 0%{?suse_version}
+if [ $1 -ge 1 ] ; then
+  /usr/bin/systemctl preset ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target >/dev/null 2>&1 || :
+fi
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_post ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+/usr/bin/systemctl start ceph-osd.target >/dev/null 2>&1 || :
+
+%preun osd
+%if 0%{?suse_version}
+%service_del_preun ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_preun ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+
+%postun osd
+test -n "$FIRST_ARG" || FIRST_ARG=$1
+%if 0%{?suse_version}
+DISABLE_RESTART_ON_UPDATE="yes"
+%service_del_postun ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+%if 0%{?fedora} || 0%{?rhel}
+%systemd_postun ceph-disk@\*.service ceph-osd@\*.service ceph-osd.target
+%endif
+if [ $FIRST_ARG -ge 1 ] ; then
+  # Restart on upgrade, but only if "CEPH_AUTO_RESTART_ON_UPGRADE" is set to
+  # "yes". In any case: if units are not running, do not touch them.
+  SYSCONF_CEPH=/etc/sysconfig/ceph
+  if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+    source $SYSCONF_CEPH
+  fi
+  if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+    /usr/bin/systemctl try-restart ceph-disk@\*.service ceph-osd@\*.service > /dev/null 2>&1 || :
+  fi
+fi
+
 #################################################################################
 %if %{with ocf}
 
@@ -1450,7 +1573,7 @@ else
     exit 0
 fi
 
-if test "$OLD_POLVER" == "$NEW_POLVER"; then
+if test "$OLD_POLVER" = "$NEW_POLVER"; then
    # Do not relabel if policy version did not change
    exit 0
 fi
diff --git a/src/test/opensuse-13.2/install-deps.sh b/src/test/opensuse-13.2/install-deps.sh
index 8249ea3..21e71ee 100755
--- a/src/test/opensuse-13.2/install-deps.sh
+++ b/src/test/opensuse-13.2/install-deps.sh
@@ -66,7 +66,7 @@ CentOS|Fedora|RedHatEnterpriseServer)
                     $SUDO yum install subscription-manager
                     $SUDO subscription-manager repos --enable=rhel-$MAJOR_VERSION-server-optional-rpms
                 fi
-                $SUDO yum-config-manager --add-repo https://dl.fedoraproject.org/pub/epel/$MAJOR_VERSION/x86_64/ 
+                $SUDO yum-config-manager --add-repo https://dl.fedoraproject.org/pub/epel/$MAJOR_VERSION/x86_64/
                 $SUDO yum install --nogpgcheck -y epel-release
                 $SUDO rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-$MAJOR_VERSION
                 $SUDO rm -f /etc/yum.repos.d/dl.fedoraproject.org*
@@ -92,8 +92,6 @@ function populate_wheelhouse() {
     local install=$1
     shift
 
-    # Ubuntu-12.04 and Python 2.7.3 require this line
-    pip --timeout 300 $install 'distribute >= 0.7.3' || return 1
     # although pip comes with virtualenv, having a recent version
     # of pip matters when it comes to using wheel packages
     pip --timeout 300 $install 'setuptools >= 0.8' 'pip >= 7.0' 'wheel >= 0.24' || return 1
diff --git a/src/test/rbd_mirror/image_replayer/test_mock_BootstrapRequest.cc b/src/test/rbd_mirror/image_replayer/test_mock_BootstrapRequest.cc
new file mode 100644
index 0000000..0cec3a2
--- /dev/null
+++ b/src/test/rbd_mirror/image_replayer/test_mock_BootstrapRequest.cc
@@ -0,0 +1,126 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/rbd_mirror/test_mock_fixture.h"
+#include "librbd/journal/TypeTraits.h"
+#include "tools/rbd_mirror/ImageSync.h"
+#include "tools/rbd_mirror/image_replayer/BootstrapRequest.h"
+#include "tools/rbd_mirror/image_replayer/CloseImageRequest.h"
+#include "tools/rbd_mirror/image_replayer/OpenLocalImageRequest.h"
+#include "test/journal/mock/MockJournaler.h"
+#include "test/librbd/mock/MockImageCtx.h"
+
+namespace librbd {
+namespace journal {
+
+template <>
+struct TypeTraits<librbd::MockImageCtx> {
+  typedef ::journal::MockJournaler Journaler;
+};
+
+} // namespace journal
+} // namespace librbd
+
+namespace rbd {
+namespace mirror {
+
+class ProgressContext;
+
+template<>
+struct ImageSync<librbd::MockImageCtx> {
+  static ImageSync* s_instance;
+  Context *on_finish = nullptr;
+
+  static ImageSync* create(librbd::MockImageCtx *local_image_ctx,
+                           librbd::MockImageCtx *remote_image_ctx,
+                           SafeTimer *timer, Mutex *timer_lock,
+                           const std::string &mirror_uuid,
+                           ::journal::MockJournaler *journaler,
+                           librbd::journal::MirrorPeerClientMeta *client_meta,
+                           Context *on_finish,
+                           ProgressContext *progress_ctx = nullptr) {
+    assert(s_instance != nullptr);
+    return s_instance;
+  }
+
+  ImageSync() {
+    assert(s_instance == nullptr);
+    s_instance = this;
+  }
+
+  MOCK_METHOD0(start, void());
+};
+
+ImageSync<librbd::MockImageCtx>* ImageSync<librbd::MockImageCtx>::s_instance = nullptr;
+
+namespace image_replayer {
+
+template<>
+struct CloseImageRequest<librbd::MockImageCtx> {
+  static CloseImageRequest* s_instance;
+  Context *on_finish = nullptr;
+
+  static CloseImageRequest* create(librbd::MockImageCtx **image_ctx,
+                                   ContextWQ *work_queue, bool destroy_only,
+                                   Context *on_finish) {
+    assert(s_instance != nullptr);
+    s_instance->on_finish = on_finish;
+    return s_instance;
+  }
+
+  CloseImageRequest() {
+    assert(s_instance == nullptr);
+    s_instance = this;
+  }
+
+  MOCK_METHOD0(send, void());
+};
+
+template<>
+struct OpenLocalImageRequest<librbd::MockImageCtx> {
+  static OpenLocalImageRequest* s_instance;
+  Context *on_finish = nullptr;
+
+  static OpenLocalImageRequest* create(librados::IoCtx &local_io_ctx,
+                                       librbd::MockImageCtx **local_image_ctx,
+                                       const std::string &local_image_name,
+                                       const std::string &local_image_id,
+                                       ContextWQ *work_queue,
+                                       Context *on_finish) {
+    assert(s_instance != nullptr);
+    s_instance->on_finish = on_finish;
+    return s_instance;
+  }
+
+  OpenLocalImageRequest() {
+    assert(s_instance == nullptr);
+    s_instance = this;
+  }
+
+  MOCK_METHOD0(send, void());
+};
+
+CloseImageRequest<librbd::MockImageCtx>* CloseImageRequest<librbd::MockImageCtx>::s_instance = nullptr;
+OpenLocalImageRequest<librbd::MockImageCtx>* OpenLocalImageRequest<librbd::MockImageCtx>::s_instance = nullptr;
+
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
+
+// template definitions
+#include "tools/rbd_mirror/image_replayer/BootstrapRequest.cc"
+template class rbd::mirror::image_replayer::BootstrapRequest<librbd::MockImageCtx>;
+
+namespace rbd {
+namespace mirror {
+namespace image_replayer {
+
+class TestMockImageReplayerBootstrapRequest : public TestMockFixture {
+public:
+  typedef BootstrapRequest<librbd::MockImageCtx> MockBootstrapRequest;
+
+};
+
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
diff --git a/src/test/rbd_mirror/image_sync/test_mock_ImageCopyRequest.cc b/src/test/rbd_mirror/image_sync/test_mock_ImageCopyRequest.cc
index ff3584e..0b72167 100644
--- a/src/test/rbd_mirror/image_sync/test_mock_ImageCopyRequest.cc
+++ b/src/test/rbd_mirror/image_sync/test_mock_ImageCopyRequest.cc
@@ -6,14 +6,26 @@
 #include "librbd/ImageCtx.h"
 #include "librbd/ImageState.h"
 #include "librbd/Operations.h"
+#include "librbd/journal/TypeTraits.h"
+#include "test/journal/mock/MockJournaler.h"
 #include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
 #include "test/librbd/mock/MockImageCtx.h"
-#include "test/rbd_mirror/mock/MockJournaler.h"
 #include "tools/rbd_mirror/image_sync/ImageCopyRequest.h"
 #include "tools/rbd_mirror/image_sync/ObjectCopyRequest.h"
 #include "tools/rbd_mirror/Threads.h"
 #include <boost/scope_exit.hpp>
 
+namespace librbd {
+namespace journal {
+
+template <>
+struct TypeTraits<librbd::MockImageCtx> {
+  typedef ::journal::MockJournaler Journaler;
+};
+
+} // namespace journal
+} // namespace librbd
+
 namespace rbd {
 namespace mirror {
 namespace image_sync {
diff --git a/src/test/rbd_mirror/image_sync/test_mock_ObjectCopyRequest.cc b/src/test/rbd_mirror/image_sync/test_mock_ObjectCopyRequest.cc
index d99d7df..69c4c64 100644
--- a/src/test/rbd_mirror/image_sync/test_mock_ObjectCopyRequest.cc
+++ b/src/test/rbd_mirror/image_sync/test_mock_ObjectCopyRequest.cc
@@ -537,8 +537,6 @@ TEST_F(TestMockImageSyncObjectCopyRequest, Remove) {
   expect_remove(mock_local_io_ctx, 0);
   expect_update_object_map(mock_local_image_ctx, mock_object_map,
                            m_local_snap_ids[0], OBJECT_EXISTS, 0);
-  expect_update_object_map(mock_local_image_ctx, mock_object_map,
-                           m_local_snap_ids[1], OBJECT_NONEXISTENT, 0);
 
   request->send();
   ASSERT_EQ(0, ctx.wait());
diff --git a/src/test/rbd_mirror/image_sync/test_mock_SnapshotCopyRequest.cc b/src/test/rbd_mirror/image_sync/test_mock_SnapshotCopyRequest.cc
index 10c3ac3..b3e4a12 100644
--- a/src/test/rbd_mirror/image_sync/test_mock_SnapshotCopyRequest.cc
+++ b/src/test/rbd_mirror/image_sync/test_mock_SnapshotCopyRequest.cc
@@ -6,12 +6,55 @@
 #include "librbd/ImageCtx.h"
 #include "librbd/ImageState.h"
 #include "librbd/Operations.h"
+#include "librbd/journal/TypeTraits.h"
+#include "test/journal/mock/MockJournaler.h"
 #include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
 #include "test/librbd/mock/MockImageCtx.h"
-#include "test/rbd_mirror/mock/MockJournaler.h"
 #include "tools/rbd_mirror/image_sync/SnapshotCopyRequest.h"
+#include "tools/rbd_mirror/image_sync/SnapshotCreateRequest.h"
 #include "tools/rbd_mirror/Threads.h"
 
+namespace librbd {
+namespace journal {
+
+template <>
+struct TypeTraits<librbd::MockImageCtx> {
+  typedef ::journal::MockJournaler Journaler;
+};
+
+} // namespace journal
+} // namespace librbd
+
+namespace rbd {
+namespace mirror {
+namespace image_sync {
+
+template <>
+struct SnapshotCreateRequest<librbd::MockImageCtx> {
+  static SnapshotCreateRequest* s_instance;
+  static SnapshotCreateRequest* create(librbd::MockImageCtx* image_ctx,
+                                       const std::string &snap_name,
+                                       uint64_t size, Context *on_finish) {
+    assert(s_instance != nullptr);
+    s_instance->on_finish = on_finish;
+    return s_instance;
+  }
+
+  Context *on_finish = nullptr;
+
+  SnapshotCreateRequest() {
+    s_instance = this;
+  }
+
+  MOCK_METHOD0(send, void());
+};
+
+SnapshotCreateRequest<librbd::MockImageCtx>* SnapshotCreateRequest<librbd::MockImageCtx>::s_instance = nullptr;
+
+} // namespace image_sync
+} // namespace mirror
+} // namespace rbd
+
 // template definitions
 #include "tools/rbd_mirror/image_sync/SnapshotCopyRequest.cc"
 template class rbd::mirror::image_sync::SnapshotCopyRequest<librbd::MockImageCtx>;
@@ -27,12 +70,14 @@ using ::testing::InSequence;
 using ::testing::Invoke;
 using ::testing::InvokeWithoutArgs;
 using ::testing::Return;
+using ::testing::SetArgPointee;
 using ::testing::StrEq;
 using ::testing::WithArg;
 
 class TestMockImageSyncSnapshotCopyRequest : public TestMockFixture {
 public:
   typedef SnapshotCopyRequest<librbd::MockImageCtx> MockSnapshotCopyRequest;
+  typedef SnapshotCreateRequest<librbd::MockImageCtx> MockSnapshotCreateRequest;
 
   virtual void SetUp() {
     TestMockFixture::SetUp();
@@ -46,14 +91,15 @@ public:
   }
 
   void expect_snap_create(librbd::MockImageCtx &mock_image_ctx,
+                          MockSnapshotCreateRequest &mock_snapshot_create_request,
                           const std::string &snap_name, uint64_t snap_id, int r) {
-    EXPECT_CALL(*mock_image_ctx.operations, execute_snap_create(StrEq(snap_name), _, 0))
-                  .WillOnce(DoAll(InvokeWithoutArgs([&mock_image_ctx, snap_id, snap_name]() {
-                                    inject_snap(mock_image_ctx, snap_id, snap_name);
-                                  }),
-                                  WithArg<1>(Invoke([this, r](Context *ctx) {
-                                    m_threads->work_queue->queue(ctx, r);
-                                  }))));
+    EXPECT_CALL(mock_snapshot_create_request, send())
+      .WillOnce(DoAll(Invoke([&mock_image_ctx, snap_id, snap_name]() {
+                        inject_snap(mock_image_ctx, snap_id, snap_name);
+                      }),
+                      Invoke([this, &mock_snapshot_create_request, r]() {
+                        m_threads->work_queue->queue(mock_snapshot_create_request.on_finish, r);
+                      })));
   }
 
   void expect_snap_remove(librbd::MockImageCtx &mock_image_ctx,
@@ -64,6 +110,36 @@ public:
                             })));
   }
 
+  void expect_snap_protect(librbd::MockImageCtx &mock_image_ctx,
+                           const std::string &snap_name, int r) {
+    EXPECT_CALL(*mock_image_ctx.operations, execute_snap_protect(StrEq(snap_name), _))
+                  .WillOnce(WithArg<1>(Invoke([this, r](Context *ctx) {
+                              m_threads->work_queue->queue(ctx, r);
+                            })));
+  }
+
+  void expect_snap_unprotect(librbd::MockImageCtx &mock_image_ctx,
+                             const std::string &snap_name, int r) {
+    EXPECT_CALL(*mock_image_ctx.operations, execute_snap_unprotect(StrEq(snap_name), _))
+                  .WillOnce(WithArg<1>(Invoke([this, r](Context *ctx) {
+                              m_threads->work_queue->queue(ctx, r);
+                            })));
+  }
+
+  void expect_snap_is_protected(librbd::MockImageCtx &mock_image_ctx,
+                                uint64_t snap_id, bool is_protected, int r) {
+    EXPECT_CALL(mock_image_ctx, is_snap_protected(snap_id, _))
+                  .WillOnce(DoAll(SetArgPointee<1>(is_protected),
+                                  Return(r)));
+  }
+
+  void expect_snap_is_unprotected(librbd::MockImageCtx &mock_image_ctx,
+                                  uint64_t snap_id, bool is_unprotected, int r) {
+    EXPECT_CALL(mock_image_ctx, is_snap_unprotected(snap_id, _))
+                  .WillOnce(DoAll(SetArgPointee<1>(is_unprotected),
+                                  Return(r)));
+  }
+
   void expect_update_client(journal::MockJournaler &mock_journaler, int r) {
     EXPECT_CALL(mock_journaler, update_client(_, _))
                   .WillOnce(WithArg<1>(CompleteContext(r)));
@@ -84,12 +160,20 @@ public:
                                        on_finish);
   }
 
-  int create_snap(librbd::ImageCtx *image_ctx, const std::string &snap_name) {
+  int create_snap(librbd::ImageCtx *image_ctx, const std::string &snap_name,
+                  bool protect = false) {
     int r = image_ctx->operations->snap_create(snap_name.c_str());
     if (r < 0) {
       return r;
     }
 
+    if (protect) {
+      r = image_ctx->operations->snap_protect(snap_name.c_str());
+      if (r < 0) {
+        return r;
+      }
+    }
+
     r = image_ctx->state->refresh();
     if (r < 0) {
       return r;
@@ -151,13 +235,19 @@ TEST_F(TestMockImageSyncSnapshotCopyRequest, SnapCreate) {
   ASSERT_EQ(0, create_snap(m_remote_image_ctx, "snap1"));
   ASSERT_EQ(0, create_snap(m_remote_image_ctx, "snap2"));
 
+  uint64_t remote_snap_id1 = m_remote_image_ctx->snap_ids["snap1"];
+  uint64_t remote_snap_id2 = m_remote_image_ctx->snap_ids["snap2"];
+
   librbd::MockImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
   librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+  MockSnapshotCreateRequest mock_snapshot_create_request;
   journal::MockJournaler mock_journaler;
 
   InSequence seq;
-  expect_snap_create(mock_local_image_ctx, "snap1", 12, 0);
-  expect_snap_create(mock_local_image_ctx, "snap2", 14, 0);
+  expect_snap_create(mock_local_image_ctx, mock_snapshot_create_request, "snap1", 12, 0);
+  expect_snap_create(mock_local_image_ctx, mock_snapshot_create_request, "snap2", 14, 0);
+  expect_snap_is_protected(mock_remote_image_ctx, remote_snap_id1, false, 0);
+  expect_snap_is_protected(mock_remote_image_ctx, remote_snap_id2, false, 0);
   expect_update_client(mock_journaler, 0);
 
   C_SaferCond ctx;
@@ -167,10 +257,8 @@ TEST_F(TestMockImageSyncSnapshotCopyRequest, SnapCreate) {
   request->send();
   ASSERT_EQ(0, ctx.wait());
 
-  uint64_t snap_id1 = m_remote_image_ctx->snap_ids["snap1"];
-  uint64_t snap_id2 = m_remote_image_ctx->snap_ids["snap2"];
-  validate_snap_map({{snap_id1, {12}}, {snap_id2, {14, 12}}});
-  validate_snap_seqs({{snap_id1, 12}, {snap_id2, 14}});
+  validate_snap_map({{remote_snap_id1, {12}}, {remote_snap_id2, {14, 12}}});
+  validate_snap_seqs({{remote_snap_id1, 12}, {remote_snap_id2, 14}});
 }
 
 TEST_F(TestMockImageSyncSnapshotCopyRequest, SnapCreateError) {
@@ -178,10 +266,11 @@ TEST_F(TestMockImageSyncSnapshotCopyRequest, SnapCreateError) {
 
   librbd::MockImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
   librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+  MockSnapshotCreateRequest mock_snapshot_create_request;
   journal::MockJournaler mock_journaler;
 
   InSequence seq;
-  expect_snap_create(mock_local_image_ctx, "snap1", 12, -EINVAL);
+  expect_snap_create(mock_local_image_ctx, mock_snapshot_create_request, "snap1", 12, -EINVAL);
 
   C_SaferCond ctx;
   MockSnapshotCopyRequest *request = create_request(mock_remote_image_ctx,
@@ -191,17 +280,23 @@ TEST_F(TestMockImageSyncSnapshotCopyRequest, SnapCreateError) {
   ASSERT_EQ(-EINVAL, ctx.wait());
 }
 
-TEST_F(TestMockImageSyncSnapshotCopyRequest, SnapRemove) {
+TEST_F(TestMockImageSyncSnapshotCopyRequest, SnapRemoveAndCreate) {
   ASSERT_EQ(0, create_snap(m_remote_image_ctx, "snap1"));
   ASSERT_EQ(0, create_snap(m_local_image_ctx, "snap1"));
 
+  uint64_t remote_snap_id1 = m_remote_image_ctx->snap_ids["snap1"];
+
   librbd::MockImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
   librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+  MockSnapshotCreateRequest mock_snapshot_create_request;
   journal::MockJournaler mock_journaler;
 
   InSequence seq;
+  expect_snap_is_unprotected(mock_local_image_ctx,
+                             m_local_image_ctx->snap_ids["snap1"], true, 0);
   expect_snap_remove(mock_local_image_ctx, "snap1", 0);
-  expect_snap_create(mock_local_image_ctx, "snap1", 12, 0);
+  expect_snap_create(mock_local_image_ctx, mock_snapshot_create_request, "snap1", 12, 0);
+  expect_snap_is_protected(mock_remote_image_ctx, remote_snap_id1, false, 0);
   expect_update_client(mock_journaler, 0);
 
   C_SaferCond ctx;
@@ -211,9 +306,8 @@ TEST_F(TestMockImageSyncSnapshotCopyRequest, SnapRemove) {
   request->send();
   ASSERT_EQ(0, ctx.wait());
 
-  uint64_t snap_id1 = m_remote_image_ctx->snap_ids["snap1"];
-  validate_snap_map({{snap_id1, {12}}});
-  validate_snap_seqs({{snap_id1, 12}});
+  validate_snap_map({{remote_snap_id1, {12}}});
+  validate_snap_seqs({{remote_snap_id1, 12}});
 }
 
 TEST_F(TestMockImageSyncSnapshotCopyRequest, SnapRemoveError) {
@@ -224,6 +318,8 @@ TEST_F(TestMockImageSyncSnapshotCopyRequest, SnapRemoveError) {
   journal::MockJournaler mock_journaler;
 
   InSequence seq;
+  expect_snap_is_unprotected(mock_local_image_ctx,
+                             m_local_image_ctx->snap_ids["snap1"], true, 0);
   expect_snap_remove(mock_local_image_ctx, "snap1", -EINVAL);
 
   C_SaferCond ctx;
@@ -234,6 +330,176 @@ TEST_F(TestMockImageSyncSnapshotCopyRequest, SnapRemoveError) {
   ASSERT_EQ(-EINVAL, ctx.wait());
 }
 
+TEST_F(TestMockImageSyncSnapshotCopyRequest, SnapUnprotect) {
+  ASSERT_EQ(0, create_snap(m_remote_image_ctx, "snap1", true));
+  ASSERT_EQ(0, create_snap(m_local_image_ctx, "snap1", true));
+
+  uint64_t remote_snap_id1 = m_remote_image_ctx->snap_ids["snap1"];
+  uint64_t local_snap_id1 = m_local_image_ctx->snap_ids["snap1"];
+  m_client_meta.snap_seqs[remote_snap_id1] = local_snap_id1;
+
+  librbd::MockImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+  librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+  journal::MockJournaler mock_journaler;
+
+  InSequence seq;
+  expect_snap_is_unprotected(mock_local_image_ctx, local_snap_id1, false, 0);
+  expect_snap_is_unprotected(mock_remote_image_ctx, remote_snap_id1, true, 0);
+  expect_snap_unprotect(mock_local_image_ctx, "snap1", 0);
+  expect_snap_is_protected(mock_remote_image_ctx, remote_snap_id1, false, 0);
+  expect_update_client(mock_journaler, 0);
+
+  C_SaferCond ctx;
+  MockSnapshotCopyRequest *request = create_request(mock_remote_image_ctx,
+                                                    mock_local_image_ctx,
+                                                    mock_journaler, &ctx);
+  request->send();
+  ASSERT_EQ(0, ctx.wait());
+
+  validate_snap_map({{remote_snap_id1, {local_snap_id1}}});
+  validate_snap_seqs({{remote_snap_id1, local_snap_id1}});
+}
+
+TEST_F(TestMockImageSyncSnapshotCopyRequest, SnapUnprotectError) {
+  ASSERT_EQ(0, create_snap(m_remote_image_ctx, "snap1", true));
+  ASSERT_EQ(0, create_snap(m_local_image_ctx, "snap1", true));
+
+  uint64_t remote_snap_id1 = m_remote_image_ctx->snap_ids["snap1"];
+  uint64_t local_snap_id1 = m_local_image_ctx->snap_ids["snap1"];
+  m_client_meta.snap_seqs[remote_snap_id1] = local_snap_id1;
+
+  librbd::MockImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+  librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+  journal::MockJournaler mock_journaler;
+
+  InSequence seq;
+  expect_snap_is_unprotected(mock_local_image_ctx, local_snap_id1, false, 0);
+  expect_snap_is_unprotected(mock_remote_image_ctx, remote_snap_id1, true, 0);
+  expect_snap_unprotect(mock_local_image_ctx, "snap1", -EBUSY);
+
+  C_SaferCond ctx;
+  MockSnapshotCopyRequest *request = create_request(mock_remote_image_ctx,
+                                                    mock_local_image_ctx,
+                                                    mock_journaler, &ctx);
+  request->send();
+  ASSERT_EQ(-EBUSY, ctx.wait());
+}
+
+TEST_F(TestMockImageSyncSnapshotCopyRequest, SnapUnprotectRemove) {
+  ASSERT_EQ(0, create_snap(m_remote_image_ctx, "snap1", true));
+  ASSERT_EQ(0, create_snap(m_local_image_ctx, "snap1", true));
+
+  uint64_t remote_snap_id1 = m_remote_image_ctx->snap_ids["snap1"];
+
+  librbd::MockImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+  librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+  MockSnapshotCreateRequest mock_snapshot_create_request;
+  journal::MockJournaler mock_journaler;
+
+  InSequence seq;
+  expect_snap_is_unprotected(mock_local_image_ctx,
+                             m_local_image_ctx->snap_ids["snap1"], false, 0);
+  expect_snap_unprotect(mock_local_image_ctx, "snap1", 0);
+  expect_snap_remove(mock_local_image_ctx, "snap1", 0);
+  expect_snap_create(mock_local_image_ctx, mock_snapshot_create_request, "snap1", 12, 0);
+  expect_snap_is_protected(mock_remote_image_ctx, remote_snap_id1, false, 0);
+  expect_update_client(mock_journaler, 0);
+
+  C_SaferCond ctx;
+  MockSnapshotCopyRequest *request = create_request(mock_remote_image_ctx,
+                                                    mock_local_image_ctx,
+                                                    mock_journaler, &ctx);
+  request->send();
+  ASSERT_EQ(0, ctx.wait());
+
+  validate_snap_map({{remote_snap_id1, {12}}});
+  validate_snap_seqs({{remote_snap_id1, 12}});
+}
+
+TEST_F(TestMockImageSyncSnapshotCopyRequest, SnapCreateProtect) {
+  ASSERT_EQ(0, create_snap(m_remote_image_ctx, "snap1", true));
+
+  uint64_t remote_snap_id1 = m_remote_image_ctx->snap_ids["snap1"];
+
+  librbd::MockImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+  librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+  MockSnapshotCreateRequest mock_snapshot_create_request;
+  journal::MockJournaler mock_journaler;
+
+  InSequence seq;
+  expect_snap_create(mock_local_image_ctx, mock_snapshot_create_request, "snap1", 12, 0);
+  expect_snap_is_protected(mock_remote_image_ctx, remote_snap_id1, true, 0);
+  expect_snap_is_protected(mock_local_image_ctx, 12, false, 0);
+  expect_snap_protect(mock_local_image_ctx, "snap1", 0);
+  expect_update_client(mock_journaler, 0);
+
+  C_SaferCond ctx;
+  MockSnapshotCopyRequest *request = create_request(mock_remote_image_ctx,
+                                                    mock_local_image_ctx,
+                                                    mock_journaler, &ctx);
+  request->send();
+  ASSERT_EQ(0, ctx.wait());
+
+  validate_snap_map({{remote_snap_id1, {12}}});
+  validate_snap_seqs({{remote_snap_id1, 12}});
+}
+
+TEST_F(TestMockImageSyncSnapshotCopyRequest, SnapProtect) {
+  ASSERT_EQ(0, create_snap(m_remote_image_ctx, "snap1", true));
+  ASSERT_EQ(0, create_snap(m_local_image_ctx, "snap1", true));
+
+  uint64_t remote_snap_id1 = m_remote_image_ctx->snap_ids["snap1"];
+  uint64_t local_snap_id1 = m_local_image_ctx->snap_ids["snap1"];
+  m_client_meta.snap_seqs[remote_snap_id1] = local_snap_id1;
+
+  librbd::MockImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+  librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+  journal::MockJournaler mock_journaler;
+
+  InSequence seq;
+  expect_snap_is_unprotected(mock_local_image_ctx, local_snap_id1, true, 0);
+  expect_snap_is_protected(mock_remote_image_ctx, remote_snap_id1, true, 0);
+  expect_snap_is_protected(mock_local_image_ctx, local_snap_id1, false, 0);
+  expect_snap_protect(mock_local_image_ctx, "snap1", 0);
+  expect_update_client(mock_journaler, 0);
+
+  C_SaferCond ctx;
+  MockSnapshotCopyRequest *request = create_request(mock_remote_image_ctx,
+                                                    mock_local_image_ctx,
+                                                    mock_journaler, &ctx);
+  request->send();
+  ASSERT_EQ(0, ctx.wait());
+
+  validate_snap_map({{remote_snap_id1, {local_snap_id1}}});
+  validate_snap_seqs({{remote_snap_id1, local_snap_id1}});
+}
+
+TEST_F(TestMockImageSyncSnapshotCopyRequest, SnapProtectError) {
+  ASSERT_EQ(0, create_snap(m_remote_image_ctx, "snap1", true));
+  ASSERT_EQ(0, create_snap(m_local_image_ctx, "snap1", true));
+
+  uint64_t remote_snap_id1 = m_remote_image_ctx->snap_ids["snap1"];
+  uint64_t local_snap_id1 = m_local_image_ctx->snap_ids["snap1"];
+  m_client_meta.snap_seqs[remote_snap_id1] = local_snap_id1;
+
+  librbd::MockImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+  librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+  journal::MockJournaler mock_journaler;
+
+  InSequence seq;
+  expect_snap_is_unprotected(mock_local_image_ctx, local_snap_id1, true, 0);
+  expect_snap_is_protected(mock_remote_image_ctx, remote_snap_id1, true, 0);
+  expect_snap_is_protected(mock_local_image_ctx, local_snap_id1, false, 0);
+  expect_snap_protect(mock_local_image_ctx, "snap1", -EINVAL);
+
+  C_SaferCond ctx;
+  MockSnapshotCopyRequest *request = create_request(mock_remote_image_ctx,
+                                                    mock_local_image_ctx,
+                                                    mock_journaler, &ctx);
+  request->send();
+  ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
 } // namespace image_sync
 } // namespace mirror
 } // namespace rbd
diff --git a/src/test/rbd_mirror/image_sync/test_mock_SnapshotCreateRequest.cc b/src/test/rbd_mirror/image_sync/test_mock_SnapshotCreateRequest.cc
new file mode 100644
index 0000000..dd8305d
--- /dev/null
+++ b/src/test/rbd_mirror/image_sync/test_mock_SnapshotCreateRequest.cc
@@ -0,0 +1,189 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/rbd_mirror/test_mock_fixture.h"
+#include "test/librados_test_stub/LibradosTestStub.h"
+#include "include/rbd/librbd.hpp"
+#include "librbd/ImageCtx.h"
+#include "librbd/ImageState.h"
+#include "librbd/Operations.h"
+#include "osdc/Striper.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "tools/rbd_mirror/image_sync/SnapshotCreateRequest.h"
+#include "tools/rbd_mirror/Threads.h"
+
+// template definitions
+#include "tools/rbd_mirror/image_sync/SnapshotCreateRequest.cc"
+template class rbd::mirror::image_sync::SnapshotCreateRequest<librbd::MockImageCtx>;
+
+namespace rbd {
+namespace mirror {
+namespace image_sync {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::InvokeWithoutArgs;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::WithArg;
+
+class TestMockImageSyncSnapshotCreateRequest : public TestMockFixture {
+public:
+  typedef SnapshotCreateRequest<librbd::MockImageCtx> MockSnapshotCreateRequest;
+
+  virtual void SetUp() {
+    TestMockFixture::SetUp();
+
+    librbd::RBD rbd;
+    ASSERT_EQ(0, create_image(rbd, m_local_io_ctx, m_image_name, m_image_size));
+    ASSERT_EQ(0, open_image(m_local_io_ctx, m_image_name, &m_local_image_ctx));
+  }
+
+  void expect_test_features(librbd::MockImageCtx &mock_image_ctx,
+                            uint64_t features, bool enabled) {
+    EXPECT_CALL(mock_image_ctx, test_features(features))
+                  .WillOnce(Return(enabled));
+  }
+
+  void expect_set_size(librbd::MockImageCtx &mock_image_ctx, int r) {
+    EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+                exec(mock_image_ctx.header_oid, _, StrEq("rbd"), StrEq("set_size"), _, _, _))
+                  .WillOnce(Return(r));
+  }
+
+  void expect_snap_create(librbd::MockImageCtx &mock_image_ctx,
+                          const std::string &snap_name, uint64_t snap_id, int r) {
+    EXPECT_CALL(*mock_image_ctx.operations, execute_snap_create(StrEq(snap_name), _, 0, true))
+                  .WillOnce(DoAll(InvokeWithoutArgs([&mock_image_ctx, snap_id, snap_name]() {
+                                    inject_snap(mock_image_ctx, snap_id, snap_name);
+                                  }),
+                                  WithArg<1>(Invoke([this, r](Context *ctx) {
+                                    m_threads->work_queue->queue(ctx, r);
+                                  }))));
+  }
+
+  void expect_object_map_resize(librbd::MockImageCtx &mock_image_ctx,
+                                librados::snap_t snap_id, int r) {
+    std::string oid(librbd::ObjectMap::object_map_name(mock_image_ctx.id,
+                                                       snap_id));
+    EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+                exec(oid, _, StrEq("rbd"), StrEq("object_map_resize"), _, _, _))
+                  .WillOnce(Return(r));
+  }
+
+  static void inject_snap(librbd::MockImageCtx &mock_image_ctx,
+                   uint64_t snap_id, const std::string &snap_name) {
+    mock_image_ctx.snap_ids[snap_name] = snap_id;
+  }
+
+  MockSnapshotCreateRequest *create_request(librbd::MockImageCtx &mock_local_image_ctx,
+                                            const std::string &snap_name,
+                                            uint64_t size, Context *on_finish) {
+    return new MockSnapshotCreateRequest(&mock_local_image_ctx, snap_name, size,
+                                         on_finish);
+  }
+
+  librbd::ImageCtx *m_local_image_ctx;
+};
+
+TEST_F(TestMockImageSyncSnapshotCreateRequest, Resize) {
+  librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+
+  InSequence seq;
+  expect_set_size(mock_local_image_ctx, 0);
+  expect_snap_create(mock_local_image_ctx, "snap1", 10, 0);
+  expect_test_features(mock_local_image_ctx, RBD_FEATURE_OBJECT_MAP, false);
+
+  C_SaferCond ctx;
+  MockSnapshotCreateRequest *request = create_request(mock_local_image_ctx,
+                                                      "snap1", 123,
+                                                      &ctx);
+  request->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageSyncSnapshotCreateRequest, ResizeError) {
+  librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+
+  InSequence seq;
+  expect_set_size(mock_local_image_ctx, -EINVAL);
+
+  C_SaferCond ctx;
+  MockSnapshotCreateRequest *request = create_request(mock_local_image_ctx,
+                                                      "snap1", 123,
+                                                      &ctx);
+  request->send();
+  ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageSyncSnapshotCreateRequest, SnapCreate) {
+  librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+
+  InSequence seq;
+  expect_snap_create(mock_local_image_ctx, "snap1", 10, 0);
+  expect_test_features(mock_local_image_ctx, RBD_FEATURE_OBJECT_MAP, false);
+
+  C_SaferCond ctx;
+  MockSnapshotCreateRequest *request = create_request(mock_local_image_ctx,
+                                                      "snap1",
+                                                      m_local_image_ctx->size,
+                                                      &ctx);
+  request->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageSyncSnapshotCreateRequest, SnapCreateError) {
+  librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+
+  InSequence seq;
+  expect_snap_create(mock_local_image_ctx, "snap1", 10, -EINVAL);
+
+  C_SaferCond ctx;
+  MockSnapshotCreateRequest *request = create_request(mock_local_image_ctx,
+                                                      "snap1",
+                                                      m_local_image_ctx->size,
+                                                      &ctx);
+  request->send();
+  ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageSyncSnapshotCreateRequest, ResizeObjectMap) {
+  librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+
+  InSequence seq;
+  expect_snap_create(mock_local_image_ctx, "snap1", 10, 0);
+  expect_test_features(mock_local_image_ctx, RBD_FEATURE_OBJECT_MAP, true);
+  expect_object_map_resize(mock_local_image_ctx, 10, 0);
+
+  C_SaferCond ctx;
+  MockSnapshotCreateRequest *request = create_request(mock_local_image_ctx,
+                                                      "snap1",
+                                                      m_local_image_ctx->size,
+                                                      &ctx);
+  request->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageSyncSnapshotCreateRequest, ResizeObjectMapError) {
+  librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+
+  InSequence seq;
+  expect_snap_create(mock_local_image_ctx, "snap1", 10, 0);
+  expect_test_features(mock_local_image_ctx, RBD_FEATURE_OBJECT_MAP, true);
+  expect_object_map_resize(mock_local_image_ctx, 10, -EINVAL);
+
+  C_SaferCond ctx;
+  MockSnapshotCreateRequest *request = create_request(mock_local_image_ctx,
+                                                      "snap1",
+                                                      m_local_image_ctx->size,
+                                                      &ctx);
+  request->send();
+  ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+} // namespace image_sync
+} // namespace mirror
+} // namespace rbd
diff --git a/src/test/rbd_mirror/image_sync/test_mock_SyncPointCreateRequest.cc b/src/test/rbd_mirror/image_sync/test_mock_SyncPointCreateRequest.cc
index e983402..75c6479 100644
--- a/src/test/rbd_mirror/image_sync/test_mock_SyncPointCreateRequest.cc
+++ b/src/test/rbd_mirror/image_sync/test_mock_SyncPointCreateRequest.cc
@@ -4,11 +4,23 @@
 #include "test/rbd_mirror/test_mock_fixture.h"
 #include "include/rbd/librbd.hpp"
 #include "librbd/journal/Types.h"
+#include "librbd/journal/TypeTraits.h"
+#include "test/journal/mock/MockJournaler.h"
 #include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
 #include "test/librbd/mock/MockImageCtx.h"
-#include "test/rbd_mirror/mock/MockJournaler.h"
 #include "tools/rbd_mirror/image_sync/SyncPointCreateRequest.h"
 
+namespace librbd {
+namespace journal {
+
+template <>
+struct TypeTraits<librbd::MockImageCtx> {
+  typedef ::journal::MockJournaler Journaler;
+};
+
+} // namespace journal
+} // namespace librbd
+
 // template definitions
 #include "tools/rbd_mirror/image_sync/SyncPointCreateRequest.cc"
 template class rbd::mirror::image_sync::SyncPointCreateRequest<librbd::MockImageCtx>;
diff --git a/src/test/rbd_mirror/image_sync/test_mock_SyncPointPruneRequest.cc b/src/test/rbd_mirror/image_sync/test_mock_SyncPointPruneRequest.cc
index e45bf78..7025457 100644
--- a/src/test/rbd_mirror/image_sync/test_mock_SyncPointPruneRequest.cc
+++ b/src/test/rbd_mirror/image_sync/test_mock_SyncPointPruneRequest.cc
@@ -4,11 +4,23 @@
 #include "test/rbd_mirror/test_mock_fixture.h"
 #include "include/rbd/librbd.hpp"
 #include "librbd/journal/Types.h"
+#include "librbd/journal/TypeTraits.h"
+#include "test/journal/mock/MockJournaler.h"
 #include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
 #include "test/librbd/mock/MockImageCtx.h"
-#include "test/rbd_mirror/mock/MockJournaler.h"
 #include "tools/rbd_mirror/image_sync/SyncPointPruneRequest.h"
 
+namespace librbd {
+namespace journal {
+
+template <>
+struct TypeTraits<librbd::MockImageCtx> {
+  typedef ::journal::MockJournaler Journaler;
+};
+
+} // namespace journal
+} // namespace librbd
+
 // template definitions
 #include "tools/rbd_mirror/image_sync/SyncPointPruneRequest.cc"
 template class rbd::mirror::image_sync::SyncPointPruneRequest<librbd::MockImageCtx>;
diff --git a/src/test/rbd_mirror/mock/MockJournaler.h b/src/test/rbd_mirror/mock/MockJournaler.h
deleted file mode 100644
index 5f08c12..0000000
--- a/src/test/rbd_mirror/mock/MockJournaler.h
+++ /dev/null
@@ -1,149 +0,0 @@
-// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
-// vim: ts=8 sw=2 smarttab
-
-#ifndef TEST_RBD_MIRROR_MOCK_JOURNALER_H
-#define TEST_RBD_MIRROR_MOCK_JOURNALER_H
-
-#include <gmock/gmock.h>
-#include "include/int_types.h"
-#include "include/rados/librados.hpp"
-#include "cls/journal/cls_journal_types.h"
-#include "librbd/Journal.h"
-#include "librbd/journal/TypeTraits.h"
-#include <iosfwd>
-#include <string>
-
-class Context;
-class ContextWQ;
-class Mutex;
-class SafeTimer;
-
-namespace journal {
-
-struct ReplayHandler;
-
-struct MockReplayEntry {
-  static MockReplayEntry *s_instance;
-  static MockReplayEntry &get_instance() {
-    assert(s_instance != nullptr);
-    return *s_instance;
-  }
-
-  MockReplayEntry() {
-    s_instance = this;
-  }
-
-  MOCK_CONST_METHOD0(get_commit_tid, uint64_t());
-  MOCK_METHOD0(get_data, bufferlist());
-};
-
-struct MockReplayEntryProxy {
-  uint64_t get_commit_tid() const {
-    return MockReplayEntry::get_instance().get_commit_tid();
-  }
-
-  bufferlist get_data() {
-    return MockReplayEntry::get_instance().get_data();
-  }
-};
-
-struct MockJournaler {
-  static MockJournaler *s_instance;
-  static MockJournaler &get_instance() {
-    assert(s_instance != nullptr);
-    return *s_instance;
-  }
-
-  MockJournaler() {
-    s_instance = this;
-  }
-
-  MOCK_METHOD1(init, void(Context *));
-  MOCK_METHOD0(shut_down, void());
-  MOCK_CONST_METHOD0(is_initialized, bool());
-
-  MOCK_METHOD4(get_mutable_metadata, void(uint64_t*, uint64_t*,
-                                          std::set<cls::journal::Client> *,
-                                          Context*));
-
-  MOCK_METHOD2(try_pop_front, bool(MockReplayEntryProxy *, uint64_t *));
-  MOCK_METHOD2(start_live_replay, void(ReplayHandler *, double));
-  MOCK_METHOD0(stop_replay, void());
-
-  MOCK_METHOD1(committed, void(const MockReplayEntryProxy &));
-  MOCK_METHOD1(flush_commit_position, void(Context*));
-
-  MOCK_METHOD2(update_client, void(const bufferlist&, Context *on_safe));
-
-  MOCK_METHOD3(get_tag, void(uint64_t, cls::journal::Tag *, Context *));
-};
-
-struct MockJournalerProxy {
-  MockJournalerProxy(ContextWQ *work_queue, SafeTimer *timer, Mutex *timer_lock,
-                     librados::IoCtx &header_ioctx, const std::string &journal_id,
-                     const std::string &client_id, double commit_interval) {
-    MockJournaler::get_instance();
-  }
-
-  void init(Context *on_finish) {
-    MockJournaler::get_instance().init(on_finish);
-  }
-  void shut_down() {
-    MockJournaler::get_instance().shut_down();
-  }
-  bool is_initialized() const {
-    return MockJournaler::get_instance().is_initialized();
-  }
-
-  void get_mutable_metadata(uint64_t *min, uint64_t *active,
-                            std::set<cls::journal::Client> *clients,
-                            Context *on_finish) {
-    MockJournaler::get_instance().get_mutable_metadata(min, active, clients,
-                                                       on_finish);
-  }
-
-  bool try_pop_front(MockReplayEntryProxy *entry, uint64_t *tag_tid) {
-    return MockJournaler::get_instance().try_pop_front(entry, tag_tid);
-  }
-  void start_live_replay(ReplayHandler *handler, double interval) {
-    MockJournaler::get_instance().start_live_replay(handler, interval);
-  }
-  void stop_replay() {
-    MockJournaler::get_instance().stop_replay();
-  }
-
-  void committed(const MockReplayEntryProxy &entry) {
-    MockJournaler::get_instance().committed(entry);
-  }
-  void flush_commit_position(Context *on_finish) {
-    MockJournaler::get_instance().flush_commit_position(on_finish);
-  }
-
-  void update_client(const bufferlist& data, Context *on_safe) {
-    MockJournaler::get_instance().update_client(data, on_safe);
-  }
-
-  void get_tag(uint64_t tag_tid, cls::journal::Tag *tag, Context *on_finish) {
-    MockJournaler::get_instance().get_tag(tag_tid, tag, on_finish);
-  }
-};
-
-std::ostream &operator<<(std::ostream &os, const MockJournalerProxy &);
-
-} // namespace journal
-
-namespace librbd {
-
-struct MockImageCtx;
-
-namespace journal {
-
-template <>
-struct TypeTraits<MockImageCtx> {
-  typedef ::journal::MockJournaler Journaler;
-};
-
-} // namespace journal
-} // namespace librbd
-
-#endif // TEST_RBD_MIRROR_MOCK_JOURNALER_H
diff --git a/src/test/rbd_mirror/test_ImageSync.cc b/src/test/rbd_mirror/test_ImageSync.cc
index 5e6b0a9..5b80f97 100644
--- a/src/test/rbd_mirror/test_ImageSync.cc
+++ b/src/test/rbd_mirror/test_ImageSync.cc
@@ -2,12 +2,15 @@
 // vim: ts=8 sw=2 smarttab
 
 #include "test/rbd_mirror/test_fixture.h"
+#include "include/stringify.h"
 #include "include/rbd/librbd.hpp"
 #include "journal/Journaler.h"
 #include "librbd/AioImageRequestWQ.h"
 #include "librbd/ExclusiveLock.h"
 #include "librbd/ImageCtx.h"
 #include "librbd/ImageState.h"
+#include "librbd/internal.h"
+#include "librbd/Operations.h"
 #include "librbd/journal/Types.h"
 #include "tools/rbd_mirror/ImageSync.h"
 #include "tools/rbd_mirror/Threads.h"
@@ -22,6 +25,7 @@ namespace {
 
 void scribble(librbd::ImageCtx *image_ctx, int num_ops, size_t max_size)
 {
+  max_size = MIN(image_ctx->size, max_size);
   for (int i=0; i<num_ops; i++) {
     uint64_t off = rand() % (image_ctx->size - max_size + 1);
     uint64_t len = 1 + rand() % max_size;
@@ -34,6 +38,9 @@ void scribble(librbd::ImageCtx *image_ctx, int num_ops, size_t max_size)
                                                            str.c_str(), 0));
     }
   }
+
+  RWLock::RLocker owner_locker(image_ctx->owner_lock);
+  ASSERT_EQ(0, image_ctx->flush());
 }
 
 } // anonymous namespace
@@ -105,10 +112,6 @@ TEST_F(TestImageSync, Empty) {
 
 TEST_F(TestImageSync, Simple) {
   scribble(m_remote_image_ctx, 10, 102400);
-  {
-    RWLock::RLocker owner_locker(m_remote_image_ctx->owner_lock);
-    ASSERT_EQ(0, m_remote_image_ctx->flush());
-  }
 
   C_SaferCond ctx;
   ImageSync<> *request = create_request(&ctx);
@@ -132,5 +135,76 @@ TEST_F(TestImageSync, Simple) {
   }
 }
 
+TEST_F(TestImageSync, SnapshotStress) {
+  std::list<std::string> snap_names;
+
+  const int num_snaps = 4;
+  for (int idx = 0; idx <= num_snaps; ++idx) {
+    scribble(m_remote_image_ctx, 10, 102400);
+
+    librbd::NoOpProgressContext no_op_progress_ctx;
+    uint64_t size = 1 + rand() % m_image_size;
+    ASSERT_EQ(0, m_remote_image_ctx->operations->resize(size,
+                                                        no_op_progress_ctx));
+    ASSERT_EQ(0, m_remote_image_ctx->state->refresh());
+
+    if (idx < num_snaps) {
+      snap_names.push_back("snap" + stringify(idx + 1));
+      ASSERT_EQ(0, create_snap(m_remote_image_ctx, snap_names.back().c_str(),
+                               nullptr));
+    } else {
+      snap_names.push_back("");
+    }
+  }
+
+  C_SaferCond ctx;
+  ImageSync<> *request = create_request(&ctx);
+  request->start();
+  ASSERT_EQ(0, ctx.wait());
+
+  int64_t object_size = std::min<int64_t>(
+    m_remote_image_ctx->size, 1 << m_remote_image_ctx->order);
+  bufferlist read_remote_bl;
+  read_remote_bl.append(std::string(object_size, '1'));
+  bufferlist read_local_bl;
+  read_local_bl.append(std::string(object_size, '1'));
+
+  for (auto &snap_name : snap_names) {
+    uint64_t remote_size;
+    {
+      C_SaferCond ctx;
+      m_remote_image_ctx->state->snap_set(snap_name, &ctx);
+      ASSERT_EQ(0, ctx.wait());
+
+      RWLock::RLocker remote_snap_locker(m_remote_image_ctx->snap_lock);
+      remote_size = m_remote_image_ctx->get_image_size(
+        m_remote_image_ctx->snap_id);
+    }
+
+    uint64_t local_size;
+    {
+      C_SaferCond ctx;
+      m_local_image_ctx->state->snap_set(snap_name, &ctx);
+      ASSERT_EQ(0, ctx.wait());
+
+      RWLock::RLocker snap_locker(m_local_image_ctx->snap_lock);
+      local_size = m_local_image_ctx->get_image_size(
+        m_local_image_ctx->snap_id);
+      ASSERT_FALSE(m_local_image_ctx->test_flags(RBD_FLAG_OBJECT_MAP_INVALID,
+                                                 m_local_image_ctx->snap_lock));
+    }
+
+    ASSERT_EQ(remote_size, local_size);
+
+    for (uint64_t offset = 0; offset < remote_size; offset += object_size) {
+      ASSERT_LE(0, m_remote_image_ctx->aio_work_queue->read(
+                     offset, object_size, read_remote_bl.c_str(), 0));
+      ASSERT_LE(0, m_local_image_ctx->aio_work_queue->read(
+                     offset, object_size, read_local_bl.c_str(), 0));
+      ASSERT_TRUE(read_remote_bl.contents_equal(read_local_bl));
+    }
+  }
+}
+
 } // namespace mirror
 } // namespace rbd
diff --git a/src/test/rbd_mirror/test_fixture.cc b/src/test/rbd_mirror/test_fixture.cc
index 34981ea..7ec0ab3 100644
--- a/src/test/rbd_mirror/test_fixture.cc
+++ b/src/test/rbd_mirror/test_fixture.cc
@@ -38,6 +38,14 @@ void TestFixture::TearDownTestCase() {
 }
 
 void TestFixture::SetUp() {
+  static bool seeded = false;
+  if (!seeded) {
+    seeded = true;
+    int seed = getpid();
+    cout << "seed " << seed << std::endl;
+    srand(seed);
+  }
+
   ASSERT_EQ(0, _rados.ioctx_create(_local_pool_name.c_str(), m_local_io_ctx));
   ASSERT_EQ(0, _rados.ioctx_create(_remote_pool_name.c_str(), m_remote_io_ctx));
   m_image_name = get_temp_image_name();
diff --git a/src/test/rbd_mirror/test_mock_ImageReplayer.cc b/src/test/rbd_mirror/test_mock_ImageReplayer.cc
index ac4e8bc..4ec594f 100644
--- a/src/test/rbd_mirror/test_mock_ImageReplayer.cc
+++ b/src/test/rbd_mirror/test_mock_ImageReplayer.cc
@@ -6,10 +6,9 @@
 #include "tools/rbd_mirror/ImageReplayer.h"
 #include "tools/rbd_mirror/image_replayer/BootstrapRequest.h"
 #include "tools/rbd_mirror/image_replayer/CloseImageRequest.h"
-#include "tools/rbd_mirror/image_replayer/OpenLocalImageRequest.h"
+#include "test/journal/mock/MockJournaler.h"
 #include "test/librbd/mock/MockImageCtx.h"
 #include "test/librbd/mock/MockJournal.h"
-#include "test/rbd_mirror/mock/MockJournaler.h"
 
 namespace librbd {
 
@@ -65,7 +64,8 @@ struct BootstrapRequest<librbd::MockImageReplayerImageCtx> {
                                   const std::string &remote_mirror_uuid,
                                   ::journal::MockJournalerProxy *journaler,
                                   librbd::journal::MirrorPeerClientMeta *client_meta,
-                                  Context *on_finish) {
+                                  Context *on_finish,
+                                  rbd::mirror::ProgressContext *progress_ctx = nullptr) {
     assert(s_instance != nullptr);
     s_instance->on_finish = on_finish;
     return s_instance;
@@ -101,32 +101,26 @@ struct CloseImageRequest<librbd::MockImageReplayerImageCtx> {
 };
 
 template<>
-struct OpenLocalImageRequest<librbd::MockImageReplayerImageCtx> {
-  static OpenLocalImageRequest* s_instance;
-  Context *on_finish = nullptr;
+struct ReplayStatusFormatter<librbd::MockImageReplayerImageCtx> {
+  static ReplayStatusFormatter* s_instance;
 
-  static OpenLocalImageRequest* create(librados::IoCtx &local_io_ctx,
-                                       librbd::MockImageReplayerImageCtx **local_image_ctx,
-                                       const std::string &local_image_name,
-                                       const std::string &local_image_id,
-                                       ContextWQ *work_queue,
-                                       Context *on_finish) {
+  static ReplayStatusFormatter* create(::journal::MockJournalerProxy *journaler,
+				       const std::string &mirror_uuid) {
     assert(s_instance != nullptr);
-    s_instance->on_finish = on_finish;
     return s_instance;
   }
 
-  OpenLocalImageRequest() {
+  ReplayStatusFormatter() {
     assert(s_instance == nullptr);
     s_instance = this;
   }
 
-  MOCK_METHOD0(send, void());
+  MOCK_METHOD2(get_or_send_update, bool(std::string *description, Context *on_finish));
 };
 
 BootstrapRequest<librbd::MockImageReplayerImageCtx>* BootstrapRequest<librbd::MockImageReplayerImageCtx>::s_instance = nullptr;
 CloseImageRequest<librbd::MockImageReplayerImageCtx>* CloseImageRequest<librbd::MockImageReplayerImageCtx>::s_instance = nullptr;
-OpenLocalImageRequest<librbd::MockImageReplayerImageCtx>* OpenLocalImageRequest<librbd::MockImageReplayerImageCtx>::s_instance = nullptr;
+ReplayStatusFormatter<librbd::MockImageReplayerImageCtx>* ReplayStatusFormatter<librbd::MockImageReplayerImageCtx>::s_instance = nullptr;
 
 } // namespace image_replayer
 } // namespace mirror
diff --git a/src/test/rbd_mirror/test_mock_ImageSync.cc b/src/test/rbd_mirror/test_mock_ImageSync.cc
index 726fa95..f3c94ba 100644
--- a/src/test/rbd_mirror/test_mock_ImageSync.cc
+++ b/src/test/rbd_mirror/test_mock_ImageSync.cc
@@ -4,10 +4,11 @@
 #include "test/rbd_mirror/test_mock_fixture.h"
 #include "include/rbd/librbd.hpp"
 #include "librbd/journal/Types.h"
+#include "librbd/journal/TypeTraits.h"
+#include "test/journal/mock/MockJournaler.h"
 #include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
 #include "test/librbd/mock/MockImageCtx.h"
 #include "test/librbd/mock/MockObjectMap.h"
-#include "test/rbd_mirror/mock/MockJournaler.h"
 #include "tools/rbd_mirror/ImageSync.h"
 #include "tools/rbd_mirror/Threads.h"
 #include "tools/rbd_mirror/image_sync/ImageCopyRequest.h"
@@ -15,6 +16,17 @@
 #include "tools/rbd_mirror/image_sync/SyncPointCreateRequest.h"
 #include "tools/rbd_mirror/image_sync/SyncPointPruneRequest.h"
 
+namespace librbd {
+namespace journal {
+
+template <>
+struct TypeTraits<librbd::MockImageCtx> {
+  typedef ::journal::MockJournaler Journaler;
+};
+
+} // namespace journal
+} // namespace librbd
+
 // template definitions
 #include "tools/rbd_mirror/ImageSync.cc"
 template class rbd::mirror::ImageSync<librbd::MockImageCtx>;
@@ -36,7 +48,8 @@ public:
                                   journal::MockJournaler *journaler,
                                   librbd::journal::MirrorPeerClientMeta *client_meta,
                                   librbd::journal::MirrorPeerSyncPoint *sync_point,
-                                  Context *on_finish) {
+                                  Context *on_finish,
+                                  rbd::mirror::ProgressContext *progress_ctx = nullptr) {
     assert(s_instance != nullptr);
     s_instance->on_finish = on_finish;
     return s_instance;
diff --git a/src/test/ubuntu-12.04/install-deps.sh b/src/test/ubuntu-12.04/install-deps.sh
index 8249ea3..21e71ee 100755
--- a/src/test/ubuntu-12.04/install-deps.sh
+++ b/src/test/ubuntu-12.04/install-deps.sh
@@ -66,7 +66,7 @@ CentOS|Fedora|RedHatEnterpriseServer)
                     $SUDO yum install subscription-manager
                     $SUDO subscription-manager repos --enable=rhel-$MAJOR_VERSION-server-optional-rpms
                 fi
-                $SUDO yum-config-manager --add-repo https://dl.fedoraproject.org/pub/epel/$MAJOR_VERSION/x86_64/ 
+                $SUDO yum-config-manager --add-repo https://dl.fedoraproject.org/pub/epel/$MAJOR_VERSION/x86_64/
                 $SUDO yum install --nogpgcheck -y epel-release
                 $SUDO rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-$MAJOR_VERSION
                 $SUDO rm -f /etc/yum.repos.d/dl.fedoraproject.org*
@@ -92,8 +92,6 @@ function populate_wheelhouse() {
     local install=$1
     shift
 
-    # Ubuntu-12.04 and Python 2.7.3 require this line
-    pip --timeout 300 $install 'distribute >= 0.7.3' || return 1
     # although pip comes with virtualenv, having a recent version
     # of pip matters when it comes to using wheel packages
     pip --timeout 300 $install 'setuptools >= 0.8' 'pip >= 7.0' 'wheel >= 0.24' || return 1
diff --git a/src/test/ubuntu-14.04/install-deps.sh b/src/test/ubuntu-14.04/install-deps.sh
index 8249ea3..21e71ee 100755
--- a/src/test/ubuntu-14.04/install-deps.sh
+++ b/src/test/ubuntu-14.04/install-deps.sh
@@ -66,7 +66,7 @@ CentOS|Fedora|RedHatEnterpriseServer)
                     $SUDO yum install subscription-manager
                     $SUDO subscription-manager repos --enable=rhel-$MAJOR_VERSION-server-optional-rpms
                 fi
-                $SUDO yum-config-manager --add-repo https://dl.fedoraproject.org/pub/epel/$MAJOR_VERSION/x86_64/ 
+                $SUDO yum-config-manager --add-repo https://dl.fedoraproject.org/pub/epel/$MAJOR_VERSION/x86_64/
                 $SUDO yum install --nogpgcheck -y epel-release
                 $SUDO rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-$MAJOR_VERSION
                 $SUDO rm -f /etc/yum.repos.d/dl.fedoraproject.org*
@@ -92,8 +92,6 @@ function populate_wheelhouse() {
     local install=$1
     shift
 
-    # Ubuntu-12.04 and Python 2.7.3 require this line
-    pip --timeout 300 $install 'distribute >= 0.7.3' || return 1
     # although pip comes with virtualenv, having a recent version
     # of pip matters when it comes to using wheel packages
     pip --timeout 300 $install 'setuptools >= 0.8' 'pip >= 7.0' 'wheel >= 0.24' || return 1
diff --git a/src/tools/Makefile-client.am b/src/tools/Makefile-client.am
index 0b81549..7b80aac 100644
--- a/src/tools/Makefile-client.am
+++ b/src/tools/Makefile-client.am
@@ -100,9 +100,11 @@ librbd_mirror_internal_la_SOURCES = \
 	tools/rbd_mirror/image_replayer/BootstrapRequest.cc \
 	tools/rbd_mirror/image_replayer/CloseImageRequest.cc \
 	tools/rbd_mirror/image_replayer/OpenLocalImageRequest.cc \
+	tools/rbd_mirror/image_replayer/ReplayStatusFormatter.cc \
 	tools/rbd_mirror/image_sync/ImageCopyRequest.cc \
 	tools/rbd_mirror/image_sync/ObjectCopyRequest.cc \
 	tools/rbd_mirror/image_sync/SnapshotCopyRequest.cc \
+	tools/rbd_mirror/image_sync/SnapshotCreateRequest.cc \
 	tools/rbd_mirror/image_sync/SyncPointCreateRequest.cc \
 	tools/rbd_mirror/image_sync/SyncPointPruneRequest.cc
 noinst_LTLIBRARIES += librbd_mirror_internal.la
@@ -112,15 +114,18 @@ noinst_HEADERS += \
 	tools/rbd_mirror/ImageSync.h \
 	tools/rbd_mirror/Mirror.h \
 	tools/rbd_mirror/PoolWatcher.h \
+	tools/rbd_mirror/ProgressContext.h \
 	tools/rbd_mirror/Replayer.h \
 	tools/rbd_mirror/Threads.h \
 	tools/rbd_mirror/types.h \
 	tools/rbd_mirror/image_replayer/BootstrapRequest.h \
 	tools/rbd_mirror/image_replayer/CloseImageRequest.h \
 	tools/rbd_mirror/image_replayer/OpenLocalImageRequest.h \
+	tools/rbd_mirror/image_replayer/ReplayStatusFormatter.h \
 	tools/rbd_mirror/image_sync/ImageCopyRequest.h \
 	tools/rbd_mirror/image_sync/ObjectCopyRequest.h \
 	tools/rbd_mirror/image_sync/SnapshotCopyRequest.h \
+	tools/rbd_mirror/image_sync/SnapshotCreateRequest.h \
 	tools/rbd_mirror/image_sync/SyncPointCreateRequest.h \
 	tools/rbd_mirror/image_sync/SyncPointPruneRequest.h
 
diff --git a/src/tools/crushtool.cc b/src/tools/crushtool.cc
index 7e04389..b1f5aaa 100644
--- a/src/tools/crushtool.cc
+++ b/src/tools/crushtool.cc
@@ -132,7 +132,7 @@ void usage()
   cout << "                         compile with unsafe tunables\n";
   cout << "   --build --num_osds N layer1 ...\n";
   cout << "                         build a new map, where each 'layer' is\n";
-  cout << "                         'name (uniform|straw|list|tree) size'\n";
+  cout << "                         'name (uniform|straw2|straw|list|tree) size'\n";
   cout << "\n";
   cout << "Options for the tunables adjustments stage\n";
   cout << "\n";
diff --git a/src/tools/rbd/Shell.cc b/src/tools/rbd/Shell.cc
index 5fba993..6e3431b 100644
--- a/src/tools/rbd/Shell.cc
+++ b/src/tools/rbd/Shell.cc
@@ -84,6 +84,7 @@ int Shell::execute(const Arguments& cmdline_arguments) {
                                      cmdline_arguments.end());
   std::vector<std::string> command_spec;
   get_command_spec(arguments, &command_spec);
+  bool is_alias = true;
 
   if (command_spec.empty() || command_spec == CommandSpec({"help"})) {
     // list all available actions
@@ -92,12 +93,12 @@ int Shell::execute(const Arguments& cmdline_arguments) {
   } else if (command_spec[0] == HELP_SPEC) {
     // list help for specific action
     command_spec.erase(command_spec.begin());
-    Action *action = find_action(command_spec, NULL);
+    Action *action = find_action(command_spec, NULL, &is_alias);
     if (action == NULL) {
       print_unknown_action(command_spec);
       return EXIT_FAILURE;
     } else {
-      print_action_help(action);
+      print_action_help(action, is_alias);
       return 0;
     }
   } else if (command_spec[0] == BASH_COMPLETION_SPEC) {
@@ -107,7 +108,7 @@ int Shell::execute(const Arguments& cmdline_arguments) {
   }
 
   CommandSpec *matching_spec;
-  Action *action = find_action(command_spec, &matching_spec);
+  Action *action = find_action(command_spec, &matching_spec, &is_alias);
   if (action == NULL) {
     print_unknown_action(command_spec);
     return EXIT_FAILURE;
@@ -210,7 +211,7 @@ void Shell::get_command_spec(const std::vector<std::string> &arguments,
 }
 
 Shell::Action *Shell::find_action(const CommandSpec &command_spec,
-                                  CommandSpec **matching_spec) {
+                                  CommandSpec **matching_spec, bool *is_alias) {
   for (size_t i = 0; i < get_actions().size(); ++i) {
     Action *action = get_actions()[i];
     if (action->command_spec.size() <= command_spec.size()) {
@@ -221,6 +222,7 @@ Shell::Action *Shell::find_action(const CommandSpec &command_spec,
         if (matching_spec != NULL) {
           *matching_spec = &action->command_spec;
         }
+        *is_alias = false;
         return action;
       }
     }
@@ -234,6 +236,7 @@ Shell::Action *Shell::find_action(const CommandSpec &command_spec,
         if (matching_spec != NULL) {
           *matching_spec = &action->alias_command_spec;
         }
+        *is_alias = true;
         return action;
       }
     }
@@ -302,14 +305,13 @@ void Shell::print_help() {
   std::cout << std::endl << global_opts << std::endl
             << "See '" << APP_NAME << " help <command>' for help on a specific "
             << "command." << std::endl;
-}
-
-void Shell::print_action_help(Action *action) {
+ }
 
+void Shell::print_action_help(Action *action, bool is_alias) {
   std::stringstream ss;
-  ss << "usage: " << APP_NAME << " "
-     << format_command_spec(action->command_spec);
-  std::cout << ss.str();
+    ss << "usage: " << APP_NAME << " "
+       << format_command_spec(is_alias ? action->alias_command_spec : action->command_spec);
+    std::cout << ss.str();
 
   po::options_description positional;
   po::options_description options;
@@ -339,7 +341,10 @@ void Shell::print_unknown_action(const std::vector<std::string> &command_spec) {
 }
 
 void Shell::print_bash_completion(const CommandSpec &command_spec) {
-  Action *action = find_action(command_spec, NULL);
+  
+  bool is_alias = true;
+
+  Action *action = find_action(command_spec, NULL, &is_alias);
   po::options_description global_opts;
   get_global_options(&global_opts);
   print_bash_completion_options(global_opts);
diff --git a/src/tools/rbd/Shell.h b/src/tools/rbd/Shell.h
index 6792130..7437828 100644
--- a/src/tools/rbd/Shell.h
+++ b/src/tools/rbd/Shell.h
@@ -57,12 +57,12 @@ private:
   void get_command_spec(const std::vector<std::string> &arguments,
                         std::vector<std::string> *command_spec);
   Action *find_action(const CommandSpec &command_spec,
-                      CommandSpec **matching_spec);
+                      CommandSpec **matching_spec, bool *is_alias);
 
   void get_global_options(boost::program_options::options_description *opts);
 
   void print_help();
-  void print_action_help(Action *action);
+  void print_action_help(Action *action, bool is_alias);
   void print_unknown_action(const CommandSpec &command_spec);
 
   void print_bash_completion(const CommandSpec &command_spec);
diff --git a/src/tools/rbd/Utils.cc b/src/tools/rbd/Utils.cc
index f06e857..bc67f5d 100644
--- a/src/tools/rbd/Utils.cc
+++ b/src/tools/rbd/Utils.cc
@@ -657,8 +657,8 @@ std::string image_id(librbd::Image& image) {
   return string(prefix + strlen(RBD_DATA_PREFIX));
 }
 
-std::string mirror_image_state(rbd_mirror_image_state_t mirror_image_state) {
-  switch (mirror_image_state) {
+std::string mirror_image_state(librbd::mirror_image_state_t state) {
+  switch (state) {
     case RBD_MIRROR_IMAGE_DISABLING:
       return "disabling";
     case RBD_MIRROR_IMAGE_ENABLED:
@@ -670,5 +670,50 @@ std::string mirror_image_state(rbd_mirror_image_state_t mirror_image_state) {
   }
 }
 
+std::string mirror_image_status_state(librbd::mirror_image_status_state_t state) {
+  switch (state) {
+  case MIRROR_IMAGE_STATUS_STATE_UNKNOWN:
+    return "unknown";
+    break;
+  case MIRROR_IMAGE_STATUS_STATE_ERROR:
+    return "error";
+    break;
+  case MIRROR_IMAGE_STATUS_STATE_SYNCING:
+    return "syncing";
+    break;
+  case MIRROR_IMAGE_STATUS_STATE_STARTING_REPLAY:
+    return "starting_replay";
+    break;
+  case MIRROR_IMAGE_STATUS_STATE_REPLAYING:
+    return "replaying";
+    break;
+  case MIRROR_IMAGE_STATUS_STATE_STOPPING_REPLAY:
+    return "stopping_replay";
+    break;
+  case MIRROR_IMAGE_STATUS_STATE_STOPPED:
+    return "stopped";
+    break;
+  default:
+    return "unknown (" + stringify(static_cast<uint32_t>(state)) + ")";
+    break;
+  }
+}
+
+std::string mirror_image_status_state(librbd::mirror_image_status_t status) {
+  return (status.up ? "up+" : "down+") +
+    mirror_image_status_state(status.state);
+}
+
+std::string timestr(time_t t) {
+  struct tm tm;
+
+  localtime_r(&t, &tm);
+
+  char buf[32];
+  strftime(buf, sizeof(buf), "%F %T", &tm);
+
+  return buf;
+}
+
 } // namespace utils
 } // namespace rbd
diff --git a/src/tools/rbd/Utils.h b/src/tools/rbd/Utils.h
index 10ec2d2..cad18d9 100644
--- a/src/tools/rbd/Utils.h
+++ b/src/tools/rbd/Utils.h
@@ -108,7 +108,11 @@ int snap_set(librbd::Image &image, const std::string &snap_name);
 
 std::string image_id(librbd::Image& image);
 
-std::string mirror_image_state(rbd_mirror_image_state_t mirror_image_state);
+std::string mirror_image_state(librbd::mirror_image_state_t mirror_image_state);
+std::string mirror_image_status_state(librbd::mirror_image_status_state_t state);
+std::string mirror_image_status_state(librbd::mirror_image_status_t status);
+
+std::string timestr(time_t t);
 
 } // namespace utils
 } // namespace rbd
diff --git a/src/tools/rbd/action/DiskUsage.cc b/src/tools/rbd/action/DiskUsage.cc
index e694dd7..9ef2e7e 100644
--- a/src/tools/rbd/action/DiskUsage.cc
+++ b/src/tools/rbd/action/DiskUsage.cc
@@ -86,8 +86,8 @@ static int compute_image_disk_usage(const std::string& name,
 }
 
 static int do_disk_usage(librbd::RBD &rbd, librados::IoCtx &io_ctx,
-                        const char *imgname, const char *snapname,
-                        Formatter *f) {
+                         const char *imgname, const char *snapname,
+                         const char *from_snapname, Formatter *f) {
   std::vector<std::string> names;
   int r = rbd.list(io_ctx, names);
   if (r == -ENOENT) {
@@ -106,6 +106,7 @@ static int do_disk_usage(librbd::RBD &rbd, librados::IoCtx &io_ctx,
     tbl.define_column("USED", TextTable::RIGHT, TextTable::RIGHT);
   }
 
+  uint32_t count = 0;
   uint64_t used_size = 0;
   uint64_t total_prov = 0;
   uint64_t total_used = 0;
@@ -152,6 +153,7 @@ static int do_disk_usage(librbd::RBD &rbd, librados::IoCtx &io_ctx,
       continue;
     }
 
+    bool found_from_snap = (from_snapname == nullptr);
     std::string last_snap_name;
     std::sort(snap_list.begin(), snap_list.end(),
               boost::bind(&librbd::snap_info_t::id, _1) <
@@ -167,7 +169,8 @@ static int do_disk_usage(librbd::RBD &rbd, librados::IoCtx &io_ctx,
         goto out;
       }
 
-      if (imgname == NULL || (snapname != NULL && snap->name == snapname)) {
+      if (imgname == nullptr || found_from_snap ||
+         (found_from_snap && snapname != nullptr && snap->name == snapname)) {
         r = compute_image_disk_usage(*name, snap->name, last_snap_name,
                                      snap_image, snap->size, tbl, f,
                                      &used_size);
@@ -179,6 +182,15 @@ static int do_disk_usage(librbd::RBD &rbd, librados::IoCtx &io_ctx,
           total_prov += snap->size;
         }
         total_used += used_size;
+        ++count;
+      }
+
+      if (!found_from_snap && from_snapname != nullptr &&
+          snap->name == from_snapname) {
+        found_from_snap = true;
+      }
+      if (snapname != nullptr && snap->name == snapname) {
+        break;
       }
       last_snap_name = snap->name;
     }
@@ -191,6 +203,7 @@ static int do_disk_usage(librbd::RBD &rbd, librados::IoCtx &io_ctx,
       }
       total_prov += info.size;
       total_used += used_size;
+      ++count;
     }
   }
 
@@ -204,7 +217,7 @@ out:
     f->close_section();
     f->flush(std::cout);
   } else {
-    if (imgname == NULL) {
+    if (count > 1) {
       tbl << "<TOTAL>"
           << stringify(si_t(total_prov))
           << stringify(si_t(total_used))
@@ -221,6 +234,9 @@ void get_arguments(po::options_description *positional,
   at::add_image_or_snap_spec_options(positional, options,
                                      at::ARGUMENT_MODIFIER_NONE);
   at::add_format_options(options);
+  options->add_options()
+    (at::FROM_SNAPSHOT_NAME.c_str(), po::value<std::string>(),
+     "snapshot starting point");
 }
 
 int execute(const po::variables_map &vm) {
@@ -231,11 +247,16 @@ int execute(const po::variables_map &vm) {
   int r = utils::get_pool_image_snapshot_names(
     vm, at::ARGUMENT_MODIFIER_NONE, &arg_index, &pool_name, &image_name,
     &snap_name, utils::SNAPSHOT_PRESENCE_PERMITTED,
-    utils::SPEC_VALIDATION_NONE, false);
+    utils::SPEC_VALIDATION_NONE, vm.count(at::FROM_SNAPSHOT_NAME));
   if (r < 0) {
     return r;
   }
 
+  std::string from_snap_name;
+  if (vm.count(at::FROM_SNAPSHOT_NAME)) {
+    from_snap_name = vm[at::FROM_SNAPSHOT_NAME].as<std::string>();
+  }
+
   at::Format::Formatter formatter;
   r = utils::get_formatter(vm, &formatter);
   if (r < 0) {
@@ -253,6 +274,7 @@ int execute(const po::variables_map &vm) {
   r = do_disk_usage(rbd, io_ctx,
                     image_name.empty() ? nullptr: image_name.c_str() ,
                     snap_name.empty() ? nullptr : snap_name.c_str(),
+                    from_snap_name.empty() ? nullptr : from_snap_name.c_str(),
                     formatter.get());
   if (r < 0) {
     std::cerr << "du failed: " << cpp_strerror(r) << std::endl;
diff --git a/src/tools/rbd/action/Kernel.cc b/src/tools/rbd/action/Kernel.cc
index 74eb6d9..f8fc413 100644
--- a/src/tools/rbd/action/Kernel.cc
+++ b/src/tools/rbd/action/Kernel.cc
@@ -153,6 +153,45 @@ static int do_kernel_showmapped(Formatter *f)
   return r;
 }
 
+/*
+ * hint user to check syslog for krbd related messages and provide suggestions
+ * based on errno return by krbd_map(). also note that even if some librbd calls
+ * fail, we atleast dump the "try dmesg..." message to aid debugging.
+ */
+static void print_error_description(const char *poolname, const char *imgname,
+				    const char *snapname, int maperrno)
+{
+  int r;
+  uint8_t oldformat;
+  librados::Rados rados;
+  librados::IoCtx ioctx;
+  librbd::Image image;
+
+  if (maperrno == -ENOENT)
+    goto done;
+
+  r = utils::init_and_open_image(poolname, imgname, snapname,
+				 true, &rados, &ioctx, &image);
+  if (r < 0)
+    goto done;
+
+  r = image.old_format(&oldformat);
+  if (r < 0)
+    goto done;
+
+  /*
+   * kernel returns -ENXIO when mapping a V2 image due to unsupported feature
+   * set - so, hint about that too...
+   */
+  if (!oldformat && (maperrno == -ENXIO)) {
+    std::cout << "RBD image feature set mismatch. You can disable features unsupported by the "
+	      << "kernel with \"rbd feature disable\"." << std::endl;
+  }
+
+ done:
+  std::cout << "In some cases useful info is found in syslog - try \"dmesg | tail\" or so." << std::endl;
+}
+
 static int do_kernel_map(const char *poolname, const char *imgname,
                          const char *snapname)
 {
@@ -181,8 +220,10 @@ static int do_kernel_map(const char *poolname, const char *imgname,
   }
 
   r = krbd_map(krbd, poolname, imgname, snapname, oss.str().c_str(), &devnode);
-  if (r < 0)
+  if (r < 0) {
+    print_error_description(poolname, imgname, snapname, r);
     goto out;
+  }
 
   std::cout << devnode << std::endl;
 
diff --git a/src/tools/rbd/action/MirrorImage.cc b/src/tools/rbd/action/MirrorImage.cc
index f61c628..e703e2d 100644
--- a/src/tools/rbd/action/MirrorImage.cc
+++ b/src/tools/rbd/action/MirrorImage.cc
@@ -187,6 +187,70 @@ int execute_resync(const po::variables_map &vm) {
   return 0;
 }
 
+void get_status_arguments(po::options_description *positional,
+			  po::options_description *options) {
+  at::add_image_spec_options(positional, options, at::ARGUMENT_MODIFIER_NONE);
+  at::add_format_options(options);
+}
+
+int execute_status(const po::variables_map &vm) {
+  at::Format::Formatter formatter;
+  int r = utils::get_formatter(vm, &formatter);
+  if (r < 0) {
+    return r;
+  }
+
+  size_t arg_index = 0;
+  std::string pool_name;
+  std::string image_name;
+  std::string snap_name;
+  r = utils::get_pool_image_snapshot_names(
+      vm, at::ARGUMENT_MODIFIER_NONE, &arg_index, &pool_name, &image_name,
+      &snap_name, utils::SNAPSHOT_PRESENCE_NONE, utils::SPEC_VALIDATION_NONE);
+  if (r < 0) {
+    return r;
+  }
+
+  librados::Rados rados;
+  librados::IoCtx io_ctx;
+  librbd::Image image;
+  r = utils::init_and_open_image(pool_name, image_name, "", false,
+                                 &rados, &io_ctx, &image);
+  if (r < 0) {
+    return r;
+  }
+
+  librbd::mirror_image_status_t status;
+  r = image.mirror_image_get_status(&status, sizeof(status));
+  if (r < 0) {
+    std::cerr << "rbd: failed to get status for image " << image_name << ": "
+	      << cpp_strerror(r) << std::endl;
+    return r;
+  }
+
+  std::string state = utils::mirror_image_status_state(status);
+  std::string last_update = utils::timestr(status.last_update);
+
+  if (formatter != nullptr) {
+    formatter->open_object_section("image");
+    formatter->dump_string("name", image_name);
+    formatter->dump_string("global_id", status.info.global_id);
+    formatter->dump_string("state", state);
+    formatter->dump_string("description", status.description);
+    formatter->dump_string("last_update", last_update);
+    formatter->close_section(); // image
+    formatter->flush(std::cout);
+  } else {
+    std::cout << image_name << ":\n"
+	      << "  global_id:   " << status.info.global_id << "\n"
+	      << "  state:       " << state << "\n"
+	      << "  description: " << status.description << "\n"
+	      << "  last_update: " << last_update << std::endl;
+  }
+
+  return 0;
+}
+
 Shell::Action action_enable(
   {"mirror", "image", "enable"}, {},
   "Enable RBD mirroring for an image.", "",
@@ -207,6 +271,10 @@ Shell::Action action_resync(
   {"mirror", "image", "resync"}, {},
   "Force resync to primary image for RBD mirroring.", "",
   &get_arguments, &execute_resync);
+Shell::Action action_status(
+  {"mirror", "image", "status"}, {},
+  "Show RDB mirroring status for an image.", "",
+  &get_status_arguments, &execute_status);
 
 } // namespace mirror_image
 } // namespace action
diff --git a/src/tools/rbd/action/MirrorPool.cc b/src/tools/rbd/action/MirrorPool.cc
index 8ba055e..4552db9 100644
--- a/src/tools/rbd/action/MirrorPool.cc
+++ b/src/tools/rbd/action/MirrorPool.cc
@@ -374,6 +374,148 @@ int execute_info(const po::variables_map &vm) {
   return 0;
 }
 
+void get_status_arguments(po::options_description *positional,
+			  po::options_description *options) {
+  at::add_pool_options(positional, options);
+  at::add_format_options(options);
+  at::add_verbose_option(options);
+}
+
+int execute_status(const po::variables_map &vm) {
+  size_t arg_index = 0;
+  std::string pool_name = utils::get_pool_name(vm, &arg_index);
+
+  at::Format::Formatter formatter;
+  int r = utils::get_formatter(vm, &formatter);
+  if (r < 0) {
+    return r;
+  }
+
+  bool verbose = vm[at::VERBOSE].as<bool>();
+
+  std::string config_path;
+  if (vm.count(at::CONFIG_PATH)) {
+    config_path = vm[at::CONFIG_PATH].as<std::string>();
+  }
+
+  librados::Rados rados;
+  librados::IoCtx io_ctx;
+  r = utils::init(pool_name, &rados, &io_ctx);
+  if (r < 0) {
+    return r;
+  }
+
+  librbd::RBD rbd;
+
+  std::map<librbd::mirror_image_status_state_t, int> states;
+  r = rbd.mirror_image_status_summary(io_ctx, &states);
+  if (r < 0) {
+    std::cerr << "rbd: failed to get status summary for mirrored images: "
+	      << cpp_strerror(r) << std::endl;
+    return r;
+  }
+
+  if (formatter != nullptr) {
+    formatter->open_object_section("status");
+  }
+
+  enum Health {Ok = 0, Warning = 1, Error = 2} health = Ok;
+  const char *names[] = {"OK", "WARNING", "ERROR"};
+  int total = 0;
+
+  for (auto &it : states) {
+    auto &state = it.first;
+    if (health < Warning &&
+	(state != MIRROR_IMAGE_STATUS_STATE_REPLAYING &&
+	 state != MIRROR_IMAGE_STATUS_STATE_STOPPED)) {
+      health = Warning;
+    }
+    if (health < Error &&
+	state == MIRROR_IMAGE_STATUS_STATE_ERROR) {
+      health = Error;
+    }
+    total += it.second;
+  }
+
+  if (formatter != nullptr) {
+    formatter->open_object_section("summary");
+    formatter->dump_string("health", names[health]);
+    formatter->open_object_section("states");
+    for (auto &it : states) {
+      std::string state_name = utils::mirror_image_status_state(it.first);
+      formatter->dump_int(state_name.c_str(), it.second);
+    }
+    formatter->close_section(); // states
+    formatter->close_section(); // summary
+  } else {
+    std::cout << "health: " << names[health] << std::endl;
+    std::cout << "images: " << total << " total" << std::endl;
+    for (auto &it : states) {
+      std::cout << "    " << it.second << " "
+		<< utils::mirror_image_status_state(it.first) << std::endl;
+    }
+  }
+
+  int ret = 0;
+
+  if (verbose) {
+    if (formatter != nullptr) {
+      formatter->open_array_section("images");
+    }
+
+    std::string last_read = "";
+    int max_read = 1024;
+    do {
+      map<std::string, librbd::mirror_image_status_t> mirror_images;
+      r = rbd.mirror_image_status_list(io_ctx, last_read, max_read,
+				       &mirror_images);
+      if (r < 0) {
+	std::cerr << "rbd: failed to list mirrored image directory: "
+		  << cpp_strerror(r) << std::endl;
+	return r;
+      }
+      for (auto it = mirror_images.begin(); it != mirror_images.end(); ++it) {
+	librbd::mirror_image_status_t &status = it->second;
+	const std::string &image_name = status.name;
+	std::string &global_image_id = status.info.global_id;
+	std::string state = utils::mirror_image_status_state(status);
+	std::string last_update = utils::timestr(status.last_update);
+
+	if (formatter != nullptr) {
+	  formatter->open_object_section("image");
+	  formatter->dump_string("name", image_name);
+	  formatter->dump_string("global_id", global_image_id);
+	  formatter->dump_string("state", state);
+	  formatter->dump_string("description", status.description);
+	  formatter->dump_string("last_update", last_update);
+	  formatter->close_section(); // image
+	} else {
+	  std::cout << "\n" << image_name << ":\n"
+		    << "  global_id:   " << global_image_id << "\n"
+		    << "  state:       " << state << "\n"
+		    << "  description: " << status.description << "\n"
+		    << "  last_update: " << last_update << std::endl;
+	}
+      }
+      if (!mirror_images.empty()) {
+	last_read = mirror_images.rbegin()->first;
+      }
+      r = mirror_images.size();
+    } while (r == max_read);
+
+    if (formatter != nullptr) {
+      formatter->close_section(); // images
+    }
+  }
+
+  if (formatter != nullptr) {
+    formatter->close_section(); // status
+    formatter->flush(std::cout);
+  }
+
+  return ret;
+}
+
 Shell::Action action_add(
   {"mirror", "pool", "peer", "add"}, {},
   "Add a mirroring peer to a pool.", "",
@@ -399,6 +541,10 @@ Shell::Action action_info(
   {"mirror", "pool", "info"}, {},
   "Show information about the pool mirroring configuration.", {},
   &get_info_arguments, &execute_info);
+Shell::Action action_status(
+  {"mirror", "pool", "status"}, {},
+  "Show status for all mirrored images in the pool.", {},
+  &get_status_arguments, &execute_status);
 
 } // namespace mirror_pool
 } // namespace action
diff --git a/src/tools/rbd_mirror/ImageReplayer.cc b/src/tools/rbd_mirror/ImageReplayer.cc
index df297b0..203b78c 100644
--- a/src/tools/rbd_mirror/ImageReplayer.cc
+++ b/src/tools/rbd_mirror/ImageReplayer.cc
@@ -22,7 +22,7 @@
 #include "Threads.h"
 #include "tools/rbd_mirror/image_replayer/BootstrapRequest.h"
 #include "tools/rbd_mirror/image_replayer/CloseImageRequest.h"
-#include "tools/rbd_mirror/image_replayer/OpenLocalImageRequest.h"
+#include "tools/rbd_mirror/image_replayer/ReplayStatusFormatter.h"
 
 #define dout_subsys ceph_subsys_rbd_mirror
 #undef dout_prefix
@@ -37,6 +37,7 @@ namespace rbd {
 namespace mirror {
 
 using librbd::util::create_context_callback;
+using librbd::util::create_rados_ack_callback;
 using namespace rbd::mirror::image_replayer;
 
 template <typename I>
@@ -49,15 +50,18 @@ template <typename I>
 struct ReplayHandler : public ::journal::ReplayHandler {
   ImageReplayer<I> *replayer;
   ReplayHandler(ImageReplayer<I> *replayer) : replayer(replayer) {}
-
-  virtual void get() {}
+virtual void get() {}
   virtual void put() {}
 
   virtual void handle_entries_available() {
     replayer->handle_replay_ready();
   }
   virtual void handle_complete(int r) {
-    replayer->handle_replay_complete(r);
+    std::stringstream ss;
+    if (r < 0) {
+      ss << "replay completed with error: " << cpp_strerror(r);
+    }
+    replayer->handle_replay_complete(r, ss.str());
   }
 };
 
@@ -82,6 +86,48 @@ private:
 };
 
 template <typename I>
+class StartCommand : public ImageReplayerAdminSocketCommand {
+public:
+  explicit StartCommand(ImageReplayer<I> *replayer) : replayer(replayer) {}
+
+  bool call(Formatter *f, stringstream *ss) {
+    replayer->start(nullptr, nullptr, true);
+    return true;
+  }
+
+private:
+  ImageReplayer<I> *replayer;
+};
+
+template <typename I>
+class StopCommand : public ImageReplayerAdminSocketCommand {
+public:
+  explicit StopCommand(ImageReplayer<I> *replayer) : replayer(replayer) {}
+
+  bool call(Formatter *f, stringstream *ss) {
+    replayer->stop(nullptr, true);
+    return true;
+  }
+
+private:
+  ImageReplayer<I> *replayer;
+};
+
+template <typename I>
+class RestartCommand : public ImageReplayerAdminSocketCommand {
+public:
+  explicit RestartCommand(ImageReplayer<I> *replayer) : replayer(replayer) {}
+
+  bool call(Formatter *f, stringstream *ss) {
+    replayer->restart();
+    return true;
+  }
+
+private:
+  ImageReplayer<I> *replayer;
+};
+
+template <typename I>
 class FlushCommand : public ImageReplayerAdminSocketCommand {
 public:
   explicit FlushCommand(ImageReplayer<I> *replayer) : replayer(replayer) {}
@@ -117,6 +163,27 @@ public:
       commands[command] = new StatusCommand<I>(replayer);
     }
 
+    command = "rbd mirror start " + name;
+    r = admin_socket->register_command(command, command, this,
+				       "start rbd mirror " + name);
+    if (r == 0) {
+      commands[command] = new StartCommand<I>(replayer);
+    }
+
+    command = "rbd mirror stop " + name;
+    r = admin_socket->register_command(command, command, this,
+				       "stop rbd mirror " + name);
+    if (r == 0) {
+      commands[command] = new StopCommand<I>(replayer);
+    }
+
+    command = "rbd mirror restart " + name;
+    r = admin_socket->register_command(command, command, this,
+				       "restart rbd mirror " + name);
+    if (r == 0) {
+      commands[command] = new RestartCommand<I>(replayer);
+    }
+
     command = "rbd mirror flush " + name;
     r = admin_socket->register_command(command, command, this,
 				       "flush rbd mirror " + name);
@@ -155,6 +222,22 @@ private:
 } // anonymous namespace
 
 template <typename I>
+void ImageReplayer<I>::BootstrapProgressContext::update_progress(
+  const std::string &description, bool flush)
+{
+  const std::string desc = "bootstrapping, " + description;
+
+  FunctionContext *ctx = new FunctionContext(
+    [this, desc, flush](int r) {
+      replayer->set_state_description(0, desc);
+      if (flush) {
+	replayer->update_mirror_image_status();
+      }
+    });
+  replayer->m_threads->work_queue->queue(ctx, 0);
+}
+
+template <typename I>
 ImageReplayer<I>::ImageReplayer(Threads *threads, RadosRef local, RadosRef remote,
 			     const std::string &local_mirror_uuid,
 			     const std::string &remote_mirror_uuid,
@@ -174,48 +257,89 @@ ImageReplayer<I>::ImageReplayer(Threads *threads, RadosRef local, RadosRef remot
   m_name(stringify(remote_pool_id) + "/" + remote_image_id),
   m_lock("rbd::mirror::ImageReplayer " + stringify(remote_pool_id) + " " +
 	 remote_image_id),
-  m_state(STATE_UNINITIALIZED),
-  m_local_image_ctx(nullptr),
-  m_local_replay(nullptr),
-  m_remote_journaler(nullptr),
-  m_replay_handler(nullptr),
-  m_asok_hook(nullptr)
+  m_progress_cxt(this)
 {
+  // Register asok commands using a temporary "remote_pool_name/global_image_id"
+  // name.  When the image name becomes known on start the asok commands will be
+  // re-registered using "remote_pool_name/remote_image_name" name.
+
+  std::string pool_name;
+  int r = m_remote->pool_reverse_lookup(m_remote_pool_id, &pool_name);
+  if (r < 0) {
+    derr << "error resolving remote pool " << m_remote_pool_id
+	 << ": " << cpp_strerror(r) << dendl;
+    pool_name = stringify(m_remote_pool_id);
+  }
+  m_name = pool_name + "/" + m_global_image_id;
+
+  CephContext *cct = static_cast<CephContext *>(m_local->cct());
+  m_asok_hook = new ImageReplayerAdminSocketHook<I>(cct, m_name, this);
 }
 
 template <typename I>
 ImageReplayer<I>::~ImageReplayer()
 {
+  assert(m_replay_status_formatter == nullptr);
   assert(m_local_image_ctx == nullptr);
   assert(m_local_replay == nullptr);
   assert(m_remote_journaler == nullptr);
   assert(m_replay_handler == nullptr);
   assert(m_on_start_finish == nullptr);
   assert(m_on_stop_finish == nullptr);
+
   delete m_asok_hook;
 }
 
 template <typename I>
+void ImageReplayer<I>::set_state_description(int r, const std::string &desc) {
+  dout(20) << r << " " << desc << dendl;
+
+  Mutex::Locker l(m_lock);
+  m_last_r = r;
+  m_state_desc = desc;
+}
+
+template <typename I>
 void ImageReplayer<I>::start(Context *on_finish,
-			     const BootstrapParams *bootstrap_params)
+			     const BootstrapParams *bootstrap_params,
+			     bool manual)
 {
   assert(m_on_start_finish == nullptr);
   assert(m_on_stop_finish == nullptr);
   dout(20) << "on_finish=" << on_finish << dendl;
 
+  int r = 0;
   {
     Mutex::Locker locker(m_lock);
-    assert(is_stopped_());
 
-    m_state = STATE_STARTING;
-    m_on_start_finish = on_finish;
+    if (!is_stopped_()) {
+      derr << "already running" << dendl;
+      r = -EINVAL;
+    } else if (m_manual_stop && !manual) {
+      dout(5) << "stopped manually, ignoring start without manual flag"
+	      << dendl;
+      r = -EPERM;
+    } else {
+      m_state = STATE_STARTING;
+      m_last_r = 0;
+      m_state_desc.clear();
+      m_on_start_finish = on_finish;
+      m_manual_stop = false;
+    }
+  }
+
+  if (r < 0) {
+    if (on_finish) {
+      on_finish->complete(r);
+    }
+    return;
   }
 
-  int r = m_remote->ioctx_create2(m_remote_pool_id, m_remote_ioctx);
+  r = m_remote->ioctx_create2(m_remote_pool_id, m_remote_ioctx);
   if (r < 0) {
     derr << "error opening ioctx for remote pool " << m_remote_pool_id
 	 << ": " << cpp_strerror(r) << dendl;
-    on_start_fail_start(r);
+    on_start_fail_start(r, "error opening remote pool");
     return;
   }
 
@@ -227,10 +351,12 @@ void ImageReplayer<I>::start(Context *on_finish,
   if (r < 0) {
     derr << "error opening ioctx for local pool " << m_local_pool_id
          << ": " << cpp_strerror(r) << dendl;
-    on_start_fail_start(r);
+    on_start_fail_start(r, "error opening local pool");
     return;
   }
 
+  reschedule_update_status_task(10);
+
   CephContext *cct = static_cast<CephContext *>(m_local->cct());
   double commit_interval = cct->_conf->rbd_journal_commit_age;
   m_remote_journaler = new Journaler(m_threads->work_queue,
@@ -238,7 +364,6 @@ void ImageReplayer<I>::start(Context *on_finish,
 				     &m_threads->timer_lock, m_remote_ioctx,
 				     m_remote_image_id, m_local_mirror_uuid,
                                      commit_interval);
-
   bootstrap();
 }
 
@@ -247,15 +372,18 @@ void ImageReplayer<I>::bootstrap() {
   dout(20) << "bootstrap params: "
 	   << "local_image_name=" << m_local_image_name << dendl;
 
+  update_mirror_image_status();
+
   // TODO: add a new bootstrap state and support canceling
   Context *ctx = create_context_callback<
     ImageReplayer, &ImageReplayer<I>::handle_bootstrap>(this);
+
   BootstrapRequest<I> *request = BootstrapRequest<I>::create(
     m_local_ioctx, m_remote_ioctx, &m_local_image_ctx,
     m_local_image_name, m_remote_image_id, m_global_image_id,
     m_threads->work_queue, m_threads->timer, &m_threads->timer_lock,
     m_local_mirror_uuid, m_remote_mirror_uuid, m_remote_journaler,
-    &m_client_meta, ctx);
+    &m_client_meta, ctx, &m_progress_cxt);
   request->send();
 }
 
@@ -265,10 +393,10 @@ void ImageReplayer<I>::handle_bootstrap(int r) {
 
   if (r == -EREMOTEIO) {
     dout(5) << "remote image is non-primary or local image is primary" << dendl;
-    on_start_fail_start(0);
+    on_start_fail_start(0, "remote image is non-primary or local image is primary");
     return;
   } else if (r < 0) {
-    on_start_fail_start(r);
+    on_start_fail_start(r, "error bootstrapping replay");
     return;
   } else if (on_start_interrupted()) {
     return;
@@ -276,13 +404,25 @@ void ImageReplayer<I>::handle_bootstrap(int r) {
 
   {
     Mutex::Locker locker(m_lock);
-    m_name = m_local_ioctx.get_pool_name() + "/" + m_local_image_ctx->name;
 
-    CephContext *cct = static_cast<CephContext *>(m_local->cct());
-    delete m_asok_hook;
-    m_asok_hook = new ImageReplayerAdminSocketHook<I>(cct, m_name, this);
+    std::string name = m_local_ioctx.get_pool_name() + "/" +
+      m_local_image_ctx->name;
+    if (m_name != name) {
+      m_name = name;
+      if (m_asok_hook) {
+	// Re-register asok commands using the new name.
+	delete m_asok_hook;
+	m_asok_hook = nullptr;
+      }
+    }
+    if (!m_asok_hook) {
+      CephContext *cct = static_cast<CephContext *>(m_local->cct());
+      m_asok_hook = new ImageReplayerAdminSocketHook<I>(cct, m_name, this);
+    }
   }
 
+  update_mirror_image_status();
+
   init_remote_journaler();
 }
 
@@ -301,7 +441,7 @@ void ImageReplayer<I>::handle_init_remote_journaler(int r) {
 
   if (r < 0) {
     derr << "failed to initialize remote journal: " << cpp_strerror(r) << dendl;
-    on_start_fail_start(r);
+    on_start_fail_start(r, "error initializing remote journal");
     return;
   } else if (on_start_interrupted()) {
     return;
@@ -318,46 +458,51 @@ void ImageReplayer<I>::start_replay() {
   if (r < 0) {
     derr << "error starting external replay on local image "
 	 <<  m_local_image_id << ": " << cpp_strerror(r) << dendl;
-    on_start_fail_start(r);
+    on_start_fail_start(r, "error starting replay on local image");
     return;
   }
 
-  m_replay_handler = new ReplayHandler<I>(this);
-  m_remote_journaler->start_live_replay(m_replay_handler,
-					1 /* TODO: configurable */);
-
-  dout(20) << "m_remote_journaler=" << *m_remote_journaler << dendl;
-
-  assert(r == 0);
-
   Context *on_finish(nullptr);
   {
     Mutex::Locker locker(m_lock);
-    if (m_stop_requested) {
-      on_start_fail_start(-EINTR);
-      return;
-    }
-
     assert(m_state == STATE_STARTING);
     m_state = STATE_REPLAYING;
+    m_state_desc.clear();
     std::swap(m_on_start_finish, on_finish);
   }
 
+  m_replay_status_formatter =
+    ReplayStatusFormatter<I>::create(m_remote_journaler, m_local_mirror_uuid);
+  update_mirror_image_status();
+  reschedule_update_status_task(30);
+
   dout(20) << "start succeeded" << dendl;
   if (on_finish != nullptr) {
     dout(20) << "on finish complete, r=" << r << dendl;
     on_finish->complete(r);
   }
+
+  {
+    Mutex::Locker locker(m_lock);
+    m_replay_handler = new ReplayHandler<I>(this);
+    m_remote_journaler->start_live_replay(m_replay_handler,
+                                          1 /* TODO: configurable */);
+
+    dout(20) << "m_remote_journaler=" << *m_remote_journaler << dendl;
+  }
+
+  on_replay_interrupted();
 }
 
 template <typename I>
-void ImageReplayer<I>::on_start_fail_start(int r)
+void ImageReplayer<I>::on_start_fail_start(int r, const std::string &desc)
 {
   dout(20) << "r=" << r << dendl;
 
   FunctionContext *ctx = new FunctionContext(
-    [this, r](int r1) {
+    [this, r, desc](int r1) {
       assert(r1 == 0);
+      set_state_description(r, desc);
       on_start_fail_finish(r);
     });
 
@@ -394,12 +539,6 @@ void ImageReplayer<I>::on_start_fail_finish(int r)
     m_local_image_ctx = nullptr;
   }
 
-  m_local_ioctx.close();
-  m_remote_ioctx.close();
-
-  delete m_asok_hook;
-  m_asok_hook = nullptr;
-
   Context *on_start_finish(nullptr);
   Context *on_stop_finish(nullptr);
   {
@@ -412,12 +551,20 @@ void ImageReplayer<I>::on_start_fail_finish(int r)
     } else {
       assert(m_state == STATE_STARTING);
       dout(20) << "start failed" << dendl;
-      m_state = STATE_UNINITIALIZED;
+      m_state = (r < 0) ? STATE_UNINITIALIZED : STATE_STOPPED;
     }
     std::swap(m_on_start_finish, on_start_finish);
     std::swap(m_on_stop_finish, on_stop_finish);
   }
 
+  update_mirror_image_status(true);
+
+  m_local_ioctx.close();
+  m_remote_ioctx.close();
+
+  delete m_asok_hook;
+  m_asok_hook = nullptr;
+
   if (on_start_finish != nullptr) {
     dout(20) << "on start finish complete, r=" << r << dendl;
     on_start_finish->complete(r);
@@ -442,27 +589,39 @@ bool ImageReplayer<I>::on_start_interrupted()
 }
 
 template <typename I>
-void ImageReplayer<I>::stop(Context *on_finish)
+void ImageReplayer<I>::stop(Context *on_finish, bool manual)
 {
   dout(20) << "on_finish=" << on_finish << dendl;
 
   bool shut_down_replay = false;
+  bool running = true;
   {
     Mutex::Locker locker(m_lock);
-    assert(is_running_());
-
-    if (!is_stopped_()) {
-      if (m_state == STATE_STARTING) {
-        dout(20) << "interrupting start" << dendl;
-      } else {
-        dout(20) << "interrupting replay" << dendl;
-        shut_down_replay = true;
+    if (!is_running_()) {
+      running = false;
+    } else {
+      if (!is_stopped_()) {
+	if (m_state == STATE_STARTING) {
+	  dout(20) << "interrupting start" << dendl;
+	} else {
+	  dout(20) << "interrupting replay" << dendl;
+	  shut_down_replay = true;
+	}
+
+	assert(m_on_stop_finish == nullptr);
+	std::swap(m_on_stop_finish, on_finish);
+	m_stop_requested = true;
+	m_manual_stop = manual;
       }
+    }
+  }
 
-      assert(m_on_stop_finish == nullptr);
-      std::swap(m_on_stop_finish, on_finish);
-      m_stop_requested = true;
+  if (!running) {
+    derr << "not running" << dendl;
+    if (on_finish) {
+      on_finish->complete(-EINVAL);
     }
+    return;
   }
 
   if (shut_down_replay) {
@@ -492,6 +651,8 @@ void ImageReplayer<I>::on_stop_journal_replay_shut_down_start()
     m_state = STATE_STOPPING;
     m_local_replay->shut_down(false, ctx);
   }
+
+  update_mirror_image_status();
 }
 
 template <typename I>
@@ -536,8 +697,13 @@ void ImageReplayer<I>::on_stop_local_image_close_finish(int r)
     derr << "error closing local image: " << cpp_strerror(r) << dendl;
   }
 
+  update_mirror_image_status(true);
+
   m_local_ioctx.close();
 
+  delete m_replay_status_formatter;
+  m_replay_status_formatter = nullptr;
+
   m_remote_journaler->stop_replay();
   m_remote_journaler->shut_down();
   delete m_remote_journaler;
@@ -548,9 +714,6 @@ void ImageReplayer<I>::on_stop_local_image_close_finish(int r)
 
   m_remote_ioctx.close();
 
-  delete m_asok_hook;
-  m_asok_hook = nullptr;
-
   Context *on_finish(nullptr);
 
   {
@@ -558,6 +721,7 @@ void ImageReplayer<I>::on_stop_local_image_close_finish(int r)
     assert(m_state == STATE_STOPPING);
 
     m_state = STATE_STOPPED;
+    m_state_desc.clear();
     m_stop_requested = false;
     std::swap(m_on_stop_finish, on_finish);
   }
@@ -597,6 +761,19 @@ void ImageReplayer<I>::handle_replay_ready()
 }
 
 template <typename I>
+void ImageReplayer<I>::restart(Context *on_finish)
+{
+  FunctionContext *ctx = new FunctionContext(
+    [this, on_finish](int r) {
+      if (r < 0) {
+	// Try start anyway.
+      }
+      start(on_finish, nullptr, true);
+    });
+  stop(ctx);
+}
+
+template <typename I>
 void ImageReplayer<I>::flush(Context *on_finish)
 {
   dout(20) << "enter" << dendl;
@@ -668,6 +845,8 @@ void ImageReplayer<I>::on_flush_flush_commit_position_finish(Context *on_flush,
 	 << cpp_strerror(r) << dendl;
   }
 
+  update_mirror_image_status();
+
   dout(20) << "flush complete, r=" << r << dendl;
   on_flush->complete(r);
 }
@@ -706,11 +885,12 @@ void ImageReplayer<I>::print_status(Formatter *f, stringstream *ss)
 }
 
 template <typename I>
-void ImageReplayer<I>::handle_replay_complete(int r)
+void ImageReplayer<I>::handle_replay_complete(int r, const std::string &error_desc)
 {
   dout(20) << "r=" << r << dendl;
   if (r < 0) {
     derr << "replay encountered an error: " << cpp_strerror(r) << dendl;
+    set_state_description(r, error_desc);
   }
 
   {
@@ -735,7 +915,7 @@ void ImageReplayer<I>::handle_replay_flush(int r) {
 
   if (r < 0) {
     derr << "replay flush encountered an error: " << cpp_strerror(r) << dendl;
-    handle_replay_complete(r);
+    handle_replay_complete(r, "replay flush encountered an error");
     return;
   }
 
@@ -767,7 +947,7 @@ void ImageReplayer<I>::handle_get_remote_tag(int r) {
   if (r < 0) {
     derr << "failed to retrieve remote tag " << m_replay_tag_tid << ": "
          << cpp_strerror(r) << dendl;
-    handle_replay_complete(r);
+    handle_replay_complete(r, "failed to retrieve remote tag");
     return;
   }
 
@@ -816,7 +996,7 @@ void ImageReplayer<I>::handle_allocate_local_tag(int r) {
 
   if (r < 0) {
     derr << "failed to allocate journal tag: " << cpp_strerror(r) << dendl;
-    handle_replay_complete(r);
+    handle_replay_complete(r, "failed to allocate journal tag");
     return;
   }
 
@@ -855,7 +1035,7 @@ void ImageReplayer<I>::handle_process_entry_safe(const ReplayEntry& replay_entry
   if (r < 0) {
     derr << "failed to commit journal event: " << cpp_strerror(r) << dendl;
 
-    handle_replay_complete(r);
+    handle_replay_complete(r, "failed to commit journal event");
     return;
   }
 
@@ -874,6 +1054,193 @@ void ImageReplayer<I>::shut_down_journal_replay(bool cancel_ops)
 }
 
 template <typename I>
+void ImageReplayer<I>::update_mirror_image_status(bool final,
+						  State expected_state)
+{
+  dout(20) << "final=" << final << ", expected_state=" << expected_state
+	   << dendl;
+
+  cls::rbd::MirrorImageStatus status;
+
+  {
+    Mutex::Locker locker(m_lock);
+
+    assert(!final || !is_running_());
+
+    if (!final) {
+      if (expected_state != STATE_UNKNOWN && expected_state != m_state) {
+	dout(20) << "state changed" << dendl;
+	return;
+      }
+      if (m_update_status_comp) {
+	dout(20) << "already sending update" << dendl;
+	m_update_status_pending = true;
+	return;
+      }
+
+      Context *ctx = new FunctionContext(
+	[this](int r) {
+	  if (r < 0) {
+	    derr << "error updating mirror image status: " << cpp_strerror(r)
+	    << dendl;
+	  }
+	  bool pending = false;
+	  librados::AioCompletion *comp = nullptr;
+	  {
+	    Mutex::Locker locker(m_lock);
+	    std::swap(m_update_status_comp, comp);
+	    std::swap(m_update_status_pending, pending);
+	  }
+	  if (comp) {
+	    comp->release();
+	  }
+	  if (pending && r == 0 && is_running_()) {
+	    update_mirror_image_status();
+	  }
+	});
+      m_update_status_comp = create_rados_ack_callback(ctx);
+      m_update_status_pending = false;
+    }
+
+    switch (m_state) {
+    case STATE_UNINITIALIZED:
+      if (m_last_r < 0) {
+	status.state = cls::rbd::MIRROR_IMAGE_STATUS_STATE_ERROR;
+	status.description = m_state_desc;
+      } else {
+	status.state = cls::rbd::MIRROR_IMAGE_STATUS_STATE_UNKNOWN;
+	status.description = m_state_desc.empty() ? "not started yet" :
+	  m_state_desc;
+      }
+      break;
+    case STATE_STARTING:
+      // TODO: a better way to detect syncing state.
+      if (!m_asok_hook) {
+	status.state = cls::rbd::MIRROR_IMAGE_STATUS_STATE_SYNCING;
+	status.description = m_state_desc.empty() ? "syncing" : m_state_desc;
+      } else {
+	status.state = cls::rbd::MIRROR_IMAGE_STATUS_STATE_STARTING_REPLAY;
+	status.description = "starting replay";
+      }
+      break;
+    case STATE_REPLAYING:
+      status.state = cls::rbd::MIRROR_IMAGE_STATUS_STATE_REPLAYING;
+      break;
+    case STATE_STOPPING:
+      if (m_local_image_ctx) {
+	status.state = cls::rbd::MIRROR_IMAGE_STATUS_STATE_STOPPING_REPLAY;
+	status.description = "stopping replay";
+	break;
+      }
+      /* FALLTHROUGH */
+    case STATE_STOPPED:
+      if (m_last_r < 0) {
+	status.state = cls::rbd::MIRROR_IMAGE_STATUS_STATE_ERROR;
+	status.description = m_state_desc;
+      } else {
+	status.state = cls::rbd::MIRROR_IMAGE_STATUS_STATE_STOPPED;
+	status.description = m_state_desc.empty() ? "stopped" : m_state_desc;
+      }
+      break;
+    default:
+      assert(!"invalid state");
+    }
+  }
+
+  if (status.state == cls::rbd::MIRROR_IMAGE_STATUS_STATE_REPLAYING) {
+    Context *on_req_finish = new FunctionContext(
+      [this](int r) {
+	if (r == 0) {
+	  librados::AioCompletion *comp = nullptr;
+	  {
+	    Mutex::Locker locker(m_lock);
+	    std::swap(m_update_status_comp, comp);
+	  }
+	  if (comp) {
+	    comp->release();
+	  }
+	  update_mirror_image_status(false, STATE_REPLAYING);
+	}
+      });
+    std::string desc;
+    if (!m_replay_status_formatter->get_or_send_update(&desc, on_req_finish)) {
+      return;
+    }
+    status.description = "replaying, " + desc;
+  }
+
+  dout(20) << "status=" << status << dendl;
+
+  librados::ObjectWriteOperation op;
+  librbd::cls_client::mirror_image_status_set(&op, m_global_image_id, status);
+
+  if (final) {
+    reschedule_update_status_task(-1);
+    m_local_ioctx.aio_flush();
+    librados::AioCompletion *comp = nullptr;
+    {
+      Mutex::Locker locker(m_lock);
+      std::swap(m_update_status_comp, comp);
+    }
+    if (comp) {
+      comp->wait_for_complete();
+    }
+    int r = m_local_ioctx.operate(RBD_MIRRORING, &op);
+    if (r < 0) {
+      derr << "error updating mirror image status: " << cpp_strerror(r)
+	   << dendl;
+    }
+    return;
+  }
+
+  int r = m_local_ioctx.aio_operate(RBD_MIRRORING, m_update_status_comp, &op);
+  assert(r == 0);
+
+  reschedule_update_status_task();
+}
+
+template <typename I>
+void ImageReplayer<I>::reschedule_update_status_task(int new_interval)
+{
+  Mutex::Locker locker(m_threads->timer_lock);
+
+  if (m_update_status_task) {
+    m_threads->timer->cancel_event(m_update_status_task);
+    m_update_status_task = nullptr;
+  }
+
+  if (new_interval > 0) {
+    m_update_status_interval = new_interval;
+  }
+
+  if (new_interval < 0) {
+    return;
+  }
+
+  m_update_status_task = new FunctionContext(
+    [this](int r) {
+      start_update_status_task();
+    });
+
+  m_threads->timer->add_event_after(m_update_status_interval,
+				    m_update_status_task);
+}
+
+template <typename I>
+void ImageReplayer<I>::start_update_status_task()
+{
+  FunctionContext *ctx = new FunctionContext(
+    [this](int r) {
+      {
+	Mutex::Locker locker(m_threads->timer_lock);
+	m_update_status_task = nullptr;
+      }
+      update_mirror_image_status();
+    });
+  m_threads->work_queue->queue(ctx, 0);
+}
+
+template <typename I>
 std::string ImageReplayer<I>::to_string(const State state) {
   switch (state) {
   case ImageReplayer<I>::STATE_UNINITIALIZED:
diff --git a/src/tools/rbd_mirror/ImageReplayer.h b/src/tools/rbd_mirror/ImageReplayer.h
index 17315ad..752eaad 100644
--- a/src/tools/rbd_mirror/ImageReplayer.h
+++ b/src/tools/rbd_mirror/ImageReplayer.h
@@ -12,9 +12,11 @@
 #include "common/WorkQueue.h"
 #include "include/rados/librados.hpp"
 #include "cls/journal/cls_journal_types.h"
+#include "cls/rbd/cls_rbd_types.h"
 #include "journal/ReplayEntry.h"
 #include "librbd/journal/Types.h"
 #include "librbd/journal/TypeTraits.h"
+#include "ProgressContext.h"
 #include "types.h"
 
 class AdminSocketHook;
@@ -38,6 +40,8 @@ namespace mirror {
 
 struct Threads;
 
+namespace image_replayer { template <typename> class ReplayStatusFormatter; }
+
 /**
  * Replays changes from a remote cluster for a single image.
  */
@@ -47,6 +51,7 @@ public:
   typedef typename librbd::journal::TypeTraits<ImageCtxT>::ReplayEntry ReplayEntry;
 
   enum State {
+    STATE_UNKNOWN,
     STATE_UNINITIALIZED,
     STATE_STARTING,
     STATE_REPLAYING,
@@ -80,16 +85,19 @@ public:
   bool is_running() { Mutex::Locker l(m_lock); return is_running_(); }
 
   std::string get_name() { Mutex::Locker l(m_lock); return m_name; };
+  void set_state_description(int r, const std::string &desc);
 
   void start(Context *on_finish = nullptr,
-	     const BootstrapParams *bootstrap_params = nullptr);
-  void stop(Context *on_finish = nullptr);
+	     const BootstrapParams *bootstrap_params = nullptr,
+	     bool manual = false);
+  void stop(Context *on_finish = nullptr, bool manual = false);
+  void restart(Context *on_finish = nullptr);
   void flush(Context *on_finish = nullptr);
 
   void print_status(Formatter *f, stringstream *ss);
 
   virtual void handle_replay_ready();
-  virtual void handle_replay_complete(int r);
+  virtual void handle_replay_complete(int r, const std::string &error_desc);
 
   inline int64_t get_remote_pool_id() const {
     return m_remote_pool_id;
@@ -159,7 +167,7 @@ protected:
    * @endverbatim
    */
 
-  virtual void on_start_fail_start(int r);
+  virtual void on_start_fail_start(int r, const std::string &desc = "");
   virtual void on_start_fail_finish(int r);
   virtual bool on_start_interrupted();
 
@@ -180,6 +188,18 @@ protected:
 private:
   typedef typename librbd::journal::TypeTraits<ImageCtxT>::Journaler Journaler;
 
+  class BootstrapProgressContext : public ProgressContext {
+  public:
+    BootstrapProgressContext(ImageReplayer<ImageCtxT> *replayer) :
+      replayer(replayer) {
+    }
+
+    virtual void update_progress(const std::string &description,
+				 bool flush = true);
+  private:
+    ImageReplayer<ImageCtxT> *replayer;
+  };
+
   Threads *m_threads;
   RadosRef m_local, m_remote;
   std::string m_local_mirror_uuid;
@@ -189,18 +209,28 @@ private:
   std::string m_local_image_name;
   std::string m_name;
   Mutex m_lock;
-  State m_state;
+  State m_state = STATE_UNINITIALIZED;
+  int m_last_r = 0;
+  std::string m_state_desc;
+  BootstrapProgressContext m_progress_cxt;
+  image_replayer::ReplayStatusFormatter<ImageCtxT> *m_replay_status_formatter =
+    nullptr;
   librados::IoCtx m_local_ioctx, m_remote_ioctx;
-  ImageCtxT *m_local_image_ctx;
-  librbd::journal::Replay<ImageCtxT> *m_local_replay;
-  Journaler* m_remote_journaler;
-  ::journal::ReplayHandler *m_replay_handler;
+  ImageCtxT *m_local_image_ctx = nullptr;
+  librbd::journal::Replay<ImageCtxT> *m_local_replay = nullptr;
+  Journaler* m_remote_journaler = nullptr;
+  ::journal::ReplayHandler *m_replay_handler = nullptr;
 
   Context *m_on_start_finish = nullptr;
   Context *m_on_stop_finish = nullptr;
+  Context *m_update_status_task = nullptr;
+  int m_update_status_interval = 0;
+  librados::AioCompletion *m_update_status_comp = nullptr;
+  bool m_update_status_pending = false;
   bool m_stop_requested = false;
+  bool m_manual_stop = false;
 
-  AdminSocketHook *m_asok_hook;
+  AdminSocketHook *m_asok_hook = nullptr;
 
   librbd::journal::MirrorPeerClientMeta m_client_meta;
 
@@ -232,6 +262,11 @@ private:
 
   void shut_down_journal_replay(bool cancel_ops);
 
+  void update_mirror_image_status(bool final = false,
+				  State expected_state = STATE_UNKNOWN);
+  void reschedule_update_status_task(int new_interval = 0);
+  void start_update_status_task();
+
   void bootstrap();
   void handle_bootstrap(int r);
 
diff --git a/src/tools/rbd_mirror/ImageSync.cc b/src/tools/rbd_mirror/ImageSync.cc
index 5bb1457..f71dfdd 100644
--- a/src/tools/rbd_mirror/ImageSync.cc
+++ b/src/tools/rbd_mirror/ImageSync.cc
@@ -2,6 +2,7 @@
 // vim: ts=8 sw=2 smarttab
 
 #include "ImageSync.h"
+#include "ProgressContext.h"
 #include "common/errno.h"
 #include "journal/Journaler.h"
 #include "librbd/ImageCtx.h"
@@ -29,10 +30,12 @@ template <typename I>
 ImageSync<I>::ImageSync(I *local_image_ctx, I *remote_image_ctx,
                         SafeTimer *timer, Mutex *timer_lock,
                         const std::string &mirror_uuid, Journaler *journaler,
-                        MirrorPeerClientMeta *client_meta, Context *on_finish)
+                        MirrorPeerClientMeta *client_meta, Context *on_finish,
+			ProgressContext *progress_ctx)
   : m_local_image_ctx(local_image_ctx), m_remote_image_ctx(remote_image_ctx),
     m_timer(timer), m_timer_lock(timer_lock), m_mirror_uuid(mirror_uuid),
     m_journaler(journaler), m_client_meta(client_meta), m_on_finish(on_finish),
+    m_progress_ctx(progress_ctx),
     m_lock(unique_lock_name("ImageSync::m_lock", this)) {
 }
 
@@ -56,6 +59,8 @@ void ImageSync<I>::cancel() {
 
 template <typename I>
 void ImageSync<I>::send_prune_catch_up_sync_point() {
+  update_progress("PRUNE_CATCH_UP_SYNC_POINT");
+
   if (m_client_meta->sync_points.size() <= 1) {
     send_create_sync_point();
     return;
@@ -77,7 +82,7 @@ void ImageSync<I>::handle_prune_catch_up_sync_point(int r) {
   ldout(cct, 20) << ": r=" << r << dendl;
 
   if (r < 0) {
-    lderr(cct) << "failed to prune catch-up sync point: "
+    lderr(cct) << ": failed to prune catch-up sync point: "
                << cpp_strerror(r) << dendl;
     finish(r);
     return;
@@ -88,6 +93,8 @@ void ImageSync<I>::handle_prune_catch_up_sync_point(int r) {
 
 template <typename I>
 void ImageSync<I>::send_create_sync_point() {
+  update_progress("CREATE_SYNC_POINT");
+
   // TODO: when support for disconnecting laggy clients is added,
   //       re-connect and create catch-up sync point
   if (m_client_meta->sync_points.size() > 0) {
@@ -111,7 +118,7 @@ void ImageSync<I>::handle_create_sync_point(int r) {
   ldout(cct, 20) << ": r=" << r << dendl;
 
   if (r < 0) {
-    lderr(cct) << "failed to create sync point: " << cpp_strerror(r)
+    lderr(cct) << ": failed to create sync point: " << cpp_strerror(r)
                << dendl;
     finish(r);
     return;
@@ -122,6 +129,8 @@ void ImageSync<I>::handle_create_sync_point(int r) {
 
 template <typename I>
 void ImageSync<I>::send_copy_snapshots() {
+  update_progress("COPY_SNAPSHOTS");
+
   CephContext *cct = m_local_image_ctx->cct;
   ldout(cct, 20) << dendl;
 
@@ -139,7 +148,7 @@ void ImageSync<I>::handle_copy_snapshots(int r) {
   ldout(cct, 20) << ": r=" << r << dendl;
 
   if (r < 0) {
-    lderr(cct) << "failed to copy snapshot metadata: "
+    lderr(cct) << ": failed to copy snapshot metadata: "
                << cpp_strerror(r) << dendl;
     finish(r);
     return;
@@ -157,6 +166,8 @@ void ImageSync<I>::send_copy_image() {
     return;
   }
 
+  update_progress("COPY_IMAGE");
+
   CephContext *cct = m_local_image_ctx->cct;
   ldout(cct, 20) << dendl;
 
@@ -165,7 +176,7 @@ void ImageSync<I>::send_copy_image() {
   m_image_copy_request = ImageCopyRequest<I>::create(
     m_local_image_ctx, m_remote_image_ctx, m_timer, m_timer_lock,
     m_journaler, m_client_meta, &m_client_meta->sync_points.front(),
-    ctx);
+    ctx, m_progress_ctx);
   m_lock.Unlock();
 
   m_image_copy_request->send();
@@ -185,11 +196,11 @@ void ImageSync<I>::handle_copy_image(int r) {
   ldout(cct, 20) << ": r=" << r << dendl;
 
   if (r == -ECANCELED) {
-    ldout(cct, 10) << "image copy canceled" << dendl;
+    ldout(cct, 10) << ": image copy canceled" << dendl;
     finish(r);
     return;
   } else if (r < 0) {
-    lderr(cct) << "failed to copy image: " << cpp_strerror(r) << dendl;
+    lderr(cct) << ": failed to copy image: " << cpp_strerror(r) << dendl;
     finish(r);
     return;
   }
@@ -207,6 +218,8 @@ void ImageSync<I>::send_copy_object_map() {
     return;
   }
 
+  update_progress("COPY_OBJECT_MAP");
+
   assert(m_local_image_ctx->object_map != nullptr);
 
   assert(!m_client_meta->sync_points.empty());
@@ -242,6 +255,8 @@ void ImageSync<I>::send_refresh_object_map() {
   CephContext *cct = m_local_image_ctx->cct;
   ldout(cct, 20) << dendl;
 
+  update_progress("REFRESH_OBJECT_MAP");
+
   Context *ctx = create_context_callback<
     ImageSync<I>, &ImageSync<I>::handle_refresh_object_map>(this);
   m_object_map = m_local_image_ctx->create_object_map(CEPH_NOSNAP);
@@ -268,6 +283,8 @@ void ImageSync<I>::send_prune_sync_points() {
   CephContext *cct = m_local_image_ctx->cct;
   ldout(cct, 20) << dendl;
 
+  update_progress("PRUNE_SYNC_POINTS");
+
   Context *ctx = create_context_callback<
     ImageSync<I>, &ImageSync<I>::handle_prune_sync_points>(this);
   SyncPointPruneRequest<I> *request = SyncPointPruneRequest<I>::create(
@@ -281,7 +298,7 @@ void ImageSync<I>::handle_prune_sync_points(int r) {
   ldout(cct, 20) << ": r=" << r << dendl;
 
   if (r < 0) {
-    lderr(cct) << "failed to prune sync point: "
+    lderr(cct) << ": failed to prune sync point: "
                << cpp_strerror(r) << dendl;
     finish(r);
     return;
@@ -304,6 +321,15 @@ void ImageSync<I>::finish(int r) {
   delete this;
 }
 
+template <typename I>
+void ImageSync<I>::update_progress(const std::string &description) {
+  dout(20) << ": " << description << dendl;
+
+  if (m_progress_ctx) {
+    m_progress_ctx->update_progress("IMAGE_SYNC/" + description);
+  }
+}
+
 } // namespace mirror
 } // namespace rbd
 
diff --git a/src/tools/rbd_mirror/ImageSync.h b/src/tools/rbd_mirror/ImageSync.h
index 1ed2256..95809ab 100644
--- a/src/tools/rbd_mirror/ImageSync.h
+++ b/src/tools/rbd_mirror/ImageSync.h
@@ -20,6 +20,8 @@ namespace librbd { namespace journal { struct MirrorPeerClientMeta; } }
 namespace rbd {
 namespace mirror {
 
+class ProgressContext;
+
 namespace image_sync { template <typename> class ImageCopyRequest; }
 
 template <typename ImageCtxT = librbd::ImageCtx>
@@ -34,15 +36,17 @@ public:
                            Mutex *timer_lock, const std::string &mirror_uuid,
                            Journaler *journaler,
                            MirrorPeerClientMeta *client_meta,
-                           Context *on_finish) {
+                           Context *on_finish,
+			   ProgressContext *progress_ctx = nullptr) {
     return new ImageSync(local_image_ctx, remote_image_ctx, timer, timer_lock,
-                         mirror_uuid, journaler, client_meta, on_finish);
+                         mirror_uuid, journaler, client_meta, on_finish,
+			 progress_ctx);
   }
 
   ImageSync(ImageCtxT *local_image_ctx, ImageCtxT *remote_image_ctx,
             SafeTimer *timer, Mutex *timer_lock, const std::string &mirror_uuid,
             Journaler *journaler, MirrorPeerClientMeta *client_meta,
-            Context *on_finish);
+            Context *on_finish, ProgressContext *progress_ctx = nullptr);
 
   void start();
   void cancel();
@@ -91,6 +95,7 @@ private:
   Journaler *m_journaler;
   MirrorPeerClientMeta *m_client_meta;
   Context *m_on_finish;
+  ProgressContext *m_progress_ctx;
 
   SnapMap m_snap_map;
 
@@ -122,6 +127,8 @@ private:
   void handle_prune_sync_points(int r);
 
   void finish(int r);
+
+  void update_progress(const std::string &description);
 };
 
 } // namespace mirror
diff --git a/src/tools/rbd_mirror/Mirror.cc b/src/tools/rbd_mirror/Mirror.cc
index 5dd59be..9878780 100644
--- a/src/tools/rbd_mirror/Mirror.cc
+++ b/src/tools/rbd_mirror/Mirror.cc
@@ -50,6 +50,45 @@ private:
   Mirror *mirror;
 };
 
+class StartCommand : public MirrorAdminSocketCommand {
+public:
+  explicit StartCommand(Mirror *mirror) : mirror(mirror) {}
+
+  bool call(Formatter *f, stringstream *ss) {
+    mirror->start();
+    return true;
+  }
+
+private:
+  Mirror *mirror;
+};
+
+class StopCommand : public MirrorAdminSocketCommand {
+public:
+  explicit StopCommand(Mirror *mirror) : mirror(mirror) {}
+
+  bool call(Formatter *f, stringstream *ss) {
+    mirror->stop();
+    return true;
+  }
+
+private:
+  Mirror *mirror;
+};
+
+class RestartCommand : public MirrorAdminSocketCommand {
+public:
+  explicit RestartCommand(Mirror *mirror) : mirror(mirror) {}
+
+  bool call(Formatter *f, stringstream *ss) {
+    mirror->restart();
+    return true;
+  }
+
+private:
+  Mirror *mirror;
+};
+
 class FlushCommand : public MirrorAdminSocketCommand {
 public:
   explicit FlushCommand(Mirror *mirror) : mirror(mirror) {}
@@ -79,6 +118,27 @@ public:
       commands[command] = new StatusCommand(mirror);
     }
 
+    command = "rbd mirror start";
+    r = admin_socket->register_command(command, command, this,
+				       "start rbd mirror");
+    if (r == 0) {
+      commands[command] = new StartCommand(mirror);
+    }
+
+    command = "rbd mirror stop";
+    r = admin_socket->register_command(command, command, this,
+				       "stop rbd mirror");
+    if (r == 0) {
+      commands[command] = new StopCommand(mirror);
+    }
+
+    command = "rbd mirror restart";
+    r = admin_socket->register_command(command, command, this,
+				       "restart rbd mirror");
+    if (r == 0) {
+      commands[command] = new RestartCommand(mirror);
+    }
+
     command = "rbd mirror flush";
     r = admin_socket->register_command(command, command, this,
 				       "flush rbd mirror");
@@ -165,7 +225,9 @@ void Mirror::run()
   while (!m_stopping.read()) {
     m_local_cluster_watcher->refresh_pools();
     Mutex::Locker l(m_lock);
-    update_replayers(m_local_cluster_watcher->get_peer_configs());
+    if (!m_manual_stop) {
+      update_replayers(m_local_cluster_watcher->get_peer_configs());
+    }
     // TODO: make interval configurable
     m_cond.WaitInterval(g_ceph_context, m_lock, seconds(30));
   }
@@ -199,7 +261,24 @@ void Mirror::print_status(Formatter *f, stringstream *ss)
   }
 }
 
-void Mirror::flush()
+void Mirror::start()
+{
+  dout(20) << "enter" << dendl;
+  Mutex::Locker l(m_lock);
+
+  if (m_stopping.read()) {
+    return;
+  }
+
+  m_manual_stop = false;
+
+  for (auto it = m_replayers.begin(); it != m_replayers.end(); it++) {
+    auto &replayer = it->second;
+    replayer->start();
+  }
+}
+
+void Mirror::stop()
 {
   dout(20) << "enter" << dendl;
   Mutex::Locker l(m_lock);
@@ -208,6 +287,40 @@ void Mirror::flush()
     return;
   }
 
+  m_manual_stop = true;
+
+  for (auto it = m_replayers.begin(); it != m_replayers.end(); it++) {
+    auto &replayer = it->second;
+    replayer->stop();
+  }
+}
+
+void Mirror::restart()
+{
+  dout(20) << "enter" << dendl;
+  Mutex::Locker l(m_lock);
+
+  if (m_stopping.read()) {
+    return;
+  }
+
+  m_manual_stop = false;
+
+  for (auto it = m_replayers.begin(); it != m_replayers.end(); it++) {
+    auto &replayer = it->second;
+    replayer->restart();
+  }
+}
+
+void Mirror::flush()
+{
+  dout(20) << "enter" << dendl;
+  Mutex::Locker l(m_lock);
+
+  if (m_stopping.read() || m_manual_stop) {
+    return;
+  }
+
   for (auto it = m_replayers.begin(); it != m_replayers.end(); it++) {
     auto &replayer = it->second;
     replayer->flush();
diff --git a/src/tools/rbd_mirror/Mirror.h b/src/tools/rbd_mirror/Mirror.h
index a23c448..298f805 100644
--- a/src/tools/rbd_mirror/Mirror.h
+++ b/src/tools/rbd_mirror/Mirror.h
@@ -40,6 +40,9 @@ public:
   void handle_signal(int signum);
 
   void print_status(Formatter *f, stringstream *ss);
+  void start();
+  void stop();
+  void restart();
   void flush();
 
 private:
@@ -57,6 +60,7 @@ private:
   std::unique_ptr<ClusterWatcher> m_local_cluster_watcher;
   std::map<peer_t, std::unique_ptr<Replayer> > m_replayers;
   atomic_t m_stopping;
+  bool m_manual_stop = false;
   MirrorAdminSocketHook *m_asok_hook;
 };
 
diff --git a/src/tools/rbd_mirror/ProgressContext.h b/src/tools/rbd_mirror/ProgressContext.h
new file mode 100644
index 0000000..e4430ee
--- /dev/null
+++ b/src/tools/rbd_mirror/ProgressContext.h
@@ -0,0 +1,21 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef RBD_MIRROR_PROGRESS_CONTEXT_H
+#define RBD_MIRROR_PROGRESS_CONTEXT_H
+
+namespace rbd {
+namespace mirror {
+
+class ProgressContext
+{
+public:
+  virtual ~ProgressContext() {}
+  virtual void update_progress(const std::string &description,
+			       bool flush = true) = 0;
+};
+
+} // namespace mirror
+} // namespace rbd
+
+#endif // RBD_MIRROR_PROGRESS_CONTEXT_H
diff --git a/src/tools/rbd_mirror/Replayer.cc b/src/tools/rbd_mirror/Replayer.cc
index d73bc10..54a003d 100644
--- a/src/tools/rbd_mirror/Replayer.cc
+++ b/src/tools/rbd_mirror/Replayer.cc
@@ -12,7 +12,9 @@
 #include "common/errno.h"
 #include "include/stringify.h"
 #include "cls/rbd/cls_rbd_client.h"
+#include "librbd/ObjectWatcher.h"
 #include "Replayer.h"
+#include "Threads.h"
 
 #define dout_subsys ceph_subsys_rbd_mirror
 #undef dout_prefix
@@ -48,6 +50,45 @@ private:
   Replayer *replayer;
 };
 
+class StartCommand : public ReplayerAdminSocketCommand {
+public:
+  explicit StartCommand(Replayer *replayer) : replayer(replayer) {}
+
+  bool call(Formatter *f, stringstream *ss) {
+    replayer->start();
+    return true;
+  }
+
+private:
+  Replayer *replayer;
+};
+
+class StopCommand : public ReplayerAdminSocketCommand {
+public:
+  explicit StopCommand(Replayer *replayer) : replayer(replayer) {}
+
+  bool call(Formatter *f, stringstream *ss) {
+    replayer->stop();
+    return true;
+  }
+
+private:
+  Replayer *replayer;
+};
+
+class RestartCommand : public ReplayerAdminSocketCommand {
+public:
+  explicit RestartCommand(Replayer *replayer) : replayer(replayer) {}
+
+  bool call(Formatter *f, stringstream *ss) {
+    replayer->restart();
+    return true;
+  }
+
+private:
+  Replayer *replayer;
+};
+
 class FlushCommand : public ReplayerAdminSocketCommand {
 public:
   explicit FlushCommand(Replayer *replayer) : replayer(replayer) {}
@@ -78,6 +119,27 @@ public:
       commands[command] = new StatusCommand(replayer);
     }
 
+    command = "rbd mirror start " + name;
+    r = admin_socket->register_command(command, command, this,
+				       "start rbd mirror " + name);
+    if (r == 0) {
+      commands[command] = new StartCommand(replayer);
+    }
+
+    command = "rbd mirror stop " + name;
+    r = admin_socket->register_command(command, command, this,
+				       "stop rbd mirror " + name);
+    if (r == 0) {
+      commands[command] = new StopCommand(replayer);
+    }
+
+    command = "rbd mirror restart " + name;
+    r = admin_socket->register_command(command, command, this,
+				       "restart rbd mirror " + name);
+    if (r == 0) {
+      commands[command] = new RestartCommand(replayer);
+    }
+
     command = "rbd mirror flush " + name;
     r = admin_socket->register_command(command, command, this,
 				       "flush rbd mirror " + name);
@@ -113,6 +175,55 @@ private:
   Commands commands;
 };
 
+class MirrorStatusWatchCtx {
+public:
+  MirrorStatusWatchCtx(librados::IoCtx &ioctx, ContextWQ *work_queue) {
+    m_ioctx.dup(ioctx);
+    m_watcher = new Watcher(m_ioctx, work_queue);
+  }
+
+  ~MirrorStatusWatchCtx() {
+    delete m_watcher;
+  }
+
+  int register_watch() {
+    C_SaferCond cond;
+    m_watcher->register_watch(&cond);
+    return cond.wait();
+  }
+
+  int unregister_watch() {
+    C_SaferCond cond;
+    m_watcher->unregister_watch(&cond);
+    return cond.wait();
+  }
+
+  std::string get_oid() const {
+    return m_watcher->get_oid();
+  }
+
+private:
+  class Watcher : public librbd::ObjectWatcher<> {
+  public:
+    Watcher(librados::IoCtx &ioctx, ContextWQ *work_queue) :
+      ObjectWatcher<>(ioctx, work_queue) {
+    }
+
+    virtual std::string get_oid() const {
+      return RBD_MIRRORING;
+    }
+
+    virtual void handle_notify(uint64_t notify_id, uint64_t handle,
+			       bufferlist &bl) {
+      bufferlist out;
+      acknowledge_notify(notify_id, handle, out);
+    }
+  };
+
+  librados::IoCtx m_ioctx;
+  Watcher *m_watcher;
+};
+
 Replayer::Replayer(Threads *threads, RadosRef local_cluster,
                    const peer_t &peer, const std::vector<const char*> &args) :
   m_threads(threads),
@@ -224,7 +335,9 @@ void Replayer::run()
 
   while (!m_stopping.read()) {
     Mutex::Locker l(m_lock);
-    set_sources(m_pool_watcher->get_images());
+    if (!m_manual_stop) {
+      set_sources(m_pool_watcher->get_images());
+    }
     m_cond.WaitInterval(g_ceph_context, m_lock, seconds(30));
   }
 
@@ -267,7 +380,28 @@ void Replayer::print_status(Formatter *f, stringstream *ss)
   }
 }
 
-void Replayer::flush()
+void Replayer::start()
+{
+  dout(20) << "enter" << dendl;
+
+  Mutex::Locker l(m_lock);
+
+  if (m_stopping.read()) {
+    return;
+  }
+
+  m_manual_stop = false;
+
+  for (auto it = m_images.begin(); it != m_images.end(); it++) {
+    auto &pool_images = it->second;
+    for (auto i = pool_images.begin(); i != pool_images.end(); i++) {
+      auto &image_replayer = i->second;
+      image_replayer->start(nullptr, nullptr, true);
+    }
+  }
+}
+
+void Replayer::stop()
 {
   dout(20) << "enter" << dendl;
 
@@ -277,6 +411,48 @@ void Replayer::flush()
     return;
   }
 
+  m_manual_stop = true;
+
+  for (auto it = m_images.begin(); it != m_images.end(); it++) {
+    auto &pool_images = it->second;
+    for (auto i = pool_images.begin(); i != pool_images.end(); i++) {
+      auto &image_replayer = i->second;
+      image_replayer->stop(nullptr, true);
+    }
+  }
+}
+
+void Replayer::restart()
+{
+  dout(20) << "enter" << dendl;
+
+  Mutex::Locker l(m_lock);
+
+  if (m_stopping.read()) {
+    return;
+  }
+
+  m_manual_stop = false;
+
+  for (auto it = m_images.begin(); it != m_images.end(); it++) {
+    auto &pool_images = it->second;
+    for (auto i = pool_images.begin(); i != pool_images.end(); i++) {
+      auto &image_replayer = i->second;
+      image_replayer->restart();
+    }
+  }
+}
+
+void Replayer::flush()
+{
+  dout(20) << "enter" << dendl;
+
+  Mutex::Locker l(m_lock);
+
+  if (m_stopping.read() || m_manual_stop) {
+    return;
+  }
+
   for (auto it = m_images.begin(); it != m_images.end(); it++) {
     auto &pool_images = it->second;
     for (auto i = pool_images.begin(); i != pool_images.end(); i++) {
@@ -306,6 +482,7 @@ void Replayer::set_sources(const PoolImageIds &pool_image_ids)
         }
       }
       if (pool_images.empty()) {
+	mirror_image_status_shut_down(pool_id);
 	it = m_images.erase(it);
       } else {
         ++it;
@@ -369,6 +546,14 @@ void Replayer::set_sources(const PoolImageIds &pool_image_ids)
 
     // create entry for pool if it doesn't exist
     auto &pool_replayers = m_images[pool_id];
+
+    if (pool_replayers.empty()) {
+      r = mirror_image_status_init(pool_id, local_ioctx);
+      if (r < 0) {
+	continue;
+      }
+    }
+
     for (const auto &image_id : kv.second) {
       auto it = pool_replayers.find(image_id.id);
       if (it == pool_replayers.end()) {
@@ -383,6 +568,51 @@ void Replayer::set_sources(const PoolImageIds &pool_image_ids)
   }
 }
 
+int Replayer::mirror_image_status_init(int64_t pool_id,
+				       librados::IoCtx& ioctx) {
+  assert(m_status_watchers.find(pool_id) == m_status_watchers.end());
+
+  uint64_t instance_id = librados::Rados(ioctx).get_instance_id();
+
+  dout(20) << "pool_id=" << pool_id << ", instance_id=" << instance_id << dendl;
+
+  librados::ObjectWriteOperation op;
+  librbd::cls_client::mirror_image_status_remove_down(&op);
+  int r = ioctx.operate(RBD_MIRRORING, &op);
+  if (r < 0) {
+    derr << "error initializing " << RBD_MIRRORING << "object: "
+	 << cpp_strerror(r) << dendl;
+    return r;
+  }
+
+  unique_ptr<MirrorStatusWatchCtx>
+    watch_ctx(new MirrorStatusWatchCtx(ioctx, m_threads->work_queue));
+
+  r = watch_ctx->register_watch();
+  if (r < 0) {
+    derr << "error registering watcher for " << watch_ctx->get_oid()
+	 << " object: " << cpp_strerror(r) << dendl;
+    return r;
+  }
+
+  m_status_watchers.insert(std::make_pair(pool_id, std::move(watch_ctx)));
+
+  return 0;
+}
+
+void Replayer::mirror_image_status_shut_down(int64_t pool_id) {
+  auto watcher_it = m_status_watchers.find(pool_id);
+  assert(watcher_it != m_status_watchers.end());
+
+  int r = watcher_it->second->unregister_watch();
+  if (r < 0) {
+    derr << "error unregistering watcher for " << watcher_it->second->get_oid()
+	 << " object: " << cpp_strerror(r) << dendl;
+  }
+
+  m_status_watchers.erase(watcher_it);
+}
+
 void Replayer::start_image_replayer(unique_ptr<ImageReplayer<> > &image_replayer)
 {
   if (!image_replayer->is_stopped()) {
diff --git a/src/tools/rbd_mirror/Replayer.h b/src/tools/rbd_mirror/Replayer.h
index f7c623b..0dcd5ed 100644
--- a/src/tools/rbd_mirror/Replayer.h
+++ b/src/tools/rbd_mirror/Replayer.h
@@ -25,6 +25,7 @@ namespace mirror {
 
 struct Threads;
 class ReplayerAdminSocketHook;
+class MirrorStatusWatchCtx;
 
 /**
  * Controls mirroring for a single remote cluster.
@@ -41,6 +42,9 @@ public:
   void run();
 
   void print_status(Formatter *f, stringstream *ss);
+  void start();
+  void stop();
+  void restart();
   void flush();
 
 private:
@@ -52,10 +56,14 @@ private:
   void start_image_replayer(unique_ptr<ImageReplayer<> > &image_replayer);
   bool stop_image_replayer(unique_ptr<ImageReplayer<> > &image_replayer);
 
+  int mirror_image_status_init(int64_t pool_id, librados::IoCtx& ioctx);
+  void mirror_image_status_shut_down(int64_t pool_id);
+
   Threads *m_threads;
   Mutex m_lock;
   Cond m_cond;
   atomic_t m_stopping;
+  bool m_manual_stop = false;
 
   peer_t m_peer;
   std::vector<const char*> m_args;
@@ -65,6 +73,7 @@ private:
   // when a pool's configuration changes
   std::map<int64_t, std::map<std::string,
 			     std::unique_ptr<ImageReplayer<> > > > m_images;
+  std::map<int64_t, std::unique_ptr<MirrorStatusWatchCtx> > m_status_watchers;
   ReplayerAdminSocketHook *m_asok_hook;
 
   class ReplayerThread : public Thread {
diff --git a/src/tools/rbd_mirror/image_replayer/BootstrapRequest.cc b/src/tools/rbd_mirror/image_replayer/BootstrapRequest.cc
index 92f98c0..6f7210e 100644
--- a/src/tools/rbd_mirror/image_replayer/BootstrapRequest.cc
+++ b/src/tools/rbd_mirror/image_replayer/BootstrapRequest.cc
@@ -17,6 +17,7 @@
 #include "librbd/Utils.h"
 #include "librbd/journal/Types.h"
 #include "tools/rbd_mirror/ImageSync.h"
+#include "tools/rbd_mirror/ProgressContext.h"
 
 #define dout_subsys ceph_subsys_rbd_mirror
 #undef dout_prefix
@@ -95,14 +96,16 @@ BootstrapRequest<I>::BootstrapRequest(librados::IoCtx &local_io_ctx,
                                       const std::string &remote_mirror_uuid,
                                       Journaler *journaler,
                                       MirrorPeerClientMeta *client_meta,
-                                      Context *on_finish)
+                                      Context *on_finish,
+				      rbd::mirror::ProgressContext *progress_ctx)
   : m_local_io_ctx(local_io_ctx), m_remote_io_ctx(remote_io_ctx),
     m_local_image_ctx(local_image_ctx), m_local_image_name(local_image_name),
     m_remote_image_id(remote_image_id), m_global_image_id(global_image_id),
     m_work_queue(work_queue), m_timer(timer), m_timer_lock(timer_lock),
     m_local_mirror_uuid(local_mirror_uuid),
     m_remote_mirror_uuid(remote_mirror_uuid), m_journaler(journaler),
-    m_client_meta(client_meta), m_on_finish(on_finish) {
+    m_client_meta(client_meta), m_on_finish(on_finish),
+    m_progress_ctx(progress_ctx) {
 }
 
 template <typename I>
@@ -119,6 +122,8 @@ template <typename I>
 void BootstrapRequest<I>::get_local_image_id() {
   dout(20) << dendl;
 
+  update_progress("GET_LOCAL_IMAGE_ID");
+
   // attempt to cross-reference a local image by the global image id
   librados::ObjectReadOperation op;
   librbd::cls_client::mirror_image_get_image_id_start(&op, m_global_image_id);
@@ -156,6 +161,8 @@ template <typename I>
 void BootstrapRequest<I>::get_remote_tag_class() {
   dout(20) << dendl;
 
+  update_progress("GET_REMOTE_TAG_CLASS");
+
   Context *ctx = create_context_callback<
     BootstrapRequest<I>, &BootstrapRequest<I>::handle_get_remote_tag_class>(
       this);
@@ -201,6 +208,8 @@ template <typename I>
 void BootstrapRequest<I>::get_client() {
   dout(20) << dendl;
 
+  update_progress("GET_CLIENT");
+
   Context *ctx = create_context_callback<
     BootstrapRequest<I>, &BootstrapRequest<I>::handle_get_client>(
       this);
@@ -230,6 +239,8 @@ template <typename I>
 void BootstrapRequest<I>::register_client() {
   dout(20) << dendl;
 
+  update_progress("REGISTER_CLIENT");
+
   // record an place-holder record
   librbd::journal::ClientData client_data{
     librbd::journal::MirrorPeerClientMeta{m_local_image_id}};
@@ -261,6 +272,8 @@ template <typename I>
 void BootstrapRequest<I>::open_remote_image() {
   dout(20) << dendl;
 
+  update_progress("OPEN_REMOTE_IMAGE");
+
   m_remote_image_ctx = I::create("", m_remote_image_id, nullptr,
                                  m_remote_io_ctx, false);
   Context *ctx = create_context_callback<
@@ -319,6 +332,8 @@ template <typename I>
 void BootstrapRequest<I>::open_local_image() {
   dout(20) << dendl;
 
+  update_progress("OPEN_LOCAL_IMAGE");
+
   Context *ctx = create_context_callback<
     BootstrapRequest<I>, &BootstrapRequest<I>::handle_open_local_image>(
       this);
@@ -359,6 +374,8 @@ template <typename I>
 void BootstrapRequest<I>::remove_local_image() {
   dout(20) << dendl;
 
+  update_progress("REMOVE_LOCAL_IMAGE");
+
   // TODO
 }
 
@@ -373,6 +390,8 @@ template <typename I>
 void BootstrapRequest<I>::create_local_image() {
   dout(20) << dendl;
 
+  update_progress("CREATE_LOCAL_IMAGE");
+
   // TODO: librbd should provide an AIO image creation method -- this is
   //       blocking so we execute in our worker thread
   Context *ctx = create_context_callback<
@@ -401,6 +420,10 @@ void BootstrapRequest<I>::handle_create_local_image(int r) {
 
 template <typename I>
 void BootstrapRequest<I>::update_client() {
+  dout(20) << dendl;
+
+  update_progress("UPDATE_CLIENT");
+
   if (m_client_meta->image_id == (*m_local_image_ctx)->id) {
     // already registered local image with remote journal
     get_remote_tags();
@@ -440,6 +463,10 @@ void BootstrapRequest<I>::handle_update_client(int r) {
 
 template <typename I>
 void BootstrapRequest<I>::get_remote_tags() {
+  dout(20) << dendl;
+
+  update_progress("GET_REMOTE_TAGS");
+
   if (m_created_local_image) {
     // optimization -- no need to compare remote tags if we just created
     // the image locally
@@ -503,8 +530,8 @@ void BootstrapRequest<I>::handle_get_remote_tags(int r) {
       local_image_ctx->journal->get_tag_data();
     dout(20) << ": local tag data: " << tag_data << dendl;
 
-    if (!((tag_data.mirror_uuid == librbd::Journal<I>::ORPHAN_MIRROR_UUID &&
-           remote_tag_data.mirror_uuid == librbd::Journal<I>::ORPHAN_MIRROR_UUID &&
+    if (!((tag_data.mirror_uuid == librbd::Journal<>::ORPHAN_MIRROR_UUID &&
+           remote_tag_data.mirror_uuid == librbd::Journal<>::ORPHAN_MIRROR_UUID &&
            remote_tag_data.predecessor_mirror_uuid == m_local_mirror_uuid) ||
           (tag_data.mirror_uuid == m_remote_mirror_uuid &&
            m_client_meta->state == librbd::journal::MIRROR_PEER_STATE_REPLAYING))) {
@@ -520,6 +547,10 @@ void BootstrapRequest<I>::handle_get_remote_tags(int r) {
 
 template <typename I>
 void BootstrapRequest<I>::image_sync() {
+  dout(20) << dendl;
+
+  update_progress("IMAGE_SYNC");
+
   if (m_client_meta->state == librbd::journal::MIRROR_PEER_STATE_REPLAYING) {
     // clean replay state -- no image sync required
     close_remote_image();
@@ -535,7 +566,8 @@ void BootstrapRequest<I>::image_sync() {
                                                m_remote_image_ctx, m_timer,
                                                m_timer_lock,
                                                m_local_mirror_uuid, m_journaler,
-                                               m_client_meta, ctx);
+                                               m_client_meta, ctx,
+					       m_progress_ctx);
   request->start();
 }
 
@@ -555,6 +587,8 @@ template <typename I>
 void BootstrapRequest<I>::close_local_image() {
   dout(20) << dendl;
 
+  update_progress("CLOSE_LOCAL_IMAGE");
+
   Context *ctx = create_context_callback<
     BootstrapRequest<I>, &BootstrapRequest<I>::handle_close_local_image>(
       this);
@@ -579,6 +613,8 @@ template <typename I>
 void BootstrapRequest<I>::close_remote_image() {
   dout(20) << dendl;
 
+  update_progress("CLOSE_REMOTE_IMAGE");
+
   Context *ctx = create_context_callback<
     BootstrapRequest<I>, &BootstrapRequest<I>::handle_close_remote_image>(
       this);
@@ -636,6 +672,15 @@ bool BootstrapRequest<I>::decode_client_meta() {
   return true;
 }
 
+template <typename I>
+void BootstrapRequest<I>::update_progress(const std::string &description) {
+  dout(20) << ": " << description << dendl;
+
+  if (m_progress_ctx) {
+    m_progress_ctx->update_progress(description);
+  }
+}
+
 } // namespace image_replayer
 } // namespace mirror
 } // namespace rbd
diff --git a/src/tools/rbd_mirror/image_replayer/BootstrapRequest.h b/src/tools/rbd_mirror/image_replayer/BootstrapRequest.h
index bf9629f..0f6a79e 100644
--- a/src/tools/rbd_mirror/image_replayer/BootstrapRequest.h
+++ b/src/tools/rbd_mirror/image_replayer/BootstrapRequest.h
@@ -21,6 +21,9 @@ namespace librbd { namespace journal { struct MirrorPeerClientMeta; } }
 
 namespace rbd {
 namespace mirror {
+
+class ProgressContext;
+
 namespace image_replayer {
 
 template <typename ImageCtxT = librbd::ImageCtx>
@@ -29,6 +32,7 @@ public:
   typedef librbd::journal::TypeTraits<ImageCtxT> TypeTraits;
   typedef typename TypeTraits::Journaler Journaler;
   typedef librbd::journal::MirrorPeerClientMeta MirrorPeerClientMeta;
+  typedef rbd::mirror::ProgressContext ProgressContext;
 
   static BootstrapRequest* create(librados::IoCtx &local_io_ctx,
                                   librados::IoCtx &remote_io_ctx,
@@ -42,12 +46,14 @@ public:
                                   const std::string &remote_mirror_uuid,
                                   Journaler *journaler,
                                   MirrorPeerClientMeta *client_meta,
-                                  Context *on_finish) {
+                                  Context *on_finish,
+				  ProgressContext *progress_ctx = nullptr) {
     return new BootstrapRequest(local_io_ctx, remote_io_ctx, local_image_ctx,
                                 local_image_name, remote_image_id,
                                 global_image_id, work_queue, timer, timer_lock,
                                 local_mirror_uuid, remote_mirror_uuid,
-                                journaler, client_meta, on_finish);
+                                journaler, client_meta, on_finish,
+				progress_ctx);
   }
 
   BootstrapRequest(librados::IoCtx &local_io_ctx,
@@ -59,7 +65,8 @@ public:
                    SafeTimer *timer, Mutex *timer_lock,
                    const std::string &local_mirror_uuid,
                    const std::string &remote_mirror_uuid, Journaler *journaler,
-                   MirrorPeerClientMeta *client_meta, Context *on_finish);
+                   MirrorPeerClientMeta *client_meta, Context *on_finish,
+		   ProgressContext *progress_ctx = nullptr);
   ~BootstrapRequest();
 
   void send();
@@ -136,6 +143,7 @@ private:
   Journaler *m_journaler;
   MirrorPeerClientMeta *m_client_meta;
   Context *m_on_finish;
+  ProgressContext *m_progress_ctx;
 
   Tags m_remote_tags;
   cls::journal::Client m_client;
@@ -188,6 +196,8 @@ private:
   void finish(int r);
 
   bool decode_client_meta();
+
+  void update_progress(const std::string &description);
 };
 
 } // namespace image_replayer
diff --git a/src/tools/rbd_mirror/image_replayer/ReplayStatusFormatter.cc b/src/tools/rbd_mirror/image_replayer/ReplayStatusFormatter.cc
new file mode 100644
index 0000000..303fb58
--- /dev/null
+++ b/src/tools/rbd_mirror/image_replayer/ReplayStatusFormatter.cc
@@ -0,0 +1,240 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "ReplayStatusFormatter.h"
+#include "common/debug.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "journal/Journaler.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/Journal.h"
+#include "librbd/Utils.h"
+
+#define dout_subsys ceph_subsys_rbd_mirror
+#undef dout_prefix
+#define dout_prefix *_dout << "rbd::mirror::image_replayer::ReplayStatusFormatter: " \
+    << this << " " << __func__ << ": "
+
+namespace rbd {
+namespace mirror {
+namespace image_replayer {
+
+using librbd::util::unique_lock_name;
+
+template <typename I>
+ReplayStatusFormatter<I>::ReplayStatusFormatter(Journaler *journaler,
+						const std::string &mirror_uuid)
+  : m_journaler(journaler),
+    m_mirror_uuid(mirror_uuid),
+    m_lock(unique_lock_name("ReplayStatusFormatter::m_lock", this)) {
+}
+
+template <typename I>
+bool ReplayStatusFormatter<I>::get_or_send_update(std::string *description,
+						  Context *on_finish) {
+  dout(20) << dendl;
+
+  bool in_progress = false;
+  {
+    Mutex::Locker locker(m_lock);
+    if (m_on_finish) {
+      in_progress = true;
+    } else {
+      m_on_finish = on_finish;
+    }
+  }
+
+  if (in_progress) {
+    dout(10) << "previous request is still in progress, ignoring" << dendl;
+    on_finish->complete(-EAGAIN);
+    return false;
+  }
+
+  m_master_position = cls::journal::ObjectPosition();
+  m_mirror_position = cls::journal::ObjectPosition();
+
+  cls::journal::Client master_client, mirror_client;
+  int r;
+
+  r = m_journaler->get_cached_client(librbd::Journal<>::IMAGE_CLIENT_ID,
+                                     &master_client);
+  if (r < 0) {
+    derr << "error retrieving registered master client: "
+	 << cpp_strerror(r) << dendl;
+  } else {
+    r = m_journaler->get_cached_client(m_mirror_uuid, &mirror_client);
+    if (r < 0) {
+      derr << "error retrieving registered mirror client: "
+	   << cpp_strerror(r) << dendl;
+    }
+  }
+
+  if (!master_client.commit_position.object_positions.empty()) {
+    m_master_position =
+      *(master_client.commit_position.object_positions.begin());
+  }
+
+  if (!mirror_client.commit_position.object_positions.empty()) {
+    m_mirror_position =
+      *(mirror_client.commit_position.object_positions.begin());
+  }
+
+  if (!calculate_behind_master_or_send_update()) {
+    dout(20) << "need to update tag cache" << dendl;
+    return false;
+  }
+
+  format(description);
+
+  {
+    Mutex::Locker locker(m_lock);
+    assert(m_on_finish == on_finish);
+    m_on_finish = nullptr;
+  }
+
+  on_finish->complete(-EEXIST);
+  return true;
+}
+
+template <typename I>
+bool ReplayStatusFormatter<I>::calculate_behind_master_or_send_update() {
+  dout(20) << "m_master_position=" << m_master_position
+	   << ", m_mirror_position=" << m_mirror_position << dendl;
+
+  m_entries_behind_master = 0;
+
+  if (m_master_position == cls::journal::ObjectPosition()) {
+    return true;
+  }
+
+  cls::journal::ObjectPosition master = m_master_position;
+  uint64_t mirror_tag_tid = m_mirror_position.tag_tid;
+
+  while (master.tag_tid != mirror_tag_tid) {
+    auto tag_it = m_tag_cache.find(master.tag_tid);
+    if (tag_it == m_tag_cache.end()) {
+      send_update_tag_cache(master.tag_tid, mirror_tag_tid);
+      return false;
+    }
+    librbd::journal::TagData &tag_data = tag_it->second;
+    m_entries_behind_master += master.entry_tid;
+    master = cls::journal::ObjectPosition(0, tag_data.predecessor_tag_tid,
+					  tag_data.predecessor_entry_tid);
+  }
+  m_entries_behind_master += master.entry_tid - m_mirror_position.entry_tid;
+
+  dout(20) << "clearing tags not needed any more (below mirror position)"
+	   << dendl;
+
+  uint64_t tag_tid = mirror_tag_tid;
+  size_t old_size = m_tag_cache.size();
+  while (tag_tid != 0) {
+    auto tag_it = m_tag_cache.find(tag_tid);
+    if (tag_it == m_tag_cache.end()) {
+      break;
+    }
+    librbd::journal::TagData &tag_data = tag_it->second;
+
+    dout(20) << "erasing tag " <<  tag_data << "for tag_tid " << tag_tid
+	     << dendl;
+
+    tag_tid = tag_data.predecessor_tag_tid;
+    m_tag_cache.erase(tag_it);
+  }
+
+  dout(20) << old_size - m_tag_cache.size() << " entries cleared" << dendl;
+
+  return true;
+}
+
+template <typename I>
+void ReplayStatusFormatter<I>::send_update_tag_cache(uint64_t master_tag_tid,
+						     uint64_t mirror_tag_tid) {
+
+  dout(20) << "master_tag_tid=" << master_tag_tid << ", mirror_tag_tid="
+	   << mirror_tag_tid << dendl;
+
+  if (master_tag_tid == mirror_tag_tid) {
+    Context *on_finish = nullptr;
+    {
+      Mutex::Locker locker(m_lock);
+      std::swap(m_on_finish, on_finish);
+    }
+
+    assert(on_finish);
+    on_finish->complete(0);
+    return;
+  }
+
+  FunctionContext *ctx = new FunctionContext(
+    [this, master_tag_tid, mirror_tag_tid](int r) {
+      handle_update_tag_cache(master_tag_tid, mirror_tag_tid, r);
+    });
+  m_journaler->get_tag(master_tag_tid, &m_tag, ctx);
+}
+
+template <typename I>
+void ReplayStatusFormatter<I>::handle_update_tag_cache(uint64_t master_tag_tid,
+						       uint64_t mirror_tag_tid,
+						       int r) {
+  librbd::journal::TagData tag_data;
+
+  if (r < 0) {
+    derr << "error retrieving tag " << master_tag_tid << ": " << cpp_strerror(r)
+	 << dendl;
+  } else {
+    dout(20) << "retrieved tag " << master_tag_tid << ": " << m_tag << dendl;
+
+    bufferlist::iterator it = m_tag.data.begin();
+    try {
+      ::decode(tag_data, it);
+    } catch (const buffer::error &err) {
+      derr << "error decoding tag " << master_tag_tid << ": " << err.what()
+	   << dendl;
+    }
+  }
+
+  if (tag_data.predecessor_tag_tid == 0) {
+    // We failed. Don't consider this fatal, just terminate retrieving.
+    dout(20) << "making fake tag" << dendl;
+    tag_data.predecessor_tag_tid = mirror_tag_tid;
+  }
+
+  dout(20) << "decoded tag " << master_tag_tid << ": " << tag_data << dendl;
+
+  m_tag_cache.insert(std::make_pair(master_tag_tid, tag_data));
+  send_update_tag_cache(tag_data.predecessor_tag_tid, mirror_tag_tid);
+}
+
+template <typename I>
+void ReplayStatusFormatter<I>::format(std::string *description) {
+
+  dout(20) << "m_master_position=" << m_master_position
+	   << ", m_mirror_position=" << m_mirror_position
+	   << ", m_entries_behind_master=" << m_entries_behind_master << dendl;
+
+  std::stringstream ss;
+  ss << "master_position=";
+  if (m_master_position == cls::journal::ObjectPosition()) {
+    ss << "[]";
+  } else {
+    ss << m_master_position;
+  }
+  ss << ", mirror_position=";
+  if (m_mirror_position == cls::journal::ObjectPosition()) {
+    ss << "[]";
+  } else {
+    ss << m_mirror_position;
+  }
+  ss << ", entries_behind_master="
+     << (m_entries_behind_master > 0 ? m_entries_behind_master : 0);
+
+  *description = ss.str();
+}
+
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
+
+template class
+rbd::mirror::image_replayer::ReplayStatusFormatter<librbd::ImageCtx>;
diff --git a/src/tools/rbd_mirror/image_replayer/ReplayStatusFormatter.h b/src/tools/rbd_mirror/image_replayer/ReplayStatusFormatter.h
new file mode 100644
index 0000000..00d7a05
--- /dev/null
+++ b/src/tools/rbd_mirror/image_replayer/ReplayStatusFormatter.h
@@ -0,0 +1,56 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef RBD_MIRROR_IMAGE_REPLAYER_REPLAY_STATUS_FORMATTER_H
+#define RBD_MIRROR_IMAGE_REPLAYER_REPLAY_STATUS_FORMATTER_H
+
+#include "include/Context.h"
+#include "common/Mutex.h"
+#include "cls/journal/cls_journal_types.h"
+#include "librbd/journal/Types.h"
+#include "librbd/journal/TypeTraits.h"
+
+namespace journal { class Journaler; }
+namespace librbd { class ImageCtx; }
+
+namespace rbd {
+namespace mirror {
+namespace image_replayer {
+
+template <typename ImageCtxT = librbd::ImageCtx>
+class ReplayStatusFormatter {
+public:
+  typedef typename librbd::journal::TypeTraits<ImageCtxT>::Journaler Journaler;
+
+  static ReplayStatusFormatter* create(Journaler *journaler,
+				       const std::string &mirror_uuid) {
+    return new ReplayStatusFormatter(journaler, mirror_uuid);
+  }
+
+  ReplayStatusFormatter(Journaler *journaler, const std::string &mirror_uuid);
+
+  bool get_or_send_update(std::string *description, Context *on_finish);
+
+private:
+  Journaler *m_journaler;
+  std::string m_mirror_uuid;
+  Mutex m_lock;
+  Context *m_on_finish = nullptr;
+  cls::journal::ObjectPosition m_master_position;
+  cls::journal::ObjectPosition m_mirror_position;
+  int m_entries_behind_master = 0;
+  cls::journal::Tag m_tag;
+  std::map<uint64_t, librbd::journal::TagData> m_tag_cache;
+
+  bool calculate_behind_master_or_send_update();
+  void send_update_tag_cache(uint64_t master_tag_tid, uint64_t mirror_tag_tid);
+  void handle_update_tag_cache(uint64_t master_tag_tid, uint64_t mirror_tag_tid,
+			       int r);
+  void format(std::string *description);
+};
+
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
+
+#endif // RBD_MIRROR_IMAGE_REPLAYER_REPLAY_STATUS_FORMATTER_H
diff --git a/src/tools/rbd_mirror/image_sync/ImageCopyRequest.cc b/src/tools/rbd_mirror/image_sync/ImageCopyRequest.cc
index f627f17..df41d81 100644
--- a/src/tools/rbd_mirror/image_sync/ImageCopyRequest.cc
+++ b/src/tools/rbd_mirror/image_sync/ImageCopyRequest.cc
@@ -3,9 +3,11 @@
 
 #include "ImageCopyRequest.h"
 #include "ObjectCopyRequest.h"
+#include "include/stringify.h"
 #include "common/errno.h"
 #include "journal/Journaler.h"
 #include "librbd/Utils.h"
+#include "tools/rbd_mirror/ProgressContext.h"
 
 #define dout_subsys ceph_subsys_rbd_mirror
 #undef dout_prefix
@@ -25,11 +27,12 @@ ImageCopyRequest<I>::ImageCopyRequest(I *local_image_ctx, I *remote_image_ctx,
                                       Journaler *journaler,
                                       MirrorPeerClientMeta *client_meta,
                                       MirrorPeerSyncPoint *sync_point,
-                                      Context *on_finish)
+                                      Context *on_finish,
+				      ProgressContext *progress_ctx)
   : m_local_image_ctx(local_image_ctx), m_remote_image_ctx(remote_image_ctx),
     m_timer(timer), m_timer_lock(timer_lock), m_journaler(journaler),
     m_client_meta(client_meta), m_sync_point(sync_point),
-    m_on_finish(on_finish),
+    m_on_finish(on_finish), m_progress_ctx(progress_ctx),
     m_lock(unique_lock_name("ImageCopyRequest::m_lock", this)),
     m_client_meta_copy(*client_meta) {
   assert(!m_client_meta_copy.sync_points.empty());
@@ -74,6 +77,8 @@ void ImageCopyRequest<I>::send_update_max_object_count() {
     return;
   }
 
+  update_progress("UPDATE_MAX_OBJECT_COUNT");
+
   CephContext *cct = m_local_image_ctx->cct;
   ldout(cct, 20) << ": sync_object_count=" << max_objects << dendl;
 
@@ -121,6 +126,8 @@ void ImageCopyRequest<I>::send_object_copies() {
   dout(20) << ": start_object=" << m_object_no << ", "
            << "end_object=" << m_end_object_no << dendl;
 
+  update_progress("COPY_OBJECT");
+
   bool complete;
   {
     Mutex::Locker locker(m_lock);
@@ -165,11 +172,13 @@ void ImageCopyRequest<I>::handle_object_copy(int r) {
   CephContext *cct = m_local_image_ctx->cct;
   ldout(cct, 20) << ": r=" << r << dendl;
 
+  int percent;
   bool complete;
   {
     Mutex::Locker locker(m_lock);
     assert(m_current_ops > 0);
     --m_current_ops;
+    percent = 100 * m_object_no / m_end_object_no;
 
     if (r < 0) {
       lderr(cct) << ": object copy failed: " << cpp_strerror(r) << dendl;
@@ -182,6 +191,8 @@ void ImageCopyRequest<I>::handle_object_copy(int r) {
     complete = (m_current_ops == 0);
   }
 
+  update_progress("COPY_OBJECT " + stringify(percent) + "%", false);
+
   if (complete) {
     send_flush_sync_point();
   }
@@ -194,6 +205,8 @@ void ImageCopyRequest<I>::send_flush_sync_point() {
     return;
   }
 
+  update_progress("FLUSH_SYNC_POINT");
+
   m_client_meta_copy = *m_client_meta;
   if (m_object_no > 0) {
     m_sync_point->object_number = m_object_no - 1;
@@ -287,6 +300,16 @@ int ImageCopyRequest<I>::compute_snap_map() {
   return 0;
 }
 
+template <typename I>
+void ImageCopyRequest<I>::update_progress(const std::string &description,
+					  bool flush) {
+  dout(20) << ": " << description << dendl;
+
+  if (m_progress_ctx) {
+    m_progress_ctx->update_progress("IMAGE_COPY/" + description, flush);
+  }
+}
+
 } // namespace image_sync
 } // namespace mirror
 } // namespace rbd
diff --git a/src/tools/rbd_mirror/image_sync/ImageCopyRequest.h b/src/tools/rbd_mirror/image_sync/ImageCopyRequest.h
index 0d1f5e3..7198aae 100644
--- a/src/tools/rbd_mirror/image_sync/ImageCopyRequest.h
+++ b/src/tools/rbd_mirror/image_sync/ImageCopyRequest.h
@@ -19,6 +19,9 @@ namespace librbd { struct ImageCtx; }
 
 namespace rbd {
 namespace mirror {
+
+class ProgressContext;
+
 namespace image_sync {
 
 template <typename ImageCtxT = librbd::ImageCtx>
@@ -30,6 +33,7 @@ public:
   typedef typename TypeTraits::Journaler Journaler;
   typedef librbd::journal::MirrorPeerSyncPoint MirrorPeerSyncPoint;
   typedef librbd::journal::MirrorPeerClientMeta MirrorPeerClientMeta;
+  typedef rbd::mirror::ProgressContext ProgressContext;
 
   static ImageCopyRequest* create(ImageCtxT *local_image_ctx,
                                   ImageCtxT *remote_image_ctx,
@@ -37,16 +41,18 @@ public:
                                   Journaler *journaler,
                                   MirrorPeerClientMeta *client_meta,
                                   MirrorPeerSyncPoint *sync_point,
-                                  Context *on_finish) {
+                                  Context *on_finish,
+				  ProgressContext *progress_ctx = nullptr) {
     return new ImageCopyRequest(local_image_ctx, remote_image_ctx, timer,
                                 timer_lock, journaler, client_meta, sync_point,
-                                on_finish);
+                                on_finish, progress_ctx);
   }
 
   ImageCopyRequest(ImageCtxT *local_image_ctx, ImageCtxT *remote_image_ctx,
                    SafeTimer *timer, Mutex *timer_lock, Journaler *journaler,
                    MirrorPeerClientMeta *client_meta,
-                   MirrorPeerSyncPoint *sync_point, Context *on_finish);
+                   MirrorPeerSyncPoint *sync_point, Context *on_finish,
+		   ProgressContext *progress_ctx = nullptr);
 
   void send();
   void cancel();
@@ -82,6 +88,7 @@ private:
   MirrorPeerClientMeta *m_client_meta;
   MirrorPeerSyncPoint *m_sync_point;
   Context *m_on_finish;
+  ProgressContext *m_progress_ctx;
 
   SnapMap m_snap_map;
 
@@ -109,6 +116,7 @@ private:
 
   int compute_snap_map();
 
+  void update_progress(const std::string &description, bool flush = true);
 };
 
 } // namespace image_sync
diff --git a/src/tools/rbd_mirror/image_sync/ObjectCopyRequest.cc b/src/tools/rbd_mirror/image_sync/ObjectCopyRequest.cc
index 7cf1292..38567ce 100644
--- a/src/tools/rbd_mirror/image_sync/ObjectCopyRequest.cc
+++ b/src/tools/rbd_mirror/image_sync/ObjectCopyRequest.cc
@@ -35,6 +35,11 @@ ObjectCopyRequest<I>::ObjectCopyRequest(I *local_image_ctx, I *remote_image_ctx,
 
   m_remote_io_ctx.dup(m_remote_image_ctx->data_ctx);
   m_remote_oid = m_remote_image_ctx->get_object_name(object_number);
+
+  CephContext *cct = m_local_image_ctx->cct;
+  ldout(cct, 20) << ": "
+                 << "remote_oid=" << m_remote_oid << ", "
+                 << "local_oid=" << m_local_oid << dendl;
 }
 
 template <typename I>
@@ -97,11 +102,8 @@ void ObjectCopyRequest<I>::send_read_object() {
   assert(!sync_ops.empty());
 
   // map the sync op start snap id back to the necessary read snap id
-  auto snap_map_it = m_snap_map->upper_bound(
-    m_snap_sync_ops.begin()->first);
-  assert(snap_map_it != m_snap_map->end());
-  librados::snap_t snap_seq = snap_map_it->first;
-  m_remote_io_ctx.snap_set_read(snap_seq);
+  librados::snap_t remote_snap_seq = m_snap_sync_ops.begin()->first;
+  m_remote_io_ctx.snap_set_read(remote_snap_seq);
 
   bool read_required = false;
   librados::ObjectReadOperation op;
@@ -109,7 +111,7 @@ void ObjectCopyRequest<I>::send_read_object() {
     switch (std::get<0>(sync_op)) {
     case SYNC_OP_TYPE_WRITE:
       if (!read_required) {
-        ldout(cct, 20) << ": snap_seq=" << snap_seq << dendl;
+        ldout(cct, 20) << ": remote_snap_seq=" << remote_snap_seq << dendl;
         read_required = true;
       }
 
@@ -154,18 +156,26 @@ void ObjectCopyRequest<I>::handle_read_object(int r) {
 template <typename I>
 void ObjectCopyRequest<I>::send_write_object() {
   // retrieve the local snap context for the op
-  SnapIds snap_ids;
-  librados::snap_t snap_seq = m_snap_sync_ops.begin()->first;
-  if (snap_seq != 0) {
-    auto snap_map_it = m_snap_map->find(snap_seq);
+  SnapIds local_snap_ids;
+  librados::snap_t local_snap_seq = 0;
+  librados::snap_t remote_snap_seq = m_snap_sync_ops.begin()->first;
+  if (remote_snap_seq != 0) {
+    auto snap_map_it = m_snap_map->find(remote_snap_seq);
     assert(snap_map_it != m_snap_map->end());
-    snap_ids = snap_map_it->second;
+
+    // write snapshot context should be before actual snapshot
+    if (snap_map_it != m_snap_map->begin()) {
+      --snap_map_it;
+      assert(!snap_map_it->second.empty());
+      local_snap_seq = snap_map_it->second.front();
+      local_snap_ids = snap_map_it->second;
+    }
   }
 
   CephContext *cct = m_local_image_ctx->cct;
   ldout(cct, 20) << ": "
-                 << "snap_seq=" << snap_seq << ", "
-                 << "snaps=" << snap_ids << dendl;
+                 << "local_snap_seq=" << local_snap_seq << ", "
+                 << "local_snaps=" << local_snap_ids << dendl;
 
   auto &sync_ops = m_snap_sync_ops.begin()->second;
   assert(!sync_ops.empty());
@@ -193,8 +203,8 @@ void ObjectCopyRequest<I>::send_write_object() {
 
   librados::AioCompletion *comp = create_rados_safe_callback<
     ObjectCopyRequest<I>, &ObjectCopyRequest<I>::handle_write_object>(this);
-  int r = m_local_io_ctx.aio_operate(m_local_oid, comp, &op, snap_seq,
-                                     snap_ids);
+  int r = m_local_io_ctx.aio_operate(m_local_oid, comp, &op, local_snap_seq,
+                                     local_snap_ids);
   assert(r == 0);
   comp->release();
 }
@@ -241,7 +251,7 @@ void ObjectCopyRequest<I>::send_update_object_map() {
 
   CephContext *cct = m_local_image_ctx->cct;
   ldout(cct, 20) << ": "
-                 << "snap_id=" << snap_object_state.first << ", "
+                 << "local_snap_id=" << snap_object_state.first << ", "
                  << "object_state=" << static_cast<uint32_t>(
                       snap_object_state.second)
                  << dendl;
@@ -277,21 +287,22 @@ void ObjectCopyRequest<I>::compute_diffs() {
 
   uint64_t prev_end_size = 0;
   bool prev_exists = false;
-  librados::snap_t start_snap_id = 0;
-  librados::snap_t end_snap_id;
+  librados::snap_t start_remote_snap_id = 0;
   for (auto &pair : *m_snap_map) {
     assert(!pair.second.empty());
-    end_snap_id = pair.second.front();
+    librados::snap_t end_remote_snap_id = pair.first;
+    librados::snap_t end_local_snap_id = pair.second.front();
 
     interval_set<uint64_t> diff;
     uint64_t end_size;
     bool exists;
-    calc_snap_set_diff(cct, m_snap_set, start_snap_id, end_snap_id, &diff,
-                       &end_size, &exists);
+    calc_snap_set_diff(cct, m_snap_set, start_remote_snap_id,
+                       end_remote_snap_id, &diff, &end_size, &exists);
 
     ldout(cct, 20) << ": "
-                   << "start_snap=" << start_snap_id << ", "
-                   << "end_snap_id=" << end_snap_id << ", "
+                   << "start_remote_snap=" << start_remote_snap_id << ", "
+                   << "end_remote_snap_id=" << end_remote_snap_id << ", "
+                   << "end_local_snap_id=" << end_local_snap_id << ", "
                    << "diff=" << diff << ", "
                    << "end_size=" << end_size << ", "
                    << "exists=" << exists << dendl;
@@ -315,36 +326,36 @@ void ObjectCopyRequest<I>::compute_diffs() {
             diff.empty() && end_size == prev_end_size) {
           object_state = OBJECT_EXISTS_CLEAN;
         }
-        m_snap_object_states[end_snap_id] = object_state;
+        m_snap_object_states[end_local_snap_id] = object_state;
       }
 
       // object write/zero, or truncate
       for (auto it = diff.begin(); it != diff.end(); ++it) {
         ldout(cct, 20) << ": read/write op: " << it.get_start() << "~"
                        << it.get_len() << dendl;
-        m_snap_sync_ops[start_snap_id].emplace_back(SYNC_OP_TYPE_WRITE,
-                                                    it.get_start(),
-                                                    it.get_len(),
-                                                    bufferlist());
+        m_snap_sync_ops[end_remote_snap_id].emplace_back(SYNC_OP_TYPE_WRITE,
+                                                         it.get_start(),
+                                                         it.get_len(),
+                                                         bufferlist());
       }
       if (end_size < prev_end_size) {
         ldout(cct, 20) << ": trunc op: " << end_size << dendl;
-        m_snap_sync_ops[start_snap_id].emplace_back(SYNC_OP_TYPE_TRUNC,
-                                                    end_size, 0U, bufferlist());
+        m_snap_sync_ops[end_remote_snap_id].emplace_back(SYNC_OP_TYPE_TRUNC,
+                                                         end_size, 0U,
+                                                         bufferlist());
       }
     } else {
-      m_snap_object_states[end_snap_id] = OBJECT_NONEXISTENT;
       if (prev_exists) {
         // object remove
         ldout(cct, 20) << ": remove op" << dendl;
-        m_snap_sync_ops[start_snap_id].emplace_back(SYNC_OP_TYPE_REMOVE, 0U, 0U,
-                                                    bufferlist());
+        m_snap_sync_ops[end_remote_snap_id].emplace_back(SYNC_OP_TYPE_REMOVE,
+                                                         0U, 0U, bufferlist());
       }
     }
 
     prev_end_size = end_size;
     prev_exists = exists;
-    start_snap_id = end_snap_id;
+    start_remote_snap_id = end_remote_snap_id;
   }
 }
 
diff --git a/src/tools/rbd_mirror/image_sync/SnapshotCopyRequest.cc b/src/tools/rbd_mirror/image_sync/SnapshotCopyRequest.cc
index 53dc1e3..016e4b4 100644
--- a/src/tools/rbd_mirror/image_sync/SnapshotCopyRequest.cc
+++ b/src/tools/rbd_mirror/image_sync/SnapshotCopyRequest.cc
@@ -2,6 +2,7 @@
 // vim: ts=8 sw=2 smarttab
 
 #include "SnapshotCopyRequest.h"
+#include "SnapshotCreateRequest.h"
 #include "common/errno.h"
 #include "journal/Journaler.h"
 #include "librbd/Operations.h"
@@ -35,8 +36,6 @@ const std::string &get_snapshot_name(I *image_ctx, librados::snap_t snap_id) {
 
 using librbd::util::create_context_callback;
 
-
-
 template <typename I>
 SnapshotCopyRequest<I>::SnapshotCopyRequest(I *local_image_ctx,
                                             I *remote_image_ctx,
@@ -58,52 +57,160 @@ SnapshotCopyRequest<I>::SnapshotCopyRequest(I *local_image_ctx,
 
 template <typename I>
 void SnapshotCopyRequest<I>::send() {
-  send_snap_remove();
+  send_snap_unprotect();
 }
 
 template <typename I>
-void SnapshotCopyRequest<I>::send_snap_remove() {
+void SnapshotCopyRequest<I>::send_snap_unprotect() {
   CephContext *cct = m_local_image_ctx->cct;
+
   // TODO: issue #14937 needs to add support for cloned images
-  {
-    RWLock::RLocker snap_locker(m_remote_image_ctx->snap_lock);
-    if (m_remote_image_ctx->parent_md.spec.pool_id != -1 ||
-        std::find_if(m_remote_image_ctx->snap_info.begin(),
-                     m_remote_image_ctx->snap_info.end(),
-                     [](const std::pair<librados::snap_t, librbd::SnapInfo>& pair) {
-            return pair.second.parent.spec.pool_id != -1;
-          }) != m_remote_image_ctx->snap_info.end()) {
-      lderr(cct) << ": cloned images are not currently supported" << dendl;
-      finish(-EINVAL);
+  m_remote_image_ctx->snap_lock.get_read();
+  if (m_remote_image_ctx->parent_md.spec.pool_id != -1 ||
+      std::find_if(m_remote_image_ctx->snap_info.begin(),
+                   m_remote_image_ctx->snap_info.end(),
+                   [](const std::pair<librados::snap_t, librbd::SnapInfo>& pair) {
+          return pair.second.parent.spec.pool_id != -1;
+        }) != m_remote_image_ctx->snap_info.end()) {
+    lderr(cct) << ": cloned images are not currently supported" << dendl;
+    m_remote_image_ctx->snap_lock.put_read();
+    finish(-EINVAL);
+    return;
+  }
+  m_remote_image_ctx->snap_lock.put_read();
+
+  SnapIdSet::iterator snap_id_it = m_local_snap_ids.begin();
+  if (m_prev_snap_id != CEPH_NOSNAP) {
+    snap_id_it = m_local_snap_ids.upper_bound(m_prev_snap_id);
+  }
+
+  for (; snap_id_it != m_local_snap_ids.end(); ++snap_id_it) {
+    librados::snap_t local_snap_id = *snap_id_it;
+
+    m_local_image_ctx->snap_lock.get_read();
+    bool local_unprotected;
+    int r = m_local_image_ctx->is_snap_unprotected(local_snap_id,
+                                                   &local_unprotected);
+    if (r < 0) {
+      lderr(cct) << "failed to retrieve local snap unprotect status: "
+                 << cpp_strerror(r) << dendl;
+      m_local_image_ctx->snap_lock.put_read();
+      finish(r);
       return;
     }
+    m_local_image_ctx->snap_lock.put_read();
+
+    if (local_unprotected) {
+      // snap is already unprotected -- check next snap
+      continue;
+    }
+
+    // if local snapshot is protected and (1) it isn't in our mapping
+    // table, or (2) the remote snapshot isn't protected, unprotect it
+    auto snap_seq_it = std::find_if(
+      m_snap_seqs.begin(), m_snap_seqs.end(),
+      [local_snap_id](const SnapSeqs::value_type& pair) {
+        return pair.second == local_snap_id;
+      });
+
+    if (snap_seq_it != m_snap_seqs.end()) {
+      m_remote_image_ctx->snap_lock.get_read();
+      bool remote_unprotected;
+      r = m_remote_image_ctx->is_snap_unprotected(snap_seq_it->first,
+                                                  &remote_unprotected);
+      if (r < 0) {
+        lderr(cct) << "failed to retrieve remote snap unprotect status: "
+                   << cpp_strerror(r) << dendl;
+        m_remote_image_ctx->snap_lock.put_read();
+        finish(r);
+        return;
+      }
+      m_remote_image_ctx->snap_lock.put_read();
+
+      if (remote_unprotected) {
+        // remote is unprotected -- unprotect local snap
+        break;
+      }
+    } else {
+      // remote snapshot doesn't exist -- unprotect local snap
+      break;
+    }
   }
 
-  librados::snap_t local_snap_id = CEPH_NOSNAP;
-  while (local_snap_id == CEPH_NOSNAP && !m_local_snap_ids.empty()) {
-    librados::snap_t snap_id = *m_local_snap_ids.begin();
-
-    // if local snapshot id isn't in our mapping table, delete it
-    // we match by id since snapshots can be renamed
-    if (std::find_if(m_snap_seqs.begin(), m_snap_seqs.end(),
-                     [snap_id](const SnapSeqs::value_type& pair) {
-        return pair.second == snap_id; }) == m_snap_seqs.end()) {
-      local_snap_id = snap_id;
-      m_local_snap_ids.erase(m_local_snap_ids.begin());
+  if (snap_id_it == m_local_snap_ids.end()) {
+    // no local snapshots to unprotect
+    m_prev_snap_id = CEPH_NOSNAP;
+    send_snap_remove();
+    return;
+  }
+
+  m_prev_snap_id = *snap_id_it;
+  m_snap_name = get_snapshot_name(m_local_image_ctx, m_prev_snap_id);
+
+  ldout(cct, 20) << ": "
+                 << "snap_name=" << m_snap_name << ", "
+                 << "snap_id=" << m_prev_snap_id << dendl;
+
+  Context *ctx = create_context_callback<
+    SnapshotCopyRequest<I>, &SnapshotCopyRequest<I>::handle_snap_unprotect>(
+      this);
+  RWLock::RLocker owner_locker(m_local_image_ctx->owner_lock);
+  m_local_image_ctx->operations->execute_snap_unprotect(m_snap_name.c_str(),
+                                                        ctx);
+}
+
+template <typename I>
+void SnapshotCopyRequest<I>::handle_snap_unprotect(int r) {
+  CephContext *cct = m_local_image_ctx->cct;
+  ldout(cct, 20) << ": r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(cct) << ": failed to unprotect snapshot '" << m_snap_name << "': "
+               << cpp_strerror(r) << dendl;
+    finish(r);
+    return;
+  }
+
+  send_snap_unprotect();
+}
+
+template <typename I>
+void SnapshotCopyRequest<I>::send_snap_remove() {
+  CephContext *cct = m_local_image_ctx->cct;
+
+  SnapIdSet::iterator snap_id_it = m_local_snap_ids.begin();
+  if (m_prev_snap_id != CEPH_NOSNAP) {
+    snap_id_it = m_local_snap_ids.upper_bound(m_prev_snap_id);
+  }
+
+  for (; snap_id_it != m_local_snap_ids.end(); ++snap_id_it) {
+    librados::snap_t local_snap_id = *snap_id_it;
+
+    // if the local snapshot isn't in our mapping table, remove it
+    auto snap_seq_it = std::find_if(
+      m_snap_seqs.begin(), m_snap_seqs.end(),
+      [local_snap_id](const SnapSeqs::value_type& pair) {
+        return pair.second == local_snap_id;
+      });
+
+    if (snap_seq_it == m_snap_seqs.end()) {
+      break;
     }
   }
 
-  if (local_snap_id == CEPH_NOSNAP && m_local_snap_ids.empty()) {
+  if (snap_id_it == m_local_snap_ids.end()) {
     // no local snapshots to delete
+    m_prev_snap_id = CEPH_NOSNAP;
     send_snap_create();
     return;
   }
 
-  m_snap_name = get_snapshot_name(m_local_image_ctx, local_snap_id);
+  m_prev_snap_id = *snap_id_it;
+  m_snap_name = get_snapshot_name(m_local_image_ctx, m_prev_snap_id);
 
   ldout(cct, 20) << ": "
                  << "snap_name=" << m_snap_name << ", "
-                 << "snap_id=" << local_snap_id << dendl;
+                 << "snap_id=" << m_prev_snap_id << dendl;
 
   Context *ctx = create_context_callback<
     SnapshotCopyRequest<I>, &SnapshotCopyRequest<I>::handle_snap_remove>(
@@ -129,37 +236,55 @@ void SnapshotCopyRequest<I>::handle_snap_remove(int r) {
 
 template <typename I>
 void SnapshotCopyRequest<I>::send_snap_create() {
-  librados::snap_t remote_snap_id = CEPH_NOSNAP;
-  while (remote_snap_id == CEPH_NOSNAP && !m_remote_snap_ids.empty()) {
-    librados::snap_t snap_id = *m_remote_snap_ids.begin();
-    if (m_snap_seqs.find(snap_id) == m_snap_seqs.end()) {
-      // missing remote -> local mapping
-      remote_snap_id = snap_id;
-    } else {
-      // already have remote -> local mapping
-      m_remote_snap_ids.erase(m_remote_snap_ids.begin());
+  CephContext *cct = m_local_image_ctx->cct;
+
+  SnapIdSet::iterator snap_id_it = m_remote_snap_ids.begin();
+  if (m_prev_snap_id != CEPH_NOSNAP) {
+    snap_id_it = m_remote_snap_ids.upper_bound(m_prev_snap_id);
+  }
+
+  for (; snap_id_it != m_remote_snap_ids.end(); ++snap_id_it) {
+    librados::snap_t remote_snap_id = *snap_id_it;
+
+    // if the remote snapshot isn't in our mapping table, create it
+    if (m_snap_seqs.find(remote_snap_id) == m_snap_seqs.end()) {
+      break;
     }
   }
 
-  if (remote_snap_id == CEPH_NOSNAP && m_remote_snap_ids.empty()) {
-    // no local snapshots to create
-    send_update_client();
+  if (snap_id_it == m_remote_snap_ids.end()) {
+    // no remote snapshots to create
+    m_prev_snap_id = CEPH_NOSNAP;
+    send_snap_protect();
     return;
   }
 
-  m_snap_name = get_snapshot_name(m_remote_image_ctx, remote_snap_id);
+  m_prev_snap_id = *snap_id_it;
+  m_snap_name = get_snapshot_name(m_remote_image_ctx, m_prev_snap_id);
+
+  m_remote_image_ctx->snap_lock.get_read();
+  auto snap_info_it = m_remote_image_ctx->snap_info.find(m_prev_snap_id);
+  if (snap_info_it == m_remote_image_ctx->snap_info.end()) {
+    m_remote_image_ctx->snap_lock.put_read();
+    lderr(cct) << "failed to retrieve remote snap info: " << m_snap_name
+               << dendl;
+    finish(-ENOENT);
+    return;
+  }
+  uint64_t size = snap_info_it->second.size;
+  m_remote_image_ctx->snap_lock.put_read();
 
-  CephContext *cct = m_local_image_ctx->cct;
   ldout(cct, 20) << ": "
                  << "snap_name=" << m_snap_name << ", "
-                 << "snap_id=" << remote_snap_id << dendl;
+                 << "snap_id=" << m_prev_snap_id << ", "
+                 << "size=" << size << dendl;
 
   Context *ctx = create_context_callback<
     SnapshotCopyRequest<I>, &SnapshotCopyRequest<I>::handle_snap_create>(
       this);
-  RWLock::RLocker owner_locker(m_local_image_ctx->owner_lock);
-  m_local_image_ctx->operations->execute_snap_create(m_snap_name.c_str(), ctx,
-                                                     0U);
+  SnapshotCreateRequest<I> *req = SnapshotCreateRequest<I>::create(
+    m_local_image_ctx, m_snap_name, size, ctx);
+  req->send();
 }
 
 template <typename I>
@@ -174,22 +299,108 @@ void SnapshotCopyRequest<I>::handle_snap_create(int r) {
     return;
   }
 
-  assert(!m_remote_snap_ids.empty());
-  librados::snap_t remote_snap_id = *m_remote_snap_ids.begin();
-  m_remote_snap_ids.erase(m_remote_snap_ids.begin());
+  assert(m_prev_snap_id != CEPH_NOSNAP);
 
   auto snap_it = m_local_image_ctx->snap_ids.find(m_snap_name);
   assert(snap_it != m_local_image_ctx->snap_ids.end());
   librados::snap_t local_snap_id = snap_it->second;
 
-  ldout(cct, 20) << ": mapping remote snap id " << remote_snap_id << " to "
+  ldout(cct, 20) << ": mapping remote snap id " << m_prev_snap_id << " to "
                  << local_snap_id << dendl;
-  m_snap_seqs[remote_snap_id] = local_snap_id;
+  m_snap_seqs[m_prev_snap_id] = local_snap_id;
 
   send_snap_create();
 }
 
 template <typename I>
+void SnapshotCopyRequest<I>::send_snap_protect() {
+  CephContext *cct = m_local_image_ctx->cct;
+
+  SnapIdSet::iterator snap_id_it = m_remote_snap_ids.begin();
+  if (m_prev_snap_id != CEPH_NOSNAP) {
+    snap_id_it = m_remote_snap_ids.upper_bound(m_prev_snap_id);
+  }
+
+  for (; snap_id_it != m_remote_snap_ids.end(); ++snap_id_it) {
+    librados::snap_t remote_snap_id = *snap_id_it;
+
+    m_remote_image_ctx->snap_lock.get_read();
+    bool remote_protected;
+    int r = m_remote_image_ctx->is_snap_protected(remote_snap_id,
+                                                  &remote_protected);
+    if (r < 0) {
+      lderr(cct) << "failed to retrieve remote snap protect status: "
+                 << cpp_strerror(r) << dendl;
+      m_remote_image_ctx->snap_lock.put_read();
+      finish(r);
+      return;
+    }
+    m_remote_image_ctx->snap_lock.put_read();
+
+    if (!remote_protected) {
+      // snap is not protected -- check next snap
+      continue;
+    }
+
+    // if local snapshot is not protected, protect it
+    auto snap_seq_it = m_snap_seqs.find(remote_snap_id);
+    assert(snap_seq_it != m_snap_seqs.end());
+
+    m_local_image_ctx->snap_lock.get_read();
+    bool local_protected;
+    r = m_local_image_ctx->is_snap_protected(snap_seq_it->second,
+                                             &local_protected);
+    if (r < 0) {
+      lderr(cct) << "failed to retrieve local snap protect status: "
+                 << cpp_strerror(r) << dendl;
+      m_local_image_ctx->snap_lock.put_read();
+      finish(r);
+      return;
+    }
+    m_local_image_ctx->snap_lock.put_read();
+
+    if (!local_protected) {
+      break;
+    }
+  }
+
+  if (snap_id_it == m_remote_snap_ids.end()) {
+    // no local snapshots to protect
+    m_prev_snap_id = CEPH_NOSNAP;
+    send_update_client();
+    return;
+  }
+
+  m_prev_snap_id = *snap_id_it;
+  m_snap_name = get_snapshot_name(m_remote_image_ctx, m_prev_snap_id);
+
+  ldout(cct, 20) << ": "
+                 << "snap_name=" << m_snap_name << ", "
+                 << "snap_id=" << m_prev_snap_id << dendl;
+
+  Context *ctx = create_context_callback<
+    SnapshotCopyRequest<I>, &SnapshotCopyRequest<I>::handle_snap_protect>(
+      this);
+  RWLock::RLocker owner_locker(m_local_image_ctx->owner_lock);
+  m_local_image_ctx->operations->execute_snap_protect(m_snap_name.c_str(), ctx);
+}
+
+template <typename I>
+void SnapshotCopyRequest<I>::handle_snap_protect(int r) {
+  CephContext *cct = m_local_image_ctx->cct;
+  ldout(cct, 20) << ": r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(cct) << ": failed to protect snapshot '" << m_snap_name << "': "
+               << cpp_strerror(r) << dendl;
+    finish(r);
+    return;
+  }
+
+  send_snap_protect();
+}
+
+template <typename I>
 void SnapshotCopyRequest<I>::send_update_client() {
   CephContext *cct = m_local_image_ctx->cct;
   ldout(cct, 20) << dendl;
diff --git a/src/tools/rbd_mirror/image_sync/SnapshotCopyRequest.h b/src/tools/rbd_mirror/image_sync/SnapshotCopyRequest.h
index b94612b..87f766f 100644
--- a/src/tools/rbd_mirror/image_sync/SnapshotCopyRequest.h
+++ b/src/tools/rbd_mirror/image_sync/SnapshotCopyRequest.h
@@ -53,15 +53,25 @@ private:
    *
    * <start>
    *    |
-   *    |   /-------\
-   *    |   |       |
-   *    v   v       | (repeat as needed)
-   * REMOVE_SNAP <--/
+   *    |   /-----------\
+   *    |   |           |
+   *    v   v           | (repeat as needed)
+   * UNPROTECT_SNAP ----/
    *    |
-   *    |   /-------\
-   *    |   |       |
-   *    v   v       | (repeat as needed)
-   * CREATE_SNAP <--/
+   *    |   /-----------\
+   *    |   |           |
+   *    v   v           | (repeat as needed)
+   * REMOVE_SNAP -------/
+   *    |
+   *    |   /-----------\
+   *    |   |           |
+   *    v   v           | (repeat as needed)
+   * CREATE_SNAP -------/
+   *    |
+   *    |   /-----------\
+   *    |   |           |
+   *    v   v           | (repeat as needed)
+   * PROTECT_SNAP ------/
    *    |
    *    v
    * UPDATE_CLIENT
@@ -85,15 +95,22 @@ private:
   SnapIdSet m_local_snap_ids;
   SnapIdSet m_remote_snap_ids;
   SnapSeqs m_snap_seqs;
+  librados::snap_t m_prev_snap_id = CEPH_NOSNAP;
 
   std::string m_snap_name;
 
+  void send_snap_unprotect();
+  void handle_snap_unprotect(int r);
+
   void send_snap_remove();
   void handle_snap_remove(int r);
 
   void send_snap_create();
   void handle_snap_create(int r);
 
+  void send_snap_protect();
+  void handle_snap_protect(int r);
+
   void send_update_client();
   void handle_update_client(int r);
 
diff --git a/src/tools/rbd_mirror/image_sync/SnapshotCreateRequest.cc b/src/tools/rbd_mirror/image_sync/SnapshotCreateRequest.cc
new file mode 100644
index 0000000..60d03ff
--- /dev/null
+++ b/src/tools/rbd_mirror/image_sync/SnapshotCreateRequest.cc
@@ -0,0 +1,221 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "SnapshotCreateRequest.h"
+#include "common/errno.h"
+#include "cls/rbd/cls_rbd_client.h"
+#include "librbd/ObjectMap.h"
+#include "librbd/Operations.h"
+#include "librbd/Utils.h"
+#include "osdc/Striper.h"
+
+#define dout_subsys ceph_subsys_rbd_mirror
+#undef dout_prefix
+#define dout_prefix *_dout << "rbd::mirror::image_sync::SnapshotCreateRequest: " \
+                           << this << " " << __func__
+
+namespace rbd {
+namespace mirror {
+namespace image_sync {
+
+using librbd::util::create_context_callback;
+using librbd::util::create_rados_safe_callback;
+
+template <typename I>
+SnapshotCreateRequest<I>::SnapshotCreateRequest(I *local_image_ctx,
+                                                const std::string &snap_name,
+                                                uint64_t size,
+                                                Context *on_finish)
+  : m_local_image_ctx(local_image_ctx), m_snap_name(snap_name), m_size(size),
+    m_on_finish(on_finish) {
+}
+
+template <typename I>
+void SnapshotCreateRequest<I>::send() {
+  send_set_size();
+}
+
+template <typename I>
+void SnapshotCreateRequest<I>::send_set_size() {
+  m_local_image_ctx->snap_lock.get_read();
+  if (m_local_image_ctx->size == m_size) {
+    m_local_image_ctx->snap_lock.put_read();
+    send_remove_parent();
+    return;
+  }
+  m_local_image_ctx->snap_lock.put_read();
+
+  CephContext *cct = m_local_image_ctx->cct;
+  ldout(cct, 20) << dendl;
+
+  // Change the image size on disk so that the snapshot picks up
+  // the expected size.  We can do this because the last snapshot
+  // we process is the sync snapshot which was created to match the
+  // image size. We also don't need to worry about trimming because
+  // we track the highest possible object number within the sync record
+  librados::ObjectWriteOperation op;
+  librbd::cls_client::set_size(&op, m_size);
+
+  librados::AioCompletion *comp = create_rados_safe_callback<
+    SnapshotCreateRequest<I>, &SnapshotCreateRequest<I>::handle_set_size>(this);
+  int r = m_local_image_ctx->md_ctx.aio_operate(m_local_image_ctx->header_oid,
+                                                comp, &op);
+  assert(r == 0);
+  comp->release();
+}
+
+template <typename I>
+void SnapshotCreateRequest<I>::handle_set_size(int r) {
+  CephContext *cct = m_local_image_ctx->cct;
+  ldout(cct, 20) << ": r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(cct) << ": failed to update image size '" << m_snap_name << "': "
+               << cpp_strerror(r) << dendl;
+    finish(r);
+    return;
+  }
+
+  {
+    // adjust in-memory image size now that it's updated on disk
+    RWLock::WLocker snap_locker(m_local_image_ctx->snap_lock);
+    m_local_image_ctx->size = m_size;
+  }
+
+  send_remove_parent();
+}
+
+template <typename I>
+void SnapshotCreateRequest<I>::send_remove_parent() {
+  // TODO: issue #14937 needs to add support for cloned images
+  if (true) {
+    send_snap_create();
+    return;
+  }
+
+  CephContext *cct = m_local_image_ctx->cct;
+  ldout(cct, 20) << dendl;
+
+}
+
+template <typename I>
+void SnapshotCreateRequest<I>::handle_remove_parent(int r) {
+  CephContext *cct = m_local_image_ctx->cct;
+  ldout(cct, 20) << ": r=" << r << dendl;
+
+  // TODO: issue #14937 needs to add support for cloned images
+}
+
+template <typename I>
+void SnapshotCreateRequest<I>::send_set_parent() {
+  CephContext *cct = m_local_image_ctx->cct;
+  ldout(cct, 20) << dendl;
+
+  // TODO: issue #14937 needs to add support for cloned images
+}
+
+template <typename I>
+void SnapshotCreateRequest<I>::handle_set_parent(int r) {
+  CephContext *cct = m_local_image_ctx->cct;
+  ldout(cct, 20) << ": r=" << r << dendl;
+
+  // TODO: issue #14937 needs to add support for cloned images
+}
+
+template <typename I>
+void SnapshotCreateRequest<I>::send_snap_create() {
+  CephContext *cct = m_local_image_ctx->cct;
+  ldout(cct, 20) << ": snap_name=" << m_snap_name << dendl;
+
+  Context *ctx = create_context_callback<
+    SnapshotCreateRequest<I>, &SnapshotCreateRequest<I>::handle_snap_create>(
+      this);
+  RWLock::RLocker owner_locker(m_local_image_ctx->owner_lock);
+  m_local_image_ctx->operations->execute_snap_create(m_snap_name.c_str(), ctx,
+                                                     0U, true);
+}
+
+template <typename I>
+void SnapshotCreateRequest<I>::handle_snap_create(int r) {
+  CephContext *cct = m_local_image_ctx->cct;
+  ldout(cct, 20) << ": r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(cct) << ": failed to create snapshot '" << m_snap_name << "': "
+               << cpp_strerror(r) << dendl;
+    finish(r);
+    return;
+  }
+
+  send_create_object_map();
+}
+template <typename I>
+void SnapshotCreateRequest<I>::send_create_object_map() {
+  CephContext *cct = m_local_image_ctx->cct;
+
+  if (!m_local_image_ctx->test_features(RBD_FEATURE_OBJECT_MAP)) {
+    finish(0);
+    return;
+  }
+
+  m_local_image_ctx->snap_lock.get_read();
+  auto snap_it = m_local_image_ctx->snap_ids.find(m_snap_name);
+  if (snap_it == m_local_image_ctx->snap_ids.end()) {
+    lderr(cct) << "failed to locate snap: " << m_snap_name << dendl;
+    m_local_image_ctx->snap_lock.put_read();
+    finish(-ENOENT);
+    return;
+  }
+  librados::snap_t local_snap_id = snap_it->second;
+  m_local_image_ctx->snap_lock.put_read();
+
+  std::string object_map_oid(librbd::ObjectMap::object_map_name(
+    m_local_image_ctx->id, local_snap_id));
+  uint64_t object_count = Striper::get_num_objects(m_local_image_ctx->layout,
+                                                   m_size);
+  ldout(cct, 20) << ": "
+                 << "object_map_oid=" << object_map_oid << ", "
+                 << "object_count=" << object_count << dendl;
+
+  // initialize an empty object map of the correct size (object sync
+  // will populate the object map)
+  librados::ObjectWriteOperation op;
+  librbd::cls_client::object_map_resize(&op, object_count, OBJECT_NONEXISTENT);
+
+  librados::AioCompletion *comp = create_rados_safe_callback<
+    SnapshotCreateRequest<I>,
+    &SnapshotCreateRequest<I>::handle_create_object_map>(this);
+  int r = m_local_image_ctx->md_ctx.aio_operate(object_map_oid, comp, &op);
+  assert(r == 0);
+  comp->release();
+}
+
+template <typename I>
+void SnapshotCreateRequest<I>::handle_create_object_map(int r) {
+  CephContext *cct = m_local_image_ctx->cct;
+  ldout(cct, 20) << ": r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(cct) << ": failed to create object map: " << cpp_strerror(r)
+               << dendl;
+    finish(r);
+    return;
+  }
+
+  finish(0);
+}
+
+template <typename I>
+void SnapshotCreateRequest<I>::finish(int r) {
+  CephContext *cct = m_local_image_ctx->cct;
+  ldout(cct, 20) << ": r=" << r << dendl;
+
+  m_on_finish->complete(r);
+  delete this;
+}
+
+} // namespace image_sync
+} // namespace mirror
+} // namespace rbd
+
+template class rbd::mirror::image_sync::SnapshotCreateRequest<librbd::ImageCtx>;
diff --git a/src/tools/rbd_mirror/image_sync/SnapshotCreateRequest.h b/src/tools/rbd_mirror/image_sync/SnapshotCreateRequest.h
new file mode 100644
index 0000000..897c7b9
--- /dev/null
+++ b/src/tools/rbd_mirror/image_sync/SnapshotCreateRequest.h
@@ -0,0 +1,95 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef RBD_MIRROR_IMAGE_SYNC_SNAPSHOT_CREATE_REQUEST_H
+#define RBD_MIRROR_IMAGE_SYNC_SNAPSHOT_CREATE_REQUEST_H
+
+#include "include/int_types.h"
+#include "include/rados/librados.hpp"
+#include "common/snap_types.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/journal/TypeTraits.h"
+#include <map>
+#include <set>
+#include <string>
+#include <tuple>
+
+class Context;
+
+namespace rbd {
+namespace mirror {
+namespace image_sync {
+
+template <typename ImageCtxT = librbd::ImageCtx>
+class SnapshotCreateRequest {
+public:
+  static SnapshotCreateRequest* create(ImageCtxT *local_image_ctx,
+                                       const std::string &snap_name,
+                                       uint64_t size, Context *on_finish) {
+    return new SnapshotCreateRequest(local_image_ctx, snap_name, size,
+                                     on_finish);
+  }
+
+  SnapshotCreateRequest(ImageCtxT *local_image_ctx,
+                        const std::string &snap_name, uint64_t size,
+                        Context *on_finish);
+
+  void send();
+
+private:
+  /**
+   * @verbatim
+   *
+   * <start>
+   *    |
+   *    v (skip if not needed)
+   * SET_SIZE
+   *    |
+   *    v (skip if not needed)
+   * REMOVE_PARENT
+   *    |
+   *    v (skip if not needed)
+   * SET_PARENT
+   *    |
+   *    v
+   * CREATE_SNAP
+   *    |
+   *    v (skip if not needed)
+   * CREATE_OBJECT_MAP
+   *    |
+   *    v
+   * <finish>
+   *
+   * @endverbatim
+   */
+
+  ImageCtxT *m_local_image_ctx;
+  std::string m_snap_name;
+  uint64_t m_size;
+  Context *m_on_finish;
+
+  void send_set_size();
+  void handle_set_size(int r);
+
+  void send_remove_parent();
+  void handle_remove_parent(int r);
+
+  void send_set_parent();
+  void handle_set_parent(int r);
+
+  void send_snap_create();
+  void handle_snap_create(int r);
+
+  void send_create_object_map();
+  void handle_create_object_map(int r);
+
+  void finish(int r);
+};
+
+} // namespace image_sync
+} // namespace mirror
+} // namespace rbd
+
+extern template class rbd::mirror::image_sync::SnapshotCreateRequest<librbd::ImageCtx>;
+
+#endif // RBD_MIRROR_IMAGE_SYNC_SNAPSHOT_CREATE_REQUEST_H
diff --git a/src/tools/rbd_mirror/image_sync/SyncPointPruneRequest.cc b/src/tools/rbd_mirror/image_sync/SyncPointPruneRequest.cc
index 332cb00..7e6ab2e 100644
--- a/src/tools/rbd_mirror/image_sync/SyncPointPruneRequest.cc
+++ b/src/tools/rbd_mirror/image_sync/SyncPointPruneRequest.cc
@@ -89,7 +89,7 @@ void SyncPointPruneRequest<I>::send_remove_snap() {
     return;
   }
 
-  std::string snap_name = m_snap_names.front();
+  const std::string &snap_name = m_snap_names.front();
 
   CephContext *cct = m_remote_image_ctx->cct;
   ldout(cct, 20) << ": snap_name=" << snap_name << dendl;
diff --git a/src/tools/rbd_nbd/rbd-nbd.cc b/src/tools/rbd_nbd/rbd-nbd.cc
index e0c0284..f7e7531 100644
--- a/src/tools/rbd_nbd/rbd-nbd.cc
+++ b/src/tools/rbd_nbd/rbd-nbd.cc
@@ -186,11 +186,13 @@ private:
 
     if (ret < 0) {
       ctx->reply.error = htonl(-ret);
-    } else if ((ctx->command == NBD_CMD_WRITE || ctx->command == NBD_CMD_READ)
-	       && ret != static_cast<int>(ctx->request.len)) {
-      derr << __func__ << ": " << *ctx << ": unexpected return value: " << ret
-	   << " (" << ctx->request.len << " expected)" << dendl;
-      ctx->reply.error = htonl(EIO);
+    } else if ((ctx->command == NBD_CMD_READ) &&
+                ret < static_cast<int>(ctx->request.len)) {
+      int pad_byte_count = static_cast<int> (ctx->request.len) - ret;
+      ctx->data.append_zero(pad_byte_count);
+      dout(20) << __func__ << ": " << *ctx << ": Pad byte count: " 
+               << pad_byte_count << dendl;
+      ctx->reply.error = 0;
     } else {
       ctx->reply.error = htonl(0);
     }
diff --git a/src/tools/setup-virtualenv.sh b/src/tools/setup-virtualenv.sh
index 9ff2d26..0b88688 100755
--- a/src/tools/setup-virtualenv.sh
+++ b/src/tools/setup-virtualenv.sh
@@ -26,7 +26,7 @@ pip --log $DIR/log.txt install --upgrade 'pip >= 6.1'
 if test -d wheelhouse ; then
     export NO_INDEX=--no-index
 fi
-pip --log $DIR/log.txt install $NO_INDEX --use-wheel --find-links=file://$(pwd)/wheelhouse --upgrade distribute
+
 pip --log $DIR/log.txt install $NO_INDEX --use-wheel --find-links=file://$(pwd)/wheelhouse 'tox >=1.9'
 if test -f requirements.txt ; then
     pip --log $DIR/log.txt install $NO_INDEX --use-wheel --find-links=file://$(pwd)/wheelhouse -r requirements.txt
diff --git a/systemd/50-ceph.preset b/systemd/50-ceph.preset
new file mode 100644
index 0000000..5b8cfa4
--- /dev/null
+++ b/systemd/50-ceph.preset
@@ -0,0 +1,5 @@
+enable ceph.target
+enable ceph-mds.target
+enable ceph-mon.target
+enable ceph-osd.target
+enable ceph-radosgw.target
diff --git a/systemd/Makefile.am b/systemd/Makefile.am
index a6aecc9..9483fe2 100644
--- a/systemd/Makefile.am
+++ b/systemd/Makefile.am
@@ -21,4 +21,5 @@ unit_DATA = $(unitfiles)
 EXTRA_DIST = \
 	$(unitfiles) \
 	ceph \
-	ceph.tmpfiles.d
+	ceph.tmpfiles.d \
+	50-ceph.preset
diff --git a/systemd/Makefile.in b/systemd/Makefile.in
index 5a62803..6055638 100644
--- a/systemd/Makefile.in
+++ b/systemd/Makefile.in
@@ -377,7 +377,8 @@ unit_DATA = $(unitfiles)
 EXTRA_DIST = \
 	$(unitfiles) \
 	ceph \
-	ceph.tmpfiles.d
+	ceph.tmpfiles.d \
+	50-ceph.preset
 
 all: all-am
 
diff --git a/systemd/ceph-osd at .service b/systemd/ceph-osd at .service
index df1893e..ac178e3 100644
--- a/systemd/ceph-osd at .service
+++ b/systemd/ceph-osd at .service
@@ -10,7 +10,7 @@ LimitNPROC=1048576
 EnvironmentFile=-/etc/sysconfig/ceph
 Environment=CLUSTER=ceph
 ExecStart=/usr/bin/ceph-osd -f --cluster ${CLUSTER} --id %i --setuser ceph --setgroup ceph
-ExecStartPre=/usr/lib/ceph/ceph-osd-prestart.sh --cluster ${CLUSTER} --id %i --setuser ceph --setgroup ceph
+ExecStartPre=/usr/lib/ceph/ceph-osd-prestart.sh --cluster ${CLUSTER} --id %i
 ExecReload=/bin/kill -HUP $MAINPID
 ProtectHome=true
 ProtectSystem=full

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ceph/ceph.git



More information about the Pkg-ceph-commits mailing list