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

James Downing Page jamespage at moszumanska.debian.org
Thu Jun 16 10:28:25 UTC 2016


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

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

commit d10be6b8623870b60d4449a706144a967bbb6881
Author: James Page <james.page at ubuntu.com>
Date:   Thu Jun 16 11:27:17 2016 +0100

    Imported Upstream version 10.2.2
---
 AUTHORS                                            |   5 +
 ChangeLog                                          | 201 ++++-
 Makefile.am                                        |   2 -
 Makefile.in                                        |   3 +-
 ceph.spec                                          | 350 +++------
 ceph.spec.in                                       | 348 +++------
 configure                                          |  20 +-
 configure.ac                                       |   2 +-
 install-deps.sh                                    |   2 +-
 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                                    | 867 ++++++++++++---------
 src/ceph-disk/ceph_disk/main.py                    |  34 +-
 src/ceph.in                                        |   2 +-
 src/client/Client.cc                               | 522 +++++++------
 src/client/Client.h                                |  91 ++-
 src/client/Dentry.h                                |   8 +-
 src/client/Dir.h                                   |   6 +-
 src/client/Inode.h                                 |  14 +-
 src/client/MetaRequest.cc                          |   7 -
 src/client/MetaRequest.h                           |  12 +-
 src/cls/journal/cls_journal.cc                     | 117 ++-
 src/cls/journal/cls_journal_client.cc              |   2 -
 src/cls/journal/cls_journal_types.h                |   2 +
 src/cls/rbd/cls_rbd.cc                             |  15 +-
 src/cls/rbd/cls_rbd_client.cc                      |  83 +-
 src/cls/rbd/cls_rbd_client.h                       |  10 +
 src/common/Readahead.cc                            |  10 +
 src/common/Readahead.h                             |  10 +
 src/common/Throttle.cc                             |  13 +-
 src/common/config_opts.h                           |   3 +-
 src/common/hobject.cc                              |   6 +
 src/common/hobject.h                               |  86 +-
 src/erasure-code/Makefile.am                       |   2 +
 src/global/Makefile.am                             |   4 +-
 src/include/ceph_fs.h                              |  13 +
 src/journal/AsyncOpTracker.cc                      |  33 +-
 src/journal/AsyncOpTracker.h                       |   6 +
 src/journal/FutureImpl.h                           |   3 +-
 src/journal/JournalMetadata.cc                     | 197 ++++-
 src/journal/JournalMetadata.h                      |  10 +-
 src/journal/JournalPlayer.cc                       | 417 +++++++---
 src/journal/JournalPlayer.h                        |  49 +-
 src/journal/JournalRecorder.cc                     | 164 +++-
 src/journal/JournalRecorder.h                      |  36 +-
 src/journal/JournalTrimmer.cc                      |  18 +-
 src/journal/JournalTrimmer.h                       |   6 +
 src/journal/Journaler.cc                           |  82 +-
 src/journal/Journaler.h                            |   2 +
 src/journal/ObjectPlayer.cc                        |  70 +-
 src/journal/ObjectPlayer.h                         |  16 +-
 src/journal/ObjectRecorder.cc                      |  81 +-
 src/journal/ObjectRecorder.h                       |  26 +-
 src/journal/Utils.h                                |  24 +
 src/librbd/AioCompletion.cc                        |  78 +-
 src/librbd/AioCompletion.h                         |  38 +-
 src/librbd/AioImageRequest.cc                      |  62 +-
 src/librbd/AioImageRequest.h                       |  12 +-
 src/librbd/AioImageRequestWQ.cc                    |   5 +-
 src/librbd/AioObjectRequest.cc                     |   2 +-
 src/librbd/ImageCtx.cc                             |  66 +-
 src/librbd/ImageCtx.h                              |   4 +-
 src/librbd/ImageState.cc                           |  26 +-
 src/librbd/ImageState.h                            |  13 +-
 src/librbd/Journal.cc                              | 262 ++++---
 src/librbd/Journal.h                               |  32 +-
 src/librbd/LibrbdWriteback.cc                      |  48 +-
 src/librbd/LibrbdWriteback.h                       |   3 +-
 src/librbd/Operations.cc                           |   8 +
 src/librbd/TaskFinisher.h                          |  49 +-
 src/librbd/exclusive_lock/AcquireRequest.cc        |  34 +-
 src/librbd/exclusive_lock/AcquireRequest.h         |  57 +-
 src/librbd/image/OpenRequest.cc                    |  95 ++-
 src/librbd/image/OpenRequest.h                     |  65 +-
 src/librbd/image/RefreshRequest.cc                 |  17 +-
 src/librbd/image/RefreshRequest.h                  |   9 +-
 src/librbd/internal.cc                             | 335 +++++---
 src/librbd/internal.h                              |  22 +-
 src/librbd/journal/Replay.cc                       |  33 +-
 src/librbd/librbd.cc                               |  12 +-
 src/librbd/operation/SnapshotRemoveRequest.cc      |   9 +-
 src/mds/CDentry.h                                  |   2 +-
 src/mds/CDir.cc                                    |  15 +-
 src/mds/CDir.h                                     |  13 +-
 src/mds/MDCache.cc                                 |   3 +
 src/mds/Server.cc                                  |  58 +-
 src/mds/mdstypes.h                                 |  18 +-
 src/mon/Monitor.cc                                 |  12 +-
 src/msg/simple/Pipe.cc                             |   8 +
 src/os/bluestore/BlueStore.cc                      |   2 +-
 src/osd/OSD.cc                                     |  71 +-
 src/osd/OSD.h                                      |   9 +-
 src/osd/OpRequest.cc                               |   1 +
 src/osd/PG.cc                                      |  18 +-
 src/osd/PG.h                                       |   2 +-
 src/osd/ReplicatedPG.cc                            |  24 +-
 src/osd/osd_types.cc                               |   2 +-
 src/osdc/ObjectCacher.cc                           | 370 +++++----
 src/osdc/ObjectCacher.h                            |  15 +-
 src/osdc/Objecter.cc                               |   4 +-
 src/osdc/Striper.cc                                |   3 +-
 src/osdc/WritebackHandler.h                        |   3 +-
 src/pybind/ceph_volume_client.py                   |  48 +-
 src/pybind/rbd/rbd.pyx                             | 552 ++++++++++++-
 src/rgw/rgw_admin.cc                               |   3 +-
 src/rgw/rgw_op.h                                   |   5 +-
 src/rgw/rgw_rados.cc                               |  41 +-
 src/rgw/rgw_rados.h                                |  19 +-
 src/rgw/rgw_rest.cc                                |   3 -
 src/test/Makefile-client.am                        |   9 +
 src/test/centos-6/ceph.spec.in                     | 348 +++------
 src/test/centos-6/install-deps.sh                  |   2 +-
 src/test/centos-7/ceph.spec.in                     | 348 +++------
 src/test/centos-7/install-deps.sh                  |   2 +-
 src/test/cls_rbd/test_cls_rbd.cc                   |  67 ++
 src/test/debian-jessie/install-deps.sh             |   2 +-
 src/test/erasure-code/Makefile.am                  |  56 +-
 src/test/fedora-21/ceph.spec.in                    | 348 +++------
 src/test/fedora-21/install-deps.sh                 |   2 +-
 src/test/journal/RadosTestFixture.cc               |   7 +
 src/test/journal/RadosTestFixture.h                |   2 +
 src/test/journal/mock/MockJournaler.h              |   8 +
 src/test/journal/test_JournalMetadata.cc           |  31 +-
 src/test/journal/test_JournalPlayer.cc             | 532 ++++++++++++-
 src/test/journal/test_JournalRecorder.cc           |  35 +
 src/test/journal/test_JournalTrimmer.cc            |   9 +-
 src/test/journal/test_Journaler.cc                 |   8 +
 src/test/journal/test_ObjectPlayer.cc              |  11 +-
 src/test/journal/test_ObjectRecorder.cc            |  61 +-
 src/test/libcephfs/test.cc                         |  48 +-
 .../exclusive_lock/test_mock_AcquireRequest.cc     |  71 ++
 src/test/librbd/fsx.cc                             |  11 +
 src/test/librbd/image/test_mock_RefreshRequest.cc  |  62 +-
 src/test/librbd/journal/test_Entries.cc            |   1 +
 src/test/librbd/journal/test_Replay.cc             |   4 +-
 src/test/librbd/journal/test_mock_Replay.cc        |  25 +-
 src/test/librbd/mock/MockImageState.h              |   3 +
 .../operation/test_mock_SnapshotRemoveRequest.cc   |  31 +
 src/test/librbd/test_librbd.cc                     |  16 -
 src/test/librbd/test_mirroring.cc                  |  97 +++
 src/test/librbd/test_mock_Journal.cc               |  80 +-
 src/test/librbd/test_support.cc                    |  18 +
 src/test/librbd/test_support.h                     |   1 +
 src/test/opensuse-13.2/ceph.spec.in                | 348 +++------
 src/test/opensuse-13.2/install-deps.sh             |   2 +-
 .../image_replayer/test_mock_BootstrapRequest.cc   | 101 ++-
 .../image_replayer/test_mock_CreateImageRequest.cc | 692 ++++++++++++++++
 .../image_sync/test_mock_ImageCopyRequest.cc       |  41 +-
 .../image_sync/test_mock_SnapshotCopyRequest.cc    | 117 ++-
 .../image_sync/test_mock_SnapshotCreateRequest.cc  | 138 +++-
 .../image_sync/test_mock_SyncPointCreateRequest.cc |   3 +
 src/test/rbd_mirror/random_write.cc                | 214 +++++
 src/test/rbd_mirror/test_ClusterWatcher.cc         |  14 +-
 src/test/rbd_mirror/test_ImageDeleter.cc           | 471 +++++++++++
 src/test/rbd_mirror/test_ImageReplayer.cc          |  90 +--
 src/test/rbd_mirror/test_ImageSync.cc              |   8 +-
 src/test/rbd_mirror/test_PoolWatcher.cc            | 116 +--
 src/test/rbd_mirror/test_fixture.cc                |  20 +-
 src/test/rbd_mirror/test_fixture.h                 |   2 +-
 src/test/rbd_mirror/test_main.cc                   |   2 +
 src/test/rbd_mirror/test_mock_ImageReplayer.cc     |  46 +-
 src/test/rbd_mirror/test_mock_ImageSync.cc         |  90 ++-
 src/test/ubuntu-12.04/install-deps.sh              |   2 +-
 src/test/ubuntu-14.04/install-deps.sh              |   2 +-
 src/tools/Makefile-client.am                       |   8 +
 src/tools/rbd/action/Journal.cc                    |  58 +-
 src/tools/rbd_mirror/BaseRequest.h                 |  42 +
 src/tools/rbd_mirror/ClusterWatcher.cc             |  33 +-
 src/tools/rbd_mirror/ClusterWatcher.h              |  18 +-
 src/tools/rbd_mirror/ImageDeleter.cc               | 570 ++++++++++++++
 src/tools/rbd_mirror/ImageDeleter.h                | 154 ++++
 src/tools/rbd_mirror/ImageReplayer.cc              | 705 ++++++++---------
 src/tools/rbd_mirror/ImageReplayer.h               |  77 +-
 src/tools/rbd_mirror/ImageSync.cc                  | 143 ++--
 src/tools/rbd_mirror/ImageSync.h                   |  24 +-
 src/tools/rbd_mirror/Mirror.cc                     |  60 +-
 src/tools/rbd_mirror/Mirror.h                      |  10 +-
 src/tools/rbd_mirror/PoolWatcher.cc                | 145 ++--
 src/tools/rbd_mirror/PoolWatcher.h                 |  29 +-
 src/tools/rbd_mirror/Replayer.cc                   | 380 +++++----
 src/tools/rbd_mirror/Replayer.h                    |  58 +-
 .../rbd_mirror/image_replayer/BootstrapRequest.cc  | 167 ++--
 .../rbd_mirror/image_replayer/BootstrapRequest.h   |  12 +-
 .../image_replayer/CreateImageRequest.cc           | 437 +++++++++++
 .../rbd_mirror/image_replayer/CreateImageRequest.h | 144 ++++
 .../rbd_mirror/image_replayer/OpenImageRequest.cc  | 100 +++
 .../rbd_mirror/image_replayer/OpenImageRequest.h   |  76 ++
 .../image_replayer/ReplayStatusFormatter.cc        |   3 +-
 src/tools/rbd_mirror/image_replayer/Utils.h        |  49 ++
 .../rbd_mirror/image_sync/ImageCopyRequest.cc      |  77 +-
 src/tools/rbd_mirror/image_sync/ImageCopyRequest.h |   6 +-
 .../rbd_mirror/image_sync/ObjectCopyRequest.cc     |  91 +--
 .../rbd_mirror/image_sync/SnapshotCopyRequest.cc   | 225 ++++--
 .../rbd_mirror/image_sync/SnapshotCopyRequest.h    |  25 +-
 .../rbd_mirror/image_sync/SnapshotCreateRequest.cc | 125 ++-
 .../rbd_mirror/image_sync/SnapshotCreateRequest.h  |  13 +-
 .../image_sync/SyncPointCreateRequest.cc           |  53 +-
 .../rbd_mirror/image_sync/SyncPointCreateRequest.h |   6 +
 .../rbd_mirror/image_sync/SyncPointPruneRequest.cc |  31 +-
 src/tools/rbd_mirror/main.cc                       |   3 +
 systemd/ceph-mds at .service                          |   4 +-
 systemd/ceph-mon at .service                          |   4 +-
 systemd/ceph-osd at .service                          |   4 +-
 systemd/ceph-radosgw at .service                      |   4 +-
 udev/60-ceph-partuuid-workaround.rules             |  37 -
 udev/95-ceph-osd-alt.rules                         |   5 -
 241 files changed, 11007 insertions(+), 4991 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 99d7569..ea5d120 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -232,6 +232,7 @@ 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-huge--abdffd45-81df-4aa8-a769-e169993c7a0f.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>
@@ -358,6 +359,7 @@ Moritz Möller <mm at mxs.de>
 Mouad Benchchaoui <m.benchchaoui at x-ion.de>
 Mykola Golub <mgolub at mirantis.com>
 Nathan Cutler <ncutler at suse.com>
+Nathan Cutler <presnypreklad at gmail.com>
 Na Xie <xie.na at h3c.com>
 Neha Ummareddy <nehaummareddy at gmail.com>
 Neil Horman <nhorman at tuxdriver.com>
@@ -376,6 +378,7 @@ Padraig O'Sullivan <posulliv at umd.edu>
 Pascal de Bruijn <pascal at unilogicnetworks.net>
 Patience Warnick <patience at cranium.pelton.net>
 Patrick Donnelly <batrick at batbytes.com>
+Patrick Donnelly <pdonnell at redhat.com>
 Patrick McGarry <patrick at inktank.com>
 Patrick McGarry <pmcgarry at redhat.com>
 Paul Chiang <paul_chiang at tcloudcomputing.com>
@@ -397,6 +400,7 @@ Radoslaw Zarzynski <rzarzynski at mirantis.com>
 Rahul Aggarwal <rahul.1aggarwal at gmail.com>
 Rajesh Nambiar <rajesh.n at msystechnologies.com>
 Raju Kurunkad <raju.kurunkad at sandisk.com>
+Ramana Raja <rraja at redhat.com>
 Ray Lv <xiangyulv at gmail.com>
 rca <bertosmailbox at gmail.com>
 Rémi Buisson <remi.buisson at cdiscount.com>
@@ -543,6 +547,7 @@ Xie Xingguo <xie.xingguo at zte.com.cn>
 Xihui He <xihuihe at gmail.com>
 Xing Lin <xinglin at cs.utah.edu>
 Xingyi Wu <wuxingyi2015 at outlook.com>
+xinxin shu <shuxinxin at chinac.com>
 Xinze Chi <xinze at xksy.com>
 Xinze Chi <xinze at xsky.com>
 Xiong Yiliang <xiongyiliang at xunlei.com>
diff --git a/ChangeLog b/ChangeLog
index b328d9a..66d6661 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,199 @@
-3a66dd4 (HEAD, tag: v10.2.1) 10.2.1
+45107e2 (HEAD, tag: v10.2.2, origin/jewel) 10.2.2
+dd1ea65 librbd: remove should ignore mirror errors from older OSDs
+2656881 librbd: track in-flight flush events for journaling
+a85fbb4 librbd: do not shut down exclusive lock while acquiring
+a38caf9 rbd-mirror: FAILED assert(!m_status_watcher)
+739f343 qa/workunits: ensure replay has started before checking position
+0579a48 rbd-mirror: reset local image id when creating new image
+ec80987 rbd-mirror: handle an attempt to delete a non-mirrored image
+6c30300 cls_rbd: disallow setting an empty mirror image entry to disabling
+edef63d librbd: avoid holding owner_lock waiting for exclusive lock
+d61e3dd client: fix simultaneous readdirs race
+ba9fa11 client: move dir_{release,ordered}_count into class Inode
+f5db278 ceph_test_libcephfs: check order of entries in readdir result
+51a7506 mds: don't reset readdir offset if client supports hash order dentry
+3fe5a09 client: using hash value to compose dentry offset
+a65b3ef client: record 'offset' for each entry of dir_result_t::buffer
+8361b98 client: fix cached readdir after seekdir
+cf26125 mds: define end/complete in readdir reply as single u16 flags
+9ce73cd mds: sort dentries in CDir in hash order
+288160b client: save readdir result into dir_result_t directly
+75edb5b client: don't allocate dir_result_t::buffer dynamically
+98e36d1 client: simplify 'offset in frag'
+2eaaf2e client: move readahead ref get/put to obj constr
+1ec2ef8 client: use fh min readahead to enable readahead
+b0bccc3 client: update comment
+e410468 client: use layout.get_period() helper
+e5759fa librbd: recursive lock possible when disabling journaling
+bc658ed rbd-mirror: do not propagate deletions when pool unavailable
+dc38a26 rbd-mirror: image deleter properly handles multiple snapshots
+0a74552 rbd-mirror: propagate deletions even if image replayer is stopped
+da6d409 qa/workunits/rbd: improve deletion propagation test case
+cb50679 librbd: quickly check for duplicate snap name upon create
+43f3bbf rbd-mirror: stop external replayer before closing remote journaler
+f0bf15e librbd: stop journal recorder before starting external replay
+3a2cd16 rbd-mirror: refresh image after creating sync point snapshot
+6b77d22 rbd-mirror: consistently use dout/derr for debug logging
+03466f0 rbd-mirror: don't return split-brain error if we still in MIRROR_PEER_STATE_SYNCING
+dfefd34 rbd-mirror: refactor split-brain detection code
+c94e5b7 rbd-mirror: support bootstrap canceling
+c8a3e75 rbd-mirror: return ECANCELED when ImageReplayer start is canceled
+0014a9e librbd: flush journal commit positions before starting op
+3cec45a librbd: leaked AioCompletion if IO issued after image shut down
+ef12536 librbd: resize and snap create can hang on journal replay failure
+c8b4cab librbd: missing journal state for flushing/restarting replay
+bc7649b librbd: track IO in the order issued
+602ffd7 librbd: AioCompletion should retrieve CephContext from ImageCtx
+6e20662 librbd: only mark async completion as done after callback
+de29be7 librbd: Journal no longer requires AioCompletion for IO events
+65556aa librbd: bubble journal error up via existing AioObjectRequests
+bddf944 librbd: potential duplicate snap removal can result in crash
+4ce4130 ObjectCacher: do not merge uncommitted journal bhs
+6e64598 ObjectCacher: don't loop the extents of OSDRead in map_read
+c5f281d ObjectCacher: fix the bytes_written in writex
+1c00d81 osdc: don't loop the extents of OSDWrite in map_write
+b48cdb2 librbd: notify image feature update before releasing lock
+680efbd librbd: refresh image after acquiring exclusive lock
+bb279f1 librbd: refresh image if needed in mirror functions
+3b8cb63 cls_rbd: mirror image status summary should read full directory
+4a967eb Revert "osd/ReplicatedPG: for copy_get get omap, firstly check ob whether has omap."
+fd8f8af Revert "osd/ReplicatedPG: For omap read ops, it should check object wether has omap"
+d59ca31 Revert "osd/ReplicatedPG: When do omapclear, it should check object whether is omap."
+f4306de osdc/Objecter: upper bound watch_check result
+64f15b3 OSD: fix deadlock in OSD::_committed_osd_maps
+adfbe95 osd: handle boot racing with NOUP set
+e424482 TaskFinisher: use singleton SafeTimer and Finisher
+99ff1fc BackoffThrottle: wait() if past max
+5ce43eb BackoffThrottle: use wait_for instead of wait_until
+cec6870 test/rbd: fsx needs to shut down the journal
+09200d4 remove invalid objectmap flag when objectmap is disabled Fixes: http://tracker.ceph.com/issues/16076 Signed-off-by: xinxin shu <shuxinxin at chinac.com>
+6c0e202 rbd: check value of config override when setting image-meta
+e15bfff pybind/rbd: create/clone/copy: default to None for features param
+f9e32ac librbd: clone: default to parent features
+6af7b22 qa: dynamic_features.sh: return error only if it failed on alive QEMU
+73464af test: initial python APIs to support mirroring
+5c31266 rbd: initial python APIs to support mirroring
+3084cf3 rbd: close journal before removing
+a32820d src/: remove all direct comparisons to get_max()
+f869594 PG::replica_scrub: don't adjust pool on max object
+1737ff3 hobject: compensate for non-canonical hobject_t::get_max() encodings
+5d9ee88 pybind: configurable cephfs_vol_client prefix and ns_prefix.
+470605c ceph_volume_client: evict client also based on mount path
+726292e client: report root's quota in statfs
+46c2bd0 pybind: fix unicode handling in CephFSVolumeClient::purge
+b989084 ceph-disk: workaround gperftool hang
+69a9500 rpm: unconditionally set ceph user's primary group to ceph (SUSE)
+208659f qa/workunits/rbd: basic cloned image test
+af6be1b rbd-mirror: copy snapshot parent image settings
+8405c2c cls_rbd: asynchronous helper method to set parent
+26f4edc rbd-mirror: add support for cloning images from mirrored parent
+b8b01c5 librbd: extend internal API to clone from open parent image
+9081c58 cls_rbd: asynchronous helper methods to retrieve mirror image metadata
+2c2f4b2 rbd-mirror: helper state machine to open remote/read-only images
+0bcc295 rbd-mirror: cluster-level asok commands need to support multiple pools
+e4547c0 rbd-mirror: replayer should only handle a single pool
+6fc5328 rbd-mirror: group peers by local pools
+0745cc3 rbd-mirror: stop stale replayers before starting new replayers
+91f8373 rbd-mirror: normalize debug log message prefix
+5f0d89a qa/workunits/rbd: create secondary replicated pool
+61542c1 rbd-mirror: image replay now uses asynchronous journal shutdown
+bf4c458 librbd: integrate with async journaler shutdown API
+36cf42a journal: extend asynchronous shutdown to facade
+1b0422d journal: trimmer now has asynchronous shutdown
+f913946 journal: metadata init and shutdown now asynchronous
+1a9d60f journal: player shutdown is now handled asynchronously
+4af5bb1 test: use randomized write sizes for rbd-mirror stress test
+df2b8d9 journal: eliminate watch delay for object refetches
+c65614f journal: keep active tag to assist with pruning watched objects
+30c7f4d journal: update commit entry object number upon overflow
+fa08330 journal: cleanup watch refetch flag handling
+6375c78 rbd-mirror: test: added image-deleter test case when mirroring is disabled
+1ba6505 rbd-mirror: Unregister clients from non-primary images journal
+a53cf28 rbd-mirror: test: Added unit test for testing image-deleter thread
+78b13a5 rbd-mirror: test: Fixed test_PoolWatcher to support the new changes in PoolWatcher class
+fef7456 rbd-mirror: Replayer: bootstrap existing mirrored images
+97eb85f rbd-mirror: replayer: Added image-deleter thread to replayer
+24406c5 rbd-mirror: Added image-deleter thread to rbd-mirror
+0780230 rbd-mirror: Added implementation of image-deleter thread
+38514ad rbd-mirror: librbd::mirror_peer_list never returns -ENOENT
+4e7233a rbd-mirror: fix typo
+c3b1bf1 rbd-mirror: calculate behind_master only if mirror tag is not newer than master
+cb950fc test: workaround failure in journal.sh
+f92c2a5 cls::journal: treat empty commit position as minimal
+be9e85d tests: rm -fr /tmp/*virtualenv*
+8fbb555 cls_journal: Select min commit position for new clients
+576ff0c cls_journal: remove duplicated key generation
+fae360f rgw: fix manager selection when APIs customized
+8bbb5ad osd/OpRequest: reset connection upon unregister
+e97cc2d osd: reset session->osdmap if session is not waiting for a map anymore
+555cec9 ceph.in: fix exception when pool name has non-ascii characters
+305ebbc librbd: metadata retrieval added to open image state machine
+5c9ecea cls_rbd: async version of metadata_list helper method
+0c99028 rbd-mirror: disable librbd caching for replicated images
+188318b rpm: Restore /var/run/ceph context manually
+ab1f65d rpm: Mute semodule in postun script
+587fb3d install-deps.sh: systemd-rpm-macros must be installed on SUSE
+69470d4 rpm: Drop SELinux priority setting
+128dbc8 rpm: Fix SELinux relabel on fedora
+8182b9a Test-enable XIO messenger and IB/RDMA support
+50626ce rpm: do a full make check when --with-tests is given
+fb6858c rpm: drop sysvinit bits from relabel_files function
+db4f2d6 rpm: replace literal paths with RPM macros
+3abf895 rpm: drop udev/95-ceph-osd-alt.rules
+a7e01dd rpm: global replace $RPM_BUILD_ROOT with %{buildroot}
+d81cec9 rpm: put dependencies in alphabetical order
+e8338a5 rpm: drop sysvinit-specific dependencies
+b18d8ac rpm: move boost-random dependency to appropriate section
+94efaef rpm: move unified python-sphinx build dependency
+54dc8bf rpm: drop init-ceph.in-fedora.patch
+641e5bb rpm: drop dead conditionals
+4317a17 rpm: drop python-argparse dependency
+70466ff rpm: drop _with_systemd
+928a815 rpm: drop python_sitelib/sitearch conditional
+9f76b9f udev: remove 60-ceph-partuuid-workaround-rules
+1ad3901 rpm: refrain from installing systemd unit files twice
+9c81f25 test/ec: build the libs only when 'make check'
+663d7c2 mon/Monitor: use CEPH_MON_PORT for CRC output
+64b948c mon/Monitor: fix memory leak
+9c3c6d5 mon/Monitor: use VOID_TEMP_FAILURE_RETRY wrapper for fd close().
+544c538 mon/Monitor: add "host" to tracked_conf_key
+e5ebb51 journal: replay position might change after pruning stale tags
+9ecc3dc librbd: delay commit of overwritten journal event
+070dc7c ObjectCacher: pass new journal tid when overwriting extent
+0dfc787 qa/workunits/rbd: record rbd CLI debug messages during mirror stress
+ffd545b rgw: keep track of written_objs correctly
+8356021 Pipe: take a ref to existing while we are waiting
+e0dfc55 qa/workunits/rbd: fixed rbd_mirror teuthology runtime errors
+5f09b9a journal: reset watch step after pruning expired tag
+2ee1e0a rbd-mirror: additional debug messages during image replayer start/stop
+f9e6bd8 rbd-mirror: ensure proper handling of status updates during shutdown
+f789d83 rbd-mirror: track bootstrap state within image status
+e0b3879 rbd-mirror: combine ImageReplayer stopped and uninitialized states
+9c7977d rbd-mirror: lock ordering issue in status update callback
+66f7f2d rbd-mirror: don't unregister asok commands if image replayer start failed
+569f06f rbd-mirror: avoid potential deadlock
+0d19390 qa/workunits/rbd: rbd-mirror daemon stress test
+d020b6a journal: helper method to detect newer tags
+5651507 journal: skip partially complete tag entries during playback
+18f663d journal: close, advance, and open object set ordering
+27fba6a journal: new ObjectRecorder closed callback
+870c21c journal: do not flag append as full if already known to be full
+1cdebc7 journal: delay object overflow event until in-flight appends settled
+821d92a journal: ignore flush on closed/overflowed object
+3d860d5 journal: implicitly detach future's flush handler on append
+f98e128 journal: async callback for advancing the active object set
+86e51eb journal: re-fetch active object before advancing set during replay
+e885f1e radosgw-admin: fix 'period push' handling of --url
+37ecfa6 global: don't link lttng into libglobal
+16ba13c doc: update mirroring guide to include pool/image status commands
+818166e doc: fixup: "rbd-mirror daemon" instead of "rbd-daemon"
+7be281d debian/control: dh_systemd_start is in the dh-systemd package
+e463aa8 debian: install systemd target files
+3a66dd4 (tag: v10.2.1) 10.2.1
+72c9b6f osd: remove all stale osdmaps in handle_osd_map()
 a496b70 cmake: fix rbd compile errors
 9a46e13 cmake: add library cls_journal for target unittest_librbd
+6c2973c cls::rbd: mirror_image_status_list returned max 64 items
 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
@@ -106,6 +299,7 @@ ae3ce5b messages: add MMDSBeacon::standby_replay
 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
+3674341 osd/PG: update info.stats.* mappings on split
 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
@@ -136,6 +330,9 @@ af1c0bc rbd-mirror: interrupting image replay startup can cause crash
 6e5091e systemd: fix typo in preset file
 d302617 systemd: fix typo in preset file
 8cae07c librbd: fix potential double free of SetSnapRequest instance
+b443fdf ceph.spec.in: recommend ntp-daemon on SUSE
+299f84c deb: make ceph-base and radosgw recommend time-daemon
+2da7cb2 systemd: make Ceph daemon units "want" time-sync.target
 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
@@ -25454,7 +25651,7 @@ f69d025 conf: make dup lines override previous value
 5f3ef77 mon: make pool snap creation ops idempotent
 53aa959 objecter: return ENOENT/EEXIST on pool snap delete/create
 507f99e librados: make snap create/destroy handle client-side errors
-3715d20 mon: check for invalid pool snap creates in preprocess_op, too
+3715d205 mon: check for invalid pool snap creates in preprocess_op, too
 640e5fd qa: simple tests for 'ceph osd create|rm' commands
 6f7837a mon: make 'osd rm ...' idempotent
 4788567 qa: simple test for pool create/delete commands
diff --git a/Makefile.am b/Makefile.am
index 7734be2..6fa1a00 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -15,9 +15,7 @@ EXTRA_DIST += \
 	etc/sysconfig/SuSEfirewall2.d/services/ceph-mon \
 	etc/sysconfig/SuSEfirewall2.d/services/ceph-osd-mds \
 	udev/50-rbd.rules \
-	udev/60-ceph-partuuid-workaround.rules \
 	udev/95-ceph-osd.rules \
-	udev/95-ceph-osd-alt.rules \
 	share/known_hosts_drop.ceph.com \
 	share/id_dsa_drop.ceph.com \
 	share/id_dsa_drop.ceph.com.pub
diff --git a/Makefile.in b/Makefile.in
index 64c99c7..f1cd62e 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -621,8 +621,7 @@ EXTRA_DIST = autogen.sh ceph.spec.in ceph.spec install-deps.sh \
 	etc/sysconfig/ceph \
 	etc/sysconfig/SuSEfirewall2.d/services/ceph-mon \
 	etc/sysconfig/SuSEfirewall2.d/services/ceph-osd-mds \
-	udev/50-rbd.rules udev/60-ceph-partuuid-workaround.rules \
-	udev/95-ceph-osd.rules udev/95-ceph-osd-alt.rules \
+	udev/50-rbd.rules udev/95-ceph-osd.rules \
 	share/known_hosts_drop.ceph.com share/id_dsa_drop.ceph.com \
 	share/id_dsa_drop.ceph.com.pub
 # the "." here makes sure check-local builds gtest and gmock before they are used
diff --git a/ceph.spec b/ceph.spec
index d08855e..992e0b7 100644
--- a/ceph.spec
+++ b/ceph.spec
@@ -17,6 +17,7 @@
 %bcond_with ocf
 %bcond_without cephfs_java
 %bcond_with tests
+%bcond_with xio
 %bcond_without tcmalloc
 %bcond_without libs_compat
 %bcond_with lowmem_builder
@@ -32,39 +33,13 @@
 %bcond_without lttng
 %endif
 
-%if (0%{?el5} || (0%{?rhel_version} >= 500 && 0%{?rhel_version} <= 600))
-%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")}
-%{!?python_sitearch: %global python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")}
-%endif
-
 %if %{with selinux}
 # get selinux policy version
 %{!?_selinux_policy_version: %global _selinux_policy_version %(sed -e 's,.*selinux-policy-\\([^/]*\\)/.*,\\1,' /usr/share/selinux/devel/policyhelp 2>/dev/null || echo 0.0.0)}
-
-%define relabel_files() \
-restorecon -R /usr/bin/ceph-mon > /dev/null 2>&1; \
-restorecon -R /usr/bin/ceph-osd > /dev/null 2>&1; \
-restorecon -R /usr/bin/ceph-mds > /dev/null 2>&1; \
-restorecon -R /usr/bin/radosgw > /dev/null 2>&1; \
-restorecon -R /etc/rc\.d/init\.d/ceph > /dev/null 2>&1; \
-restorecon -R /etc/rc\.d/init\.d/radosgw > /dev/null 2>&1; \
-restorecon -R /var/run/ceph > /dev/null 2>&1; \
-restorecon -R /var/lib/ceph > /dev/null 2>&1; \
-restorecon -R /var/log/ceph > /dev/null 2>&1; \
-restorecon -R /var/log/radosgw > /dev/null 2>&1;
 %endif
 
 %{!?_udevrulesdir: %global _udevrulesdir /lib/udev/rules.d}
-
-# Use systemd files on RHEL 7 and above and in SUSE/openSUSE.
-# Note: We don't install unit files for the services yet. For now,
-# the _with_systemd variable only implies that we'll install
-# /etc/tmpfiles.d/ceph.conf in order to set up the socket directory in
-# /var/run/ceph.
-%if 0%{?fedora} || 0%{?rhel} >= 7 || 0%{?suse_version}
-%global _with_systemd 1
 %{!?tmpfiles_create: %global tmpfiles_create systemd-tmpfiles --create}
-%endif
 
 # unify libexec for all targets
 %global _libexecdir %{_exec_prefix}/lib
@@ -74,7 +49,7 @@ restorecon -R /var/log/radosgw > /dev/null 2>&1;
 # common
 #################################################################################
 Name:		ceph
-Version:	10.2.1
+Version:	10.2.2
 Release:	0%{?dist}
 Epoch:		1
 Summary:	User space components of the Ceph file system
@@ -84,9 +59,6 @@ Group:         System/Filesystems
 %endif
 URL:		http://ceph.com/
 Source0:	http://ceph.com/download/%{name}-%{version}.tar.bz2
-%if 0%{?fedora} || 0%{?rhel}
-Patch0:		init-ceph.in-fedora.patch
-%endif
 #################################################################################
 # dependencies that apply across all distro families
 #################################################################################
@@ -103,20 +75,20 @@ BuildRequires:	checkpolicy
 BuildRequires:	selinux-policy-devel
 BuildRequires:	/usr/share/selinux/devel/policyhelp
 %endif
-BuildRequires:	gcc-c++
 BuildRequires:	boost-devel
 BuildRequires:  cmake
 BuildRequires:	cryptsetup
 BuildRequires:	fuse-devel
+BuildRequires:	gcc-c++
 BuildRequires:	gdbm
 BuildRequires:	hdparm
 BuildRequires:	leveldb-devel > 1.2
 BuildRequires:	libaio-devel
-BuildRequires:	libcurl-devel
-BuildRequires:	libxml2-devel
 BuildRequires:	libblkid-devel >= 2.17
+BuildRequires:	libcurl-devel
 BuildRequires:	libudev-devel
 BuildRequires:	libtool
+BuildRequires:	libxml2-devel
 BuildRequires:	make
 BuildRequires:	parted
 BuildRequires:	perl
@@ -125,6 +97,7 @@ BuildRequires:	python
 BuildRequires:	python-devel
 BuildRequires:	python-nose
 BuildRequires:	python-requests
+BuildRequires:	python-sphinx
 BuildRequires:	python-virtualenv
 BuildRequires:	snappy-devel
 BuildRequires:	util-linux
@@ -138,12 +111,10 @@ BuildRequires:	yasm
 # distro-conditional dependencies
 #################################################################################
 %if 0%{?suse_version}
-%if 0%{?_with_systemd}
 BuildRequires:  pkgconfig(systemd)
 BuildRequires:	systemd-rpm-macros
 BuildRequires:	systemd
 %{?systemd_requires}
-%endif
 PreReq:		%fillup_prereq
 BuildRequires:	net-tools
 BuildRequires:	libbz2-devel
@@ -160,30 +131,18 @@ BuildRequires:  openldap2-devel
 BuildRequires:	python-Cython
 %endif
 %if 0%{?fedora} || 0%{?rhel} 
-%if 0%{?_with_systemd}
 Requires:	systemd
-%endif
+BuildRequires:  boost-random
 BuildRequires:	btrfs-progs
 BuildRequires:	nss-devel
 BuildRequires:	keyutils-libs-devel
 BuildRequires:	libatomic_ops-devel
-Requires(post):	chkconfig
-Requires(preun):	chkconfig
-Requires(preun):	initscripts
 BuildRequires:	gperftools-devel
 BuildRequires:  openldap-devel
 BuildRequires:  openssl-devel
 BuildRequires:  redhat-lsb-core
 BuildRequires:	Cython
 %endif
-# boost
-%if 0%{?fedora} || 0%{?rhel} 
-BuildRequires:  boost-random
-%endif
-# python-argparse for distros with Python 2.6 or lower
-%if (0%{?rhel} && 0%{?rhel} <= 6)
-BuildRequires:	python-argparse
-%endif
 # lttng and babeltrace for rbd-replay-prep
 %if %{with lttng}
 %if 0%{?fedora} || 0%{?rhel}
@@ -204,17 +163,14 @@ BuildRequires:	FastCGI-devel
 BuildRequires:	expat-devel
 BuildRequires:	fcgi-devel
 %endif
-# python-sphinx
-%if 0%{?rhel} > 0 && 0%{?rhel} < 7
-BuildRequires:	python-sphinx10
-%endif
-%if 0%{?fedora} || 0%{?suse_version} || 0%{?rhel} >= 7
-BuildRequires:	python-sphinx
-%endif
 #hardened-cc1
 %if 0%{?fedora} || 0%{?rhel}
 BuildRequires:  redhat-rpm-config
 %endif
+# Accelio IB/RDMA
+%if 0%{with xio}
+BuildRequires:  libxio-devel
+%endif
 
 %description
 Ceph is a massively scalable, open-source, distributed storage system that runs
@@ -249,10 +205,14 @@ Requires:      findutils
 Requires:      which
 %if 0%{?suse_version}
 Requires:      lsb-release
+Recommends:    ntp-daemon
 %endif
 %if 0%{?fedora} || 0%{?rhel}
 Requires:      redhat-lsb-core
 %endif
+%if 0%{with xio}
+Requires:      libxio
+%endif
 %description base
 Base is the package that includes all the files shared amongst ceph servers
 
@@ -266,15 +226,12 @@ Requires:	python-rados = %{epoch}:%{version}-%{release}
 Requires:	python-rbd = %{epoch}:%{version}-%{release}
 Requires:	python-cephfs = %{epoch}:%{version}-%{release}
 Requires:	python-requests
-%if 0%{?_with_systemd}
 %{?systemd_requires}
-%endif
 %if 0%{?suse_version}
 Requires(pre):	pwdutils
 %endif
-# python-argparse is only needed in distros with Python 2.6 or lower
-%if (0%{?rhel} && 0%{?rhel} <= 6)
-Requires:	python-argparse
+%if 0%{with xio}
+Requires:       libxio
 %endif
 %description -n ceph-common
 Common utilities to mount and interact with a ceph storage cluster.
@@ -572,13 +529,8 @@ Group:		System Environment/Libraries
 License:	LGPL-2.0
 Requires:	java
 Requires:	libcephfs_jni1 = %{epoch}:%{version}-%{release}
-%if 0%{?el6}
-Requires:	junit4
-BuildRequires:	junit4
-%else
 Requires:       junit
 BuildRequires:  junit
-%endif
 %description -n cephfs-java
 This package contains the Java libraries for the Ceph File System.
 
@@ -660,9 +612,6 @@ python-cephfs instead.
 #################################################################################
 %prep
 %setup -q
-%if 0%{?fedora} || 0%{?rhel}
-%patch0 -p1 -b .init
-%endif
 
 %build
 %if 0%{with cephfs_java}
@@ -682,14 +631,12 @@ export RPM_OPT_FLAGS=`echo $RPM_OPT_FLAGS | sed -e 's/i386/i486/'`
 %{configure}	CPPFLAGS="$java_inc" \
 		--prefix=/usr \
                 --libexecdir=%{_libexecdir} \
-		--localstatedir=/var \
-		--sysconfdir=/etc \
+		--localstatedir=%{_localstatedir} \
+		--sysconfdir=%{_sysconfdir} \
 %if 0%{?rhel} && ! 0%{?centos}
                 --enable-subman \
 %endif
-%if 0%{?_with_systemd}
 		--with-systemdsystemunitdir=%_unitdir \
-%endif
 		--docdir=%{_docdir}/ceph \
 		--with-man-pages \
 		--mandir="%_mandir" \
@@ -699,6 +646,9 @@ export RPM_OPT_FLAGS=`echo $RPM_OPT_FLAGS | sed -e 's/i386/i486/'`
 %if 0%{with cephfs_java}
 		--enable-cephfs-java \
 %endif
+%if 0%{with xio}
+		--enable-xio \
+%endif
 %if 0%{with selinux}
 		--with-selinux \
 %endif
@@ -725,66 +675,30 @@ make %{?_smp_mflags}
 %if 0%{with tests}
 %check
 # run in-tree unittests
-make %{?_smp_mflags} check-local
+make %{?_smp_mflags} check
 
 %endif
 
 
 
 %install
-make DESTDIR=$RPM_BUILD_ROOT install
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_example.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_fail_to_initialize.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_fail_to_register.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_hangs.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_missing_entry_point.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_missing_version.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_jerasure_generic.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_jerasure_neon.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_jerasure_sse3.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_jerasure_sse4.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_shec_generic.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_shec_neon.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_shec_sse3.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_shec_sse4.so
-find $RPM_BUILD_ROOT -type f -name "*.la" -exec rm -f {} ';'
-find $RPM_BUILD_ROOT -type f -name "*.a" -exec rm -f {} ';'
-install -D src/etc-rbdmap $RPM_BUILD_ROOT%{_sysconfdir}/ceph/rbdmap
+make DESTDIR=%{buildroot} install
+find %{buildroot} -type f -name "*.la" -exec rm -f {} ';'
+find %{buildroot} -type f -name "*.a" -exec rm -f {} ';'
+install -D src/etc-rbdmap %{buildroot}%{_sysconfdir}/ceph/rbdmap
 %if 0%{?fedora} || 0%{?rhel}
-install -m 0644 -D etc/sysconfig/ceph $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig/ceph
+install -m 0644 -D etc/sysconfig/ceph %{buildroot}%{_sysconfdir}/sysconfig/ceph
 %endif
 %if 0%{?suse_version}
-install -m 0644 -D etc/sysconfig/ceph $RPM_BUILD_ROOT%{_localstatedir}/adm/fillup-templates/sysconfig.%{name}
-%endif
-%if 0%{?_with_systemd}
-  install -m 0644 -D systemd/ceph.tmpfiles.d $RPM_BUILD_ROOT%{_tmpfilesdir}/ceph-common.conf
-  install -m 0644 -D systemd/rbdmap.service $RPM_BUILD_ROOT%{_unitdir}/rbdmap.service
-  install -m 0644 -D systemd/ceph-osd at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-osd at .service
-  install -m 0644 -D systemd/ceph-mon at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-mon at .service
-  install -m 0644 -D systemd/ceph-create-keys at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-create-keys at .service
-  install -m 0644 -D systemd/ceph-mds at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-mds at .service
-  install -m 0644 -D systemd/ceph-radosgw at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-radosgw at .service
-  install -m 0644 -D systemd/ceph-rbd-mirror at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-rbd-mirror at .service
-  install -m 0644 -D systemd/ceph.target $RPM_BUILD_ROOT%{_unitdir}/ceph.target
-  install -m 0644 -D systemd/ceph-osd.target $RPM_BUILD_ROOT%{_unitdir}/ceph-osd.target
-  install -m 0644 -D systemd/ceph-mon.target $RPM_BUILD_ROOT%{_unitdir}/ceph-mon.target
-  install -m 0644 -D systemd/ceph-mds.target $RPM_BUILD_ROOT%{_unitdir}/ceph-mds.target
-  install -m 0644 -D systemd/ceph-radosgw.target $RPM_BUILD_ROOT%{_unitdir}/ceph-radosgw.target
-  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
-  install -D src/init-radosgw $RPM_BUILD_ROOT%{_initrddir}/ceph-radosgw
-  ln -sf ../../etc/init.d/ceph %{buildroot}/%{_sbindir}/rcceph
-  ln -sf ../../etc/init.d/ceph-radosgw %{buildroot}/%{_sbindir}/rcceph-radosgw
-%endif
-mkdir -p $RPM_BUILD_ROOT%{_sbindir}
-install -m 0644 -D src/logrotate.conf $RPM_BUILD_ROOT%{_sysconfdir}/logrotate.d/ceph
-chmod 0644 $RPM_BUILD_ROOT%{_docdir}/ceph/sample.ceph.conf
-chmod 0644 $RPM_BUILD_ROOT%{_docdir}/ceph/sample.fetch_config
+install -m 0644 -D etc/sysconfig/ceph %{buildroot}%{_localstatedir}/adm/fillup-templates/sysconfig.%{name}
+%endif
+install -m 0644 -D systemd/ceph.tmpfiles.d %{buildroot}%{_tmpfilesdir}/ceph-common.conf
+install -m 0755 -D systemd/ceph %{buildroot}%{_sbindir}/rcceph
+install -m 0644 -D systemd/50-ceph.preset %{buildroot}%{_libexecdir}/systemd/system-preset/50-ceph.preset
+mkdir -p %{buildroot}%{_sbindir}
+install -m 0644 -D src/logrotate.conf %{buildroot}%{_sysconfdir}/logrotate.d/ceph
+chmod 0644 %{buildroot}%{_docdir}/ceph/sample.ceph.conf
+chmod 0644 %{buildroot}%{_docdir}/ceph/sample.fetch_config
 
 # firewall templates
 %if 0%{?suse_version}
@@ -793,38 +707,26 @@ install -m 0644 -D etc/sysconfig/SuSEfirewall2.d/services/ceph-osd-mds %{buildro
 %endif
 
 # udev rules
-install -m 0644 -D udev/50-rbd.rules $RPM_BUILD_ROOT%{_udevrulesdir}/50-rbd.rules
-install -m 0644 -D udev/60-ceph-partuuid-workaround.rules $RPM_BUILD_ROOT%{_udevrulesdir}/60-ceph-partuuid-workaround.rules
-
-%if (0%{?rhel} && 0%{?rhel} < 7)
-install -m 0644 -D udev/95-ceph-osd-alt.rules $RPM_BUILD_ROOT/lib/udev/rules.d/95-ceph-osd.rules
-%else
-install -m 0644 -D udev/95-ceph-osd.rules $RPM_BUILD_ROOT/lib/udev/rules.d/95-ceph-osd.rules
-%endif
-
-%if 0%{?rhel} >= 7 || 0%{?fedora} || 0%{?suse_version}
-mv $RPM_BUILD_ROOT/lib/udev/rules.d/95-ceph-osd.rules $RPM_BUILD_ROOT/usr/lib/udev/rules.d/95-ceph-osd.rules
-mv $RPM_BUILD_ROOT/sbin/mount.ceph $RPM_BUILD_ROOT/usr/sbin/mount.ceph
-mv $RPM_BUILD_ROOT/sbin/mount.fuse.ceph $RPM_BUILD_ROOT/usr/sbin/mount.fuse.ceph
-%endif
+install -m 0644 -D udev/50-rbd.rules %{buildroot}%{_udevrulesdir}/50-rbd.rules
+install -m 0644 -D udev/95-ceph-osd.rules %{buildroot}%{_udevrulesdir}/95-ceph-osd.rules
+mv %{buildroot}/sbin/mount.ceph %{buildroot}/usr/sbin/mount.ceph
+mv %{buildroot}/sbin/mount.fuse.ceph %{buildroot}/usr/sbin/mount.fuse.ceph
 
 #set up placeholder directories
-mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/ceph
-%if ! 0%{?_with_systemd}
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/run/ceph
-%endif
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/log/ceph
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/tmp
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/mon
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/osd
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/mds
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/radosgw
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/bootstrap-osd
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/bootstrap-mds
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/bootstrap-rgw
+mkdir -p %{buildroot}%{_sysconfdir}/ceph
+mkdir -p %{buildroot}%{_localstatedir}/run/ceph
+mkdir -p %{buildroot}%{_localstatedir}/log/ceph
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/tmp
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/mon
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/osd
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/mds
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/radosgw
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/bootstrap-osd
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/bootstrap-mds
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/bootstrap-rgw
 
 %clean
-rm -rf $RPM_BUILD_ROOT
+rm -rf %{buildroot}
 
 #################################################################################
 # files and systemd scriptlets
@@ -844,19 +746,11 @@ rm -rf $RPM_BUILD_ROOT
 %{_bindir}/ceph-detect-init
 %{_bindir}/ceph-client-debug
 %{_bindir}/cephfs
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-create-keys at .service
 %{_libexecdir}/systemd/system-preset/50-ceph.preset
-%else
-%{_initrddir}/ceph
-%endif
 %{_sbindir}/ceph-create-keys
 %{_sbindir}/rcceph
-%if 0%{?rhel} >= 7 || 0%{?fedora} || 0%{?suse_version}
 %{_sbindir}/mount.ceph
-%else
-/sbin/mount.ceph
-%endif
 %dir %{_libexecdir}/ceph
 %{_libexecdir}/ceph/ceph_common.sh
 %dir %{_libdir}/rados-classes
@@ -897,9 +791,6 @@ rm -rf $RPM_BUILD_ROOT
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/bootstrap-osd
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/bootstrap-mds
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/bootstrap-rgw
-%if ! 0%{?_with_systemd}
-%attr(770,ceph,ceph) %dir %{_localstatedir}/run/ceph
-%endif
 
 %post base
 /sbin/ldconfig
@@ -955,9 +846,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 %endif
 %{_bindir}/ceph-post-file
 %{_bindir}/ceph-brag
-%if 0%{?_with_systemd}
 %{_tmpfilesdir}/ceph-common.conf
-%endif
 %{_mandir}/man8/ceph-authtool.8*
 %{_mandir}/man8/ceph-conf.8*
 %{_mandir}/man8/ceph-dencoder.8*
@@ -979,11 +868,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 %config %{_sysconfdir}/bash_completion.d/rados
 %config %{_sysconfdir}/bash_completion.d/rbd
 %config(noreplace) %{_sysconfdir}/ceph/rbdmap
-%if 0%{?_with_systemd}
 %{_unitdir}/rbdmap.service
-%else
-%{_initrddir}/rbdmap
-%endif
 %{python_sitelib}/ceph_argparse.py*
 %{python_sitelib}/ceph_daemon.py*
 %{_udevrulesdir}/50-rbd.rules
@@ -994,8 +879,8 @@ DISABLE_RESTART_ON_UPDATE="yes"
 CEPH_GROUP_ID=167
 CEPH_USER_ID=167
 %if 0%{?rhel} || 0%{?fedora}
-%{_sbindir}/groupadd ceph -g $CEPH_GROUP_ID -o -r 2>/dev/null || :
-%{_sbindir}/useradd ceph -u $CEPH_USER_ID -o -r -g ceph -s /sbin/nologin -c "Ceph daemons" -d %{_localstatedir}/lib/ceph 2>/dev/null || :
+/usr/sbin/groupadd ceph -g $CEPH_GROUP_ID -o -r 2>/dev/null || :
+/usr/sbin/useradd ceph -u $CEPH_USER_ID -o -r -g ceph -s /sbin/nologin -c "Ceph daemons" -d %{_localstatedir}/lib/ceph 2>/dev/null || :
 %endif
 %if 0%{?suse_version}
 if ! getent group ceph >/dev/null ; then
@@ -1006,33 +891,32 @@ fi
 if ! getent passwd ceph >/dev/null ; then
     CEPH_USER_ID_OPTION=""
     getent passwd $CEPH_USER_ID >/dev/null || CEPH_USER_ID_OPTION="-u $CEPH_USER_ID"
-    useradd ceph $CEPH_USER_ID_OPTION -r -g ceph -s /sbin/nologin -c "Ceph daemons" -d %{_localstatedir}/lib/ceph 2>/dev/null || :
+    useradd ceph $CEPH_USER_ID_OPTION -r -g ceph -s /sbin/nologin 2>/dev/null || :
 fi
+usermod -c "Ceph storage service" \
+        -d %{_localstatedir}/lib/ceph \   
+        -g ceph \
+        -s /sbin/nologin \
+        ceph
 %endif
 exit 0
 
 %post common
-%if 0%{?_with_systemd}
 %tmpfiles_create %{_tmpfilesdir}/ceph-common.conf
-%endif
 
 %postun common
 # Package removal cleanup
 if [ "$1" -eq "0" ] ; then
-    rm -rf /var/log/ceph
-    rm -rf /etc/ceph
+    rm -rf %{_localstatedir}/log/ceph
+    rm -rf %{_sysconfdir}/ceph
 fi
 
 #################################################################################
 %files mds
 %{_bindir}/ceph-mds
 %{_mandir}/man8/ceph-mds.8*
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-mds at .service
 %{_unitdir}/ceph-mds.target
-%else
-%{_initrddir}/ceph
-%endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/mds
 
 %post mds
@@ -1066,7 +950,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1082,12 +966,8 @@ fi
 %{_mandir}/man8/ceph-mon.8*
 %{_mandir}/man8/ceph-rest-api.8*
 %{python_sitelib}/ceph_rest_api.py*
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-mon at .service
 %{_unitdir}/ceph-mon.target
-%else
-%{_initrddir}/ceph
-%endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/mon
 
 %post mon
@@ -1121,7 +1001,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1135,11 +1015,7 @@ fi
 %defattr(-,root,root,-)
 %{_bindir}/ceph-fuse
 %{_mandir}/man8/ceph-fuse.8*
-%if 0%{?rhel} >= 7 || 0%{?fedora} || 0%{?suse_version}
 %{_sbindir}/mount.fuse.ceph
-%else
-/sbin/mount.fuse.ceph
-%endif
 
 #################################################################################
 %files -n rbd-fuse
@@ -1152,10 +1028,8 @@ fi
 %defattr(-,root,root,-)
 %{_bindir}/rbd-mirror
 %{_mandir}/man8/rbd-mirror.8*
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-rbd-mirror at .service
 %{_unitdir}/ceph-rbd-mirror.target
-%endif
 
 %post -n rbd-mirror
 %if 0%{?suse_version}
@@ -1188,7 +1062,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1214,13 +1088,8 @@ fi
 %{_mandir}/man8/radosgw-admin.8*
 %config %{_sysconfdir}/bash_completion.d/radosgw-admin
 %dir %{_localstatedir}/lib/ceph/radosgw
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-radosgw at .service
 %{_unitdir}/ceph-radosgw.target
-%else
-%{_initrddir}/ceph-radosgw
-%{_sbindir}/rcceph-radosgw
-%endif
 
 %post radosgw
 %if 0%{?suse_version}
@@ -1253,7 +1122,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1271,21 +1140,16 @@ fi
 %{_sbindir}/ceph-disk
 %{_sbindir}/ceph-disk-udev
 %{_libexecdir}/ceph/ceph-osd-prestart.sh
-%{_udevrulesdir}/60-ceph-partuuid-workaround.rules
 %{_udevrulesdir}/95-ceph-osd.rules
 %{_mandir}/man8/ceph-clsinfo.8*
 %{_mandir}/man8/ceph-disk.8*
 %{_mandir}/man8/ceph-osd.8*
 %if 0%{?rhel} && ! 0%{?centos}
-/etc/cron.hourly/subman
+%{_sysconfdir}/cron.hourly/subman
 %endif
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-osd at .service
 %{_unitdir}/ceph-osd.target
 %{_unitdir}/ceph-disk at .service
-%else
-%{_initrddir}/ceph
-%endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/osd
 
 %post osd
@@ -1319,7 +1183,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1337,18 +1201,10 @@ fi
 %dir %{_prefix}/lib/ocf
 %dir %{_prefix}/lib/ocf/resource.d
 %dir %{_prefix}/lib/ocf/resource.d/ceph
-%if 0%{_with_systemd}
 %exclude %{_prefix}/lib/ocf/resource.d/ceph/ceph
 %exclude %{_prefix}/lib/ocf/resource.d/ceph/mds
 %exclude %{_prefix}/lib/ocf/resource.d/ceph/mon
 %exclude %{_prefix}/lib/ocf/resource.d/ceph/osd
-%endif
-%if ! 0%{_with_systemd}
-%{_prefix}/lib/ocf/resource.d/ceph/ceph
-%{_prefix}/lib/ocf/resource.d/ceph/mds
-%{_prefix}/lib/ocf/resource.d/ceph/mon
-%{_prefix}/lib/ocf/resource.d/ceph/osd
-%endif
 %{_prefix}/lib/ocf/resource.d/ceph/rbd
 
 %endif
@@ -1560,95 +1416,79 @@ ln -sf %{_libdir}/librbd.so.1 /usr/lib64/qemu/librbd.so.1
 %{_mandir}/man8/ceph_selinux.8*
 
 %post selinux
+# backup file_contexts before update
+. /etc/selinux/config
+FILE_CONTEXT=/etc/selinux/${SELINUXTYPE}/contexts/files/file_contexts
+cp ${FILE_CONTEXT} ${FILE_CONTEXT}.pre
+
 # Install the policy
-OLD_POLVER=$(%{_sbindir}/semodule -l | grep -P '^ceph[\t ]' | awk '{print $2}')
-%{_sbindir}/semodule -n -i %{_datadir}/selinux/packages/ceph.pp
-NEW_POLVER=$(%{_sbindir}/semodule -l | grep -P '^ceph[\t ]' | awk '{print $2}')
+/usr/sbin/semodule -i %{_datadir}/selinux/packages/ceph.pp
 
 # Load the policy if SELinux is enabled
-if %{_sbindir}/selinuxenabled; then
-    %{_sbindir}/load_policy
-else
+if ! /usr/sbin/selinuxenabled; then
     # Do not relabel if selinux is not enabled
     exit 0
 fi
 
-if test "$OLD_POLVER" = "$NEW_POLVER"; then
-   # Do not relabel if policy version did not change
+if diff ${FILE_CONTEXT} ${FILE_CONTEXT}.pre > /dev/null 2>&1; then
+   # Do not relabel if file contexts did not change
    exit 0
 fi
 
 # Check whether the daemons are running
-%if 0%{?_with_systemd}
-    /usr/bin/systemctl status ceph.target > /dev/null 2>&1
-%else
-    /sbin/service ceph status >/dev/null 2>&1
-%endif
+/usr/bin/systemctl status ceph.target > /dev/null 2>&1
 STATUS=$?
 
 # Stop the daemons if they were running
 if test $STATUS -eq 0; then
-%if 0%{?_with_systemd}
     /usr/bin/systemctl stop ceph.target > /dev/null 2>&1
-%else
-    /sbin/service ceph stop >/dev/null 2>&1
-%endif
 fi
 
 # Now, relabel the files
-%relabel_files
+/usr/sbin/fixfiles -C ${FILE_CONTEXT}.pre restore 2> /dev/null
+rm -f ${FILE_CONTEXT}.pre
+# The fixfiles command won't fix label for /var/run/ceph
+/usr/sbin/restorecon -R /var/run/ceph > /dev/null 2>&1
 
 # Start the daemons iff they were running before
 if test $STATUS -eq 0; then
-%if 0%{?_with_systemd}
     /usr/bin/systemctl start ceph.target > /dev/null 2>&1 || :
-%else
-    /sbin/service ceph start >/dev/null 2>&1 || :
-%endif
 fi
-
 exit 0
 
 %postun selinux
 if [ $1 -eq 0 ]; then
+    # backup file_contexts before update
+    . /etc/selinux/config
+    FILE_CONTEXT=/etc/selinux/${SELINUXTYPE}/contexts/files/file_contexts
+    cp ${FILE_CONTEXT} ${FILE_CONTEXT}.pre
+
     # Remove the module
-    %{_sbindir}/semodule -n -r ceph
+    /usr/sbin/semodule -n -r ceph > /dev/null 2>&1
 
     # Reload the policy if SELinux is enabled
-    if %{_sbindir}/selinuxenabled ; then
-        %{_sbindir}/load_policy
-    else
+    if ! /usr/sbin/selinuxenabled ; then
         # Do not relabel if SELinux is not enabled
         exit 0
     fi
 
     # Check whether the daemons are running
-    %if 0%{?_with_systemd}
-        /usr/bin/systemctl status ceph.target > /dev/null 2>&1
-    %else
-        /sbin/service ceph status >/dev/null 2>&1
-    %endif
+    /usr/bin/systemctl status ceph.target > /dev/null 2>&1
     STATUS=$?
 
     # Stop the daemons if they were running
     if test $STATUS -eq 0; then
-    %if 0%{?_with_systemd}
         /usr/bin/systemctl stop ceph.target > /dev/null 2>&1
-    %else
-        /sbin/service ceph stop >/dev/null 2>&1
-    %endif
     fi
 
-    # Now, relabel the files
-    %relabel_files
+    /usr/sbin/fixfiles -C ${FILE_CONTEXT}.pre restore 2> /dev/null
+    rm -f ${FILE_CONTEXT}.pre
+    # The fixfiles command won't fix label for /var/run/ceph
+    /usr/sbin/restorecon -R /var/run/ceph > /dev/null 2>&1
 
     # Start the daemons if they were running before
     if test $STATUS -eq 0; then
-    %if 0%{?_with_systemd}
 	/usr/bin/systemctl start ceph.target > /dev/null 2>&1 || :
-    %else
-	/sbin/service ceph start >/dev/null 2>&1 || :
-    %endif
     fi
 fi
 exit 0
diff --git a/ceph.spec.in b/ceph.spec.in
index 34e4caa..3cf6307 100644
--- a/ceph.spec.in
+++ b/ceph.spec.in
@@ -17,6 +17,7 @@
 %bcond_with ocf
 %bcond_without cephfs_java
 %bcond_with tests
+%bcond_with xio
 %bcond_without tcmalloc
 %bcond_without libs_compat
 %bcond_with lowmem_builder
@@ -32,39 +33,13 @@
 %bcond_without lttng
 %endif
 
-%if (0%{?el5} || (0%{?rhel_version} >= 500 && 0%{?rhel_version} <= 600))
-%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")}
-%{!?python_sitearch: %global python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")}
-%endif
-
 %if %{with selinux}
 # get selinux policy version
 %{!?_selinux_policy_version: %global _selinux_policy_version %(sed -e 's,.*selinux-policy-\\([^/]*\\)/.*,\\1,' /usr/share/selinux/devel/policyhelp 2>/dev/null || echo 0.0.0)}
-
-%define relabel_files() \
-restorecon -R /usr/bin/ceph-mon > /dev/null 2>&1; \
-restorecon -R /usr/bin/ceph-osd > /dev/null 2>&1; \
-restorecon -R /usr/bin/ceph-mds > /dev/null 2>&1; \
-restorecon -R /usr/bin/radosgw > /dev/null 2>&1; \
-restorecon -R /etc/rc\.d/init\.d/ceph > /dev/null 2>&1; \
-restorecon -R /etc/rc\.d/init\.d/radosgw > /dev/null 2>&1; \
-restorecon -R /var/run/ceph > /dev/null 2>&1; \
-restorecon -R /var/lib/ceph > /dev/null 2>&1; \
-restorecon -R /var/log/ceph > /dev/null 2>&1; \
-restorecon -R /var/log/radosgw > /dev/null 2>&1;
 %endif
 
 %{!?_udevrulesdir: %global _udevrulesdir /lib/udev/rules.d}
-
-# Use systemd files on RHEL 7 and above and in SUSE/openSUSE.
-# Note: We don't install unit files for the services yet. For now,
-# the _with_systemd variable only implies that we'll install
-# /etc/tmpfiles.d/ceph.conf in order to set up the socket directory in
-# /var/run/ceph.
-%if 0%{?fedora} || 0%{?rhel} >= 7 || 0%{?suse_version}
-%global _with_systemd 1
 %{!?tmpfiles_create: %global tmpfiles_create systemd-tmpfiles --create}
-%endif
 
 # unify libexec for all targets
 %global _libexecdir %{_exec_prefix}/lib
@@ -84,9 +59,6 @@ Group:         System/Filesystems
 %endif
 URL:		http://ceph.com/
 Source0:	http://ceph.com/download/%{name}-%{version}.tar.bz2
-%if 0%{?fedora} || 0%{?rhel}
-Patch0:		init-ceph.in-fedora.patch
-%endif
 #################################################################################
 # dependencies that apply across all distro families
 #################################################################################
@@ -103,20 +75,20 @@ BuildRequires:	checkpolicy
 BuildRequires:	selinux-policy-devel
 BuildRequires:	/usr/share/selinux/devel/policyhelp
 %endif
-BuildRequires:	gcc-c++
 BuildRequires:	boost-devel
 BuildRequires:  cmake
 BuildRequires:	cryptsetup
 BuildRequires:	fuse-devel
+BuildRequires:	gcc-c++
 BuildRequires:	gdbm
 BuildRequires:	hdparm
 BuildRequires:	leveldb-devel > 1.2
 BuildRequires:	libaio-devel
-BuildRequires:	libcurl-devel
-BuildRequires:	libxml2-devel
 BuildRequires:	libblkid-devel >= 2.17
+BuildRequires:	libcurl-devel
 BuildRequires:	libudev-devel
 BuildRequires:	libtool
+BuildRequires:	libxml2-devel
 BuildRequires:	make
 BuildRequires:	parted
 BuildRequires:	perl
@@ -125,6 +97,7 @@ BuildRequires:	python
 BuildRequires:	python-devel
 BuildRequires:	python-nose
 BuildRequires:	python-requests
+BuildRequires:	python-sphinx
 BuildRequires:	python-virtualenv
 BuildRequires:	snappy-devel
 BuildRequires:	util-linux
@@ -138,12 +111,10 @@ BuildRequires:	yasm
 # distro-conditional dependencies
 #################################################################################
 %if 0%{?suse_version}
-%if 0%{?_with_systemd}
 BuildRequires:  pkgconfig(systemd)
 BuildRequires:	systemd-rpm-macros
 BuildRequires:	systemd
 %{?systemd_requires}
-%endif
 PreReq:		%fillup_prereq
 BuildRequires:	net-tools
 BuildRequires:	libbz2-devel
@@ -160,30 +131,18 @@ BuildRequires:  openldap2-devel
 BuildRequires:	python-Cython
 %endif
 %if 0%{?fedora} || 0%{?rhel} 
-%if 0%{?_with_systemd}
 Requires:	systemd
-%endif
+BuildRequires:  boost-random
 BuildRequires:	btrfs-progs
 BuildRequires:	nss-devel
 BuildRequires:	keyutils-libs-devel
 BuildRequires:	libatomic_ops-devel
-Requires(post):	chkconfig
-Requires(preun):	chkconfig
-Requires(preun):	initscripts
 BuildRequires:	gperftools-devel
 BuildRequires:  openldap-devel
 BuildRequires:  openssl-devel
 BuildRequires:  redhat-lsb-core
 BuildRequires:	Cython
 %endif
-# boost
-%if 0%{?fedora} || 0%{?rhel} 
-BuildRequires:  boost-random
-%endif
-# python-argparse for distros with Python 2.6 or lower
-%if (0%{?rhel} && 0%{?rhel} <= 6)
-BuildRequires:	python-argparse
-%endif
 # lttng and babeltrace for rbd-replay-prep
 %if %{with lttng}
 %if 0%{?fedora} || 0%{?rhel}
@@ -204,17 +163,14 @@ BuildRequires:	FastCGI-devel
 BuildRequires:	expat-devel
 BuildRequires:	fcgi-devel
 %endif
-# python-sphinx
-%if 0%{?rhel} > 0 && 0%{?rhel} < 7
-BuildRequires:	python-sphinx10
-%endif
-%if 0%{?fedora} || 0%{?suse_version} || 0%{?rhel} >= 7
-BuildRequires:	python-sphinx
-%endif
 #hardened-cc1
 %if 0%{?fedora} || 0%{?rhel}
 BuildRequires:  redhat-rpm-config
 %endif
+# Accelio IB/RDMA
+%if 0%{with xio}
+BuildRequires:  libxio-devel
+%endif
 
 %description
 Ceph is a massively scalable, open-source, distributed storage system that runs
@@ -249,10 +205,14 @@ Requires:      findutils
 Requires:      which
 %if 0%{?suse_version}
 Requires:      lsb-release
+Recommends:    ntp-daemon
 %endif
 %if 0%{?fedora} || 0%{?rhel}
 Requires:      redhat-lsb-core
 %endif
+%if 0%{with xio}
+Requires:      libxio
+%endif
 %description base
 Base is the package that includes all the files shared amongst ceph servers
 
@@ -266,15 +226,12 @@ Requires:	python-rados = %{epoch}:%{version}-%{release}
 Requires:	python-rbd = %{epoch}:%{version}-%{release}
 Requires:	python-cephfs = %{epoch}:%{version}-%{release}
 Requires:	python-requests
-%if 0%{?_with_systemd}
 %{?systemd_requires}
-%endif
 %if 0%{?suse_version}
 Requires(pre):	pwdutils
 %endif
-# python-argparse is only needed in distros with Python 2.6 or lower
-%if (0%{?rhel} && 0%{?rhel} <= 6)
-Requires:	python-argparse
+%if 0%{with xio}
+Requires:       libxio
 %endif
 %description -n ceph-common
 Common utilities to mount and interact with a ceph storage cluster.
@@ -572,13 +529,8 @@ Group:		System Environment/Libraries
 License:	LGPL-2.0
 Requires:	java
 Requires:	libcephfs_jni1 = %{epoch}:%{version}-%{release}
-%if 0%{?el6}
-Requires:	junit4
-BuildRequires:	junit4
-%else
 Requires:       junit
 BuildRequires:  junit
-%endif
 %description -n cephfs-java
 This package contains the Java libraries for the Ceph File System.
 
@@ -660,9 +612,6 @@ python-cephfs instead.
 #################################################################################
 %prep
 %setup -q
-%if 0%{?fedora} || 0%{?rhel}
-%patch0 -p1 -b .init
-%endif
 
 %build
 %if 0%{with cephfs_java}
@@ -682,14 +631,12 @@ export RPM_OPT_FLAGS=`echo $RPM_OPT_FLAGS | sed -e 's/i386/i486/'`
 %{configure}	CPPFLAGS="$java_inc" \
 		--prefix=/usr \
                 --libexecdir=%{_libexecdir} \
-		--localstatedir=/var \
-		--sysconfdir=/etc \
+		--localstatedir=%{_localstatedir} \
+		--sysconfdir=%{_sysconfdir} \
 %if 0%{?rhel} && ! 0%{?centos}
                 --enable-subman \
 %endif
-%if 0%{?_with_systemd}
 		--with-systemdsystemunitdir=%_unitdir \
-%endif
 		--docdir=%{_docdir}/ceph \
 		--with-man-pages \
 		--mandir="%_mandir" \
@@ -699,6 +646,9 @@ export RPM_OPT_FLAGS=`echo $RPM_OPT_FLAGS | sed -e 's/i386/i486/'`
 %if 0%{with cephfs_java}
 		--enable-cephfs-java \
 %endif
+%if 0%{with xio}
+		--enable-xio \
+%endif
 %if 0%{with selinux}
 		--with-selinux \
 %endif
@@ -725,66 +675,30 @@ make %{?_smp_mflags}
 %if 0%{with tests}
 %check
 # run in-tree unittests
-make %{?_smp_mflags} check-local
+make %{?_smp_mflags} check
 
 %endif
 
 
 
 %install
-make DESTDIR=$RPM_BUILD_ROOT install
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_example.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_fail_to_initialize.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_fail_to_register.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_hangs.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_missing_entry_point.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_missing_version.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_jerasure_generic.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_jerasure_neon.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_jerasure_sse3.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_jerasure_sse4.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_shec_generic.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_shec_neon.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_shec_sse3.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_shec_sse4.so
-find $RPM_BUILD_ROOT -type f -name "*.la" -exec rm -f {} ';'
-find $RPM_BUILD_ROOT -type f -name "*.a" -exec rm -f {} ';'
-install -D src/etc-rbdmap $RPM_BUILD_ROOT%{_sysconfdir}/ceph/rbdmap
+make DESTDIR=%{buildroot} install
+find %{buildroot} -type f -name "*.la" -exec rm -f {} ';'
+find %{buildroot} -type f -name "*.a" -exec rm -f {} ';'
+install -D src/etc-rbdmap %{buildroot}%{_sysconfdir}/ceph/rbdmap
 %if 0%{?fedora} || 0%{?rhel}
-install -m 0644 -D etc/sysconfig/ceph $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig/ceph
+install -m 0644 -D etc/sysconfig/ceph %{buildroot}%{_sysconfdir}/sysconfig/ceph
 %endif
 %if 0%{?suse_version}
-install -m 0644 -D etc/sysconfig/ceph $RPM_BUILD_ROOT%{_localstatedir}/adm/fillup-templates/sysconfig.%{name}
-%endif
-%if 0%{?_with_systemd}
-  install -m 0644 -D systemd/ceph.tmpfiles.d $RPM_BUILD_ROOT%{_tmpfilesdir}/ceph-common.conf
-  install -m 0644 -D systemd/rbdmap.service $RPM_BUILD_ROOT%{_unitdir}/rbdmap.service
-  install -m 0644 -D systemd/ceph-osd at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-osd at .service
-  install -m 0644 -D systemd/ceph-mon at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-mon at .service
-  install -m 0644 -D systemd/ceph-create-keys at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-create-keys at .service
-  install -m 0644 -D systemd/ceph-mds at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-mds at .service
-  install -m 0644 -D systemd/ceph-radosgw at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-radosgw at .service
-  install -m 0644 -D systemd/ceph-rbd-mirror at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-rbd-mirror at .service
-  install -m 0644 -D systemd/ceph.target $RPM_BUILD_ROOT%{_unitdir}/ceph.target
-  install -m 0644 -D systemd/ceph-osd.target $RPM_BUILD_ROOT%{_unitdir}/ceph-osd.target
-  install -m 0644 -D systemd/ceph-mon.target $RPM_BUILD_ROOT%{_unitdir}/ceph-mon.target
-  install -m 0644 -D systemd/ceph-mds.target $RPM_BUILD_ROOT%{_unitdir}/ceph-mds.target
-  install -m 0644 -D systemd/ceph-radosgw.target $RPM_BUILD_ROOT%{_unitdir}/ceph-radosgw.target
-  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
-  install -D src/init-radosgw $RPM_BUILD_ROOT%{_initrddir}/ceph-radosgw
-  ln -sf ../../etc/init.d/ceph %{buildroot}/%{_sbindir}/rcceph
-  ln -sf ../../etc/init.d/ceph-radosgw %{buildroot}/%{_sbindir}/rcceph-radosgw
-%endif
-mkdir -p $RPM_BUILD_ROOT%{_sbindir}
-install -m 0644 -D src/logrotate.conf $RPM_BUILD_ROOT%{_sysconfdir}/logrotate.d/ceph
-chmod 0644 $RPM_BUILD_ROOT%{_docdir}/ceph/sample.ceph.conf
-chmod 0644 $RPM_BUILD_ROOT%{_docdir}/ceph/sample.fetch_config
+install -m 0644 -D etc/sysconfig/ceph %{buildroot}%{_localstatedir}/adm/fillup-templates/sysconfig.%{name}
+%endif
+install -m 0644 -D systemd/ceph.tmpfiles.d %{buildroot}%{_tmpfilesdir}/ceph-common.conf
+install -m 0755 -D systemd/ceph %{buildroot}%{_sbindir}/rcceph
+install -m 0644 -D systemd/50-ceph.preset %{buildroot}%{_libexecdir}/systemd/system-preset/50-ceph.preset
+mkdir -p %{buildroot}%{_sbindir}
+install -m 0644 -D src/logrotate.conf %{buildroot}%{_sysconfdir}/logrotate.d/ceph
+chmod 0644 %{buildroot}%{_docdir}/ceph/sample.ceph.conf
+chmod 0644 %{buildroot}%{_docdir}/ceph/sample.fetch_config
 
 # firewall templates
 %if 0%{?suse_version}
@@ -793,38 +707,26 @@ install -m 0644 -D etc/sysconfig/SuSEfirewall2.d/services/ceph-osd-mds %{buildro
 %endif
 
 # udev rules
-install -m 0644 -D udev/50-rbd.rules $RPM_BUILD_ROOT%{_udevrulesdir}/50-rbd.rules
-install -m 0644 -D udev/60-ceph-partuuid-workaround.rules $RPM_BUILD_ROOT%{_udevrulesdir}/60-ceph-partuuid-workaround.rules
-
-%if (0%{?rhel} && 0%{?rhel} < 7)
-install -m 0644 -D udev/95-ceph-osd-alt.rules $RPM_BUILD_ROOT/lib/udev/rules.d/95-ceph-osd.rules
-%else
-install -m 0644 -D udev/95-ceph-osd.rules $RPM_BUILD_ROOT/lib/udev/rules.d/95-ceph-osd.rules
-%endif
-
-%if 0%{?rhel} >= 7 || 0%{?fedora} || 0%{?suse_version}
-mv $RPM_BUILD_ROOT/lib/udev/rules.d/95-ceph-osd.rules $RPM_BUILD_ROOT/usr/lib/udev/rules.d/95-ceph-osd.rules
-mv $RPM_BUILD_ROOT/sbin/mount.ceph $RPM_BUILD_ROOT/usr/sbin/mount.ceph
-mv $RPM_BUILD_ROOT/sbin/mount.fuse.ceph $RPM_BUILD_ROOT/usr/sbin/mount.fuse.ceph
-%endif
+install -m 0644 -D udev/50-rbd.rules %{buildroot}%{_udevrulesdir}/50-rbd.rules
+install -m 0644 -D udev/95-ceph-osd.rules %{buildroot}%{_udevrulesdir}/95-ceph-osd.rules
+mv %{buildroot}/sbin/mount.ceph %{buildroot}/usr/sbin/mount.ceph
+mv %{buildroot}/sbin/mount.fuse.ceph %{buildroot}/usr/sbin/mount.fuse.ceph
 
 #set up placeholder directories
-mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/ceph
-%if ! 0%{?_with_systemd}
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/run/ceph
-%endif
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/log/ceph
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/tmp
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/mon
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/osd
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/mds
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/radosgw
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/bootstrap-osd
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/bootstrap-mds
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/bootstrap-rgw
+mkdir -p %{buildroot}%{_sysconfdir}/ceph
+mkdir -p %{buildroot}%{_localstatedir}/run/ceph
+mkdir -p %{buildroot}%{_localstatedir}/log/ceph
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/tmp
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/mon
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/osd
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/mds
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/radosgw
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/bootstrap-osd
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/bootstrap-mds
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/bootstrap-rgw
 
 %clean
-rm -rf $RPM_BUILD_ROOT
+rm -rf %{buildroot}
 
 #################################################################################
 # files and systemd scriptlets
@@ -844,19 +746,11 @@ rm -rf $RPM_BUILD_ROOT
 %{_bindir}/ceph-detect-init
 %{_bindir}/ceph-client-debug
 %{_bindir}/cephfs
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-create-keys at .service
 %{_libexecdir}/systemd/system-preset/50-ceph.preset
-%else
-%{_initrddir}/ceph
-%endif
 %{_sbindir}/ceph-create-keys
 %{_sbindir}/rcceph
-%if 0%{?rhel} >= 7 || 0%{?fedora} || 0%{?suse_version}
 %{_sbindir}/mount.ceph
-%else
-/sbin/mount.ceph
-%endif
 %dir %{_libexecdir}/ceph
 %{_libexecdir}/ceph/ceph_common.sh
 %dir %{_libdir}/rados-classes
@@ -897,9 +791,6 @@ rm -rf $RPM_BUILD_ROOT
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/bootstrap-osd
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/bootstrap-mds
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/bootstrap-rgw
-%if ! 0%{?_with_systemd}
-%attr(770,ceph,ceph) %dir %{_localstatedir}/run/ceph
-%endif
 
 %post base
 /sbin/ldconfig
@@ -955,9 +846,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 %endif
 %{_bindir}/ceph-post-file
 %{_bindir}/ceph-brag
-%if 0%{?_with_systemd}
 %{_tmpfilesdir}/ceph-common.conf
-%endif
 %{_mandir}/man8/ceph-authtool.8*
 %{_mandir}/man8/ceph-conf.8*
 %{_mandir}/man8/ceph-dencoder.8*
@@ -979,11 +868,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 %config %{_sysconfdir}/bash_completion.d/rados
 %config %{_sysconfdir}/bash_completion.d/rbd
 %config(noreplace) %{_sysconfdir}/ceph/rbdmap
-%if 0%{?_with_systemd}
 %{_unitdir}/rbdmap.service
-%else
-%{_initrddir}/rbdmap
-%endif
 %{python_sitelib}/ceph_argparse.py*
 %{python_sitelib}/ceph_daemon.py*
 %{_udevrulesdir}/50-rbd.rules
@@ -994,8 +879,8 @@ DISABLE_RESTART_ON_UPDATE="yes"
 CEPH_GROUP_ID=167
 CEPH_USER_ID=167
 %if 0%{?rhel} || 0%{?fedora}
-%{_sbindir}/groupadd ceph -g $CEPH_GROUP_ID -o -r 2>/dev/null || :
-%{_sbindir}/useradd ceph -u $CEPH_USER_ID -o -r -g ceph -s /sbin/nologin -c "Ceph daemons" -d %{_localstatedir}/lib/ceph 2>/dev/null || :
+/usr/sbin/groupadd ceph -g $CEPH_GROUP_ID -o -r 2>/dev/null || :
+/usr/sbin/useradd ceph -u $CEPH_USER_ID -o -r -g ceph -s /sbin/nologin -c "Ceph daemons" -d %{_localstatedir}/lib/ceph 2>/dev/null || :
 %endif
 %if 0%{?suse_version}
 if ! getent group ceph >/dev/null ; then
@@ -1006,33 +891,32 @@ fi
 if ! getent passwd ceph >/dev/null ; then
     CEPH_USER_ID_OPTION=""
     getent passwd $CEPH_USER_ID >/dev/null || CEPH_USER_ID_OPTION="-u $CEPH_USER_ID"
-    useradd ceph $CEPH_USER_ID_OPTION -r -g ceph -s /sbin/nologin -c "Ceph daemons" -d %{_localstatedir}/lib/ceph 2>/dev/null || :
+    useradd ceph $CEPH_USER_ID_OPTION -r -g ceph -s /sbin/nologin 2>/dev/null || :
 fi
+usermod -c "Ceph storage service" \
+        -d %{_localstatedir}/lib/ceph \   
+        -g ceph \
+        -s /sbin/nologin \
+        ceph
 %endif
 exit 0
 
 %post common
-%if 0%{?_with_systemd}
 %tmpfiles_create %{_tmpfilesdir}/ceph-common.conf
-%endif
 
 %postun common
 # Package removal cleanup
 if [ "$1" -eq "0" ] ; then
-    rm -rf /var/log/ceph
-    rm -rf /etc/ceph
+    rm -rf %{_localstatedir}/log/ceph
+    rm -rf %{_sysconfdir}/ceph
 fi
 
 #################################################################################
 %files mds
 %{_bindir}/ceph-mds
 %{_mandir}/man8/ceph-mds.8*
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-mds at .service
 %{_unitdir}/ceph-mds.target
-%else
-%{_initrddir}/ceph
-%endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/mds
 
 %post mds
@@ -1066,7 +950,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1082,12 +966,8 @@ fi
 %{_mandir}/man8/ceph-mon.8*
 %{_mandir}/man8/ceph-rest-api.8*
 %{python_sitelib}/ceph_rest_api.py*
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-mon at .service
 %{_unitdir}/ceph-mon.target
-%else
-%{_initrddir}/ceph
-%endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/mon
 
 %post mon
@@ -1121,7 +1001,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1135,11 +1015,7 @@ fi
 %defattr(-,root,root,-)
 %{_bindir}/ceph-fuse
 %{_mandir}/man8/ceph-fuse.8*
-%if 0%{?rhel} >= 7 || 0%{?fedora} || 0%{?suse_version}
 %{_sbindir}/mount.fuse.ceph
-%else
-/sbin/mount.fuse.ceph
-%endif
 
 #################################################################################
 %files -n rbd-fuse
@@ -1152,10 +1028,8 @@ fi
 %defattr(-,root,root,-)
 %{_bindir}/rbd-mirror
 %{_mandir}/man8/rbd-mirror.8*
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-rbd-mirror at .service
 %{_unitdir}/ceph-rbd-mirror.target
-%endif
 
 %post -n rbd-mirror
 %if 0%{?suse_version}
@@ -1188,7 +1062,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1214,13 +1088,8 @@ fi
 %{_mandir}/man8/radosgw-admin.8*
 %config %{_sysconfdir}/bash_completion.d/radosgw-admin
 %dir %{_localstatedir}/lib/ceph/radosgw
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-radosgw at .service
 %{_unitdir}/ceph-radosgw.target
-%else
-%{_initrddir}/ceph-radosgw
-%{_sbindir}/rcceph-radosgw
-%endif
 
 %post radosgw
 %if 0%{?suse_version}
@@ -1253,7 +1122,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1271,21 +1140,16 @@ fi
 %{_sbindir}/ceph-disk
 %{_sbindir}/ceph-disk-udev
 %{_libexecdir}/ceph/ceph-osd-prestart.sh
-%{_udevrulesdir}/60-ceph-partuuid-workaround.rules
 %{_udevrulesdir}/95-ceph-osd.rules
 %{_mandir}/man8/ceph-clsinfo.8*
 %{_mandir}/man8/ceph-disk.8*
 %{_mandir}/man8/ceph-osd.8*
 %if 0%{?rhel} && ! 0%{?centos}
-/etc/cron.hourly/subman
+%{_sysconfdir}/cron.hourly/subman
 %endif
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-osd at .service
 %{_unitdir}/ceph-osd.target
 %{_unitdir}/ceph-disk at .service
-%else
-%{_initrddir}/ceph
-%endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/osd
 
 %post osd
@@ -1319,7 +1183,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1337,18 +1201,10 @@ fi
 %dir %{_prefix}/lib/ocf
 %dir %{_prefix}/lib/ocf/resource.d
 %dir %{_prefix}/lib/ocf/resource.d/ceph
-%if 0%{_with_systemd}
 %exclude %{_prefix}/lib/ocf/resource.d/ceph/ceph
 %exclude %{_prefix}/lib/ocf/resource.d/ceph/mds
 %exclude %{_prefix}/lib/ocf/resource.d/ceph/mon
 %exclude %{_prefix}/lib/ocf/resource.d/ceph/osd
-%endif
-%if ! 0%{_with_systemd}
-%{_prefix}/lib/ocf/resource.d/ceph/ceph
-%{_prefix}/lib/ocf/resource.d/ceph/mds
-%{_prefix}/lib/ocf/resource.d/ceph/mon
-%{_prefix}/lib/ocf/resource.d/ceph/osd
-%endif
 %{_prefix}/lib/ocf/resource.d/ceph/rbd
 
 %endif
@@ -1560,95 +1416,79 @@ ln -sf %{_libdir}/librbd.so.1 /usr/lib64/qemu/librbd.so.1
 %{_mandir}/man8/ceph_selinux.8*
 
 %post selinux
+# backup file_contexts before update
+. /etc/selinux/config
+FILE_CONTEXT=/etc/selinux/${SELINUXTYPE}/contexts/files/file_contexts
+cp ${FILE_CONTEXT} ${FILE_CONTEXT}.pre
+
 # Install the policy
-OLD_POLVER=$(%{_sbindir}/semodule -l | grep -P '^ceph[\t ]' | awk '{print $2}')
-%{_sbindir}/semodule -n -i %{_datadir}/selinux/packages/ceph.pp
-NEW_POLVER=$(%{_sbindir}/semodule -l | grep -P '^ceph[\t ]' | awk '{print $2}')
+/usr/sbin/semodule -i %{_datadir}/selinux/packages/ceph.pp
 
 # Load the policy if SELinux is enabled
-if %{_sbindir}/selinuxenabled; then
-    %{_sbindir}/load_policy
-else
+if ! /usr/sbin/selinuxenabled; then
     # Do not relabel if selinux is not enabled
     exit 0
 fi
 
-if test "$OLD_POLVER" = "$NEW_POLVER"; then
-   # Do not relabel if policy version did not change
+if diff ${FILE_CONTEXT} ${FILE_CONTEXT}.pre > /dev/null 2>&1; then
+   # Do not relabel if file contexts did not change
    exit 0
 fi
 
 # Check whether the daemons are running
-%if 0%{?_with_systemd}
-    /usr/bin/systemctl status ceph.target > /dev/null 2>&1
-%else
-    /sbin/service ceph status >/dev/null 2>&1
-%endif
+/usr/bin/systemctl status ceph.target > /dev/null 2>&1
 STATUS=$?
 
 # Stop the daemons if they were running
 if test $STATUS -eq 0; then
-%if 0%{?_with_systemd}
     /usr/bin/systemctl stop ceph.target > /dev/null 2>&1
-%else
-    /sbin/service ceph stop >/dev/null 2>&1
-%endif
 fi
 
 # Now, relabel the files
-%relabel_files
+/usr/sbin/fixfiles -C ${FILE_CONTEXT}.pre restore 2> /dev/null
+rm -f ${FILE_CONTEXT}.pre
+# The fixfiles command won't fix label for /var/run/ceph
+/usr/sbin/restorecon -R /var/run/ceph > /dev/null 2>&1
 
 # Start the daemons iff they were running before
 if test $STATUS -eq 0; then
-%if 0%{?_with_systemd}
     /usr/bin/systemctl start ceph.target > /dev/null 2>&1 || :
-%else
-    /sbin/service ceph start >/dev/null 2>&1 || :
-%endif
 fi
-
 exit 0
 
 %postun selinux
 if [ $1 -eq 0 ]; then
+    # backup file_contexts before update
+    . /etc/selinux/config
+    FILE_CONTEXT=/etc/selinux/${SELINUXTYPE}/contexts/files/file_contexts
+    cp ${FILE_CONTEXT} ${FILE_CONTEXT}.pre
+
     # Remove the module
-    %{_sbindir}/semodule -n -r ceph
+    /usr/sbin/semodule -n -r ceph > /dev/null 2>&1
 
     # Reload the policy if SELinux is enabled
-    if %{_sbindir}/selinuxenabled ; then
-        %{_sbindir}/load_policy
-    else
+    if ! /usr/sbin/selinuxenabled ; then
         # Do not relabel if SELinux is not enabled
         exit 0
     fi
 
     # Check whether the daemons are running
-    %if 0%{?_with_systemd}
-        /usr/bin/systemctl status ceph.target > /dev/null 2>&1
-    %else
-        /sbin/service ceph status >/dev/null 2>&1
-    %endif
+    /usr/bin/systemctl status ceph.target > /dev/null 2>&1
     STATUS=$?
 
     # Stop the daemons if they were running
     if test $STATUS -eq 0; then
-    %if 0%{?_with_systemd}
         /usr/bin/systemctl stop ceph.target > /dev/null 2>&1
-    %else
-        /sbin/service ceph stop >/dev/null 2>&1
-    %endif
     fi
 
-    # Now, relabel the files
-    %relabel_files
+    /usr/sbin/fixfiles -C ${FILE_CONTEXT}.pre restore 2> /dev/null
+    rm -f ${FILE_CONTEXT}.pre
+    # The fixfiles command won't fix label for /var/run/ceph
+    /usr/sbin/restorecon -R /var/run/ceph > /dev/null 2>&1
 
     # Start the daemons if they were running before
     if test $STATUS -eq 0; then
-    %if 0%{?_with_systemd}
 	/usr/bin/systemctl start ceph.target > /dev/null 2>&1 || :
-    %else
-	/sbin/service ceph start >/dev/null 2>&1 || :
-    %endif
     fi
 fi
 exit 0
diff --git a/configure b/configure
index 1e4e171..8083cc5 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.1.
+# Generated by GNU Autoconf 2.69 for ceph 10.2.2.
 #
 # 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.1'
-PACKAGE_STRING='ceph 10.2.1'
+PACKAGE_VERSION='10.2.2'
+PACKAGE_STRING='ceph 10.2.2'
 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.1 to adapt to many kinds of systems.
+\`configure' configures ceph 10.2.2 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.1:";;
+     short | recursive ) echo "Configuration of ceph 10.2.2:";;
    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.1
+ceph configure 10.2.2
 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.1, which was
+It was created by ceph $as_me 10.2.2, 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.1'
+ VERSION='10.2.2'
 
 
 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.1, which was
+This file was extended by ceph $as_me 10.2.2, 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.1
+ceph config.status 10.2.2
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 
diff --git a/configure.ac b/configure.ac
index cd0c91c..e06baf1 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.1], [ceph-devel at vger.kernel.org])
+AC_INIT([ceph], [10.2.2], [ceph-devel at vger.kernel.org])
 
 AX_CXX_COMPILE_STDCXX_11(, mandatory)
 
diff --git a/install-deps.sh b/install-deps.sh
index 21e71ee..03ca760 100755
--- a/install-deps.sh
+++ b/install-deps.sh
@@ -28,7 +28,7 @@ if type apt-get > /dev/null 2>&1 ; then
 fi
 
 if type zypper > /dev/null 2>&1 ; then
-    $SUDO zypper --gpg-auto-import-keys --non-interactive install lsb-release
+    $SUDO zypper --gpg-auto-import-keys --non-interactive install lsb-release systemd-rpm-macros
 fi
 
 case $(lsb_release -si) in
diff --git a/man/ceph-authtool.8 b/man/ceph-authtool.8
index 7179110..0502d27 100644
--- a/man/ceph-authtool.8
+++ b/man/ceph-authtool.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-AUTHTOOL" "8" "May 13, 2016" "dev" "Ceph"
+.TH "CEPH-AUTHTOOL" "8" "June 14, 2016" "dev" "Ceph"
 .SH NAME
 ceph-authtool \- ceph keyring manipulation tool
 .
diff --git a/man/ceph-clsinfo.8 b/man/ceph-clsinfo.8
index 66168bc..7e5c299 100644
--- a/man/ceph-clsinfo.8
+++ b/man/ceph-clsinfo.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-CLSINFO" "8" "May 13, 2016" "dev" "Ceph"
+.TH "CEPH-CLSINFO" "8" "June 14, 2016" "dev" "Ceph"
 .SH NAME
 ceph-clsinfo \- show class object information
 .
diff --git a/man/ceph-conf.8 b/man/ceph-conf.8
index 274c94b..6ffbd16 100644
--- a/man/ceph-conf.8
+++ b/man/ceph-conf.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-CONF" "8" "May 13, 2016" "dev" "Ceph"
+.TH "CEPH-CONF" "8" "June 14, 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 25c4ac1..fa5ecd8 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" "May 13, 2016" "dev" "Ceph"
+.TH "CEPH-CREATE-KEYS" "8" "June 14, 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 277a94a..2735a75 100644
--- a/man/ceph-debugpack.8
+++ b/man/ceph-debugpack.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-DEBUGPACK" "8" "May 13, 2016" "dev" "Ceph"
+.TH "CEPH-DEBUGPACK" "8" "June 14, 2016" "dev" "Ceph"
 .SH NAME
 ceph-debugpack \- ceph debug packer utility
 .
diff --git a/man/ceph-dencoder.8 b/man/ceph-dencoder.8
index d5ebf27..63f1356 100644
--- a/man/ceph-dencoder.8
+++ b/man/ceph-dencoder.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-DENCODER" "8" "May 13, 2016" "dev" "Ceph"
+.TH "CEPH-DENCODER" "8" "June 14, 2016" "dev" "Ceph"
 .SH NAME
 ceph-dencoder \- ceph encoder/decoder utility
 .
diff --git a/man/ceph-deploy.8 b/man/ceph-deploy.8
index d65e1af..6ccf587 100644
--- a/man/ceph-deploy.8
+++ b/man/ceph-deploy.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-DEPLOY" "8" "May 13, 2016" "dev" "Ceph"
+.TH "CEPH-DEPLOY" "8" "June 14, 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 a4fff0d..94242e9 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" "May 13, 2016" "dev" "Ceph"
+.TH "CEPH-DETECT-INIT" "8" "June 14, 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 9eff08a..618897f 100644
--- a/man/ceph-disk.8
+++ b/man/ceph-disk.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-DISK" "8" "May 13, 2016" "dev" "Ceph"
+.TH "CEPH-DISK" "8" "June 14, 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 5090e8b..219fefb 100644
--- a/man/ceph-fuse.8
+++ b/man/ceph-fuse.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-FUSE" "8" "May 13, 2016" "dev" "Ceph"
+.TH "CEPH-FUSE" "8" "June 14, 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 7f93717..0b16acd 100644
--- a/man/ceph-mds.8
+++ b/man/ceph-mds.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-MDS" "8" "May 13, 2016" "dev" "Ceph"
+.TH "CEPH-MDS" "8" "June 14, 2016" "dev" "Ceph"
 .SH NAME
 ceph-mds \- ceph metadata server daemon
 .
diff --git a/man/ceph-mon.8 b/man/ceph-mon.8
index fab207c..ee60c53 100644
--- a/man/ceph-mon.8
+++ b/man/ceph-mon.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-MON" "8" "May 13, 2016" "dev" "Ceph"
+.TH "CEPH-MON" "8" "June 14, 2016" "dev" "Ceph"
 .SH NAME
 ceph-mon \- ceph monitor daemon
 .
diff --git a/man/ceph-osd.8 b/man/ceph-osd.8
index 8ecaa25..528ce83 100644
--- a/man/ceph-osd.8
+++ b/man/ceph-osd.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-OSD" "8" "May 13, 2016" "dev" "Ceph"
+.TH "CEPH-OSD" "8" "June 14, 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 4707583..c835754 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" "May 13, 2016" "dev" "Ceph"
+.TH "CEPH-POST-FILE" "8" "June 14, 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 3441426..cac2ad6 100644
--- a/man/ceph-rbdnamer.8
+++ b/man/ceph-rbdnamer.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-RBDNAMER" "8" "May 13, 2016" "dev" "Ceph"
+.TH "CEPH-RBDNAMER" "8" "June 14, 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 2cd75b6..ebe5524 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" "May 13, 2016" "dev" "Ceph"
+.TH "CEPH-REST-API" "8" "June 14, 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 9dae47d..0ff4647 100644
--- a/man/ceph-run.8
+++ b/man/ceph-run.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-RUN" "8" "May 13, 2016" "dev" "Ceph"
+.TH "CEPH-RUN" "8" "June 14, 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 edb791f..e09a243 100644
--- a/man/ceph-syn.8
+++ b/man/ceph-syn.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH-SYN" "8" "May 13, 2016" "dev" "Ceph"
+.TH "CEPH-SYN" "8" "June 14, 2016" "dev" "Ceph"
 .SH NAME
 ceph-syn \- ceph synthetic workload generator
 .
diff --git a/man/ceph.8 b/man/ceph.8
index 9ed382e..5d2be6d 100644
--- a/man/ceph.8
+++ b/man/ceph.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPH" "8" "May 13, 2016" "dev" "Ceph"
+.TH "CEPH" "8" "June 14, 2016" "dev" "Ceph"
 .SH NAME
 ceph \- ceph administration tool
 .
diff --git a/man/cephfs.8 b/man/cephfs.8
index 5dde2fa..6924d76 100644
--- a/man/cephfs.8
+++ b/man/cephfs.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CEPHFS" "8" "May 13, 2016" "dev" "Ceph"
+.TH "CEPHFS" "8" "June 14, 2016" "dev" "Ceph"
 .SH NAME
 cephfs \- ceph file system options utility
 .
diff --git a/man/crushtool.8 b/man/crushtool.8
index 93dd210..04798f8 100644
--- a/man/crushtool.8
+++ b/man/crushtool.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "CRUSHTOOL" "8" "May 13, 2016" "dev" "Ceph"
+.TH "CRUSHTOOL" "8" "June 14, 2016" "dev" "Ceph"
 .SH NAME
 crushtool \- CRUSH map manipulation tool
 .
diff --git a/man/librados-config.8 b/man/librados-config.8
index 443ae2b..2267d2a 100644
--- a/man/librados-config.8
+++ b/man/librados-config.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "LIBRADOS-CONFIG" "8" "May 13, 2016" "dev" "Ceph"
+.TH "LIBRADOS-CONFIG" "8" "June 14, 2016" "dev" "Ceph"
 .SH NAME
 librados-config \- display information about librados
 .
diff --git a/man/monmaptool.8 b/man/monmaptool.8
index b2157f5..77d81d7 100644
--- a/man/monmaptool.8
+++ b/man/monmaptool.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "MONMAPTOOL" "8" "May 13, 2016" "dev" "Ceph"
+.TH "MONMAPTOOL" "8" "June 14, 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 a0915a8..253e816 100644
--- a/man/mount.ceph.8
+++ b/man/mount.ceph.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "MOUNT.CEPH" "8" "May 13, 2016" "dev" "Ceph"
+.TH "MOUNT.CEPH" "8" "June 14, 2016" "dev" "Ceph"
 .SH NAME
 mount.ceph \- mount a ceph file system
 .
diff --git a/man/osdmaptool.8 b/man/osdmaptool.8
index aa346c4..2542850 100644
--- a/man/osdmaptool.8
+++ b/man/osdmaptool.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "OSDMAPTOOL" "8" "May 13, 2016" "dev" "Ceph"
+.TH "OSDMAPTOOL" "8" "June 14, 2016" "dev" "Ceph"
 .SH NAME
 osdmaptool \- ceph osd cluster map manipulation tool
 .
diff --git a/man/rados.8 b/man/rados.8
index 2c80dc1..bfcb603 100644
--- a/man/rados.8
+++ b/man/rados.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "RADOS" "8" "May 13, 2016" "dev" "Ceph"
+.TH "RADOS" "8" "June 14, 2016" "dev" "Ceph"
 .SH NAME
 rados \- rados object storage utility
 .
diff --git a/man/radosgw-admin.8 b/man/radosgw-admin.8
index 03aa989..d06027d 100644
--- a/man/radosgw-admin.8
+++ b/man/radosgw-admin.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "RADOSGW-ADMIN" "8" "May 13, 2016" "dev" "Ceph"
+.TH "RADOSGW-ADMIN" "8" "June 14, 2016" "dev" "Ceph"
 .SH NAME
 radosgw-admin \- rados REST gateway user administration utility
 .
diff --git a/man/radosgw.8 b/man/radosgw.8
index 80f59a2..2aea650 100644
--- a/man/radosgw.8
+++ b/man/radosgw.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "RADOSGW" "8" "May 13, 2016" "dev" "Ceph"
+.TH "RADOSGW" "8" "June 14, 2016" "dev" "Ceph"
 .SH NAME
 radosgw \- rados REST gateway
 .
diff --git a/man/rbd-fuse.8 b/man/rbd-fuse.8
index 1805707..7111fc3 100644
--- a/man/rbd-fuse.8
+++ b/man/rbd-fuse.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "RBD-FUSE" "8" "May 13, 2016" "dev" "Ceph"
+.TH "RBD-FUSE" "8" "June 14, 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 0ea21a6..2cf1656 100644
--- a/man/rbd-mirror.8
+++ b/man/rbd-mirror.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "RBD-MIRROR" "8" "May 13, 2016" "dev" "Ceph"
+.TH "RBD-MIRROR" "8" "June 14, 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 002739a..c2c37b6 100644
--- a/man/rbd-nbd.8
+++ b/man/rbd-nbd.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "RBD-NBD" "8" "May 13, 2016" "dev" "Ceph"
+.TH "RBD-NBD" "8" "June 14, 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 7d27441..eb67bfa 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" "May 13, 2016" "dev" "Ceph"
+.TH "RBD-REPLAY-MANY" "8" "June 14, 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 0042271..078db7e 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" "May 13, 2016" "dev" "Ceph"
+.TH "RBD-REPLAY-PREP" "8" "June 14, 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 8b65dfa..5fbdf7f 100644
--- a/man/rbd-replay.8
+++ b/man/rbd-replay.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "RBD-REPLAY" "8" "May 13, 2016" "dev" "Ceph"
+.TH "RBD-REPLAY" "8" "June 14, 2016" "dev" "Ceph"
 .SH NAME
 rbd-replay \- replay rados block device (RBD) workloads
 .
diff --git a/man/rbd.8 b/man/rbd.8
index ba08591..72d34b1 100644
--- a/man/rbd.8
+++ b/man/rbd.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "RBD" "8" "May 13, 2016" "dev" "Ceph"
+.TH "RBD" "8" "June 14, 2016" "dev" "Ceph"
 .SH NAME
 rbd \- manage rados block device (RBD) images
 .
diff --git a/man/rbdmap.8 b/man/rbdmap.8
index 3d17a37..92596c8 100644
--- a/man/rbdmap.8
+++ b/man/rbdmap.8
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "RBDMAP" "8" "May 13, 2016" "dev" "Ceph"
+.TH "RBDMAP" "8" "June 14, 2016" "dev" "Ceph"
 .SH NAME
 rbdmap \- map RBD devices at boot time
 .
diff --git a/src/.git_version b/src/.git_version
index 188316b..1dd57db 100644
--- a/src/.git_version
+++ b/src/.git_version
@@ -1,2 +1,2 @@
-3a66dd4f30852819c1bdaa8ec23c795d4ad77269
-v10.2.1
+45107e21c568dd033c2f0a3107dec8f0b0e58374
+v10.2.2
diff --git a/src/Makefile.in b/src/Makefile.in
index bc76a8a..06d9f7e 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -453,50 +453,49 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @ENABLE_CLIENT_TRUE@@WITH_FUSE_TRUE at am__append_94 = libclient_fuse.la
 @ENABLE_CLIENT_TRUE@@WITH_FUSE_TRUE at am__append_95 = client/fuse_ll.h
 @ENABLE_CLIENT_TRUE at am__append_96 = ceph_test_ioctls
- at WITH_LTTNG_TRUE@am__append_97 = -ldl -llttng-ust
- at WITH_TCMALLOC_TRUE@am__append_98 = perfglue/heap_profiler.cc
+ at WITH_TCMALLOC_TRUE@am__append_97 = perfglue/heap_profiler.cc
+ at WITH_TCMALLOC_TRUE@am__append_98 = -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-free
 @WITH_TCMALLOC_TRUE at am__append_99 = -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-free
- at WITH_TCMALLOC_TRUE@am__append_100 = -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-free
- at WITH_TCMALLOC_FALSE@@WITH_TCMALLOC_MINIMAL_TRUE at am__append_101 = perfglue/heap_profiler.cc
+ at WITH_TCMALLOC_FALSE@@WITH_TCMALLOC_MINIMAL_TRUE at am__append_100 = perfglue/heap_profiler.cc
+ at WITH_TCMALLOC_FALSE@@WITH_TCMALLOC_MINIMAL_TRUE at am__append_101 = -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-free
 @WITH_TCMALLOC_FALSE@@WITH_TCMALLOC_MINIMAL_TRUE at am__append_102 = -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-free
- at WITH_TCMALLOC_FALSE@@WITH_TCMALLOC_MINIMAL_TRUE at am__append_103 = -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-free
- at WITH_TCMALLOC_FALSE@@WITH_TCMALLOC_MINIMAL_FALSE at am__append_104 = perfglue/disabled_heap_profiler.cc
- at WITH_PROFILER_TRUE@am__append_105 = perfglue/cpu_profiler.cc
- at WITH_PROFILER_FALSE@am__append_106 = perfglue/disabled_stubs.cc
- at ENABLE_SERVER_TRUE@am__append_107 = \
+ at WITH_TCMALLOC_FALSE@@WITH_TCMALLOC_MINIMAL_FALSE at am__append_103 = perfglue/disabled_heap_profiler.cc
+ at WITH_PROFILER_TRUE@am__append_104 = perfglue/cpu_profiler.cc
+ at WITH_PROFILER_FALSE@am__append_105 = perfglue/disabled_stubs.cc
+ at ENABLE_SERVER_TRUE@am__append_106 = \
 @ENABLE_SERVER_TRUE@	common/xattr.c \
 @ENABLE_SERVER_TRUE@	common/ipaddr.cc \
 @ENABLE_SERVER_TRUE@	common/ceph_json.cc \
 @ENABLE_SERVER_TRUE@	common/util.cc \
 @ENABLE_SERVER_TRUE@	common/pick_address.cc
 
- at LINUX_TRUE@am__append_108 = \
+ at LINUX_TRUE@am__append_107 = \
 @LINUX_TRUE@	common/linux_version.c
 
- at SOLARIS_TRUE@am__append_109 = \
+ at SOLARIS_TRUE@am__append_108 = \
 @SOLARIS_TRUE@        common/solaris_errno.cc
 
- at AIX_TRUE@am__append_110 = \
+ at AIX_TRUE@am__append_109 = \
 @AIX_TRUE@        common/aix_errno.cc
 
- at ENABLE_XIO_TRUE@am__append_111 = \
+ at ENABLE_XIO_TRUE@am__append_110 = \
 @ENABLE_XIO_TRUE@	common/address_helper.cc
 
- at WITH_GOOD_YASM_ELF64_TRUE@am__append_112 = common/crc32c_intel_fast_asm.S common/crc32c_intel_fast_zero_asm.S
+ at WITH_GOOD_YASM_ELF64_TRUE@am__append_111 = common/crc32c_intel_fast_asm.S common/crc32c_intel_fast_zero_asm.S
+ at HAVE_ARMV8_CRC_TRUE@am__append_112 = libcommon_crc_aarch64.la
 @HAVE_ARMV8_CRC_TRUE at am__append_113 = libcommon_crc_aarch64.la
- at HAVE_ARMV8_CRC_TRUE@am__append_114 = libcommon_crc_aarch64.la
- at LINUX_TRUE@am__append_115 = -lrt -lblkid
- at ENABLE_XIO_TRUE@am__append_116 = \
+ at LINUX_TRUE@am__append_114 = -lrt -lblkid
+ at ENABLE_XIO_TRUE@am__append_115 = \
 @ENABLE_XIO_TRUE@	common/address_helper.h
 
- at LINUX_TRUE@am__append_117 = libsecret.la
- at LINUX_TRUE@am__append_118 = msg/async/EventEpoll.cc
- at DARWIN_TRUE@am__append_119 = msg/async/EventKqueue.cc
- at FREEBSD_TRUE@am__append_120 = msg/async/EventKqueue.cc
- at LINUX_TRUE@am__append_121 = msg/async/EventEpoll.h
- at DARWIN_TRUE@am__append_122 = msg/async/EventKqueue.h
- at FREEBSD_TRUE@am__append_123 = msg/async/EventKqueue.h
- at ENABLE_XIO_TRUE@am__append_124 = \
+ at LINUX_TRUE@am__append_116 = libsecret.la
+ at LINUX_TRUE@am__append_117 = msg/async/EventEpoll.cc
+ at DARWIN_TRUE@am__append_118 = msg/async/EventKqueue.cc
+ at FREEBSD_TRUE@am__append_119 = msg/async/EventKqueue.cc
+ at LINUX_TRUE@am__append_120 = msg/async/EventEpoll.h
+ at DARWIN_TRUE@am__append_121 = msg/async/EventKqueue.h
+ at FREEBSD_TRUE@am__append_122 = msg/async/EventKqueue.h
+ at ENABLE_XIO_TRUE@am__append_123 = \
 @ENABLE_XIO_TRUE@	msg/xio/QueueStrategy.cc \
 @ENABLE_XIO_TRUE@	msg/xio/XioConnection.cc \
 @ENABLE_XIO_TRUE@	msg/xio/XioMessenger.cc \
@@ -504,7 +503,7 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @ENABLE_XIO_TRUE@	msg/xio/XioPortal.cc \
 @ENABLE_XIO_TRUE@	msg/xio/XioPool.cc
 
- at ENABLE_XIO_TRUE@am__append_125 = \
+ at ENABLE_XIO_TRUE@am__append_124 = \
 @ENABLE_XIO_TRUE@	msg/xio/DispatchStrategy.h \
 @ENABLE_XIO_TRUE@	msg/xio/FastStrategy.h \
 @ENABLE_XIO_TRUE@	msg/xio/QueueStrategy.h \
@@ -516,22 +515,22 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @ENABLE_XIO_TRUE@	msg/xio/XioPortal.h \
 @ENABLE_XIO_TRUE@	msg/xio/XioSubmit.h
 
- at ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE at am__append_126 = \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE at am__append_125 = \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	$(srcdir)/include/rados/librgw.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	$(srcdir)/include/rados/rgw_file.h
 
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_127 =  \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_126 =  \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	librados_internal.la \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	librados_api.la \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	libjournal.la
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_128 = \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_127 = \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	librados_internal.la libcls_lock_client.la \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	$(LIBOSDC) $(LIBCOMMON_DEPS)
 
- at ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE at am__append_129 = -fvisibility=hidden -fvisibility-inlines-hidden
- at ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE at am__append_130 = -Xcompiler -Xlinker -Xcompiler '--exclude-libs=ALL'
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_131 = librados.la
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_132 = \
+ at ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE at am__append_128 = -fvisibility=hidden -fvisibility-inlines-hidden
+ at ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE at am__append_129 = -Xcompiler -Xlinker -Xcompiler '--exclude-libs=ALL'
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_130 = librados.la
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_131 = \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	librados/snap_set_diff.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	librados/AioCompletionImpl.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	librados/IoCtxImpl.h \
@@ -540,13 +539,13 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	librados/RadosXattrIter.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	librados/ListObjectImpl.h
 
- at ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOSSTRIPER_TRUE@@WITH_RADOS_TRUE at am__append_133 = -export-symbols-regex '^radosstriper_.*'
- at ENABLE_CLIENT_TRUE@@WITH_RADOSSTRIPER_TRUE@@WITH_RADOS_TRUE at am__append_134 = libradosstriper.la
- at ENABLE_CLIENT_TRUE@@WITH_RADOSSTRIPER_TRUE@@WITH_RADOS_TRUE at am__append_135 = \
+ at ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOSSTRIPER_TRUE@@WITH_RADOS_TRUE at am__append_132 = -export-symbols-regex '^radosstriper_.*'
+ at ENABLE_CLIENT_TRUE@@WITH_RADOSSTRIPER_TRUE@@WITH_RADOS_TRUE at am__append_133 = libradosstriper.la
+ at ENABLE_CLIENT_TRUE@@WITH_RADOSSTRIPER_TRUE@@WITH_RADOS_TRUE at am__append_134 = \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSSTRIPER_TRUE@@WITH_RADOS_TRUE@	libradosstriper/RadosStriperImpl.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSSTRIPER_TRUE@@WITH_RADOS_TRUE@	libradosstriper/MultiAioCompletionImpl.h
 
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_136 = \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_135 = \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	journal/AsyncOpTracker.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	journal/Entry.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	journal/Future.h \
@@ -562,12 +561,12 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	journal/ReplayHandler.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	journal/Utils.h
 
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_137 = libjournal.la
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_138 = librbd_internal.la \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_136 = libjournal.la
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_137 = librbd_internal.la \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	librbd_api.la
- at ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_139 = -Xcompiler -Xlinker -Xcompiler '--exclude-libs=ALL'
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_140 = librbd.la
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_141 = \
+ at ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_138 = -Xcompiler -Xlinker -Xcompiler '--exclude-libs=ALL'
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_139 = librbd.la
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_140 = \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	librbd/AioCompletion.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	librbd/AioImageRequest.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	librbd/AioImageRequestWQ.h \
@@ -636,7 +635,7 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 
 
 # inject rgw stuff in the decoder testcase
- at ENABLE_CLIENT_TRUE@am__append_142 = \
+ at ENABLE_CLIENT_TRUE@am__append_141 = \
 @ENABLE_CLIENT_TRUE@	rgw/rgw_dencoder.cc \
 @ENABLE_CLIENT_TRUE@	rgw/rgw_acl.cc \
 @ENABLE_CLIENT_TRUE@	rgw/rgw_basic_types.cc \
@@ -645,7 +644,7 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @ENABLE_CLIENT_TRUE@	rgw/rgw_json_enc.cc \
 @ENABLE_CLIENT_TRUE@	rgw/rgw_keystone.cc
 
- at ENABLE_CLIENT_TRUE@am__append_143 = -lcurl -lexpat \
+ at ENABLE_CLIENT_TRUE@am__append_142 = -lcurl -lexpat \
 @ENABLE_CLIENT_TRUE@	libcls_version_client.la \
 @ENABLE_CLIENT_TRUE@	libcls_log_client.la \
 @ENABLE_CLIENT_TRUE@	libcls_refcount_client.la \
@@ -659,9 +658,9 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @ENABLE_CLIENT_TRUE@	libcls_user_client.la \
 @ENABLE_CLIENT_TRUE@	libcls_numops_client.la \
 @ENABLE_CLIENT_TRUE@	libcls_journal_client.la
- at ENABLE_CLIENT_TRUE@@WITH_OPENLDAP_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE at am__append_144 = rgw/rgw_ldap.cc
+ at ENABLE_CLIENT_TRUE@@WITH_OPENLDAP_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE at am__append_143 = rgw/rgw_ldap.cc
 # noinst_LTLIBRARIES += librgw.la
- at ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE at am__append_145 = \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE at am__append_144 = \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	$(LIBRADOS) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	libcls_rgw_client.la \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	libcls_log_client.la \
@@ -678,16 +677,16 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	-lfcgi \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	-ldl
 
- at ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE at am__append_146 = librgw.la
- at ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE at am__append_147 = -lssl -lcrypto
- at ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE at am__append_148 = libcivetweb.la
- at ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE at am__append_149 = radosgw \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE at am__append_145 = librgw.la
+ at ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE at am__append_146 = -lssl -lcrypto
+ at ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE at am__append_147 = libcivetweb.la
+ at ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE at am__append_148 = radosgw \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	radosgw-admin \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	radosgw-token \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	radosgw-object-expirer
- at ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE at am__append_150 = ceph_rgw_multiparser \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE at am__append_149 = ceph_rgw_multiparser \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	ceph_rgw_jsonparser
- at ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE at am__append_151 = \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE at am__append_150 = \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	rgw/rgw_acl.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	rgw/rgw_acl_s3.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	rgw/rgw_acl_swift.h \
@@ -773,7 +772,7 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	civetweb/include/civetweb_conf.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	civetweb/src/md5.h
 
- at ENABLE_CLIENT_TRUE@am__append_152 = libcls_lock_client.la \
+ at ENABLE_CLIENT_TRUE@am__append_151 = libcls_lock_client.la \
 @ENABLE_CLIENT_TRUE@	libcls_refcount_client.la \
 @ENABLE_CLIENT_TRUE@	libcls_version_client.la \
 @ENABLE_CLIENT_TRUE@	libcls_log_client.la \
@@ -785,7 +784,7 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @ENABLE_CLIENT_TRUE@	libcls_cephfs_client.la \
 @ENABLE_CLIENT_TRUE@	libcls_numops_client.la \
 @ENABLE_CLIENT_TRUE@	libcls_journal_client.la
- at ENABLE_CLIENT_TRUE@am__append_153 = \
+ at ENABLE_CLIENT_TRUE@am__append_152 = \
 @ENABLE_CLIENT_TRUE@	cls/lock/cls_lock_types.h \
 @ENABLE_CLIENT_TRUE@	cls/lock/cls_lock_ops.h \
 @ENABLE_CLIENT_TRUE@	cls/lock/cls_lock_client.h \
@@ -821,7 +820,7 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @ENABLE_CLIENT_TRUE@	cls/journal/cls_journal_client.h \
 @ENABLE_CLIENT_TRUE@	cls/journal/cls_journal_types.h
 
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_154 = libcls_hello.la \
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_153 = libcls_hello.la \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	libcls_numops.la \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	libcls_rbd.la \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	libcls_lock.la \
@@ -835,13 +834,13 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	libcls_rgw.la \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	libcls_cephfs.la \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	libcls_journal.la
- at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_155 = libcls_kvs.la
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_156 = \
+ at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_154 = libcls_kvs.la
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_155 = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	key_value_store/key_value_structure.h \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	key_value_store/kv_flat_btree_async.h \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	key_value_store/kvs_arg_types.h
 
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_157 = rbd_replay/ActionTypes.h \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_156 = rbd_replay/ActionTypes.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	rbd_replay/actions.hpp \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	rbd_replay/BoundedBuffer.hpp \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	rbd_replay/BufferReader.h \
@@ -851,27 +850,27 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	rbd_replay/rbd_loc.hpp \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	rbd_replay/rbd_replay_debug.hpp \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	rbd_replay/Replayer.hpp
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_158 = librbd_replay_types.la \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_157 = librbd_replay_types.la \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	librbd_replay.la \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	librbd_replay_ios.la
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_159 = librbd_replay_types.la
- at ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_160 = rbd-replay
- at ENABLE_CLIENT_TRUE@@WITH_BABELTRACE_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_161 = rbd-replay-prep
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_162 = \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_158 = librbd_replay_types.la
+ at ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_159 = rbd-replay
+ at ENABLE_CLIENT_TRUE@@WITH_BABELTRACE_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_160 = rbd-replay-prep
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_161 = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	test/erasure-code/test-erasure-code.sh \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	test/erasure-code/test-erasure-eio.sh
 
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_163 = test/erasure-code/ceph_erasure_code_benchmark.h \
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_162 = test/erasure-code/ceph_erasure_code_benchmark.h \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	test/erasure-code/ceph_erasure_code_benchmark.h \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	test/erasure-code/ErasureCodeExample.h
- at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_164 = -ldl
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_165 = ceph_erasure_code_benchmark \
+ at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_163 = -ldl
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_164 = ceph_erasure_code_benchmark \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	ceph_erasure_code
- at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_166 = -ldl
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_167 = ceph_erasure_code_non_regression
- at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_168 = -ldl
- at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_169 = -export-symbols-regex '.*__erasure_code_.*'
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_170 = libec_example.la \
+ at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_165 = -ldl
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_166 = ceph_erasure_code_non_regression
+ at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_167 = -ldl
+ at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_168 = -export-symbols-regex '.*__erasure_code_.*'
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_169 = libec_example.la \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	libec_missing_entry_point.la \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	libec_missing_version.la \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	libec_hangs.la \
@@ -885,6 +884,7 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	libec_test_shec_sse4.la \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	libec_test_shec_sse3.la \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	libec_test_shec_generic.la
+ at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_170 = -export-symbols-regex '.*__erasure_code_.*'
 @ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_171 = -export-symbols-regex '.*__erasure_code_.*'
 @ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_172 = -export-symbols-regex '.*__erasure_code_.*'
 @ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_173 = -export-symbols-regex '.*__erasure_code_.*'
@@ -893,20 +893,19 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_176 = -export-symbols-regex '.*__erasure_code_.*'
 @ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_177 = -export-symbols-regex '.*__erasure_code_.*'
 @ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_178 = -export-symbols-regex '.*__erasure_code_.*'
- at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_179 = -export-symbols-regex '.*__erasure_code_.*'
- at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_180 = -ldl
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_181 = unittest_erasure_code_plugin \
+ at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_179 = -ldl
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_180 = unittest_erasure_code_plugin \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	unittest_erasure_code \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	unittest_erasure_code_jerasure \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	unittest_erasure_code_plugin_jerasure
+ at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_181 = -ldl
 @ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_182 = -ldl
- at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_183 = -ldl
- at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_BETTER_YASM_ELF64_TRUE@@WITH_OSD_TRUE at am__append_184 = -ldl
- at ENABLE_SERVER_TRUE@@WITH_BETTER_YASM_ELF64_TRUE@@WITH_OSD_TRUE at am__append_185 = unittest_erasure_code_isa \
+ at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_BETTER_YASM_ELF64_TRUE@@WITH_OSD_TRUE at am__append_183 = -ldl
+ at ENABLE_SERVER_TRUE@@WITH_BETTER_YASM_ELF64_TRUE@@WITH_OSD_TRUE at am__append_184 = unittest_erasure_code_isa \
 @ENABLE_SERVER_TRUE@@WITH_BETTER_YASM_ELF64_TRUE@@WITH_OSD_TRUE@	unittest_erasure_code_plugin_isa
- at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_BETTER_YASM_ELF64_TRUE@@WITH_OSD_TRUE at am__append_186 = -ldl
- at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_187 = -ldl
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_188 =  \
+ at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_BETTER_YASM_ELF64_TRUE@@WITH_OSD_TRUE at am__append_185 = -ldl
+ at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_186 = -ldl
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_187 =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	unittest_erasure_code_lrc \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	unittest_erasure_code_plugin_lrc \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	unittest_erasure_code_shec \
@@ -920,61 +919,61 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	unittest_compression_plugin_snappy \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	unittest_compression_zlib \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	unittest_compression_plugin_zlib
+ at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_188 = -ldl
 @ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_189 = -ldl
 @ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_190 = -ldl
 @ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_191 = -ldl
 @ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_192 = -ldl
 @ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_193 = -ldl
- at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_194 = -ldl
+ at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_194 = -export-symbols-regex '.*__erasure_code_.*'
 @ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_195 = -export-symbols-regex '.*__erasure_code_.*'
 @ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_196 = -export-symbols-regex '.*__erasure_code_.*'
 @ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_197 = -export-symbols-regex '.*__erasure_code_.*'
- at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_198 = -export-symbols-regex '.*__erasure_code_.*'
- at ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE at am__append_199 = test/messenger/message_helper.h \
+ at ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE at am__append_198 = test/messenger/message_helper.h \
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	test/messenger/simple_dispatcher.h \
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	test/messenger/xio_dispatcher.h
+ at ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@@LINUX_TRUE at am__append_199 = -ldl
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@@LINUX_TRUE at am__append_200 = -ldl
- at ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@@LINUX_TRUE at am__append_201 = -ldl
- at ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE at am__append_202 = simple_server \
+ at ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE at am__append_201 = simple_server \
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	simple_client xio_server \
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	xio_client
+ at ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@@LINUX_TRUE at am__append_202 = -ldl
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@@LINUX_TRUE at am__append_203 = -ldl
- at ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@@LINUX_TRUE at am__append_204 = -ldl
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_205 = test/compressor/compressor_example.h
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_206 = libceph_example.la
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_204 = test/compressor/compressor_example.h
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_205 = libceph_example.la
+ at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_206 = -ldl
 @ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_207 = -ldl
 @ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_208 = -ldl
 @ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_209 = -ldl
 @ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_210 = -ldl
- at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_211 = -ldl
+ at COMPILER_HAS_VTA_TRUE@@ENABLE_CLIENT_TRUE at am__append_211 = -fno-var-tracking-assignments
 @COMPILER_HAS_VTA_TRUE@@ENABLE_CLIENT_TRUE at am__append_212 = -fno-var-tracking-assignments
- at COMPILER_HAS_VTA_TRUE@@ENABLE_CLIENT_TRUE at am__append_213 = -fno-var-tracking-assignments
- at ENABLE_CLIENT_TRUE@@WITH_RBD_TRUE at am__append_214 = -DWITH_RBD
- at ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE at am__append_215 = -DWITH_RADOSGW
- at ENABLE_CLIENT_TRUE@am__append_216 = ceph-dencoder
- at ENABLE_CLIENT_TRUE@am__append_217 = \
+ at ENABLE_CLIENT_TRUE@@WITH_RBD_TRUE at am__append_213 = -DWITH_RBD
+ at ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE at am__append_214 = -DWITH_RADOSGW
+ at ENABLE_CLIENT_TRUE@am__append_215 = ceph-dencoder
+ at ENABLE_CLIENT_TRUE@am__append_216 = \
 @ENABLE_CLIENT_TRUE@	test/encoding/test_ceph_time.h
 
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_218 = libradostest.la \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_217 = libradostest.la \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	librados_test_stub.la \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	libjournal_test_mock.la
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_219 = ceph_test_rados \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_218 = ceph_test_rados \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	ceph_test_mutate
- at ENABLE_CLIENT_TRUE@@WITH_BUILD_TESTS_TRUE@@WITH_RADOS_TRUE at am__append_220 = test_build_librados
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_221 =  \
+ at ENABLE_CLIENT_TRUE@@WITH_BUILD_TESTS_TRUE@@WITH_RADOS_TRUE at am__append_219 = test_build_librados
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_220 =  \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	ceph_smalliobench \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	ceph_omapbench \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	ceph_objectstore_bench
- at ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE at am__append_222 = ceph_kvstorebench \
+ at ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE at am__append_221 = ceph_kvstorebench \
 @ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@	ceph_test_rados_list_parallel \
 @ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@	ceph_test_rados_open_pools_parallel \
 @ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@	ceph_test_rados_delete_pools_parallel \
 @ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@	ceph_test_rados_watch_notify
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_223 =  \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_222 =  \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	unittest_librados \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	unittest_librados_config \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	unittest_journal
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_224 =  \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_223 =  \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	ceph_multi_stress_watch \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	ceph_test_cls_rbd \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	ceph_test_cls_refcount \
@@ -1003,7 +1002,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 = test/librados_test_stub/LibradosTestStub.h \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_224 = 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 \
@@ -1013,19 +1012,20 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	test/librados_test_stub/TestMemIoCtxImpl.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	test/librados_test_stub/TestIoCtxImpl.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	test/journal/mock/MockJournaler.h
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_226 = ceph_smalliobenchrbd \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_225 = 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 \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	ceph_test_rbd_mirror \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	ceph_test_rbd_mirror_random_write \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	ceph_test_rbd_mirror_image_replay
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_227 = unittest_rbd_replay
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_228 = librbd_test.la \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_226 = unittest_rbd_replay
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_227 = librbd_test.la \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	librbd_test_mock.la \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	librbd_mirror_test.la
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_229 = unittest_librbd \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_228 = unittest_librbd \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	unittest_rbd_mirror
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_230 = test/run-rbd-unit-tests.sh
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_231 = test/librbd/test_fixture.h \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_229 = test/run-rbd-unit-tests.sh
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_230 = test/librbd/test_fixture.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/librbd/test_mock_fixture.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/librbd/test_support.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/librbd/mock/MockAioImageRequestWQ.h \
@@ -1042,23 +1042,23 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @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 \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/test_mock_fixture.h
- at ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_232 = ceph_test_librbd_fsx
- at ENABLE_CLIENT_TRUE@@WITH_RADOSSTRIPER_TRUE@@WITH_RADOS_TRUE at am__append_233 = libradosstripertest.la
- at ENABLE_CLIENT_TRUE@@WITH_RADOSSTRIPER_TRUE@@WITH_RADOS_TRUE at am__append_234 = ceph_test_rados_striper_api_io \
+ at ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_231 = ceph_test_librbd_fsx
+ at ENABLE_CLIENT_TRUE@@WITH_RADOSSTRIPER_TRUE@@WITH_RADOS_TRUE at am__append_232 = libradosstripertest.la
+ at ENABLE_CLIENT_TRUE@@WITH_RADOSSTRIPER_TRUE@@WITH_RADOS_TRUE at am__append_233 = ceph_test_rados_striper_api_io \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSSTRIPER_TRUE@@WITH_RADOS_TRUE@	ceph_test_rados_striper_api_aio \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSSTRIPER_TRUE@@WITH_RADOS_TRUE@	ceph_test_rados_striper_api_striping
- at ENABLE_CLIENT_TRUE@@WITH_BUILD_TESTS_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at am__append_235 = test_build_libcephfs
- at ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at am__append_236 = unittest_encoding \
+ at ENABLE_CLIENT_TRUE@@WITH_BUILD_TESTS_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at am__append_234 = test_build_libcephfs
+ at ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at am__append_235 = unittest_encoding \
 @ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE@	unittest_base64 \
 @ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE@	unittest_run_cmd \
 @ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE@	unittest_simple_spin \
 @ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE@	unittest_libcephfs_config
- at ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at am__append_237 = test/libcephfs/flock.cc
- at ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at am__append_238 = ceph_test_libcephfs \
+ at ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at am__append_236 = test/libcephfs/flock.cc
+ at ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at am__append_237 = ceph_test_libcephfs \
 @ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE@	ceph_test_c_headers
- at CLANG_FALSE@@ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at am__append_239 = -Werror -Wold-style-declaration
- at ENABLE_CLIENT_TRUE@@WITH_BUILD_TESTS_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE at am__append_240 = test_build_librgw
- at ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE at am__append_241 = ceph_test_cors \
+ at CLANG_FALSE@@ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at am__append_238 = -Werror -Wold-style-declaration
+ at ENABLE_CLIENT_TRUE@@WITH_BUILD_TESTS_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE at am__append_239 = test_build_librgw
+ at ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE at am__append_240 = ceph_test_cors \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	ceph_test_rgw_manifest \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	ceph_test_rgw_period_history \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	ceph_test_rgw_obj \
@@ -1071,20 +1071,20 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	librgw_file_gp \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	librgw_file_aw \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	librgw_file_nfsns
- at ENABLE_SERVER_TRUE@am__append_242 = ceph_test_async_driver \
+ at ENABLE_SERVER_TRUE@am__append_241 = ceph_test_async_driver \
 @ENABLE_SERVER_TRUE@	ceph_test_msgr ceph_test_trans \
 @ENABLE_SERVER_TRUE@	ceph_test_mon_workloadgen \
 @ENABLE_SERVER_TRUE@	ceph_test_mon_msg ceph_perf_objectstore \
 @ENABLE_SERVER_TRUE@	ceph_perf_local ceph_perf_msgr_server \
 @ENABLE_SERVER_TRUE@	ceph_perf_msgr_client
- at ENABLE_SERVER_TRUE@am__append_243 = test/perf_helper.h
- at ENABLE_SERVER_TRUE@@LINUX_TRUE at am__append_244 =  \
+ at ENABLE_SERVER_TRUE@am__append_242 = test/perf_helper.h
+ at ENABLE_SERVER_TRUE@@LINUX_TRUE at am__append_243 =  \
 @ENABLE_SERVER_TRUE@@LINUX_TRUE@	ceph_test_objectstore \
 @ENABLE_SERVER_TRUE@@LINUX_TRUE@	ceph_test_keyvaluedb \
 @ENABLE_SERVER_TRUE@@LINUX_TRUE@	ceph_test_filestore
- at ENABLE_SERVER_TRUE@@LINUX_TRUE at am__append_245 = unittest_bluefs \
+ at ENABLE_SERVER_TRUE@@LINUX_TRUE at am__append_244 = unittest_bluefs \
 @ENABLE_SERVER_TRUE@@LINUX_TRUE@	unittest_bluestore_types
- at ENABLE_SERVER_TRUE@am__append_246 =  \
+ at ENABLE_SERVER_TRUE@am__append_245 =  \
 @ENABLE_SERVER_TRUE@	ceph_test_objectstore_workloadgen \
 @ENABLE_SERVER_TRUE@	ceph_test_filestore_idempotent \
 @ENABLE_SERVER_TRUE@	ceph_test_filestore_idempotent_sequence \
@@ -1092,51 +1092,52 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @ENABLE_SERVER_TRUE@	ceph_test_object_map \
 @ENABLE_SERVER_TRUE@	ceph_test_keyvaluedb_atomicity \
 @ENABLE_SERVER_TRUE@	ceph_test_keyvaluedb_iterators
- at ENABLE_SERVER_TRUE@am__append_247 = unittest_transaction
- at ENABLE_CLIENT_TRUE@@ENABLE_SERVER_TRUE@@WITH_RADOS_TRUE at am__append_248 = ceph_smalliobenchfs \
+ at ENABLE_SERVER_TRUE@am__append_246 = unittest_transaction
+ at ENABLE_CLIENT_TRUE@@ENABLE_SERVER_TRUE@@WITH_RADOS_TRUE at am__append_247 = ceph_smalliobenchfs \
 @ENABLE_CLIENT_TRUE@@ENABLE_SERVER_TRUE@@WITH_RADOS_TRUE@	ceph_smalliobenchdumb \
 @ENABLE_CLIENT_TRUE@@ENABLE_SERVER_TRUE@@WITH_RADOS_TRUE@	ceph_tpbench
- at ENABLE_SERVER_TRUE@@WITH_MON_TRUE at am__append_249 = ceph_test_keys
- at ENABLE_SERVER_TRUE@@WITH_MON_TRUE at am__append_250 = get_command_descriptions
- at ENABLE_SERVER_TRUE@@WITH_MON_TRUE at am__append_251 =  \
+ at ENABLE_SERVER_TRUE@@WITH_MON_TRUE at am__append_248 = ceph_test_keys
+ at ENABLE_SERVER_TRUE@@WITH_MON_TRUE at am__append_249 = get_command_descriptions
+ at ENABLE_SERVER_TRUE@@WITH_MON_TRUE at am__append_250 =  \
 @ENABLE_SERVER_TRUE@@WITH_MON_TRUE@	unittest_mon_moncap \
 @ENABLE_SERVER_TRUE@@WITH_MON_TRUE@	unittest_mon_pgmap
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_252 =  \
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_251 =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	unittest_ecbackend \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	unittest_osdscrub \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	unittest_pglog \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	unittest_hitset \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	unittest_osd_osdcap \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	unittest_pageset
+ at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_252 = -ldl
 @ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_253 = -ldl
- at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_254 = -ldl
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_255 = ceph_test_snap_mapper
- at ENABLE_SERVER_TRUE@@WITH_SLIBROCKSDB_TRUE at am__append_256 = unittest_rocksdb_option_static
- at ENABLE_SERVER_TRUE@@WITH_DLIBROCKSDB_TRUE at am__append_257 = unittest_rocksdb_option
- at ENABLE_SERVER_TRUE@am__append_258 = unittest_chain_xattr \
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_254 = ceph_test_snap_mapper
+ at ENABLE_SERVER_TRUE@@WITH_SLIBROCKSDB_TRUE at am__append_255 = unittest_rocksdb_option_static
+ at ENABLE_SERVER_TRUE@@WITH_DLIBROCKSDB_TRUE at am__append_256 = unittest_rocksdb_option
+ at ENABLE_SERVER_TRUE@am__append_257 = unittest_chain_xattr \
 @ENABLE_SERVER_TRUE@	unittest_lfnindex
- at ENABLE_SERVER_TRUE@@WITH_MDS_TRUE at am__append_259 = unittest_mds_authcap
- at WITH_BUILD_TESTS_TRUE@am__append_260 = test_build_libcommon
- at LINUX_TRUE@am__append_261 = libsystest.la
- at SOLARIS_TRUE@am__append_262 = \
+ at ENABLE_SERVER_TRUE@@WITH_MDS_TRUE at am__append_258 = unittest_mds_authcap
+ at WITH_BUILD_TESTS_TRUE@am__append_259 = test_build_libcommon
+ at LINUX_TRUE@am__append_260 = libsystest.la
+ at SOLARIS_TRUE@am__append_261 = \
 @SOLARIS_TRUE@	-lsocket -lnsl
 
- at LINUX_TRUE@am__append_263 = unittest_blkdev
- at LINUX_TRUE@am__append_264 = ceph_test_get_blkdev_size
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_265 =  \
+ at LINUX_TRUE@am__append_262 = unittest_blkdev
+ at LINUX_TRUE@am__append_263 = ceph_test_get_blkdev_size
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_264 =  \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	ceph_scratchtool \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	ceph_scratchtoolpp \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	ceph_radosacl
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_266 = rados
- at ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_267 = \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_265 = rados
+ at ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_266 = \
 @ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd/action/Kernel.cc \
 @ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd/action/Nbd.cc
 
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_268 = tools/rbd/ArgumentTypes.h \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_267 = tools/rbd/ArgumentTypes.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd/IndentStream.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd/OptionPrinter.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd/Shell.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd/Utils.h \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/BaseRequest.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/ClusterWatcher.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/ImageReplayer.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/ImageSync.h \
@@ -1144,38 +1145,42 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/PoolWatcher.h \
 @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 \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/ImageDeleter.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 \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_replayer/CreateImageRequest.h \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_replayer/OpenImageRequest.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_replayer/OpenLocalImageRequest.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_replayer/ReplayStatusFormatter.h \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_replayer/Utils.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 \
 @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
- at ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_269 = $(LIBKRBD)
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_270 = rbd
- at ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_271 = rbd-nbd
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_272 = librbd_mirror_internal.la
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_273 = rbd-mirror
- at ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at am__append_274 = ceph-client-debug
- at ENABLE_SERVER_TRUE@am__append_275 = ceph-osdomap-tool \
+ at ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_268 = $(LIBKRBD)
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_269 = rbd
+ at ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_270 = rbd-nbd
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_271 = librbd_mirror_internal.la
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_272 = rbd-mirror
+ at ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at am__append_273 = ceph-client-debug
+ at ENABLE_SERVER_TRUE@am__append_274 = ceph-osdomap-tool \
 @ENABLE_SERVER_TRUE@	ceph-monstore-tool ceph-kvstore-tool
- at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_276 = -ldl
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_277 = ceph-objectstore-tool
- at ENABLE_CLIENT_TRUE@@ENABLE_SERVER_TRUE@@WITH_MDS_TRUE@@WITH_RADOS_TRUE at am__append_278 = cephfs-journal-tool \
+ at ENABLE_SERVER_TRUE@@LINUX_TRUE@@WITH_OSD_TRUE at am__append_275 = -ldl
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_276 = ceph-objectstore-tool
+ at ENABLE_CLIENT_TRUE@@ENABLE_SERVER_TRUE@@WITH_MDS_TRUE@@WITH_RADOS_TRUE at am__append_277 = cephfs-journal-tool \
 @ENABLE_CLIENT_TRUE@@ENABLE_SERVER_TRUE@@WITH_MDS_TRUE@@WITH_RADOS_TRUE@	cephfs-table-tool \
 @ENABLE_CLIENT_TRUE@@ENABLE_SERVER_TRUE@@WITH_MDS_TRUE@@WITH_RADOS_TRUE@	cephfs-data-scan
- at WITH_LTTNG_TRUE@am__append_279 = \
+ at WITH_LTTNG_TRUE@am__append_278 = \
 @WITH_LTTNG_TRUE@	libosd_tp.la \
 @WITH_LTTNG_TRUE@	libos_tp.la \
 @WITH_LTTNG_TRUE@	librados_tp.la \
 @WITH_LTTNG_TRUE@	librbd_tp.la
 
- at WITH_LTTNG_TRUE@am__append_280 = \
+ at WITH_LTTNG_TRUE@am__append_279 = \
 @WITH_LTTNG_TRUE@	tracing/librados.h \
 @WITH_LTTNG_TRUE@	tracing/librbd.h \
 @WITH_LTTNG_TRUE@	tracing/objectstore.h \
@@ -1183,63 +1188,63 @@ check_PROGRAMS = $(am__EXEEXT_63) $(am__EXEEXT_64) \
 @WITH_LTTNG_TRUE@	tracing/osd.h \
 @WITH_LTTNG_TRUE@	tracing/pg.h
 
- at ENABLE_CLIENT_TRUE@@WITH_CYTHON_TRUE@@WITH_RADOS_TRUE at am__append_281 = $(srcdir)/pybind/rados/setup.py $(srcdir)/pybind/rados/rados.pyx $(srcdir)/pybind/rados/rados.pxd
- at ENABLE_CLIENT_TRUE@@WITH_CYTHON_TRUE@@WITH_RADOS_TRUE at am__append_282 = rados-pybind-all
- at ENABLE_CLIENT_TRUE@@WITH_CYTHON_TRUE@@WITH_RADOS_TRUE at am__append_283 = rados-pybind-clean
- at ENABLE_CLIENT_TRUE@@WITH_CYTHON_TRUE@@WITH_RADOS_TRUE at am__append_284 = rados-pybind-install-exec
- at ENABLE_CLIENT_TRUE@@WITH_CYTHON_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_285 = $(srcdir)/pybind/rbd/setup.py $(srcdir)/pybind/rbd/rbd.pyx
- at ENABLE_CLIENT_TRUE@@WITH_CYTHON_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_286 = rbd-pybind-all
- at ENABLE_CLIENT_TRUE@@WITH_CYTHON_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_287 = rbd-pybind-clean
- at ENABLE_CLIENT_TRUE@@WITH_CYTHON_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_288 = rbd-pybind-install-exec
- at ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_CYTHON_TRUE@@WITH_RADOS_TRUE at am__append_289 = $(srcdir)/pybind/cephfs/setup.py $(srcdir)/pybind/cephfs/cephfs.pyx
- at ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_CYTHON_TRUE@@WITH_RADOS_TRUE at am__append_290 = cephfs-pybind-all
- at ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_CYTHON_TRUE@@WITH_RADOS_TRUE at am__append_291 = cephfs-pybind-clean
- at ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_CYTHON_TRUE@@WITH_RADOS_TRUE at am__append_292 = cephfs-pybind-install-exec
+ at ENABLE_CLIENT_TRUE@@WITH_CYTHON_TRUE@@WITH_RADOS_TRUE at am__append_280 = $(srcdir)/pybind/rados/setup.py $(srcdir)/pybind/rados/rados.pyx $(srcdir)/pybind/rados/rados.pxd
+ at ENABLE_CLIENT_TRUE@@WITH_CYTHON_TRUE@@WITH_RADOS_TRUE at am__append_281 = rados-pybind-all
+ at ENABLE_CLIENT_TRUE@@WITH_CYTHON_TRUE@@WITH_RADOS_TRUE at am__append_282 = rados-pybind-clean
+ at ENABLE_CLIENT_TRUE@@WITH_CYTHON_TRUE@@WITH_RADOS_TRUE at am__append_283 = rados-pybind-install-exec
+ at ENABLE_CLIENT_TRUE@@WITH_CYTHON_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_284 = $(srcdir)/pybind/rbd/setup.py $(srcdir)/pybind/rbd/rbd.pyx
+ at ENABLE_CLIENT_TRUE@@WITH_CYTHON_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_285 = rbd-pybind-all
+ at ENABLE_CLIENT_TRUE@@WITH_CYTHON_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_286 = rbd-pybind-clean
+ at ENABLE_CLIENT_TRUE@@WITH_CYTHON_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_287 = rbd-pybind-install-exec
+ at ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_CYTHON_TRUE@@WITH_RADOS_TRUE at am__append_288 = $(srcdir)/pybind/cephfs/setup.py $(srcdir)/pybind/cephfs/cephfs.pyx
+ at ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_CYTHON_TRUE@@WITH_RADOS_TRUE at am__append_289 = cephfs-pybind-all
+ at ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_CYTHON_TRUE@@WITH_RADOS_TRUE at am__append_290 = cephfs-pybind-clean
+ at ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_CYTHON_TRUE@@WITH_RADOS_TRUE at am__append_291 = cephfs-pybind-install-exec
 TESTS = $(am__EXEEXT_63) $(check_SCRIPTS)
- at ENABLE_CLIENT_TRUE@am__append_293 = \
+ at ENABLE_CLIENT_TRUE@am__append_292 = \
 @ENABLE_CLIENT_TRUE@	pybind/ceph_argparse.py \
 @ENABLE_CLIENT_TRUE@	pybind/ceph_daemon.py
 
- at ENABLE_CLIENT_TRUE@am__append_294 = ceph-syn
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_295 = \
+ at ENABLE_CLIENT_TRUE@am__append_293 = ceph-syn
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_294 = \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	$(srcdir)/bash_completion/rados \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	$(srcdir)/bash_completion/radosgw-admin
 
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_296 = librados-config
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_297 = \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at am__append_295 = librados-config
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_296 = \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	$(srcdir)/bash_completion/rbd
 
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_298 = \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_297 = \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	ceph-rbdnamer \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	rbd-replay-many \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@        rbdmap
 
- at ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_299 = libkrbd.la
- at ENABLE_CLIENT_TRUE@@WITH_FUSE_TRUE@@WITH_RADOS_TRUE at am__append_300 = ceph-fuse
- at ENABLE_CLIENT_TRUE@@WITH_FUSE_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_301 = rbd-fuse
- at ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at am__append_302 = cephfs
- at ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at am__append_303 = pybind/ceph_volume_client.py
- at ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at am__append_304 = -Xcompiler -Xlinker -Xcompiler '--exclude-libs=libcommon.a'
- at ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at am__append_305 = libcephfs.la
- at ENABLE_CEPHFS_JAVA_TRUE@@ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at am__append_306 = libcephfs_jni.la
- at ENABLE_SERVER_TRUE@am__append_307 = ceph-run ceph-rest-api \
+ at ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_298 = libkrbd.la
+ at ENABLE_CLIENT_TRUE@@WITH_FUSE_TRUE@@WITH_RADOS_TRUE at am__append_299 = ceph-fuse
+ at ENABLE_CLIENT_TRUE@@WITH_FUSE_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__append_300 = rbd-fuse
+ at ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at am__append_301 = cephfs
+ at ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at am__append_302 = pybind/ceph_volume_client.py
+ at ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at am__append_303 = -Xcompiler -Xlinker -Xcompiler '--exclude-libs=libcommon.a'
+ at ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at am__append_304 = libcephfs.la
+ at ENABLE_CEPHFS_JAVA_TRUE@@ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at am__append_305 = libcephfs_jni.la
+ at ENABLE_SERVER_TRUE@am__append_306 = ceph-run ceph-rest-api \
 @ENABLE_SERVER_TRUE@	ceph-debugpack ceph-crush-location \
 @ENABLE_SERVER_TRUE@	ceph-coverage
- at ENABLE_SERVER_TRUE@am__append_308 = pybind/ceph_rest_api.py
- at ENABLE_SERVER_TRUE@am__append_309 = ceph-coverage init-ceph
- at ENABLE_SERVER_TRUE@am__append_310 = init-ceph
- at ENABLE_SERVER_TRUE@@LINUX_TRUE at am__append_311 = mount.ceph
- at ENABLE_SERVER_TRUE@am__append_312 = mount.fuse.ceph
- at ENABLE_SERVER_TRUE@@WITH_MON_TRUE at am__append_313 = ceph-mon
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_314 = \
+ at ENABLE_SERVER_TRUE@am__append_307 = pybind/ceph_rest_api.py
+ at ENABLE_SERVER_TRUE@am__append_308 = ceph-coverage init-ceph
+ at ENABLE_SERVER_TRUE@am__append_309 = init-ceph
+ at ENABLE_SERVER_TRUE@@LINUX_TRUE at am__append_310 = mount.ceph
+ at ENABLE_SERVER_TRUE@am__append_311 = mount.fuse.ceph
+ at ENABLE_SERVER_TRUE@@WITH_MON_TRUE at am__append_312 = ceph-mon
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_313 = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	ceph-disk-udev
 
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_315 = \
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_314 = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	ceph-clsinfo
 
- at ENABLE_SERVER_TRUE@@WITH_LTTNG_TRUE@@WITH_OSD_TRUE at am__append_316 = $(LIBOSD_TP)
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_317 = ceph-osd
- at ENABLE_SERVER_TRUE@@WITH_MDS_TRUE at am__append_318 = ceph-mds
+ at ENABLE_SERVER_TRUE@@WITH_LTTNG_TRUE@@WITH_OSD_TRUE at am__append_315 = $(LIBOSD_TP)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am__append_316 = ceph-osd
+ at ENABLE_SERVER_TRUE@@WITH_MDS_TRUE at am__append_317 = ceph-mds
 subdir = src
 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_check_classpath.m4 \
@@ -1892,7 +1897,7 @@ libcls_version_client_la_OBJECTS =  \
 	$(am_libcls_version_client_la_OBJECTS)
 @ENABLE_CLIENT_TRUE at am_libcls_version_client_la_rpath =
 am__DEPENDENCIES_5 = libcommon_internal.la libcommon_crc.la \
-	$(am__append_113) $(LIBERASURE_CODE) $(LIBCOMPRESSOR) \
+	$(am__append_112) $(LIBERASURE_CODE) $(LIBCOMPRESSOR) \
 	$(LIBMSG) $(LIBAUTH) $(LIBCRUSH) $(LIBJSON_SPIRIT) $(LIBLOG) \
 	$(LIBARCH) $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
 libcommon_la_DEPENDENCIES = $(am__DEPENDENCIES_5)
@@ -2026,8 +2031,7 @@ libec_example_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
 	$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
 	$(libec_example_la_CXXFLAGS) $(CXXFLAGS) \
 	$(libec_example_la_LDFLAGS) $(LDFLAGS) -o $@
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_example_la_rpath = -rpath \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(erasure_codelibdir)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_example_la_rpath =
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_fail_to_initialize_la_DEPENDENCIES =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__DEPENDENCIES_1) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__DEPENDENCIES_3)
@@ -2040,9 +2044,7 @@ libec_fail_to_initialize_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
 	$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
 	$(libec_fail_to_initialize_la_CXXFLAGS) $(CXXFLAGS) \
 	$(libec_fail_to_initialize_la_LDFLAGS) $(LDFLAGS) -o $@
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_fail_to_initialize_la_rpath =  \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-rpath \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(erasure_codelibdir)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_fail_to_initialize_la_rpath =
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_fail_to_register_la_DEPENDENCIES =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__DEPENDENCIES_1) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__DEPENDENCIES_3)
@@ -2055,9 +2057,7 @@ libec_fail_to_register_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
 	$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
 	$(libec_fail_to_register_la_CXXFLAGS) $(CXXFLAGS) \
 	$(libec_fail_to_register_la_LDFLAGS) $(LDFLAGS) -o $@
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_fail_to_register_la_rpath =  \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-rpath \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(erasure_codelibdir)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_fail_to_register_la_rpath =
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_hangs_la_DEPENDENCIES =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__DEPENDENCIES_1) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__DEPENDENCIES_3)
@@ -2069,8 +2069,7 @@ libec_hangs_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
 	$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
 	$(libec_hangs_la_CXXFLAGS) $(CXXFLAGS) \
 	$(libec_hangs_la_LDFLAGS) $(LDFLAGS) -o $@
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_hangs_la_rpath = -rpath \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(erasure_codelibdir)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_hangs_la_rpath =
 @WITH_BETTER_YASM_ELF64_TRUE at libec_isa_la_DEPENDENCIES = libisa.la \
 @WITH_BETTER_YASM_ELF64_TRUE@	$(LIBCRUSH) $(am__DEPENDENCIES_1) \
 @WITH_BETTER_YASM_ELF64_TRUE@	$(am__DEPENDENCIES_3)
@@ -2233,9 +2232,7 @@ libec_missing_entry_point_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
 	$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
 	$(libec_missing_entry_point_la_CXXFLAGS) $(CXXFLAGS) \
 	$(libec_missing_entry_point_la_LDFLAGS) $(LDFLAGS) -o $@
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_missing_entry_point_la_rpath =  \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-rpath \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(erasure_codelibdir)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_missing_entry_point_la_rpath =
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_missing_version_la_DEPENDENCIES =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__DEPENDENCIES_1) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__DEPENDENCIES_3)
@@ -2248,9 +2245,7 @@ libec_missing_version_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
 	$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
 	$(libec_missing_version_la_CXXFLAGS) $(CXXFLAGS) \
 	$(libec_missing_version_la_LDFLAGS) $(LDFLAGS) -o $@
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_missing_version_la_rpath =  \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-rpath \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(erasure_codelibdir)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_missing_version_la_rpath =
 libec_shec_la_DEPENDENCIES = $(LIBCRUSH) $(am__DEPENDENCIES_1) \
 	$(am__DEPENDENCIES_3)
 am_libec_shec_la_OBJECTS = erasure-code/shec/libec_shec_la-ErasureCodePluginSelectShec.lo
@@ -2398,9 +2393,7 @@ libec_test_jerasure_generic_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
 	$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
 	$(libec_test_jerasure_generic_la_CXXFLAGS) $(CXXFLAGS) \
 	$(libec_test_jerasure_generic_la_LDFLAGS) $(LDFLAGS) -o $@
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_test_jerasure_generic_la_rpath =  \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-rpath \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(erasure_codelibdir)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_test_jerasure_generic_la_rpath =
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_jerasure_neon_la_DEPENDENCIES =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__DEPENDENCIES_1) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__DEPENDENCIES_3)
@@ -2413,9 +2406,7 @@ libec_test_jerasure_neon_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
 	$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
 	$(libec_test_jerasure_neon_la_CXXFLAGS) $(CXXFLAGS) \
 	$(libec_test_jerasure_neon_la_LDFLAGS) $(LDFLAGS) -o $@
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_test_jerasure_neon_la_rpath =  \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-rpath \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(erasure_codelibdir)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_test_jerasure_neon_la_rpath =
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_jerasure_sse3_la_DEPENDENCIES =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__DEPENDENCIES_1) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__DEPENDENCIES_3)
@@ -2428,9 +2419,7 @@ libec_test_jerasure_sse3_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
 	$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
 	$(libec_test_jerasure_sse3_la_CXXFLAGS) $(CXXFLAGS) \
 	$(libec_test_jerasure_sse3_la_LDFLAGS) $(LDFLAGS) -o $@
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_test_jerasure_sse3_la_rpath =  \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-rpath \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(erasure_codelibdir)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_test_jerasure_sse3_la_rpath =
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_jerasure_sse4_la_DEPENDENCIES =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__DEPENDENCIES_1) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__DEPENDENCIES_3)
@@ -2443,9 +2432,7 @@ libec_test_jerasure_sse4_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
 	$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
 	$(libec_test_jerasure_sse4_la_CXXFLAGS) $(CXXFLAGS) \
 	$(libec_test_jerasure_sse4_la_LDFLAGS) $(LDFLAGS) -o $@
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_test_jerasure_sse4_la_rpath =  \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-rpath \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(erasure_codelibdir)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_test_jerasure_sse4_la_rpath =
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_shec_generic_la_DEPENDENCIES =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__DEPENDENCIES_1) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__DEPENDENCIES_3)
@@ -2458,9 +2445,7 @@ libec_test_shec_generic_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
 	$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
 	$(libec_test_shec_generic_la_CXXFLAGS) $(CXXFLAGS) \
 	$(libec_test_shec_generic_la_LDFLAGS) $(LDFLAGS) -o $@
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_test_shec_generic_la_rpath =  \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-rpath \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(erasure_codelibdir)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_test_shec_generic_la_rpath =
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_shec_neon_la_DEPENDENCIES =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__DEPENDENCIES_1) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__DEPENDENCIES_3)
@@ -2473,9 +2458,7 @@ libec_test_shec_neon_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
 	$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
 	$(libec_test_shec_neon_la_CXXFLAGS) $(CXXFLAGS) \
 	$(libec_test_shec_neon_la_LDFLAGS) $(LDFLAGS) -o $@
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_test_shec_neon_la_rpath =  \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-rpath \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(erasure_codelibdir)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_test_shec_neon_la_rpath =
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_shec_sse3_la_DEPENDENCIES =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__DEPENDENCIES_1) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__DEPENDENCIES_3)
@@ -2488,9 +2471,7 @@ libec_test_shec_sse3_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
 	$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
 	$(libec_test_shec_sse3_la_CXXFLAGS) $(CXXFLAGS) \
 	$(libec_test_shec_sse3_la_LDFLAGS) $(LDFLAGS) -o $@
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_test_shec_sse3_la_rpath =  \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-rpath \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(erasure_codelibdir)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_test_shec_sse3_la_rpath =
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_shec_sse4_la_DEPENDENCIES =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__DEPENDENCIES_1) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__DEPENDENCIES_3)
@@ -2503,13 +2484,10 @@ libec_test_shec_sse4_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
 	$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
 	$(libec_test_shec_sse4_la_CXXFLAGS) $(CXXFLAGS) \
 	$(libec_test_shec_sse4_la_LDFLAGS) $(LDFLAGS) -o $@
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_test_shec_sse4_la_rpath =  \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-rpath \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(erasure_codelibdir)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at am_libec_test_shec_sse4_la_rpath =
 am_liberasure_code_la_OBJECTS = erasure-code/ErasureCodePlugin.lo
 liberasure_code_la_OBJECTS = $(am_liberasure_code_la_OBJECTS)
-libglobal_la_DEPENDENCIES = $(am__DEPENDENCIES_4) \
-	$(am__DEPENDENCIES_1)
+libglobal_la_DEPENDENCIES = $(am__DEPENDENCIES_4)
 am_libglobal_la_OBJECTS = global/global_context.lo \
 	global/global_init.lo global/pidfile.lo \
 	global/signal_handler.lo common/TrackedOp.lo
@@ -3018,9 +2996,12 @@ am__librbd_mirror_internal_la_SOURCES_DIST =  \
 	tools/rbd_mirror/ImageReplayer.cc \
 	tools/rbd_mirror/ImageSync.cc tools/rbd_mirror/Mirror.cc \
 	tools/rbd_mirror/PoolWatcher.cc tools/rbd_mirror/Replayer.cc \
-	tools/rbd_mirror/Threads.cc tools/rbd_mirror/types.cc \
+	tools/rbd_mirror/ImageDeleter.cc tools/rbd_mirror/Threads.cc \
+	tools/rbd_mirror/types.cc \
 	tools/rbd_mirror/image_replayer/BootstrapRequest.cc \
 	tools/rbd_mirror/image_replayer/CloseImageRequest.cc \
+	tools/rbd_mirror/image_replayer/CreateImageRequest.cc \
+	tools/rbd_mirror/image_replayer/OpenImageRequest.cc \
 	tools/rbd_mirror/image_replayer/OpenLocalImageRequest.cc \
 	tools/rbd_mirror/image_replayer/ReplayStatusFormatter.cc \
 	tools/rbd_mirror/image_sync/ImageCopyRequest.cc \
@@ -3035,10 +3016,13 @@ am__librbd_mirror_internal_la_SOURCES_DIST =  \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/Mirror.lo \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/PoolWatcher.lo \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/Replayer.lo \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/ImageDeleter.lo \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/Threads.lo \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/types.lo \
 @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 \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_replayer/CreateImageRequest.lo \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_replayer/OpenImageRequest.lo \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_replayer/OpenLocalImageRequest.lo \
 @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 \
@@ -3055,11 +3039,13 @@ am__librbd_mirror_test_la_SOURCES_DIST =  \
 	test/rbd_mirror/test_ClusterWatcher.cc \
 	test/rbd_mirror/test_PoolWatcher.cc \
 	test/rbd_mirror/test_ImageReplayer.cc \
+	test/rbd_mirror/test_ImageDeleter.cc \
 	test/rbd_mirror/test_ImageSync.cc \
 	test/rbd_mirror/test_fixture.cc
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am_librbd_mirror_test_la_OBJECTS = test/rbd_mirror/librbd_mirror_test_la-test_ClusterWatcher.lo \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/librbd_mirror_test_la-test_PoolWatcher.lo \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/librbd_mirror_test_la-test_ImageReplayer.lo \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/librbd_mirror_test_la-test_ImageDeleter.lo \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/librbd_mirror_test_la-test_ImageSync.lo \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/librbd_mirror_test_la-test_fixture.lo
 librbd_mirror_test_la_OBJECTS = $(am_librbd_mirror_test_la_OBJECTS)
@@ -3334,6 +3320,7 @@ libsystest_la_OBJECTS = $(am_libsystest_la_OBJECTS)
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	ceph_test_librbd$(EXEEXT) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	ceph_test_librbd_api$(EXEEXT) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	ceph_test_rbd_mirror$(EXEEXT) \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	ceph_test_rbd_mirror_random_write$(EXEEXT) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	ceph_test_rbd_mirror_image_replay$(EXEEXT)
 @ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am__EXEEXT_10 = ceph_test_librbd_fsx$(EXEEXT)
 @ENABLE_CLIENT_TRUE@@WITH_RADOSSTRIPER_TRUE@@WITH_RADOS_TRUE at am__EXEEXT_11 = ceph_test_rados_striper_api_io$(EXEEXT) \
@@ -3631,8 +3618,8 @@ am__DEPENDENCIES_17 = librgw.la $(am__DEPENDENCIES_1)
 @ENABLE_CLIENT_TRUE@	libcls_user_client.la \
 @ENABLE_CLIENT_TRUE@	libcls_numops_client.la \
 @ENABLE_CLIENT_TRUE@	libcls_journal_client.la
-am__DEPENDENCIES_19 = $(am__append_48) $(am__append_137) \
-	$(am__DEPENDENCIES_18) $(am__append_159)
+am__DEPENDENCIES_19 = $(am__append_48) $(am__append_136) \
+	$(am__DEPENDENCIES_18) $(am__append_158)
 @ENABLE_CLIENT_TRUE at ceph_dencoder_DEPENDENCIES =  \
 @ENABLE_CLIENT_TRUE@	$(am__DEPENDENCIES_17) $(LIBRADOS) \
 @ENABLE_CLIENT_TRUE@	$(LIBRBD_TYPES) $(LIBOSD_TYPES) \
@@ -4813,6 +4800,19 @@ ceph_test_rbd_mirror_image_replay_OBJECTS =  \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	libcls_lock_client.la \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	libcls_journal_client.la \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	$(am__DEPENDENCIES_10)
+am__ceph_test_rbd_mirror_random_write_SOURCES_DIST =  \
+	test/rbd_mirror/random_write.cc
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am_ceph_test_rbd_mirror_random_write_OBJECTS = test/rbd_mirror/ceph_test_rbd_mirror_random_write-random_write.$(OBJEXT)
+ceph_test_rbd_mirror_random_write_OBJECTS =  \
+	$(am_ceph_test_rbd_mirror_random_write_OBJECTS)
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at ceph_test_rbd_mirror_random_write_DEPENDENCIES =  \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	$(LIBRBD) \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	$(LIBRADOS) \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	$(am__DEPENDENCIES_10)
+ceph_test_rbd_mirror_random_write_LINK = $(LIBTOOL) $(AM_V_lt) \
+	--tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \
+	$(CXXLD) $(ceph_test_rbd_mirror_random_write_CXXFLAGS) \
+	$(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
 am_ceph_test_rewrite_latency_OBJECTS =  \
 	test/test_rewrite_latency.$(OBJEXT)
 ceph_test_rewrite_latency_OBJECTS =  \
@@ -5180,7 +5180,7 @@ rbd_OBJECTS = $(am_rbd_OBJECTS)
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	$(am__DEPENDENCIES_10) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	$(am__DEPENDENCIES_1) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	$(am__DEPENDENCIES_1) \
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	$(am__append_269)
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	$(am__append_268)
 am__rbd_fuse_SOURCES_DIST = rbd_fuse/rbd-fuse.cc
 @ENABLE_CLIENT_TRUE@@WITH_FUSE_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at am_rbd_fuse_OBJECTS = rbd_fuse/rbd_fuse-rbd-fuse.$(OBJEXT)
 rbd_fuse_OBJECTS = $(am_rbd_fuse_OBJECTS)
@@ -6618,6 +6618,7 @@ am__unittest_rbd_mirror_SOURCES_DIST = test/rbd_mirror/test_main.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_replayer/test_mock_CreateImageRequest.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 \
@@ -6629,6 +6630,7 @@ am__unittest_rbd_mirror_SOURCES_DIST = test/rbd_mirror/test_main.cc \
 @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) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/image_replayer/unittest_rbd_mirror-test_mock_BootstrapRequest.$(OBJEXT) \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/image_replayer/unittest_rbd_mirror-test_mock_CreateImageRequest.$(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) \
@@ -7167,6 +7169,7 @@ SOURCES = $(libkv_a_SOURCES) $(libmon_a_SOURCES) $(libos_a_SOURCES) \
 	$(ceph_test_rados_watch_notify_SOURCES) \
 	$(ceph_test_rbd_mirror_SOURCES) \
 	$(ceph_test_rbd_mirror_image_replay_SOURCES) \
+	$(ceph_test_rbd_mirror_random_write_SOURCES) \
 	$(ceph_test_rewrite_latency_SOURCES) \
 	$(ceph_test_rgw_manifest_SOURCES) $(ceph_test_rgw_obj_SOURCES) \
 	$(ceph_test_rgw_period_history_SOURCES) \
@@ -7458,6 +7461,7 @@ DIST_SOURCES = $(am__libkv_a_SOURCES_DIST) \
 	$(am__ceph_test_rados_watch_notify_SOURCES_DIST) \
 	$(am__ceph_test_rbd_mirror_SOURCES_DIST) \
 	$(am__ceph_test_rbd_mirror_image_replay_SOURCES_DIST) \
+	$(am__ceph_test_rbd_mirror_random_write_SOURCES_DIST) \
 	$(ceph_test_rewrite_latency_SOURCES) \
 	$(am__ceph_test_rgw_manifest_SOURCES_DIST) \
 	$(am__ceph_test_rgw_obj_SOURCES_DIST) \
@@ -8060,15 +8064,20 @@ am__noinst_HEADERS_DIST = arch/intel.h arch/arm.h arch/probe.h \
 	test/unit.h test/journal/RadosTestFixture.h \
 	tools/rbd/ArgumentTypes.h tools/rbd/IndentStream.h \
 	tools/rbd/OptionPrinter.h tools/rbd/Shell.h tools/rbd/Utils.h \
+	tools/rbd_mirror/BaseRequest.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/ProgressContext.h tools/rbd_mirror/Replayer.h \
-	tools/rbd_mirror/Threads.h tools/rbd_mirror/types.h \
+	tools/rbd_mirror/ImageDeleter.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/CreateImageRequest.h \
+	tools/rbd_mirror/image_replayer/OpenImageRequest.h \
 	tools/rbd_mirror/image_replayer/OpenLocalImageRequest.h \
 	tools/rbd_mirror/image_replayer/ReplayStatusFormatter.h \
+	tools/rbd_mirror/image_replayer/Utils.h \
 	tools/rbd_mirror/image_sync/ImageCopyRequest.h \
 	tools/rbd_mirror/image_sync/ObjectCopyRequest.h \
 	tools/rbd_mirror/image_sync/SnapshotCopyRequest.h \
@@ -8337,8 +8346,8 @@ ACLOCAL = @ACLOCAL@
 AMTAR = @AMTAR@
 AM_CXXFLAGS = @AM_CXXFLAGS@ $(AM_COMMON_CFLAGS) -ftemplate-depth-1024 \
 	-Wnon-virtual-dtor -Wno-invalid-offsetof $(am__append_7) \
-	$(am__append_10) $(am__append_13) $(am__append_100) \
-	$(am__append_103)
+	$(am__append_10) $(am__append_13) $(am__append_99) \
+	$(am__append_102)
 AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
 AR = @AR@
 ARM_CRC_FLAGS = @ARM_CRC_FLAGS@
@@ -8542,7 +8551,7 @@ top_srcdir = @top_srcdir@
 AUTOMAKE_OPTIONS = gnu subdir-objects
 SUBDIRS = ocf java
 DIST_SUBDIRS = gmock ocf java
-BUILT_SOURCES = $(am__append_280) $(am__append_310)
+BUILT_SOURCES = $(am__append_279) $(am__append_309)
 
 # extra bits
 EXTRA_DIST = $(am__append_31) ceph-detect-init/AUTHORS.rst \
@@ -9234,7 +9243,7 @@ EXTRA_DIST = $(am__append_31) ceph-detect-init/AUTHORS.rst \
 	spdk/include/spdk/queue_extras.h spdk/include/spdk/file.h \
 	spdk/include/spdk/assert.h spdk/include/spdk/barrier.h \
 	spdk/include/spdk/mmio.h tracing/tracing-common.h \
-	$(am__append_281) $(am__append_285) $(am__append_289) \
+	$(am__append_280) $(am__append_284) $(am__append_288) \
 	$(srcdir)/$(shell_scripts:%=%.in) $(srcdir)/vstart.sh \
 	$(srcdir)/stop.sh ceph-run $(srcdir)/ceph-osd-prestart.sh \
 	$(srcdir)/ceph_common.sh $(srcdir)/init-radosgw \
@@ -9398,7 +9407,7 @@ noinst_HEADERS = arch/intel.h arch/arm.h arch/probe.h \
 	common/TracepointProvider.h common/event_socket.h \
 	common/PluginRegistry.h common/scrub_types.h \
 	common/ceph_time.h common/ceph_timer.h common/align.h \
-	common/mutex_debug.h common/shunique_lock.h $(am__append_116) \
+	common/mutex_debug.h common/shunique_lock.h $(am__append_115) \
 	common/secret.h msg/Connection.h msg/Dispatcher.h \
 	msg/Message.h msg/Messenger.h msg/SimplePolicyMessenger.h \
 	msg/msg_types.h msg/simple/Accepter.h \
@@ -9407,7 +9416,7 @@ noinst_HEADERS = arch/intel.h arch/arm.h arch/probe.h \
 	msg/async/AsyncConnection.h msg/async/AsyncMessenger.h \
 	msg/async/Event.h msg/async/EventEpoll.h \
 	msg/async/EventSelect.h msg/async/net_handler.h \
-	$(am__append_125) messages/MAuth.h messages/MAuthReply.h \
+	$(am__append_124) messages/MAuth.h messages/MAuthReply.h \
 	messages/MCacheExpire.h messages/MClientCaps.h \
 	messages/MClientCapRelease.h messages/MClientLease.h \
 	messages/MClientReconnect.h messages/MClientReply.h \
@@ -9500,12 +9509,12 @@ noinst_HEADERS = arch/intel.h arch/arm.h arch/probe.h \
 	include/util.h include/stat.h include/on_exit.h \
 	include/memory.h include/rados/memory.h \
 	include/unordered_set.h include/unordered_map.h \
-	include/timegm.h include/event_type.h $(am__append_132) \
-	$(am__append_135) $(am__append_136) $(am__append_141) \
-	$(am__append_151) $(am__append_153) $(am__append_156) \
-	$(am__append_157) $(am__append_163) $(am__append_199) \
-	$(am__append_205) $(am__append_217) $(am__append_225) \
-	$(am__append_231) $(am__append_243) test/bench/backend.h \
+	include/timegm.h include/event_type.h $(am__append_131) \
+	$(am__append_134) $(am__append_135) $(am__append_140) \
+	$(am__append_150) $(am__append_152) $(am__append_155) \
+	$(am__append_156) $(am__append_162) $(am__append_198) \
+	$(am__append_204) $(am__append_216) $(am__append_224) \
+	$(am__append_230) $(am__append_242) 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 test/bench/rbd_backend.h \
@@ -9528,7 +9537,7 @@ noinst_HEADERS = arch/intel.h arch/arm.h arch/probe.h \
 	test/system/st_rados_list_objects.h \
 	test/system/st_rados_notify.h test/system/st_rados_watch.h \
 	test/system/systest_runnable.h test/system/systest_settings.h \
-	test/unit.h test/journal/RadosTestFixture.h $(am__append_268) \
+	test/unit.h test/journal/RadosTestFixture.h $(am__append_267) \
 	tools/cephfs/JournalTool.h tools/cephfs/JournalScanner.h \
 	tools/cephfs/JournalFilter.h tools/cephfs/EventOutput.h \
 	tools/cephfs/Resetter.h tools/cephfs/Dumper.h \
@@ -9540,42 +9549,42 @@ noinst_HEADERS = arch/intel.h arch/arm.h arch/probe.h \
 	bash_completion/ceph bash_completion/rados bash_completion/rbd \
 	bash_completion/radosgw-admin mount/canonicalize.c \
 	mount/mtab.c objclass/objclass.h
-bin_SCRIPTS = $(am__append_30) $(am__append_298) $(am__append_307) \
-	$(am__append_315)
+bin_SCRIPTS = $(am__append_30) $(am__append_297) $(am__append_306) \
+	$(am__append_314)
 sbin_SCRIPTS = 
-su_sbin_SCRIPTS = $(am__append_312)
+su_sbin_SCRIPTS = $(am__append_311)
 dist_bin_SCRIPTS = 
-lib_LTLIBRARIES = $(am__append_131) $(am__append_134) \
-	$(am__append_140) $(am__append_146) $(am__append_279) \
-	$(am__append_305) $(am__append_306)
+lib_LTLIBRARIES = $(am__append_130) $(am__append_133) \
+	$(am__append_139) $(am__append_145) $(am__append_278) \
+	$(am__append_304) $(am__append_305)
 noinst_LTLIBRARIES = libarch.la libauth.la libcrush.la libmon_types.la \
 	$(am__append_49) libosd_types.la $(am__append_87) \
 	liberasure_code.la libcompressor.la libosdc.la \
 	$(am__append_92) $(am__append_94) libglobal.la \
 	libjson_spirit.la liblog.la libperfglue.la \
-	libcommon_internal.la libcommon_crc.la $(am__append_114) \
-	libcommon.la $(am__append_117) libmsg.la $(am__append_127) \
-	librbd_types.la $(am__append_138) $(am__append_148) \
-	$(am__append_152) $(am__append_158) $(am__append_218) \
-	$(am__append_228) $(am__append_233) $(am__append_261) \
-	$(am__append_272) $(am__append_299)
+	libcommon_internal.la libcommon_crc.la $(am__append_113) \
+	libcommon.la $(am__append_116) libmsg.la $(am__append_126) \
+	librbd_types.la $(am__append_137) $(am__append_147) \
+	$(am__append_151) $(am__append_157) $(am__append_217) \
+	$(am__append_227) $(am__append_232) $(am__append_260) \
+	$(am__append_271) $(am__append_298)
 noinst_LIBRARIES = $(am__append_32) $(am__append_45) libos_types.a \
 	$(am__append_57) $(am__append_61) $(am__append_67)
-radoslib_LTLIBRARIES = $(am__append_154) $(am__append_155)
+radoslib_LTLIBRARIES = $(am__append_153) $(am__append_154)
 
 # like bin_PROGRAMS, but these targets are only built for debug builds
-bin_DEBUGPROGRAMS = $(am__append_96) $(am__append_150) \
-	$(am__append_165) $(am__append_219) $(am__append_220) \
-	$(am__append_221) $(am__append_222) $(am__append_224) \
-	$(am__append_226) $(am__append_232) $(am__append_234) \
-	$(am__append_235) $(am__append_238) $(am__append_240) \
-	$(am__append_241) $(am__append_242) $(am__append_244) \
-	$(am__append_246) $(am__append_248) $(am__append_249) \
-	$(am__append_255) ceph_test_timers ceph_test_signal_handlers \
-	ceph_test_rewrite_latency ceph_test_crypto $(am__append_260) \
+bin_DEBUGPROGRAMS = $(am__append_96) $(am__append_149) \
+	$(am__append_164) $(am__append_218) $(am__append_219) \
+	$(am__append_220) $(am__append_221) $(am__append_223) \
+	$(am__append_225) $(am__append_231) $(am__append_233) \
+	$(am__append_234) $(am__append_237) $(am__append_239) \
+	$(am__append_240) $(am__append_241) $(am__append_243) \
+	$(am__append_245) $(am__append_247) $(am__append_248) \
+	$(am__append_254) ceph_test_timers ceph_test_signal_handlers \
+	ceph_test_rewrite_latency ceph_test_crypto $(am__append_259) \
 	ceph_bench_log ceph_test_objectcacher_stress \
-	ceph_test_cfuse_cache_invalidate $(am__append_264) \
-	$(am__append_265) $(am__append_274) $(am__append_275) \
+	ceph_test_cfuse_cache_invalidate $(am__append_263) \
+	$(am__append_264) $(am__append_273) $(am__append_274) \
 	ceph_psim
 
 # like sbin_SCRIPTS but can be used to install to e.g. /usr/sbin
@@ -9585,12 +9594,12 @@ ceph_sbindir = $(sbindir)
 su_sbindir = /sbin
 
 # C/C++ tests to build and executed will be appended to this
-check_TESTPROGRAMS = $(am__append_181) $(am__append_185) \
-	$(am__append_188) $(am__append_223) $(am__append_227) \
-	$(am__append_236) $(am__append_245) $(am__append_247) \
-	$(am__append_251) $(am__append_252) $(am__append_256) \
-	$(am__append_257) $(am__append_258) $(am__append_259) \
-	unittest_addrs $(am__append_263) unittest_bloom_filter \
+check_TESTPROGRAMS = $(am__append_180) $(am__append_184) \
+	$(am__append_187) $(am__append_222) $(am__append_226) \
+	$(am__append_235) $(am__append_244) $(am__append_246) \
+	$(am__append_250) $(am__append_251) $(am__append_255) \
+	$(am__append_256) $(am__append_257) $(am__append_258) \
+	unittest_addrs $(am__append_262) unittest_bloom_filter \
 	unittest_histogram unittest_prioritized_queue \
 	unittest_weighted_priority_queue unittest_str_map \
 	unittest_mutex_debug unittest_shunique_lock \
@@ -9652,7 +9661,7 @@ check_TESTPROGRAMS = $(am__append_181) $(am__append_185) \
 # GNU Library Public License for more details.
 #
 check_SCRIPTS = ceph-detect-init/run-tox.sh ceph-disk/run-tox.sh \
-	$(am__append_162) $(am__append_230) \
+	$(am__append_161) $(am__append_229) \
 	test/ceph_objectstore_tool.py test/test-ceph-helpers.sh \
 	test/cephtool-test-osd.sh test/cephtool-test-mon.sh \
 	test/cephtool-test-mds.sh test/cephtool-test-rados.sh \
@@ -9708,7 +9717,7 @@ AM_COMMON_CFLAGS = \
 	-fsigned-char
 
 AM_CFLAGS = $(AM_COMMON_CFLAGS) $(am__append_6) $(am__append_12) \
-	$(am__append_99) $(am__append_102)
+	$(am__append_98) $(am__append_101)
 AM_CPPFLAGS = $(AM_COMMON_CPPFLAGS)
 
 # note: this is position dependant, it affects the -l options that
@@ -9773,29 +9782,29 @@ CEPH_GLOBAL = $(LIBGLOBAL) $(LIBCOMMON) $(PTHREAD_LIBS) -lm $(CRYPTO_LIBS) $(EXT
 
 # important; libmsg before libauth!
 LIBCOMMON_DEPS = libcommon_internal.la libcommon_crc.la \
-	$(am__append_113) $(LIBERASURE_CODE) $(LIBCOMPRESSOR) \
+	$(am__append_112) $(LIBERASURE_CODE) $(LIBCOMPRESSOR) \
 	$(LIBMSG) $(LIBAUTH) $(LIBCRUSH) $(LIBJSON_SPIRIT) $(LIBLOG) \
-	$(LIBARCH) $(BOOST_RANDOM_LIBS) -luuid $(am__append_115)
-LIBRADOS_DEPS = $(am__append_128)
-LIBRGW_DEPS = $(am__append_145)
-LIBCIVETWEB_DEPS = $(am__append_147)
+	$(LIBARCH) $(BOOST_RANDOM_LIBS) -luuid $(am__append_114)
+LIBRADOS_DEPS = $(am__append_127)
+LIBRGW_DEPS = $(am__append_144)
+LIBCIVETWEB_DEPS = $(am__append_146)
 
 # This is used by the dencoder test
 
 # Do not use TCMALLOC with dencoder
 DENCODER_SOURCES = $(am__append_47) perfglue/disabled_heap_profiler.cc \
-	perfglue/disabled_stubs.cc $(am__append_142)
-DENCODER_DEPS = $(am__append_48) $(am__append_137) $(am__append_143) \
-	$(am__append_159)
+	perfglue/disabled_stubs.cc $(am__append_141)
+DENCODER_DEPS = $(am__append_48) $(am__append_136) $(am__append_142) \
+	$(am__append_158)
 radoslibdir = $(libdir)/rados-classes
-LOCAL_ALL = ceph-detect-init-all ceph-disk-all $(am__append_282) \
-	$(am__append_286) $(am__append_290)
-LOCAL_CLEAN = ceph-detect-init-clean ceph-disk-clean $(am__append_283) \
-	$(am__append_287) $(am__append_291) base-clean-local
+LOCAL_ALL = ceph-detect-init-all ceph-disk-all $(am__append_281) \
+	$(am__append_285) $(am__append_289)
+LOCAL_CLEAN = ceph-detect-init-clean ceph-disk-clean $(am__append_282) \
+	$(am__append_286) $(am__append_290) base-clean-local
 LOCAL_INSTALLDATA = ceph-detect-init-install-data \
 	ceph-disk-install-data base-install-data-local
-LOCAL_INSTALLEXEC = $(am__append_284) $(am__append_288) \
-	$(am__append_292)
+LOCAL_INSTALLEXEC = $(am__append_283) $(am__append_287) \
+	$(am__append_291)
 libarch_la_SOURCES = \
 	arch/intel.c \
 	arch/arm.c \
@@ -9964,7 +9973,8 @@ erasure_codelib_LTLIBRARIES = libec_jerasure_generic.la \
 	$(am__append_71) $(am__append_73) $(am__append_75) \
 	libec_jerasure.la libec_lrc.la libec_shec_generic.la \
 	$(am__append_80) $(am__append_82) $(am__append_84) \
-	libec_shec.la $(am__append_89) $(am__append_170)
+	libec_shec.la $(am__append_89)
+check_LTLIBRARIES = $(am__append_169)
 jerasure_sources = \
   erasure-code/ErasureCode.cc \
   erasure-code/jerasure/jerasure/src/cauchy.c \
@@ -10271,7 +10281,7 @@ liberasure_code_la_DEPENDENCIES = $(erasure_codelib_LTLIBRARIES)
 @LINUX_TRUE at liberasure_code_la_LIBADD = -ldl
 compressorlibdir = $(pkglibdir)/compressor
 compressorlib_LTLIBRARIES = libceph_zlib.la libceph_snappy.la \
-	$(am__append_206)
+	$(am__append_205)
 zlib_sources = \
   compressor/Compressor.cc \
   compressor/zlib/CompressionPluginZlib.cc \
@@ -10332,7 +10342,7 @@ libglobal_la_SOURCES = \
 	global/signal_handler.cc \
 	common/TrackedOp.cc
 
-libglobal_la_LIBADD = $(LIBCOMMON) $(am__append_97)
+libglobal_la_LIBADD = $(LIBCOMMON)
 libjson_spirit_la_SOURCES = \
 	json_spirit/json_spirit_reader.cpp \
 	json_spirit/json_spirit_writer.cpp
@@ -10342,8 +10352,8 @@ liblog_la_SOURCES = \
 	log/Log.cc \
 	log/SubsystemMap.cc
 
-libperfglue_la_SOURCES = $(am__append_98) $(am__append_101) \
-	$(am__append_104) $(am__append_105) $(am__append_106)
+libperfglue_la_SOURCES = $(am__append_97) $(am__append_100) \
+	$(am__append_103) $(am__append_104) $(am__append_105)
 @WITH_TCMALLOC_FALSE@@WITH_TCMALLOC_MINIMAL_TRUE at libperfglue_la_LIBADD = -ltcmalloc_minimal
 @WITH_TCMALLOC_TRUE at libperfglue_la_LIBADD = -ltcmalloc
 
@@ -10377,9 +10387,9 @@ libcommon_internal_la_SOURCES = ceph_ver.c common/DecayCounter.cc \
 	common/bloom_filter.cc common/module.c common/Readahead.cc \
 	common/Cycles.cc common/ContextCompletion.cc \
 	common/TracepointProvider.cc common/PluginRegistry.cc \
-	common/scrub_types.cc common/blkdev.cc $(am__append_107) \
-	$(am__append_108) $(am__append_109) $(am__append_110) \
-	$(am__append_111) mon/MonCap.cc mon/MonClient.cc mon/MonMap.cc \
+	common/scrub_types.cc common/blkdev.cc $(am__append_106) \
+	$(am__append_107) $(am__append_108) $(am__append_109) \
+	$(am__append_110) mon/MonCap.cc mon/MonClient.cc mon/MonMap.cc \
 	osd/OSDMap.cc osd/osd_types.cc osd/ECMsgTypes.cc osd/HitSet.cc \
 	mds/MDSMap.cc mds/FSMap.cc mds/inode_backtrace.cc \
 	mds/mdstypes.cc mds/flock.cc
@@ -10387,7 +10397,7 @@ libcommon_internal_la_SOURCES = ceph_ver.c common/DecayCounter.cc \
 # inject crc in common
 libcommon_crc_la_SOURCES = common/sctp_crc32.c common/crc32c.cc \
 	common/crc32c_intel_baseline.c common/crc32c_intel_fast.c \
-	$(am__append_112)
+	$(am__append_111)
 @WITH_GOOD_YASM_ELF64_TRUE at libcommon_crc_la_LIBTOOLFLAGS = --tag=CC
 @HAVE_ARMV8_CRC_TRUE at libcommon_crc_aarch64_la_SOURCES = common/crc32c_aarch64.c
 @HAVE_ARMV8_CRC_TRUE at libcommon_crc_aarch64_la_CFLAGS = $(AM_CFLAGS) $(ARM_CRC_FLAGS)
@@ -10401,9 +10411,9 @@ libmsg_la_SOURCES = msg/Message.cc msg/Messenger.cc msg/msg_types.cc \
 	msg/simple/SimpleMessenger.cc msg/async/AsyncConnection.cc \
 	msg/async/AsyncMessenger.cc msg/async/Event.cc \
 	msg/async/net_handler.cc msg/async/EventSelect.cc \
-	$(am__append_118) $(am__append_119) $(am__append_120) \
-	$(am__append_121) $(am__append_122) $(am__append_123) \
-	$(am__append_124)
+	$(am__append_117) $(am__append_118) $(am__append_119) \
+	$(am__append_120) $(am__append_121) $(am__append_122) \
+	$(am__append_123)
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at rados_includedir = $(includedir)/rados
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at rados_include_DATA = $(srcdir)/include/rados/librados.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	$(srcdir)/include/rados/rados_types.h \
@@ -10414,7 +10424,7 @@ libmsg_la_SOURCES = msg/Message.cc msg/Messenger.cc msg/msg_types.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	$(srcdir)/include/page.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	$(srcdir)/include/crc32c.h \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	$(srcdir)/include/memory.h \
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	$(am__append_126)
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	$(am__append_125)
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at librbd_includedir = $(includedir)/rbd
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at librbd_include_DATA = \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	$(srcdir)/include/rbd/features.h \
@@ -10446,12 +10456,12 @@ libmsg_la_SOURCES = msg/Message.cc msg/Messenger.cc msg/msg_types.cc \
 # We need this to avoid basename conflicts with the librados build tests in test/Makefile.am
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at librados_la_CXXFLAGS =  \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	${AM_CXXFLAGS} \
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	$(am__append_129)
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	$(am__append_128)
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at librados_la_LIBADD = $(LIBRADOS_DEPS) $(PTHREAD_LIBS) $(CRYPTO_LIBS) $(EXTRALIBS)
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at librados_la_LDFLAGS =  \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	${AM_LDFLAGS} \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	-version-info 2:0:0 \
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	$(am__append_130)
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	$(am__append_129)
 @ENABLE_CLIENT_TRUE@@WITH_RADOSSTRIPER_TRUE@@WITH_RADOS_TRUE at libradosstriper_la_SOURCES = \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSSTRIPER_TRUE@@WITH_RADOS_TRUE@	libradosstriper/libradosstriper.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSSTRIPER_TRUE@@WITH_RADOS_TRUE@	libradosstriper/RadosStriperImpl.cc \
@@ -10465,7 +10475,7 @@ libmsg_la_SOURCES = msg/Message.cc msg/Messenger.cc msg/msg_types.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSSTRIPER_TRUE@@WITH_RADOS_TRUE at libradosstriper_la_LDFLAGS = ${AM_LDFLAGS} \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSSTRIPER_TRUE@@WITH_RADOS_TRUE@	-version-info \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSSTRIPER_TRUE@@WITH_RADOS_TRUE@	1:0:0 \
- at ENABLE_CLIENT_TRUE@@WITH_RADOSSTRIPER_TRUE@@WITH_RADOS_TRUE@	$(am__append_133)
+ at ENABLE_CLIENT_TRUE@@WITH_RADOSSTRIPER_TRUE@@WITH_RADOS_TRUE@	$(am__append_132)
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at libjournal_la_SOURCES = \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	journal/AsyncOpTracker.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	journal/Entry.cc \
@@ -10561,7 +10571,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at librbd_la_LDFLAGS = ${AM_LDFLAGS} \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	-version-info \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	1:0:0 \
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	$(am__append_139)
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	$(am__append_138)
 @ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at librbd_la_CXXFLAGS = -fvisibility=hidden -fvisibility-inlines-hidden
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE at librgw_la_SOURCES = rgw/rgw_acl.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	rgw/rgw_acl_s3.cc \
@@ -10631,7 +10641,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	rgw/rgw_xml.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	rgw/rgw_xml_enc.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	rgw/rgw_website.cc \
- at ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	$(am__append_144)
+ at ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	$(am__append_143)
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE at librgw_la_CXXFLAGS = -Woverloaded-virtual -fPIC -I$(srcdir)/xxHash \
 @ENABLE_CLIENT_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	${AM_CXXFLAGS}
 
@@ -10835,7 +10845,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(LIBOSD) $(LIBCOMMON) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(BOOST_PROGRAM_OPTIONS_LIBS) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(CEPH_GLOBAL) \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_164)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_163)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at ceph_erasure_code_non_regression_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	test/erasure-code/ceph_erasure_code_non_regression.cc
 
@@ -10843,7 +10853,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(LIBOSD) $(LIBCOMMON) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(BOOST_PROGRAM_OPTIONS_LIBS) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(CEPH_GLOBAL) \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_166)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_165)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at ceph_erasure_code_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	test/erasure-code/ceph_erasure_code.cc
 
@@ -10851,7 +10861,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(LIBOSD) $(LIBCOMMON) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(BOOST_PROGRAM_OPTIONS_LIBS) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(CEPH_GLOBAL) \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_168)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_167)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_example_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	erasure-code/ErasureCode.cc \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	test/erasure-code/ErasureCodePluginExample.cc
@@ -10862,7 +10872,8 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_example_la_LDFLAGS =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	${AM_LDFLAGS} -module \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-avoid-version -shared \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_169)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-rpath /nowhere \
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_168)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_missing_entry_point_la_SOURCES = test/erasure-code/ErasureCodePluginMissingEntryPoint.cc
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_missing_entry_point_la_CFLAGS = ${AM_CFLAGS}
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_missing_entry_point_la_CXXFLAGS = ${AM_CXXFLAGS}
@@ -10870,7 +10881,8 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_missing_entry_point_la_LDFLAGS =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	${AM_LDFLAGS} -module \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-avoid-version -shared \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_171)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-rpath /nowhere \
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_170)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_missing_version_la_SOURCES = test/erasure-code/ErasureCodePluginMissingVersion.cc
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_missing_version_la_CFLAGS = ${AM_CFLAGS}
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_missing_version_la_CXXFLAGS = ${AM_CXXFLAGS}
@@ -10878,7 +10890,8 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_missing_version_la_LDFLAGS =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	${AM_LDFLAGS} -module \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-avoid-version -shared \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_172)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-rpath /nowhere \
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_171)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_hangs_la_SOURCES = test/erasure-code/ErasureCodePluginHangs.cc
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_hangs_la_CFLAGS = ${AM_CFLAGS}
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_hangs_la_CXXFLAGS = ${AM_CXXFLAGS}
@@ -10886,7 +10899,8 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_hangs_la_LDFLAGS =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	${AM_LDFLAGS} -module \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-avoid-version -shared \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_173)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-rpath /nowhere \
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_172)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_fail_to_initialize_la_SOURCES = test/erasure-code/ErasureCodePluginFailToInitialize.cc
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_fail_to_initialize_la_CFLAGS = ${AM_CFLAGS}
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_fail_to_initialize_la_CXXFLAGS = ${AM_CXXFLAGS}
@@ -10894,7 +10908,8 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_fail_to_initialize_la_LDFLAGS =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	${AM_LDFLAGS} -module \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-avoid-version -shared \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_174)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-rpath /nowhere \
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_173)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_fail_to_register_la_SOURCES = test/erasure-code/ErasureCodePluginFailToRegister.cc
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_fail_to_register_la_CFLAGS = ${AM_CFLAGS}
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_fail_to_register_la_CXXFLAGS = ${AM_CXXFLAGS}
@@ -10902,7 +10917,8 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_fail_to_register_la_LDFLAGS =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	${AM_LDFLAGS} -module \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-avoid-version -shared \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_175)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-rpath /nowhere \
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_174)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_jerasure_neon_la_SOURCES = test/erasure-code/TestJerasurePluginNEON.cc
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_jerasure_neon_la_CFLAGS = ${AM_CFLAGS}
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_jerasure_neon_la_CXXFLAGS = ${AM_CXXFLAGS}
@@ -10910,7 +10926,8 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_jerasure_neon_la_LDFLAGS =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	${AM_LDFLAGS} -module \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-avoid-version -shared \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_176)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-rpath /nowhere \
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_175)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_jerasure_sse4_la_SOURCES = test/erasure-code/TestJerasurePluginSSE4.cc
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_jerasure_sse4_la_CFLAGS = ${AM_CFLAGS}
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_jerasure_sse4_la_CXXFLAGS = ${AM_CXXFLAGS}
@@ -10918,7 +10935,8 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_jerasure_sse4_la_LDFLAGS =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	${AM_LDFLAGS} -module \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-avoid-version -shared \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_177)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-rpath /nowhere \
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_176)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_jerasure_sse3_la_SOURCES = test/erasure-code/TestJerasurePluginSSE3.cc
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_jerasure_sse3_la_CFLAGS = ${AM_CFLAGS}
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_jerasure_sse3_la_CXXFLAGS = ${AM_CXXFLAGS}
@@ -10926,7 +10944,8 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_jerasure_sse3_la_LDFLAGS =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	${AM_LDFLAGS} -module \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-avoid-version -shared \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_178)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-rpath /nowhere \
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_177)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_jerasure_generic_la_SOURCES = test/erasure-code/TestJerasurePluginGeneric.cc
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_jerasure_generic_la_CFLAGS = ${AM_CFLAGS}
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_jerasure_generic_la_CXXFLAGS = ${AM_CXXFLAGS}
@@ -10934,7 +10953,8 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_jerasure_generic_la_LDFLAGS =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	${AM_LDFLAGS} -module \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-avoid-version -shared \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_179)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-rpath /nowhere \
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_178)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at unittest_erasure_code_plugin_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	erasure-code/ErasureCode.cc \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	test/erasure-code/TestErasureCodePlugin.cc 
@@ -10944,7 +10964,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(LIBOSD) $(LIBCOMMON) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(UNITTEST_LDADD) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(CEPH_GLOBAL) \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_180)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_179)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at unittest_erasure_code_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	erasure-code/ErasureCode.cc \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	test/erasure-code/TestErasureCode.cc
@@ -10967,7 +10987,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(LIBOSD) $(LIBCOMMON) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(UNITTEST_LDADD) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(CEPH_GLOBAL) \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_182)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_181)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at unittest_erasure_code_plugin_jerasure_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	test/erasure-code/TestErasureCodePluginJerasure.cc
 
@@ -10976,7 +10996,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(LIBOSD) $(LIBCOMMON) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(UNITTEST_LDADD) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(CEPH_GLOBAL) \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_183)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_182)
 @ENABLE_SERVER_TRUE@@WITH_BETTER_YASM_ELF64_TRUE@@WITH_OSD_TRUE at unittest_erasure_code_isa_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_BETTER_YASM_ELF64_TRUE@@WITH_OSD_TRUE@	erasure-code/ErasureCode.cc \
 @ENABLE_SERVER_TRUE@@WITH_BETTER_YASM_ELF64_TRUE@@WITH_OSD_TRUE@	test/erasure-code/TestErasureCodeIsa.cc
@@ -10988,7 +11008,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_BETTER_YASM_ELF64_TRUE@@WITH_OSD_TRUE@	$(CEPH_GLOBAL) \
 @ENABLE_SERVER_TRUE@@WITH_BETTER_YASM_ELF64_TRUE@@WITH_OSD_TRUE@	libisa.la \
 @ENABLE_SERVER_TRUE@@WITH_BETTER_YASM_ELF64_TRUE@@WITH_OSD_TRUE@	$(LIBERASURE_CODE) \
- at ENABLE_SERVER_TRUE@@WITH_BETTER_YASM_ELF64_TRUE@@WITH_OSD_TRUE@	$(am__append_184)
+ at ENABLE_SERVER_TRUE@@WITH_BETTER_YASM_ELF64_TRUE@@WITH_OSD_TRUE@	$(am__append_183)
 @ENABLE_SERVER_TRUE@@WITH_BETTER_YASM_ELF64_TRUE@@WITH_OSD_TRUE at unittest_erasure_code_plugin_isa_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_BETTER_YASM_ELF64_TRUE@@WITH_OSD_TRUE@	erasure-code/ErasureCode.cc \
 @ENABLE_SERVER_TRUE@@WITH_BETTER_YASM_ELF64_TRUE@@WITH_OSD_TRUE@	test/erasure-code/TestErasureCodePluginIsa.cc
@@ -10999,7 +11019,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_BETTER_YASM_ELF64_TRUE@@WITH_OSD_TRUE@	$(UNITTEST_LDADD) \
 @ENABLE_SERVER_TRUE@@WITH_BETTER_YASM_ELF64_TRUE@@WITH_OSD_TRUE@	$(CEPH_GLOBAL) \
 @ENABLE_SERVER_TRUE@@WITH_BETTER_YASM_ELF64_TRUE@@WITH_OSD_TRUE@	$(LIBERASURE_CODE) \
- at ENABLE_SERVER_TRUE@@WITH_BETTER_YASM_ELF64_TRUE@@WITH_OSD_TRUE@	$(am__append_186)
+ at ENABLE_SERVER_TRUE@@WITH_BETTER_YASM_ELF64_TRUE@@WITH_OSD_TRUE@	$(am__append_185)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at unittest_erasure_code_lrc_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	test/erasure-code/TestErasureCodeLrc.cc \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	${lrc_sources}
@@ -11009,7 +11029,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(LIBOSD) $(LIBCOMMON) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(UNITTEST_LDADD) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(CEPH_GLOBAL) \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_187)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_186)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at unittest_erasure_code_plugin_lrc_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	test/erasure-code/TestErasureCodePluginLrc.cc
 
@@ -11018,7 +11038,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(LIBOSD) $(LIBCOMMON) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(UNITTEST_LDADD) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(CEPH_GLOBAL) \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_189)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_188)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at unittest_erasure_code_shec_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	test/erasure-code/TestErasureCodeShec.cc \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	${shec_sources}
@@ -11039,7 +11059,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(LIBOSD) $(LIBCOMMON) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(UNITTEST_LDADD) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(CEPH_GLOBAL) \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_190)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_189)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at unittest_erasure_code_shec_all_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	test/erasure-code/TestErasureCodeShec_all.cc \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	${shec_sources}
@@ -11060,7 +11080,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(LIBOSD) $(LIBCOMMON) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(UNITTEST_LDADD) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(CEPH_GLOBAL) \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_191)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_190)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at unittest_erasure_code_shec_thread_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	test/erasure-code/TestErasureCodeShec_thread.cc \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	${shec_sources}
@@ -11081,7 +11101,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(LIBOSD) $(LIBCOMMON) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(UNITTEST_LDADD) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(CEPH_GLOBAL) \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_192)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_191)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at unittest_erasure_code_shec_arguments_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	test/erasure-code/TestErasureCodeShec_arguments.cc \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	${shec_sources}
@@ -11102,7 +11122,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(LIBOSD) $(LIBCOMMON) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(UNITTEST_LDADD) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(CEPH_GLOBAL) \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_193)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_192)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at unittest_erasure_code_plugin_shec_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@        test/erasure-code/TestErasureCodePluginShec.cc
 
@@ -11111,7 +11131,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(LIBOSD) $(LIBCOMMON) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(UNITTEST_LDADD) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(CEPH_GLOBAL) \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_194)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_193)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_shec_neon_la_SOURCES = test/erasure-code/TestShecPluginNEON.cc
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_shec_neon_la_CFLAGS = ${AM_CFLAGS}
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_shec_neon_la_CXXFLAGS = ${AM_CXXFLAGS}
@@ -11119,7 +11139,8 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_shec_neon_la_LDFLAGS =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	${AM_LDFLAGS} -module \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-avoid-version -shared \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_195)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-rpath /nowhere \
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_194)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_shec_sse4_la_SOURCES = test/erasure-code/TestShecPluginSSE4.cc
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_shec_sse4_la_CFLAGS = ${AM_CFLAGS}
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_shec_sse4_la_CXXFLAGS = ${AM_CXXFLAGS}
@@ -11127,7 +11148,8 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_shec_sse4_la_LDFLAGS =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	${AM_LDFLAGS} -module \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-avoid-version -shared \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_196)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-rpath /nowhere \
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_195)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_shec_sse3_la_SOURCES = test/erasure-code/TestShecPluginSSE3.cc
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_shec_sse3_la_CFLAGS = ${AM_CFLAGS}
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_shec_sse3_la_CXXFLAGS = ${AM_CXXFLAGS}
@@ -11135,7 +11157,8 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_shec_sse3_la_LDFLAGS =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	${AM_LDFLAGS} -module \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-avoid-version -shared \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_197)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-rpath /nowhere \
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_196)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_shec_generic_la_SOURCES = test/erasure-code/TestShecPluginGeneric.cc
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_shec_generic_la_CFLAGS = ${AM_CFLAGS}
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_shec_generic_la_CXXFLAGS = ${AM_CXXFLAGS}
@@ -11143,7 +11166,8 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libec_test_shec_generic_la_LDFLAGS =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	${AM_LDFLAGS} -module \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-avoid-version -shared \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_198)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	-rpath /nowhere \
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_197)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at unittest_erasure_code_example_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	erasure-code/ErasureCode.cc \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	test/erasure-code/TestErasureCodeExample.cc
@@ -11161,7 +11185,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	$(CEPH_GLOBAL) \
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	$(PTHREAD_LIBS) \
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	$(EXTRALIBS) \
- at ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	$(am__append_200)
+ at ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	$(am__append_199)
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE at simple_client_SOURCES = \
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	test/messenger/simple_client.cc \
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	test/messenger/simple_dispatcher.cc
@@ -11173,7 +11197,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	$(CEPH_GLOBAL) \
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	$(PTHREAD_LIBS) \
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	$(EXTRALIBS) \
- at ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	$(am__append_201)
+ at ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	$(am__append_200)
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE at xio_server_SOURCES = \
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	test/messenger/xio_server.cc \
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	test/messenger/xio_dispatcher.cc
@@ -11185,7 +11209,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	$(LIBCOMMON) \
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	$(PTHREAD_LIBS) \
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	$(EXTRALIBS) \
- at ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	$(am__append_203)
+ at ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	$(am__append_202)
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE at xio_client_SOURCES = \
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	test/messenger/xio_client.cc \
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	test/messenger/xio_dispatcher.cc
@@ -11197,7 +11221,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	$(LIBCOMMON) \
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	$(PTHREAD_LIBS) \
 @ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	$(EXTRALIBS) \
- at ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	$(am__append_204)
+ at ENABLE_SERVER_TRUE@@ENABLE_XIO_TRUE@	$(am__append_203)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at libceph_example_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	compressor/Compressor.cc \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	test/compressor/compressor_plugin_example.cc
@@ -11215,7 +11239,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(LIBOSD) $(LIBCOMMON) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(UNITTEST_LDADD) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(CEPH_GLOBAL) \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_207)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_206)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at unittest_compression_snappy_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	test/compressor/test_compression_snappy.cc \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	${snappy_sources}
@@ -11225,7 +11249,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(LIBOSD) $(LIBCOMMON) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(UNITTEST_LDADD) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(CEPH_GLOBAL) \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_208)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_207)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at unittest_compression_snappy_LDFLAGS = -lsnappy
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at unittest_compression_plugin_snappy_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	test/compressor/test_compression_plugin_snappy.cc \
@@ -11237,7 +11261,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(UNITTEST_LDADD) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(CEPH_GLOBAL) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(LIBCOMPRESSOR) \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_209)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_208)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at unittest_compression_plugin_snappy_LDFLAGS = -lsnappy
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at unittest_compression_zlib_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	test/compressor/test_compression_zlib.cc \
@@ -11248,7 +11272,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(LIBOSD) $(LIBCOMMON) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(UNITTEST_LDADD) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(CEPH_GLOBAL) \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_210)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_209)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at unittest_compression_zlib_LDFLAGS = -lz
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at unittest_compression_plugin_zlib_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	test/compressor/test_compression_plugin_zlib.cc \
@@ -11260,7 +11284,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(UNITTEST_LDADD) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(CEPH_GLOBAL) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(LIBCOMPRESSOR) \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_211)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_210)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at unittest_compression_plugin_zlib_LDFLAGS = -lz
 
 # This should use LIBMDS_TYPES once it exists
@@ -11281,10 +11305,10 @@ librbd_types_la_SOURCES = \
 
 # These should always use explicit _CFLAGS/_CXXFLAGS so avoid basename conflicts
 @ENABLE_CLIENT_TRUE at ceph_dencoder_CFLAGS = ${AM_CFLAGS} \
- at ENABLE_CLIENT_TRUE@	$(am__append_212)
+ at ENABLE_CLIENT_TRUE@	$(am__append_211)
 @ENABLE_CLIENT_TRUE at ceph_dencoder_CXXFLAGS = ${AM_CXXFLAGS} \
- at ENABLE_CLIENT_TRUE@	$(am__append_213) $(am__append_214) \
- at ENABLE_CLIENT_TRUE@	$(am__append_215)
+ at ENABLE_CLIENT_TRUE@	$(am__append_212) $(am__append_213) \
+ at ENABLE_CLIENT_TRUE@	$(am__append_214)
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE at libradostest_la_SOURCES = \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	test/librados/test.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@	test/librados/TestCase.cc
@@ -11602,6 +11626,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/test_ClusterWatcher.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/test_PoolWatcher.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/test_ImageReplayer.cc \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@        test/rbd_mirror/test_ImageDeleter.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/test_ImageSync.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/test_fixture.cc
 
@@ -11612,6 +11637,7 @@ librbd_types_la_SOURCES = \
 @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 \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/image_replayer/test_mock_BootstrapRequest.cc \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/image_replayer/test_mock_CreateImageRequest.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 \
@@ -11656,6 +11682,13 @@ librbd_types_la_SOURCES = \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	$(LIBOSDC) $(UNITTEST_LDADD) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	$(CEPH_GLOBAL) $(RADOS_TEST_LDADD)
 
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at ceph_test_rbd_mirror_random_write_SOURCES = \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/random_write.cc
+
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at ceph_test_rbd_mirror_random_write_CXXFLAGS = $(UNITTEST_CXXFLAGS)
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at ceph_test_rbd_mirror_random_write_LDADD = \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	$(LIBRBD) $(LIBRADOS) $(CEPH_GLOBAL)
+
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at ceph_test_rbd_mirror_image_replay_SOURCES = \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	test/rbd_mirror/image_replay.cc
 
@@ -11728,7 +11761,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE@	test/libcephfs/multiclient.cc \
 @ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE@	test/libcephfs/access.cc \
 @ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE@	test/libcephfs/acl.cc \
- at ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE@	$(am__append_237)
+ at ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE@	$(am__append_236)
 @ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at ceph_test_libcephfs_LDADD = $(LIBRADOS) $(LIBCEPHFS) $(LIBCOMMON) $(UNITTEST_LDADD)
 @ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at ceph_test_libcephfs_CXXFLAGS = $(UNITTEST_CXXFLAGS)
 @ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at unittest_encoding_SOURCES = test/encoding.cc
@@ -11751,7 +11784,7 @@ librbd_types_la_SOURCES = \
 @ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE@	-Wignored-qualifiers \
 @ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE@	-Wold-style-definition \
 @ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE@	-Wtype-limits \
- at ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE@	$(am__append_239)
+ at ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE@	$(am__append_238)
 @ENABLE_CLIENT_TRUE@@WITH_BUILD_TESTS_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE at test_build_librgw_SOURCES = \
 @ENABLE_CLIENT_TRUE@@WITH_BUILD_TESTS_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	test/buildtest_skeleton.cc \
 @ENABLE_CLIENT_TRUE@@WITH_BUILD_TESTS_TRUE@@WITH_RADOSGW_TRUE@@WITH_RADOS_TRUE@	$(librgw_la_SOURCES)
@@ -11979,13 +12012,13 @@ librbd_types_la_SOURCES = \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at unittest_osdscrub_LDADD =  \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(LIBOSD) $(UNITTEST_LDADD) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(CEPH_GLOBAL) \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_253)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_252)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at unittest_pglog_SOURCES = test/osd/TestPGLog.cc
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at unittest_pglog_CXXFLAGS = $(UNITTEST_CXXFLAGS)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at unittest_pglog_LDADD = $(LIBOSD) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(UNITTEST_LDADD) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(CEPH_GLOBAL) \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_254)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_253)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at unittest_hitset_SOURCES = test/osd/hitset.cc
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at unittest_hitset_CXXFLAGS = $(UNITTEST_CXXFLAGS)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE at unittest_hitset_LDADD = $(LIBOSD) $(UNITTEST_LDADD) $(CEPH_GLOBAL)
@@ -12052,7 +12085,7 @@ UNITTEST_CXXFLAGS = \
 UNITTEST_LDADD = $(top_builddir)/src/gmock/lib/libgmock_main.la \
 	$(top_builddir)/src/gmock/lib/libgmock.la \
 	$(top_builddir)/src/gmock/gtest/lib/libgtest.la \
-	$(PTHREAD_LIBS) $(am__append_262)
+	$(PTHREAD_LIBS) $(am__append_261)
 unittest_addrs_SOURCES = test/test_addrs.cc
 unittest_addrs_CXXFLAGS = $(UNITTEST_CXXFLAGS)
 unittest_addrs_LDADD = $(UNITTEST_LDADD) $(CEPH_GLOBAL)
@@ -12294,7 +12327,7 @@ ceph_test_cfuse_cache_invalidate_SOURCES = test/test_cfuse_cache_invalidate.cc
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd/action/Snap.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd/action/Status.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd/action/Watch.cc \
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	$(am__append_267)
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	$(am__append_266)
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at rbd_LDADD = libjournal.la \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	libcls_journal_client.la \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	libcls_rbd_client.la \
@@ -12305,7 +12338,7 @@ ceph_test_cfuse_cache_invalidate_SOURCES = test/test_cfuse_cache_invalidate.cc
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	$(CEPH_GLOBAL) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	$(BOOST_REGEX_LIBS) \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	$(BOOST_PROGRAM_OPTIONS_LIBS) \
- at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	$(am__append_269)
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	$(am__append_268)
 @ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at rbd_nbd_SOURCES = tools/rbd_nbd/rbd-nbd.cc
 @ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at rbd_nbd_CXXFLAGS = $(AM_CXXFLAGS)
 @ENABLE_CLIENT_TRUE@@LINUX_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE at rbd_nbd_LDADD = $(LIBRBD) $(LIBRADOS) $(CEPH_GLOBAL) $(BOOST_REGEX_LIBS)
@@ -12318,10 +12351,13 @@ ceph_test_cfuse_cache_invalidate_SOURCES = test/test_cfuse_cache_invalidate.cc
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/Mirror.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/PoolWatcher.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/Replayer.cc \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@        tools/rbd_mirror/ImageDeleter.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/Threads.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/types.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 \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_replayer/CreateImageRequest.cc \
+ at ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_replayer/OpenImageRequest.cc \
 @ENABLE_CLIENT_TRUE@@WITH_RADOS_TRUE@@WITH_RBD_TRUE@	tools/rbd_mirror/image_replayer/OpenLocalImageRequest.cc \
 @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 \
@@ -12363,7 +12399,7 @@ ceph_test_cfuse_cache_invalidate_SOURCES = test/test_cfuse_cache_invalidate.cc
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(LIBOSD) $(LIBOS) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(CEPH_GLOBAL) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(BOOST_PROGRAM_OPTIONS_LIBS) \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_276)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_275)
 @ENABLE_CLIENT_TRUE@@ENABLE_SERVER_TRUE@@WITH_MDS_TRUE@@WITH_RADOS_TRUE at cephfs_journal_tool_SOURCES = \
 @ENABLE_CLIENT_TRUE@@ENABLE_SERVER_TRUE@@WITH_MDS_TRUE@@WITH_RADOS_TRUE@	tools/cephfs/cephfs-journal-tool.cc \
 @ENABLE_CLIENT_TRUE@@ENABLE_SERVER_TRUE@@WITH_MDS_TRUE@@WITH_RADOS_TRUE@	tools/cephfs/JournalTool.cc \
@@ -12469,7 +12505,7 @@ editpaths = sed \
 	-e 's|@@GCOV_PREFIX_STRIP[@][@]|$(GCOV_PREFIX_STRIP)|g'
 
 shell_scripts = ceph-debugpack ceph-post-file ceph-crush-location \
-	$(am__append_309)
+	$(am__append_308)
 doc_DATA = $(srcdir)/sample.ceph.conf sample.fetch_config
 
 # various scripts in $(libexecdir)
@@ -12489,11 +12525,11 @@ AM_TESTS_ENVIRONMENT = export CEPH_ROOT="$(abs_top_srcdir)"; export \
 	PATH="$(abs_srcdir):$$PATH";
 
 # pybind
-python_PYTHON = $(am__append_293) $(am__append_303) $(am__append_308)
+python_PYTHON = $(am__append_292) $(am__append_302) $(am__append_307)
 @ENABLE_CLIENT_TRUE at bash_completiondir = $(sysconfdir)/bash_completion.d
 @ENABLE_CLIENT_TRUE at bash_completion_DATA =  \
 @ENABLE_CLIENT_TRUE@	$(srcdir)/bash_completion/ceph \
- at ENABLE_CLIENT_TRUE@	$(am__append_295) $(am__append_297)
+ at ENABLE_CLIENT_TRUE@	$(am__append_294) $(am__append_296)
 @ENABLE_CLIENT_TRUE at ceph_syn_SOURCES = ceph_syn.cc \
 @ENABLE_CLIENT_TRUE@	client/SyntheticClient.cc # uses g_conf.. \
 @ENABLE_CLIENT_TRUE@	needs cleanup
@@ -12520,7 +12556,7 @@ python_PYTHON = $(am__append_293) $(am__append_303) $(am__append_308)
 @ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE@	1:0:0 \
 @ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE@	-export-symbols-regex \
 @ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE@	'^ceph_.*' \
- at ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE@	$(am__append_304)
+ at ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE@	$(am__append_303)
 
 # jni library (java source is in src/java)
 @ENABLE_CEPHFS_JAVA_TRUE@@ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at libcephfs_jni_la_SOURCES = \
@@ -12533,7 +12569,7 @@ python_PYTHON = $(am__append_293) $(am__append_303) $(am__append_308)
 @ENABLE_CEPHFS_JAVA_TRUE@@ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at libcephfs_jni_la_CPPFLAGS = $(JDK_CPPFLAGS) $(AM_CPPFLAGS)
 @ENABLE_CEPHFS_JAVA_TRUE@@ENABLE_CLIENT_TRUE@@WITH_CEPHFS_TRUE@@WITH_RADOS_TRUE at libcephfs_jni_la_LDFLAGS = ${AM_LDFLAGS} -version-info 1:0:0
 @ENABLE_SERVER_TRUE at ceph_sbin_SCRIPTS = ceph-create-keys \
- at ENABLE_SERVER_TRUE@	$(am__append_314)
+ at ENABLE_SERVER_TRUE@	$(am__append_313)
 @ENABLE_SERVER_TRUE at mount_ceph_SOURCES = mount/mount.ceph.c
 @ENABLE_SERVER_TRUE at mount_ceph_LDADD = $(LIBSECRET) $(LIBCOMMON)
 @ENABLE_SERVER_TRUE@@WITH_MON_TRUE at ceph_mon_SOURCES = ceph_mon.cc
@@ -12543,7 +12579,7 @@ python_PYTHON = $(am__append_293) $(am__append_303) $(am__append_308)
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(LIBOSD) $(LIBOSD_TYPES) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(LIBOS_TYPES) $(LIBOS) \
 @ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(CEPH_GLOBAL) $(LIBCOMMON) \
- at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_316)
+ at ENABLE_SERVER_TRUE@@WITH_OSD_TRUE@	$(am__append_315)
 @ENABLE_SERVER_TRUE@@WITH_MDS_TRUE at ceph_mds_SOURCES = ceph_mds.cc
 @ENABLE_SERVER_TRUE@@WITH_MDS_TRUE at ceph_mds_LDADD = $(LIBMDS) $(LIBOSDC) $(CEPH_GLOBAL) $(LIBCOMMON)
 @ENABLE_COVERAGE_TRUE@@ENABLE_SERVER_TRUE at COV_DIR = $(DESTDIR)$(libdir)/ceph/coverage
@@ -12854,6 +12890,17 @@ libosd.a: $(libosd_a_OBJECTS) $(libosd_a_DEPENDENCIES) $(EXTRA_libosd_a_DEPENDEN
 	$(AM_V_AR)$(libosd_a_AR) libosd.a $(libosd_a_OBJECTS) $(libosd_a_LIBADD)
 	$(AM_V_at)$(RANLIB) libosd.a
 
+clean-checkLTLIBRARIES:
+	-test -z "$(check_LTLIBRARIES)" || rm -f $(check_LTLIBRARIES)
+	@list='$(check_LTLIBRARIES)'; \
+	locs=`for p in $$list; do echo $$p; done | \
+	      sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+	      sort -u`; \
+	test -z "$$locs" || { \
+	  echo rm -f $${locs}; \
+	  rm -f $${locs}; \
+	}
+
 install-compressorlibLTLIBRARIES: $(compressorlib_LTLIBRARIES)
 	@$(NORMAL_INSTALL)
 	@list='$(compressorlib_LTLIBRARIES)'; test -n "$(compressorlibdir)" || list=; \
@@ -15138,6 +15185,8 @@ tools/rbd_mirror/PoolWatcher.lo: tools/rbd_mirror/$(am__dirstamp) \
 	tools/rbd_mirror/$(DEPDIR)/$(am__dirstamp)
 tools/rbd_mirror/Replayer.lo: tools/rbd_mirror/$(am__dirstamp) \
 	tools/rbd_mirror/$(DEPDIR)/$(am__dirstamp)
+tools/rbd_mirror/ImageDeleter.lo: tools/rbd_mirror/$(am__dirstamp) \
+	tools/rbd_mirror/$(DEPDIR)/$(am__dirstamp)
 tools/rbd_mirror/Threads.lo: tools/rbd_mirror/$(am__dirstamp) \
 	tools/rbd_mirror/$(DEPDIR)/$(am__dirstamp)
 tools/rbd_mirror/types.lo: tools/rbd_mirror/$(am__dirstamp) \
@@ -15154,6 +15203,12 @@ tools/rbd_mirror/image_replayer/BootstrapRequest.lo:  \
 tools/rbd_mirror/image_replayer/CloseImageRequest.lo:  \
 	tools/rbd_mirror/image_replayer/$(am__dirstamp) \
 	tools/rbd_mirror/image_replayer/$(DEPDIR)/$(am__dirstamp)
+tools/rbd_mirror/image_replayer/CreateImageRequest.lo:  \
+	tools/rbd_mirror/image_replayer/$(am__dirstamp) \
+	tools/rbd_mirror/image_replayer/$(DEPDIR)/$(am__dirstamp)
+tools/rbd_mirror/image_replayer/OpenImageRequest.lo:  \
+	tools/rbd_mirror/image_replayer/$(am__dirstamp) \
+	tools/rbd_mirror/image_replayer/$(DEPDIR)/$(am__dirstamp)
 tools/rbd_mirror/image_replayer/OpenLocalImageRequest.lo:  \
 	tools/rbd_mirror/image_replayer/$(am__dirstamp) \
 	tools/rbd_mirror/image_replayer/$(DEPDIR)/$(am__dirstamp)
@@ -15202,6 +15257,9 @@ test/rbd_mirror/librbd_mirror_test_la-test_PoolWatcher.lo:  \
 test/rbd_mirror/librbd_mirror_test_la-test_ImageReplayer.lo:  \
 	test/rbd_mirror/$(am__dirstamp) \
 	test/rbd_mirror/$(DEPDIR)/$(am__dirstamp)
+test/rbd_mirror/librbd_mirror_test_la-test_ImageDeleter.lo:  \
+	test/rbd_mirror/$(am__dirstamp) \
+	test/rbd_mirror/$(DEPDIR)/$(am__dirstamp)
 test/rbd_mirror/librbd_mirror_test_la-test_ImageSync.lo:  \
 	test/rbd_mirror/$(am__dirstamp) \
 	test/rbd_mirror/$(DEPDIR)/$(am__dirstamp)
@@ -16651,6 +16709,13 @@ test/rbd_mirror/image_replay.$(OBJEXT):  \
 ceph_test_rbd_mirror_image_replay$(EXEEXT): $(ceph_test_rbd_mirror_image_replay_OBJECTS) $(ceph_test_rbd_mirror_image_replay_DEPENDENCIES) $(EXTRA_ceph_test_rbd_mirror_image_replay_DEPENDENCIES) 
 	@rm -f ceph_test_rbd_mirror_image_replay$(EXEEXT)
 	$(AM_V_CXXLD)$(CXXLINK) $(ceph_test_rbd_mirror_image_replay_OBJECTS) $(ceph_test_rbd_mirror_image_replay_LDADD) $(LIBS)
+test/rbd_mirror/ceph_test_rbd_mirror_random_write-random_write.$(OBJEXT):  \
+	test/rbd_mirror/$(am__dirstamp) \
+	test/rbd_mirror/$(DEPDIR)/$(am__dirstamp)
+
+ceph_test_rbd_mirror_random_write$(EXEEXT): $(ceph_test_rbd_mirror_random_write_OBJECTS) $(ceph_test_rbd_mirror_random_write_DEPENDENCIES) $(EXTRA_ceph_test_rbd_mirror_random_write_DEPENDENCIES) 
+	@rm -f ceph_test_rbd_mirror_random_write$(EXEEXT)
+	$(AM_V_CXXLD)$(ceph_test_rbd_mirror_random_write_LINK) $(ceph_test_rbd_mirror_random_write_OBJECTS) $(ceph_test_rbd_mirror_random_write_LDADD) $(LIBS)
 test/test_rewrite_latency.$(OBJEXT): test/$(am__dirstamp) \
 	test/$(DEPDIR)/$(am__dirstamp)
 
@@ -18288,6 +18353,9 @@ 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_replayer/unittest_rbd_mirror-test_mock_CreateImageRequest.$(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)
@@ -20243,8 +20311,10 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote at test/osdc/$(DEPDIR)/FakeWriteback.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/osdc/$(DEPDIR)/object_cacher_stress.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/rbd_mirror/$(DEPDIR)/ceph_test_rbd_mirror-test_main.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at test/rbd_mirror/$(DEPDIR)/ceph_test_rbd_mirror_random_write-random_write.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/rbd_mirror/$(DEPDIR)/image_replay.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/rbd_mirror/$(DEPDIR)/librbd_mirror_test_la-test_ClusterWatcher.Plo at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at test/rbd_mirror/$(DEPDIR)/librbd_mirror_test_la-test_ImageDeleter.Plo at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/rbd_mirror/$(DEPDIR)/librbd_mirror_test_la-test_ImageReplayer.Plo at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/rbd_mirror/$(DEPDIR)/librbd_mirror_test_la-test_ImageSync.Plo at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/rbd_mirror/$(DEPDIR)/librbd_mirror_test_la-test_PoolWatcher.Plo at am__quote@
@@ -20254,6 +20324,7 @@ distclean-compile:
 @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@
 @AMDEP_TRUE@@am__include@ @am__quote at test/rbd_mirror/image_replayer/$(DEPDIR)/unittest_rbd_mirror-test_mock_BootstrapRequest.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at test/rbd_mirror/image_replayer/$(DEPDIR)/unittest_rbd_mirror-test_mock_CreateImageRequest.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@
@@ -20346,6 +20417,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote at tools/rbd/action/$(DEPDIR)/Status.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at tools/rbd/action/$(DEPDIR)/Watch.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at tools/rbd_mirror/$(DEPDIR)/ClusterWatcher.Plo at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at tools/rbd_mirror/$(DEPDIR)/ImageDeleter.Plo at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at tools/rbd_mirror/$(DEPDIR)/ImageReplayer.Plo at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at tools/rbd_mirror/$(DEPDIR)/ImageSync.Plo at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at tools/rbd_mirror/$(DEPDIR)/Mirror.Plo at am__quote@
@@ -20356,6 +20428,8 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote at tools/rbd_mirror/$(DEPDIR)/types.Plo at am__quote@
 @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@
+ at AMDEP_TRUE@@am__include@ @am__quote at tools/rbd_mirror/image_replayer/$(DEPDIR)/CreateImageRequest.Plo at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at tools/rbd_mirror/image_replayer/$(DEPDIR)/OpenImageRequest.Plo at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at tools/rbd_mirror/image_replayer/$(DEPDIR)/OpenLocalImageRequest.Plo at am__quote@
 @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@
@@ -24127,6 +24201,13 @@ test/rbd_mirror/librbd_mirror_test_la-test_ImageReplayer.lo: test/rbd_mirror/tes
 @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_mirror_test_la_CXXFLAGS) $(CXXFLAGS) -c -o test/rbd_mirror/librbd_mirror_test_la-test_ImageReplayer.lo `test -f 'test/rbd_mirror/test_ImageReplayer.cc' || echo '$(srcdir)/'`test/rbd_mirror/test_ImageReplayer.cc
 
+test/rbd_mirror/librbd_mirror_test_la-test_ImageDeleter.lo: test/rbd_mirror/test_ImageDeleter.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_mirror_test_la_CXXFLAGS) $(CXXFLAGS) -MT test/rbd_mirror/librbd_mirror_test_la-test_ImageDeleter.lo -MD -MP -MF test/rbd_mirror/$(DEPDIR)/librbd_mirror_test_la-test_ImageDeleter.Tpo -c -o test/rbd_mirror/librbd_mirror_test_la-test_ImageDeleter.lo `test -f 'test/rbd_mirror/test_ImageDeleter.cc' [...]
+ at am__fastdepCXX_TRUE@	$(AM_V_at)$(am__mv) test/rbd_mirror/$(DEPDIR)/librbd_mirror_test_la-test_ImageDeleter.Tpo test/rbd_mirror/$(DEPDIR)/librbd_mirror_test_la-test_ImageDeleter.Plo
+ at AMDEP_TRUE@@am__fastdepCXX_FALSE@	$(AM_V_CXX)source='test/rbd_mirror/test_ImageDeleter.cc' object='test/rbd_mirror/librbd_mirror_test_la-test_ImageDeleter.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_mirror_test_la_CXXFLAGS) $(CXXFLAGS) -c -o test/rbd_mirror/librbd_mirror_test_la-test_ImageDeleter.lo `test -f 'test/rbd_mirror/test_ImageDeleter.cc' || echo '$(srcdir)/'`test/rbd_mirror/test_ImageDeleter.cc
+
 test/rbd_mirror/librbd_mirror_test_la-test_ImageSync.lo: test/rbd_mirror/test_ImageSync.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) $(librbd_mirror_test_la_CXXFLAGS) $(CXXFLAGS) -MT test/rbd_mirror/librbd_mirror_test_la-test_ImageSync.lo -MD -MP -MF test/rbd_mirror/$(DEPDIR)/librbd_mirror_test_la-test_ImageSync.Tpo -c -o test/rbd_mirror/librbd_mirror_test_la-test_ImageSync.lo `test -f 'test/rbd_mirror/test_ImageSync.cc' || echo '$( [...]
 @am__fastdepCXX_TRUE@	$(AM_V_at)$(am__mv) test/rbd_mirror/$(DEPDIR)/librbd_mirror_test_la-test_ImageSync.Tpo test/rbd_mirror/$(DEPDIR)/librbd_mirror_test_la-test_ImageSync.Plo
@@ -26248,6 +26329,20 @@ test/rbd_mirror/ceph_test_rbd_mirror-test_main.obj: test/rbd_mirror/test_main.cc
 @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) $(ceph_test_rbd_mirror_CXXFLAGS) $(CXXFLAGS) -c -o test/rbd_mirror/ceph_test_rbd_mirror-test_main.obj `if test -f 'test/rbd_mirror/test_main.cc'; then $(CYGPATH_W) 'test/rbd_mirror/test_main.cc'; else $(CYGPATH_W) '$(srcdir)/test/rbd_mirror/test_main.cc'; fi`
 
+test/rbd_mirror/ceph_test_rbd_mirror_random_write-random_write.o: test/rbd_mirror/random_write.cc
+ at am__fastdepCXX_TRUE@	$(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ceph_test_rbd_mirror_random_write_CXXFLAGS) $(CXXFLAGS) -MT test/rbd_mirror/ceph_test_rbd_mirror_random_write-random_write.o -MD -MP -MF test/rbd_mirror/$(DEPDIR)/ceph_test_rbd_mirror_random_write-random_write.Tpo -c -o test/rbd_mirror/ceph_test_rbd_mirror_random_write-random_write.o `test -f 'test/rbd_mirror/random_write.cc' || echo '$(srcdir)/'`test/rbd_mirror/random_write.cc
+ at am__fastdepCXX_TRUE@	$(AM_V_at)$(am__mv) test/rbd_mirror/$(DEPDIR)/ceph_test_rbd_mirror_random_write-random_write.Tpo test/rbd_mirror/$(DEPDIR)/ceph_test_rbd_mirror_random_write-random_write.Po
+ at AMDEP_TRUE@@am__fastdepCXX_FALSE@	$(AM_V_CXX)source='test/rbd_mirror/random_write.cc' object='test/rbd_mirror/ceph_test_rbd_mirror_random_write-random_write.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) $(ceph_test_rbd_mirror_random_write_CXXFLAGS) $(CXXFLAGS) -c -o test/rbd_mirror/ceph_test_rbd_mirror_random_write-random_write.o `test -f 'test/rbd_mirror/random_write.cc' || echo '$(srcdir)/'`test/rbd_mirror/random_write.cc
+
+test/rbd_mirror/ceph_test_rbd_mirror_random_write-random_write.obj: test/rbd_mirror/random_write.cc
+ at am__fastdepCXX_TRUE@	$(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ceph_test_rbd_mirror_random_write_CXXFLAGS) $(CXXFLAGS) -MT test/rbd_mirror/ceph_test_rbd_mirror_random_write-random_write.obj -MD -MP -MF test/rbd_mirror/$(DEPDIR)/ceph_test_rbd_mirror_random_write-random_write.Tpo -c -o test/rbd_mirror/ceph_test_rbd_mirror_random_write-random_write.obj `if test -f 'test/rbd_mirror/random_write.cc'; then $(CYGPATH_W) 'test/rbd_mirror/random_write [...]
+ at am__fastdepCXX_TRUE@	$(AM_V_at)$(am__mv) test/rbd_mirror/$(DEPDIR)/ceph_test_rbd_mirror_random_write-random_write.Tpo test/rbd_mirror/$(DEPDIR)/ceph_test_rbd_mirror_random_write-random_write.Po
+ at AMDEP_TRUE@@am__fastdepCXX_FALSE@	$(AM_V_CXX)source='test/rbd_mirror/random_write.cc' object='test/rbd_mirror/ceph_test_rbd_mirror_random_write-random_write.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) $(ceph_test_rbd_mirror_random_write_CXXFLAGS) $(CXXFLAGS) -c -o test/rbd_mirror/ceph_test_rbd_mirror_random_write-random_write.obj `if test -f 'test/rbd_mirror/random_write.cc'; then $(CYGPATH_W) 'test/rbd_mirror/random_write.cc'; else $(CYGPATH_W) '$(srcdir)/test/rbd_mirror/random_write.cc'; fi`
+
 test/rgw/ceph_test_rgw_manifest-test_rgw_manifest.o: test/rgw/test_rgw_manifest.cc
 @am__fastdepCXX_TRUE@	$(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ceph_test_rgw_manifest_CXXFLAGS) $(CXXFLAGS) -MT test/rgw/ceph_test_rgw_manifest-test_rgw_manifest.o -MD -MP -MF test/rgw/$(DEPDIR)/ceph_test_rgw_manifest-test_rgw_manifest.Tpo -c -o test/rgw/ceph_test_rgw_manifest-test_rgw_manifest.o `test -f 'test/rgw/test_rgw_manifest.cc' || echo '$(srcdir)/'`test/rgw/test_rgw_manifest.cc
 @am__fastdepCXX_TRUE@	$(AM_V_at)$(am__mv) test/rgw/$(DEPDIR)/ceph_test_rgw_manifest-test_rgw_manifest.Tpo test/rgw/$(DEPDIR)/ceph_test_rgw_manifest-test_rgw_manifest.Po
@@ -29776,6 +29871,20 @@ test/rbd_mirror/image_replayer/unittest_rbd_mirror-test_mock_BootstrapRequest.ob
 @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_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_replayer/unittest_rbd_mirror-test_mock_CreateImageRequest.o: test/rbd_mirror/image_replayer/test_mock_CreateImageRequest.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_CreateImageRequest.o -MD -MP -MF test/rbd_mirror/image_replayer/$(DEPDIR)/unittest_rbd_mirror-test_mock_CreateImageRequest.Tpo -c -o test/rbd_mirror/image_replayer/unittest_rbd_mirror-test_mock_CreateImageRequest.o `test -f 'test/rbd_mirror/image_replayer/test_mock_CreateImag [...]
+ at am__fastdepCXX_TRUE@	$(AM_V_at)$(am__mv) test/rbd_mirror/image_replayer/$(DEPDIR)/unittest_rbd_mirror-test_mock_CreateImageRequest.Tpo test/rbd_mirror/image_replayer/$(DEPDIR)/unittest_rbd_mirror-test_mock_CreateImageRequest.Po
+ at AMDEP_TRUE@@am__fastdepCXX_FALSE@	$(AM_V_CXX)source='test/rbd_mirror/image_replayer/test_mock_CreateImageRequest.cc' object='test/rbd_mirror/image_replayer/unittest_rbd_mirror-test_mock_CreateImageRequest.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_CreateImageRequest.o `test -f 'test/rbd_mirror/image_replayer/test_mock_CreateImageRequest.cc' || echo '$(srcdir)/'`test/rbd_mirror/image_replayer/test_mock_CreateImageRequest.cc
+
+test/rbd_mirror/image_replayer/unittest_rbd_mirror-test_mock_CreateImageRequest.obj: test/rbd_mirror/image_replayer/test_mock_CreateImageRequest.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_CreateImageRequest.obj -MD -MP -MF test/rbd_mirror/image_replayer/$(DEPDIR)/unittest_rbd_mirror-test_mock_CreateImageRequest.Tpo -c -o test/rbd_mirror/image_replayer/unittest_rbd_mirror-test_mock_CreateImageRequest.obj `if test -f 'test/rbd_mirror/image_replayer/test_mock_Cre [...]
+ at am__fastdepCXX_TRUE@	$(AM_V_at)$(am__mv) test/rbd_mirror/image_replayer/$(DEPDIR)/unittest_rbd_mirror-test_mock_CreateImageRequest.Tpo test/rbd_mirror/image_replayer/$(DEPDIR)/unittest_rbd_mirror-test_mock_CreateImageRequest.Po
+ at AMDEP_TRUE@@am__fastdepCXX_FALSE@	$(AM_V_CXX)source='test/rbd_mirror/image_replayer/test_mock_CreateImageRequest.cc' object='test/rbd_mirror/image_replayer/unittest_rbd_mirror-test_mock_CreateImageRequest.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_CreateImageRequest.obj `if test -f 'test/rbd_mirror/image_replayer/test_mock_CreateImageRequest.cc'; then $(CYGPATH_W) 'test/rbd_mirror/image_replayer/test_mock_CreateImageRequest.cc'; else $(CYGPATH_W) '$(srcdir)/test/rbd_mirror/image_replayer/test_mock_CreateI [...]
+
 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
@@ -30952,7 +31061,7 @@ check-TESTS:
 	log_list=`echo $$log_list`; trs_list=`echo $$trs_list`; \
 	$(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) TEST_LOGS="$$log_list"; \
 	exit $$?;
-recheck: all $(check_PROGRAMS) $(check_SCRIPTS)
+recheck: all $(check_LTLIBRARIES) $(check_PROGRAMS) $(check_SCRIPTS)
 	@test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
 	@set +e; $(am__set_TESTS_bases); \
 	bases=`for i in $$bases; do echo $$i; done \
@@ -32024,7 +32133,8 @@ distdir: $(DISTFILES)
 	  top_distdir="$(top_distdir)" distdir="$(distdir)" \
 	  dist-hook
 check-am: all-am
-	$(MAKE) $(AM_MAKEFLAGS) $(check_PROGRAMS) $(check_SCRIPTS)
+	$(MAKE) $(AM_MAKEFLAGS) $(check_LTLIBRARIES) $(check_PROGRAMS) \
+	  $(check_SCRIPTS)
 	$(MAKE) $(AM_MAKEFLAGS) check-TESTS
 check: $(BUILT_SOURCES)
 	$(MAKE) $(AM_MAKEFLAGS) check-recursive
@@ -32335,7 +32445,7 @@ maintainer-clean-generic:
 	-test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES)
 clean: clean-recursive
 
-clean-am: clean-binPROGRAMS clean-checkPROGRAMS \
+clean-am: clean-binPROGRAMS clean-checkLTLIBRARIES clean-checkPROGRAMS \
 	clean-compressorlibLTLIBRARIES \
 	clean-erasure_codelibLTLIBRARIES clean-generic \
 	clean-libLTLIBRARIES clean-libtool clean-local \
@@ -32437,7 +32547,8 @@ uninstall-am: uninstall-bash_completionDATA uninstall-binPROGRAMS \
 
 .PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am all-local \
 	check check-TESTS check-am clean clean-binPROGRAMS \
-	clean-checkPROGRAMS clean-compressorlibLTLIBRARIES \
+	clean-checkLTLIBRARIES clean-checkPROGRAMS \
+	clean-compressorlibLTLIBRARIES \
 	clean-erasure_codelibLTLIBRARIES clean-generic \
 	clean-libLTLIBRARIES clean-libtool clean-local \
 	clean-noinstLIBRARIES clean-noinstLTLIBRARIES \
diff --git a/src/ceph-disk/ceph_disk/main.py b/src/ceph-disk/ceph_disk/main.py
index 45e9bb2..99558fb 100755
--- a/src/ceph-disk/ceph_disk/main.py
+++ b/src/ceph-disk/ceph_disk/main.py
@@ -2642,6 +2642,36 @@ class PrepareBluestoreData(PrepareData):
         write_one_line(path, 'type', 'bluestore')
 
 
+#
+# Temporary workaround: if ceph-osd --mkfs does not
+# complete within 5 minutes, assume it is blocked
+# because of http://tracker.ceph.com/issues/13522
+# and retry a few times.
+#
+# Remove this function calls with command_check_call
+# when http://tracker.ceph.com/issues/13522 is fixed
+#
+def ceph_osd_mkfs(arguments):
+    timeout = _get_command_executable(['timeout'])
+    mkfs_ok = False
+    error = 'unknown error'
+    for delay in os.environ.get('CEPH_OSD_MKFS_DELAYS',
+                                '300 300 300 300 300').split():
+        try:
+            _check_output(timeout + [delay] + arguments)
+            mkfs_ok = True
+            break
+        except subprocess.CalledProcessError as e:
+            error = e.output
+            if e.returncode == 124:  # timeout fired, retry
+                LOG.debug('%s timed out : %s (retry)'
+                          % (str(arguments), error))
+            else:
+                break
+    if not mkfs_ok:
+        raise Error('%s failed : %s' % (str(arguments), error))
+
+
 def mkfs(
     path,
     cluster,
@@ -2663,7 +2693,7 @@ def mkfs(
     osd_type = read_one_line(path, 'type')
 
     if osd_type == 'bluestore':
-        command_check_call(
+        ceph_osd_mkfs(
             [
                 'ceph-osd',
                 '--cluster', cluster,
@@ -2679,7 +2709,7 @@ def mkfs(
             ],
         )
     else:
-        command_check_call(
+        ceph_osd_mkfs(
             [
                 'ceph-osd',
                 '--cluster', cluster,
diff --git a/src/ceph.in b/src/ceph.in
index ecc3eca..cf1210d 100755
--- a/src/ceph.in
+++ b/src/ceph.in
@@ -892,7 +892,7 @@ def main():
 
         if ret < 0:
             ret = -ret
-            print('Error {0}: {1}'.format(errno.errorcode.get(ret, 'Unknown'), outs), file=sys.stderr)
+            print(u'Error {0}: {1}'.format(errno.errorcode.get(ret, 'Unknown'), outs), file=sys.stderr)
             if len(targets) > 1:
                 final_ret = ret
             else:
diff --git a/src/client/Client.cc b/src/client/Client.cc
index 7fcd907..edbe073 100644
--- a/src/client/Client.cc
+++ b/src/client/Client.cc
@@ -174,10 +174,9 @@ bool Client::CommandHook::call(std::string command, cmdmap_t& cmdmap,
 // -------------
 
 dir_result_t::dir_result_t(Inode *in)
-  : inode(in), owner_uid(-1), owner_gid(-1), offset(0), this_offset(2),
-    next_offset(2), release_count(0), ordered_count(0), start_shared_gen(0),
-    buffer(0) {
-}
+  : inode(in), owner_uid(-1), owner_gid(-1), offset(0), next_offset(2),
+    release_count(0), ordered_count(0), cache_index(0), start_shared_gen(0)
+  { }
 
 void Client::_reset_faked_inos()
 {
@@ -714,12 +713,9 @@ void Client::trim_dentry(Dentry *dn)
 		 << " in dir " << hex << dn->dir->parent_inode->ino 
 		 << dendl;
   if (dn->inode) {
-    dn->dir->release_count++;
-    if (dn->dir->parent_inode->flags & I_COMPLETE) {
-      ldout(cct, 10) << " clearing (I_COMPLETE|I_DIR_ORDERED) on "
-		     << *dn->dir->parent_inode << dendl;
-      dn->dir->parent_inode->flags &= ~(I_COMPLETE | I_DIR_ORDERED);
-    }
+    Inode *diri = dn->dir->parent_inode;
+    diri->dir_release_count++;
+    clear_dir_complete_and_ordered(diri, true);
   }
   unlink(dn, false, false);  // drop dir, drop dentry
 }
@@ -955,13 +951,14 @@ Inode * Client::add_update_inode(InodeStat *st, utime_t from,
     in->flags |= I_COMPLETE | I_DIR_ORDERED;
     if (in->dir) {
       ldout(cct, 10) << " dir is open on empty dir " << in->ino << " with "
-		     << in->dir->dentry_list.size() << " entries, marking all dentries null" << dendl;
-      for (xlist<Dentry*>::iterator p = in->dir->dentry_list.begin();
-	   !p.end();
+		     << in->dir->dentries.size() << " entries, marking all dentries null" << dendl;
+      in->dir->readdir_cache.clear();
+      for (auto p = in->dir->dentries.begin();
+	   p != in->dir->dentries.end();
 	   ++p) {
-	unlink(*p, true, true);  // keep dir, keep dentry
+	unlink(p->second, true, true);  // keep dir, keep dentry
       }
-      if (in->dir->dentry_list.empty())
+      if (in->dir->dentries.empty())
 	close_dir(in->dir);
     }
   }
@@ -1003,20 +1000,15 @@ Dentry *Client::insert_dentry_inode(Dir *dir, const string& dname, LeaseStat *dl
     InodeRef tmp_ref(in);
     if (old_dentry) {
       if (old_dentry->dir != dir) {
-	old_dentry->dir->ordered_count++;
-	if (old_dentry->dir->parent_inode->flags & I_DIR_ORDERED) {
-	  ldout(cct, 10) << " clearing I_DIR_ORDERED on "
-			 << *old_dentry->dir->parent_inode << dendl;
-	  old_dentry->dir->parent_inode->flags &= ~I_DIR_ORDERED;
-	}
+	Inode *old_diri = old_dentry->dir->parent_inode;
+	old_diri->dir_ordered_count++;
+	clear_dir_complete_and_ordered(old_diri, false);
       }
       unlink(old_dentry, dir == old_dentry->dir, false);  // drop dentry, keep dir open if its the same dir
     }
-    dir->ordered_count++;
-    if (dir->parent_inode->flags & I_DIR_ORDERED) {
-	ldout(cct, 10) << " clearing I_DIR_ORDERED on " << *dir->parent_inode << dendl;
-	dir->parent_inode->flags &= ~I_DIR_ORDERED;
-    }
+    Inode *diri = dir->parent_inode;
+    diri->dir_ordered_count++;
+    clear_dir_complete_and_ordered(diri, false);
     dn = link(dir, dname, in, dn);
   }
 
@@ -1080,6 +1072,23 @@ void Client::update_dir_dist(Inode *in, DirStat *dst)
   */
 }
 
+void Client::clear_dir_complete_and_ordered(Inode *diri, bool complete)
+{
+  if (diri->flags & I_COMPLETE) {
+    if (complete) {
+      ldout(cct, 10) << " clearing (I_COMPLETE|I_DIR_ORDERED) on " << *diri << dendl;
+      diri->flags &= ~(I_COMPLETE | I_DIR_ORDERED);
+    } else {
+      if (diri->flags & I_DIR_ORDERED) {
+	ldout(cct, 10) << " clearing I_DIR_ORDERED on " << *diri << dendl;
+	diri->flags &= ~I_DIR_ORDERED;
+      }
+    }
+    if (diri->dir)
+      diri->dir->readdir_cache.clear();
+  }
+}
+
 /*
  * insert results from readdir or lssnap into the metadata cache.
  */
@@ -1089,7 +1098,8 @@ void Client::insert_readdir_results(MetaRequest *request, MetaSession *session,
   ConnectionRef con = request->reply->get_connection();
   uint64_t features = con->get_features();
 
-  assert(request->readdir_result.empty());
+  dir_result_t *dirp = request->dirp;
+  assert(dirp);
 
   // the extra buffer list is only set for readdir and lssnap replies
   bufferlist::iterator p = reply->get_extra_bl().begin();
@@ -1107,31 +1117,47 @@ void Client::insert_readdir_results(MetaRequest *request, MetaSession *session,
     // dirstat
     DirStat dst(p);
     __u32 numdn;
-    __u8 complete, end;
+    __u16 flags;
     ::decode(numdn, p);
-    ::decode(end, p);
-    ::decode(complete, p);
+    ::decode(flags, p);
+
+    bool end = ((unsigned)flags & CEPH_READDIR_FRAG_END);
+    bool hash_order = ((unsigned)flags & CEPH_READDIR_HASH_ORDER);
+
+    frag_t fg = (unsigned)request->head.args.readdir.frag;
+    unsigned readdir_offset = dirp->next_offset;
+    string readdir_start = dirp->last_name;
+
+    unsigned last_hash = 0;
+    if (!readdir_start.empty())
+      last_hash = ceph_frag_value(diri->hash_dentry_name(readdir_start));
 
-    frag_t fg = request->readdir_frag;
-    uint64_t readdir_offset = request->readdir_offset;
-    string readdir_start = request->readdir_start;
     if (fg != dst.frag) {
       ldout(cct, 10) << "insert_trace got new frag " << fg << " -> " << dst.frag << dendl;
       fg = dst.frag;
-      if (fg.is_leftmost())
+      if (!hash_order) {
 	readdir_offset = 2;
-      else
-	readdir_offset = 0;
-      readdir_start.clear();
+	readdir_start.clear();
+	dirp->offset = dir_result_t::make_fpos(fg, readdir_offset, false);
+      }
     }
 
-    ldout(cct, 10) << __func__ << " " << numdn << " readdir items, end=" << (int)end
-		   << ", offset " << readdir_offset
+    ldout(cct, 10) << __func__ << " " << numdn << " readdir items, end=" << end
+		   << ", hash_order=" << hash_order << ", offset " << readdir_offset
 		   << ", readdir_start " << readdir_start << dendl;
 
-    request->readdir_reply_frag = fg;
-    request->readdir_end = end;
-    request->readdir_num = numdn;
+    if (diri->snapid != CEPH_SNAPDIR &&
+	fg.is_leftmost() && readdir_offset == 2) {
+      dirp->release_count = diri->dir_release_count;
+      dirp->ordered_count = diri->dir_ordered_count;
+      dirp->start_shared_gen = diri->shared_gen;
+      dirp->cache_index = 0;
+    }
+
+    dirp->buffer_frag = fg;
+
+    _readdir_drop_dirp_buffer(dirp);
+    dirp->buffer.reserve(numdn);
 
     string dname;
     LeaseStat dlease;
@@ -1155,21 +1181,53 @@ void Client::insert_readdir_results(MetaRequest *request, MetaSession *session,
 	  // keep existing dn
 	  dn = olddn;
 	  touch_dn(dn);
-	  dn->item_dentry_list.move_to_back();
 	}
       } else {
 	// new dn
 	dn = link(dir, dname, in, NULL);
       }
-      update_dentry_lease(dn, &dlease, request->sent_stamp, session);
-      dn->offset = dir_result_t::make_fpos(fg, i + readdir_offset);
 
+      update_dentry_lease(dn, &dlease, request->sent_stamp, session);
+      if (hash_order) {
+	unsigned hash = ceph_frag_value(diri->hash_dentry_name(dname));
+	if (hash != last_hash)
+	  readdir_offset = 2;
+	last_hash = hash;
+	dn->offset = dir_result_t::make_fpos(hash, readdir_offset++, true);
+      } else {
+	dn->offset = dir_result_t::make_fpos(fg, readdir_offset++, false);
+      }
+      // add to readdir cache
+      if (dirp->release_count == diri->dir_release_count &&
+	  dirp->ordered_count == diri->dir_ordered_count &&
+	  dirp->start_shared_gen == diri->shared_gen) {
+	if (dirp->cache_index == dir->readdir_cache.size()) {
+	  if (i == 0) {
+	    assert(!dirp->inode->is_complete_and_ordered());
+	    dir->readdir_cache.reserve(dirp->cache_index + numdn);
+	  }
+	  dir->readdir_cache.push_back(dn);
+	} else if (dirp->cache_index < dir->readdir_cache.size()) {
+	  if (dirp->inode->is_complete_and_ordered())
+	    assert(dir->readdir_cache[dirp->cache_index] == dn);
+	  else
+	    dir->readdir_cache[dirp->cache_index] = dn;
+	} else {
+	  assert(0 == "unexpected readdir buffer idx");
+	}
+	dirp->cache_index++;
+      }
       // add to cached result list
-      request->readdir_result.push_back(pair<string,InodeRef>(dname, in));
-
+      dirp->buffer.push_back(dir_result_t::dentry(dn->offset, dname, in));
       ldout(cct, 15) << __func__ << "  " << hex << dn->offset << dec << ": '" << dname << "' -> " << in->ino << dendl;
     }
-    request->readdir_last_name = dname;
+
+    if (numdn > 0)
+      dirp->last_name = dname;
+    if (end)
+      dirp->next_offset = 2;
+    else
+      dirp->next_offset = readdir_offset;
 
     if (dir->is_empty())
       close_dir(dir);
@@ -1200,12 +1258,10 @@ Inode* Client::insert_trace(MetaRequest *request, MetaSession *session)
     ldout(cct, 10) << "insert_trace -- no trace" << dendl;
 
     Dentry *d = request->dentry();
-    if (d && d->dir) {
-      d->dir->release_count++;
-      if (d->dir->parent_inode->flags & I_COMPLETE) {
-	ldout(cct, 10) << " clearing (I_COMPLETE|I_DIR_ORDERED) on " << *d->dir->parent_inode << dendl;
-	d->dir->parent_inode->flags &= ~(I_COMPLETE | I_DIR_ORDERED);
-      }
+    if (d) {
+      Inode *diri = d->dir->parent_inode;
+      diri->dir_release_count++;
+      clear_dir_complete_and_ordered(diri, true);
     }
 
     if (d && reply->get_result() == 0) {
@@ -1283,11 +1339,8 @@ Inode* Client::insert_trace(MetaRequest *request, MetaSession *session)
       if (diri->dir && diri->dir->dentries.count(dname)) {
 	Dentry *dn = diri->dir->dentries[dname];
 	if (dn->inode) {
-	  diri->dir->ordered_count++;
-	  if (diri->flags & I_DIR_ORDERED) {
-	    ldout(cct, 10) << " clearing I_DIR_ORDERED on " << *diri << dendl;
-	    diri->flags &= ~I_DIR_ORDERED;
-	  }
+	  diri->dir_ordered_count++;
+	  clear_dir_complete_and_ordered(diri, false);
 	  unlink(dn, true, true);  // keep dir, dentry
 	}
       }
@@ -2836,7 +2889,6 @@ Dentry* Client::link(Dir *dir, const string& name, Inode *in, Dentry *dn)
     // link to dir
     dn->dir = dir;
     dir->dentries[dn->name] = dn;
-    dir->dentry_list.push_back(&dn->item_dentry_list);
     lru.lru_insert_mid(dn);    // mid or top?
 
     ldout(cct, 15) << "link dir " << dir->parent_inode << " '" << name << "' to inode " << in
@@ -2844,7 +2896,6 @@ Dentry* Client::link(Dir *dir, const string& name, Inode *in, Dentry *dn)
   } else {
     ldout(cct, 15) << "link dir " << dir->parent_inode << " '" << name << "' to inode " << in
 		   << " dn " << dn << " (old dn)" << dendl;
-    dn->item_dentry_list.move_to_back();
   }
 
   if (in) {    // link to inode
@@ -2862,6 +2913,9 @@ Dentry* Client::link(Dir *dir, const string& name, Inode *in, Dentry *dn)
     if (in->is_dir() && !in->dn_set.empty()) {
       Dentry *olddn = in->get_first_parent();
       assert(olddn->dir != dir || olddn->name != name);
+      Inode *old_diri = olddn->dir->parent_inode;
+      old_diri->dir_release_count++;
+      clear_dir_complete_and_ordered(old_diri, true);
       unlink(olddn, true, true);  // keep dir, dentry
     }
 
@@ -2902,7 +2956,6 @@ void Client::unlink(Dentry *dn, bool keepdir, bool keepdentry)
 
     // unlink from dir
     dn->dir->dentries.erase(dn->name);
-    dn->item_dentry_list.remove_myself();
     if (dn->dir->is_empty() && !keepdir)
       close_dir(dn->dir);
     dn->dir = 0;
@@ -3659,10 +3712,8 @@ void Client::check_cap_issue(Inode *in, Cap *cap, unsigned issued)
       !(had & CEPH_CAP_FILE_SHARED)) {
     in->shared_gen++;
 
-    if (in->is_dir() && (in->flags & I_COMPLETE)) {
-      ldout(cct, 10) << " clearing (I_COMPLETE|I_DIR_ORDERED) on " << *in << dendl;
-      in->flags &= ~(I_COMPLETE | I_DIR_ORDERED);
-    }
+    if (in->is_dir())
+      clear_dir_complete_and_ordered(in, true);
   }
 }
 
@@ -4724,10 +4775,10 @@ void Client::_try_to_trim_inode(Inode *in)
 {
   int ref = in->get_num_ref();
 
-  if (in->dir && !in->dir->dentry_list.empty()) {
-    for (xlist<Dentry*>::iterator p = in->dir->dentry_list.begin();
-	!p.end(); ) {
-      Dentry *dn = *p;
+  if (in->dir && !in->dir->dentries.empty()) {
+    for (auto p = in->dir->dentries.begin();
+	 p != in->dir->dentries.end(); ) {
+      Dentry *dn = p->second;
       ++p;
       if (dn->lru_is_expireable())
 	unlink(dn, false, false);  // close dir, drop dentry
@@ -6780,14 +6831,8 @@ int Client::_opendir(Inode *in, dir_result_t **dirpp, int uid, int gid)
     return -ENOTDIR;
   *dirpp = new dir_result_t(in);
   opened_dirs.insert(*dirpp);
-  if (in->dir) {
-    (*dirpp)->release_count = in->dir->release_count;
-    (*dirpp)->ordered_count = in->dir->ordered_count;
-  }
-  (*dirpp)->start_shared_gen = in->shared_gen;
   (*dirpp)->owner_uid = uid;
   (*dirpp)->owner_gid = gid;
-  ldout(cct, 10) << "_opendir " << in->ino << ", our cache says the first dirfrag is " << (*dirpp)->frag() << dendl;
   ldout(cct, 3) << "_opendir(" << in->ino << ") = " << 0 << " (" << *dirpp << ")" << dendl;
   return 0;
 }
@@ -6838,25 +6883,31 @@ void Client::seekdir(dir_result_t *dirp, loff_t offset)
   Mutex::Locker lock(client_lock);
 
   ldout(cct, 3) << "seekdir(" << dirp << ", " << offset << ")" << dendl;
-  dir_result_t *d = static_cast<dir_result_t*>(dirp);
-
-  if (offset == 0 ||
-      dir_result_t::fpos_frag(offset) != d->frag() ||
-      dir_result_t::fpos_off(offset) < d->fragpos()) {
-    _readdir_drop_dirp_buffer(d);
-    d->reset();
-  }
-
-  if (offset > d->offset)
-    d->release_count--;   // bump if we do a forward seek
 
-  d->offset = offset;
-  if (!d->frag().is_leftmost() && d->next_offset == 2)
-    d->next_offset = 0;  // not 2 on non-leftmost frags!
-}
+  if (offset == dirp->offset)
+    return;
 
+  if (offset > dirp->offset)
+    dirp->release_count = 0;   // bump if we do a forward seek
+  else
+    dirp->ordered_count = 0;   // disable filling readdir cache
 
+  if (dirp->hash_order()) {
+    if (dirp->offset > offset) {
+      _readdir_drop_dirp_buffer(dirp);
+      dirp->reset();
+    }
+  } else {
+    if (offset == 0 ||
+	dirp->buffer_frag != frag_t(dir_result_t::fpos_high(offset)) ||
+	dirp->offset_low() > dir_result_t::fpos_low(offset))  {
+      _readdir_drop_dirp_buffer(dirp);
+      dirp->reset();
+    }
+  }
 
+  dirp->offset = offset;
+}
 
 
 //struct dirent {
@@ -6884,14 +6935,26 @@ void Client::fill_dirent(struct dirent *de, const char *name, int type, uint64_t
 
 void Client::_readdir_next_frag(dir_result_t *dirp)
 {
-  frag_t fg = dirp->frag();
+  frag_t fg = dirp->buffer_frag;
 
-  // advance
-  dirp->next_frag();
-  if (dirp->at_end()) {
+  if (fg.is_rightmost()) {
     ldout(cct, 10) << "_readdir_next_frag advance from " << fg << " to END" << dendl;
+    dirp->set_end();
+    return;
+  }
+
+  // advance
+  fg = fg.next();
+  ldout(cct, 10) << "_readdir_next_frag advance from " << dirp->buffer_frag << " to " << fg << dendl;
+
+  if (dirp->hash_order()) {
+    // keep last_name
+    int64_t new_offset = dir_result_t::make_fpos(fg.value(), 2, true);
+    if (dirp->offset < new_offset) // don't decrease offset
+      dirp->offset = new_offset;
   } else {
-    ldout(cct, 10) << "_readdir_next_frag advance from " << fg << " to " << dirp->frag() << dendl;
+    dirp->last_name.clear();
+    dirp->offset = dir_result_t::make_fpos(fg, 2, false);
     _readdir_rechoose_frag(dirp);
   }
 }
@@ -6899,21 +6962,24 @@ void Client::_readdir_next_frag(dir_result_t *dirp)
 void Client::_readdir_rechoose_frag(dir_result_t *dirp)
 {
   assert(dirp->inode);
-  frag_t cur = dirp->frag();
-  frag_t f = dirp->inode->dirfragtree[cur.value()];
-  if (f != cur) {
-    ldout(cct, 10) << "_readdir_rechoose_frag frag " << cur << " maps to " << f << dendl;
-    dirp->set_frag(f);
+
+  if (dirp->hash_order())
+    return;
+
+  frag_t cur = frag_t(dirp->offset_high());
+  frag_t fg = dirp->inode->dirfragtree[cur.value()];
+  if (fg != cur) {
+    ldout(cct, 10) << "_readdir_rechoose_frag frag " << cur << " maps to " << fg << dendl;
+    dirp->offset = dir_result_t::make_fpos(fg, 2, false);
+    dirp->last_name.clear();
+    dirp->next_offset = 2;
   }
 }
 
 void Client::_readdir_drop_dirp_buffer(dir_result_t *dirp)
 {
   ldout(cct, 10) << "_readdir_drop_dirp_buffer " << dirp << dendl;
-  if (dirp->buffer) {
-    delete dirp->buffer;
-    dirp->buffer = NULL;
-  }
+  dirp->buffer.clear();
 }
 
 int Client::_readdir_get_frag(dir_result_t *dirp)
@@ -6922,11 +6988,14 @@ int Client::_readdir_get_frag(dir_result_t *dirp)
   assert(dirp->inode);
 
   // get the current frag.
-  frag_t fg = dirp->frag();
+  frag_t fg;
+  if (dirp->hash_order())
+    fg = dirp->inode->dirfragtree[dirp->offset_high()];
+  else
+    fg = frag_t(dirp->offset_high());
   
   ldout(cct, 10) << "_readdir_get_frag " << dirp << " on " << dirp->inode->ino << " fg " << fg
-	   << " next_offset " << dirp->next_offset
-	   << dendl;
+		 << " offset " << hex << dirp->offset << dendl;
 
   int op = CEPH_MDS_OP_READDIR;
   if (dirp->inode && dirp->inode->snapid == CEPH_SNAPDIR)
@@ -6940,13 +7009,11 @@ int Client::_readdir_get_frag(dir_result_t *dirp)
   req->set_filepath(path); 
   req->set_inode(diri.get());
   req->head.args.readdir.frag = fg;
+  req->head.args.readdir.flags = CEPH_READDIR_REPLY_BITFLAGS;
   if (dirp->last_name.length()) {
     req->path2.set_path(dirp->last_name.c_str());
-    req->readdir_start = dirp->last_name;
   }
-  req->readdir_offset = dirp->next_offset;
-  req->readdir_frag = fg;
-  
+  req->dirp = dirp;
   
   bufferlist dirbl;
   int res = make_request(req, dirp->owner_uid, dirp->owner_gid, NULL, NULL, -1, &dirbl);
@@ -6958,38 +7025,8 @@ int Client::_readdir_get_frag(dir_result_t *dirp)
   }
 
   if (res == 0) {
-    // stuff dir contents to cache, dir_result_t
-    assert(diri);
-
-    _readdir_drop_dirp_buffer(dirp);
-
-    dirp->buffer = new vector<pair<string,InodeRef> >;
-    dirp->buffer->swap(req->readdir_result);
-
-    if (fg != req->readdir_reply_frag) {
-      fg = req->readdir_reply_frag;
-      if (fg.is_leftmost())
-	dirp->next_offset = 2;
-      else
-	dirp->next_offset = 0;
-      dirp->offset = dir_result_t::make_fpos(fg, dirp->next_offset);
-    }
-    dirp->buffer_frag = fg;
-    dirp->this_offset = dirp->next_offset;
     ldout(cct, 10) << "_readdir_get_frag " << dirp << " got frag " << dirp->buffer_frag
-	     << " this_offset " << dirp->this_offset
-	     << " size " << dirp->buffer->size() << dendl;
-
-    if (req->readdir_end) {
-      dirp->last_name.clear();
-      if (fg.is_rightmost())
-	dirp->next_offset = 2;
-      else
-	dirp->next_offset = 0;
-    } else {
-      dirp->last_name = req->readdir_last_name;
-      dirp->next_offset += req->readdir_num;
-    }
+		   << " size " << dirp->buffer.size() << dendl;
   } else {
     ldout(cct, 10) << "_readdir_get_frag got error " << res << ", setting end flag" << dendl;
     dirp->set_end();
@@ -6998,11 +7035,17 @@ int Client::_readdir_get_frag(dir_result_t *dirp)
   return res;
 }
 
+struct dentry_off_lt {
+  bool operator()(const Dentry* dn, int64_t off) const {
+    return dir_result_t::fpos_cmp(dn->offset, off) < 0;
+  }
+};
+
 int Client::_readdir_cache_cb(dir_result_t *dirp, add_dirent_cb_t cb, void *p)
 {
   assert(client_lock.is_locked());
   ldout(cct, 10) << "_readdir_cache_cb " << dirp << " on " << dirp->inode->ino
-	   << " at_cache_name " << dirp->at_cache_name << " offset " << hex << dirp->offset << dec
+	   << " last_name " << dirp->last_name << " offset " << hex << dirp->offset << dec
 	   << dendl;
   Dir *dir = dirp->inode->dir;
 
@@ -7012,21 +7055,15 @@ int Client::_readdir_cache_cb(dir_result_t *dirp, add_dirent_cb_t cb, void *p)
     return 0;
   }
 
-  xlist<Dentry*>::iterator pd = dir->dentry_list.begin();
-  if (dirp->at_cache_name.length()) {
-    ceph::unordered_map<string,Dentry*>::iterator it = dir->dentries.find(dirp->at_cache_name);
-    if (it == dir->dentries.end())
-      return -EAGAIN;
-    Dentry *dn = it->second;
-    pd = xlist<Dentry*>::iterator(&dn->item_dentry_list);
-    ++pd;
-  }
+  vector<Dentry*>::iterator pd = std::lower_bound(dir->readdir_cache.begin(),
+						  dir->readdir_cache.end(),
+						  dirp->offset, dentry_off_lt());
 
   string dn_name;
   while (true) {
     if (!dirp->inode->is_complete_and_ordered())
       return -EAGAIN;
-    if (pd.end())
+    if (pd == dir->readdir_cache.end())
       break;
     Dentry *dn = *pd;
     if (dn->inode == NULL) {
@@ -7043,28 +7080,31 @@ int Client::_readdir_cache_cb(dir_result_t *dirp, add_dirent_cb_t cb, void *p)
     struct stat st;
     struct dirent de;
     int stmask = fill_stat(dn->inode, &st);
-    fill_dirent(&de, dn->name.c_str(), st.st_mode, st.st_ino, dirp->offset + 1);
-      
+
     uint64_t next_off = dn->offset + 1;
     ++pd;
-    if (pd.end())
+    if (pd == dir->readdir_cache.end())
       next_off = dir_result_t::END;
 
+    fill_dirent(&de, dn->name.c_str(), st.st_mode, st.st_ino, next_off);
+
     dn_name = dn->name; // fill in name while we have lock
 
     client_lock.Unlock();
     int r = cb(p, &de, &st, stmask, next_off);  // _next_ offset
     client_lock.Lock();
     ldout(cct, 15) << " de " << de.d_name << " off " << hex << dn->offset << dec
-	     << " = " << r
-	     << dendl;
+		   << " = " << r << dendl;
     if (r < 0) {
-      dirp->next_offset = next_off - 1;
       return r;
     }
 
-    dirp->next_offset = dirp->offset = next_off;
-    dirp->at_cache_name = dn_name; // we successfully returned this one; update!
+    dirp->offset = next_off;
+    if (dirp->at_end())
+      dirp->next_offset = 2;
+    else
+      dirp->next_offset = dirp->offset_low();
+    dirp->last_name = dn_name; // we successfully returned this one; update!
     if (r > 0)
       return r;
   }
@@ -7080,19 +7120,15 @@ int Client::readdir_r_cb(dir_result_t *d, add_dirent_cb_t cb, void *p)
 
   dir_result_t *dirp = static_cast<dir_result_t*>(d);
 
-  ldout(cct, 10) << "readdir_r_cb " << *dirp->inode << " offset " << hex << dirp->offset << dec
-	   << " frag " << dirp->frag() << " fragpos " << hex << dirp->fragpos() << dec
-	   << " at_end=" << dirp->at_end()
-	   << dendl;
+  ldout(cct, 10) << "readdir_r_cb " << *dirp->inode << " offset " << hex << dirp->offset
+		 << dec << " at_end=" << dirp->at_end()
+		 << " hash_order=" << dirp->hash_order() << dendl;
 
   struct dirent de;
   struct stat st;
   memset(&de, 0, sizeof(de));
   memset(&st, 0, sizeof(st));
 
-  frag_t fg = dirp->frag();
-  uint32_t off = dirp->fragpos();
-
   InodeRef& diri = dirp->inode;
 
   if (dirp->at_end())
@@ -7113,95 +7149,89 @@ int Client::readdir_r_cb(dir_result_t *d, add_dirent_cb_t cb, void *p)
       return r;
 
     dirp->offset = next_off;
-    off = next_off;
     if (r > 0)
       return r;
   }
   if (dirp->offset == 1) {
     ldout(cct, 15) << " including .." << dendl;
+    uint64_t next_off = 2;
     if (!diri->dn_set.empty()) {
       InodeRef& in = diri->get_first_parent()->inode;
       fill_stat(in, &st);
-      fill_dirent(&de, "..", S_IFDIR, st.st_ino, 2);
+      fill_dirent(&de, "..", S_IFDIR, st.st_ino, next_off);
     } else {
       /* must be at the root (no parent),
        * so we add the dotdot with a special inode (3) */
-      fill_dirent(&de, "..", S_IFDIR, CEPH_INO_DOTDOT, 2);
+      fill_dirent(&de, "..", S_IFDIR, CEPH_INO_DOTDOT, next_off);
     }
 
 
     client_lock.Unlock();
-    int r = cb(p, &de, &st, -1, 2);
+    int r = cb(p, &de, &st, -1, next_off);
     client_lock.Lock();
     if (r < 0)
       return r;
 
-    dirp->offset = 2;
-    off = 2;
+    dirp->offset = next_off;
     if (r > 0)
       return r;
   }
 
   // can we read from our cache?
-  ldout(cct, 10) << "offset " << hex << dirp->offset << dec << " at_cache_name " << dirp->at_cache_name
+  ldout(cct, 10) << "offset " << hex << dirp->offset
 	   << " snapid " << dirp->inode->snapid << " (complete && ordered) "
 	   << dirp->inode->is_complete_and_ordered()
 	   << " issued " << ccap_string(dirp->inode->caps_issued())
 	   << dendl;
-  if ((dirp->offset == 2 || dirp->at_cache_name.length()) &&
-      dirp->inode->snapid != CEPH_SNAPDIR &&
+  if (dirp->inode->snapid != CEPH_SNAPDIR &&
       dirp->inode->is_complete_and_ordered() &&
       dirp->inode->caps_issued_mask(CEPH_CAP_FILE_SHARED)) {
     int err = _readdir_cache_cb(dirp, cb, p);
     if (err != -EAGAIN)
       return err;
   }
-  if (dirp->at_cache_name.length()) {
-    dirp->last_name = dirp->at_cache_name;
-    dirp->at_cache_name.clear();
-  }
 
   while (1) {
     if (dirp->at_end())
       return 0;
 
-    if (dirp->buffer_frag != dirp->frag() || dirp->buffer == NULL) {
+    if (!dirp->is_cached()) {
       int r = _readdir_get_frag(dirp);
       if (r)
 	return r;
       // _readdir_get_frag () may updates dirp->offset if the replied dirfrag is
       // different than the requested one. (our dirfragtree was outdated)
-      fg = dirp->buffer_frag;
-      off = dirp->fragpos();
     }
+    frag_t fg = dirp->buffer_frag;
 
-    ldout(cct, 10) << "off " << off << " this_offset " << hex << dirp->this_offset << dec << " size " << dirp->buffer->size()
-	     << " frag " << fg << dendl;
+    ldout(cct, 10) << "frag " << fg << " buffer size " << dirp->buffer.size()
+		   << " offset " << hex << dirp->offset << dendl;
 
-    dirp->offset = dir_result_t::make_fpos(fg, off);
-    while (off >= dirp->this_offset &&
-	   off - dirp->this_offset < dirp->buffer->size()) {
-      pair<string,InodeRef>& ent = (*dirp->buffer)[off - dirp->this_offset];
+    for (auto it = std::lower_bound(dirp->buffer.begin(), dirp->buffer.end(),
+				    dirp->offset, dir_result_t::dentry_off_lt());
+	 it != dirp->buffer.end();
+	 ++it) {
+      dir_result_t::dentry &entry = *it;
+
+      uint64_t next_off = entry.offset + 1;
+      int stmask = fill_stat(entry.inode, &st);
+      fill_dirent(&de, entry.name.c_str(), st.st_mode, st.st_ino, next_off);
 
-      int stmask = fill_stat(ent.second, &st);
-      fill_dirent(&de, ent.first.c_str(), st.st_mode, st.st_ino, dirp->offset + 1);
-      
       client_lock.Unlock();
-      int r = cb(p, &de, &st, stmask, dirp->offset + 1);  // _next_ offset
+      int r = cb(p, &de, &st, stmask, next_off);  // _next_ offset
       client_lock.Lock();
-      ldout(cct, 15) << " de " << de.d_name << " off " << hex << dirp->offset << dec
-	       << " = " << r
-	       << dendl;
+
+      ldout(cct, 15) << " de " << de.d_name << " off " << hex << next_off - 1 << dec
+		     << " = " << r << dendl;
       if (r < 0)
 	return r;
-      
-      off++;
-      dirp->offset++;
+
+      dirp->offset = next_off;
       if (r > 0)
 	return r;
     }
 
-    if (dirp->last_name.length()) {
+    if (dirp->next_offset > 2) {
       ldout(cct, 10) << " fetching next chunk of this frag" << dendl;
       _readdir_drop_dirp_buffer(dirp);
       continue;  // more!
@@ -7210,17 +7240,17 @@ int Client::readdir_r_cb(dir_result_t *d, add_dirent_cb_t cb, void *p)
     if (!fg.is_rightmost()) {
       // next frag!
       _readdir_next_frag(dirp);
-      ldout(cct, 10) << " advancing to next frag: " << fg << " -> " << dirp->frag() << dendl;
-      fg = dirp->frag();
-      off = 0;
       continue;
     }
 
-    if (diri->dir &&
-	diri->shared_gen == dirp->start_shared_gen &&
-	diri->dir->release_count == dirp->release_count) {
-      if (diri->dir->ordered_count == dirp->ordered_count) {
+    if (diri->shared_gen == dirp->start_shared_gen &&
+	diri->dir_release_count == dirp->release_count) {
+      if (diri->dir_ordered_count == dirp->ordered_count) {
 	ldout(cct, 10) << " marking (I_COMPLETE|I_DIR_ORDERED) on " << *diri << dendl;
+	if (diri->dir) {
+	  assert(diri->dir->readdir_cache.size() >= dirp->cache_index);
+	  diri->dir->readdir_cache.resize(dirp->cache_index);
+	}
 	diri->flags |= I_COMPLETE | I_DIR_ORDERED;
       } else {
 	ldout(cct, 10) << " marking I_COMPLETE on " << *diri << dendl;
@@ -7635,7 +7665,6 @@ Fh *Client::_create_fh(Inode *in, int flags, int cmode)
   }
 
   const md_config_t *conf = cct->_conf;
-  loff_t p = in->layout.stripe_count * in->layout.object_size;
   f->readahead.set_trigger_requests(1);
   f->readahead.set_min_readahead_size(conf->client_readahead_min);
   uint64_t max_readahead = Readahead::NO_LIMIT;
@@ -7643,11 +7672,11 @@ Fh *Client::_create_fh(Inode *in, int flags, int cmode)
     max_readahead = MIN(max_readahead, (uint64_t)conf->client_readahead_max_bytes);
   }
   if (conf->client_readahead_max_periods) {
-    max_readahead = MIN(max_readahead, ((uint64_t)conf->client_readahead_max_periods) * p);
+    max_readahead = MIN(max_readahead, in->layout.get_period()*(uint64_t)conf->client_readahead_max_periods);
   }
   f->readahead.set_max_readahead_size(max_readahead);
   vector<uint64_t> alignments;
-  alignments.push_back(p);
+  alignments.push_back(in->layout.get_period());
   alignments.push_back(in->layout.stripe_unit);
   f->readahead.set_alignments(alignments);
 
@@ -8054,15 +8083,19 @@ done:
 }
 
 Client::C_Readahead::C_Readahead(Client *c, Fh *f) :
-  client(c), f(f) {
-    f->get();
+    client(c), f(f) {
+  f->get();
+  f->readahead.inc_pending();
+}
+
+Client::C_Readahead::~C_Readahead() {
+  f->readahead.dec_pending();
+  client->_put_fh(f);
 }
 
 void Client::C_Readahead::finish(int r) {
   lgeneric_subdout(client->cct, client, 20) << "client." << client->get_nodeid() << " " << "C_Readahead on " << f->inode << dendl;
   client->put_cap_ref(f->inode.get(), CEPH_CAP_FILE_RD | CEPH_CAP_FILE_CACHE);
-  f->readahead.dec_pending();
-  client->_put_fh(f);
 }
 
 int Client::_read_async(Fh *f, uint64_t off, uint64_t len, bufferlist *bl)
@@ -8081,8 +8114,9 @@ int Client::_read_async(Fh *f, uint64_t off, uint64_t len, bufferlist *bl)
     len = in->size - off;    
   }
 
-  ldout(cct, 10) << " max_bytes=" << conf->client_readahead_max_bytes
-		 << " max_periods=" << conf->client_readahead_max_periods << dendl;
+  ldout(cct, 10) << " min_bytes=" << f->readahead.get_min_readahead_size()
+                 << " max_bytes=" << f->readahead.get_max_readahead_size()
+                 << " max_periods=" << conf->client_readahead_max_periods << dendl;
 
   // read (and possibly block)
   int r, rvalue = 0;
@@ -8107,13 +8141,12 @@ int Client::_read_async(Fh *f, uint64_t off, uint64_t len, bufferlist *bl)
     delete onfinish;
   }
 
-  if(conf->client_readahead_max_bytes > 0) {
+  if(f->readahead.get_min_readahead_size() > 0) {
     pair<uint64_t, uint64_t> readahead_extent = f->readahead.update(off, len, in->size);
     if (readahead_extent.second > 0) {
       ldout(cct, 20) << "readahead " << readahead_extent.first << "~" << readahead_extent.second
 		     << " (caller wants " << off << "~" << len << ")" << dendl;
       Context *onfinish2 = new C_Readahead(this, f);
-      f->readahead.inc_pending();
       int r2 = objectcacher->file_read(&in->oset, &in->layout, in->snapid,
 				       readahead_extent.first, readahead_extent.second,
 				       NULL, 0, onfinish2);
@@ -8121,7 +8154,6 @@ int Client::_read_async(Fh *f, uint64_t off, uint64_t len, bufferlist *bl)
 	ldout(cct, 20) << "readahead initiated, c " << onfinish2 << dendl;
 	get_cap_ref(in, CEPH_CAP_FILE_RD | CEPH_CAP_FILE_CACHE);
       } else {
-	f->readahead.dec_pending();
 	ldout(cct, 20) << "readahead was no-op, already cached" << dendl;
 	delete onfinish2;
       }
@@ -8752,19 +8784,11 @@ int Client::statfs(const char *path, struct statvfs *stbuf)
   tout(cct) << "statfs" << std::endl;
 
   ceph_statfs stats;
-
-  Mutex lock("Client::statfs::lock");
-  Cond cond;
-  bool done;
-  int rval;
-
-  objecter->get_fs_stats(stats, new C_SafeCond(&lock, &cond, &done, &rval));
+  C_SaferCond cond;
+  objecter->get_fs_stats(stats, &cond);
 
   client_lock.Unlock();
-  lock.Lock();
-  while (!done)
-    cond.Wait(lock);
-  lock.Unlock();
+  int rval = cond.wait();
   client_lock.Lock();
 
   memset(stbuf, 0, sizeof(*stbuf));
@@ -8779,9 +8803,6 @@ int Client::statfs(const char *path, struct statvfs *stbuf)
   const int CEPH_BLOCK_SHIFT = 22;
   stbuf->f_frsize = 1 << CEPH_BLOCK_SHIFT;
   stbuf->f_bsize = 1 << CEPH_BLOCK_SHIFT;
-  stbuf->f_blocks = stats.kb >> (CEPH_BLOCK_SHIFT - 10);
-  stbuf->f_bfree = stats.kb_avail >> (CEPH_BLOCK_SHIFT - 10);
-  stbuf->f_bavail = stats.kb_avail >> (CEPH_BLOCK_SHIFT - 10);
   stbuf->f_files = stats.num_objects;
   stbuf->f_ffree = -1;
   stbuf->f_favail = -1;
@@ -8789,6 +8810,37 @@ int Client::statfs(const char *path, struct statvfs *stbuf)
   stbuf->f_flag = 0;        // ??
   stbuf->f_namemax = NAME_MAX;
 
+  // Usually quota_root will == root_ancestor, but if the mount root has no
+  // quota but we can see a parent of it that does have a quota, we'll
+  // respect that one instead.
+  assert(root != nullptr);
+  Inode *quota_root = get_quota_root(root);
+
+  // get_quota_root should always give us something if client quotas are
+  // enabled
+  assert(cct->_conf->client_quota == false || quota_root != nullptr);
+
+  if (quota_root && cct->_conf->client_quota_df && quota_root->quota.max_bytes) {
+    // Special case: if there is a size quota set on the Inode acting
+    // as the root for this client mount, then report the quota status
+    // as the filesystem statistics.
+    const fsblkcnt_t total = quota_root->quota.max_bytes >> CEPH_BLOCK_SHIFT;
+    const fsblkcnt_t used = quota_root->rstat.rbytes >> CEPH_BLOCK_SHIFT;
+    const fsblkcnt_t free = total - used;
+
+
+    stbuf->f_blocks = total;
+    stbuf->f_bfree = free;
+    stbuf->f_bavail = free;
+  } else {
+    // General case: report the overall RADOS cluster's statistics.  Because
+    // multiple pools may be used without one filesystem namespace via
+    // layouts, this is the most correct thing we can do.
+    stbuf->f_blocks = stats.kb >> (CEPH_BLOCK_SHIFT - 10);
+    stbuf->f_bfree = stats.kb_avail >> (CEPH_BLOCK_SHIFT - 10);
+    stbuf->f_bavail = stats.kb_avail >> (CEPH_BLOCK_SHIFT - 10);
+  }
+
   return rval;
 }
 
diff --git a/src/client/Client.h b/src/client/Client.h
index d912db0..647a122 100644
--- a/src/client/Client.h
+++ b/src/client/Client.h
@@ -161,65 +161,98 @@ struct client_callback_args {
 struct dir_result_t {
   static const int SHIFT = 28;
   static const int64_t MASK = (1 << SHIFT) - 1;
+  static const int64_t HASH = 0xFFULL << (SHIFT + 24); // impossible frag bits
   static const loff_t END = 1ULL << (SHIFT + 32);
 
-  static uint64_t make_fpos(unsigned frag, unsigned off) {
-    return ((uint64_t)frag << SHIFT) | (uint64_t)off;
+  static uint64_t make_fpos(unsigned h, unsigned l, bool hash) {
+    uint64_t v =  ((uint64_t)h<< SHIFT) | (uint64_t)l;
+    if (hash)
+      v |= HASH;
+    else
+      assert((v & HASH) != HASH);
+    return v;
   }
-  static unsigned fpos_frag(uint64_t p) {
-    return (p & ~END) >> SHIFT;
+  static unsigned fpos_high(uint64_t p) {
+    unsigned v = (p & (END-1)) >> SHIFT;
+    if ((p & HASH) == HASH)
+      return ceph_frag_value(v);
+    return v;
   }
-  static unsigned fpos_off(uint64_t p) {
+  static unsigned fpos_low(uint64_t p) {
     return p & MASK;
   }
-
+  static int fpos_cmp(uint64_t l, uint64_t r) {
+    int c = ceph_frag_compare(fpos_high(l), fpos_high(r));
+    if (c)
+      return c;
+    if (fpos_low(l) == fpos_low(r))
+      return 0;
+    return fpos_low(l) < fpos_low(r) ? -1 : 1;
+  }
 
   InodeRef inode;
   int owner_uid;
   int owner_gid;
 
-  int64_t offset;        // high bits: frag_t, low bits: an offset
+  int64_t offset;        // hash order:
+			 //   (0xff << 52) | ((24 bits hash) << 28) |
+			 //   (the nth entry has hash collision);
+			 // frag+name order;
+			 //   ((frag value) << 28) | (the nth entry in frag);
 
-  uint64_t this_offset;  // offset of last chunk, adjusted for . and ..
-  uint64_t next_offset;  // offset of next chunk (last_name's + 1)
+  unsigned next_offset;  // offset of next chunk (last_name's + 1)
   string last_name;      // last entry in previous chunk
 
   uint64_t release_count;
   uint64_t ordered_count;
+  unsigned cache_index;
   int start_shared_gen;  // dir shared_gen at start of readdir
 
   frag_t buffer_frag;
-  vector<pair<string,InodeRef> > *buffer;
 
-  string at_cache_name;  // last entry we successfully returned
+  struct dentry {
+    int64_t offset;
+    string name;
+    InodeRef inode;
+    dentry(int64_t o) : offset(o) {}
+    dentry(int64_t o, const string& n, const InodeRef& in) :
+      offset(o), name(n), inode(in) {}
+  };
+  struct dentry_off_lt {
+    bool operator()(const dentry& d, int64_t off) const {
+      return dir_result_t::fpos_cmp(d.offset, off) < 0;
+    }
+  };
+  vector<dentry> buffer;
 
   explicit dir_result_t(Inode *in);
 
-  frag_t frag() { return frag_t(offset >> SHIFT); }
-  unsigned fragpos() { return offset & MASK; }
+  unsigned offset_high() { return fpos_high(offset); }
+  unsigned offset_low() { return fpos_low(offset); }
 
-  void next_frag() {
-    frag_t fg = offset >> SHIFT;
-    if (fg.is_rightmost())
-      set_end();
-    else 
-      set_frag(fg.next());
-  }
-  void set_frag(frag_t f) {
-    offset = (uint64_t)f << SHIFT;
-    assert(sizeof(offset) == 8);
-  }
   void set_end() { offset |= END; }
   bool at_end() { return (offset & END); }
 
+  void set_hash_order() { offset |= HASH; }
+  bool hash_order() { return (offset & HASH) == HASH; }
+
+  bool is_cached() {
+    if (buffer.empty())
+      return false;
+    if (hash_order()) {
+      return buffer_frag.contains(offset_high());
+    } else {
+      return buffer_frag == frag_t(offset_high());
+    }
+  }
+
   void reset() {
     last_name.clear();
-    at_cache_name.clear();
     next_offset = 2;
-    this_offset = 0;
     offset = 0;
-    delete buffer;
-    buffer = 0;
+    ordered_count = 0;
+    cache_index = 0;
+    buffer.clear();
   }
 };
 
@@ -663,6 +696,7 @@ protected:
   // metadata cache
   void update_dir_dist(Inode *in, DirStat *st);
 
+  void clear_dir_complete_and_ordered(Inode *diri, bool complete);
   void insert_readdir_results(MetaRequest *request, MetaSession *session, Inode *diri);
   Inode* insert_trace(MetaRequest *request, MetaSession *session);
   void update_inode_file_bits(Inode *in,
@@ -713,6 +747,7 @@ private:
     Client *client;
     Fh *f;
     C_Readahead(Client *c, Fh *f);
+    ~C_Readahead();
     void finish(int r);
   };
 
diff --git a/src/client/Dentry.h b/src/client/Dentry.h
index 198b375..c218195 100644
--- a/src/client/Dentry.h
+++ b/src/client/Dentry.h
@@ -17,15 +17,13 @@ class Dentry : public LRUObject {
   Dir	   *dir;
   InodeRef inode;
   int	   ref;                       // 1 if there's a dir beneath me.
-  uint64_t offset;
+  int64_t offset;
   mds_rank_t lease_mds;
   utime_t lease_ttl;
   uint64_t lease_gen;
   ceph_seq_t lease_seq;
   int cap_shared_gen;
 
-  xlist<Dentry*>::item item_dentry_list;
-
   /*
    * ref==1 -> cached, unused
    * ref >1 -> pinned in lru
@@ -49,8 +47,8 @@ class Dentry : public LRUObject {
 
   Dentry() :
     dir(0), ref(1), offset(0),
-    lease_mds(-1), lease_gen(0), lease_seq(0), cap_shared_gen(0),
-    item_dentry_list(this)  { }
+    lease_mds(-1), lease_gen(0), lease_seq(0), cap_shared_gen(0)
+  { }
 private:
   ~Dentry() {
     assert(ref == 0);
diff --git a/src/client/Dir.h b/src/client/Dir.h
index a7f484d..e6d7f99 100644
--- a/src/client/Dir.h
+++ b/src/client/Dir.h
@@ -7,11 +7,9 @@ class Dir {
  public:
   Inode    *parent_inode;  // my inode
   ceph::unordered_map<string, Dentry*> dentries;
-  xlist<Dentry*> dentry_list;
-  uint64_t release_count;
-  uint64_t ordered_count;
+  vector<Dentry*> readdir_cache;
 
-  explicit Dir(Inode* in) : release_count(0), ordered_count(0) { parent_inode = in; }
+  explicit Dir(Inode* in) { parent_inode = in; }
 
   bool is_empty() {  return dentries.empty(); }
 };
diff --git a/src/client/Inode.h b/src/client/Inode.h
index c92b103..969da13 100644
--- a/src/client/Inode.h
+++ b/src/client/Inode.h
@@ -226,8 +226,11 @@ struct Inode {
   }
 
   // about the dir (if this is one!)
+  Dir       *dir;     // if i'm a dir.
+  fragtree_t dirfragtree;
   set<int>  dir_contacts;
-  bool      dir_hashed, dir_replicated;
+  uint64_t dir_release_count, dir_ordered_count;
+  bool dir_hashed, dir_replicated;
 
   // per-mds caps
   map<mds_rank_t, Cap*> caps;            // mds -> Cap
@@ -256,10 +259,8 @@ struct Inode {
 
   int       _ref;      // ref count. 1 for each dentry, fh that links to me.
   int       ll_ref;   // separate ref count for ll client
-  Dir       *dir;     // if i'm a dir.
   set<Dentry*> dn_set;      // if i'm linked to a dentry.
   string    symlink;  // symlink content, if it's a symlink
-  fragtree_t dirfragtree;
   map<string,bufferptr> xattrs;
   map<frag_t,int> fragmap;  // known frag -> mds mappings
 
@@ -300,9 +301,8 @@ struct Inode {
       rdev(0), mode(0), uid(0), gid(0), nlink(0),
       size(0), truncate_seq(1), truncate_size(-1),
       time_warp_seq(0), max_size(0), version(0), xattr_version(0),
-      inline_version(0),
-      flags(0),
-      qtree(NULL),
+      inline_version(0), flags(0), qtree(NULL),
+      dir(0), dir_release_count(1), dir_ordered_count(1),
       dir_hashed(false), dir_replicated(false), auth_cap(NULL),
       cap_dirtier_uid(-1), cap_dirtier_gid(-1),
       dirty_caps(0), flushing_caps(0), shared_gen(0), cache_gen(0),
@@ -311,7 +311,7 @@ struct Inode {
       snaprealm(0), snaprealm_item(this),
       oset((void *)this, newlayout->pool_id, ino),
       reported_size(0), wanted_max_size(0), requested_max_size(0),
-      _ref(0), ll_ref(0), dir(0), dn_set(),
+      _ref(0), ll_ref(0), dn_set(),
       fcntl_locks(NULL), flock_locks(NULL),
       async_err(0)
   {
diff --git a/src/client/MetaRequest.cc b/src/client/MetaRequest.cc
index 1004f50..b6574de 100644
--- a/src/client/MetaRequest.cc
+++ b/src/client/MetaRequest.cc
@@ -37,13 +37,6 @@ void MetaRequest::dump(Formatter *f) const
 
   f->dump_int("got_unsafe", got_unsafe);
 
-  if (head.op == CEPH_MDS_OP_READDIR ||
-      head.op == CEPH_MDS_OP_LSSNAP) {
-    f->dump_stream("readdir_frag") << readdir_frag;
-    f->dump_string("readdir_start", readdir_start);
-    f->dump_unsigned("readdir_offset", readdir_offset);
-  }
-
   f->dump_unsigned("uid", head.caller_uid);
   f->dump_unsigned("gid", head.caller_gid);
 
diff --git a/src/client/MetaRequest.h b/src/client/MetaRequest.h
index 8098a0f..c180de6 100644
--- a/src/client/MetaRequest.h
+++ b/src/client/MetaRequest.h
@@ -19,6 +19,7 @@
 
 class MClientReply;
 class Dentry;
+class dir_result_t;
 
 struct MetaRequest {
 private:
@@ -56,15 +57,7 @@ public:
   bool success;
   
   // readdir result
-  frag_t readdir_frag;
-  string readdir_start;  // starting _after_ this name
-  uint64_t readdir_offset;
-
-  frag_t readdir_reply_frag;
-  vector<pair<string,InodeRef> > readdir_result;
-  bool readdir_end;
-  int readdir_num;
-  string readdir_last_name;
+  dir_result_t *dirp;
 
   //possible responses
   bool got_unsafe;
@@ -93,7 +86,6 @@ public:
     num_fwd(0), retry_attempt(0),
     ref(1), reply(0), 
     kick(false), success(false),
-    readdir_offset(0), readdir_end(false), readdir_num(0),
     got_unsafe(false), item(this), unsafe_item(this),
     unsafe_dir_item(this), unsafe_target_item(this),
     caller_cond(0), dispatch_cond(0) {
diff --git a/src/cls/journal/cls_journal.cc b/src/cls/journal/cls_journal.cc
index 6e7d9d5..2682926 100644
--- a/src/cls/journal/cls_journal.cc
+++ b/src/cls/journal/cls_journal.cc
@@ -224,6 +224,85 @@ int expire_tags(cls_method_context_t hctx, const std::string *skip_client_id) {
   return 0;
 }
 
+int get_client_list_range(cls_method_context_t hctx,
+                          std::set<cls::journal::Client> *clients,
+                          std::string start_after, uint64_t max_return) {
+  std::string last_read;
+  if (!start_after.empty()) {
+    last_read = key_from_client_id(start_after);
+  }
+
+  std::map<std::string, bufferlist> vals;
+  int r = cls_cxx_map_get_vals(hctx, last_read, HEADER_KEY_CLIENT_PREFIX,
+                               max_return, &vals);
+  if (r < 0) {
+    CLS_ERR("failed to retrieve omap values: %s", cpp_strerror(r).c_str());
+    return r;
+  }
+
+  for (std::map<std::string, bufferlist>::iterator it = vals.begin();
+       it != vals.end(); ++it) {
+    try {
+      bufferlist::iterator iter = it->second.begin();
+
+      cls::journal::Client client;
+      ::decode(client, iter);
+      clients->insert(client);
+    } catch (const buffer::error &err) {
+      CLS_ERR("could not decode client '%s': %s", it->first.c_str(),
+              err.what());
+      return -EIO;
+    }
+  }
+
+  return 0;
+}
+
+int find_min_commit_position(cls_method_context_t hctx,
+                             cls::journal::ObjectSetPosition *minset) {
+  int r;
+  bool valid = false;
+  std::string start_after = "";
+  uint64_t tag_tid = 0, entry_tid = 0;
+
+  while (true) {
+    std::set<cls::journal::Client> batch;
+
+    r = get_client_list_range(hctx, &batch, start_after, cls::journal::JOURNAL_MAX_RETURN);
+    if ((r < 0) || batch.empty()) {
+      break;
+    }
+
+    start_after = batch.rbegin()->id;
+
+    // update the (minimum) commit position from this batch of clients
+    for(std::set<cls::journal::Client>::iterator it = batch.begin();
+        it != batch.end(); ++it) {
+      cls::journal::ObjectSetPosition object_set_position = (*it).commit_position;
+      if (object_set_position.object_positions.empty()) {
+	*minset = cls::journal::ObjectSetPosition();
+	break;
+      }
+      cls::journal::ObjectPosition first = object_set_position.object_positions.front();
+
+      // least tag_tid (or least entry_tid for matching tag_tid)
+      if (!valid || (tag_tid > first.tag_tid) || ((tag_tid == first.tag_tid) && (entry_tid > first.entry_tid))) {
+	tag_tid = first.tag_tid;
+	entry_tid = first.entry_tid;
+	*minset = cls::journal::ObjectSetPosition(object_set_position);
+	valid = true;
+      }
+    }
+
+    // got the last batch, we're done
+    if (batch.size() < cls::journal::JOURNAL_MAX_RETURN) {
+      break;
+    }
+  }
+
+  return r;
+}
+
 } // anonymous namespace
 
 /**
@@ -565,8 +644,12 @@ int journal_client_register(cls_method_context_t hctx, bufferlist *in,
     return r;
   }
 
-  cls::journal::Client client(id, data);
-  key = key_from_client_id(id);
+  cls::journal::ObjectSetPosition minset;
+  r = find_min_commit_position(hctx, &minset);
+  if (r < 0)
+    return r;
+
+  cls::journal::Client client(id, data, minset);
   r = write_key(hctx, key, client);
   if (r < 0) {
     return r;
@@ -761,34 +844,10 @@ int journal_client_list(cls_method_context_t hctx, bufferlist *in,
     return -EINVAL;
   }
 
-  std::string last_read;
-  if (!start_after.empty()) {
-    last_read = key_from_client_id(start_after);
-  }
-
-  std::map<std::string, bufferlist> vals;
-  int r = cls_cxx_map_get_vals(hctx, last_read, HEADER_KEY_CLIENT_PREFIX,
-                               max_return, &vals);
-  if (r < 0) {
-    CLS_ERR("failed to retrieve omap values: %s", cpp_strerror(r).c_str());
-    return r;
-  }
-
   std::set<cls::journal::Client> clients;
-  for (std::map<std::string, bufferlist>::iterator it = vals.begin();
-       it != vals.end(); ++it) {
-    try {
-      bufferlist::iterator iter = it->second.begin();
-
-      cls::journal::Client client;
-      ::decode(client, iter);
-      clients.insert(client);
-    } catch (const buffer::error &err) {
-      CLS_ERR("could not decode client '%s': %s", it->first.c_str(),
-              err.what());
-      return -EIO;
-    }
-  }
+  int r = get_client_list_range(hctx, &clients, start_after, max_return);
+  if (r < 0)
+    return r;
 
   ::encode(clients, *out);
   return 0;
diff --git a/src/cls/journal/cls_journal_client.cc b/src/cls/journal/cls_journal_client.cc
index 7fbfb51..50549dc 100644
--- a/src/cls/journal/cls_journal_client.cc
+++ b/src/cls/journal/cls_journal_client.cc
@@ -14,8 +14,6 @@ namespace client {
 
 namespace {
 
-static const uint64_t JOURNAL_MAX_RETURN = 256;
-
 struct C_AioExec : public Context {
   librados::IoCtx ioctx;
   std::string oid;
diff --git a/src/cls/journal/cls_journal_types.h b/src/cls/journal/cls_journal_types.h
index 0381aff..4e1f2d7 100644
--- a/src/cls/journal/cls_journal_types.h
+++ b/src/cls/journal/cls_journal_types.h
@@ -19,6 +19,8 @@ class Formatter;
 namespace cls {
 namespace journal {
 
+static const uint64_t JOURNAL_MAX_RETURN = 256;
+
 struct ObjectPosition {
   uint64_t object_number;
   uint64_t tag_tid;
diff --git a/src/cls/rbd/cls_rbd.cc b/src/cls/rbd/cls_rbd.cc
index f56e4e0..c430adf 100644
--- a/src/cls/rbd/cls_rbd.cc
+++ b/src/cls/rbd/cls_rbd.cc
@@ -3112,6 +3112,12 @@ int image_set(cls_method_context_t hctx, const string &image_id,
               cpp_strerror(r).c_str());
       return r;
     }
+
+    // make sure this was not a race for disabling
+    if (mirror_image.state == cls::rbd::MIRROR_IMAGE_STATE_DISABLING) {
+      CLS_ERR("image '%s' is already disabled", image_id.c_str());
+      return r;
+    }
   } else if (r < 0) {
     CLS_ERR("error reading mirrored image '%s': '%s'", image_id.c_str(),
 	    cpp_strerror(r).c_str());
@@ -3317,8 +3323,8 @@ int image_status_list(cls_method_context_t hctx,
       (*mirror_images)[image_id] = mirror_image;
 
       cls::rbd::MirrorImageStatus status;
-      r = image_status_get(hctx, mirror_image.global_image_id, &status);
-      if (r < 0) {
+      int r1 = image_status_get(hctx, mirror_image.global_image_id, &status);
+      if (r1 < 0) {
 	continue;
       }
 
@@ -3380,10 +3386,7 @@ int image_status_get_summary(cls_method_context_t hctx,
       }
 
       cls::rbd::MirrorImageStatus status;
-      r = image_status_get(hctx, mirror_image.global_image_id, &status);
-      if (r < 0) {
-	// Ignore.
-      }
+      image_status_get(hctx, mirror_image.global_image_id, &status);
 
       cls::rbd::MirrorImageStatusState state = status.up ? status.state :
 	cls::rbd::MIRROR_IMAGE_STATUS_STATE_UNKNOWN;
diff --git a/src/cls/rbd/cls_rbd_client.cc b/src/cls/rbd/cls_rbd_client.cc
index ec57f7f..6ff2451 100644
--- a/src/cls/rbd/cls_rbd_client.cc
+++ b/src/cls/rbd/cls_rbd_client.cc
@@ -275,13 +275,20 @@ namespace librbd {
     int set_parent(librados::IoCtx *ioctx, const std::string &oid,
 		   parent_spec pspec, uint64_t parent_overlap)
     {
-      bufferlist inbl, outbl;
-      ::encode(pspec.pool_id, inbl);
-      ::encode(pspec.image_id, inbl);
-      ::encode(pspec.snap_id, inbl);
-      ::encode(parent_overlap, inbl);
+      librados::ObjectWriteOperation op;
+      set_parent(&op, pspec, parent_overlap);
+      return ioctx->operate(oid, &op);
+    }
+
+    void set_parent(librados::ObjectWriteOperation *op,
+                    parent_spec pspec, uint64_t parent_overlap) {
+      bufferlist in_bl;
+      ::encode(pspec.pool_id, in_bl);
+      ::encode(pspec.image_id, in_bl);
+      ::encode(pspec.snap_id, in_bl);
+      ::encode(parent_overlap, in_bl);
 
-      return ioctx->exec(oid, "rbd", "set_parent", inbl, outbl);
+      op->exec("rbd", "set_parent", in_bl);
     }
 
     void get_flags_start(librados::ObjectReadOperation *op,
@@ -942,21 +949,37 @@ namespace librbd {
                       const std::string &start, uint64_t max_return,
                       map<string, bufferlist> *pairs)
     {
-      assert(pairs);
-      bufferlist in, out;
-      ::encode(start, in);
-      ::encode(max_return, in);
-      int r = ioctx->exec(oid, "rbd", "metadata_list", in, out);
-      if (r < 0)
+      librados::ObjectReadOperation op;
+      metadata_list_start(&op, start, max_return);
+
+      bufferlist out_bl;
+      int r = ioctx->operate(oid, &op, &out_bl);
+      if (r < 0) {
         return r;
+      }
 
-      bufferlist::iterator iter = out.begin();
+      bufferlist::iterator it = out_bl.begin();
+      return metadata_list_finish(&it, pairs);
+    }
+
+    void metadata_list_start(librados::ObjectReadOperation *op,
+                             const std::string &start, uint64_t max_return)
+    {
+      bufferlist in_bl;
+      ::encode(start, in_bl);
+      ::encode(max_return, in_bl);
+      op->exec("rbd", "metadata_list", in_bl);
+    }
+
+    int metadata_list_finish(bufferlist::iterator *it,
+                             std::map<std::string, bufferlist> *pairs)
+    {
+      assert(pairs);
       try {
-        ::decode(*pairs, iter);
+        ::decode(*pairs, *it);
       } catch (const buffer::error &err) {
         return -EBADMSG;
       }
-
       return 0;
     }
 
@@ -1189,19 +1212,35 @@ namespace librbd {
 
     int mirror_image_get(librados::IoCtx *ioctx, const std::string &image_id,
 			 cls::rbd::MirrorImage *mirror_image) {
-      bufferlist in_bl;
+      librados::ObjectReadOperation op;
+      mirror_image_get_start(&op, image_id);
+
       bufferlist out_bl;
-      ::encode(image_id, in_bl);
+      int r = ioctx->operate(RBD_MIRRORING, &op, &out_bl);
+      if (r < 0) {
+	return r;
+      }
 
-      int r = ioctx->exec(RBD_MIRRORING, "rbd", "mirror_image_get", in_bl,
-			  out_bl);
+      bufferlist::iterator iter = out_bl.begin();
+      r = mirror_image_get_finish(&iter, mirror_image);
       if (r < 0) {
-        return r;
+	return r;
       }
+      return 0;
+    }
 
+    void mirror_image_get_start(librados::ObjectReadOperation *op,
+                                const std::string &image_id) {
+      bufferlist in_bl;
+      ::encode(image_id, in_bl);
+
+      op->exec("rbd", "mirror_image_get", in_bl);
+    }
+
+    int mirror_image_get_finish(bufferlist::iterator *iter,
+			        cls::rbd::MirrorImage *mirror_image) {
       try {
-        bufferlist::iterator bl_it = out_bl.begin();
-        ::decode(*mirror_image, bl_it);
+        ::decode(*mirror_image, *iter);
       } catch (const buffer::error &err) {
         return -EBADMSG;
       }
diff --git a/src/cls/rbd/cls_rbd_client.h b/src/cls/rbd/cls_rbd_client.h
index b3dd22e..1ccbf76 100644
--- a/src/cls/rbd/cls_rbd_client.h
+++ b/src/cls/rbd/cls_rbd_client.h
@@ -66,6 +66,8 @@ namespace librbd {
 		   uint64_t *parent_overlap);
     int set_parent(librados::IoCtx *ioctx, const std::string &oid,
 		   parent_spec pspec, uint64_t parent_overlap);
+    void set_parent(librados::ObjectWriteOperation *op,
+                    parent_spec pspec, uint64_t parent_overlap);
     void get_flags_start(librados::ObjectReadOperation *op,
                          const std::vector<snapid_t> &snap_ids);
     int get_flags_finish(bufferlist::iterator *it, uint64_t *flags,
@@ -135,6 +137,10 @@ namespace librbd {
     int metadata_list(librados::IoCtx *ioctx, const std::string &oid,
                       const std::string &start, uint64_t max_return,
                       map<string, bufferlist> *pairs);
+    void metadata_list_start(librados::ObjectReadOperation *op,
+                             const std::string &start, uint64_t max_return);
+    int metadata_list_finish(bufferlist::iterator *it,
+                             std::map<std::string, bufferlist> *pairs);
     int metadata_set(librados::IoCtx *ioctx, const std::string &oid,
                      const map<std::string, bufferlist> &data);
     int metadata_remove(librados::IoCtx *ioctx, const std::string &oid,
@@ -239,6 +245,10 @@ namespace librbd {
                                   std::string *image_id);
     int mirror_image_get(librados::IoCtx *ioctx, const std::string &image_id,
 			 cls::rbd::MirrorImage *mirror_image);
+    void mirror_image_get_start(librados::ObjectReadOperation *op,
+                                const std::string &image_id);
+    int mirror_image_get_finish(bufferlist::iterator *iter,
+			        cls::rbd::MirrorImage *mirror_image);
     int mirror_image_set(librados::IoCtx *ioctx, const std::string &image_id,
 			 const cls::rbd::MirrorImage &mirror_image);
     int mirror_image_remove(librados::IoCtx *ioctx,
diff --git a/src/common/Readahead.cc b/src/common/Readahead.cc
index 34f37da..9318bbf 100644
--- a/src/common/Readahead.cc
+++ b/src/common/Readahead.cc
@@ -168,6 +168,16 @@ void Readahead::set_trigger_requests(int trigger_requests) {
   m_lock.Unlock();
 }
 
+uint64_t Readahead::get_min_readahead_size(void) {
+  Mutex::Locker lock(m_lock);
+  return m_readahead_min_bytes;
+}
+
+uint64_t Readahead::get_max_readahead_size(void) {
+  Mutex::Locker lock(m_lock);
+  return m_readahead_max_bytes;
+}
+
 void Readahead::set_min_readahead_size(uint64_t min_readahead_size) {
   m_lock.Lock();
   m_readahead_min_bytes = min_readahead_size;
diff --git a/src/common/Readahead.h b/src/common/Readahead.h
index 75822c5..26b05b6 100644
--- a/src/common/Readahead.h
+++ b/src/common/Readahead.h
@@ -79,6 +79,16 @@ public:
   void set_trigger_requests(int trigger_requests);
 
   /**
+     Gets the minimum size of a readahead request, in bytes.
+   */
+  uint64_t get_min_readahead_size(void);
+
+  /**
+     Gets the maximum size of a readahead request, in bytes.
+   */
+  uint64_t get_max_readahead_size(void);
+
+  /**
      Sets the minimum size of a readahead request, in bytes.
    */
   void set_min_readahead_size(uint64_t min_readahead_size);
diff --git a/src/common/Throttle.cc b/src/common/Throttle.cc
index 3b909ee..8a52906 100644
--- a/src/common/Throttle.cc
+++ b/src/common/Throttle.cc
@@ -376,11 +376,16 @@ std::chrono::duration<double> BackoffThrottle::get(uint64_t c)
 
   auto start = std::chrono::system_clock::now();
   delay = _get_delay(c);
-  while (((start + delay) > std::chrono::system_clock::now()) ||
-	 !((max == 0) || (current == 0) || ((current + c) <= max))) {
+  while (true) {
+    if (!((max == 0) || (current == 0) || (current + c) <= max)) {
+      (*ticket)->wait(l);
+    } else if (delay > std::chrono::duration<double>(0)) {
+      (*ticket)->wait_for(l, delay);
+    } else {
+      break;
+    }
     assert(ticket == waiters.begin());
-    (*ticket)->wait_until(l, start + delay);
-    delay = _get_delay(c);
+    delay = _get_delay(c) - (std::chrono::system_clock::now() - start);
   }
   waiters.pop_front();
   _kick_waiters();
diff --git a/src/common/config_opts.h b/src/common/config_opts.h
index b8a0cec..398d068 100644
--- a/src/common/config_opts.h
+++ b/src/common/config_opts.h
@@ -371,7 +371,7 @@ OPTION(client_mount_timeout, OPT_DOUBLE, 300.0)
 OPTION(client_tick_interval, OPT_DOUBLE, 1.0)
 OPTION(client_trace, OPT_STR, "")
 OPTION(client_readahead_min, OPT_LONGLONG, 128*1024)  // readahead at _least_ this much.
-OPTION(client_readahead_max_bytes, OPT_LONGLONG, 0)  //8 * 1024*1024
+OPTION(client_readahead_max_bytes, OPT_LONGLONG, 0)  // default unlimited
 OPTION(client_readahead_max_periods, OPT_LONGLONG, 4)  // as multiple of file layout period (object size * num stripes)
 OPTION(client_snapdir, OPT_STR, ".snap")
 OPTION(client_mountpoint, OPT_STR, "/")
@@ -381,6 +381,7 @@ OPTION(client_notify_timeout, OPT_INT, 10) // in seconds
 OPTION(osd_client_watch_timeout, OPT_INT, 30) // in seconds
 OPTION(client_caps_release_delay, OPT_INT, 5) // in seconds
 OPTION(client_quota, OPT_BOOL, false)
+OPTION(client_quota_df, OPT_BOOL, true) // use quota for df on subdir mounts
 OPTION(client_oc, OPT_BOOL, true)
 OPTION(client_oc_size, OPT_INT, 1024*1024* 200)    // MB * n
 OPTION(client_oc_max_dirty, OPT_INT, 1024*1024* 100)    // MB * n  (dirty OR tx.. bigish)
diff --git a/src/common/hobject.cc b/src/common/hobject.cc
index 51403d7..8c3f9a2 100644
--- a/src/common/hobject.cc
+++ b/src/common/hobject.cc
@@ -142,6 +142,12 @@ void hobject_t::decode(bufferlist::iterator& bl)
       pool = INT64_MIN;
       assert(is_min());
     }
+
+    // for compatibility with some earlier verisons which might encoded
+    // a non-canonical max object
+    if (max) {
+      *this = hobject_t::get_max();
+    }
   }
   DECODE_FINISH(bl);
   build_hash_cache();
diff --git a/src/common/hobject.h b/src/common/hobject.h
index 0cc96fb..69bb611 100644
--- a/src/common/hobject.h
+++ b/src/common/hobject.h
@@ -52,6 +52,8 @@ public:
 private:
   string key;
 
+  class hobject_t_max {};
+
 public:
   const string &get_key() const {
     return key;
@@ -92,7 +94,25 @@ public:
     build_hash_cache();
   }
 
-  hobject_t(object_t oid, const string& key, snapid_t snap, uint64_t hash,
+  hobject_t(const hobject_t &rhs) = default;
+  hobject_t(hobject_t &&rhs) = default;
+  hobject_t(hobject_t_max &&singleon) : hobject_t() {
+    max = true;
+  }
+  hobject_t &operator=(const hobject_t &rhs) = default;
+  hobject_t &operator=(hobject_t &&rhs) = default;
+  hobject_t &operator=(hobject_t_max &&singleton) {
+    *this = hobject_t();
+    max = true;
+    return *this;
+  }
+
+  // maximum sorted value.
+  static hobject_t_max get_max() {
+    return hobject_t_max();
+  }
+
+  hobject_t(object_t oid, const string& key, snapid_t snap, uint32_t hash,
 	    int64_t pool, string nspace)
     : oid(oid), snap(snap), hash(hash), max(false),
       pool(pool), nspace(nspace),
@@ -158,12 +178,6 @@ public:
     set_hash(std::hash<sobject_t>()(o));
   }
 
-  // maximum sorted value.
-  static hobject_t get_max() {
-    hobject_t h;
-    h.max = true;
-    return h;
-  }
   bool is_max() const {
     return max;
   }
@@ -320,6 +334,32 @@ ostream& operator<<(ostream& out, const hobject_t& o);
 
 WRITE_EQ_OPERATORS_7(hobject_t, hash, oid, get_key(), snap, pool, max, nspace)
 
+template <typename T>
+struct always_false {
+  using value = std::false_type;
+};
+
+template <typename T>
+inline bool operator==(const hobject_t &lhs, const T&) {
+  static_assert(always_false<T>::value::value, "Do not compare to get_max()");
+  return lhs.is_max();
+}
+template <typename T>
+inline bool operator==(const T&, const hobject_t &rhs) {
+  static_assert(always_false<T>::value::value, "Do not compare to get_max()");
+  return rhs.is_max();
+}
+template <typename T>
+inline bool operator!=(const hobject_t &lhs, const T&) {
+  static_assert(always_false<T>::value::value, "Do not compare to get_max()");
+  return !lhs.is_max();
+}
+template <typename T>
+inline bool operator!=(const T&, const hobject_t &rhs) {
+  static_assert(always_false<T>::value::value, "Do not compare to get_max()");
+  return !rhs.is_max();
+}
+
 extern int cmp_nibblewise(const hobject_t& l, const hobject_t& r);
 extern int cmp_bitwise(const hobject_t& l, const hobject_t& r);
 static inline int cmp(const hobject_t& l, const hobject_t& r, bool sort_bitwise) {
@@ -328,6 +368,38 @@ static inline int cmp(const hobject_t& l, const hobject_t& r, bool sort_bitwise)
   else
     return cmp_nibblewise(l, r);
 }
+template <typename T>
+static inline int cmp(const hobject_t &l, const T&, bool sort_bitwise) {
+  static_assert(always_false<T>::value::value, "Do not compare to get_max()");
+  return l.is_max() ? 0 : -1;
+}
+template <typename T>
+static inline int cmp(const T&, const hobject_t&r, bool sort_bitwise) {
+  static_assert(always_false<T>::value::value, "Do not compare to get_max()");
+  return r.is_max() ? 0 : 1;
+}
+template <typename T>
+static inline int cmp_nibblewise(const hobject_t &l, const T&, bool sort_bitwise) {
+  static_assert(always_false<T>::value::value, "Do not compare to get_max()");
+  return l.is_max() ? 0 : -1;
+}
+template <typename T>
+static inline int cmp_nibblewise(const T&, const hobject_t&r, bool sort_bitwise) {
+  static_assert(always_false<T>::value::value, "Do not compare to get_max()");
+  return r.is_max() ? 0 : 1;
+}
+template <typename T>
+static inline int cmp_bitwise(const hobject_t &l, const T&, bool sort_bitwise) {
+  static_assert(always_false<T>::value::value, "Do not compare to get_max()");
+  return l.is_max() ? 0 : -1;
+}
+template <typename T>
+static inline int cmp_bitwise(const T&, const hobject_t&r, bool sort_bitwise) {
+  static_assert(always_false<T>::value::value, "Do not compare to get_max()");
+  return r.is_max() ? 0 : 1;
+}
+
+
 
 // these are convenient
 static inline hobject_t MAX_HOBJ(const hobject_t& l, const hobject_t& r, bool bitwise) {
diff --git a/src/erasure-code/Makefile.am b/src/erasure-code/Makefile.am
index 3390bac..224e67b 100644
--- a/src/erasure-code/Makefile.am
+++ b/src/erasure-code/Makefile.am
@@ -3,6 +3,8 @@
 erasure_codelibdir = $(pkglibdir)/erasure-code
 erasure_codelib_LTLIBRARIES =  
 
+check_LTLIBRARIES =  
+
 include erasure-code/jerasure/Makefile.am
 include erasure-code/lrc/Makefile.am
 include erasure-code/shec/Makefile.am
diff --git a/src/global/Makefile.am b/src/global/Makefile.am
index ed93426..243a7fa 100644
--- a/src/global/Makefile.am
+++ b/src/global/Makefile.am
@@ -6,9 +6,7 @@ libglobal_la_SOURCES = \
 	common/TrackedOp.cc
 
 libglobal_la_LIBADD = $(LIBCOMMON)
-if WITH_LTTNG
-libglobal_la_LIBADD += -ldl -llttng-ust
-endif
+
 noinst_LTLIBRARIES += libglobal.la
 
 noinst_HEADERS += \
diff --git a/src/include/ceph_fs.h b/src/include/ceph_fs.h
index 70f83e3..e8f5f2f 100644
--- a/src/include/ceph_fs.h
+++ b/src/include/ceph_fs.h
@@ -387,6 +387,18 @@ extern const char *ceph_mds_op_name(int op);
 #define CEPH_XATTR_REPLACE (1 << 1)
 #define CEPH_XATTR_REMOVE  (1 << 31)
 
+/*
+ * readdir request flags;
+ */
+#define CEPH_READDIR_REPLY_BITFLAGS	(1<<0)
+
+/*
+ * readdir reply flags.
+ */
+#define CEPH_READDIR_FRAG_END		(1<<0)
+#define CEPH_READDIR_FRAG_COMPLETE	(1<<8)
+#define CEPH_READDIR_HASH_ORDER		(1<<9)
+
 union ceph_mds_request_args {
 	struct {
 		__le32 mask;                 /* CEPH_CAP_* */
@@ -404,6 +416,7 @@ union ceph_mds_request_args {
 		__le32 frag;                 /* which dir fragment */
 		__le32 max_entries;          /* how many dentries to grab */
 		__le32 max_bytes;
+		__le16 flags;
 	} __attribute__ ((packed)) readdir;
 	struct {
 		__le32 mode;
diff --git a/src/journal/AsyncOpTracker.cc b/src/journal/AsyncOpTracker.cc
index 8c24088..13a55fa 100644
--- a/src/journal/AsyncOpTracker.cc
+++ b/src/journal/AsyncOpTracker.cc
@@ -22,10 +22,18 @@ void AsyncOpTracker::start_op() {
 }
 
 void AsyncOpTracker::finish_op() {
-  Mutex::Locker locker(m_lock);
-  assert(m_pending_ops > 0);
-  if (--m_pending_ops == 0) {
-    m_cond.Signal();
+  Context *on_finish = nullptr;
+  {
+    Mutex::Locker locker(m_lock);
+    assert(m_pending_ops > 0);
+    if (--m_pending_ops == 0) {
+      m_cond.Signal();
+      std::swap(on_finish, m_on_finish);
+    }
+  }
+
+  if (on_finish != nullptr) {
+    on_finish->complete(0);
   }
 }
 
@@ -36,4 +44,21 @@ void AsyncOpTracker::wait_for_ops() {
   }
 }
 
+void AsyncOpTracker::wait_for_ops(Context *on_finish) {
+  {
+    Mutex::Locker locker(m_lock);
+    assert(m_on_finish == nullptr);
+    if (m_pending_ops > 0) {
+      m_on_finish = on_finish;
+      return;
+    }
+  }
+  on_finish->complete(0);
+}
+
+bool AsyncOpTracker::empty() {
+  Mutex::Locker locker(m_lock);
+  return (m_pending_ops == 0);
+}
+
 } // namespace journal
diff --git a/src/journal/AsyncOpTracker.h b/src/journal/AsyncOpTracker.h
index cec332f..a88cd45 100644
--- a/src/journal/AsyncOpTracker.h
+++ b/src/journal/AsyncOpTracker.h
@@ -8,6 +8,8 @@
 #include "common/Cond.h"
 #include "common/Mutex.h"
 
+struct Context;
+
 namespace journal {
 
 class AsyncOpTracker {
@@ -19,11 +21,15 @@ public:
   void finish_op();
 
   void wait_for_ops();
+  void wait_for_ops(Context *on_finish);
+
+  bool empty();
 
 private:
   Mutex m_lock;
   Cond m_cond;
   uint32_t m_pending_ops;
+  Context *m_on_finish = nullptr;
 
 };
 
diff --git a/src/journal/FutureImpl.h b/src/journal/FutureImpl.h
index 5c11c4b..0d5e86f 100644
--- a/src/journal/FutureImpl.h
+++ b/src/journal/FutureImpl.h
@@ -56,13 +56,14 @@ public:
   }
   inline void set_flush_in_progress() {
     Mutex::Locker locker(m_lock);
+    assert(m_flush_handler);
+    m_flush_handler.reset();
     m_flush_state = FLUSH_STATE_IN_PROGRESS;
   }
 
   bool attach(const FlushHandlerPtr &flush_handler);
   inline void detach() {
     Mutex::Locker locker(m_lock);
-    assert(m_flush_handler);
     m_flush_handler.reset();
   }
   inline FlushHandlerPtr get_flush_handler() const {
diff --git a/src/journal/JournalMetadata.cc b/src/journal/JournalMetadata.cc
index ba85f4b..18bef13 100644
--- a/src/journal/JournalMetadata.cc
+++ b/src/journal/JournalMetadata.cc
@@ -336,6 +336,66 @@ struct C_FlushCommitPosition : public Context {
   }
 };
 
+struct C_AssertActiveTag : public Context {
+  CephContext *cct;
+  librados::IoCtx &ioctx;
+  const std::string &oid;
+  AsyncOpTracker &async_op_tracker;
+  std::string client_id;
+  uint64_t tag_tid;
+  Context *on_finish;
+
+  bufferlist out_bl;
+
+  C_AssertActiveTag(CephContext *cct, librados::IoCtx &ioctx,
+                    const std::string &oid, AsyncOpTracker &async_op_tracker,
+                    const std::string &client_id, uint64_t tag_tid,
+                    Context *on_finish)
+    : cct(cct), ioctx(ioctx), oid(oid), async_op_tracker(async_op_tracker),
+      client_id(client_id), tag_tid(tag_tid), on_finish(on_finish) {
+    async_op_tracker.start_op();
+  }
+  virtual ~C_AssertActiveTag() {
+    async_op_tracker.finish_op();
+  }
+
+  void send() {
+    ldout(cct, 20) << "C_AssertActiveTag: " << __func__ << dendl;
+
+    librados::ObjectReadOperation op;
+    client::tag_list_start(&op, tag_tid, 2, client_id, boost::none);
+
+    librados::AioCompletion *comp = librados::Rados::aio_create_completion(
+      this, nullptr, &utils::rados_state_callback<
+        C_AssertActiveTag, &C_AssertActiveTag::handle_send>);
+
+    int r = ioctx.aio_operate(oid, comp, &op, &out_bl);
+    assert(r == 0);
+    comp->release();
+  }
+
+  void handle_send(int r) {
+    ldout(cct, 20) << "C_AssertActiveTag: " << __func__ << ": r=" << r << dendl;
+
+    std::set<cls::journal::Tag> tags;
+    if (r == 0) {
+      bufferlist::iterator it = out_bl.begin();
+      r = client::tag_list_finish(&it, &tags);
+    }
+
+    // NOTE: since 0 is treated as an uninitialized list filter, we need to
+    // load to entries and look at the last tid
+    if (r == 0 && !tags.empty() && tags.rbegin()->tid > tag_tid) {
+      r = -ESTALE;
+    }
+    complete(r);
+  }
+
+  virtual void finish(int r) {
+    on_finish->complete(r);
+  }
+};
+
 } // anonymous namespace
 
 JournalMetadata::JournalMetadata(ContextWQ *work_queue, SafeTimer *timer,
@@ -356,48 +416,80 @@ JournalMetadata::JournalMetadata(ContextWQ *work_queue, SafeTimer *timer,
 }
 
 JournalMetadata::~JournalMetadata() {
-  if (m_initialized) {
-    shut_down();
-  }
-}
-
-void JournalMetadata::init(Context *on_init) {
+  Mutex::Locker locker(m_lock);
   assert(!m_initialized);
-  m_initialized = true;
+}
 
-  int r = m_ioctx.watch2(m_oid, &m_watch_handle, &m_watch_ctx);
-  if (r < 0) {
-    lderr(m_cct) << __func__ << ": failed to watch journal"
-                 << cpp_strerror(r) << dendl;
-    on_init->complete(r);
-    return;
+void JournalMetadata::init(Context *on_finish) {
+  {
+    Mutex::Locker locker(m_lock);
+    assert(!m_initialized);
+    m_initialized = true;
   }
 
-  C_ImmutableMetadata *ctx = new C_ImmutableMetadata(this, on_init);
-  get_immutable_metadata(&m_order, &m_splay_width, &m_pool_id, ctx);
+  // chain the init sequence (reverse order)
+  on_finish = utils::create_async_context_callback(
+    this, on_finish);
+  on_finish = new C_ImmutableMetadata(this, on_finish);
+  on_finish = new FunctionContext([this, on_finish](int r) {
+      if (r < 0) {
+        lderr(m_cct) << __func__ << ": failed to watch journal"
+                     << cpp_strerror(r) << dendl;
+        Mutex::Locker locker(m_lock);
+        m_watch_handle = 0;
+        on_finish->complete(r);
+        return;
+      }
+
+      get_immutable_metadata(&m_order, &m_splay_width, &m_pool_id, on_finish);
+    });
+
+  librados::AioCompletion *comp = librados::Rados::aio_create_completion(
+    on_finish, nullptr, utils::rados_ctx_callback);
+  int r = m_ioctx.aio_watch(m_oid, comp, &m_watch_handle, &m_watch_ctx);
+  assert(r == 0);
+  comp->release();
 }
 
-void JournalMetadata::shut_down() {
+void JournalMetadata::shut_down(Context *on_finish) {
 
   ldout(m_cct, 20) << __func__ << dendl;
 
-  assert(m_initialized);
+  uint64_t watch_handle = 0;
   {
     Mutex::Locker locker(m_lock);
     m_initialized = false;
-
-    if (m_watch_handle != 0) {
-      m_ioctx.unwatch2(m_watch_handle);
-      m_watch_handle = 0;
-    }
+    std::swap(watch_handle, m_watch_handle);
   }
 
-  flush_commit_position();
-
-  librados::Rados rados(m_ioctx);
-  rados.watch_flush();
-
-  m_async_op_tracker.wait_for_ops();
+  // chain the shut down sequence (reverse order)
+  on_finish = utils::create_async_context_callback(
+    this, on_finish);
+  on_finish = new FunctionContext([this, on_finish](int r) {
+      ldout(m_cct, 20) << "shut_down: waiting for ops" << dendl;
+      m_async_op_tracker.wait_for_ops(on_finish);
+    });
+  on_finish = new FunctionContext([this, on_finish](int r) {
+      ldout(m_cct, 20) << "shut_down: flushing watch" << dendl;
+      librados::Rados rados(m_ioctx);
+      librados::AioCompletion *comp = librados::Rados::aio_create_completion(
+        on_finish, nullptr, utils::rados_ctx_callback);
+      r = rados.aio_watch_flush(comp);
+      assert(r == 0);
+      comp->release();
+    });
+  on_finish = new FunctionContext([this, on_finish](int r) {
+      flush_commit_position(on_finish);
+    });
+  if (watch_handle != 0) {
+    librados::AioCompletion *comp = librados::Rados::aio_create_completion(
+      on_finish, nullptr, utils::rados_ctx_callback);
+    int r = m_ioctx.aio_unwatch(watch_handle, comp);
+    assert(r == 0);
+    comp->release();
+  } else {
+    on_finish->complete(0);
+  }
 }
 
 void JournalMetadata::get_immutable_metadata(uint8_t *order,
@@ -534,19 +626,26 @@ void JournalMetadata::set_minimum_set(uint64_t object_set) {
   m_minimum_set = object_set;
 }
 
-void JournalMetadata::set_active_set(uint64_t object_set) {
+int JournalMetadata::set_active_set(uint64_t object_set) {
+  C_SaferCond ctx;
+  set_active_set(object_set, &ctx);
+  return ctx.wait();
+}
+
+void JournalMetadata::set_active_set(uint64_t object_set, Context *on_finish) {
   Mutex::Locker locker(m_lock);
 
   ldout(m_cct, 20) << __func__ << ": current=" << m_active_set
                    << ", new=" << object_set << dendl;
   if (m_active_set >= object_set) {
+    m_work_queue->queue(on_finish, 0);
     return;
   }
 
   librados::ObjectWriteOperation op;
   client::set_active_set(&op, object_set);
 
-  C_NotifyUpdate *ctx = new C_NotifyUpdate(this);
+  C_NotifyUpdate *ctx = new C_NotifyUpdate(this, on_finish);
   librados::AioCompletion *comp =
     librados::Rados::aio_create_completion(ctx, NULL,
                                            utils::rados_ctx_callback);
@@ -557,6 +656,16 @@ void JournalMetadata::set_active_set(uint64_t object_set) {
   m_active_set = object_set;
 }
 
+void JournalMetadata::assert_active_tag(uint64_t tag_tid, Context *on_finish) {
+  Mutex::Locker locker(m_lock);
+
+  C_AssertActiveTag *ctx = new C_AssertActiveTag(m_cct, m_ioctx, m_oid,
+                                                 m_async_op_tracker,
+                                                 m_client_id, tag_tid,
+                                                 on_finish);
+  ctx->send();
+}
+
 void JournalMetadata::flush_commit_position() {
   ldout(m_cct, 20) << __func__ << dendl;
 
@@ -776,6 +885,34 @@ uint64_t JournalMetadata::allocate_commit_tid(uint64_t object_num,
   return commit_tid;
 }
 
+void JournalMetadata::overflow_commit_tid(uint64_t commit_tid,
+                                          uint64_t object_num) {
+  Mutex::Locker locker(m_lock);
+
+  auto it = m_pending_commit_tids.find(commit_tid);
+  assert(it != m_pending_commit_tids.end());
+  assert(it->second.object_num < object_num);
+
+  ldout(m_cct, 20) << __func__ << ": "
+                   << "commit_tid=" << commit_tid << ", "
+                   << "old_object_num=" << it->second.object_num << ", "
+                   << "new_object_num=" << object_num << dendl;
+  it->second.object_num = object_num;
+}
+
+void JournalMetadata::get_commit_entry(uint64_t commit_tid,
+                                       uint64_t *object_num,
+                                       uint64_t *tag_tid, uint64_t *entry_tid) {
+  Mutex::Locker locker(m_lock);
+
+  auto it = m_pending_commit_tids.find(commit_tid);
+  assert(it != m_pending_commit_tids.end());
+
+  *object_num = it->second.object_num;
+  *tag_tid = it->second.tag_tid;
+  *entry_tid = it->second.entry_tid;
+}
+
 void JournalMetadata::committed(uint64_t commit_tid,
                                 const CreateContext &create_context) {
   ldout(m_cct, 20) << "committed tid=" << commit_tid << dendl;
diff --git a/src/journal/JournalMetadata.h b/src/journal/JournalMetadata.h
index c9e6d93..1c084a4 100644
--- a/src/journal/JournalMetadata.h
+++ b/src/journal/JournalMetadata.h
@@ -52,7 +52,7 @@ public:
   ~JournalMetadata();
 
   void init(Context *on_init);
-  void shut_down();
+  void shut_down(Context *on_finish);
 
   bool is_initialized() const { return m_initialized; }
 
@@ -110,12 +110,15 @@ public:
     return m_minimum_set;
   }
 
-  void set_active_set(uint64_t object_set);
+  int set_active_set(uint64_t object_set);
+  void set_active_set(uint64_t object_set, Context *on_finish);
   inline uint64_t get_active_set() const {
     Mutex::Locker locker(m_lock);
     return m_active_set;
   }
 
+  void assert_active_tag(uint64_t tag_tid, Context *on_finish);
+
   void flush_commit_position();
   void flush_commit_position(Context *on_safe);
   void get_commit_position(ObjectSetPosition *commit_position) const {
@@ -137,6 +140,9 @@ public:
 
   uint64_t allocate_commit_tid(uint64_t object_num, uint64_t tag_tid,
                                uint64_t entry_tid);
+  void overflow_commit_tid(uint64_t commit_tid, uint64_t object_num);
+  void get_commit_entry(uint64_t commit_tid, uint64_t *object_num,
+                        uint64_t *tag_tid, uint64_t *entry_tid);
   void committed(uint64_t commit_tid, const CreateContext &create_context);
 
   void notify_update();
diff --git a/src/journal/JournalPlayer.cc b/src/journal/JournalPlayer.cc
index 2f1d96d..28905a2 100644
--- a/src/journal/JournalPlayer.cc
+++ b/src/journal/JournalPlayer.cc
@@ -79,10 +79,12 @@ JournalPlayer::JournalPlayer(librados::IoCtx &ioctx,
 }
 
 JournalPlayer::~JournalPlayer() {
-  m_async_op_tracker.wait_for_ops();
+  assert(m_async_op_tracker.empty());
   {
     Mutex::Locker locker(m_lock);
+    assert(m_shut_down);
     assert(m_fetch_object_numbers.empty());
+    assert(!m_watch_scheduled);
   }
   m_replay_handler->put();
 }
@@ -134,20 +136,37 @@ void JournalPlayer::prefetch_and_watch(double interval) {
     Mutex::Locker locker(m_lock);
     m_watch_enabled = true;
     m_watch_interval = interval;
+    m_watch_step = WATCH_STEP_FETCH_CURRENT;
   }
   prefetch();
 }
 
-void JournalPlayer::unwatch() {
+void JournalPlayer::shut_down(Context *on_finish) {
   ldout(m_cct, 20) << __func__ << dendl;
   Mutex::Locker locker(m_lock);
+
+  assert(!m_shut_down);
+  m_shut_down = true;
   m_watch_enabled = false;
+
+  on_finish = utils::create_async_context_callback(
+      m_journal_metadata, on_finish);
+
   if (m_watch_scheduled) {
-    for (auto &players : m_object_players) {
-      players.second.begin()->second->unwatch();
+    ObjectPlayerPtr object_player = get_object_player();
+    switch (m_watch_step) {
+    case WATCH_STEP_FETCH_FIRST:
+      object_player = m_object_players.begin()->second.begin()->second;
+      // fallthrough
+    case WATCH_STEP_FETCH_CURRENT:
+      object_player->unwatch();
+      break;
+    case WATCH_STEP_ASSERT_ACTIVE:
+      break;
     }
-    m_watch_scheduled = false;
   }
+
+  m_async_op_tracker.wait_for_ops(on_finish);
 }
 
 bool JournalPlayer::try_pop_front(Entry *entry, uint64_t *commit_tid) {
@@ -159,17 +178,16 @@ bool JournalPlayer::try_pop_front(Entry *entry, uint64_t *commit_tid) {
     return false;
   }
 
-  if (!is_object_set_ready()) {
-    m_handler_notified = false;
-    return false;
-  }
-
   if (!verify_playback_ready()) {
-    if (!m_watch_enabled) {
-      notify_complete(0);
-    } else if (!m_watch_scheduled) {
+    if (!is_object_set_ready()) {
       m_handler_notified = false;
-      schedule_watch();
+    } else {
+      if (!m_watch_enabled) {
+        notify_complete(0);
+      } else if (!m_watch_scheduled) {
+        m_handler_notified = false;
+        schedule_watch();
+      }
     }
     return false;
   }
@@ -181,15 +199,9 @@ bool JournalPlayer::try_pop_front(Entry *entry, uint64_t *commit_tid) {
   object_player->pop_front();
 
   uint64_t last_entry_tid;
-  if (m_active_tag_tid && *m_active_tag_tid != entry->get_tag_tid()) {
-    lderr(m_cct) << "unexpected tag in journal entry: " << *entry << dendl;
-
-    m_state = STATE_ERROR;
-    notify_complete(-ENOMSG);
-    return false;
-  } else if (m_journal_metadata->get_last_allocated_entry_tid(
-               entry->get_tag_tid(), &last_entry_tid) &&
-             entry->get_entry_tid() != last_entry_tid + 1) {
+  if (m_journal_metadata->get_last_allocated_entry_tid(
+        entry->get_tag_tid(), &last_entry_tid) &&
+      entry->get_entry_tid() != last_entry_tid + 1) {
     lderr(m_cct) << "missing prior journal entry: " << *entry << dendl;
 
     m_state = STATE_ERROR;
@@ -197,7 +209,6 @@ bool JournalPlayer::try_pop_front(Entry *entry, uint64_t *commit_tid) {
     return false;
   }
 
-  m_active_tag_tid = entry->get_tag_tid();
   advance_splay_object();
   remove_empty_object_player(object_player);
 
@@ -329,16 +340,16 @@ int JournalPlayer::process_prefetch(uint64_t object_number) {
   }
 
   m_state = STATE_PLAYBACK;
-  if (!is_object_set_ready()) {
-    ldout(m_cct, 10) << __func__ << ": waiting for full object set" << dendl;
-  } else if (verify_playback_ready()) {
+  if (verify_playback_ready()) {
     notify_entries_available();
-  } else if (m_watch_enabled) {
-    schedule_watch();
-  } else {
-    ldout(m_cct, 10) << __func__ << ": no uncommitted entries available"
-                     << dendl;
-    notify_complete(0);
+  } else if (is_object_set_ready()) {
+    if (m_watch_enabled) {
+      schedule_watch();
+    } else {
+      ldout(m_cct, 10) << __func__ << ": no uncommitted entries available"
+                       << dendl;
+      notify_complete(0);
+    }
   }
   return 0;
 }
@@ -347,21 +358,19 @@ int JournalPlayer::process_playback(uint64_t object_number) {
   ldout(m_cct, 10) << __func__ << ": object_num=" << object_number << dendl;
   assert(m_lock.is_locked());
 
-  if (!is_object_set_ready()) {
-    return 0;
-  }
-
-  ObjectPlayerPtr object_player = get_object_player();
   if (verify_playback_ready()) {
     notify_entries_available();
-  } else if (m_watch_enabled) {
-    schedule_watch();
-  } else {
-    uint8_t splay_width = m_journal_metadata->get_splay_width();
-    uint64_t active_set = m_journal_metadata->get_active_set();
-    uint64_t object_set = object_player->get_object_number() / splay_width;
-    if (object_set == active_set) {
-      notify_complete(0);
+  } else if (is_object_set_ready()) {
+    if (m_watch_enabled) {
+      schedule_watch();
+    } else {
+      ObjectPlayerPtr object_player = get_object_player();
+      uint8_t splay_width = m_journal_metadata->get_splay_width();
+      uint64_t active_set = m_journal_metadata->get_active_set();
+      uint64_t object_set = object_player->get_object_number() / splay_width;
+      if (object_set == active_set) {
+        notify_complete(0);
+      }
     }
   }
   return 0;
@@ -377,51 +386,149 @@ bool JournalPlayer::is_object_set_ready() const {
 
 bool JournalPlayer::verify_playback_ready() {
   assert(m_lock.is_locked());
-  assert(is_object_set_ready());
 
-  ObjectPlayerPtr object_player = get_object_player();
-  assert(object_player);
-
-  // Verify is the active object player has another entry available
-  // in the sequence
-  Entry entry;
-  bool entry_available = false;
-  if (!object_player->empty()) {
-    entry_available = true;
-    object_player->front(&entry);
-    if (!m_active_tag_tid || entry.get_tag_tid() == *m_active_tag_tid) {
-      return true;
+  while (true) {
+    if (!is_object_set_ready()) {
+      ldout(m_cct, 10) << __func__ << ": waiting for full object set" << dendl;
+      return false;
+    }
+
+    ObjectPlayerPtr object_player = get_object_player();
+    assert(object_player);
+    uint64_t object_num = object_player->get_object_number();
+
+    // Verify is the active object player has another entry available
+    // in the sequence
+    // NOTE: replay currently does not check tag class to playback multiple tags
+    // from different classes (issue #14909).  When a new tag is discovered, it
+    // is assumed that the previous tag was closed at the last replayable entry.
+    Entry entry;
+    if (!object_player->empty()) {
+      m_watch_prune_active_tag = false;
+      object_player->front(&entry);
+
+      if (!m_active_tag_tid) {
+        ldout(m_cct, 10) << __func__ << ": "
+                         << "object_num=" << object_num << ", "
+                         << "initial tag=" << entry.get_tag_tid()
+                         << dendl;
+        m_active_tag_tid = entry.get_tag_tid();
+        return true;
+      } else if (entry.get_tag_tid() < *m_active_tag_tid ||
+                 (m_prune_tag_tid && entry.get_tag_tid() <= *m_prune_tag_tid)) {
+        // entry occurred before the current active tag
+        ldout(m_cct, 10) << __func__ << ": detected stale entry: "
+                         << "object_num=" << object_num << ", "
+                         << "entry=" << entry << dendl;
+        prune_tag(entry.get_tag_tid());
+        continue;
+      } else if (entry.get_tag_tid() > *m_active_tag_tid) {
+        // new tag at current playback position -- implies that previous
+        // tag ended abruptly without flushing out all records
+        // search for the start record for the next tag
+        ldout(m_cct, 10) << __func__ << ": new tag detected: "
+                         << "object_num=" << object_num << ", "
+                         << "active_tag=" << *m_active_tag_tid << ", "
+                         << "new_tag=" << entry.get_tag_tid() << dendl;
+        if (entry.get_entry_tid() == 0) {
+          // first entry in new tag -- can promote to active
+          prune_active_tag(entry.get_tag_tid());
+          return true;
+        } else {
+          // prune current active and wait for initial entry for new tag
+          prune_active_tag(boost::none);
+          continue;
+        }
+      } else {
+        ldout(m_cct, 20) << __func__ << ": "
+                         << "object_num=" << object_num << ", "
+                         << "entry: " << entry << dendl;
+        assert(entry.get_tag_tid() == *m_active_tag_tid);
+        return true;
+      }
+    } else {
+      if (!m_active_tag_tid) {
+        // waiting for our first entry
+        ldout(m_cct, 10) << __func__ << ": waiting for first entry: "
+                         << "object_num=" << object_num << dendl;
+        return false;
+      } else if (m_prune_tag_tid && *m_prune_tag_tid == *m_active_tag_tid) {
+        ldout(m_cct, 10) << __func__ << ": no more entries" << dendl;
+        return false;
+      } else if (!m_watch_enabled) {
+        // current playback position is empty so this tag is done
+        ldout(m_cct, 10) << __func__ << ": no more in-sequence entries: "
+                         << "object_num=" << object_num << ", "
+                         << "active_tag=" << *m_active_tag_tid << dendl;
+        prune_active_tag(boost::none);
+        continue;
+      } else if (m_watch_enabled && m_watch_prune_active_tag) {
+        // detected current tag is now longer active and we have re-read the
+        // current object but it's still empty, so this tag is done
+        ldout(m_cct, 10) << __func__ << ": assuming no more in-sequence entries: "
+                         << "object_num=" << object_num << ", "
+                         << "active_tag " << *m_active_tag_tid << dendl;
+        prune_active_tag(boost::none);
+        continue;
+      } else if (m_watch_enabled && object_player->refetch_required()) {
+        // if the active object requires a refetch, don't proceed looking for a
+        // new tag before this process completes
+        ldout(m_cct, 10) << __func__ << ": refetch required: "
+                         << "object_num=" << object_num << dendl;
+        return false;
+      }
     }
   }
+  return false;
+}
 
-  // if we just advanced to this object, make sure we have the latest
-  // set of data before advancing to a new tag
-  if (m_watch_enabled && m_watch_required) {
-    m_watch_required = false;
-    return false;
+void JournalPlayer::prune_tag(uint64_t tag_tid) {
+  assert(m_lock.is_locked());
+  ldout(m_cct, 10) << __func__ << ": pruning remaining entries for tag "
+                   << tag_tid << dendl;
+
+  // prune records that are at or below the largest prune tag tid
+  if (!m_prune_tag_tid || *m_prune_tag_tid < tag_tid) {
+    m_prune_tag_tid = tag_tid;
   }
 
-  // NOTE: replay currently does not check tag class to playback multiple tags
-  // from different classes (issue #14909).  When a new tag is discovered, it
-  // is assumed that the previous tag was closed at the last replayable entry.
-  object_player = m_object_players.begin()->second.begin()->second;
-  if (!object_player->empty() && m_active_tag_tid) {
-    object_player->front(&entry);
-    if (entry.get_tag_tid() > *m_active_tag_tid &&
-        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;
+  for (auto &players : m_object_players) {
+    for (auto player_pair : players.second) {
+      ObjectPlayerPtr object_player = player_pair.second;
+      ldout(m_cct, 15) << __func__ << ": checking " << object_player->get_oid()
+                       << dendl;
+      while (!object_player->empty()) {
+        Entry entry;
+        object_player->front(&entry);
+        if (entry.get_tag_tid() == tag_tid) {
+          ldout(m_cct, 20) << __func__ << ": pruned " << entry << dendl;
+          object_player->pop_front();
+        } else {
+          break;
+        }
+      }
+    }
 
-      ldout(m_cct, 20) << __func__ << ": new tag " << entry.get_tag_tid() << " "
-                       << "detected, adjusting offset to "
-                       << static_cast<uint32_t>(m_splay_offset) << dendl;
-      return true;
+    // trim any empty players to prefetch the next available object
+    ObjectPlayers object_players(players.second);
+    for (auto player_pair : object_players) {
+      remove_empty_object_player(player_pair.second);
     }
   }
+}
+
+void JournalPlayer::prune_active_tag(const boost::optional<uint64_t>& tag_tid) {
+  assert(m_lock.is_locked());
+  assert(m_active_tag_tid);
+
+  uint64_t active_tag_tid = *m_active_tag_tid;
+  if (tag_tid) {
+    m_active_tag_tid = tag_tid;
+  }
+  m_splay_offset = 0;
+  m_watch_step = WATCH_STEP_FETCH_CURRENT;
 
-  // if any entry is available, we can test if the sequence is corrupt
-  return entry_available;
+  prune_tag(active_tag_tid);
 }
 
 const JournalPlayer::ObjectPlayers &JournalPlayer::get_object_players() const {
@@ -441,6 +548,20 @@ ObjectPlayerPtr JournalPlayer::get_object_player() const {
   return object_players.begin()->second;
 }
 
+ObjectPlayerPtr JournalPlayer::get_object_player(uint64_t object_number) const {
+  assert(m_lock.is_locked());
+
+  uint8_t splay_width = m_journal_metadata->get_splay_width();
+  uint8_t splay_offset = object_number % splay_width;
+  auto splay_it = m_object_players.find(splay_offset);
+  assert(splay_it != m_object_players.end());
+
+  const ObjectPlayers &object_players = splay_it->second;
+  auto player_it = object_players.find(object_number);
+  assert(player_it != object_players.end());
+  return player_it->second;
+}
+
 ObjectPlayerPtr JournalPlayer::get_next_set_object_player() const {
   assert(m_lock.is_locked());
 
@@ -452,7 +573,7 @@ void JournalPlayer::advance_splay_object() {
   assert(m_lock.is_locked());
   ++m_splay_offset;
   m_splay_offset %= m_journal_metadata->get_splay_width();
-  m_watch_required = true;
+  m_watch_step = WATCH_STEP_FETCH_CURRENT;
   ldout(m_cct, 20) << __func__ << ": new offset "
                    << static_cast<uint32_t>(m_splay_offset) << dendl;
 }
@@ -466,9 +587,18 @@ bool JournalPlayer::remove_empty_object_player(const ObjectPlayerPtr &player) {
   uint64_t active_set = m_journal_metadata->get_active_set();
   if (!player->empty() || object_set == active_set) {
     return false;
+  } else if (m_watch_enabled && player->refetch_required()) {
+    ldout(m_cct, 20) << __func__ << ": " << player->get_oid() << " requires "
+                     << "a refetch" << dendl;
+    return false;
   }
 
-  ldout(m_cct, 15) << player->get_oid() << " empty" << dendl;
+  ldout(m_cct, 15) << __func__ << ": " << player->get_oid() << " empty"
+                   << dendl;
+
+  m_watch_prune_active_tag = false;
+  m_watch_step = WATCH_STEP_FETCH_CURRENT;
+
   ObjectPlayers &object_players = m_object_players[
     player->get_object_number() % splay_width];
   assert(!object_players.empty());
@@ -510,17 +640,15 @@ void JournalPlayer::handle_fetched(uint64_t object_num, int r) {
   assert(m_fetch_object_numbers.count(object_num) == 1);
   m_fetch_object_numbers.erase(object_num);
 
+  if (m_shut_down) {
+    return;
+  }
+
   if (r == -ENOENT) {
     r = 0;
   }
   if (r == 0) {
-    uint8_t splay_width = m_journal_metadata->get_splay_width();
-    uint8_t splay_offset = object_num % splay_width;
-    assert(m_object_players.count(splay_offset) == 1);
-    ObjectPlayers &object_players = m_object_players[splay_offset];
-
-    assert(object_players.count(object_num) == 1);
-    ObjectPlayerPtr object_player = object_players[object_num];
+    ObjectPlayerPtr object_player = get_object_player(object_num);
     remove_empty_object_player(object_player);
   }
   process_state(object_num, r);
@@ -533,43 +661,110 @@ void JournalPlayer::schedule_watch() {
     return;
   }
 
-  // poll first splay offset and active splay offset since
-  // new records should only appear in those two objects
-  C_Watch *ctx = new C_Watch(this);
-
-  ObjectPlayerPtr object_player = get_object_player();
-  uint8_t splay_width = m_journal_metadata->get_splay_width();
-  if (object_player->get_object_number() % splay_width != 0) {
-    ++ctx->pending_fetches;
+  m_watch_scheduled = true;
 
-    ObjectPlayerPtr first_object_player =
-      m_object_players.begin()->second.begin()->second;
-    first_object_player->watch(ctx, m_watch_interval);
+  if (m_watch_step == WATCH_STEP_ASSERT_ACTIVE) {
+    // detect if a new tag has been created in case we are blocked
+    // by an incomplete tag sequence
+    ldout(m_cct, 20) << __func__ << ": asserting active tag="
+                     << *m_active_tag_tid << dendl;
+
+    m_async_op_tracker.start_op();
+    FunctionContext *ctx = new FunctionContext([this](int r) {
+        handle_watch_assert_active(r);
+      });
+    m_journal_metadata->assert_active_tag(*m_active_tag_tid, ctx);
+    return;
   }
 
-  object_player->watch(ctx, m_watch_interval);
-  m_watch_scheduled = true;
+  ObjectPlayerPtr object_player;
+  double watch_interval = m_watch_interval;
+
+  switch (m_watch_step) {
+  case WATCH_STEP_FETCH_CURRENT:
+    {
+      object_player = get_object_player();
+
+      uint8_t splay_width = m_journal_metadata->get_splay_width();
+      uint64_t active_set = m_journal_metadata->get_active_set();
+      uint64_t object_set = object_player->get_object_number() / splay_width;
+      if (object_set < active_set && object_player->refetch_required()) {
+        ldout(m_cct, 20) << __func__ << ": refetching "
+                         << object_player->get_oid()
+                         << dendl;
+        object_player->clear_refetch_required();
+        watch_interval = 0;
+      }
+    }
+    break;
+  case WATCH_STEP_FETCH_FIRST:
+    object_player = m_object_players.begin()->second.begin()->second;
+    watch_interval = 0;
+    break;
+  default:
+    assert(false);
+  }
+
+  ldout(m_cct, 20) << __func__ << ": scheduling watch on "
+                   << object_player->get_oid() << dendl;
+  Context *ctx = utils::create_async_context_callback(
+    m_journal_metadata, new C_Watch(this, object_player->get_object_number()));
+  object_player->watch(ctx, watch_interval);
 }
 
-void JournalPlayer::handle_watch(int r) {
+void JournalPlayer::handle_watch(uint64_t object_num, int r) {
   ldout(m_cct, 10) << __func__ << ": r=" << r << dendl;
-  if (r == -ECANCELED) {
+  Mutex::Locker locker(m_lock);
+  assert(m_watch_scheduled);
+  m_watch_scheduled = false;
+
+  if (m_shut_down || r == -ECANCELED) {
     // unwatch of object player(s)
     return;
   }
 
+  ObjectPlayerPtr object_player = get_object_player(object_num);
+  if (r == 0 && object_player->empty()) {
+    // possibly need to prune this empty object player if we've
+    // already fetched it after the active set was advanced with no
+    // new records
+    remove_empty_object_player(object_player);
+  }
+
+  // determine what object to query on next watch schedule tick
+  uint8_t splay_width = m_journal_metadata->get_splay_width();
+  if (m_watch_step == WATCH_STEP_FETCH_CURRENT &&
+      object_player->get_object_number() % splay_width != 0) {
+    m_watch_step = WATCH_STEP_FETCH_FIRST;
+  } else if (m_active_tag_tid) {
+    m_watch_step = WATCH_STEP_ASSERT_ACTIVE;
+  } else {
+    m_watch_step = WATCH_STEP_FETCH_CURRENT;
+  }
+
+  process_state(object_num, r);
+}
+
+void JournalPlayer::handle_watch_assert_active(int r) {
+  ldout(m_cct, 10) << __func__ << ": r=" << r << dendl;
+
   Mutex::Locker locker(m_lock);
+  assert(m_watch_scheduled);
   m_watch_scheduled = false;
 
-  std::set<uint64_t> object_numbers;
-  for (auto &players : m_object_players) {
-    object_numbers.insert(
-      players.second.begin()->second->get_object_number());
+  if (r == -ESTALE) {
+    // newer tag exists -- since we are at this step in the watch sequence,
+    // we know we can prune the active tag if watch fails again
+    ldout(m_cct, 10) << __func__ << ": tag " << *m_active_tag_tid << " "
+                     << "no longer active" << dendl;
+    m_watch_prune_active_tag = true;
   }
 
-  for (auto object_num : object_numbers) {
-    process_state(object_num, r);
+  m_watch_step = WATCH_STEP_FETCH_CURRENT;
+  if (!m_shut_down && m_watch_enabled) {
+    schedule_watch();
   }
+  m_async_op_tracker.finish_op();
 }
 
 void JournalPlayer::notify_entries_available() {
diff --git a/src/journal/JournalPlayer.h b/src/journal/JournalPlayer.h
index 80a9ff7..f502582 100644
--- a/src/journal/JournalPlayer.h
+++ b/src/journal/JournalPlayer.h
@@ -36,7 +36,7 @@ public:
 
   void prefetch();
   void prefetch_and_watch(double interval);
-  void unwatch();
+  void shut_down(Context *on_finish);
 
   bool try_pop_front(Entry *entry, uint64_t *commit_tid);
 
@@ -54,6 +54,12 @@ private:
     STATE_ERROR
   };
 
+  enum WatchStep {
+    WATCH_STEP_FETCH_CURRENT,
+    WATCH_STEP_FETCH_FIRST,
+    WATCH_STEP_ASSERT_ACTIVE
+  };
+
   struct C_Fetch : public Context {
     JournalPlayer *player;
     uint64_t object_num;
@@ -70,31 +76,17 @@ private:
 
   struct C_Watch : public Context {
     JournalPlayer *player;
-    Mutex lock;
-    uint8_t pending_fetches = 1;
-    int ret_val = 0;
-
-    C_Watch(JournalPlayer *player)
-      : player(player), lock("JournalPlayer::C_Watch::lock") {
+    uint64_t object_num;
+    C_Watch(JournalPlayer *player, uint64_t object_num)
+      : player(player), object_num(object_num) {
+      player->m_async_op_tracker.start_op();
     }
-
-    virtual void complete(int r) override {
-      lock.Lock();
-      if (ret_val == 0 && r < 0) {
-        ret_val = r;
-      }
-
-      assert(pending_fetches > 0);
-      if (--pending_fetches == 0) {
-        lock.Unlock();
-        Context::complete(ret_val);
-      } else {
-        lock.Unlock();
-      }
+    virtual ~C_Watch() {
+      player->m_async_op_tracker.finish_op();
     }
 
     virtual void finish(int r) override {
-      player->handle_watch(r);
+      player->handle_watch(object_num, r);
     }
   };
 
@@ -114,8 +106,10 @@ private:
   bool m_watch_enabled;
   bool m_watch_scheduled;
   double m_watch_interval;
-  bool m_watch_required = false;
+  WatchStep m_watch_step = WATCH_STEP_FETCH_CURRENT;
+  bool m_watch_prune_active_tag = false;
 
+  bool m_shut_down = false;
   bool m_handler_notified = false;
 
   ObjectNumbers m_fetch_object_numbers;
@@ -124,14 +118,20 @@ private:
   SplayedObjectPlayers m_object_players;
   uint64_t m_commit_object;
   SplayedObjectPositions m_commit_positions;
+
   boost::optional<uint64_t> m_active_tag_tid = boost::none;
+  boost::optional<uint64_t> m_prune_tag_tid = boost::none;
 
   void advance_splay_object();
 
   bool is_object_set_ready() const;
   bool verify_playback_ready();
+  void prune_tag(uint64_t tag_tid);
+  void prune_active_tag(const boost::optional<uint64_t>& tag_tid);
+
   const ObjectPlayers &get_object_players() const;
   ObjectPlayerPtr get_object_player() const;
+  ObjectPlayerPtr get_object_player(uint64_t object_number) const;
   ObjectPlayerPtr get_next_set_object_player() const;
   bool remove_empty_object_player(const ObjectPlayerPtr &object_player);
 
@@ -143,7 +143,8 @@ private:
   void handle_fetched(uint64_t object_num, int r);
 
   void schedule_watch();
-  void handle_watch(int r);
+  void handle_watch(uint64_t object_num, int r);
+  void handle_watch_assert_active(int r);
 
   void notify_entries_available();
   void notify_complete(int r);
diff --git a/src/journal/JournalRecorder.cc b/src/journal/JournalRecorder.cc
index f78f0c8..b4da4ff 100644
--- a/src/journal/JournalRecorder.cc
+++ b/src/journal/JournalRecorder.cc
@@ -2,6 +2,7 @@
 // vim: ts=8 sw=2 smarttab
 
 #include "journal/JournalRecorder.h"
+#include "common/errno.h"
 #include "journal/Entry.h"
 #include "journal/Utils.h"
 
@@ -49,7 +50,7 @@ JournalRecorder::JournalRecorder(librados::IoCtx &ioctx,
   : m_cct(NULL), m_object_oid_prefix(object_oid_prefix),
     m_journal_metadata(journal_metadata), m_flush_interval(flush_interval),
     m_flush_bytes(flush_bytes), m_flush_age(flush_age), m_listener(this),
-    m_overflow_handler(this), m_lock("JournalerRecorder::m_lock"),
+    m_object_handler(this), m_lock("JournalerRecorder::m_lock"),
     m_current_set(m_journal_metadata->get_active_set()) {
 
   Mutex::Locker locker(m_lock);
@@ -67,6 +68,10 @@ JournalRecorder::JournalRecorder(librados::IoCtx &ioctx,
 
 JournalRecorder::~JournalRecorder() {
   m_journal_metadata->remove_listener(&m_listener);
+
+  Mutex::Locker locker(m_lock);
+  assert(m_in_flight_advance_sets == 0);
+  assert(m_in_flight_object_closes == 0);
 }
 
 Future JournalRecorder::append(uint64_t tag_tid,
@@ -97,9 +102,8 @@ Future JournalRecorder::append(uint64_t tag_tid,
   if (object_full) {
     ldout(m_cct, 10) << "object " << object_ptr->get_oid() << " now full"
                      << dendl;
-    close_object_set(object_ptr->get_object_number() / splay_width);
+    close_and_advance_object_set(object_ptr->get_object_number() / splay_width);
   }
-
   return Future(future);
 }
 
@@ -127,36 +131,100 @@ ObjectRecorderPtr JournalRecorder::get_object(uint8_t splay_offset) {
   return object_recoder;
 }
 
-void JournalRecorder::close_object_set(uint64_t object_set) {
+void JournalRecorder::close_and_advance_object_set(uint64_t object_set) {
   assert(m_lock.is_locked());
 
-  uint8_t splay_width = m_journal_metadata->get_splay_width();
-  if (object_set != m_current_set) {
+  // entry overflow from open object
+  if (m_current_set != object_set) {
+    ldout(m_cct, 20) << __func__ << ": close already in-progress" << dendl;
     return;
   }
 
+  // we shouldn't overflow upon append if already closed and we
+  // shouldn't receive an overflowed callback if already closed
+  assert(m_in_flight_advance_sets == 0);
+  assert(m_in_flight_object_closes == 0);
+
   uint64_t active_set = m_journal_metadata->get_active_set();
-  if (active_set < m_current_set + 1) {
-    m_journal_metadata->set_active_set(m_current_set + 1);
+  assert(m_current_set == active_set);
+  ++m_current_set;
+  ++m_in_flight_advance_sets;
+
+  ldout(m_cct, 20) << __func__ << ": closing active object set "
+                   << object_set << dendl;
+  if (close_object_set(m_current_set)) {
+    advance_object_set();
+  }
+}
+
+void JournalRecorder::advance_object_set() {
+  assert(m_lock.is_locked());
+
+  assert(m_in_flight_object_closes == 0);
+  ldout(m_cct, 20) << __func__ << ": advance to object set " << m_current_set
+                   << dendl;
+  m_journal_metadata->set_active_set(m_current_set, new C_AdvanceObjectSet(
+    this));
+}
+
+void JournalRecorder::handle_advance_object_set(int r) {
+  Mutex::Locker locker(m_lock);
+  ldout(m_cct, 20) << __func__ << ": r=" << r << dendl;
+
+  assert(m_in_flight_advance_sets > 0);
+  --m_in_flight_advance_sets;
+
+  if (r < 0 && r != -ESTALE) {
+    lderr(m_cct) << __func__ << ": failed to advance object set: "
+                 << cpp_strerror(r) << dendl;
+  }
+
+  if (m_in_flight_advance_sets == 0 && m_in_flight_object_closes == 0) {
+    open_object_set();
   }
-  m_current_set = m_journal_metadata->get_active_set();
+}
+
+void JournalRecorder::open_object_set() {
+  assert(m_lock.is_locked());
 
-  ldout(m_cct, 10) << __func__ << ": advancing to object set "
-                   << m_current_set << dendl;
+  ldout(m_cct, 10) << __func__ << ": opening object set " << m_current_set
+                   << dendl;
+
+  uint8_t splay_width = m_journal_metadata->get_splay_width();
+  for (ObjectRecorderPtrs::iterator it = m_object_ptrs.begin();
+       it != m_object_ptrs.end(); ++it) {
+    ObjectRecorderPtr object_recorder = it->second;
+    if (object_recorder->get_object_number() / splay_width != m_current_set) {
+      assert(object_recorder->is_closed());
+
+      // ready to close object and open object in active set
+      create_next_object_recorder(object_recorder);
+    }
+  }
+}
+
+bool JournalRecorder::close_object_set(uint64_t active_set) {
+  assert(m_lock.is_locked());
 
   // object recorders will invoke overflow handler as they complete
   // closing the object to ensure correct order of future appends
+  uint8_t splay_width = m_journal_metadata->get_splay_width();
   for (ObjectRecorderPtrs::iterator it = m_object_ptrs.begin();
        it != m_object_ptrs.end(); ++it) {
     ObjectRecorderPtr object_recorder = it->second;
-    if (object_recorder != NULL &&
-        object_recorder->get_object_number() / splay_width == m_current_set) {
-      if (object_recorder->close_object()) {
-        // no in-flight ops, immediately create new recorder
-        create_next_object_recorder(object_recorder);
+    if (object_recorder->get_object_number() / splay_width != active_set) {
+      ldout(m_cct, 10) << __func__ << ": closing object "
+                       << object_recorder->get_oid() << dendl;
+      // flush out all queued appends and hold future appends
+      if (!object_recorder->close()) {
+        ++m_in_flight_object_closes;
+      } else {
+        ldout(m_cct, 20) << __func__ << ": object "
+                         << object_recorder->get_oid() << " closed" << dendl;
       }
     }
   }
+  return (m_in_flight_object_closes == 0);
 }
 
 ObjectRecorderPtr JournalRecorder::create_object_recorder(
@@ -164,7 +232,7 @@ ObjectRecorderPtr JournalRecorder::create_object_recorder(
   ObjectRecorderPtr object_recorder(new ObjectRecorder(
     m_ioctx, utils::get_object_name(m_object_oid_prefix, object_number),
     object_number, m_journal_metadata->get_timer(),
-    m_journal_metadata->get_timer_lock(), &m_overflow_handler,
+    m_journal_metadata->get_timer_lock(), &m_object_handler,
     m_journal_metadata->get_order(), m_flush_interval, m_flush_bytes,
     m_flush_age));
   return object_recorder;
@@ -181,8 +249,19 @@ void JournalRecorder::create_next_object_recorder(
   ObjectRecorderPtr new_object_recorder = create_object_recorder(
      (m_current_set * splay_width) + splay_offset);
 
+  ldout(m_cct, 10) << __func__ << ": "
+                   << "old oid=" << object_recorder->get_oid() << ", "
+                   << "new oid=" << new_object_recorder->get_oid() << dendl;
   AppendBuffers append_buffers;
   object_recorder->claim_append_buffers(&append_buffers);
+
+  // update the commit record to point to the correct object number
+  for (auto &append_buffer : append_buffers) {
+    m_journal_metadata->overflow_commit_tid(
+      append_buffer.first->get_commit_tid(),
+      new_object_recorder->get_object_number());
+  }
+
   new_object_recorder->append(append_buffers);
 
   m_object_ptrs[splay_offset] = new_object_recorder;
@@ -192,8 +271,49 @@ void JournalRecorder::handle_update() {
   Mutex::Locker locker(m_lock);
 
   uint64_t active_set = m_journal_metadata->get_active_set();
-  if (active_set > m_current_set) {
-    close_object_set(m_current_set);
+  if (m_current_set < active_set) {
+    // peer journal client advanced the active set
+    ldout(m_cct, 20) << __func__ << ": "
+                     << "current_set=" << m_current_set << ", "
+                     << "active_set=" << active_set << dendl;
+
+    uint64_t current_set = m_current_set;
+    m_current_set = active_set;
+    if (m_in_flight_advance_sets == 0 && m_in_flight_object_closes == 0) {
+      ldout(m_cct, 20) << __func__ << ": closing current object set "
+                       << current_set << dendl;
+      if (close_object_set(active_set)) {
+        open_object_set();
+      }
+    }
+  }
+}
+
+void JournalRecorder::handle_closed(ObjectRecorder *object_recorder) {
+  ldout(m_cct, 10) << __func__ << ": " << object_recorder->get_oid() << dendl;
+
+  Mutex::Locker locker(m_lock);
+
+  uint64_t object_number = object_recorder->get_object_number();
+  uint8_t splay_width = m_journal_metadata->get_splay_width();
+  uint8_t splay_offset = object_number % splay_width;
+  ObjectRecorderPtr active_object_recorder = m_object_ptrs[splay_offset];
+  assert(active_object_recorder->get_object_number() == object_number);
+
+  assert(m_in_flight_object_closes > 0);
+  --m_in_flight_object_closes;
+
+  // object closed after advance active set committed
+  ldout(m_cct, 20) << __func__ << ": object "
+                   << active_object_recorder->get_oid() << " closed" << dendl;
+  if (m_in_flight_object_closes == 0) {
+    if (m_in_flight_advance_sets == 0) {
+      // peer forced closing of object set
+      open_object_set();
+    } else {
+      // local overflow advanced object set
+      advance_object_set();
+    }
   }
 }
 
@@ -208,8 +328,10 @@ void JournalRecorder::handle_overflow(ObjectRecorder *object_recorder) {
   ObjectRecorderPtr active_object_recorder = m_object_ptrs[splay_offset];
   assert(active_object_recorder->get_object_number() == object_number);
 
-  close_object_set(object_number / splay_width);
-  create_next_object_recorder(active_object_recorder);
+  ldout(m_cct, 20) << __func__ << ": object "
+                   << active_object_recorder->get_oid() << " overflowed"
+                   << dendl;
+  close_and_advance_object_set(object_number / splay_width);
 }
 
 } // namespace journal
diff --git a/src/journal/JournalRecorder.h b/src/journal/JournalRecorder.h
index be92298..68a1d8f 100644
--- a/src/journal/JournalRecorder.h
+++ b/src/journal/JournalRecorder.h
@@ -47,17 +47,32 @@ private:
     }
   };
 
-  struct OverflowHandler : public ObjectRecorder::OverflowHandler {
+  struct ObjectHandler : public ObjectRecorder::Handler {
     JournalRecorder *journal_recorder;
 
-    OverflowHandler(JournalRecorder *_journal_recorder)
-      : journal_recorder(_journal_recorder) {}
+    ObjectHandler(JournalRecorder *_journal_recorder)
+      : journal_recorder(_journal_recorder) {
+    }
 
+    virtual void closed(ObjectRecorder *object_recorder) {
+      journal_recorder->handle_closed(object_recorder);
+    }
     virtual void overflow(ObjectRecorder *object_recorder) {
       journal_recorder->handle_overflow(object_recorder);
     }
   };
 
+  struct C_AdvanceObjectSet : public Context {
+    JournalRecorder *journal_recorder;
+
+    C_AdvanceObjectSet(JournalRecorder *_journal_recorder)
+      : journal_recorder(_journal_recorder) {
+    }
+    virtual void finish(int r) {
+      journal_recorder->handle_advance_object_set(r);
+    }
+  };
+
   librados::IoCtx m_ioctx;
   CephContext *m_cct;
   std::string m_object_oid_prefix;
@@ -69,20 +84,31 @@ private:
   double m_flush_age;
 
   Listener m_listener;
-  OverflowHandler m_overflow_handler;
+  ObjectHandler m_object_handler;
 
   Mutex m_lock;
 
+  uint32_t m_in_flight_advance_sets = 0;
+  uint32_t m_in_flight_object_closes = 0;
   uint64_t m_current_set;
   ObjectRecorderPtrs m_object_ptrs;
 
   FutureImplPtr m_prev_future;
 
-  void close_object_set(uint64_t object_set);
+  void open_object_set();
+  bool close_object_set(uint64_t active_set);
+
+  void advance_object_set();
+  void handle_advance_object_set(int r);
+
+  void close_and_advance_object_set(uint64_t object_set);
+
   ObjectRecorderPtr create_object_recorder(uint64_t object_number);
   void create_next_object_recorder(ObjectRecorderPtr object_recorder);
 
   void handle_update();
+
+  void handle_closed(ObjectRecorder *object_recorder);
   void handle_overflow(ObjectRecorder *object_recorder);
 };
 
diff --git a/src/journal/JournalTrimmer.cc b/src/journal/JournalTrimmer.cc
index 74df78a..a4a47fa 100644
--- a/src/journal/JournalTrimmer.cc
+++ b/src/journal/JournalTrimmer.cc
@@ -27,10 +27,24 @@ JournalTrimmer::JournalTrimmer(librados::IoCtx &ioctx,
 }
 
 JournalTrimmer::~JournalTrimmer() {
+  assert(m_shutdown);
+}
+
+void JournalTrimmer::shut_down(Context *on_finish) {
+  ldout(m_cct, 20) << __func__ << dendl;
+  {
+    Mutex::Locker locker(m_lock);
+    assert(!m_shutdown);
+    m_shutdown = true;
+  }
+
   m_journal_metadata->remove_listener(&m_metadata_listener);
 
-  m_journal_metadata->flush_commit_position();
-  m_async_op_tracker.wait_for_ops();
+  // chain the shut down sequence (reverse order)
+  on_finish = new FunctionContext([this, on_finish](int r) {
+      m_async_op_tracker.wait_for_ops(on_finish);
+    });
+  m_journal_metadata->flush_commit_position(on_finish);
 }
 
 int JournalTrimmer::remove_objects(bool force) {
diff --git a/src/journal/JournalTrimmer.h b/src/journal/JournalTrimmer.h
index 8c57776..26bfca7 100644
--- a/src/journal/JournalTrimmer.h
+++ b/src/journal/JournalTrimmer.h
@@ -13,6 +13,8 @@
 #include "cls/journal/cls_journal_types.h"
 #include <functional>
 
+struct Context;
+
 namespace journal {
 
 class JournalTrimmer {
@@ -23,6 +25,8 @@ public:
                  const JournalMetadataPtr &journal_metadata);
   ~JournalTrimmer();
 
+  void shut_down(Context *on_finish);
+
   int remove_objects(bool force);
   void committed(uint64_t commit_tid);
 
@@ -85,6 +89,8 @@ private:
   uint64_t m_remove_set;
   Context *m_remove_set_ctx;
 
+  bool m_shutdown = false;
+
   CreateContext m_create_commit_position_safe_context = [this]() {
       return new C_CommitPositionSafe(this);
     };
diff --git a/src/journal/Journaler.cc b/src/journal/Journaler.cc
index 2a02f60..1db5247 100644
--- a/src/journal/Journaler.cc
+++ b/src/journal/Journaler.cc
@@ -28,18 +28,6 @@ namespace {
 static const std::string JOURNAL_HEADER_PREFIX = "journal.";
 static const std::string JOURNAL_OBJECT_PREFIX = "journal_data.";
 
-struct C_DeleteRecorder : public Context {
-  JournalRecorder *recorder;
-  Context *on_safe;
-  C_DeleteRecorder(JournalRecorder *_recorder, Context *_on_safe)
-    : recorder(_recorder), on_safe(_on_safe) {
-  }
-  virtual void finish(int r) {
-    delete recorder;
-    on_safe->complete(r);
-  }
-};
-
 } // anonymous namespace
 
 using namespace cls::journal;
@@ -113,10 +101,11 @@ void Journaler::set_up(ContextWQ *work_queue, SafeTimer *timer,
 
 Journaler::~Journaler() {
   if (m_metadata != nullptr) {
+    assert(!m_metadata->is_initialized());
     m_metadata->put();
     m_metadata = nullptr;
   }
-  delete m_trimmer;
+  assert(m_trimmer == nullptr);
   assert(m_player == nullptr);
   assert(m_recorder == nullptr);
 
@@ -162,7 +151,36 @@ int Journaler::init_complete() {
 }
 
 void Journaler::shut_down() {
-  m_metadata->shut_down();
+  C_SaferCond ctx;
+  shut_down(&ctx);
+  ctx.wait();
+}
+
+void Journaler::shut_down(Context *on_finish) {
+  assert(m_player == nullptr);
+  assert(m_recorder == nullptr);
+
+  JournalMetadata *metadata = nullptr;
+  std::swap(metadata, m_metadata);
+  assert(metadata != nullptr);
+
+  on_finish = new FunctionContext([metadata, on_finish](int r) {
+      metadata->put();
+      on_finish->complete(0);
+    });
+
+  JournalTrimmer *trimmer = nullptr;
+  std::swap(trimmer, m_trimmer);
+  if (trimmer == nullptr) {
+    metadata->shut_down(on_finish);
+    return;
+  }
+
+  on_finish = new FunctionContext([trimmer, metadata, on_finish](int r) {
+      delete trimmer;
+      metadata->shut_down(on_finish);
+    });
+  trimmer->shut_down(on_finish);
 }
 
 bool Journaler::is_initialized() const {
@@ -201,7 +219,9 @@ int Journaler::create(uint8_t order, uint8_t splay_width, int64_t pool_id) {
 }
 
 int Journaler::remove(bool force) {
-  m_metadata->shut_down();
+  C_SaferCond ctx;
+  m_metadata->shut_down(&ctx);
+  ctx.wait();
 
   ldout(m_cct, 5) << "removing journal: " << m_header_oid << dendl;
   int r = m_trimmer->remove_objects(force);
@@ -316,10 +336,21 @@ bool Journaler::try_pop_front(ReplayEntry *replay_entry,
 }
 
 void Journaler::stop_replay() {
-  assert(m_player != NULL);
-  m_player->unwatch();
-  delete m_player;
-  m_player = NULL;
+  C_SaferCond ctx;
+  stop_replay(&ctx);
+  ctx.wait();
+}
+
+void Journaler::stop_replay(Context *on_finish) {
+  JournalPlayer *player = nullptr;
+  std::swap(player, m_player);
+  assert(player != nullptr);
+
+  on_finish = new FunctionContext([player, on_finish](int r) {
+      delete player;
+      on_finish->complete(r);
+    });
+  player->shut_down(on_finish);
 }
 
 void Journaler::committed(const ReplayEntry &replay_entry) {
@@ -343,10 +374,15 @@ void Journaler::start_append(int flush_interval, uint64_t flush_bytes,
 }
 
 void Journaler::stop_append(Context *on_safe) {
-  assert(m_recorder != NULL);
-
-  flush_append(new C_DeleteRecorder(m_recorder, on_safe));
-  m_recorder = NULL;
+  JournalRecorder *recorder = nullptr;
+  std::swap(recorder, m_recorder);
+  assert(recorder != nullptr);
+
+  on_safe = new FunctionContext([recorder, on_safe](int r) {
+      delete recorder;
+      on_safe->complete(r);
+    });
+  recorder->flush(on_safe);
 }
 
 uint64_t Journaler::get_max_append_size() const {
diff --git a/src/journal/Journaler.h b/src/journal/Journaler.h
index f74f7ab..f30a3a5 100644
--- a/src/journal/Journaler.h
+++ b/src/journal/Journaler.h
@@ -62,6 +62,7 @@ public:
 
   void init(Context *on_init);
   void shut_down();
+  void shut_down(Context *on_finish);
 
   bool is_initialized() const;
 
@@ -95,6 +96,7 @@ public:
   void start_live_replay(ReplayHandler *replay_handler, double interval);
   bool try_pop_front(ReplayEntry *replay_entry, uint64_t *tag_tid = nullptr);
   void stop_replay();
+  void stop_replay(Context *on_finish);
 
   uint64_t get_max_append_size() const;
   void start_append(int flush_interval, uint64_t flush_bytes, double flush_age);
diff --git a/src/journal/ObjectPlayer.cc b/src/journal/ObjectPlayer.cc
index 894b56f..f86e3ef 100644
--- a/src/journal/ObjectPlayer.cc
+++ b/src/journal/ObjectPlayer.cc
@@ -21,8 +21,7 @@ ObjectPlayer::ObjectPlayer(librados::IoCtx &ioctx,
     m_cct(NULL), m_timer(timer), m_timer_lock(timer_lock), m_order(order),
     m_watch_interval(0), m_watch_task(NULL),
     m_lock(utils::unique_lock_name("ObjectPlayer::m_lock", this)),
-    m_fetch_in_progress(false), m_read_off(0), m_watch_ctx(NULL),
-    m_watch_in_progress(false) {
+    m_fetch_in_progress(false), m_read_off(0) {
   m_ioctx.dup(ioctx);
   m_cct = reinterpret_cast<CephContext*>(m_ioctx.cct());
 }
@@ -32,8 +31,7 @@ ObjectPlayer::~ObjectPlayer() {
     Mutex::Locker timer_locker(m_timer_lock);
     Mutex::Locker locker(m_lock);
     assert(!m_fetch_in_progress);
-    assert(!m_watch_in_progress);
-    assert(m_watch_ctx == NULL);
+    assert(m_watch_ctx == nullptr);
   }
 }
 
@@ -62,13 +60,10 @@ void ObjectPlayer::watch(Context *on_fetch, double interval) {
   Mutex::Locker timer_locker(m_timer_lock);
   m_watch_interval = interval;
 
-  assert(m_watch_ctx == NULL);
+  assert(m_watch_ctx == nullptr);
   m_watch_ctx = on_fetch;
 
-  // watch callback might lead to re-scheduled watch
-  if (!m_watch_in_progress) {
-    schedule_watch();
-  }
+  schedule_watch();
 }
 
 void ObjectPlayer::unwatch() {
@@ -76,13 +71,14 @@ void ObjectPlayer::unwatch() {
   Context *watch_ctx = nullptr;
   {
     Mutex::Locker timer_locker(m_timer_lock);
+    assert(!m_unwatched);
+    m_unwatched = true;
 
-    cancel_watch();
+    if (!cancel_watch()) {
+      return;
+    }
 
     std::swap(watch_ctx, m_watch_ctx);
-    while (m_watch_in_progress) {
-      m_watch_in_progress_cond.Wait(m_timer_lock);
-    }
   }
 
   if (watch_ctx != nullptr) {
@@ -190,24 +186,27 @@ void ObjectPlayer::schedule_watch() {
   m_timer.add_event_after(m_watch_interval, m_watch_task);
 }
 
-void ObjectPlayer::cancel_watch() {
+bool ObjectPlayer::cancel_watch() {
   assert(m_timer_lock.is_locked());
   ldout(m_cct, 20) << __func__ << ": " << m_oid << " cancelling watch" << dendl;
-  if (m_watch_task != NULL) {
-    m_timer.cancel_event(m_watch_task);
-    m_watch_task = NULL;
+  if (m_watch_task != nullptr) {
+    bool canceled = m_timer.cancel_event(m_watch_task);
+    assert(canceled);
+
+    m_watch_task = nullptr;
+    return true;
   }
+  return false;
 }
 
 void ObjectPlayer::handle_watch_task() {
   assert(m_timer_lock.is_locked());
 
   ldout(m_cct, 10) << __func__ << ": " << m_oid << " polling" << dendl;
-  assert(m_watch_ctx != NULL);
+  assert(m_watch_ctx != nullptr);
+  assert(m_watch_task != nullptr);
 
-  assert(!m_watch_in_progress);
-  m_watch_in_progress = true;
-  m_watch_task = NULL;
+  m_watch_task = nullptr;
   fetch(new C_WatchFetch(this));
 }
 
@@ -215,36 +214,31 @@ void ObjectPlayer::handle_watch_fetched(int r) {
   ldout(m_cct, 10) << __func__ << ": " << m_oid << " poll complete, r=" << r
                    << dendl;
 
-  Context *on_finish = nullptr;
+  Context *watch_ctx = nullptr;
   {
     Mutex::Locker timer_locker(m_timer_lock);
-    assert(m_watch_in_progress);
     if (r == -ENOENT) {
       r = 0;
+    } else {
+      m_refetch_required = true;
     }
-    std::swap(on_finish, m_watch_ctx);
-  }
-
-  if (on_finish != nullptr) {
-    on_finish->complete(r);
-  }
-
-  {
-    Mutex::Locker locker(m_timer_lock);
-    assert(m_watch_in_progress);
+    std::swap(watch_ctx, m_watch_ctx);
 
-    // callback might have attempted to re-schedule the watch -- complete now
-    if (m_watch_ctx != nullptr) {
-      schedule_watch();
+    if (m_unwatched) {
+      m_unwatched = false;
+      r = -ECANCELED;
     }
+  }
 
-    m_watch_in_progress = false;
-    m_watch_in_progress_cond.Signal();
+  if (watch_ctx != nullptr) {
+    watch_ctx->complete(r);
   }
 }
 
 void ObjectPlayer::C_Fetch::finish(int r) {
   r = object_player->handle_fetch_complete(r, read_bl);
+  object_player.reset();
+
   on_finish->complete(r);
 }
 
diff --git a/src/journal/ObjectPlayer.h b/src/journal/ObjectPlayer.h
index f68ee37..d0809ce 100644
--- a/src/journal/ObjectPlayer.h
+++ b/src/journal/ObjectPlayer.h
@@ -62,6 +62,13 @@ public:
     *invalid_ranges = m_invalid_ranges;
   }
 
+  inline bool refetch_required() const {
+    return m_refetch_required;
+  }
+  inline void clear_refetch_required() {
+    m_refetch_required = false;
+  }
+
 private:
   typedef std::pair<uint64_t, uint64_t> EntryKey;
   typedef boost::unordered_map<EntryKey, Entries::iterator> EntryKeys;
@@ -110,14 +117,15 @@ private:
   EntryKeys m_entry_keys;
   InvalidRanges m_invalid_ranges;
 
-  Context *m_watch_ctx;
-  Cond m_watch_in_progress_cond;
-  bool m_watch_in_progress;
+  Context *m_watch_ctx = nullptr;
+
+  bool m_unwatched = false;
+  bool m_refetch_required = true;
 
   int handle_fetch_complete(int r, const bufferlist &bl);
 
   void schedule_watch();
-  void cancel_watch();
+  bool cancel_watch();
   void handle_watch_task();
   void handle_watch_fetched(int r);
 };
diff --git a/src/journal/ObjectRecorder.cc b/src/journal/ObjectRecorder.cc
index c7f62b4..5972d89 100644
--- a/src/journal/ObjectRecorder.cc
+++ b/src/journal/ObjectRecorder.cc
@@ -19,26 +19,26 @@ namespace journal {
 ObjectRecorder::ObjectRecorder(librados::IoCtx &ioctx, const std::string &oid,
                                uint64_t object_number,
                                SafeTimer &timer, Mutex &timer_lock,
-                               OverflowHandler *overflow_handler, uint8_t order,
+                               Handler *handler, uint8_t order,
                                uint32_t flush_interval, uint64_t flush_bytes,
                                double flush_age)
   : RefCountedObject(NULL, 0), m_oid(oid), m_object_number(object_number),
     m_cct(NULL), m_timer(timer), m_timer_lock(timer_lock),
-    m_overflow_handler(overflow_handler), m_order(order),
-    m_soft_max_size(1 << m_order), m_flush_interval(flush_interval),
-    m_flush_bytes(flush_bytes), m_flush_age(flush_age), m_flush_handler(this),
-    m_append_task(NULL),
+    m_handler(handler), m_order(order), m_soft_max_size(1 << m_order),
+    m_flush_interval(flush_interval), m_flush_bytes(flush_bytes),
+    m_flush_age(flush_age), m_flush_handler(this), m_append_task(NULL),
     m_lock(utils::unique_lock_name("ObjectRecorder::m_lock", this)),
     m_append_tid(0), m_pending_bytes(0), m_size(0), m_overflowed(false),
     m_object_closed(false), m_in_flight_flushes(false) {
   m_ioctx.dup(ioctx);
   m_cct = reinterpret_cast<CephContext*>(m_ioctx.cct());
-  assert(m_overflow_handler != NULL);
+  assert(m_handler != NULL);
 }
 
 ObjectRecorder::~ObjectRecorder() {
   assert(m_append_task == NULL);
   assert(m_append_buffers.empty());
+  assert(m_in_flight_tids.empty());
   assert(m_in_flight_appends.empty());
 }
 
@@ -68,7 +68,8 @@ bool ObjectRecorder::append(const AppendBuffers &append_buffers) {
   } else {
     cancel_append_task();
   }
-  return (m_size + m_pending_bytes >= m_soft_max_size);
+  return (!m_object_closed && !m_overflowed &&
+          m_size + m_pending_bytes >= m_soft_max_size);
 }
 
 void ObjectRecorder::flush(Context *on_safe) {
@@ -119,7 +120,10 @@ void ObjectRecorder::flush(const FutureImplPtr &future) {
     return;
   }
 
-  assert(!m_object_closed);
+  if (m_object_closed || m_overflowed) {
+    return;
+  }
+
   AppendBuffers::iterator it;
   for (it = m_append_buffers.begin(); it != m_append_buffers.end(); ++it) {
     if (it->first == future) {
@@ -139,21 +143,24 @@ void ObjectRecorder::claim_append_buffers(AppendBuffers *append_buffers) {
   ldout(m_cct, 20) << __func__ << ": " << m_oid << dendl;
 
   Mutex::Locker locker(m_lock);
+  assert(m_in_flight_tids.empty());
   assert(m_in_flight_appends.empty());
   assert(m_object_closed || m_overflowed);
   append_buffers->splice(append_buffers->end(), m_append_buffers,
                          m_append_buffers.begin(), m_append_buffers.end());
 }
 
-bool ObjectRecorder::close_object() {
+bool ObjectRecorder::close() {
   ldout(m_cct, 20) << __func__ << ": " << m_oid << dendl;
 
   cancel_append_task();
 
   Mutex::Locker locker(m_lock);
-  m_object_closed = true;
   flush_appends(true);
-  return m_in_flight_appends.empty();
+
+  assert(!m_object_closed);
+  m_object_closed = true;
+  return m_in_flight_tids.empty();
 }
 
 void ObjectRecorder::handle_append_task() {
@@ -184,7 +191,11 @@ bool ObjectRecorder::append(const AppendBuffer &append_buffer,
                             bool *schedule_append) {
   assert(m_lock.is_locked());
 
-  bool flush_requested = append_buffer.first->attach(&m_flush_handler);
+  bool flush_requested = false;
+  if (!m_object_closed && !m_overflowed) {
+    flush_requested = append_buffer.first->attach(&m_flush_handler);
+  }
+
   m_append_buffers.push_back(append_buffer);
   m_pending_bytes += append_buffer.second.length();
 
@@ -222,25 +233,35 @@ void ObjectRecorder::handle_append_flushed(uint64_t tid, int r) {
   AppendBuffers append_buffers;
   {
     Mutex::Locker locker(m_lock);
+    auto tid_iter = m_in_flight_tids.find(tid);
+    assert(tid_iter != m_in_flight_tids.end());
+    m_in_flight_tids.erase(tid_iter);
+
     InFlightAppends::iterator iter = m_in_flight_appends.find(tid);
-    if (iter == m_in_flight_appends.end()) {
-      // must have seen an overflow on a previous append op
-      assert(m_overflowed);
-      return;
-    } else if (r == -EOVERFLOW) {
-      m_overflowed = true;
-      append_overflowed(tid);
+    if (r == -EOVERFLOW || m_overflowed) {
+      if (iter != m_in_flight_appends.end()) {
+        m_overflowed = true;
+        append_overflowed(tid);
+      } else {
+        // must have seen an overflow on a previous append op
+        assert(r == -EOVERFLOW && m_overflowed);
+      }
+
+      // notify of overflow once all in-flight ops are complete
+      if (m_in_flight_tids.empty()) {
+        notify_handler();
+      }
       return;
     }
 
-    assert(!m_overflowed || r != 0);
+    assert(iter != m_in_flight_appends.end());
     append_buffers.swap(iter->second);
     assert(!append_buffers.empty());
 
     m_in_flight_appends.erase(iter);
     if (m_in_flight_appends.empty() && m_object_closed) {
       // all remaining unsent appends should be redirected to new object
-      notify_overflow();
+      notify_handler();
     }
     m_in_flight_flushes = true;
   }
@@ -284,7 +305,6 @@ void ObjectRecorder::append_overflowed(uint64_t tid) {
                                 m_append_buffers.begin(),
                                 m_append_buffers.end());
   restart_append_buffers.swap(m_append_buffers);
-  notify_overflow();
 }
 
 void ObjectRecorder::send_appends(AppendBuffers *append_buffers) {
@@ -308,6 +328,7 @@ void ObjectRecorder::send_appends(AppendBuffers *append_buffers) {
     op.set_op_flags2(CEPH_OSD_OP_FLAG_FADVISE_DONTNEED);
     m_size += it->second.length();
   }
+  m_in_flight_tids.insert(append_tid);
   m_in_flight_appends[append_tid].swap(*append_buffers);
 
   librados::AioCompletion *rados_completion =
@@ -318,7 +339,7 @@ void ObjectRecorder::send_appends(AppendBuffers *append_buffers) {
   rados_completion->release();
 }
 
-void ObjectRecorder::notify_overflow() {
+void ObjectRecorder::notify_handler() {
   assert(m_lock.is_locked());
 
   for (AppendBuffers::const_iterator it = m_append_buffers.begin();
@@ -328,10 +349,16 @@ void ObjectRecorder::notify_overflow() {
     it->first->detach();
   }
 
-  // TODO need to delay completion until after aio_notify completes
-  m_lock.Unlock();
-  m_overflow_handler->overflow(this);
-  m_lock.Lock();
+  if (m_object_closed) {
+    m_lock.Unlock();
+    m_handler->closed(this);
+    m_lock.Lock();
+  } else {
+    // TODO need to delay completion until after aio_notify completes
+    m_lock.Unlock();
+    m_handler->overflow(this);
+    m_lock.Lock();
+  }
 }
 
 } // namespace journal
diff --git a/src/journal/ObjectRecorder.h b/src/journal/ObjectRecorder.h
index 2cc8541..53f8cc9 100644
--- a/src/journal/ObjectRecorder.h
+++ b/src/journal/ObjectRecorder.h
@@ -11,6 +11,8 @@
 #include "common/RefCountedObj.h"
 #include "journal/FutureImpl.h"
 #include <list>
+#include <map>
+#include <set>
 #include <boost/intrusive_ptr.hpp>
 #include <boost/noncopyable.hpp>
 #include "include/assert.h"
@@ -27,16 +29,17 @@ typedef std::list<AppendBuffer> AppendBuffers;
 
 class ObjectRecorder : public RefCountedObject, boost::noncopyable {
 public:
-  struct OverflowHandler {
-    virtual ~OverflowHandler() {}
+  struct Handler {
+    virtual ~Handler() {
+    }
+    virtual void closed(ObjectRecorder *object_recorder) = 0;
     virtual void overflow(ObjectRecorder *object_recorder) = 0;
   };
 
   ObjectRecorder(librados::IoCtx &ioctx, const std::string &oid,
                  uint64_t object_number, SafeTimer &timer, Mutex &timer_lock,
-                 OverflowHandler *overflow_handler, uint8_t order,
-                 uint32_t flush_interval, uint64_t flush_bytes,
-                 double flush_age);
+                 Handler *handler, uint8_t order, uint32_t flush_interval,
+                 uint64_t flush_bytes, double flush_age);
   ~ObjectRecorder();
 
   inline uint64_t get_object_number() const {
@@ -51,7 +54,12 @@ public:
   void flush(const FutureImplPtr &future);
 
   void claim_append_buffers(AppendBuffers *append_buffers);
-  bool close_object();
+
+  bool is_closed() const {
+    Mutex::Locker locker(m_lock);
+    return (m_object_closed && m_in_flight_appends.empty());
+  }
+  bool close();
 
   inline CephContext *cct() const {
     return m_cct;
@@ -63,6 +71,7 @@ public:
   }
 
 private:
+  typedef std::set<uint64_t> InFlightTids;
   typedef std::map<uint64_t, AppendBuffers> InFlightAppends;
 
   struct FlushHandler : public FutureImpl::FlushHandler {
@@ -107,7 +116,7 @@ private:
   SafeTimer &m_timer;
   Mutex &m_timer_lock;
 
-  OverflowHandler *m_overflow_handler;
+  Handler *m_handler;
 
   uint8_t m_order;
   uint64_t m_soft_max_size;
@@ -125,6 +134,7 @@ private:
   uint64_t m_append_tid;
   uint32_t m_pending_bytes;
 
+  InFlightTids m_in_flight_tids;
   InFlightAppends m_in_flight_appends;
   uint64_t m_size;
   bool m_overflowed;
@@ -145,7 +155,7 @@ private:
   void append_overflowed(uint64_t tid);
   void send_appends(AppendBuffers *append_buffers);
 
-  void notify_overflow();
+  void notify_handler();
 };
 
 } // namespace journal
diff --git a/src/journal/Utils.h b/src/journal/Utils.h
index e29f359..b0cee75 100644
--- a/src/journal/Utils.h
+++ b/src/journal/Utils.h
@@ -5,12 +5,30 @@
 #define CEPH_JOURNAL_UTILS_H
 
 #include "include/int_types.h"
+#include "include/Context.h"
 #include "include/rados/librados.hpp"
 #include <string>
 
 namespace journal {
 namespace utils {
 
+namespace detail {
+
+template <typename M>
+struct C_AsyncCallback : public Context {
+  M journal_metadata;
+  Context *on_finish;
+
+  C_AsyncCallback(M journal_metadata, Context *on_finish)
+    : journal_metadata(journal_metadata), on_finish(on_finish) {
+  }
+  virtual void finish(int r) {
+    journal_metadata->queue(on_finish, r);
+  }
+};
+
+} // namespace detail
+
 template <typename T, void(T::*MF)(int)>
 void rados_state_callback(rados_completion_t c, void *arg) {
   T *obj = reinterpret_cast<T*>(arg);
@@ -24,6 +42,12 @@ std::string unique_lock_name(const std::string &name, void *address);
 
 void rados_ctx_callback(rados_completion_t c, void *arg);
 
+template <typename M>
+Context *create_async_context_callback(M journal_metadata, Context *on_finish) {
+  // use async callback to acquire a clean lock context
+  return new detail::C_AsyncCallback<M>(journal_metadata, on_finish);
+}
+
 } // namespace utils
 } // namespace journal
 
diff --git a/src/librbd/AioCompletion.cc b/src/librbd/AioCompletion.cc
index 3501032..1e892ac 100644
--- a/src/librbd/AioCompletion.cc
+++ b/src/librbd/AioCompletion.cc
@@ -31,15 +31,19 @@ namespace librbd {
   int AioCompletion::wait_for_complete() {
     tracepoint(librbd, aio_wait_for_complete_enter, this);
     lock.Lock();
-    while (!done)
+    while (state != STATE_COMPLETE)
       cond.Wait(lock);
     lock.Unlock();
     tracepoint(librbd, aio_wait_for_complete_exit, 0);
     return 0;
   }
 
-  void AioCompletion::finalize(CephContext *cct, ssize_t rval)
+  void AioCompletion::finalize(ssize_t rval)
   {
+    assert(lock.is_locked());
+    assert(ictx != nullptr);
+    CephContext *cct = ictx->cct;
+
     ldout(cct, 20) << this << " " << __func__ << ": r=" << rval << ", "
                    << "read_buf=" << reinterpret_cast<void*>(read_buf) << ", "
                    << "real_bl=" <<  reinterpret_cast<void*>(read_bl) << dendl;
@@ -67,10 +71,13 @@ namespace librbd {
     }
   }
 
-  void AioCompletion::complete(CephContext *cct) {
+  void AioCompletion::complete() {
+    assert(lock.is_locked());
+    assert(ictx != nullptr);
+    CephContext *cct = ictx->cct;
+
     tracepoint(librbd, aio_complete_enter, this, rval);
     utime_t elapsed;
-    assert(lock.is_locked());
     elapsed = ceph_clock_now(cct) - start_time;
     switch (aio_type) {
     case AIO_TYPE_OPEN:
@@ -95,7 +102,7 @@ namespace librbd {
       ictx->journal->commit_io_event(journal_tid, rval);
     }
 
-    done = true;
+    state = STATE_CALLBACK;
     if (complete_cb) {
       lock.Unlock();
       complete_cb(rbd_comp, complete_arg);
@@ -108,6 +115,8 @@ namespace librbd {
       ictx->completed_reqs_lock.Unlock();
       ictx->event_socket.notify();
     }
+
+    state = STATE_COMPLETE;
     cond.Signal();
 
     // note: possible for image to be closed after op marked finished
@@ -118,47 +127,57 @@ namespace librbd {
   }
 
   void AioCompletion::init_time(ImageCtx *i, aio_type_t t) {
-    if (ictx == NULL) {
+    Mutex::Locker locker(lock);
+    if (ictx == nullptr) {
       ictx = i;
       aio_type = t;
       start_time = ceph_clock_now(ictx->cct);
     }
   }
 
-  void AioCompletion::start_op(ImageCtx *i, aio_type_t t) {
-    init_time(i, t);
-
+  void AioCompletion::start_op(bool ignore_type) {
     Mutex::Locker locker(lock);
-    if (!done && !async_op.started()) {
+    assert(ictx != nullptr);
+    assert(!async_op.started());
+    if (state == STATE_PENDING && (ignore_type || aio_type != AIO_TYPE_FLUSH)) {
       async_op.start_op(*ictx);
     }
   }
 
-  void AioCompletion::fail(CephContext *cct, int r)
+  void AioCompletion::fail(int r)
   {
+    lock.Lock();
+    assert(ictx != nullptr);
+    CephContext *cct = ictx->cct;
+
     lderr(cct) << this << " " << __func__ << ": " << cpp_strerror(r)
                << dendl;
-    lock.Lock();
     assert(pending_count == 0);
     rval = r;
-    complete(cct);
+    complete();
     put_unlock();
   }
 
-  void AioCompletion::set_request_count(CephContext *cct, uint32_t count) {
-    ldout(cct, 20) << this << " " << __func__ << ": pending=" << count << dendl;
+  void AioCompletion::set_request_count(uint32_t count) {
     lock.Lock();
+    assert(ictx != nullptr);
+    CephContext *cct = ictx->cct;
+
+    ldout(cct, 20) << this << " " << __func__ << ": pending=" << count << dendl;
     assert(pending_count == 0);
     pending_count = count;
     lock.Unlock();
 
     // if no pending requests, completion will fire now
-    unblock(cct);
+    unblock();
   }
 
-  void AioCompletion::complete_request(CephContext *cct, ssize_t r)
+  void AioCompletion::complete_request(ssize_t r)
   {
     lock.Lock();
+    assert(ictx != nullptr);
+    CephContext *cct = ictx->cct;
+
     if (rval >= 0) {
       if (r < 0 && r != -EEXIST)
 	rval = r;
@@ -171,15 +190,15 @@ namespace librbd {
     ldout(cct, 20) << this << " " << __func__ << ": cb=" << complete_cb << ", "
                    << "pending=" << pending_count << dendl;
     if (!count && blockers == 0) {
-      finalize(cct, rval);
-      complete(cct);
+      finalize(rval);
+      complete();
     }
     put_unlock();
   }
 
   void AioCompletion::associate_journal_event(uint64_t tid) {
     Mutex::Locker l(lock);
-    assert(!done);
+    assert(state == STATE_PENDING);
     journal_tid = tid;
   }
 
@@ -188,7 +207,7 @@ namespace librbd {
     bool done;
     {
       Mutex::Locker l(lock);
-      done = this->done;
+      done = this->state == STATE_COMPLETE;
     }
     tracepoint(librbd, aio_is_complete_exit, done);
     return done;
@@ -205,24 +224,27 @@ namespace librbd {
 
   void C_AioRead::finish(int r)
   {
-    ldout(m_cct, 10) << "C_AioRead::finish() " << this << " r = " << r << dendl;
+    m_completion->lock.Lock();
+    CephContext *cct = m_completion->ictx->cct;
+    ldout(cct, 10) << "C_AioRead::finish() " << this << " r = " << r << dendl;
+
     if (r >= 0 || r == -ENOENT) { // this was a sparse_read operation
-      ldout(m_cct, 10) << " got " << m_req->m_ext_map
-		       << " for " << m_req->m_buffer_extents
-		       << " bl " << m_req->data().length() << dendl;
+      ldout(cct, 10) << " got " << m_req->m_ext_map
+		     << " for " << m_req->m_buffer_extents
+		     << " bl " << m_req->data().length() << dendl;
       // reads from the parent don't populate the m_ext_map and the overlap
       // may not be the full buffer.  compensate here by filling in m_ext_map
       // with the read extent when it is empty.
       if (m_req->m_ext_map.empty())
 	m_req->m_ext_map[m_req->m_object_off] = m_req->data().length();
 
-      m_completion->lock.Lock();
       m_completion->destriper.add_partial_sparse_result(
-	  m_cct, m_req->data(), m_req->m_ext_map, m_req->m_object_off,
+	  cct, m_req->data(), m_req->m_ext_map, m_req->m_object_off,
 	  m_req->m_buffer_extents);
-      m_completion->lock.Unlock();
       r = m_req->m_object_len;
     }
+    m_completion->lock.Unlock();
+
     C_AioRequest::finish(r);
   }
 
diff --git a/src/librbd/AioCompletion.h b/src/librbd/AioCompletion.h
index c554bc5..758b1d8 100644
--- a/src/librbd/AioCompletion.h
+++ b/src/librbd/AioCompletion.h
@@ -30,6 +30,12 @@ namespace librbd {
     AIO_TYPE_FLUSH,
   } aio_type_t;
 
+  typedef enum {
+    STATE_PENDING = 0,
+    STATE_CALLBACK,
+    STATE_COMPLETE,
+  } aio_state_t;
+
   /**
    * AioCompletion is the overall completion for a single
    * rbd I/O request. It may be composed of many AioObjectRequests,
@@ -46,7 +52,7 @@ namespace librbd {
   struct AioCompletion {
     Mutex lock;
     Cond cond;
-    bool done;
+    aio_state_t state;
     ssize_t rval;
     callback_t complete_cb;
     void *complete_arg;
@@ -95,7 +101,7 @@ namespace librbd {
     }
 
     AioCompletion() : lock("AioCompletion::lock", true, false),
-		      done(false), rval(0), complete_cb(NULL),
+		      state(STATE_PENDING), rval(0), complete_cb(NULL),
 		      complete_arg(NULL), rbd_comp(NULL),
 		      pending_count(0), blockers(1),
 		      ref(1), released(false), ictx(NULL),
@@ -109,27 +115,27 @@ namespace librbd {
 
     int wait_for_complete();
 
-    void finalize(CephContext *cct, ssize_t rval);
+    void finalize(ssize_t rval);
 
     void init_time(ImageCtx *i, aio_type_t t);
-    void start_op(ImageCtx *i, aio_type_t t);
-    void fail(CephContext *cct, int r);
+    void start_op(bool ignore_type = false);
+    void fail(int r);
 
-    void complete(CephContext *cct);
+    void complete();
 
     void set_complete_cb(void *cb_arg, callback_t cb) {
       complete_cb = cb;
       complete_arg = cb_arg;
     }
 
-    void set_request_count(CephContext *cct, uint32_t num);
+    void set_request_count(uint32_t num);
     void add_request() {
       lock.Lock();
       assert(pending_count > 0);
       lock.Unlock();
       get();
     }
-    void complete_request(CephContext *cct, ssize_t r);
+    void complete_request(ssize_t r);
 
     void associate_journal_event(uint64_t tid);
 
@@ -177,13 +183,13 @@ namespace librbd {
       Mutex::Locker l(lock);
       ++blockers;
     }
-    void unblock(CephContext *cct) {
+    void unblock() {
       Mutex::Locker l(lock);
       assert(blockers > 0);
       --blockers;
       if (pending_count == 0 && blockers == 0) {
-        finalize(cct, rval);
-        complete(cct);
+        finalize(rval);
+        complete();
       }
     }
 
@@ -199,23 +205,21 @@ namespace librbd {
 
   class C_AioRequest : public Context {
   public:
-    C_AioRequest(CephContext *cct, AioCompletion *completion)
-      : m_cct(cct), m_completion(completion) {
+    C_AioRequest(AioCompletion *completion) : m_completion(completion) {
       m_completion->add_request();
     }
     virtual ~C_AioRequest() {}
     virtual void finish(int r) {
-      m_completion->complete_request(m_cct, r);
+      m_completion->complete_request(r);
     }
   protected:
-    CephContext *m_cct;
     AioCompletion *m_completion;
   };
 
   class C_AioRead : public C_AioRequest {
   public:
-    C_AioRead(CephContext *cct, AioCompletion *completion)
-      : C_AioRequest(cct, completion), m_req(NULL) {
+    C_AioRead(AioCompletion *completion)
+      : C_AioRequest(completion), m_req(nullptr) {
     }
     virtual ~C_AioRead() {}
     virtual void finish(int r);
diff --git a/src/librbd/AioImageRequest.cc b/src/librbd/AioImageRequest.cc
index 5a1904b..1877af1 100644
--- a/src/librbd/AioImageRequest.cc
+++ b/src/librbd/AioImageRequest.cc
@@ -48,7 +48,7 @@ struct C_DiscardJournalCommit : public Context {
 
     Mutex::Locker cache_locker(image_ctx.cache_lock);
     image_ctx.object_cacher->discard_set(image_ctx.object_set, object_extents);
-    aio_comp->complete_request(cct, r);
+    aio_comp->complete_request(r);
   }
 };
 
@@ -71,7 +71,7 @@ struct C_FlushJournalCommit : public Context {
     CephContext *cct = image_ctx.cct;
     ldout(cct, 20) << this << " C_FlushJournalCommit: journal committed"
                    << dendl;
-    aio_comp->complete_request(cct, r);
+    aio_comp->complete_request(r);
   }
 };
 
@@ -82,7 +82,10 @@ void AioImageRequest<I>::aio_read(
     I *ictx, AioCompletion *c,
     const std::vector<std::pair<uint64_t,uint64_t> > &extents,
     char *buf, bufferlist *pbl, int op_flags) {
+  c->init_time(ictx, librbd::AIO_TYPE_READ);
+
   AioImageRead req(*ictx, c, extents, buf, pbl, op_flags);
+  req.start_op();
   req.send();
 }
 
@@ -90,7 +93,10 @@ template <typename I>
 void AioImageRequest<I>::aio_read(I *ictx, AioCompletion *c,
                                   uint64_t off, size_t len, char *buf,
                                   bufferlist *pbl, int op_flags) {
+  c->init_time(ictx, librbd::AIO_TYPE_READ);
+
   AioImageRead req(*ictx, c, off, len, buf, pbl, op_flags);
+  req.start_op();
   req.send();
 }
 
@@ -98,20 +104,29 @@ template <typename I>
 void AioImageRequest<I>::aio_write(I *ictx, AioCompletion *c,
                                    uint64_t off, size_t len, const char *buf,
                                    int op_flags) {
+  c->init_time(ictx, librbd::AIO_TYPE_WRITE);
+
   AioImageWrite req(*ictx, c, off, len, buf, op_flags);
+  req.start_op();
   req.send();
 }
 
 template <typename I>
 void AioImageRequest<I>::aio_discard(I *ictx, AioCompletion *c,
                                      uint64_t off, uint64_t len) {
+  c->init_time(ictx, librbd::AIO_TYPE_DISCARD);
+
   AioImageDiscard req(*ictx, c, off, len);
+  req.start_op();
   req.send();
 }
 
 template <typename I>
 void AioImageRequest<I>::aio_flush(I *ictx, AioCompletion *c) {
+  c->init_time(ictx, librbd::AIO_TYPE_FLUSH);
+
   AioImageFlush req(*ictx, c);
+  req.start_op();
   req.send();
 }
 
@@ -130,7 +145,7 @@ void AioImageRequest<I>::send() {
 template <typename I>
 void AioImageRequest<I>::fail(int r) {
   m_aio_comp->get();
-  m_aio_comp->fail(m_image_ctx.cct, r);
+  m_aio_comp->fail(r);
 }
 
 void AioImageRead::send_request() {
@@ -157,7 +172,7 @@ void AioImageRead::send_request() {
       uint64_t len = p->second;
       int r = clip_io(&m_image_ctx, p->first, &len);
       if (r < 0) {
-        m_aio_comp->fail(cct, r);
+        m_aio_comp->fail(r);
         return;
       }
       if (len == 0) {
@@ -169,8 +184,6 @@ void AioImageRead::send_request() {
                                object_extents, buffer_ofs);
       buffer_ofs += len;
     }
-
-    m_aio_comp->start_op(&m_image_ctx, AIO_TYPE_READ);
   }
 
   m_aio_comp->read_buf = m_buf;
@@ -182,7 +195,7 @@ void AioImageRead::send_request() {
   for (auto &object_extent : object_extents) {
     request_count += object_extent.second.size();
   }
-  m_aio_comp->set_request_count(cct, request_count);
+  m_aio_comp->set_request_count(request_count);
 
   // issue the requests
   for (auto &object_extent : object_extents) {
@@ -191,7 +204,7 @@ void AioImageRead::send_request() {
                      << extent.length << " from " << extent.buffer_extents
                      << dendl;
 
-      C_AioRead *req_comp = new C_AioRead(cct, m_aio_comp);
+      C_AioRead *req_comp = new C_AioRead(m_aio_comp);
       AioObjectRead *req = new AioObjectRead(&m_image_ctx, extent.oid.name,
                                              extent.objectno, extent.offset,
                                              extent.length,
@@ -231,18 +244,17 @@ void AbstractAioImageWrite::send_request() {
     // pending async operation
     RWLock::RLocker snap_locker(m_image_ctx.snap_lock);
     if (m_image_ctx.snap_id != CEPH_NOSNAP || m_image_ctx.read_only) {
-      m_aio_comp->fail(cct, -EROFS);
+      m_aio_comp->fail(-EROFS);
       return;
     }
 
     int r = clip_io(&m_image_ctx, m_off, &clip_len);
     if (r < 0) {
-      m_aio_comp->fail(cct, r);
+      m_aio_comp->fail(r);
       return;
     }
 
     snapc = m_image_ctx.snapc;
-    m_aio_comp->start_op(&m_image_ctx, get_aio_type());
 
     // map to object extents
     if (clip_len > 0) {
@@ -258,7 +270,7 @@ void AbstractAioImageWrite::send_request() {
   if (!object_extents.empty()) {
     uint64_t journal_tid = 0;
     m_aio_comp->set_request_count(
-      cct, object_extents.size() + get_cache_request_count(journaling));
+      object_extents.size() + get_cache_request_count(journaling));
 
     AioObjectRequests requests;
     send_object_requests(object_extents, snapc,
@@ -275,7 +287,7 @@ void AbstractAioImageWrite::send_request() {
     }
   } else {
     // no IO to perform -- fire completion
-    m_aio_comp->unblock(cct);
+    m_aio_comp->unblock();
   }
 
   update_stats(clip_len);
@@ -291,7 +303,7 @@ void AbstractAioImageWrite::send_object_requests(
        p != object_extents.end(); ++p) {
     ldout(cct, 20) << " oid " << p->oid << " " << p->offset << "~" << p->length
                    << " from " << p->buffer_extents << dendl;
-    C_AioRequest *req_comp = new C_AioRequest(cct, m_aio_comp);
+    C_AioRequest *req_comp = new C_AioRequest(m_aio_comp);
     AioObjectRequest *request = create_object_request(*p, snapc, req_comp);
 
     // if journaling, stash the request for later; otherwise send
@@ -318,9 +330,8 @@ uint64_t AioImageWrite::append_journal_event(
   bufferlist bl;
   bl.append(m_buf, m_len);
 
-  uint64_t tid = m_image_ctx.journal->append_write_event(m_aio_comp, m_off,
-                                                         m_len, bl, requests,
-                                                         synchronous);
+  uint64_t tid = m_image_ctx.journal->append_write_event(m_off, m_len, bl,
+                                                         requests, synchronous);
   if (m_image_ctx.object_cacher == NULL) {
     m_aio_comp->associate_journal_event(tid);
   }
@@ -329,7 +340,6 @@ uint64_t AioImageWrite::append_journal_event(
 
 void AioImageWrite::send_cache_requests(const ObjectExtents &object_extents,
                                         uint64_t journal_tid) {
-  CephContext *cct = m_image_ctx.cct;
   for (ObjectExtents::const_iterator p = object_extents.begin();
        p != object_extents.end(); ++p) {
     const ObjectExtent &object_extent = *p;
@@ -337,7 +347,7 @@ void AioImageWrite::send_cache_requests(const ObjectExtents &object_extents,
     bufferlist bl;
     assemble_extent(object_extent, &bl);
 
-    C_AioRequest *req_comp = new C_AioRequest(cct, m_aio_comp);
+    C_AioRequest *req_comp = new C_AioRequest(m_aio_comp);
     m_image_ctx.write_to_cache(object_extent.oid, bl, object_extent.length,
                                object_extent.offset, req_comp, m_op_flags,
                                journal_tid);
@@ -378,8 +388,7 @@ void AioImageWrite::update_stats(size_t length) {
 uint64_t AioImageDiscard::append_journal_event(
     const AioObjectRequests &requests, bool synchronous) {
   journal::EventEntry event_entry(journal::AioDiscardEvent(m_off, m_len));
-  uint64_t tid = m_image_ctx.journal->append_io_event(m_aio_comp,
-                                                      std::move(event_entry),
+  uint64_t tid = m_image_ctx.journal->append_io_event(std::move(event_entry),
                                                       requests, m_off, m_len,
                                                       synchronous);
   m_aio_comp->associate_journal_event(tid);
@@ -438,8 +447,6 @@ void AioImageDiscard::update_stats(size_t length) {
 }
 
 void AioImageFlush::send_request() {
-  CephContext *cct = m_image_ctx.cct;
-
   bool journaling = false;
   {
     RWLock::RLocker snap_locker(m_image_ctx.snap_lock);
@@ -447,12 +454,12 @@ void AioImageFlush::send_request() {
                   !m_image_ctx.journal->is_journal_replaying());
   }
 
-  m_aio_comp->set_request_count(cct, journaling ? 2 : 1);
+  m_aio_comp->set_request_count(journaling ? 2 : 1);
 
   if (journaling) {
     // in-flight ops are flushed prior to closing the journal
     uint64_t journal_tid = m_image_ctx.journal->append_io_event(
-      m_aio_comp, journal::EventEntry(journal::AioFlushEvent()),
+      journal::EventEntry(journal::AioFlushEvent()),
       AioObjectRequests(), 0, 0, false);
 
     C_FlushJournalCommit *ctx = new C_FlushJournalCommit(m_image_ctx,
@@ -462,10 +469,11 @@ void AioImageFlush::send_request() {
     m_aio_comp->associate_journal_event(journal_tid);
   }
 
-  C_AioRequest *req_comp = new C_AioRequest(cct, m_aio_comp);
+  C_AioRequest *req_comp = new C_AioRequest(m_aio_comp);
   m_image_ctx.flush(req_comp);
 
-  m_aio_comp->start_op(&m_image_ctx, AIO_TYPE_FLUSH);
+  // track flush op for block writes
+  m_aio_comp->start_op(true);
   m_aio_comp->put();
 
   m_image_ctx.perfcounter->inc(l_librbd_aio_flush);
diff --git a/src/librbd/AioImageRequest.h b/src/librbd/AioImageRequest.h
index e8a3fd5..b30cc30 100644
--- a/src/librbd/AioImageRequest.h
+++ b/src/librbd/AioImageRequest.h
@@ -40,6 +40,10 @@ public:
     return false;
   }
 
+  void start_op() {
+    m_aio_comp->start_op();
+  }
+
   void send();
   void fail(int r);
 
@@ -106,8 +110,6 @@ protected:
       m_synchronous(false) {
   }
 
-  virtual aio_type_t get_aio_type() const = 0;
-
   virtual void send_request();
 
   virtual uint32_t get_cache_request_count(bool journaling) const {
@@ -140,9 +142,6 @@ public:
   }
 
 protected:
-  virtual aio_type_t get_aio_type() const {
-    return AIO_TYPE_WRITE;
-  }
   virtual const char *get_request_type() const {
     return "aio_write";
   }
@@ -175,9 +174,6 @@ public:
   }
 
 protected:
-  virtual aio_type_t get_aio_type() const {
-    return AIO_TYPE_DISCARD;
-  }
   virtual const char *get_request_type() const {
     return "aio_discard";
   }
diff --git a/src/librbd/AioImageRequestWQ.cc b/src/librbd/AioImageRequestWQ.cc
index 5ed3e2e..fba7933 100644
--- a/src/librbd/AioImageRequestWQ.cc
+++ b/src/librbd/AioImageRequestWQ.cc
@@ -341,6 +341,8 @@ void *AioImageRequestWQ::_void_dequeue() {
     get_pool_lock().Lock();
     return nullptr;
   }
+
+  item->start_op();
   return item;
 }
 
@@ -398,8 +400,7 @@ int AioImageRequestWQ::start_in_flight_op(AioCompletion *c) {
     CephContext *cct = m_image_ctx.cct;
     lderr(cct) << "IO received on closed image" << dendl;
 
-    c->get();
-    c->fail(cct, -ESHUTDOWN);
+    c->fail(-ESHUTDOWN);
     return false;
   }
 
diff --git a/src/librbd/AioObjectRequest.cc b/src/librbd/AioObjectRequest.cc
index 77aaa7e..faee3d2 100644
--- a/src/librbd/AioObjectRequest.cc
+++ b/src/librbd/AioObjectRequest.cc
@@ -173,7 +173,7 @@ namespace librbd {
           // release reference to the parent read completion.  this request
           // might be completed after unblock is invoked.
           AioCompletion *parent_completion = m_parent_completion;
-          parent_completion->unblock(m_ictx->cct);
+          parent_completion->unblock();
           parent_completion->put();
         }
       }
diff --git a/src/librbd/ImageCtx.cc b/src/librbd/ImageCtx.cc
index fa05103..5649969 100644
--- a/src/librbd/ImageCtx.cc
+++ b/src/librbd/ImageCtx.cc
@@ -234,10 +234,6 @@ struct C_InvalidateCache : public Context {
   void ImageCtx::init() {
     assert(!header_oid.empty());
     assert(old_format || !id.empty());
-    if (!old_format) {
-      init_layout();
-    }
-    apply_metadata_confs();
 
     asok_hook = new LibrbdAdminSocketHook(this);
 
@@ -888,31 +884,32 @@ struct C_InvalidateCache : public Context {
     completed_reqs.clear();
   }
 
-  bool ImageCtx::_filter_metadata_confs(const string &prefix, map<string, bool> &configs,
-                                        map<string, bufferlist> &pairs, map<string, bufferlist> *res) {
+  bool ImageCtx::_filter_metadata_confs(const string &prefix,
+                                        map<string, bool> &configs,
+                                        const map<string, bufferlist> &pairs,
+                                        map<string, bufferlist> *res) {
     size_t conf_prefix_len = prefix.size();
 
     string start = prefix;
-    for (map<string, bufferlist>::iterator it = pairs.begin(); it != pairs.end(); ++it) {
-      if (it->first.compare(0, MIN(conf_prefix_len, it->first.size()), prefix) > 0)
+    for (auto it : pairs) {
+      if (it.first.compare(0, MIN(conf_prefix_len, it.first.size()), prefix) > 0)
         return false;
 
-      if (it->first.size() <= conf_prefix_len)
+      if (it.first.size() <= conf_prefix_len)
         continue;
 
-      string key = it->first.substr(conf_prefix_len, it->first.size() - conf_prefix_len);
-      map<string, bool>::iterator cit = configs.find(key);
-      if ( cit != configs.end()) {
+      string key = it.first.substr(conf_prefix_len, it.first.size() - conf_prefix_len);
+      auto cit = configs.find(key);
+      if (cit != configs.end()) {
         cit->second = true;
-        res->insert(make_pair(key, it->second));
+        res->insert(make_pair(key, it.second));
       }
     }
     return true;
   }
 
-  void ImageCtx::apply_metadata_confs() {
+  void ImageCtx::apply_metadata(const std::map<std::string, bufferlist> &meta) {
     ldout(cct, 20) << __func__ << dendl;
-    static uint64_t max_conf_items = 128;
     std::map<string, bool> configs = boost::assign::map_list_of(
         "rbd_non_blocking_aio", false)(
         "rbd_cache", false)(
@@ -943,39 +940,18 @@ struct C_InvalidateCache : public Context {
         "rbd_journal_object_flush_age", false)(
         "rbd_journal_pool", false);
 
-    string start = METADATA_CONF_PREFIX;
     md_config_t local_config_t;
-
-    bool retrieve_metadata = !old_format;
-    while (retrieve_metadata) {
-      map<string, bufferlist> pairs, res;
-      int r = cls_client::metadata_list(&md_ctx, header_oid, start, max_conf_items,
-                                    &pairs);
-      if (r == -EOPNOTSUPP || r == -EIO) {
-        ldout(cct, 10) << "config metadata not supported by OSD" << dendl;
-        break;
-      } else if (r < 0) {
-        lderr(cct) << __func__ << " couldn't list config metadata: " << r
+    std::map<std::string, bufferlist> res;
+
+    _filter_metadata_confs(METADATA_CONF_PREFIX, configs, meta, &res);
+    for (auto it : res) {
+      std::string val(it.second.c_str(), it.second.length());
+      int j = local_config_t.set_val(it.first.c_str(), val);
+      if (j < 0) {
+        lderr(cct) << __func__ << " failed to set config " << it.first
+                   << " with value " << it.second.c_str() << ": " << j
                    << dendl;
-        break;
-      }
-      if (pairs.empty()) {
-        break;
-      }
-
-      retrieve_metadata = _filter_metadata_confs(METADATA_CONF_PREFIX, configs,
-                                                 pairs, &res);
-      for (map<string, bufferlist>::iterator it = res.begin();
-           it != res.end(); ++it) {
-        string val(it->second.c_str(), it->second.length());
-        int j = local_config_t.set_val(it->first.c_str(), val);
-        if (j < 0) {
-          lderr(cct) << __func__ << " failed to set config " << it->first
-                     << " with value " << it->second.c_str() << ": " << j
-                     << dendl;
-        }
       }
-      start = pairs.rbegin()->first;
     }
 
 #define ASSIGN_OPTION(config)                                                  \
diff --git a/src/librbd/ImageCtx.h b/src/librbd/ImageCtx.h
index 3b58c66..076072c 100644
--- a/src/librbd/ImageCtx.h
+++ b/src/librbd/ImageCtx.h
@@ -192,7 +192,7 @@ namespace librbd {
     journal::Policy *journal_policy = nullptr;
 
     static bool _filter_metadata_confs(const string &prefix, std::map<string, bool> &configs,
-                                       map<string, bufferlist> &pairs, map<string, bufferlist> *res);
+                                       const map<string, bufferlist> &pairs, map<string, bufferlist> *res);
 
     // unit test mock helpers
     static ImageCtx* create(const std::string &image_name,
@@ -285,7 +285,7 @@ namespace librbd {
     void cancel_async_requests();
     void cancel_async_requests(Context *on_finish);
 
-    void apply_metadata_confs();
+    void apply_metadata(const std::map<std::string, bufferlist> &meta);
 
     ExclusiveLock<ImageCtx> *create_exclusive_lock();
     ObjectMap *create_object_map(uint64_t snap_id);
diff --git a/src/librbd/ImageState.cc b/src/librbd/ImageState.cc
index 0bf2e45..39f4ee6 100644
--- a/src/librbd/ImageState.cc
+++ b/src/librbd/ImageState.cc
@@ -105,6 +105,18 @@ template <typename I>
 void ImageState<I>::refresh(Context *on_finish) {
   CephContext *cct = m_image_ctx->cct;
   ldout(cct, 20) << __func__ << dendl;
+  refresh(false, on_finish);
+}
+
+template <typename I>
+void ImageState<I>::acquire_lock_refresh(Context *on_finish) {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << __func__ << dendl;
+  refresh(true, on_finish);
+}
+
+template <typename I>
+void ImageState<I>::refresh(bool acquiring_lock, Context *on_finish) {
 
   m_lock.Lock();
   if (is_closed()) {
@@ -115,6 +127,7 @@ void ImageState<I>::refresh(Context *on_finish) {
 
   Action action(ACTION_TYPE_REFRESH);
   action.refresh_seq = m_refresh_seq;
+  action.refresh_acquiring_lock = acquiring_lock;
   execute_action_unlock(action, on_finish);
 }
 
@@ -326,12 +339,15 @@ void ImageState<I>::send_refresh_unlock() {
   ldout(cct, 10) << this << " " << __func__ << dendl;
 
   m_state = STATE_REFRESHING;
+  assert(!m_actions_contexts.empty());
+  auto &action_context = m_actions_contexts.front().first;
+  assert(action_context.action_type == ACTION_TYPE_REFRESH);
 
   Context *ctx = create_async_context_callback(
     *m_image_ctx, create_context_callback<
       ImageState<I>, &ImageState<I>::handle_refresh>(this));
   image::RefreshRequest<I> *req = image::RefreshRequest<I>::create(
-    *m_image_ctx, ctx);
+    *m_image_ctx, action_context.refresh_acquiring_lock, ctx);
 
   m_lock.Unlock();
   req->send();
@@ -348,7 +364,13 @@ void ImageState<I>::handle_refresh(int r) {
   ActionContexts &action_contexts(m_actions_contexts.front());
   assert(action_contexts.first.action_type == ACTION_TYPE_REFRESH);
   assert(m_last_refresh <= action_contexts.first.refresh_seq);
-  m_last_refresh = action_contexts.first.refresh_seq;
+
+  if (r == -ERESTART) {
+    ldout(cct, 5) << "incomplete refresh: not updating sequence" << dendl;
+    r = 0;
+  } else {
+    m_last_refresh = action_contexts.first.refresh_seq;
+  }
 
   complete_action_unlock(STATE_OPEN, r);
 }
diff --git a/src/librbd/ImageState.h b/src/librbd/ImageState.h
index 2834116..b60172f 100644
--- a/src/librbd/ImageState.h
+++ b/src/librbd/ImageState.h
@@ -34,8 +34,9 @@ public:
   bool is_refresh_required() const;
 
   int refresh();
-  void refresh(Context *on_finish);
   int refresh_if_required();
+  void refresh(Context *on_finish);
+  void acquire_lock_refresh(Context *on_finish);
 
   void snap_set(const std::string &snap_name, Context *on_finish);
 
@@ -59,10 +60,11 @@ private:
 
   struct Action {
     ActionType action_type;
-    uint64_t refresh_seq;
+    uint64_t refresh_seq = 0;
+    bool refresh_acquiring_lock = false;
     std::string snap_name;
 
-    Action(ActionType action_type) : action_type(action_type), refresh_seq(0) {
+    Action(ActionType action_type) : action_type(action_type) {
     }
     inline bool operator==(const Action &action) const {
       if (action_type != action.action_type) {
@@ -70,7 +72,8 @@ private:
       }
       switch (action_type) {
       case ACTION_TYPE_REFRESH:
-        return refresh_seq == action.refresh_seq;
+        return (refresh_seq == action.refresh_seq &&
+                refresh_acquiring_lock == action.refresh_acquiring_lock);
       case ACTION_TYPE_SET_SNAP:
         return snap_name == action.snap_name;
       default:
@@ -95,6 +98,8 @@ private:
   bool is_transition_state() const;
   bool is_closed() const;
 
+  void refresh(bool acquiring_lock, Context *on_finish);
+
   void append_context(const Action &action, Context *context);
   void execute_next_action_unlock();
   void execute_action_unlock(const Action &action, Context *context);
diff --git a/src/librbd/Journal.cc b/src/librbd/Journal.cc
index 92b360d..b874005 100644
--- a/src/librbd/Journal.cc
+++ b/src/librbd/Journal.cc
@@ -2,7 +2,6 @@
 // vim: ts=8 sw=2 smarttab
 
 #include "librbd/Journal.h"
-#include "librbd/AioCompletion.h"
 #include "librbd/AioImageRequestWQ.h"
 #include "librbd/AioObjectRequest.h"
 #include "librbd/ExclusiveLock.h"
@@ -160,15 +159,14 @@ public:
   }
 };
 
-template <typename I, typename J>
-int open_journaler(I *image_ctx, J *journaler, bool *initialized,
+template <typename J>
+int open_journaler(CephContext *cct, J *journaler,
                    cls::journal::Client *client,
                    journal::ImageClientMeta *client_meta,
                    journal::TagData *tag_data) {
   C_SaferCond init_ctx;
   journaler->init(&init_ctx);
   int r = init_ctx.wait();
-  *initialized = (r >= 0);
   if (r < 0) {
     return r;
   }
@@ -197,7 +195,7 @@ int open_journaler(I *image_ctx, J *journaler, bool *initialized,
   Mutex lock("lock");
   uint64_t tag_tid;
   C_DecodeTags *tags_ctx = new C_DecodeTags(
-    image_ctx->cct, &lock, &tag_tid, tag_data, &get_tags_ctx);
+      cct, &lock, &tag_tid, tag_data, &get_tags_ctx);
   journaler->get_tags(client_meta->tag_class, &tags_ctx->tags, tags_ctx);
 
   r = get_tags_ctx.wait();
@@ -414,6 +412,9 @@ int Journal<I>::remove(librados::IoCtx &io_ctx, const std::string &image_id) {
 
   C_SaferCond cond;
   journaler.init(&cond);
+  BOOST_SCOPE_EXIT_ALL(&journaler) {
+    journaler.shut_down();
+  };
 
   r = cond.wait();
   if (r == -ENOENT) {
@@ -441,6 +442,9 @@ int Journal<I>::reset(librados::IoCtx &io_ctx, const std::string &image_id) {
 
   C_SaferCond cond;
   journaler.init(&cond);
+  BOOST_SCOPE_EXIT_ALL(&journaler) {
+    journaler.shut_down();
+  };
 
   int r = cond.wait();
   if (r == -ENOENT) {
@@ -480,8 +484,14 @@ int Journal<I>::reset(librados::IoCtx &io_ctx, const std::string &image_id) {
 
 template <typename I>
 int Journal<I>::is_tag_owner(I *image_ctx, bool *is_tag_owner) {
+  return Journal<>::is_tag_owner(image_ctx->md_ctx, image_ctx->id, is_tag_owner);
+}
+
+template <typename I>
+int Journal<I>::is_tag_owner(IoCtx& io_ctx, std::string& image_id,
+                             bool *is_tag_owner) {
   std::string mirror_uuid;
-  int r = get_tag_owner(image_ctx, &mirror_uuid);
+  int r = get_tag_owner(io_ctx, image_id, &mirror_uuid);
   if (r < 0) {
     return r;
   }
@@ -492,25 +502,27 @@ int Journal<I>::is_tag_owner(I *image_ctx, bool *is_tag_owner) {
 
 template <typename I>
 int Journal<I>::get_tag_owner(I *image_ctx, std::string *mirror_uuid) {
-  CephContext *cct = image_ctx->cct;
+  return get_tag_owner(image_ctx->md_ctx, image_ctx->id, mirror_uuid);
+}
+
+template <typename I>
+int Journal<I>::get_tag_owner(IoCtx& io_ctx, std::string& image_id,
+                              std::string *mirror_uuid) {
+  CephContext *cct = (CephContext *)io_ctx.cct();
   ldout(cct, 20) << __func__ << dendl;
 
-  Journaler journaler(image_ctx->md_ctx, image_ctx->id, IMAGE_CLIENT_ID,
-                      image_ctx->cct->_conf->rbd_journal_commit_age);
+  Journaler journaler(io_ctx, image_id, IMAGE_CLIENT_ID,
+                      cct->_conf->rbd_journal_commit_age);
 
-  bool initialized;
   cls::journal::Client client;
   journal::ImageClientMeta client_meta;
   journal::TagData tag_data;
-  int r = open_journaler(image_ctx, &journaler, &initialized, &client,
-                         &client_meta, &tag_data);
+  int r = open_journaler(cct, &journaler, &client, &client_meta, &tag_data);
   if (r >= 0) {
     *mirror_uuid = tag_data.mirror_uuid;
   }
 
-  if (initialized) {
-    journaler.shut_down();
-  }
+  journaler.shut_down();
   return r;
 }
 
@@ -522,16 +534,13 @@ int Journal<I>::request_resync(I *image_ctx) {
   Journaler journaler(image_ctx->md_ctx, image_ctx->id, IMAGE_CLIENT_ID,
                       image_ctx->cct->_conf->rbd_journal_commit_age);
 
-  bool initialized;
   cls::journal::Client client;
   journal::ImageClientMeta client_meta;
   journal::TagData tag_data;
-  int r = open_journaler(image_ctx, &journaler, &initialized, &client,
-                         &client_meta, &tag_data);
-  BOOST_SCOPE_EXIT_ALL(&journaler, &initialized) {
-    if (initialized) {
-      journaler.shut_down();
-    }
+  int r = open_journaler(image_ctx->cct, &journaler, &client, &client_meta,
+                         &tag_data);
+  BOOST_SCOPE_EXIT_ALL(&journaler) {
+    journaler.shut_down();
   };
 
   if (r < 0) {
@@ -563,16 +572,13 @@ int Journal<I>::promote(I *image_ctx) {
   Journaler journaler(image_ctx->md_ctx, image_ctx->id, IMAGE_CLIENT_ID,
                       image_ctx->cct->_conf->rbd_journal_commit_age);
 
-  bool initialized;
   cls::journal::Client client;
   journal::ImageClientMeta client_meta;
   journal::TagData tag_data;
-  int r = open_journaler(image_ctx, &journaler, &initialized, &client,
-                         &client_meta, &tag_data);
-  BOOST_SCOPE_EXIT_ALL(&journaler, &initialized) {
-    if (initialized) {
-      journaler.shut_down();
-    }
+  int r = open_journaler(image_ctx->cct, &journaler, &client, &client_meta,
+                         &tag_data);
+  BOOST_SCOPE_EXIT_ALL(&journaler) {
+    journaler.shut_down();
   };
 
   if (r < 0) {
@@ -600,6 +606,7 @@ bool Journal<I>::is_journal_replaying() const {
   Mutex::Locker locker(m_lock);
   return (m_state == STATE_REPLAYING ||
           m_state == STATE_FLUSHING_REPLAY ||
+          m_state == STATE_FLUSHING_RESTART ||
           m_state == STATE_RESTARTING_REPLAY);
 }
 
@@ -795,8 +802,7 @@ 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,
+uint64_t Journal<I>::append_write_event(uint64_t offset, size_t length,
                                         const bufferlist &bl,
                                         const AioObjectRequests &requests,
                                         bool flush_entry) {
@@ -826,13 +832,12 @@ uint64_t Journal<I>::append_write_event(AioCompletion *aio_comp,
     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);
+  return append_io_events(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,
+uint64_t Journal<I>::append_io_event(journal::EventEntry &&event_entry,
                                      const AioObjectRequests &requests,
                                      uint64_t offset, size_t length,
                                      bool flush_entry) {
@@ -840,13 +845,12 @@ 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);
+  return append_io_events(event_entry.get_event_type(), {bl}, requests, offset,
+                          length, flush_entry);
 }
 
 template <typename I>
-uint64_t Journal<I>::append_io_events(AioCompletion *aio_comp,
-                                      journal::EventType event_type,
+uint64_t Journal<I>::append_io_events(journal::EventType event_type,
                                       const Bufferlists &bufferlists,
                                       const AioObjectRequests &requests,
                                       uint64_t offset, size_t length,
@@ -868,7 +872,7 @@ uint64_t Journal<I>::append_io_events(AioCompletion *aio_comp,
       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);
+    m_events[tid] = Event(futures, requests, offset, length);
   }
 
   CephContext *cct = m_image_ctx.cct;
@@ -961,6 +965,10 @@ void Journal<I>::append_op_event(uint64_t op_tid,
   }
 
   on_safe = create_async_context_callback(m_image_ctx, on_safe);
+  on_safe = new FunctionContext([this, on_safe](int r) {
+      // ensure all committed IO before this op is committed
+      m_journaler->flush_commit_position(on_safe);
+    });
   future.flush(on_safe);
 
   CephContext *cct = m_image_ctx.cct;
@@ -1062,7 +1070,8 @@ typename Journal<I>::Future Journal<I>::wait_event(Mutex &lock, uint64_t tid,
 }
 
 template <typename I>
-int Journal<I>::start_external_replay(journal::Replay<I> **journal_replay) {
+void Journal<I>::start_external_replay(journal::Replay<I> **journal_replay,
+                                       Context *on_finish) {
   CephContext *cct = m_image_ctx.cct;
   ldout(cct, 20) << this << " " << __func__ << dendl;
 
@@ -1070,11 +1079,42 @@ int Journal<I>::start_external_replay(journal::Replay<I> **journal_replay) {
   assert(m_state == STATE_READY);
   assert(m_journal_replay == nullptr);
 
+  on_finish = util::create_async_context_callback(m_image_ctx, on_finish);
+  on_finish = new FunctionContext(
+    [this, journal_replay, on_finish](int r) {
+      handle_start_external_replay(r, journal_replay, on_finish);
+    });
+
+  // safely flush all in-flight events before starting external replay
+  m_journaler->stop_append(util::create_async_context_callback(m_image_ctx,
+                                                               on_finish));
+}
+
+template <typename I>
+void Journal<I>::handle_start_external_replay(int r,
+                                              journal::Replay<I> **journal_replay,
+                                              Context *on_finish) {
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 20) << this << " " << __func__ << dendl;
+
+  Mutex::Locker locker(m_lock);
+  assert(m_state == STATE_READY);
+  assert(m_journal_replay == nullptr);
+
+  if (r < 0) {
+    lderr(cct) << "failed to stop recording: " << cpp_strerror(r) << dendl;
+    *journal_replay = nullptr;
+
+    // get back to a sane-state
+    start_append();
+    on_finish->complete(r);
+    return;
+  }
+
   transition_state(STATE_REPLAYING, 0);
   m_journal_replay = journal::Replay<I>::create(m_image_ctx);
-
   *journal_replay = m_journal_replay;
-  return 0;
+  on_finish->complete(0);
 }
 
 template <typename I>
@@ -1085,7 +1125,8 @@ void Journal<I>::stop_external_replay() {
 
   delete m_journal_replay;
   m_journal_replay = nullptr;
-  transition_state(STATE_READY, 0);
+
+  start_append();
 }
 
 template <typename I>
@@ -1117,8 +1158,9 @@ void Journal<I>::destroy_journaler(int r) {
   m_journal_replay = NULL;
 
   transition_state(STATE_CLOSING, r);
-  m_image_ctx.op_work_queue->queue(create_context_callback<
-    Journal<I>, &Journal<I>::handle_journal_destroyed>(this), 0);
+  m_journaler->shut_down(create_async_context_callback(
+    m_image_ctx, create_context_callback<
+      Journal<I>, &Journal<I>::handle_journal_destroyed>(this)));
 }
 
 template <typename I>
@@ -1134,8 +1176,9 @@ void Journal<I>::recreate_journaler(int r) {
   m_journal_replay = NULL;
 
   transition_state(STATE_RESTARTING_REPLAY, r);
-  m_image_ctx.op_work_queue->queue(create_context_callback<
-    Journal<I>, &Journal<I>::handle_journal_destroyed>(this), 0);
+  m_journaler->shut_down(create_async_context_callback(
+    m_image_ctx, create_context_callback<
+      Journal<I>, &Journal<I>::handle_journal_destroyed>(this)));
 }
 
 template <typename I>
@@ -1168,6 +1211,15 @@ void Journal<I>::complete_event(typename Events::iterator it, int r) {
 }
 
 template <typename I>
+void Journal<I>::start_append() {
+  assert(m_lock.is_locked());
+  m_journaler->start_append(m_image_ctx.journal_object_flush_interval,
+			    m_image_ctx.journal_object_flush_bytes,
+			    m_image_ctx.journal_object_flush_age);
+  transition_state(STATE_READY, 0);
+}
+
+template <typename I>
 void Journal<I>::handle_initialized(int r) {
   CephContext *cct = m_image_ctx.cct;
   ldout(cct, 20) << this << " " << __func__ << ": r=" << r << dendl;
@@ -1277,27 +1329,47 @@ template <typename I>
 void Journal<I>::handle_replay_complete(int r) {
   CephContext *cct = m_image_ctx.cct;
 
-  m_lock.Lock();
-  if (m_state != STATE_REPLAYING) {
-    m_lock.Unlock();
-    return;
+  bool cancel_ops = false;
+  {
+    Mutex::Locker locker(m_lock);
+    if (m_state != STATE_REPLAYING) {
+      return;
+    }
+
+    ldout(cct, 20) << this << " " << __func__ << ": r=" << r << dendl;
+    if (r < 0) {
+      cancel_ops = true;
+      transition_state(STATE_FLUSHING_RESTART, r);
+    } else {
+      // state might change back to FLUSHING_RESTART on flush error
+      transition_state(STATE_FLUSHING_REPLAY, 0);
+    }
   }
 
-  ldout(cct, 20) << this << " " << __func__ << ": r=" << r << dendl;
-  m_journaler->stop_replay();
-  if (r < 0) {
-    transition_state(STATE_FLUSHING_RESTART, r);
-    m_lock.Unlock();
+  Context *ctx = new FunctionContext([this, cct](int r) {
+      ldout(cct, 20) << this << " handle_replay_complete: "
+                     << "handle shut down replay" << dendl;
 
-    m_journal_replay->shut_down(true, create_context_callback<
-      Journal<I>, &Journal<I>::handle_flushing_restart>(this));
-  } else {
-    transition_state(STATE_FLUSHING_REPLAY, 0);
-    m_lock.Unlock();
+      State state;
+      {
+        Mutex::Locker locker(m_lock);
+        assert(m_state == STATE_FLUSHING_RESTART ||
+               m_state == STATE_FLUSHING_REPLAY);
+        state = m_state;
+      }
 
-    m_journal_replay->shut_down(false, create_context_callback<
-      Journal<I>, &Journal<I>::handle_flushing_replay>(this));
-  }
+      if (state == STATE_FLUSHING_RESTART) {
+        handle_flushing_restart(0);
+      } else {
+        handle_flushing_replay();
+      }
+    });
+  ctx = new FunctionContext([this, cct, cancel_ops, ctx](int r) {
+      ldout(cct, 20) << this << " handle_replay_complete: "
+                     << "shut down replay" << dendl;
+      m_journal_replay->shut_down(cancel_ops, ctx);
+    });
+  m_journaler->stop_replay(ctx);
 }
 
 template <typename I>
@@ -1317,9 +1389,13 @@ void Journal<I>::handle_replay_process_ready(int r) {
 
 template <typename I>
 void Journal<I>::handle_replay_process_safe(ReplayEntry replay_entry, int r) {
-  Mutex::Locker locker(m_lock);
-
   CephContext *cct = m_image_ctx.cct;
+
+  m_lock.Lock();
+  assert(m_state == STATE_REPLAYING ||
+         m_state == STATE_FLUSHING_RESTART ||
+         m_state == STATE_FLUSHING_REPLAY);
+
   ldout(cct, 20) << this << " " << __func__ << ": r=" << r << dendl;
   if (r < 0) {
     lderr(cct) << "failed to commit journal event to disk: " << cpp_strerror(r)
@@ -1327,21 +1403,34 @@ void Journal<I>::handle_replay_process_safe(ReplayEntry replay_entry, int r) {
 
     if (m_state == STATE_REPLAYING) {
       // abort the replay if we have an error
-      m_journaler->stop_replay();
       transition_state(STATE_FLUSHING_RESTART, r);
-
-      m_journal_replay->shut_down(true, create_context_callback<
-        Journal<I>, &Journal<I>::handle_flushing_restart>(this));
+      m_lock.Unlock();
+
+      // stop replay, shut down, and restart
+      Context *ctx = new FunctionContext([this, cct](int r) {
+          ldout(cct, 20) << this << " handle_replay_process_safe: "
+                         << "shut down replay" << dendl;
+          {
+            Mutex::Locker locker(m_lock);
+            assert(m_state == STATE_FLUSHING_RESTART);
+          }
+
+          m_journal_replay->shut_down(true, create_context_callback<
+            Journal<I>, &Journal<I>::handle_flushing_restart>(this));
+        });
+      m_journaler->stop_replay(ctx);
       return;
     } else if (m_state == STATE_FLUSHING_REPLAY) {
       // end-of-replay flush in-progress -- we need to restart replay
       transition_state(STATE_FLUSHING_RESTART, r);
+      m_lock.Unlock();
       return;
     }
   } else {
     // only commit the entry if written successfully
     m_journaler->committed(replay_entry);
   }
+  m_lock.Unlock();
 }
 
 template <typename I>
@@ -1362,16 +1451,15 @@ void Journal<I>::handle_flushing_restart(int r) {
 }
 
 template <typename I>
-void Journal<I>::handle_flushing_replay(int r) {
+void Journal<I>::handle_flushing_replay() {
   Mutex::Locker locker(m_lock);
 
   CephContext *cct = m_image_ctx.cct;
-  ldout(cct, 20) << this << " " << __func__ << ": r=" << r << dendl;
+  ldout(cct, 20) << this << " " << __func__ << dendl;
 
-  assert(r == 0);
   assert(m_state == STATE_FLUSHING_REPLAY || m_state == STATE_FLUSHING_RESTART);
   if (m_close_pending) {
-    destroy_journaler(r);
+    destroy_journaler(0);
     return;
   } else if (m_state == STATE_FLUSHING_RESTART) {
     // failed to replay one-or-more events -- restart
@@ -1383,10 +1471,7 @@ void Journal<I>::handle_flushing_replay(int r) {
   m_journal_replay = NULL;
 
   m_error_result = 0;
-  m_journaler->start_append(m_image_ctx.journal_object_flush_interval,
-			    m_image_ctx.journal_object_flush_bytes,
-			    m_image_ctx.journal_object_flush_age);
-  transition_state(STATE_READY, 0);
+  start_append();
 }
 
 template <typename I>
@@ -1436,7 +1521,6 @@ void Journal<I>::handle_io_event_safe(int r, uint64_t tid) {
     lderr(cct) << "failed to commit IO event: "  << cpp_strerror(r) << dendl;
   }
 
-  AioCompletion *aio_comp;
   AioObjectRequests aio_object_requests;
   Contexts on_safe_contexts;
   {
@@ -1445,7 +1529,6 @@ void Journal<I>::handle_io_event_safe(int r, uint64_t tid) {
     assert(it != m_events.end());
 
     Event &event = it->second;
-    aio_comp = event.aio_comp;
     aio_object_requests.swap(event.aio_object_requests);
     on_safe_contexts.swap(event.on_safe_contexts);
 
@@ -1466,15 +1549,14 @@ void Journal<I>::handle_io_event_safe(int r, uint64_t tid) {
   }
 
   ldout(cct, 20) << "completing tid=" << tid << dendl;
-
-  if (r < 0) {
-    // don't send aio requests if the journal fails -- bubble error up
-    aio_comp->fail(cct, r);
-  } else {
-    // send any waiting aio requests now that journal entry is safe
-    RWLock::RLocker owner_locker(m_image_ctx.owner_lock);
-    for (AioObjectRequests::iterator it = aio_object_requests.begin();
-         it != aio_object_requests.end(); ++it) {
+  for (AioObjectRequests::iterator it = aio_object_requests.begin();
+       it != aio_object_requests.end(); ++it) {
+    if (r < 0) {
+      // don't send aio requests if the journal fails -- bubble error up
+      (*it)->complete(r);
+    } else {
+      // send any waiting aio requests now that journal entry is safe
+      RWLock::RLocker owner_locker(m_image_ctx.owner_lock);
       (*it)->send();
     }
   }
diff --git a/src/librbd/Journal.h b/src/librbd/Journal.h
index e738d02..d77d50e 100644
--- a/src/librbd/Journal.h
+++ b/src/librbd/Journal.h
@@ -30,7 +30,6 @@ class Journaler;
 
 namespace librbd {
 
-class AioCompletion;
 class AioObjectRequest;
 class ImageCtx;
 
@@ -101,7 +100,11 @@ public:
   static int reset(librados::IoCtx &io_ctx, const std::string &image_id);
 
   static int is_tag_owner(ImageCtxT *image_ctx, bool *is_tag_owner);
+  static int is_tag_owner(librados::IoCtx& io_ctx, std::string& image_id,
+                          bool *is_tag_owner);
   static int get_tag_owner(ImageCtxT *image_ctx, std::string *mirror_uuid);
+  static int get_tag_owner(librados::IoCtx& io_ctx, std::string& image_id,
+                           std::string *mirror_uuid);
   static int request_resync(ImageCtxT *image_ctx);
   static int promote(ImageCtxT *image_ctx);
 
@@ -125,13 +128,11 @@ public:
 
   void flush_commit_position(Context *on_finish);
 
-  uint64_t append_write_event(AioCompletion *aio_comp,
-                              uint64_t offset, size_t length,
+  uint64_t append_write_event(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,
+  uint64_t append_io_event(journal::EventEntry &&event_entry,
                            const AioObjectRequests &requests,
                            uint64_t offset, size_t length,
                            bool flush_entry);
@@ -153,7 +154,8 @@ public:
     return op_tid;
   }
 
-  int start_external_replay(journal::Replay<ImageCtxT> **journal_replay);
+  void start_external_replay(journal::Replay<ImageCtxT> **journal_replay,
+                             Context *on_finish);
   void stop_external_replay();
 
 private:
@@ -172,7 +174,6 @@ private:
 
   struct Event {
     Futures futures;
-    AioCompletion *aio_comp = nullptr;
     AioObjectRequests aio_object_requests;
     Contexts on_safe_contexts;
     ExtentInterval pending_extents;
@@ -182,9 +183,9 @@ private:
 
     Event() {
     }
-    Event(const Futures &_futures, AioCompletion *_aio_comp,
-          const AioObjectRequests &_requests, uint64_t offset, size_t length)
-      : futures(_futures), aio_comp(_aio_comp), aio_object_requests(_requests) {
+    Event(const Futures &_futures, const AioObjectRequests &_requests,
+          uint64_t offset, size_t length)
+      : futures(_futures), aio_object_requests(_requests) {
       if (length > 0) {
         pending_extents.insert(offset, length);
       }
@@ -286,8 +287,7 @@ private:
 
   journal::Replay<ImageCtxT> *m_journal_replay;
 
-  uint64_t append_io_events(AioCompletion *aio_comp,
-                            journal::EventType event_type,
+  uint64_t append_io_events(journal::EventType event_type,
                             const Bufferlists &bufferlists,
                             const AioObjectRequests &requests,
                             uint64_t offset, size_t length, bool flush_entry);
@@ -299,6 +299,8 @@ private:
 
   void complete_event(typename Events::iterator it, int r);
 
+  void start_append();
+
   void handle_initialized(int r);
   void handle_get_tags(int r);
 
@@ -307,8 +309,12 @@ private:
   void handle_replay_process_ready(int r);
   void handle_replay_process_safe(ReplayEntry replay_entry, int r);
 
+  void handle_start_external_replay(int r,
+                                    journal::Replay<ImageCtxT> **journal_replay,
+                                    Context *on_finish);
+
   void handle_flushing_restart(int r);
-  void handle_flushing_replay(int r);
+  void handle_flushing_replay();
 
   void handle_recording_stopped(int r);
 
diff --git a/src/librbd/LibrbdWriteback.cc b/src/librbd/LibrbdWriteback.cc
index 231c9b1..e3ba517 100644
--- a/src/librbd/LibrbdWriteback.cc
+++ b/src/librbd/LibrbdWriteback.cc
@@ -167,6 +167,27 @@ namespace librbd {
     }
   };
 
+  struct C_CommitIOEventExtent : public Context {
+    ImageCtx *image_ctx;
+    uint64_t journal_tid;
+    uint64_t offset;
+    uint64_t length;
+
+    C_CommitIOEventExtent(ImageCtx *image_ctx, uint64_t journal_tid,
+                          uint64_t offset, uint64_t length)
+      : image_ctx(image_ctx), journal_tid(journal_tid), offset(offset),
+        length(length) {
+    }
+
+    virtual void finish(int r) {
+      // all IO operations are flushed prior to closing the journal
+      assert(image_ctx->journal != nullptr);
+
+      image_ctx->journal->commit_io_event_extent(journal_tid, offset, length,
+                                                 r);
+    }
+  };
+
   LibrbdWriteback::LibrbdWriteback(ImageCtx *ictx, Mutex& lock)
     : m_tid(0), m_lock(lock), m_ictx(ictx) {
   }
@@ -248,8 +269,8 @@ namespace librbd {
     assert(journal_tid == 0 || m_ictx->journal != NULL);
     if (journal_tid != 0) {
       m_ictx->journal->flush_event(
-	journal_tid, new C_WriteJournalCommit(m_ictx, oid.name, object_no, off,
-					      bl, snapc, req_comp,
+        journal_tid, new C_WriteJournalCommit(m_ictx, oid.name, object_no, off,
+                                              bl, snapc, req_comp,
 					      journal_tid));
     } else {
       AioObjectWrite *req = new AioObjectWrite(m_ictx, oid.name, object_no,
@@ -262,22 +283,37 @@ namespace librbd {
 
   void LibrbdWriteback::overwrite_extent(const object_t& oid, uint64_t off,
 					 uint64_t len,
-					 ceph_tid_t journal_tid) {
+					 ceph_tid_t original_journal_tid,
+                                         ceph_tid_t new_journal_tid) {
     typedef std::vector<std::pair<uint64_t,uint64_t> > Extents;
 
+    ldout(m_ictx->cct, 20) << __func__ << ": " << oid << " "
+                           << off << "~" << len << " "
+                           << "journal_tid=" << original_journal_tid << ", "
+                           << "new_journal_tid=" << new_journal_tid << dendl;
+
     assert(m_ictx->owner_lock.is_locked());
     uint64_t object_no = oid_to_object_no(oid.name, m_ictx->object_prefix);
 
     // all IO operations are flushed prior to closing the journal
-    assert(journal_tid != 0 && m_ictx->journal != NULL);
+    assert(original_journal_tid != 0 && m_ictx->journal != NULL);
 
     Extents file_extents;
     Striper::extent_to_file(m_ictx->cct, &m_ictx->layout, object_no, off,
 			    len, file_extents);
     for (Extents::iterator it = file_extents.begin();
 	 it != file_extents.end(); ++it) {
-      m_ictx->journal->commit_io_event_extent(journal_tid, it->first,
-					      it->second, 0);
+      if (new_journal_tid != 0) {
+        // ensure new journal event is safely committed to disk before
+        // committing old event
+        m_ictx->journal->flush_event(
+          new_journal_tid, new C_CommitIOEventExtent(m_ictx,
+                                                     original_journal_tid,
+                                                     it->first, it->second));
+      } else {
+        m_ictx->journal->commit_io_event_extent(original_journal_tid, it->first,
+					        it->second, 0);
+      }
     }
   }
 
diff --git a/src/librbd/LibrbdWriteback.h b/src/librbd/LibrbdWriteback.h
index 018b043..ef5fa75 100644
--- a/src/librbd/LibrbdWriteback.h
+++ b/src/librbd/LibrbdWriteback.h
@@ -42,7 +42,8 @@ namespace librbd {
     using WritebackHandler::write;
 
     virtual void overwrite_extent(const object_t& oid, uint64_t off,
-				  uint64_t len, ceph_tid_t journal_tid);
+				  uint64_t len, ceph_tid_t original_journal_tid,
+                                  ceph_tid_t new_journal_tid);
 
     virtual void get_client_lock();
     virtual void put_client_lock();
diff --git a/src/librbd/Operations.cc b/src/librbd/Operations.cc
index c523b91..7766a12 100644
--- a/src/librbd/Operations.cc
+++ b/src/librbd/Operations.cc
@@ -617,6 +617,14 @@ void Operations<I>::execute_snap_create(const char *snap_name,
   ldout(cct, 5) << this << " " << __func__ << ": snap_name=" << snap_name
                 << dendl;
 
+  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();
+
   operation::SnapshotCreateRequest<I> *req =
     new operation::SnapshotCreateRequest<I>(
       m_image_ctx, new C_NotifyUpdate<I>(m_image_ctx, on_finish), snap_name,
diff --git a/src/librbd/TaskFinisher.h b/src/librbd/TaskFinisher.h
index 201ff01..466537e 100644
--- a/src/librbd/TaskFinisher.h
+++ b/src/librbd/TaskFinisher.h
@@ -16,32 +16,45 @@ class Context;
 
 namespace librbd {
 
-template <typename Task>
-class TaskFinisher {
-public:
-  TaskFinisher(CephContext &cct)
-    : m_cct(cct), m_lock("librbd::TaskFinisher::m_lock"),
-      m_finisher(new Finisher(&cct)),
-      m_safe_timer(new SafeTimer(&cct, m_lock, false))
-  {
-    m_finisher->start();
+struct TaskFinisherSingleton {
+  Mutex m_lock;
+  SafeTimer *m_safe_timer;
+  Finisher *m_finisher;
+
+  explicit TaskFinisherSingleton(CephContext *cct)
+    : m_lock("librbd::TaskFinisher::m_lock") {
+    m_safe_timer = new SafeTimer(cct, m_lock, false);
     m_safe_timer->init();
+    m_finisher = new Finisher(cct, "librbd::TaskFinisher::m_finisher", "taskfin_librbd");
+    m_finisher->start();
   }
-
-  ~TaskFinisher() {
+  virtual ~TaskFinisherSingleton() {
     {
       Mutex::Locker l(m_lock);
       m_safe_timer->shutdown();
       delete m_safe_timer;
     }
-
     m_finisher->wait_for_empty();
     m_finisher->stop();
     delete m_finisher;
   }
+};
+
+
+template <typename Task>
+class TaskFinisher {
+public:
+  TaskFinisher(CephContext &cct) : m_cct(cct) {
+    TaskFinisherSingleton *singleton;
+    cct.lookup_or_create_singleton_object<TaskFinisherSingleton>(
+      singleton, "librbd::TaskFinisher::m_safe_timer");
+    m_lock = &singleton->m_lock;
+    m_safe_timer = singleton->m_safe_timer;
+    m_finisher = singleton->m_finisher;
+  }
 
   void cancel(const Task& task) {
-    Mutex::Locker l(m_lock);
+    Mutex::Locker l(*m_lock);
     typename TaskContexts::iterator it = m_task_contexts.find(task);
     if (it != m_task_contexts.end()) {
       delete it->second.first;
@@ -51,7 +64,7 @@ public:
   }
 
   void cancel_all() {
-    Mutex::Locker l(m_lock);
+    Mutex::Locker l(*m_lock);
     for (typename TaskContexts::iterator it = m_task_contexts.begin();
          it != m_task_contexts.end(); ++it) {
       delete it->second.first;
@@ -60,7 +73,7 @@ public:
   }
 
   bool add_event_after(const Task& task, double seconds, Context *ctx) {
-    Mutex::Locker l(m_lock);
+    Mutex::Locker l(*m_lock);
     if (m_task_contexts.count(task) != 0) {
       // task already scheduled on finisher or timer
       delete ctx;
@@ -78,7 +91,7 @@ public:
   }
 
   bool queue(const Task& task, Context *ctx) {
-    Mutex::Locker l(m_lock);
+    Mutex::Locker l(*m_lock);
     typename TaskContexts::iterator it = m_task_contexts.find(task);
     if (it != m_task_contexts.end()) {
       if (it->second.second != NULL) {
@@ -114,7 +127,7 @@ private:
 
   CephContext &m_cct;
 
-  Mutex m_lock;
+  Mutex *m_lock;
   Finisher *m_finisher;
   SafeTimer *m_safe_timer;
 
@@ -124,7 +137,7 @@ private:
   void complete(const Task& task) {
     Context *ctx = NULL;
     {
-      Mutex::Locker l(m_lock);
+      Mutex::Locker l(*m_lock);
       typename TaskContexts::iterator it = m_task_contexts.find(task);
       if (it != m_task_contexts.end()) {
         ctx = it->second.first;
diff --git a/src/librbd/exclusive_lock/AcquireRequest.cc b/src/librbd/exclusive_lock/AcquireRequest.cc
index 4a4ee6d..f030b0e 100644
--- a/src/librbd/exclusive_lock/AcquireRequest.cc
+++ b/src/librbd/exclusive_lock/AcquireRequest.cc
@@ -10,6 +10,7 @@
 #include "include/stringify.h"
 #include "librbd/ExclusiveLock.h"
 #include "librbd/ImageCtx.h"
+#include "librbd/ImageState.h"
 #include "librbd/ImageWatcher.h"
 #include "librbd/Journal.h"
 #include "librbd/ObjectMap.h"
@@ -123,7 +124,7 @@ Context *AcquireRequest<I>::handle_lock(int *ret_val) {
   ldout(cct, 10) << __func__ << ": r=" << *ret_val << dendl;
 
   if (*ret_val == 0) {
-    return send_open_object_map();
+    return send_refresh();
   } else if (*ret_val != -EBUSY) {
     lderr(cct) << "failed to lock: " << cpp_strerror(*ret_val) << dendl;
     return m_on_finish;
@@ -134,6 +135,37 @@ Context *AcquireRequest<I>::handle_lock(int *ret_val) {
 }
 
 template <typename I>
+Context *AcquireRequest<I>::send_refresh() {
+  if (!m_image_ctx.state->is_refresh_required()) {
+    return send_open_object_map();
+  }
+
+  CephContext *cct = m_image_ctx.cct;
+  ldout(cct, 10) << __func__ << dendl;
+
+  using klass = AcquireRequest<I>;
+  Context *ctx = create_context_callback<klass, &klass::handle_refresh>(this);
+  m_image_ctx.state->acquire_lock_refresh(ctx);
+  return nullptr;
+}
+
+template <typename I>
+Context *AcquireRequest<I>::handle_refresh(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 refresh image: " << cpp_strerror(*ret_val)
+               << dendl;
+    m_error_result = *ret_val;
+    send_unlock();
+    return nullptr;
+  }
+
+  return send_open_object_map();
+}
+
+template <typename I>
 Context *AcquireRequest<I>::send_open_journal() {
   // alert caller that we now own the exclusive lock
   m_on_acquire->complete(0);
diff --git a/src/librbd/exclusive_lock/AcquireRequest.h b/src/librbd/exclusive_lock/AcquireRequest.h
index 9a16fc5..4990abb 100644
--- a/src/librbd/exclusive_lock/AcquireRequest.h
+++ b/src/librbd/exclusive_lock/AcquireRequest.h
@@ -45,32 +45,34 @@ private:
    *    |     |   . . . . . . . . . . . . . . . . . . . . . .             |
    *    |     |   .                                         .             |
    *    |     v   v      (EBUSY)                            .             |
-   *    \--> LOCK_IMAGE * * * * * * * >   GET_LOCKERS . . . .             |
-   *          .   |                         |                             |
-   *    . . . .   |                         |                             |
-   *    .         v                         v                             |
-   *    .     OPEN_OBJECT_MAP (skip if    GET_WATCHERS . . .              |
-   *    .         |            disabled)    |              .              |
-   *    .         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> <----------/
+   *    \--> LOCK_IMAGE * * * * * * * * > GET_LOCKERS . . . .             |
+   *              |                         |                             |
+   *              v                         v                             |
+   *         REFRESH (skip if not         GET_WATCHERS                    |
+   *              |   needed)               |                             |
+   *              v                         v                             |
+   *         OPEN_OBJECT_MAP (skip if     BLACKLIST (skip if blacklist    |
+   *              |           disabled)     |        disabled)            |
+   *              v                         v                             |
+   *         OPEN_JOURNAL (skip if        BREAK_LOCK                      |
+   *              |   *     disabled)       |                             |
+   *              |   *                     \-----------------------------/
+   *              |   * * * * * * * *
+   *              v                 *
+   *          ALLOCATE_JOURNAL_TAG  *
+   *              |            *    *
+   *              |            *    *
+   *              |            v    v
+   *              |         CLOSE_JOURNAL
+   *              |               |
+   *              |               v
+   *              |         CLOSE_OBJECT_MAP
+   *              |               |
+   *              |               v
+   *              |         UNLOCK_IMAGE
+   *              |               |
+   *              v               |
+   *          <finish> <----------/
    *
    * @endverbatim
    */
@@ -104,6 +106,9 @@ private:
   void send_lock();
   Context *handle_lock(int *ret_val);
 
+  Context *send_refresh();
+  Context *handle_refresh(int *ret_val);
+
   Context *send_open_journal();
   Context *handle_open_journal(int *ret_val);
 
diff --git a/src/librbd/image/OpenRequest.cc b/src/librbd/image/OpenRequest.cc
index 3027eca..7714722 100644
--- a/src/librbd/image/OpenRequest.cc
+++ b/src/librbd/image/OpenRequest.cc
@@ -11,6 +11,8 @@
 #include "librbd/image/CloseRequest.h"
 #include "librbd/image/RefreshRequest.h"
 #include "librbd/image/SetSnapRequest.h"
+#include <boost/algorithm/string/predicate.hpp>
+#include "include/assert.h"
 
 #define dout_subsys ceph_subsys_rbd
 #undef dout_prefix
@@ -19,12 +21,19 @@
 namespace librbd {
 namespace image {
 
+namespace {
+
+static uint64_t MAX_METADATA_ITEMS = 128;
+
+}
+
 using util::create_context_callback;
 using util::create_rados_ack_callback;
 
 template <typename I>
 OpenRequest<I>::OpenRequest(I *image_ctx, Context *on_finish)
-  : m_image_ctx(image_ctx), m_on_finish(on_finish), m_error_result(0) {
+  : m_image_ctx(image_ctx), m_on_finish(on_finish), m_error_result(0),
+    m_last_metadata_key(ImageCtx::METADATA_CONF_PREFIX) {
 }
 
 template <typename I>
@@ -63,6 +72,8 @@ Context *OpenRequest<I>::handle_v1_detect_header(int *result) {
 
     m_image_ctx->old_format = true;
     m_image_ctx->header_oid = util::old_header_name(m_image_ctx->name);
+    m_image_ctx->apply_metadata({});
+
     send_register_watch();
   }
   return nullptr;
@@ -260,9 +271,66 @@ Context *OpenRequest<I>::handle_v2_get_stripe_unit_count(int *result) {
     lderr(cct) << "failed to read striping metadata: " << cpp_strerror(*result)
                << dendl;
     send_close_image(*result);
-  } else {
-    send_register_watch();
+    return nullptr;
+  }
+
+  m_image_ctx->init_layout();
+
+  send_v2_apply_metadata();
+  return nullptr;
+}
+
+template <typename I>
+void OpenRequest<I>::send_v2_apply_metadata() {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 10) << this << " " << __func__ << ": "
+                 << "start_key=" << m_last_metadata_key << dendl;
+
+  librados::ObjectReadOperation op;
+  cls_client::metadata_list_start(&op, m_last_metadata_key, MAX_METADATA_ITEMS);
+
+  using klass = OpenRequest<I>;
+  librados::AioCompletion *comp =
+    create_rados_ack_callback<klass, &klass::handle_v2_apply_metadata>(this);
+  m_out_bl.clear();
+  m_image_ctx->md_ctx.aio_operate(m_image_ctx->header_oid, comp, &op,
+                                  &m_out_bl);
+  comp->release();
+}
+
+template <typename I>
+Context *OpenRequest<I>::handle_v2_apply_metadata(int *result) {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 10) << this << " " << __func__ << ": r=" << *result << dendl;
+
+  std::map<std::string, bufferlist> metadata;
+  if (*result == 0) {
+    bufferlist::iterator it = m_out_bl.begin();
+    *result = cls_client::metadata_list_finish(&it, &metadata);
+  }
+
+  if (*result == -EOPNOTSUPP || *result == -EIO) {
+    ldout(cct, 10) << "config metadata not supported by OSD" << dendl;
+  } else if (*result < 0) {
+    lderr(cct) << "failed to retrieve metadata: " << cpp_strerror(*result)
+               << dendl;
+    send_close_image(*result);
+    return nullptr;
   }
+
+  if (!metadata.empty()) {
+    m_metadata.insert(metadata.begin(), metadata.end());
+    m_last_metadata_key = metadata.rbegin()->first;
+    if (boost::starts_with(m_last_metadata_key,
+                           ImageCtx::METADATA_CONF_PREFIX)) {
+      send_v2_apply_metadata();
+      return nullptr;
+    }
+  }
+
+  m_image_ctx->apply_metadata(m_metadata);
+
+  send_register_watch();
   return nullptr;
 }
 
@@ -270,17 +338,18 @@ template <typename I>
 void OpenRequest<I>::send_register_watch() {
   m_image_ctx->init();
 
-  if (!m_image_ctx->read_only) {
-    CephContext *cct = m_image_ctx->cct;
-    ldout(cct, 10) << this << " " << __func__ << dendl;
-
-    using klass = OpenRequest<I>;
-    Context *ctx = create_context_callback<
-      klass, &klass::handle_register_watch>(this);
-    m_image_ctx->register_watch(ctx);
-  } else {
+  if (m_image_ctx->read_only) {
     send_refresh();
+    return;
   }
+
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 10) << this << " " << __func__ << dendl;
+
+  using klass = OpenRequest<I>;
+  Context *ctx = create_context_callback<
+    klass, &klass::handle_register_watch>(this);
+  m_image_ctx->register_watch(ctx);
 }
 
 template <typename I>
@@ -305,7 +374,7 @@ void OpenRequest<I>::send_refresh() {
 
   using klass = OpenRequest<I>;
   RefreshRequest<I> *ctx = RefreshRequest<I>::create(
-    *m_image_ctx,
+    *m_image_ctx, false,
     create_context_callback<klass, &klass::handle_refresh>(this));
   ctx->send();
 }
diff --git a/src/librbd/image/OpenRequest.h b/src/librbd/image/OpenRequest.h
index 48aed37..cf506eb 100644
--- a/src/librbd/image/OpenRequest.h
+++ b/src/librbd/image/OpenRequest.h
@@ -6,6 +6,8 @@
 
 #include "include/int_types.h"
 #include "include/buffer.h"
+#include <map>
+#include <string>
 
 class Context;
 
@@ -30,33 +32,36 @@ private:
    *
    * <start>
    *    |
-   *    | (v1)                          (read only)
-   *    |-----> V1_DETECT_HEADER  . . . . . . . . . . . . . . . . .
-   *    |           |                                             .
-   *    |           \-------------------------------\             .
-   *    | (v2)                                      |             .
-   *    \-----> V2_DETECT_HEADER                    |             .
-   *                |                               |             .
-   *                v                               |             .
-   *            V2_GET_ID|NAME                      |             .
-   *                |                               |             .
-   *                v                               |             .
-   *            V2_GET_IMMUTABLE_METADATA           |             .
-   *                |                               |             .
-   *                v                               v             .
-   *            V2_GET_STRIPE_UNIT_COUNT  ----> REGISTER_WATCH    .
-   *                .                               |             .
-   *                .  (read only)                  v             .
-   *                . . . . . . . . . . . . . > REFRESH < . . . . .
-   *                                             .   |
-   *                                             .   |
-   *                                             .   \--> SET_SNAP
-   *                                   (no snap) .          |
-   *                                             .          v
-   *                                             . . . > <finish>
-   *                                                        ^
-   *     (on error)                                         |
-   *    * * * * * * > CLOSE --------------------------------/
+   *    | (v1)
+   *    |-----> V1_DETECT_HEADER
+   *    |           |
+   *    |           \-------------------------------\
+   *    | (v2)                                      |
+   *    \-----> V2_DETECT_HEADER                    |
+   *                |                               |
+   *                v                               |
+   *            V2_GET_ID|NAME                      |
+   *                |                               |
+   *                v                               |
+   *            V2_GET_IMMUTABLE_METADATA           |
+   *                |                               |
+   *                v                               |
+   *            V2_GET_STRIPE_UNIT_COUNT            |
+   *                |                               |
+   *                v                               v
+   *      /---> V2_APPLY_METADATA -------------> REGISTER_WATCH (skip if
+   *      |         |                               |            read-only)
+   *      \---------/                               v
+   *                                             REFRESH
+   *                                                |
+   *                                                v
+   *                                             SET_SNAP (skip if no snap)
+   *                                                |
+   *                                                v
+   *                                             <finish>
+   *                                                ^
+   *     (on error)                                 |
+   *    * * * * * * > CLOSE ------------------------/
    *
    * @endverbatim
    */
@@ -69,6 +74,9 @@ private:
   bufferlist m_out_bl;
   int m_error_result;
 
+  std::string m_last_metadata_key;
+  std::map<std::string, bufferlist> m_metadata;
+
   void send_v1_detect_header();
   Context *handle_v1_detect_header(int *result);
 
@@ -87,6 +95,9 @@ private:
   void send_v2_get_stripe_unit_count();
   Context *handle_v2_get_stripe_unit_count(int *result);
 
+  void send_v2_apply_metadata();
+  Context *handle_v2_apply_metadata(int *result);
+
   void send_register_watch();
   Context *handle_register_watch(int *result);
 
diff --git a/src/librbd/image/RefreshRequest.cc b/src/librbd/image/RefreshRequest.cc
index dedf307..ebd86ef 100644
--- a/src/librbd/image/RefreshRequest.cc
+++ b/src/librbd/image/RefreshRequest.cc
@@ -28,8 +28,9 @@ using util::create_async_context_callback;
 using util::create_context_callback;
 
 template <typename I>
-RefreshRequest<I>::RefreshRequest(I &image_ctx, Context *on_finish)
-  : m_image_ctx(image_ctx),
+RefreshRequest<I>::RefreshRequest(I &image_ctx, bool acquiring_lock,
+                                  Context *on_finish)
+  : m_image_ctx(image_ctx), m_acquiring_lock(acquiring_lock),
     m_on_finish(create_async_context_callback(m_image_ctx, on_finish)),
     m_error_result(0), m_flush_aio(false), m_exclusive_lock(nullptr),
     m_object_map(nullptr), m_journal(nullptr), m_refresh_parent(nullptr) {
@@ -271,6 +272,12 @@ Context *RefreshRequest<I>::handle_v2_get_mutable_metadata(int *result) {
     return m_on_finish;
   }
 
+  if (m_acquiring_lock && (m_features & RBD_FEATURE_EXCLUSIVE_LOCK) == 0) {
+    ldout(cct, 5) << "ignoring dynamically disabled exclusive lock" << dendl;
+    m_features |= RBD_FEATURE_EXCLUSIVE_LOCK;
+    m_incomplete_update = true;
+  }
+
   send_v2_get_flags();
   return nullptr;
 }
@@ -636,6 +643,7 @@ Context *RefreshRequest<I>::handle_v2_apply(int *result) {
   ldout(cct, 10) << this << " " << __func__ << dendl;
 
   apply();
+
   return send_v2_finalize_refresh_parent();
 }
 
@@ -779,6 +787,11 @@ Context *RefreshRequest<I>::handle_v2_close_object_map(int *result) {
 
 template <typename I>
 Context *RefreshRequest<I>::send_flush_aio() {
+  if (m_incomplete_update && m_error_result == 0) {
+    // if this was a partial refresh, notify ImageState
+    m_error_result = -ERESTART;
+  }
+
   if (m_flush_aio) {
     CephContext *cct = m_image_ctx.cct;
     ldout(cct, 10) << this << " " << __func__ << dendl;
diff --git a/src/librbd/image/RefreshRequest.h b/src/librbd/image/RefreshRequest.h
index 63c858b..58b6487 100644
--- a/src/librbd/image/RefreshRequest.h
+++ b/src/librbd/image/RefreshRequest.h
@@ -27,11 +27,12 @@ template<typename> class RefreshParentRequest;
 template<typename ImageCtxT = ImageCtx>
 class RefreshRequest {
 public:
-  static RefreshRequest *create(ImageCtxT &image_ctx, Context *on_finish) {
-    return new RefreshRequest(image_ctx, on_finish);
+  static RefreshRequest *create(ImageCtxT &image_ctx, bool acquiring_lock,
+                                Context *on_finish) {
+    return new RefreshRequest(image_ctx, acquiring_lock, on_finish);
   }
 
-  RefreshRequest(ImageCtxT &image_ctx, Context *on_finish);
+  RefreshRequest(ImageCtxT &image_ctx, bool acquiring_lock, Context *on_finish);
   ~RefreshRequest();
 
   void send();
@@ -97,6 +98,7 @@ private:
    */
 
   ImageCtxT &m_image_ctx;
+  bool m_acquiring_lock;
   Context *m_on_finish;
 
   int m_error_result;
@@ -129,6 +131,7 @@ private:
   bool m_exclusive_locked;
 
   bool m_blocked_writes = false;
+  bool m_incomplete_update = false;
 
   void send_v1_read_header();
   Context *handle_v1_read_header(int *result);
diff --git a/src/librbd/internal.cc b/src/librbd/internal.cc
index 92fab7f..13682df 100644
--- a/src/librbd/internal.cc
+++ b/src/librbd/internal.cc
@@ -289,7 +289,8 @@ int mirror_image_enable_internal(ImageCtx *ictx) {
   return 0;
 }
 
-int mirror_image_disable_internal(ImageCtx *ictx, bool force) {
+int mirror_image_disable_internal(ImageCtx *ictx, bool force,
+                                  bool remove=true) {
   CephContext *cct = ictx->cct;
 
   cls::rbd::MirrorImage mirror_image_internal;
@@ -334,10 +335,6 @@ int mirror_image_disable_internal(ImageCtx *ictx, bool force) {
     return r;
   }
 
-  if (!is_primary) {
-    goto remove_mirroring_image;
-  }
-
   r = MirroringWatcher<>::notify_image_updated(
     ictx->md_ctx, cls::rbd::MIRROR_IMAGE_STATE_DISABLING,
     ictx->id, mirror_image_internal.global_image_id);
@@ -396,18 +393,19 @@ int mirror_image_disable_internal(ImageCtx *ictx, bool force) {
     }
   }
 
-remove_mirroring_image:
-  r = cls_client::mirror_image_remove(&ictx->md_ctx, ictx->id);
-  if (r < 0) {
-    lderr(cct) << "failed to remove image from mirroring directory: "
-      << cpp_strerror(r) << dendl;
-    return r;
-  }
+  if (remove) {
+    r = cls_client::mirror_image_remove(&ictx->md_ctx, ictx->id);
+    if (r < 0) {
+      lderr(cct) << "failed to remove image from mirroring directory: "
+                 << cpp_strerror(r) << dendl;
+      return r;
+    }
 
-  ldout(cct, 20) << "removed image state from rbd_mirroring object" << dendl;
+    ldout(cct, 20) << "removed image state from rbd_mirroring object" << dendl;
 
-  if (is_primary) {
-    // TODO: send notification to mirroring object about update
+    if (is_primary) {
+      // TODO: send notification to mirroring object about update
+    }
   }
 
   return 0;
@@ -1234,7 +1232,7 @@ remove_mirroring_image:
     r = opts.set(RBD_IMAGE_OPTION_STRIPE_COUNT, stripe_count);
     assert(r == 0);
 
-    r = create(io_ctx, imgname, size, opts);
+    r = create(io_ctx, imgname, size, opts, "", "");
 
     int r1 = opts.get(RBD_IMAGE_OPTION_ORDER, &order_);
     assert(r1 == 0);
@@ -1244,7 +1242,9 @@ remove_mirroring_image:
   }
 
   int create(IoCtx& io_ctx, const char *imgname, uint64_t size,
-	     ImageOptions& opts)
+	     ImageOptions& opts,
+             const std::string &non_primary_global_image_id,
+             const std::string &primary_mirror_uuid)
   {
     CephContext *cct = (CephContext *)io_ctx.cct();
     ldout(cct, 10) << __func__ << " name=" << imgname << ", "
@@ -1339,7 +1339,8 @@ remove_mirroring_image:
 
       r = create_v2(io_ctx, imgname, bid, size, order, features, stripe_unit,
 		    stripe_count, journal_order, journal_splay_width,
-                    journal_pool, "", "");
+                    journal_pool, non_primary_global_image_id,
+                    primary_mirror_uuid);
     }
 
     int r1 = opts.set(RBD_IMAGE_OPTION_ORDER, order);
@@ -1374,9 +1375,48 @@ remove_mirroring_image:
 	    IoCtx& c_ioctx, const char *c_name, ImageOptions& c_opts)
   {
     CephContext *cct = (CephContext *)p_ioctx.cct();
-    ldout(cct, 20) << "clone " << &p_ioctx << " name " << p_name << " snap "
-		   << p_snap_name << " to child " << &c_ioctx << " name "
-		   << c_name << " opts = " << c_opts << dendl;
+    if (p_snap_name == NULL) {
+      lderr(cct) << "image to be cloned must be a snapshot" << dendl;
+      return -EINVAL;
+    }
+
+    // make sure parent snapshot exists
+    ImageCtx *p_imctx = new ImageCtx(p_name, "", p_snap_name, p_ioctx, true);
+    int r = p_imctx->state->open();
+    if (r < 0) {
+      lderr(cct) << "error opening parent image: "
+		 << cpp_strerror(-r) << dendl;
+      delete p_imctx;
+      return r;
+    }
+
+    r = clone(p_imctx, c_ioctx, c_name, c_opts, "", "");
+
+    int close_r = p_imctx->state->close();
+    if (r == 0 && close_r < 0) {
+      r = close_r;
+    }
+
+    if (r < 0) {
+      return r;
+    }
+    return 0;
+  }
+
+  int clone(ImageCtx *p_imctx, IoCtx& c_ioctx, const char *c_name,
+            ImageOptions& c_opts,
+            const std::string &non_primary_global_image_id,
+            const std::string &primary_mirror_uuid)
+  {
+    CephContext *cct = p_imctx->cct;
+    if (p_imctx->snap_id == CEPH_NOSNAP) {
+      lderr(cct) << "image to be cloned must be a snapshot" << dendl;
+      return -EINVAL;
+    }
+
+    ldout(cct, 20) << "clone " << &p_imctx->md_ctx << " name " << p_imctx->name
+                   << " snap " << p_imctx->snap_name << " to child " << &c_ioctx
+                   << " name " << c_name << " opts = " << c_opts << dendl;
 
     bool default_format_set;
     c_opts.is_set(RBD_IMAGE_OPTION_FORMAT, &default_format_set);
@@ -1391,12 +1431,14 @@ remove_mirroring_image:
       return -EINVAL;
     }
 
+    bool use_p_features = true;
     uint64_t features;
-    if (c_opts.get(RBD_IMAGE_OPTION_FEATURES, &features) != 0) {
+    if (c_opts.get(RBD_IMAGE_OPTION_FEATURES, &features) == 0) {
       if (features & ~RBD_FEATURES_ALL) {
 	lderr(cct) << "librbd does not support requested features" << dendl;
 	return -ENOSYS;
       }
+      use_p_features = false;
     }
 
     // make sure child doesn't already exist, in either format
@@ -1406,12 +1448,8 @@ remove_mirroring_image:
       return -EEXIST;
     }
 
-    if (p_snap_name == NULL) {
-      lderr(cct) << "image to be cloned must be a snapshot" << dendl;
-      return -EINVAL;
-    }
-
     bool snap_protected;
+
     uint64_t order;
     uint64_t size;
     uint64_t p_features;
@@ -1419,23 +1457,11 @@ remove_mirroring_image:
     librbd::NoOpProgressContext no_op;
     ImageCtx *c_imctx = NULL;
     map<string, bufferlist> pairs;
-    // make sure parent snapshot exists
-    ImageCtx *p_imctx = new ImageCtx(p_name, "", p_snap_name, p_ioctx, true);
-    r = p_imctx->state->open();
-    if (r < 0) {
-      lderr(cct) << "error opening parent image: "
-		 << cpp_strerror(-r) << dendl;
-      delete p_imctx;
-      return r;
-    }
-
-    parent_spec pspec(p_ioctx.get_id(), p_imctx->id,
-				  p_imctx->snap_id);
+    parent_spec pspec(p_imctx->md_ctx.get_id(), p_imctx->id, p_imctx->snap_id);
 
     if (p_imctx->old_format) {
       lderr(cct) << "parent image must be in new format" << dendl;
-      r = -EINVAL;
-      goto err_close_parent;
+      return -EINVAL;
     }
 
     p_imctx->snap_lock.get_read();
@@ -1446,20 +1472,22 @@ remove_mirroring_image:
 
     if ((p_features & RBD_FEATURE_LAYERING) != RBD_FEATURE_LAYERING) {
       lderr(cct) << "parent image must support layering" << dendl;
-      r = -ENOSYS;
-      goto err_close_parent;
+      return -ENOSYS;
     }
-    
+
     if (r < 0) {
       // we lost the race with snap removal?
       lderr(cct) << "unable to locate parent's snapshot" << dendl;
-      goto err_close_parent;
+      return r;
     }
 
     if (!snap_protected) {
       lderr(cct) << "parent snapshot must be protected" << dendl;
-      r = -EINVAL;
-      goto err_close_parent;
+      return -EINVAL;
+    }
+
+    if (use_p_features) {
+      c_opts.set(RBD_IMAGE_OPTION_FEATURES, p_features);
     }
 
     order = p_imctx->order;
@@ -1467,10 +1495,11 @@ remove_mirroring_image:
       c_opts.set(RBD_IMAGE_OPTION_ORDER, order);
     }
 
-    r = create(c_ioctx, c_name, size, c_opts);
+    r = create(c_ioctx, c_name, size, c_opts, non_primary_global_image_id,
+               primary_mirror_uuid);
     if (r < 0) {
       lderr(cct) << "error creating child: " << cpp_strerror(r) << dendl;
-      goto err_close_parent;
+      return r;
     }
 
     c_imctx = new ImageCtx(c_name, "", NULL, c_ioctx, false);
@@ -1505,7 +1534,8 @@ remove_mirroring_image:
       goto err_remove_child;
     }
 
-    r = cls_client::metadata_list(&p_ioctx, p_imctx->header_oid, "", 0, &pairs);
+    r = cls_client::metadata_list(&p_imctx->md_ctx, p_imctx->header_oid, "", 0,
+                                  &pairs);
     if (r < 0 && r != -EOPNOTSUPP && r != -EIO) {
       lderr(cct) << "couldn't list metadata: " << cpp_strerror(r) << dendl;
       goto err_remove_child;
@@ -1519,11 +1549,6 @@ remove_mirroring_image:
 
     ldout(cct, 2) << "done." << dendl;
     r = c_imctx->state->close();
-    partial_r = p_imctx->state->close();
-
-    if (r == 0 && partial_r < 0) {
-      r = partial_r;
-    }
     return r;
 
   err_remove_child:
@@ -1541,8 +1566,6 @@ remove_mirroring_image:
       lderr(cct) << "Error removing failed clone: "
 		 << cpp_strerror(partial_r) << dendl;
     }
-  err_close_parent:
-    p_imctx->state->close();
     return r;
   }
 
@@ -1623,15 +1646,6 @@ remove_mirroring_image:
       return -EINVAL;
     }
 
-    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) ||
@@ -1643,6 +1657,35 @@ remove_mirroring_image:
       return -EINVAL;
     }
 
+    rbd_mirror_mode_t mirror_mode = RBD_MIRROR_MODE_DISABLED;
+    if ((features & RBD_FEATURE_JOURNALING) != 0) {
+      r = librbd::mirror_mode_get(ictx->md_ctx, &mirror_mode);
+      if (r < 0) {
+        lderr(cct) << "error in retrieving pool mirroring status: "
+                   << cpp_strerror(r) << dendl;
+        return r;
+      }
+
+      // mark mirroring as disabling and prune all sync snapshots
+      // before acquiring locks
+      if (mirror_mode == RBD_MIRROR_MODE_POOL && !enabled) {
+        r = mirror_image_disable_internal(ictx, false, false);
+        if (r < 0) {
+          lderr(cct) << "error disabling image mirroring: "
+                     << cpp_strerror(r) << dendl;
+        }
+      }
+    }
+
+    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;
+    }
+
     // avoid accepting new requests from peers while we manipulate
     // the image features
     if (ictx->exclusive_lock != nullptr) {
@@ -1663,11 +1706,17 @@ remove_mirroring_image:
 
       C_SaferCond lock_ctx;
       ictx->exclusive_lock->request_lock(&lock_ctx);
+
+      // don't block holding lock since refresh might be required
+      ictx->owner_lock.put_read();
       r = lock_ctx.wait();
+      ictx->owner_lock.get_read();
+
       if (r < 0) {
         lderr(cct) << "failed to lock image: " << cpp_strerror(r) << dendl;
         return r;
-      } else if (!ictx->exclusive_lock->is_lock_owner()) {
+      } else if (ictx->exclusive_lock == nullptr ||
+                 !ictx->exclusive_lock->is_lock_owner()) {
         lderr(cct) << "failed to acquire exclusive lock" << dendl;
         return -EROFS;
       }
@@ -1726,14 +1775,6 @@ remove_mirroring_image:
             return r;
           }
 
-          rbd_mirror_mode_t mirror_mode;
-          r = librbd::mirror_mode_get(ictx->md_ctx, &mirror_mode);
-          if (r < 0) {
-            lderr(cct) << "error in retrieving pool mirroring status: "
-              << cpp_strerror(r) << dendl;
-            return r;
-          }
-
           enable_mirroring = (mirror_mode == RBD_MIRROR_MODE_POOL);
         }
 
@@ -1759,7 +1800,7 @@ remove_mirroring_image:
             return -EINVAL;
           }
 
-          disable_flags = RBD_FLAG_OBJECT_MAP_INVALID;
+          disable_flags |= RBD_FLAG_OBJECT_MAP_INVALID;
           r = remove_object_map(ictx);
           if (r < 0) {
             lderr(cct) << "failed to remove object map" << dendl;
@@ -1767,17 +1808,9 @@ remove_mirroring_image:
           }
         }
         if ((features & RBD_FEATURE_FAST_DIFF) != 0) {
-          disable_flags = RBD_FLAG_FAST_DIFF_INVALID;
+          disable_flags |= RBD_FLAG_FAST_DIFF_INVALID;
         }
         if ((features & RBD_FEATURE_JOURNALING) != 0) {
-          rbd_mirror_mode_t mirror_mode;
-          r = librbd::mirror_mode_get(ictx->md_ctx, &mirror_mode);
-          if (r < 0) {
-            lderr(cct) << "error in retrieving pool mirroring status: "
-              << cpp_strerror(r) << dendl;
-            return r;
-          }
-
           if (mirror_mode == RBD_MIRROR_MODE_IMAGE) {
             cls::rbd::MirrorImage mirror_image;
             r = cls_client::mirror_image_get(&ictx->md_ctx, ictx->id,
@@ -1793,13 +1826,23 @@ remove_mirroring_image:
               return -EINVAL;
             }
           } else if (mirror_mode == RBD_MIRROR_MODE_POOL) {
-            r = mirror_image_disable_internal(ictx, false);
+            r = cls_client::mirror_image_remove(&ictx->md_ctx, ictx->id);
             if (r < 0) {
-              lderr(cct) << "error disabling image mirroring: "
-                << cpp_strerror(r) << dendl;
+              lderr(cct) << "failed to remove image from mirroring directory: "
+                         << cpp_strerror(r) << dendl;
+              return r;
             }
           }
 
+          C_SaferCond cond;
+          ictx->journal->close(&cond);
+          r = cond.wait();
+          if (r < 0) {
+            lderr(cct) << "error closing image journal: " << cpp_strerror(r)
+                       << dendl;
+            return r;
+          }
+
           r = Journal<>::remove(ictx->md_ctx, ictx->id);
           if (r < 0) {
             lderr(cct) << "error removing image journal: " << cpp_strerror(r)
@@ -1852,6 +1895,8 @@ remove_mirroring_image:
       }
     }
 
+    ictx->notify_update();
+
     if (ictx->exclusive_lock != nullptr && acquired_lock) {
       C_SaferCond lock_ctx;
       ictx->exclusive_lock->release_lock(&lock_ctx);
@@ -1861,8 +1906,6 @@ remove_mirroring_image:
         return r;
       }
     }
-
-    ictx->notify_update();
     return 0;
   }
 
@@ -1971,7 +2014,8 @@ remove_mirroring_image:
     return 0;
   }
 
-  int remove(IoCtx& io_ctx, const char *imgname, ProgressContext& prog_ctx)
+  int remove(IoCtx& io_ctx, const char *imgname, ProgressContext& prog_ctx,
+             bool force)
   {
     CephContext *cct((CephContext *)io_ctx.cct());
     ldout(cct, 20) << "remove " << &io_ctx << " " << imgname << dendl;
@@ -1992,12 +2036,31 @@ remove_mirroring_image:
 
       ictx->owner_lock.get_read();
       if (ictx->exclusive_lock != nullptr) {
-        r = ictx->operations->prepare_image_update();
-        if (r < 0 || !ictx->exclusive_lock->is_lock_owner()) {
-	  lderr(cct) << "cannot obtain exclusive lock - not removing" << dendl;
-	  ictx->owner_lock.put_read();
-	  ictx->state->close();
-          return -EBUSY;
+        if (force) {
+          // releasing read lock to avoid a deadlock when upgrading to
+          // write lock in the shut_down process
+          ictx->owner_lock.put_read();
+          if (ictx->exclusive_lock != nullptr) {
+            C_SaferCond ctx;
+            ictx->exclusive_lock->shut_down(&ctx);
+            r = ctx.wait();
+            if (r < 0) {
+              lderr(cct) << "error shutting down exclusive lock"
+                         << cpp_strerror(r) << dendl;
+              ictx->state->close();
+              return r;
+            }
+            assert (ictx->exclusive_lock == nullptr);
+            ictx->owner_lock.get_read();
+          }
+        } else {
+          r = ictx->operations->prepare_image_update();
+          if (r < 0 || !ictx->exclusive_lock->is_lock_owner()) {
+	    lderr(cct) << "cannot obtain exclusive lock - not removing" << dendl;
+	    ictx->owner_lock.put_read();
+	    ictx->state->close();
+            return -EBUSY;
+          }
         }
       }
 
@@ -2040,7 +2103,7 @@ remove_mirroring_image:
       }
 
       if (!old_format) {
-        r = mirror_image_disable_internal(ictx, false);
+        r = mirror_image_disable_internal(ictx, force, !force);
         if (r < 0 && r != -EOPNOTSUPP) {
           lderr(cct) << "error disabling image mirroring: " << cpp_strerror(r)
                      << dendl;
@@ -2108,6 +2171,14 @@ remove_mirroring_image:
         }
 	return r;
       }
+
+      ldout(cct, 2) << "removing image from rbd_mirroring object..." << dendl;
+      r = cls_client::mirror_image_remove(&io_ctx, id);
+      if (r < 0 && r != -ENOENT && r != -EOPNOTSUPP) {
+        lderr(cct) << "failed to remove image from mirroring directory: "
+                   << cpp_strerror(r) << dendl;
+        return r;
+      }
     }
 
     ldout(cct, 2) << "done." << dendl;
@@ -2194,7 +2265,7 @@ remove_mirroring_image:
       opts.set(RBD_IMAGE_OPTION_ORDER, order);
     }
 
-    int r = create(dest_md_ctx, destname, src_size, opts);
+    int r = create(dest_md_ctx, destname, src_size, opts, "", "");
     if (r < 0) {
       lderr(cct) << "header creation failed" << dendl;
       return r;
@@ -2688,6 +2759,15 @@ remove_mirroring_image:
   int metadata_set(ImageCtx *ictx, const string &key, const string &value)
   {
     CephContext *cct = ictx->cct;
+    string start = ictx->METADATA_CONF_PREFIX;
+    size_t conf_prefix_len = start.size();
+
+    if(key.size() > conf_prefix_len && !key.compare(0,conf_prefix_len,start)) {
+      string subkey = key.substr(conf_prefix_len, key.size()-conf_prefix_len);
+      int r = cct->_conf->set_val(subkey.c_str(), value);
+      if (r < 0)
+        return r;
+    }
     ldout(cct, 20) << "metadata_set " << ictx << " key=" << key << " value=" << value << dendl;
 
     int r = ictx->state->refresh_if_required();
@@ -2730,8 +2810,13 @@ remove_mirroring_image:
     CephContext *cct = ictx->cct;
     ldout(cct, 20) << "mirror_image_enable " << ictx << dendl;
 
+    int r = ictx->state->refresh_if_required();
+    if (r < 0) {
+      return r;
+    }
+
     cls::rbd::MirrorMode mirror_mode;
-    int r = cls_client::mirror_mode_get(&ictx->md_ctx, &mirror_mode);
+    r = cls_client::mirror_mode_get(&ictx->md_ctx, &mirror_mode);
     if (r < 0) {
       lderr(cct) << "cannot enable mirroring: failed to retrieve mirror mode: "
         << cpp_strerror(r) << dendl;
@@ -2755,8 +2840,13 @@ remove_mirroring_image:
     CephContext *cct = ictx->cct;
     ldout(cct, 20) << "mirror_image_disable " << ictx << dendl;
 
+    int r = ictx->state->refresh_if_required();
+    if (r < 0) {
+      return r;
+    }
+
     cls::rbd::MirrorMode mirror_mode;
-    int r = cls_client::mirror_mode_get(&ictx->md_ctx, &mirror_mode);
+    r = cls_client::mirror_mode_get(&ictx->md_ctx, &mirror_mode);
     if (r < 0) {
       lderr(cct) << "cannot disable mirroring: failed to retrieve pool "
         "mirroring mode: " << cpp_strerror(r) << dendl;
@@ -2781,7 +2871,12 @@ remove_mirroring_image:
     ldout(cct, 20) << __func__ << ": ictx=" << ictx << ", "
                    << "force=" << force << dendl;
 
-    int r = validate_mirroring_enabled(ictx);
+    int r = ictx->state->refresh_if_required();
+    if (r < 0) {
+      return r;
+    }
+
+    r = validate_mirroring_enabled(ictx);
     if (r < 0) {
       return r;
     }
@@ -2816,7 +2911,12 @@ remove_mirroring_image:
     CephContext *cct = ictx->cct;
     ldout(cct, 20) << __func__ << ": ictx=" << ictx << dendl;
 
-    int r = validate_mirroring_enabled(ictx);
+    int r = ictx->state->refresh_if_required();
+    if (r < 0) {
+      return r;
+    }
+
+    r = validate_mirroring_enabled(ictx);
     if (r < 0) {
       return r;
     }
@@ -2842,11 +2942,17 @@ remove_mirroring_image:
 
     C_SaferCond lock_ctx;
     ictx->exclusive_lock->request_lock(&lock_ctx);
+
+    // don't block holding lock since refresh might be required
+    ictx->owner_lock.put_read();
     r = lock_ctx.wait();
+    ictx->owner_lock.get_read();
+
     if (r < 0) {
       lderr(cct) << "failed to lock image: " << cpp_strerror(r) << dendl;
       return r;
-    } else if (!ictx->exclusive_lock->is_lock_owner()) {
+    } else if (ictx->exclusive_lock == nullptr ||
+               !ictx->exclusive_lock->is_lock_owner()) {
       lderr(cct) << "failed to acquire exclusive lock" << dendl;
       return -EROFS;
     }
@@ -2873,7 +2979,12 @@ remove_mirroring_image:
     CephContext *cct = ictx->cct;
     ldout(cct, 20) << __func__ << ": ictx=" << ictx << dendl;
 
-    int r = validate_mirroring_enabled(ictx);
+    int r = ictx->state->refresh_if_required();
+    if (r < 0) {
+      return r;
+    }
+
+    r = validate_mirroring_enabled(ictx);
     if (r < 0) {
       return r;
     }
@@ -2907,8 +3018,13 @@ remove_mirroring_image:
       return -ERANGE;
     }
 
+    int r = ictx->state->refresh_if_required();
+    if (r < 0) {
+      return r;
+    }
+
     cls::rbd::MirrorImage mirror_image_internal;
-    int r = cls_client::mirror_image_get(&ictx->md_ctx, ictx->id,
+    r = cls_client::mirror_image_get(&ictx->md_ctx, ictx->id,
         &mirror_image_internal);
     if (r < 0 && r != -ENOENT) {
       lderr(cct) << "failed to retrieve mirroring state: " << cpp_strerror(r)
@@ -2946,8 +3062,13 @@ remove_mirroring_image:
       return -ERANGE;
     }
 
+    int r = ictx->state->refresh_if_required();
+    if (r < 0) {
+      return r;
+    }
+
     mirror_image_info_t info;
-    int r = mirror_image_get_info(ictx, &info, sizeof(info));
+    r = mirror_image_get_info(ictx, &info, sizeof(info));
     if (r < 0) {
       return r;
     }
diff --git a/src/librbd/internal.h b/src/librbd/internal.h
index dcb0350..3fd0686 100644
--- a/src/librbd/internal.h
+++ b/src/librbd/internal.h
@@ -13,6 +13,7 @@
 #include "include/buffer_fwd.h"
 #include "include/rbd/librbd.hpp"
 #include "include/rbd_types.h"
+#include "librbd/parent_types.h"
 
 enum {
   l_librbd_first = 26000,
@@ -88,6 +89,9 @@ namespace librbd {
   bool image_options_is_empty(rbd_image_options_t opts);
 
   int snap_set(ImageCtx *ictx, const char *snap_name);
+
+  int list_images_v2(librados::IoCtx& io_ctx,
+      std::map<std::string, std::string>& images);
   int list(librados::IoCtx& io_ctx, std::vector<std::string>& names);
   int list_children(ImageCtx *ictx,
 		    std::set<std::pair<std::string, std::string> > & names);
@@ -97,12 +101,14 @@ namespace librbd {
 	     bool old_format, uint64_t features, int *order,
 	     uint64_t stripe_unit, uint64_t stripe_count);
   int create(IoCtx& io_ctx, const char *imgname, uint64_t size,
-	     ImageOptions& opts);
+	     ImageOptions& opts,
+             const std::string &non_primary_global_image_id,
+             const std::string &primary_mirror_uuid);
   int create_v2(IoCtx& io_ctx, const char *imgname, uint64_t bid, uint64_t size,
-		int order, uint64_t features, uint64_t stripe_unit,
-		uint64_t stripe_count, uint8_t journal_order,
-		uint8_t journal_splay_width,
-		const std::string &journal_pool,
+                int order, uint64_t features, uint64_t stripe_unit,
+                uint64_t stripe_count, uint8_t journal_order,
+                uint8_t journal_splay_width,
+                const std::string &journal_pool,
                 const std::string &non_primary_global_image_id,
                 const std::string &primary_mirror_uuid);
   int clone(IoCtx& p_ioctx, const char *p_name, const char *p_snap_name,
@@ -111,6 +117,10 @@ namespace librbd {
 	    uint64_t stripe_unit, int stripe_count);
   int clone(IoCtx& p_ioctx, const char *p_name, const char *p_snap_name,
 	    IoCtx& c_ioctx, const char *c_name, ImageOptions& c_opts);
+  int clone(ImageCtx *p_imctx, IoCtx& c_ioctx, const char *c_name,
+            ImageOptions& c_opts,
+            const std::string &non_primary_global_image_id,
+            const std::string &primary_mirror_uuid);
   int rename(librados::IoCtx& io_ctx, const char *srcname, const char *dstname);
   int info(ImageCtx *ictx, image_info_t& info, size_t image_size);
   int get_old_format(ImageCtx *ictx, uint8_t *old);
@@ -125,7 +135,7 @@ namespace librbd {
   int is_exclusive_lock_owner(ImageCtx *ictx, bool *is_owner);
 
   int remove(librados::IoCtx& io_ctx, const char *imgname,
-	     ProgressContext& prog_ctx);
+	     ProgressContext& prog_ctx, bool force=false);
   int snap_list(ImageCtx *ictx, std::vector<snap_info_t>& snaps);
   int snap_exists(ImageCtx *ictx, const char *snap_name, bool *exists);
   int snap_is_protected(ImageCtx *ictx, const char *snap_name,
diff --git a/src/librbd/journal/Replay.cc b/src/librbd/journal/Replay.cc
index c6c4f55..c57202a 100644
--- a/src/librbd/journal/Replay.cc
+++ b/src/librbd/journal/Replay.cc
@@ -87,6 +87,14 @@ struct ExecuteOp : public Context {
   }
 
   virtual void finish(int r) override {
+    CephContext *cct = image_ctx.cct;
+    if (r < 0) {
+      lderr(cct) << "ExecuteOp: " << __func__ << ": r=" << r << dendl;
+      on_op_complete->complete(r);
+      return;
+    }
+
+    ldout(cct, 20) << "ExecuteOp: " << __func__ << dendl;
     RWLock::RLocker owner_locker(image_ctx.owner_lock);
     execute(event);
   }
@@ -102,7 +110,17 @@ struct C_RefreshIfRequired : public Context {
   }
 
   virtual void finish(int r) override {
+    CephContext *cct = image_ctx.cct;
+
+    if (r < 0) {
+      lderr(cct) << "C_RefreshIfRequired: " << __func__ << ": r=" << r << dendl;
+      image_ctx.op_work_queue->queue(on_finish, r);
+      return;
+    }
+
     if (image_ctx.state->is_refresh_required()) {
+      ldout(cct, 20) << "C_RefreshIfRequired: " << __func__ << ": "
+                     << "refresh required" << dendl;
       image_ctx.state->refresh(on_finish);
       return;
     }
@@ -156,8 +174,6 @@ void Replay<I>::shut_down(bool cancel_ops, Context *on_finish) {
   ldout(cct, 20) << this << " " << __func__ << dendl;
 
   AioCompletion *flush_comp = nullptr;
-  OpTids cancel_op_tids;
-  Contexts op_finish_events;
   on_finish = util::create_async_context_callback(
     m_image_ctx, on_finish);
 
@@ -176,7 +192,9 @@ void Replay<I>::shut_down(bool cancel_ops, Context *on_finish) {
         // OpFinishEvent or waiting for ready)
         if (op_event.on_start_ready == nullptr &&
             op_event.on_op_finish_event != nullptr) {
-          cancel_op_tids.push_back(op_event_pair.first);
+          Context *on_op_finish_event = nullptr;
+          std::swap(on_op_finish_event, op_event.on_op_finish_event);
+          m_image_ctx.op_work_queue->queue(on_op_finish_event, -ERESTART);
         }
       } else if (op_event.on_op_finish_event != nullptr) {
         // start ops waiting for OpFinishEvent
@@ -200,9 +218,6 @@ void Replay<I>::shut_down(bool cancel_ops, Context *on_finish) {
     RWLock::RLocker owner_locker(m_image_ctx.owner_lock);
     AioImageRequest<I>::aio_flush(&m_image_ctx, flush_comp);
   }
-  for (auto op_tid : cancel_op_tids) {
-    handle_op_complete(op_tid, -ERESTART);
-  }
   if (on_finish != nullptr) {
     on_finish->complete(0);
   }
@@ -743,10 +758,8 @@ void Replay<I>::handle_op_complete(uint64_t op_tid, int r) {
             op_event.on_finish_safe != nullptr) || shutting_down);
   }
 
-  // skipped upon error -- so clean up if non-null
-  delete op_event.on_op_finish_event;
-  if (r == -ERESTART) {
-    delete op_event.on_op_complete;
+  if (op_event.on_op_finish_event != nullptr) {
+    op_event.on_op_finish_event->complete(r);
   }
 
   if (op_event.on_finish_ready != nullptr) {
diff --git a/src/librbd/librbd.cc b/src/librbd/librbd.cc
index 2cb5132..c31bd64 100644
--- a/src/librbd/librbd.cc
+++ b/src/librbd/librbd.cc
@@ -87,11 +87,11 @@ struct C_OpenComplete : public Context {
     }
     if (r < 0) {
       *ictxp = nullptr;
-      comp->fail(ictx->cct, r);
+      comp->fail(r);
     } else {
       *ictxp = ictx;
       comp->lock.Lock();
-      comp->complete(ictx->cct);
+      comp->complete();
       comp->put_unlock();
     }
   }
@@ -123,10 +123,10 @@ struct C_CloseComplete : public Context {
   virtual void finish(int r) {
     ldout(cct, 20) << "C_CloseComplete::finish: r=" << r << dendl;
     if (r < 0) {
-      comp->fail(cct, r);
+      comp->fail(r);
     } else {
       comp->lock.Lock();
-      comp->complete(cct);
+      comp->complete();
       comp->put_unlock();
     }
   }
@@ -313,7 +313,7 @@ namespace librbd {
   {
     TracepointProvider::initialize<tracepoint_traits>(get_cct(io_ctx));
     tracepoint(librbd, create4_enter, io_ctx.get_pool_name().c_str(), io_ctx.get_id(), name, size, opts.opts);
-    int r = librbd::create(io_ctx, name, size, opts);
+    int r = librbd::create(io_ctx, name, size, opts, "", "");
     tracepoint(librbd, create4_exit, r);
     return r;
   }
@@ -1619,7 +1619,7 @@ extern "C" int rbd_create4(rados_ioctx_t p, const char *name,
   TracepointProvider::initialize<tracepoint_traits>(get_cct(io_ctx));
   tracepoint(librbd, create4_enter, io_ctx.get_pool_name().c_str(), io_ctx.get_id(), name, size, opts);
   librbd::ImageOptions opts_(opts);
-  int r = librbd::create(io_ctx, name, size, opts_);
+  int r = librbd::create(io_ctx, name, size, opts_, "", "");
   tracepoint(librbd, create4_exit, r);
   return r;
 }
diff --git a/src/librbd/operation/SnapshotRemoveRequest.cc b/src/librbd/operation/SnapshotRemoveRequest.cc
index 1bdba71..8ad123b 100644
--- a/src/librbd/operation/SnapshotRemoveRequest.cc
+++ b/src/librbd/operation/SnapshotRemoveRequest.cc
@@ -101,10 +101,17 @@ void SnapshotRemoveRequest<I>::send_remove_object_map() {
   assert(image_ctx.owner_lock.is_locked());
 
   {
+    CephContext *cct = image_ctx.cct;
     RWLock::WLocker snap_locker(image_ctx.snap_lock);
     RWLock::RLocker object_map_locker(image_ctx.object_map_lock);
+    if (image_ctx.snap_info.find(m_snap_id) == image_ctx.snap_info.end()) {
+      lderr(cct) << this << " " << __func__ << ": snapshot doesn't exist"
+                 << dendl;
+      this->async_complete(-ENOENT);
+      return;
+    }
+
     if (image_ctx.object_map != nullptr) {
-      CephContext *cct = image_ctx.cct;
       ldout(cct, 5) << this << " " << __func__ << dendl;
       m_state = STATE_REMOVE_OBJECT_MAP;
 
diff --git a/src/mds/CDentry.h b/src/mds/CDentry.h
index 7861b4d..0198d5b 100644
--- a/src/mds/CDentry.h
+++ b/src/mds/CDentry.h
@@ -110,7 +110,7 @@ public:
   snapid_t first, last;
 
   dentry_key_t key() { 
-    return dentry_key_t(last, name.c_str()); 
+    return dentry_key_t(last, name.c_str(), hash);
   }
 
 public:
diff --git a/src/mds/CDir.cc b/src/mds/CDir.cc
index 9119582..d99c896 100644
--- a/src/mds/CDir.cc
+++ b/src/mds/CDir.cc
@@ -295,10 +295,11 @@ bool CDir::check_rstats(bool scrub)
   return good;
 }
 
-CDentry *CDir::lookup(const char *name, snapid_t snap)
+CDentry *CDir::lookup(const string& name, snapid_t snap)
 { 
   dout(20) << "lookup (" << snap << ", '" << name << "')" << dendl;
-  map_t::iterator iter = items.lower_bound(dentry_key_t(snap, name));
+  map_t::iterator iter = items.lower_bound(dentry_key_t(snap, name.c_str(),
+							inode->hash_dentry_name(name)));
   if (iter == items.end())
     return 0;
   if (iter->second->name == name &&
@@ -310,9 +311,13 @@ CDentry *CDir::lookup(const char *name, snapid_t snap)
   return 0;
 }
 
-
-
-
+CDentry *CDir::lookup_exact_snap(const string& name, snapid_t last) {
+  map_t::iterator p = items.find(dentry_key_t(last, name.c_str(),
+					      inode->hash_dentry_name(name)));
+  if (p == items.end())
+    return NULL;
+  return p->second;
+}
 
 /***
  * linking fun
diff --git a/src/mds/CDir.h b/src/mds/CDir.h
index 2748526..7b6d488 100644
--- a/src/mds/CDir.h
+++ b/src/mds/CDir.h
@@ -448,16 +448,11 @@ protected:
 
   // -- dentries and inodes --
  public:
-  CDentry* lookup_exact_snap(const std::string& dname, snapid_t last) {
-    map_t::iterator p = items.find(dentry_key_t(last, dname.c_str()));
-    if (p == items.end())
-      return NULL;
-    return p->second;
+  CDentry* lookup_exact_snap(const std::string& dname, snapid_t last);
+  CDentry* lookup(const std::string& n, snapid_t snap=CEPH_NOSNAP);
+  CDentry* lookup(const char *n, snapid_t snap=CEPH_NOSNAP) {
+    return lookup(std::string(n), snap);
   }
-  CDentry* lookup(const std::string& n, snapid_t snap=CEPH_NOSNAP) {
-    return lookup(n.c_str(), snap);
-  }
-  CDentry* lookup(const char *n, snapid_t snap=CEPH_NOSNAP);
 
   CDentry* add_null_dentry(const std::string& dname, 
 			   snapid_t first=2, snapid_t last=CEPH_NOSNAP);
diff --git a/src/mds/MDCache.cc b/src/mds/MDCache.cc
index 152b47b..ddf4684 100644
--- a/src/mds/MDCache.cc
+++ b/src/mds/MDCache.cc
@@ -4558,6 +4558,7 @@ CDir *MDCache::rejoin_invent_dirfrag(dirfrag_t df)
   if (!in->is_dir()) {
     assert(in->state_test(CInode::STATE_REJOINUNDEF));
     in->inode.mode = S_IFDIR;
+    in->inode.dir_layout.dl_dir_hash = g_conf->mds_default_dir_hash;
   }
   CDir *dir = in->get_or_open_dirfrag(this, df.frag);
   dir->state_set(CDir::STATE_REJOINUNDEF);
@@ -5753,6 +5754,8 @@ void MDCache::opened_undef_inode(CInode *in) {
   dout(10) << "opened_undef_inode " << *in << dendl;
   rejoin_undef_inodes.erase(in);
   if (in->is_dir()) {
+    // FIXME: re-hash dentries if necessary
+    assert(in->inode.dir_layout.dl_dir_hash == g_conf->mds_default_dir_hash);
     if (in->has_dirfrags() && !in->dirfragtree.is_leaf(frag_t())) {
       CDir *dir = in->get_dirfrag(frag_t());
       assert(dir);
diff --git a/src/mds/Server.cc b/src/mds/Server.cc
index 750fb41..3137447 100644
--- a/src/mds/Server.cc
+++ b/src/mds/Server.cc
@@ -3274,15 +3274,31 @@ void Server::handle_client_readdir(MDRequestRef& mdr)
 
   // which frag?
   frag_t fg = (__u32)req->head.args.readdir.frag;
+  unsigned req_flags = (__u32)req->head.args.readdir.flags;
   string offset_str = req->get_path2();
-  dout(10) << " frag " << fg << " offset '" << offset_str << "'" << dendl;
+  dout(10) << " frag " << fg << " offset '" << offset_str << "'"
+	   << " flags " << req_flags << dendl;
+
+  __u32 offset_hash = 0;
+  if (!offset_str.empty())
+    offset_hash = ceph_frag_value(diri->hash_dentry_name(offset_str));
 
   // does the frag exist?
   if (diri->dirfragtree[fg.value()] != fg) {
-    frag_t newfg = diri->dirfragtree[fg.value()];
+    frag_t newfg;
+    if (req_flags & CEPH_READDIR_REPLY_BITFLAGS) {
+      if (fg.contains((unsigned)offset_hash)) {
+	newfg = diri->dirfragtree[offset_hash];
+      } else {
+	// client actually wants next frag
+	newfg = diri->dirfragtree[fg.value()];
+      }
+    } else {
+      offset_str.clear();
+      newfg = diri->dirfragtree[fg.value()];
+    }
     dout(10) << " adjust frag " << fg << " -> " << newfg << " " << diri->dirfragtree << dendl;
     fg = newfg;
-    offset_str.clear();
   }
   
   CDir *dir = try_open_auth_dirfrag(diri, fg, mdr);
@@ -3338,7 +3354,7 @@ void Server::handle_client_readdir(MDRequestRef& mdr)
   // build dir contents
   bufferlist dnbl;
   __u32 numfiles = 0;
-  __u8 end = (dir->begin() == dir->end());
+  bool end = (dir->begin() == dir->end());
   for (CDir::map_t::iterator it = dir->begin();
        !end && numfiles < max;
        end = (it == dir->end())) {
@@ -3359,8 +3375,11 @@ void Server::handle_client_readdir(MDRequestRef& mdr)
       continue;
     }
 
-    if (!offset_str.empty() && dn->get_name().compare(offset_str) <= 0)
-      continue;
+    if (!offset_str.empty()) {
+      dentry_key_t offset_key(dn->last, offset_str.c_str(), offset_hash);
+      if (!(offset_key < dn->key()))
+	continue;
+    }
 
     CInode *in = dnl->get_inode();
 
@@ -3428,12 +3447,22 @@ void Server::handle_client_readdir(MDRequestRef& mdr)
     mdcache->lru.lru_touch(dn);
   }
   
-  __u8 complete = (end && offset_str.empty());  // FIXME: what purpose does this serve
+  bool complete = false;
+  __u16 flags = 0;
+  if (end) {
+    flags = CEPH_READDIR_FRAG_END;
+    complete = offset_str.empty(); // FIXME: what purpose does this serve
+    if (complete)
+      flags |= CEPH_READDIR_FRAG_COMPLETE;
+  }
+  // client only understand END and COMPLETE flags ?
+  if (req_flags & CEPH_READDIR_REPLY_BITFLAGS) {
+    flags |= CEPH_READDIR_HASH_ORDER;
+  }
   
   // finish final blob
   ::encode(numfiles, dirbl);
-  ::encode(end, dirbl);
-  ::encode(complete, dirbl);
+  ::encode(flags, dirbl);
   dirbl.claim_append(dnbl);
   
   // yay, reply
@@ -7888,10 +7917,13 @@ void Server::handle_client_lssnap(MDRequestRef& mdr)
   }
 
   ::encode(num, dirbl);
-  __u8 end = (p == infomap.end());
-  ::encode(end, dirbl);  // end
-  __u8 complete = end && last_snapid == 0;
-  ::encode(complete, dirbl);  // complete
+  __u16 flags = 0;
+  if (p == infomap.end()) {
+    flags = CEPH_READDIR_FRAG_END;
+    if (last_snapid == 0)
+      flags |= CEPH_READDIR_FRAG_COMPLETE;
+  }
+  ::encode(flags, dirbl);
   dirbl.claim_append(dnbl);
   
   mdr->reply_extra_bl = dirbl;
diff --git a/src/mds/mdstypes.h b/src/mds/mdstypes.h
index e789856..991712d 100644
--- a/src/mds/mdstypes.h
+++ b/src/mds/mdstypes.h
@@ -721,8 +721,10 @@ WRITE_CLASS_ENCODER(session_info_t)
 struct dentry_key_t {
   snapid_t snapid;
   const char *name;
-  dentry_key_t() : snapid(0), name(0) {}
-  dentry_key_t(snapid_t s, const char *n) : snapid(s), name(n) {}
+  __u32 hash;
+  dentry_key_t() : snapid(0), name(0), hash(0) {}
+  dentry_key_t(snapid_t s, const char *n, __u32 h=0) :
+    snapid(s), name(n), hash(h) {}
 
   bool is_valid() { return name || snapid; }
 
@@ -774,11 +776,15 @@ inline std::ostream& operator<<(std::ostream& out, const dentry_key_t &k)
 inline bool operator<(const dentry_key_t& k1, const dentry_key_t& k2)
 {
   /*
-   * order by name, then snap
+   * order by hash, name, snap
    */
-  int c = strcmp(k1.name, k2.name);
-  return 
-    c < 0 || (c == 0 && k1.snapid < k2.snapid);
+  int c = ceph_frag_value(k1.hash) - ceph_frag_value(k2.hash);
+  if (c)
+    return c < 0;
+  c = strcmp(k1.name, k2.name);
+  if (c)
+    return c < 0;
+  return k1.snapid < k2.snapid;
 }
 
 
diff --git a/src/mon/Monitor.cc b/src/mon/Monitor.cc
index 9f9bf59..719d5aa 100644
--- a/src/mon/Monitor.cc
+++ b/src/mon/Monitor.cc
@@ -86,6 +86,7 @@
 #include "common/config.h"
 #include "common/cmdparse.h"
 #include "include/assert.h"
+#include "include/compat.h"
 
 #define dout_subsys ceph_subsys_mon
 #undef dout_prefix
@@ -464,6 +465,7 @@ const char** Monitor::get_tracked_conf_keys() const
     "clog_to_graylog",
     "clog_to_graylog_host",
     "clog_to_graylog_port",
+    "host",
     "fsid",
     // periodic health to clog
     "mon_health_to_clog",
@@ -2923,7 +2925,7 @@ void Monitor::handle_command(MonOpRequestRef op)
     f->flush(rdata);
 
     ostringstream ss2;
-    ss2 << "report " << rdata.crc32c(6789);
+    ss2 << "report " << rdata.crc32c(CEPH_MON_PORT);
     rs = ss2.str();
     r = 0;
   } else if (prefix == "node ls") {
@@ -3760,14 +3762,14 @@ void Monitor::handle_ping(MonOpRequestRef op)
   MPing *reply = new MPing;
   entity_inst_t inst = m->get_source_inst();
   bufferlist payload;
-  Formatter *f = new JSONFormatter(true);
+  boost::scoped_ptr<Formatter> f(new JSONFormatter(true));
   f->open_object_section("pong");
 
   list<string> health_str;
-  get_health(health_str, NULL, f);
+  get_health(health_str, NULL, f.get());
   {
     stringstream ss;
-    get_mon_status(f, ss);
+    get_mon_status(f.get(), ss);
   }
 
   f->close_section();
@@ -5017,7 +5019,7 @@ int Monitor::write_default_keyring(bufferlist& bl)
   err = bl.write_fd(fd);
   if (!err)
     ::fsync(fd);
-  ::close(fd);
+  VOID_TEMP_FAILURE_RETRY(::close(fd));
 
   return err;
 }
diff --git a/src/msg/simple/Pipe.cc b/src/msg/simple/Pipe.cc
index f6eee33..6d72754 100644
--- a/src/msg/simple/Pipe.cc
+++ b/src/msg/simple/Pipe.cc
@@ -472,13 +472,21 @@ int Pipe::accept()
 	 *  held by somebody trying to make use of the SimpleMessenger lock.
 	 *  So drop locks, wait, and retry. It just looks like a slow network
 	 *  to everybody else.
+	 *
+	 *  We take a ref to existing here since it might get reaped before we
+	 *  wake up (see bug #15870).  We can be confident that it lived until
+	 *  locked it since we held the msgr lock from _lookup_pipe through to
+	 *  locking existing->lock and checking reader_dispatching.
 	 */
+	existing->get();
 	pipe_lock.Unlock();
 	msgr->lock.Unlock();
 	existing->notify_on_dispatch_done = true;
 	while (existing->reader_dispatching)
 	  existing->cond.Wait(existing->pipe_lock);
 	existing->pipe_lock.Unlock();
+	existing->put();
+	existing = nullptr;
 	goto retry_existing_lookup;
       }
 
diff --git a/src/os/bluestore/BlueStore.cc b/src/os/bluestore/BlueStore.cc
index 553bf10..f8e8c73 100644
--- a/src/os/bluestore/BlueStore.cc
+++ b/src/os/bluestore/BlueStore.cc
@@ -3018,7 +3018,7 @@ int BlueStore::collection_list(
     pnext = &static_next;
 
   if (start == ghobject_t::get_max() ||
-      start.hobj == hobject_t::get_max()) {
+      start.hobj.is_max()) {
     goto out;
   }
   get_coll_key_range(c->cid, c->cnode.bits, &temp_start_key, &temp_end_key,
diff --git a/src/osd/OSD.cc b/src/osd/OSD.cc
index 66aebb7..5e7f8de 100644
--- a/src/osd/OSD.cc
+++ b/src/osd/OSD.cc
@@ -5782,6 +5782,7 @@ void OSD::dispatch_session_waiting(Session *session, OSDMapRef osdmap)
   } else {
     register_session_waiting_on_map(session);
   }
+  session->maybe_reset_osdmap();
 }
 
 
@@ -5860,6 +5861,7 @@ void OSD::session_notify_pg_cleared(
   assert(session->session_dispatch_lock.is_locked());
   update_waiting_for_pg(session, osdmap);
   session->waiting_for_pg.erase(pgid);
+  session->maybe_reset_osdmap();
   clear_session_waiting_on_pg(session, pgid);
 }
 
@@ -6542,6 +6544,44 @@ void OSD::osdmap_subscribe(version_t epoch, bool force_request)
   }
 }
 
+void OSD::trim_maps(epoch_t oldest, int nreceived, bool skip_maps)
+{
+  epoch_t min = std::min(oldest, service.map_cache.cached_key_lower_bound());
+  if (min <= superblock.oldest_map)
+    return;
+
+  int num = 0;
+  ObjectStore::Transaction t;
+  for (epoch_t e = superblock.oldest_map; e < min; ++e) {
+    dout(20) << " removing old osdmap epoch " << e << dendl;
+    t.remove(coll_t::meta(), get_osdmap_pobject_name(e));
+    t.remove(coll_t::meta(), get_inc_osdmap_pobject_name(e));
+    superblock.oldest_map = e + 1;
+    num++;
+    if (num >= cct->_conf->osd_target_transaction_size && num >= nreceived) {
+      service.publish_superblock(superblock);
+      write_superblock(t);
+      store->queue_transaction(service.meta_osr.get(), std::move(t), nullptr);
+      num = 0;
+      if (!skip_maps) {
+	// skip_maps leaves us with a range of old maps if we fail to remove all
+	// of them before moving superblock.oldest_map forward to the first map
+	// in the incoming MOSDMap msg. so we should continue removing them in
+	// this case, even we could do huge series of delete transactions all at
+	// once.
+	break;
+      }
+    }
+  }
+  if (num > 0) {
+    service.publish_superblock(superblock);
+    write_superblock(t);
+    store->queue_transaction(service.meta_osr.get(), std::move(t), nullptr);
+  }
+  // we should not remove the cached maps
+  assert(min <= service.map_cache.cached_key_lower_bound());
+}
+
 void OSD::handle_osd_map(MOSDMap *m)
 {
   assert(osd_lock.is_locked());
@@ -6717,23 +6757,8 @@ void OSD::handle_osd_map(MOSDMap *m)
   }
 
   if (superblock.oldest_map) {
-    int num = 0;
-    epoch_t cache_lb = service.map_cache.cached_key_lower_bound();
-    epoch_t min = MIN(m->oldest_map, cache_lb);
-    dout(20) << __func__ << " oldest_map " << m->oldest_map
-	     << " cache lb " << cache_lb
-	     << " min " << min << dendl;
-    for (epoch_t e = superblock.oldest_map; e < min; ++e) {
-      dout(20) << " removing old osdmap epoch " << e << dendl;
-      t.remove(coll_t::meta(), get_osdmap_pobject_name(e));
-      t.remove(coll_t::meta(), get_inc_osdmap_pobject_name(e));
-      superblock.oldest_map = e+1;
-      num++;
-      // make sure we at least keep pace with incoming maps
-      if (num >= cct->_conf->osd_target_transaction_size &&
-	  (uint64_t)num > (last - first))
-	break;
-    }
+    // make sure we at least keep pace with incoming maps
+    trim_maps(m->oldest_map, last - first + 1, skip_maps);
   }
 
   if (!superblock.oldest_map || skip_maps)
@@ -6801,9 +6826,9 @@ void OSD::_committed_osd_maps(epoch_t first, epoch_t last, MOSDMap *m)
       }
     }
 
-    if (osdmap->test_flag(CEPH_OSDMAP_NOUP) &&
-	!newmap->test_flag(CEPH_OSDMAP_NOUP)) {
-      dout(10) << __func__ << " NOUP flag cleared in " << newmap->get_epoch()
+    if (osdmap->test_flag(CEPH_OSDMAP_NOUP) !=
+	newmap->test_flag(CEPH_OSDMAP_NOUP)) {
+      dout(10) << __func__ << " NOUP flag changed in " << newmap->get_epoch()
 	       << dendl;
       if (is_booting()) {
 	// this captures the case where we sent the boot message while
@@ -6993,9 +7018,9 @@ void OSD::_committed_osd_maps(epoch_t first, epoch_t last, MOSDMap *m)
         failure_pending.erase(it++);
       }
     }
-    osd_lock.Unlock();
-    shutdown();
-    osd_lock.Lock();
+    // trigger shutdown in a different thread
+    dout(0) << __func__ << " shutdown OSD via async signal" << dendl;
+    queue_async_signal(SIGINT);
   }
   else if (is_preboot()) {
     if (m->get_source().is_mon())
diff --git a/src/osd/OSD.h b/src/osd/OSD.h
index 8a62ebb..79a0d17 100644
--- a/src/osd/OSD.h
+++ b/src/osd/OSD.h
@@ -1349,8 +1349,11 @@ public:
       session_dispatch_lock("Session::session_dispatch_lock"), 
       last_sent_epoch(0), received_map_epoch(0)
     {}
-
-
+    void maybe_reset_osdmap() {
+      if (waiting_for_pg.empty()) {
+	osdmap.reset();
+      }
+    }
   };
   void update_waiting_for_pg(Session *session, OSDMapRef osdmap);
   void session_notify_pg_create(Session *session, OSDMapRef osdmap, spg_t pgid);
@@ -1449,6 +1452,7 @@ public:
      */
     session->waiting_on_map.clear();
     session->waiting_for_pg.clear();
+    session->osdmap.reset();
   }
   void register_session_waiting_on_pg(Session *session, spg_t pgid) {
     Mutex::Locker l(session_waiting_lock);
@@ -1880,6 +1884,7 @@ private:
   void wait_for_new_map(OpRequestRef op);
   void handle_osd_map(class MOSDMap *m);
   void _committed_osd_maps(epoch_t first, epoch_t last, class MOSDMap *m);
+  void trim_maps(epoch_t oldest, int nreceived, bool skip_maps);
   void note_down_osd(int osd);
   void note_up_osd(int osd);
   friend class C_OnMapCommit;
diff --git a/src/osd/OpRequest.cc b/src/osd/OpRequest.cc
index 8805d1a..9bad447 100644
--- a/src/osd/OpRequest.cc
+++ b/src/osd/OpRequest.cc
@@ -82,6 +82,7 @@ void OpRequest::_unregistered() {
   request->clear_data();
   request->clear_payload();
   request->release_message_throttle();
+  request->set_connection(nullptr);
 }
 
 bool OpRequest::check_rmw(int flag) {
diff --git a/src/osd/PG.cc b/src/osd/PG.cc
index 58d2cc5..51b5c13 100644
--- a/src/osd/PG.cc
+++ b/src/osd/PG.cc
@@ -532,7 +532,7 @@ bool PG::MissingLoc::add_source_info(
 	       << dendl;
       continue;
     }
-    if (oinfo.last_backfill != hobject_t::get_max() &&
+    if (!oinfo.last_backfill.is_max() &&
 	oinfo.last_backfill_bitwise != sort_bitwise) {
       dout(10) << "search_for_missing " << soid << " " << need
 	       << " also missing on osd." << fromosd
@@ -1798,7 +1798,7 @@ void PG::activate(ObjectStore::Transaction& t,
       } else {
 	assert(peer_missing.count(*i));
 	missing_loc.add_active_missing(peer_missing[*i]);
-        if (!peer_missing[*i].have_missing() && peer_info[*i].last_backfill == hobject_t::get_max())
+        if (!peer_missing[*i].have_missing() && peer_info[*i].last_backfill.is_max())
           complete_shards.insert(*i);
       }
     }
@@ -2337,6 +2337,12 @@ void PG::split_into(pg_t child_pgid, PG *child, unsigned split_bits)
   if (get_primary() != child->get_primary())
     child->info.history.same_primary_since = get_osdmap()->get_epoch();
 
+  child->info.stats.up = up;
+  child->info.stats.up_primary = up_primary;
+  child->info.stats.acting = acting;
+  child->info.stats.acting_primary = primary;
+  child->info.stats.mapping_epoch = get_osdmap()->get_epoch();
+
   // History
   child->past_intervals = past_intervals;
 
@@ -3893,8 +3899,10 @@ void PG::replica_scrub(
   // compensate for hobject_t's with wrong pool from sloppy hammer OSDs
   hobject_t start = msg->start;
   hobject_t end = msg->end;
-  start.pool = info.pgid.pool();
-  end.pool = info.pgid.pool();
+  if (!start.is_max())
+    start.pool = info.pgid.pool();
+  if (!end.is_max())
+    end.pool = info.pgid.pool();
 
   build_scrub_map_chunk(
     map, start, end, msg->deep, msg->seed,
@@ -4273,7 +4281,7 @@ void PG::chunky_scrub(ThreadPool::TPHandle &handle)
 	  break;
 	}
 
-	if (cmp(scrubber.end, hobject_t::get_max(), get_sort_bitwise()) < 0) {
+	if (!(scrubber.end.is_max())) {
           scrubber.state = PG::Scrubber::NEW_CHUNK;
 	  requeue_scrub();
           done = true;
diff --git a/src/osd/PG.h b/src/osd/PG.h
index 50a8d72..2121c67 100644
--- a/src/osd/PG.h
+++ b/src/osd/PG.h
@@ -457,7 +457,7 @@ public:
       needs_recovery_map[hoid] = *item;
       auto mliter =
 	missing_loc.insert(make_pair(hoid, set<pg_shard_t>())).first;
-      assert(info.last_backfill == hobject_t::get_max());
+      assert(info.last_backfill.is_max());
       assert(info.last_update >= item->need);
       if (!missing.is_missing(hoid))
 	mliter->second.insert(self);
diff --git a/src/osd/ReplicatedPG.cc b/src/osd/ReplicatedPG.cc
index be65e6e..43a29ad 100644
--- a/src/osd/ReplicatedPG.cc
+++ b/src/osd/ReplicatedPG.cc
@@ -953,7 +953,7 @@ void ReplicatedPG::do_pg_op(OpRequestRef op)
         dout(10) << " pgnls lower_bound " << lower_bound
 		 << " pg_end " << pg_end << dendl;
 	if (get_sort_bitwise() &&
-	    ((lower_bound != hobject_t::get_max() &&
+	    ((!lower_bound.is_max() &&
 	      cmp_bitwise(lower_bound, pg_end) >= 0) ||
 	     (lower_bound != hobject_t() &&
 	      cmp_bitwise(lower_bound, pg_start) < 0))) {
@@ -5577,7 +5577,7 @@ int ReplicatedPG::do_osd_ops(OpContext *ctx, vector<OSDOp>& ops)
 	tracepoint(osd, do_osd_op_pre_omapgetkeys, soid.oid.name.c_str(), soid.snap.val, start_after.c_str(), max_return);
 	set<string> out_set;
 
-	if (oi.is_omap()) {
+	if (pool.info.supports_omap()) {
 	  ObjectMap::ObjectMapIterator iter = osd->store->get_omap_iterator(
 	    coll, ghobject_t(soid)
 	    );
@@ -5614,7 +5614,7 @@ int ReplicatedPG::do_osd_ops(OpContext *ctx, vector<OSDOp>& ops)
 	tracepoint(osd, do_osd_op_pre_omapgetvals, soid.oid.name.c_str(), soid.snap.val, start_after.c_str(), max_return, filter_prefix.c_str());
 	map<string, bufferlist> out_set;
 
-	if (oi.is_omap()) {
+	if (pool.info.supports_omap()) {
 	  ObjectMap::ObjectMapIterator iter = osd->store->get_omap_iterator(
 	    coll, ghobject_t(soid)
 	    );
@@ -5640,7 +5640,7 @@ int ReplicatedPG::do_osd_ops(OpContext *ctx, vector<OSDOp>& ops)
 
     case CEPH_OSD_OP_OMAPGETHEADER:
       tracepoint(osd, do_osd_op_pre_omapgetheader, soid.oid.name.c_str(), soid.snap.val);
-      if (!oi.is_omap()) {
+      if (!pool.info.supports_omap()) {
 	// return empty header
 	break;
       }
@@ -5666,7 +5666,7 @@ int ReplicatedPG::do_osd_ops(OpContext *ctx, vector<OSDOp>& ops)
 	}
 	tracepoint(osd, do_osd_op_pre_omapgetvalsbykeys, soid.oid.name.c_str(), soid.snap.val, list_entries(keys_to_get).c_str());
 	map<string, bufferlist> out;
-	if (oi.is_omap()) {
+	if (pool.info.supports_omap()) {
 	  osd->store->omap_get_values(ch, ghobject_t(soid), keys_to_get, &out);
 	} // else return empty omap entries
 	::encode(out, osd_op.outdata);
@@ -5696,7 +5696,7 @@ int ReplicatedPG::do_osd_ops(OpContext *ctx, vector<OSDOp>& ops)
 	
 	map<string, bufferlist> out;
 
-	if (oi.is_omap()) {
+	if (pool.info.supports_omap()) {
 	  set<string> to_get;
 	  for (map<string, pair<bufferlist, int> >::iterator i = assertions.begin();
 	       i != assertions.end();
@@ -5822,12 +5822,10 @@ int ReplicatedPG::do_osd_ops(OpContext *ctx, vector<OSDOp>& ops)
 	  result = -ENOENT;
 	  break;
 	}
-	if (oi.is_omap()) {
-	  t->omap_clear(soid);
-	  ctx->delta_stats.num_wr++;
-	  obs.oi.clear_omap_digest();
-	  obs.oi.clear_flag(object_info_t::FLAG_OMAP);
-	}
+	t->omap_clear(soid);
+	ctx->delta_stats.num_wr++;
+	obs.oi.clear_omap_digest();
+	obs.oi.clear_flag(object_info_t::FLAG_OMAP);
       }
       break;
 
@@ -7029,7 +7027,7 @@ int ReplicatedPG::fill_in_copy_get(
 
   // omap
   uint32_t omap_keys = 0;
-  if (!pool.info.supports_omap() || !oi.is_omap()) {
+  if (!pool.info.supports_omap()) {
     cursor.omap_complete = true;
   } else {
     if (left > 0 && !cursor.omap_complete) {
diff --git a/src/osd/osd_types.cc b/src/osd/osd_types.cc
index c5f7906..7ff99cb 100644
--- a/src/osd/osd_types.cc
+++ b/src/osd/osd_types.cc
@@ -2761,7 +2761,7 @@ void pg_info_t::encode(bufferlist &bl) const
   ::encode(last_update, bl);
   ::encode(last_complete, bl);
   ::encode(log_tail, bl);
-  if (last_backfill_bitwise && last_backfill != last_backfill.get_max()) {
+  if (last_backfill_bitwise && !last_backfill.is_max()) {
     ::encode(hobject_t(), bl);
   } else {
     ::encode(last_backfill, bl);
diff --git a/src/osdc/ObjectCacher.cc b/src/osdc/ObjectCacher.cc
index f7ae589..7e76b3c 100644
--- a/src/osdc/ObjectCacher.cc
+++ b/src/osdc/ObjectCacher.cc
@@ -213,99 +213,89 @@ bool ObjectCacher::Object::include_all_cached_data(loff_t off, loff_t len)
  * map a range of bytes into buffer_heads.
  * - create missing buffer_heads as necessary.
  */
-int ObjectCacher::Object::map_read(OSDRead *rd,
-				   map<loff_t, BufferHead*>& hits,
-				   map<loff_t, BufferHead*>& missing,
-				   map<loff_t, BufferHead*>& rx,
+int ObjectCacher::Object::map_read(ObjectExtent &ex,
+                                   map<loff_t, BufferHead*>& hits,
+                                   map<loff_t, BufferHead*>& missing,
+                                   map<loff_t, BufferHead*>& rx,
 				   map<loff_t, BufferHead*>& errors)
 {
   assert(oc->lock.is_locked());
-  for (vector<ObjectExtent>::iterator ex_it = rd->extents.begin();
-       ex_it != rd->extents.end();
-       ++ex_it) {
-
-    if (ex_it->oid != oid.oid)
-      continue;
-
-    ldout(oc->cct, 10) << "map_read " << ex_it->oid
-		       << " " << ex_it->offset << "~" << ex_it->length
-		       << dendl;
-
-    loff_t cur = ex_it->offset;
-    loff_t left = ex_it->length;
-
-    map<loff_t, BufferHead*>::iterator p = data_lower_bound(ex_it->offset);
-    while (left > 0) {
-      // at end?
-      if (p == data.end()) {
-	// rest is a miss.
-	BufferHead *n = new BufferHead(this);
-	n->set_start(cur);
-	n->set_length(left);
-	oc->bh_add(this, n);
-	if (complete) {
-	  oc->mark_zero(n);
-	  hits[cur] = n;
-	  ldout(oc->cct, 20) << "map_read miss+complete+zero " << left
-			     << " left, " << *n << dendl;
-	} else {
-	  missing[cur] = n;
-	  ldout(oc->cct, 20) << "map_read miss " << left << " left, " << *n
-			     << dendl;
-	}
-	cur += left;
-	assert(cur == (loff_t)ex_it->offset + (loff_t)ex_it->length);
-	break;  // no more.
+  ldout(oc->cct, 10) << "map_read " << ex.oid 
+      	       << " " << ex.offset << "~" << ex.length
+      	       << dendl;
+  
+  loff_t cur = ex.offset;
+  loff_t left = ex.length;
+
+  map<loff_t, BufferHead*>::iterator p = data_lower_bound(ex.offset);
+  while (left > 0) {
+    // at end?
+    if (p == data.end()) {
+      // rest is a miss.
+      BufferHead *n = new BufferHead(this);
+      n->set_start(cur);
+      n->set_length(left);
+      oc->bh_add(this, n);
+      if (complete) {
+        oc->mark_zero(n);
+        hits[cur] = n;
+        ldout(oc->cct, 20) << "map_read miss+complete+zero " << left << " left, " << *n << dendl;
+      } else {
+        missing[cur] = n;
+        ldout(oc->cct, 20) << "map_read miss " << left << " left, " << *n << dendl;
       }
-
-      if (p->first <= cur) {
-	// have it (or part of it)
-	BufferHead *e = p->second;
-
-	if (e->is_clean() ||
-	    e->is_dirty() ||
-	    e->is_tx() ||
-	    e->is_zero()) {
-	  hits[cur] = e;     // readable!
-	  ldout(oc->cct, 20) << "map_read hit " << *e << dendl;
-	} else if (e->is_rx()) {
-	  rx[cur] = e;       // missing, not readable.
-	  ldout(oc->cct, 20) << "map_read rx " << *e << dendl;
-	} else if (e->is_error()) {
-	  errors[cur] = e;
-	  ldout(oc->cct, 20) << "map_read error " << *e << dendl;
-	} else {
-	  assert(0);
-	}
-
-	loff_t lenfromcur = MIN(e->end() - cur, left);
-	cur += lenfromcur;
-	left -= lenfromcur;
-	++p;
-	continue;  // more?
-
-      } else if (p->first > cur) {
-	// gap.. miss
-	loff_t next = p->first;
-	BufferHead *n = new BufferHead(this);
-	loff_t len = MIN(next - cur, left);
-	n->set_start(cur);
-	n->set_length(len);
-	oc->bh_add(this,n);
-	if (complete) {
-	  oc->mark_zero(n);
-	  hits[cur] = n;
-	  ldout(oc->cct, 20) << "map_read gap+complete+zero " << *n << dendl;
-	} else {
-	  missing[cur] = n;
-	  ldout(oc->cct, 20) << "map_read gap " << *n << dendl;
-	}
-	cur += MIN(left, n->length());
-	left -= MIN(left, n->length());
-	continue;    // more?
+      cur += left;
+      assert(cur == (loff_t)ex.offset + (loff_t)ex.length);
+      break;  // no more.
+    }
+    
+    if (p->first <= cur) {
+      // have it (or part of it)
+      BufferHead *e = p->second;
+
+      if (e->is_clean() ||
+          e->is_dirty() ||
+          e->is_tx() ||
+          e->is_zero()) {
+        hits[cur] = e;     // readable!
+        ldout(oc->cct, 20) << "map_read hit " << *e << dendl;
+      } else if (e->is_rx()) {
+        rx[cur] = e;       // missing, not readable.
+        ldout(oc->cct, 20) << "map_read rx " << *e << dendl;
+      } else if (e->is_error()) {
+        errors[cur] = e;
+        ldout(oc->cct, 20) << "map_read error " << *e << dendl;
+      } else {
+        assert(0);
+      }
+      
+      loff_t lenfromcur = MIN(e->end() - cur, left);
+      cur += lenfromcur;
+      left -= lenfromcur;
+      ++p;
+      continue;  // more?
+      
+    } else if (p->first > cur) {
+      // gap.. miss
+      loff_t next = p->first;
+      BufferHead *n = new BufferHead(this);
+      loff_t len = MIN(next - cur, left);
+      n->set_start(cur);
+      n->set_length(len);
+      oc->bh_add(this,n);
+      if (complete) {
+        oc->mark_zero(n);
+        hits[cur] = n;
+        ldout(oc->cct, 20) << "map_read gap+complete+zero " << *n << dendl;
       } else {
-	assert(0);
+        missing[cur] = n;
+        ldout(oc->cct, 20) << "map_read gap " << *n << dendl;
       }
+      cur += MIN(left, n->length());
+      left -= MIN(left, n->length());
+      continue;    // more?
+    } else {
+      assert(0);
     }
   }
   return 0;
@@ -351,123 +341,117 @@ void ObjectCacher::Object::audit_buffers()
  * //no! - return a bh that includes the write.  may also include
  * other dirty data to left and/or right.
  */
-ObjectCacher::BufferHead *ObjectCacher::Object::map_write(OSDWrite *wr)
+ObjectCacher::BufferHead *ObjectCacher::Object::map_write(ObjectExtent &ex,
+    ceph_tid_t tid)
 {
   assert(oc->lock.is_locked());
   BufferHead *final = 0;
 
-  for (vector<ObjectExtent>::iterator ex_it = wr->extents.begin();
-       ex_it != wr->extents.end();
-       ++ex_it) {
+  ldout(oc->cct, 10) << "map_write oex " << ex.oid
+      	       << " " << ex.offset << "~" << ex.length << dendl;
 
-    if (ex_it->oid != oid.oid) continue;
+  loff_t cur = ex.offset;
+  loff_t left = ex.length;
 
-    ldout(oc->cct, 10) << "map_write oex " << ex_it->oid << " "
-		       << ex_it->offset << "~" << ex_it->length << dendl;
-
-    loff_t cur = ex_it->offset;
-    loff_t left = ex_it->length;
-
-    map<loff_t, BufferHead*>::iterator p = data_lower_bound(ex_it->offset);
-    while (left > 0) {
-      loff_t max = left;
-
-      // at end ?
-      if (p == data.end()) {
-	if (final == NULL) {
-	  final = new BufferHead(this);
-	  final->set_start( cur );
-	  final->set_length( max );
-	  oc->bh_add(this, final);
-	  ldout(oc->cct, 10) << "map_write adding trailing bh " << *final
-			     << dendl;
-	} else {
-	  replace_journal_tid(final, wr->journal_tid);
-	  oc->bh_stat_sub(final);
-	  final->set_length(final->length() + max);
-	  oc->bh_stat_add(final);
-	}
-	left -= max;
-	cur += max;
-	continue;
+  map<loff_t, BufferHead*>::iterator p = data_lower_bound(ex.offset);
+  while (left > 0) {
+    loff_t max = left;
+
+    // at end ?
+    if (p == data.end()) {
+      if (final == NULL) {
+        final = new BufferHead(this);
+        replace_journal_tid(final, tid);
+        final->set_start( cur );
+        final->set_length( max );
+        oc->bh_add(this, final);
+        ldout(oc->cct, 10) << "map_write adding trailing bh " << *final << dendl;
+      } else {
+        oc->bh_stat_sub(final);
+        final->set_length(final->length() + max);
+        oc->bh_stat_add(final);
       }
+      left -= max;
+      cur += max;
+      continue;
+    }
 
-      ldout(oc->cct, 10) << "cur is " << cur << ", p is " << *p->second
-			 << dendl;
-      //oc->verify_stats();
-
-      if (p->first <= cur) {
-	BufferHead *bh = p->second;
-	ldout(oc->cct, 10) << "map_write bh " << *bh << " intersected"
-			   << dendl;
-
-	if (p->first < cur) {
-	  assert(final == 0);
-	  if (cur + max >= bh->end()) {
-	    // we want right bit (one splice)
-	    final = split(bh, cur);   // just split it, take right half.
-	    ++p;
-	    assert(p->second == final);
-	  } else {
-	    // we want middle bit (two splices)
-	    final = split(bh, cur);
-	    ++p;
-	    assert(p->second == final);
-	    split(final, cur+max);
-	  }
-	} else {
-	  assert(p->first == cur);
-	  if (bh->length() <= max) {
-	    // whole bufferhead, piece of cake.
-	  } else {
-	    // we want left bit (one splice)
-	    split(bh, cur + max); // just split
-	  }
-	  if (final) {
-	    oc->mark_dirty(bh);
-	    oc->mark_dirty(final);
-	    --p;  // move iterator back to final
-	    assert(p->second == final);
-	    replace_journal_tid(bh, 0);
-	    merge_left(final, bh);
-	  } else {
-	    final = bh;
-	  }
-	}
+    ldout(oc->cct, 10) << "cur is " << cur << ", p is " << *p->second << dendl;
+    //oc->verify_stats();
 
-	// keep going.
-	loff_t lenfromcur = final->end() - cur;
-	cur += lenfromcur;
-	left -= lenfromcur;
-	++p;
-	continue;
+    if (p->first <= cur) {
+      BufferHead *bh = p->second;
+      ldout(oc->cct, 10) << "map_write bh " << *bh << " intersected" << dendl;
+
+      if (p->first < cur) {
+        assert(final == 0);
+        if (cur + max >= bh->end()) {
+          // we want right bit (one splice)
+          final = split(bh, cur);   // just split it, take right half.
+          replace_journal_tid(final, tid);
+          ++p;
+          assert(p->second == final);
+        } else {
+          // we want middle bit (two splices)
+          final = split(bh, cur);
+          ++p;
+          assert(p->second == final);
+          split(final, cur+max);
+          replace_journal_tid(final, tid);
+        }
       } else {
-	// gap!
-	loff_t next = p->first;
-	loff_t glen = MIN(next - cur, max);
-	ldout(oc->cct, 10) << "map_write gap " << cur << "~" << glen << dendl;
-	if (final) {
-	  replace_journal_tid(final, wr->journal_tid);
-	  oc->bh_stat_sub(final);
-	  final->set_length(final->length() + glen);
-	  oc->bh_stat_add(final);
-	} else {
-	  final = new BufferHead(this);
-	  final->set_start( cur );
-	  final->set_length( glen );
-	  oc->bh_add(this, final);
-	}
+        assert(p->first == cur);
+        if (bh->length() <= max) {
+          // whole bufferhead, piece of cake.
+        } else {
+          // we want left bit (one splice)
+          split(bh, cur + max);        // just split
+        }
+        if (final) {
+          oc->mark_dirty(bh);
+          oc->mark_dirty(final);
+          --p;  // move iterator back to final
+          assert(p->second == final);
+          replace_journal_tid(bh, tid);
+          merge_left(final, bh);
+        } else {
+          final = bh;
+          replace_journal_tid(final, tid);
+        }
+      }
 
-	cur += glen;
-	left -= glen;
-	continue;    // more?
+      // keep going.
+      loff_t lenfromcur = final->end() - cur;
+      cur += lenfromcur;
+      left -= lenfromcur;
+      ++p;
+      continue;
+    } else {
+      // gap!
+      loff_t next = p->first;
+      loff_t glen = MIN(next - cur, max);
+      ldout(oc->cct, 10) << "map_write gap " << cur << "~" << glen << dendl;
+      if (final) {
+        oc->bh_stat_sub(final);
+        final->set_length(final->length() + glen);
+        oc->bh_stat_add(final);
+      } else {
+        final = new BufferHead(this);
+	replace_journal_tid(final, tid);
+        final->set_start( cur );
+        final->set_length( glen );
+        oc->bh_add(this, final);
       }
+
+      cur += glen;
+      left -= glen;
+      continue;    // more?
     }
   }
 
   // set version
   assert(final);
-  replace_journal_tid(final, wr->journal_tid);
+  assert(final->get_journal_tid() == tid);
   ldout(oc->cct, 10) << "map_write final is " << *final << dendl;
 
   return final;
@@ -481,7 +465,7 @@ void ObjectCacher::Object::replace_journal_tid(BufferHead *bh,
   if (bh_tid != 0 && bh_tid != tid) {
     // inform journal that it should not expect a writeback from this extent
     oc->writeback_handler.overwrite_extent(get_oid(), bh->start(),
-					   bh->length(), bh_tid);
+					   bh->length(), bh_tid, tid);
   }
   bh->set_journal_tid(tid);
 }
@@ -1332,7 +1316,7 @@ int ObjectCacher::_readx(OSDRead *rd, ObjectSet *oset, Context *onfinish,
 
     // map extent into bufferheads
     map<loff_t, BufferHead*> hits, missing, rx, errors;
-    o->map_read(rd, hits, missing, rx, errors);
+    o->map_read(*ex_it, hits, missing, rx, errors);
     if (external_call) {
       // retry reading error buffers
       missing.insert(errors.begin(), errors.end());
@@ -1577,13 +1561,13 @@ int ObjectCacher::writex(OSDWrite *wr, ObjectSet *oset, Context *onfreespace)
 			   ex_it->truncate_size, oset->truncate_seq);
 
     // map it all into a single bufferhead.
-    BufferHead *bh = o->map_write(wr);
+    BufferHead *bh = o->map_write(*ex_it, wr->journal_tid);
     bool missing = bh->is_missing();
     bh->snapc = wr->snapc;
-
-    bytes_written += bh->length();
+    
+    bytes_written += ex_it->length;
     if (bh->is_tx()) {
-      bytes_written_in_flush += bh->length();
+      bytes_written_in_flush += ex_it->length;
     }
 
     // adjust buffer pointers (ie "copy" data into my cache)
diff --git a/src/osdc/ObjectCacher.h b/src/osdc/ObjectCacher.h
index b95c3ba..d136c39 100644
--- a/src/osdc/ObjectCacher.h
+++ b/src/osdc/ObjectCacher.h
@@ -201,8 +201,7 @@ class ObjectCacher {
     }
 
     inline bool can_merge_journal(BufferHead *bh) const {
-      return (get_journal_tid() == 0 || bh->get_journal_tid() == 0 ||
-	      get_journal_tid() == bh->get_journal_tid());
+      return (get_journal_tid() == bh->get_journal_tid());
     }
 
     struct ptr_lt {
@@ -344,13 +343,13 @@ class ObjectCacher {
 
     bool is_cached(loff_t off, loff_t len);
     bool include_all_cached_data(loff_t off, loff_t len);
-    int map_read(OSDRead *rd,
-		 map<loff_t, BufferHead*>& hits,
-		 map<loff_t, BufferHead*>& missing,
-		 map<loff_t, BufferHead*>& rx,
+    int map_read(ObjectExtent &ex,
+                 map<loff_t, BufferHead*>& hits,
+                 map<loff_t, BufferHead*>& missing,
+                 map<loff_t, BufferHead*>& rx,
 		 map<loff_t, BufferHead*>& errors);
-    BufferHead *map_write(OSDWrite *wr);
-
+    BufferHead *map_write(ObjectExtent &ex, ceph_tid_t tid);
+    
     void replace_journal_tid(BufferHead *bh, ceph_tid_t tid);
     void truncate(loff_t s);
     void discard(loff_t off, loff_t len);
diff --git a/src/osdc/Objecter.cc b/src/osdc/Objecter.cc
index c1f4e91..2f028cf 100644
--- a/src/osdc/Objecter.cc
+++ b/src/osdc/Objecter.cc
@@ -706,7 +706,9 @@ int Objecter::linger_check(LingerOp *info)
 		 << " age " << age << dendl;
   if (info->last_error)
     return info->last_error;
-  return std::chrono::duration_cast<std::chrono::milliseconds>(age).count();
+  // return a safe upper bound (we are truncating to ms)
+  return
+    1 + std::chrono::duration_cast<std::chrono::milliseconds>(age).count();
 }
 
 void Objecter::linger_cancel(LingerOp *info)
diff --git a/src/osdc/Striper.cc b/src/osdc/Striper.cc
index 2c83520..e2af5c1 100644
--- a/src/osdc/Striper.cc
+++ b/src/osdc/Striper.cc
@@ -241,10 +241,9 @@ uint64_t Striper::object_truncate_size(CephContext *cct,
 uint64_t Striper::get_num_objects(const file_layout_t& layout,
 				  uint64_t size)
 {
-  __u32 object_size = layout.object_size;
   __u32 stripe_unit = layout.stripe_unit;
   __u32 stripe_count = layout.stripe_count;
-  uint64_t period = (uint64_t)stripe_count * object_size;
+  uint64_t period = layout.get_period();
   uint64_t num_periods = (size + period - 1) / period;
   uint64_t remainder_bytes = size % period;
   uint64_t remainder_objs = 0;
diff --git a/src/osdc/WritebackHandler.h b/src/osdc/WritebackHandler.h
index f0efd20..842ae54 100644
--- a/src/osdc/WritebackHandler.h
+++ b/src/osdc/WritebackHandler.h
@@ -37,7 +37,8 @@ class WritebackHandler {
                            ceph_tid_t journal_tid, Context *oncommit) = 0;
 
   virtual void overwrite_extent(const object_t& oid, uint64_t off, uint64_t len,
-                                ceph_tid_t journal_tid) {}
+                                ceph_tid_t original_journal_tid,
+                                ceph_tid_t new_journal_tid) {}
 
   virtual bool can_scattered_write() { return false; }
   virtual ceph_tid_t write(const object_t& oid, const object_locator_t& oloc,
diff --git a/src/pybind/ceph_volume_client.py b/src/pybind/ceph_volume_client.py
index d5748f1..e37c904 100644
--- a/src/pybind/ceph_volume_client.py
+++ b/src/pybind/ceph_volume_client.py
@@ -203,28 +203,36 @@ class CephFSVolumeClient(object):
     """
 
     # Where shall we create our volumes?
-    VOLUME_PREFIX = "/volumes"
     POOL_PREFIX = "fsvolume_"
-    POOL_NS_PREFIX = "fsvolumens_"
+    DEFAULT_VOL_PREFIX = "/volumes"
+    DEFAULT_NS_PREFIX = "fsvolumens_"
 
-    def __init__(self, auth_id, conf_path, cluster_name):
+    def __init__(self, auth_id, conf_path, cluster_name, volume_prefix=None, pool_ns_prefix=None):
         self.fs = None
         self.rados = None
         self.connected = False
         self.conf_path = conf_path
         self.cluster_name = cluster_name
         self.auth_id = auth_id
+        self.volume_prefix = volume_prefix if volume_prefix else self.DEFAULT_VOL_PREFIX
+        self.pool_ns_prefix = pool_ns_prefix if pool_ns_prefix else self.DEFAULT_NS_PREFIX
 
-    def evict(self, auth_id, timeout=30):
+    def evict(self, auth_id, timeout=30, volume_path=None):
         """
-        Evict all clients using this authorization ID. Assumes that the
-        authorisation key has been revoked prior to calling this function.
+        Evict all clients based on the authorization ID and optionally based on
+        the volume path mounted.  Assumes that the authorization key has been
+        revoked prior to calling this function.
 
         This operation can throw an exception if the mon cluster is unresponsive, or
         any individual MDS daemon is unresponsive for longer than the timeout passed in.
         """
 
-        log.info("evict: {0}".format(auth_id))
+        client_spec = ["auth_name={0}".format(auth_id), ]
+        if volume_path:
+            client_spec.append("client_metadata.root={0}".
+                               format(self._get_path(volume_path)))
+
+        log.info("evict clients with {0}".format(', '.join(client_spec)))
 
         mds_map = self._rados_command("mds dump", {})
 
@@ -239,7 +247,8 @@ class CephFSVolumeClient(object):
         # the latter doesn't give us per-mds output
         threads = []
         for rank, gid in up.items():
-            thread = RankEvicter(self, ["auth_name={0}".format(auth_id)], rank, gid, mds_map, timeout)
+            thread = RankEvicter(self, client_spec, rank, gid, mds_map,
+                                 timeout)
             thread.start()
             threads.append(thread)
 
@@ -250,9 +259,9 @@ class CephFSVolumeClient(object):
 
         for t in threads:
             if not t.success:
-                msg = "Failed to evict client {0} from mds {1}/{2}: {3}".format(
-                    auth_id, t.rank, t.gid, t.exception
-                )
+                msg = ("Failed to evict client with {0} from mds {1}/{2}: {3}".
+                       format(', '.join(client_spec), t.rank, t.gid, t.exception)
+                      )
                 log.error(msg)
                 raise EvictionError(msg)
 
@@ -262,7 +271,7 @@ class CephFSVolumeClient(object):
         :return: absolute path (string)
         """
         return os.path.join(
-            self.VOLUME_PREFIX,
+            self.volume_prefix,
             volume_path.group_id if volume_path.group_id is not None else NO_GROUP_NAME,
             volume_path.volume_id)
 
@@ -271,7 +280,7 @@ class CephFSVolumeClient(object):
             raise ValueError("group_id may not be None")
 
         return os.path.join(
-            self.VOLUME_PREFIX,
+            self.volume_prefix,
             group_id
         )
 
@@ -402,7 +411,7 @@ class CephFSVolumeClient(object):
     def destroy_group(self, group_id):
         path = self._get_group_path(group_id)
         try:
-            self.fs.stat(self.VOLUME_PREFIX)
+            self.fs.stat(self.volume_prefix)
         except cephfs.ObjectNotFound:
             pass
         else:
@@ -458,7 +467,7 @@ 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)
+        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)
 
@@ -479,7 +488,7 @@ class CephFSVolumeClient(object):
         log.info("delete_volume: {0}".format(volume_path))
 
         # Create the trash folder if it doesn't already exist
-        trash = os.path.join(self.VOLUME_PREFIX, "_deleting")
+        trash = os.path.join(self.volume_prefix, "_deleting")
         self._mkdir_p(trash)
 
         # We'll move it to here
@@ -501,7 +510,7 @@ class CephFSVolumeClient(object):
         function is idempotent.
         """
 
-        trash = os.path.join(self.VOLUME_PREFIX, "_deleting")
+        trash = os.path.join(self.volume_prefix, "_deleting")
         trashed_volume = os.path.join(trash, volume_path.volume_id)
 
         try:
@@ -517,7 +526,10 @@ class CephFSVolumeClient(object):
             d = self.fs.readdir(dir_handle)
             while d:
                 if d.d_name not in [".", ".."]:
-                    d_full = os.path.join(root_path, d.d_name)
+                    # Do not use os.path.join because it is sensitive
+                    # to string encoding, we just pass through dnames
+                    # as byte arrays
+                    d_full = "{0}/{1}".format(root_path, d.d_name)
                     if d.is_dir():
                         rmtree(d_full)
                     else:
diff --git a/src/pybind/rbd/rbd.pyx b/src/pybind/rbd/rbd.pyx
index 23a9895..5dadc37 100644
--- a/src/pybind/rbd/rbd.pyx
+++ b/src/pybind/rbd/rbd.pyx
@@ -18,8 +18,10 @@ from cpython cimport PyObject, ref, exc
 from libc cimport errno
 from libc.stdint cimport *
 from libc.stdlib cimport realloc, free
+from libc.string cimport strdup
 
 from collections import Iterable
+from datetime import datetime
 
 cimport rados
 
@@ -33,6 +35,9 @@ cdef extern from "Python.h":
     char* PyBytes_AsString(PyObject *string) except NULL
     int _PyBytes_Resize(PyObject **string, Py_ssize_t newsize) except -1
 
+cdef extern from "time.h":
+    ctypedef long int time_t
+
 ctypedef int (*librbd_progress_fn_t)(uint64_t offset, uint64_t total, void* ptr)
 
 cdef extern from "rbd/librbd.h" nogil:
@@ -81,6 +86,43 @@ cdef extern from "rbd/librbd.h" nogil:
         uint64_t size
         char *name
 
+    ctypedef enum rbd_mirror_mode_t:
+        _RBD_MIRROR_MODE_DISABLED "RBD_MIRROR_MODE_DISABLED"
+        _RBD_MIRROR_MODE_IMAGE "RBD_MIRROR_MODE_IMAGE"
+        _RBD_MIRROR_MODE_POOL "RBD_MIRROR_MODE_POOL"
+
+    ctypedef struct rbd_mirror_peer_t:
+        char *uuid
+        char *cluster_name
+        char *client_name
+
+    ctypedef enum rbd_mirror_image_state_t:
+        _RBD_MIRROR_IMAGE_DISABLING "RBD_MIRROR_IMAGE_DISABLING"
+        _RBD_MIRROR_IMAGE_ENABLED "RBD_MIRROR_IMAGE_ENABLED"
+        _RBD_MIRROR_IMAGE_DISABLED "RBD_MIRROR_IMAGE_DISABLED"
+
+    ctypedef struct rbd_mirror_image_info_t:
+        char *global_id
+        rbd_mirror_image_state_t state
+        bint primary
+
+    ctypedef enum rbd_mirror_image_status_state_t:
+        _MIRROR_IMAGE_STATUS_STATE_UNKNOWN "MIRROR_IMAGE_STATUS_STATE_UNKNOWN"
+        _MIRROR_IMAGE_STATUS_STATE_ERROR "MIRROR_IMAGE_STATUS_STATE_ERROR"
+        _MIRROR_IMAGE_STATUS_STATE_SYNCING "MIRROR_IMAGE_STATUS_STATE_SYNCING"
+        _MIRROR_IMAGE_STATUS_STATE_STARTING_REPLAY "MIRROR_IMAGE_STATUS_STATE_STARTING_REPLAY"
+        _MIRROR_IMAGE_STATUS_STATE_REPLAYING "MIRROR_IMAGE_STATUS_STATE_REPLAYING"
+        _MIRROR_IMAGE_STATUS_STATE_STOPPING_REPLAY "MIRROR_IMAGE_STATUS_STATE_STOPPING_REPLAY"
+        _MIRROR_IMAGE_STATUS_STATE_STOPPED "MIRROR_IMAGE_STATUS_STATE_STOPPED"
+
+    ctypedef struct rbd_mirror_image_status_t:
+        char *name
+        rbd_mirror_image_info_t info
+        rbd_mirror_image_status_state_t state
+        char *description
+        time_t last_update
+        bint up
+
     void rbd_version(int *major, int *minor, int *extra)
 
     void rbd_image_options_create(rbd_image_options_t* opts)
@@ -108,6 +150,31 @@ cdef extern from "rbd/librbd.h" nogil:
     int rbd_remove(rados_ioctx_t io, const char *name)
     int rbd_rename(rados_ioctx_t src_io_ctx, const char *srcname,
                    const char *destname)
+
+    int rbd_mirror_mode_get(rados_ioctx_t io, rbd_mirror_mode_t *mirror_mode)
+    int rbd_mirror_mode_set(rados_ioctx_t io, rbd_mirror_mode_t mirror_mode)
+    int rbd_mirror_peer_add(rados_ioctx_t io, char *uuid,
+                            size_t uuid_max_length, const char *cluster_name,
+                            const char *client_name)
+    int rbd_mirror_peer_remove(rados_ioctx_t io, const char *uuid)
+    int rbd_mirror_peer_list(rados_ioctx_t io_ctx, rbd_mirror_peer_t *peers,
+                             int *max_peers)
+    void rbd_mirror_peer_list_cleanup(rbd_mirror_peer_t *peers, int max_peers)
+    int rbd_mirror_peer_set_client(rados_ioctx_t io, const char *uuid,
+                                   const char *client_name)
+    int rbd_mirror_peer_set_cluster(rados_ioctx_t io_ctx, const char *uuid,
+                                    const char *cluster_name)
+    int rbd_mirror_image_status_list(rados_ioctx_t io, const char *start_id,
+                                     size_t max, char **image_ids,
+                                     rbd_mirror_image_status_t *images,
+                                     size_t *len)
+    void rbd_mirror_image_status_list_cleanup(char **image_ids,
+                                              rbd_mirror_image_status_t *images,
+                                              size_t len)
+    int rbd_mirror_image_status_summary(rados_ioctx_t io,
+                                        rbd_mirror_image_status_state_t *states,
+                                        int *counts, size_t *maxlen)
+
     int rbd_open(rados_ioctx_t io, const char *name,
                  rbd_image_t *image, const char *snap_name)
     int rbd_open_read_only(rados_ioctx_t io, const char *name,
@@ -179,6 +246,17 @@ cdef extern from "rbd/librbd.h" nogil:
     int rbd_flush(rbd_image_t image)
     int rbd_invalidate_cache(rbd_image_t image)
 
+    int rbd_mirror_image_enable(rbd_image_t image)
+    int rbd_mirror_image_disable(rbd_image_t image, bint force)
+    int rbd_mirror_image_promote(rbd_image_t image, bint force)
+    int rbd_mirror_image_demote(rbd_image_t image)
+    int rbd_mirror_image_resync(rbd_image_t image)
+    int rbd_mirror_image_get_info(rbd_image_t image,
+                                  rbd_mirror_image_info_t *mirror_image_info,
+                                  size_t info_size)
+    int rbd_mirror_image_get_status(rbd_image_t image,
+                                    rbd_mirror_image_status_t *mirror_image_status,
+                                    size_t status_size)
 
 RBD_FEATURE_LAYERING = _RBD_FEATURE_LAYERING
 RBD_FEATURE_STRIPINGV2 = _RBD_FEATURE_STRIPINGV2
@@ -196,6 +274,22 @@ RBD_FEATURES_ALL = _RBD_FEATURES_ALL
 
 RBD_FLAG_OBJECT_MAP_INVALID = _RBD_FLAG_OBJECT_MAP_INVALID
 
+RBD_MIRROR_MODE_DISABLED = _RBD_MIRROR_MODE_DISABLED
+RBD_MIRROR_MODE_IMAGE = _RBD_MIRROR_MODE_IMAGE
+RBD_MIRROR_MODE_POOL = _RBD_MIRROR_MODE_POOL
+
+RBD_MIRROR_IMAGE_DISABLING = _RBD_MIRROR_IMAGE_DISABLING
+RBD_MIRROR_IMAGE_ENABLED = _RBD_MIRROR_IMAGE_ENABLED
+RBD_MIRROR_IMAGE_DISABLED = _RBD_MIRROR_IMAGE_DISABLED
+
+MIRROR_IMAGE_STATUS_STATE_UNKNOWN = _MIRROR_IMAGE_STATUS_STATE_UNKNOWN
+MIRROR_IMAGE_STATUS_STATE_ERROR = _MIRROR_IMAGE_STATUS_STATE_ERROR
+MIRROR_IMAGE_STATUS_STATE_SYNCING = _MIRROR_IMAGE_STATUS_STATE_SYNCING
+MIRROR_IMAGE_STATUS_STATE_STARTING_REPLAY = _MIRROR_IMAGE_STATUS_STATE_STARTING_REPLAY
+MIRROR_IMAGE_STATUS_STATE_REPLAYING = _MIRROR_IMAGE_STATUS_STATE_REPLAYING
+MIRROR_IMAGE_STATUS_STATE_STOPPING_REPLAY = _MIRROR_IMAGE_STATUS_STATE_STOPPING_REPLAY
+MIRROR_IMAGE_STATUS_STATE_STOPPED = _MIRROR_IMAGE_STATUS_STATE_STOPPED
+
 RBD_IMAGE_OPTION_FORMAT = _RBD_IMAGE_OPTION_FORMAT
 RBD_IMAGE_OPTION_FEATURES = _RBD_IMAGE_OPTION_FEATURES
 RBD_IMAGE_OPTION_ORDER = _RBD_IMAGE_OPTION_ORDER
@@ -368,7 +462,7 @@ class RBD(object):
         return (major, minor, extra)
 
     def create(self, ioctx, name, size, order=None, old_format=True,
-               features=0, stripe_unit=0, stripe_count=0):
+               features=None, stripe_unit=0, stripe_count=0):
         """
         Create an rbd image.
 
@@ -405,7 +499,7 @@ class RBD(object):
         if order is not None:
             _order = order
         if old_format:
-            if features != 0 or stripe_unit != 0 or stripe_count != 0:
+            if features or stripe_unit != 0 or stripe_count != 0:
                 raise InvalidArgument('format 1 images do not support feature'
                                       ' masks or non-default striping')
             with nogil:
@@ -415,8 +509,10 @@ class RBD(object):
             try:
                 rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_FORMAT,
                                              1 if old_format else 2)
-                rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_FEATURES,
-                                             features)
+                if features is not None:
+                    rbd_image_options_set_uint64(opts,
+                                                 RBD_IMAGE_OPTION_FEATURES,
+                                                 features)
                 rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_ORDER,
                                              _order)
                 rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_STRIPE_UNIT,
@@ -432,7 +528,7 @@ class RBD(object):
             raise make_ex(ret, 'error creating image')
 
     def clone(self, p_ioctx, p_name, p_snapname, c_ioctx, c_name,
-              features=0, order=None, stripe_unit=0, stripe_count=0):
+              features=None, order=None, stripe_unit=0, stripe_count=0):
         """
         Clone a parent rbd snapshot into a COW sparse child.
 
@@ -475,8 +571,9 @@ class RBD(object):
 
         rbd_image_options_create(&opts)
         try:
-            rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_FEATURES,
-                                         features)
+            if features is not None:
+                rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_FEATURES,
+                                             features)
             rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_ORDER,
                                          order)
             rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_STRIPE_UNIT,
@@ -566,6 +663,314 @@ class RBD(object):
         if ret != 0:
             raise make_ex(ret, 'error renaming image')
 
+    def mirror_mode_get(self, ioctx):
+        """
+        Get pool mirror mode.
+
+        :param ioctx: determines which RADOS pool is read
+        :type ioctx: :class:`rados.Ioctx`
+        :returns: int - pool mirror mode
+        """
+        cdef:
+            rados_ioctx_t _ioctx = convert_ioctx(ioctx)
+            rbd_mirror_mode_t mirror_mode
+        with nogil:
+            ret = rbd_mirror_mode_get(_ioctx, &mirror_mode)
+        if ret != 0:
+            raise make_ex(ret, 'error getting mirror mode')
+        return mirror_mode
+
+    def mirror_mode_set(self, ioctx, mirror_mode):
+        """
+        Set pool mirror mode.
+
+        :param ioctx: determines which RADOS pool is written
+        :type ioctx: :class:`rados.Ioctx`
+        :param mirror_mode: mirror mode to set
+        :type mirror_mode: int
+        """
+        cdef:
+            rados_ioctx_t _ioctx = convert_ioctx(ioctx)
+            rbd_mirror_mode_t _mirror_mode = mirror_mode
+        with nogil:
+            ret = rbd_mirror_mode_set(_ioctx, _mirror_mode)
+        if ret != 0:
+            raise make_ex(ret, 'error setting mirror mode')
+
+    def mirror_peer_add(self, ioctx, cluster_name, client_name):
+        """
+        Add mirror peer.
+
+        :param ioctx: determines which RADOS pool is used
+        :type ioctx: :class:`rados.Ioctx`
+        :param cluster_name: mirror peer cluster name
+        :type cluster_name: str
+        :param client_name: mirror peer client name
+        :type client_name: str
+        :returns: str - peer uuid
+        """
+        cluster_name = cstr(cluster_name, 'cluster_name')
+        client_name = cstr(client_name, 'client_name')
+        cdef:
+            rados_ioctx_t _ioctx = convert_ioctx(ioctx)
+            char *_uuid = NULL
+            size_t _uuid_max_length = 512
+            char *_cluster_name = cluster_name
+            char *_client_name = client_name
+        try:
+            _uuid = <char *>realloc_chk(_uuid, _uuid_max_length)
+            ret = rbd_mirror_peer_add(_ioctx, _uuid, _uuid_max_length,
+                                      _cluster_name, _client_name)
+            if ret != 0:
+                raise make_ex(ret, 'error adding mirror peer')
+            return decode_cstr(_uuid)
+        finally:
+            free(_uuid)
+
+    def mirror_peer_remove(self, ioctx, uuid):
+        """
+        Remove mirror peer.
+
+        :param ioctx: determines which RADOS pool is used
+        :type ioctx: :class:`rados.Ioctx`
+        :param uuid: peer uuid
+        :type uuid: str
+        """
+        uuid = cstr(uuid, 'uuid')
+        cdef:
+            rados_ioctx_t _ioctx = convert_ioctx(ioctx)
+            char *_uuid = uuid
+        with nogil:
+            ret = rbd_mirror_peer_remove(_ioctx, _uuid)
+        if ret != 0:
+            raise make_ex(ret, 'error removing mirror peer')
+
+    def mirror_peer_list(self, ioctx):
+        """
+        Iterate over the peers of a pool.
+
+        :param ioctx: determines which RADOS pool is read
+        :type ioctx: :class:`rados.Ioctx`
+        :returns: :class:`MirrorPeerIterator`
+        """
+        return MirrorPeerIterator(ioctx)
+
+    def mirror_peer_set_client(self, ioctx, uuid, client_name):
+        """
+        Set mirror peer client name
+
+        :param ioctx: determines which RADOS pool is written
+        :type ioctx: :class:`rados.Ioctx`
+        :param uuid: uuid of the mirror peer
+        :type uuid: str
+        :param client_name: client name of the mirror peer to set
+        :type client_name: str
+        """
+        uuid = cstr(uuid, 'uuid')
+        client_name = cstr(client_name, 'client_name')
+        cdef:
+            rados_ioctx_t _ioctx = convert_ioctx(ioctx)
+            char *_uuid = uuid
+            char *_client_name = client_name
+        with nogil:
+            ret = rbd_mirror_peer_set_client(_ioctx, _uuid, _client_name)
+        if ret != 0:
+            raise make_ex(ret, 'error setting mirror peer client')
+
+    def mirror_peer_set_cluster(self, ioctx, uuid, cluster_name):
+        """
+        Set mirror peer cluster name
+
+        :param ioctx: determines which RADOS pool is written
+        :type ioctx: :class:`rados.Ioctx`
+        :param uuid: uuid of the mirror peer
+        :type uuid: str
+        :param cluster_name: cluster name of the mirror peer to set
+        :type cluster_name: str
+        """
+        uuid = cstr(uuid, 'uuid')
+        cluster_name = cstr(cluster_name, 'cluster_name')
+        cdef:
+            rados_ioctx_t _ioctx = convert_ioctx(ioctx)
+            char *_uuid = uuid
+            char *_cluster_name = cluster_name
+        with nogil:
+            ret = rbd_mirror_peer_set_cluster(_ioctx, _uuid, _cluster_name)
+        if ret != 0:
+            raise make_ex(ret, 'error setting mirror peer cluster')
+
+    def mirror_image_status_list(self, ioctx):
+        """
+        Iterate over the mirror image statuses of a pool.
+
+        :param ioctx: determines which RADOS pool is read
+        :type ioctx: :class:`rados.Ioctx`
+        :returns: :class:`MirrorImageStatus`
+        """
+        return MirrorImageStatusIterator(ioctx)
+
+    def mirror_image_status_summary(self, ioctx):
+        """
+        Get mirror image status summary of a pool.
+
+        :param ioctx: determines which RADOS pool is read
+        :type ioctx: :class:`rados.Ioctx`
+        :returns: list - a list of (state, count) tuples
+        """
+        cdef:
+            rados_ioctx_t _ioctx = convert_ioctx(ioctx)
+            rbd_mirror_image_status_state_t *states = NULL
+            int *counts = NULL
+            size_t maxlen = 32
+        try:
+            states = <rbd_mirror_image_status_state_t *>realloc_chk(states,
+                sizeof(rbd_mirror_image_status_state_t) * maxlen)
+            counts = <int *>realloc_chk(counts, sizeof(int) * maxlen)
+            with nogil:
+                ret = rbd_mirror_image_status_summary(_ioctx, states, counts,
+                                                      &maxlen)
+            if ret < 0:
+                raise make_ex(ret, 'error getting mirror image status summary')
+            return [(states[i], counts[i]) for i in range(maxlen)]
+        finally:
+            free(states)
+            free(counts)
+
+cdef class MirrorPeerIterator(object):
+    """
+    Iterator over mirror peer info for a pool.
+
+    Yields a dictionary containing information about a peer.
+
+    Keys are:
+
+    * ``uuid`` (str) - uuid of the peer
+
+    * ``cluster_name`` (str) - cluster name of the peer
+
+    * ``client_name`` (str) - client name of the peer
+    """
+
+    cdef:
+        rbd_mirror_peer_t *peers
+        int num_peers
+
+    def __init__(self, ioctx):
+        cdef:
+            rados_ioctx_t _ioctx = convert_ioctx(ioctx)
+        self.peers = NULL
+        self.num_peers = 10
+        while True:
+            self.peers = <rbd_mirror_peer_t *>realloc_chk(
+                self.peers, self.num_peers * sizeof(rbd_mirror_peer_t))
+            with nogil:
+                ret = rbd_mirror_peer_list(_ioctx, self.peers, &self.num_peers)
+            if ret < 0:
+                if ret == -errno.ERANGE:
+                    continue
+                self.num_peers = 0
+                raise make_ex(ret, 'error listing peers')
+            break
+
+    def __iter__(self):
+        for i in range(self.num_peers):
+            yield {
+                'uuid'         : decode_cstr(self.peers[i].uuid),
+                'cluster_name' : decode_cstr(self.peers[i].cluster_name),
+                'client_name'  : decode_cstr(self.peers[i].client_name),
+                }
+
+    def __dealloc__(self):
+        if self.peers:
+            rbd_mirror_peer_list_cleanup(self.peers, self.num_peers)
+            free(self.peers)
+
+cdef class MirrorImageStatusIterator(object):
+    """
+    Iterator over mirror image status for a pool.
+
+    Yields a dictionary containing mirror status of an image.
+
+    Keys are:
+
+        * ``name`` (str) - mirror image name
+
+        * `info` (dict) - mirror image info
+
+        * `state` (int) - mirror state
+
+        * `description` (str) - status description
+
+        * `last_update` (datetime) - last status update time
+
+        * ``up`` (bool) - is mirroring agent up
+    """
+
+    cdef:
+        rados_ioctx_t ioctx
+        size_t max_read
+        char *last_read
+        char **image_ids
+        rbd_mirror_image_status_t *images
+        size_t size
+
+    def __init__(self, ioctx):
+        self.ioctx = convert_ioctx(ioctx)
+        self.max_read = 1024
+        self.last_read = strdup("")
+        self.image_ids = <char **>realloc_chk(NULL,
+            sizeof(char *) * self.max_read)
+        self.images = <rbd_mirror_image_status_t *>realloc_chk(NULL,
+            sizeof(rbd_mirror_image_status_t) * self.max_read)
+        self.size = 0
+        self.get_next_chunk()
+
+    def __iter__(self):
+        while self.size > 0:
+            for i in range(self.size):
+                yield {
+                    'name'        : decode_cstr(self.images[i].name),
+                    'info'        : {
+                        'global_id' : decode_cstr(self.images[i].info.global_id),
+                        'state'     : self.images[i].info.state,
+                        },
+                    'state'       : self.images[i].state,
+                    'description' : decode_cstr(self.images[i].description),
+                    'last_update' : datetime.fromtimestamp(self.images[i].last_update),
+                    'up'          : self.images[i].up,
+                    }
+            if self.size < self.max_read:
+                break
+            self.get_next_chunk()
+
+    def __dealloc__(self):
+        rbd_mirror_image_status_list_cleanup(self.image_ids, self.images,
+                                             self.size)
+        if self.last_read:
+            free(self.last_read)
+        if self.image_ids:
+            free(self.image_ids)
+        if self.images:
+            free(self.images)
+
+    def get_next_chunk(self):
+        if self.size > 0:
+            rbd_mirror_image_status_list_cleanup(self.image_ids, self.images,
+                                                 self.size)
+            self.size = 0
+        with nogil:
+            ret = rbd_mirror_image_status_list(self.ioctx, self.last_read,
+                                               self.max_read, self.image_ids,
+                                               self.images, &self.size)
+        if ret < 0:
+            raise make_ex(ret, 'error listing mirror images status')
+        if self.size > 0:
+            free(self.last_read)
+            last_read = decode_cstr(self.image_ids[self.size - 1])
+            self.last_read = strdup(last_read)
+        else:
+            free(self.last_read)
+            self.last_read = strdup("")
 
 cdef int diff_iterate_cb(uint64_t offset, size_t length, int write, void *cb) \
     except? -9000 with gil:
@@ -857,8 +1262,8 @@ cdef class Image(object):
             raise make_ex(ret, 'error getting lock status for image' % (self.name))
         return owner == 1
 
-    def copy(self, dest_ioctx, dest_name, features=0, order=None, stripe_unit=0,
-             stripe_count=0):
+    def copy(self, dest_ioctx, dest_name, features=None, order=None,
+             stripe_unit=0, stripe_count=0):
         """
         Copy the image to another location.
 
@@ -890,8 +1295,9 @@ cdef class Image(object):
 
         rbd_image_options_create(&opts)
         try:
-            rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_FEATURES,
-                                         features)
+            if features is not None:
+                rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_FEATURES,
+                                             features)
             rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_ORDER,
                                          order)
             rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_STRIPE_UNIT,
@@ -1398,6 +1804,130 @@ written." % (self.name, ret, length))
         if ret < 0:
             raise make_ex(ret, 'error unlocking image')
 
+    def mirror_image_enable(self):
+        """
+        Enable mirroring for the image.
+        """
+        with nogil:
+            ret = rbd_mirror_image_enable(self.image)
+        if ret < 0:
+            raise make_ex(ret, 'error enabling mirroring for image %s'
+                          % (self.name,))
+
+    def mirror_image_disable(self, force):
+        """
+        Disable mirroring for the image.
+
+        :param force: force disabling
+        :type force: bool
+        """
+        cdef bint c_force = force
+        with nogil:
+            ret = rbd_mirror_image_disable(self.image, c_force)
+        if ret < 0:
+            raise make_ex(ret, 'error disabling mirroring for image %s' %
+                          (self.name,))
+
+    def mirror_image_promote(self, force):
+        """
+        Promote the image to primary for mirroring.
+
+        :param force: force promoting
+        :type force: bool
+        """
+        cdef bint c_force = force
+        with nogil:
+            ret = rbd_mirror_image_promote(self.image, c_force)
+        if ret < 0:
+            raise make_ex(ret, 'error promoting image %s to primary' %
+                          (self.name,))
+
+    def mirror_image_demote(self):
+        """
+        Demote the image to secondary for mirroring.
+        """
+        with nogil:
+            ret = rbd_mirror_image_demote(self.image)
+        if ret < 0:
+            raise make_ex(ret, 'error demoting image %s to secondary' %
+                          (self.name,))
+
+    def mirror_image_resync(self):
+        """
+        Flag the image to resync.
+        """
+        with nogil:
+            ret = rbd_mirror_image_resync(self.image)
+        if ret < 0:
+            raise make_ex(ret, 'error to resync image %s' % (self.name,))
+
+    def mirror_image_get_info(self):
+        """
+        Get mirror info for the image.
+
+        :returns: dict - contains the following keys:
+
+            * ``global_id`` (str) - image global id
+
+            * ``state`` (int) - mirror state
+
+            * ``primary`` (bool) - is image primary
+        """
+        cdef rbd_mirror_image_info_t c_info
+        with nogil:
+            ret = rbd_mirror_image_get_info(self.image, &c_info, sizeof(c_info))
+        if ret != 0:
+            raise make_ex(ret, 'error getting mirror info for image %s' %
+                          (self.name,))
+        info = {
+            'global_id' : decode_cstr(c_info.global_id),
+            'state'     : int(c_info.state),
+            'primary'   : c_info.primary,
+            }
+        free(c_info.global_id)
+        return info
+
+    def mirror_image_get_status(self):
+        """
+        Get mirror status for the image.
+
+        :returns: dict - contains the following keys:
+
+            * ``name`` (str) - mirror image name
+
+            * `info` (dict) - mirror image info
+
+            * ``state`` (int) - status mirror state
+
+            * ``description`` (str) - status description
+
+            * ``last_update`` (datetime) - last status update time
+
+            * ``up`` (bool) - is mirroring agent up
+        """
+        cdef rbd_mirror_image_status_t c_status
+        with nogil:
+            ret = rbd_mirror_image_get_status(self.image, &c_status,
+                                              sizeof(c_status))
+        if ret != 0:
+            raise make_ex(ret, 'error getting mirror status for image %s' %
+                          (self.name,))
+        status = {
+            'name'      : decode_cstr(c_status.name),
+            'info'      : {
+                'global_id' : decode_cstr(c_status.info.global_id),
+                'state'     : int(c_status.info.state),
+                'primary'   : c_status.info.primary,
+                },
+            'state'       : c_status.state,
+            'description' : decode_cstr(c_status.description),
+            'last_update' : datetime.fromtimestamp(c_status.last_update),
+            'up'          : c_status.up,
+            }
+        free(c_status.name)
+        free(c_status.info.global_id)
+        free(c_status.description)
+        return status
 
 cdef class SnapIterator(object):
     """
diff --git a/src/rgw/rgw_admin.cc b/src/rgw/rgw_admin.cc
index 657adcd..87c27ed 100644
--- a/src/rgw/rgw_admin.cc
+++ b/src/rgw/rgw_admin.cc
@@ -3819,7 +3819,8 @@ int main(int argc, char **argv)
       jf.flush(bl);
 
       JSONParser p;
-      ret = send_to_remote_gateway(url, info, bl, p);
+      ret = send_to_remote_or_url(remote, url, access_key, secret_key,
+                                  info, bl, p);
       if (ret < 0) {
         cerr << "request failed: " << cpp_strerror(-ret) << std::endl;
         return ret;
diff --git a/src/rgw/rgw_op.h b/src/rgw/rgw_op.h
index c8b1cd9..d0884ea 100644
--- a/src/rgw/rgw_op.h
+++ b/src/rgw/rgw_op.h
@@ -1372,12 +1372,13 @@ static inline int put_data_and_throttle(RGWPutObjProcessor *processor,
 
   do {
     void *handle;
+    rgw_obj obj;
 
-    int ret = processor->handle_data(data, ofs, hash, &handle, &again);
+    int ret = processor->handle_data(data, ofs, hash, &handle, &obj, &again);
     if (ret < 0)
       return ret;
 
-    ret = processor->throttle_data(handle, need_to_wait);
+    ret = processor->throttle_data(handle, obj, need_to_wait);
     if (ret < 0)
       return ret;
 
diff --git a/src/rgw/rgw_rados.cc b/src/rgw/rgw_rados.cc
index 8a3420c..e6cb001 100644
--- a/src/rgw/rgw_rados.cc
+++ b/src/rgw/rgw_rados.cc
@@ -2146,7 +2146,7 @@ RGWPutObjProcessor_Aio::~RGWPutObjProcessor_Aio()
   if (is_complete)
     return;
 
-  list<rgw_obj>::iterator iter;
+  set<rgw_obj>::iterator iter;
   bool is_multipart_obj = false;
   rgw_obj multipart_obj;
 
@@ -2158,7 +2158,7 @@ RGWPutObjProcessor_Aio::~RGWPutObjProcessor_Aio()
    * details is describled on #11749
    */ 
   for (iter = written_objs.begin(); iter != written_objs.end(); ++iter) {
-    rgw_obj &obj = *iter;
+    const rgw_obj &obj = *iter;
     if (RGW_OBJ_NS_MULTIPART == obj.ns) {
       ldout(store->ctx(), 5) << "NOTE: we should not process the multipart object (" << obj << ") here" << dendl;
       multipart_obj = *iter;
@@ -2187,7 +2187,6 @@ int RGWPutObjProcessor_Aio::handle_obj_data(rgw_obj& obj, bufferlist& bl, off_t
     obj_len = abs_ofs + bl.length();
 
   if (!(obj == last_written_obj)) {
-    add_written_obj(obj);
     last_written_obj = obj;
   }
 
@@ -2197,7 +2196,6 @@ int RGWPutObjProcessor_Aio::handle_obj_data(rgw_obj& obj, bufferlist& bl, off_t
                                      bl,
                                      ((ofs != 0) ? ofs : -1),
                                      exclusive, phandle);
-
   return r;
 }
 
@@ -2216,6 +2214,11 @@ int RGWPutObjProcessor_Aio::wait_pending_front()
   }
   struct put_obj_aio_info info = pop_pending();
   int ret = store->aio_wait(info.handle);
+
+  if (ret >= 0) {
+    add_written_obj(info.obj);
+  }
+
   return ret;
 }
 
@@ -2239,13 +2242,14 @@ int RGWPutObjProcessor_Aio::drain_pending()
   return ret;
 }
 
-int RGWPutObjProcessor_Aio::throttle_data(void *handle, bool need_to_wait)
+int RGWPutObjProcessor_Aio::throttle_data(void *handle, const rgw_obj& obj, bool need_to_wait)
 {
   bool _wait = need_to_wait;
 
   if (handle) {
     struct put_obj_aio_info info;
     info.handle = handle;
+    info.obj = obj;
     pending.push_back(info);
   }
   size_t orig_size = pending.size();
@@ -2273,7 +2277,7 @@ int RGWPutObjProcessor_Aio::throttle_data(void *handle, bool need_to_wait)
   return 0;
 }
 
-int RGWPutObjProcessor_Atomic::write_data(bufferlist& bl, off_t ofs, void **phandle, bool exclusive)
+int RGWPutObjProcessor_Atomic::write_data(bufferlist& bl, off_t ofs, void **phandle, rgw_obj *pobj, bool exclusive)
 {
   if (ofs >= next_part_ofs) {
     int r = prepare_next_part(ofs);
@@ -2282,10 +2286,12 @@ int RGWPutObjProcessor_Atomic::write_data(bufferlist& bl, off_t ofs, void **phan
     }
   }
 
+  *pobj = cur_obj;
+
   return RGWPutObjProcessor_Aio::handle_obj_data(cur_obj, bl, ofs - cur_part_ofs, ofs, phandle, exclusive);
 }
 
-int RGWPutObjProcessor_Atomic::handle_data(bufferlist& bl, off_t ofs, MD5 *hash, void **phandle, bool *again)
+int RGWPutObjProcessor_Atomic::handle_data(bufferlist& bl, off_t ofs, MD5 *hash, void **phandle, rgw_obj *pobj, bool *again)
 {
   *again = false;
 
@@ -2334,7 +2340,7 @@ int RGWPutObjProcessor_Atomic::handle_data(bufferlist& bl, off_t ofs, MD5 *hash,
   bool exclusive = (!write_ofs && immutable_head()); /* immutable head object, need to verify nothing exists there
                                                         we could be racing with another upload, to the same
                                                         object and cleanup can be messy */
-  int ret = write_data(bl, write_ofs, phandle, exclusive);
+  int ret = write_data(bl, write_ofs, phandle, pobj, exclusive);
   if (ret >= 0) { /* we might return, need to clear bl as it was already sent */
     if (hash) {
       hash->Update((const byte *)bl.c_str(), bl.length());
@@ -2422,19 +2428,20 @@ int RGWPutObjProcessor_Atomic::complete_writing_data()
   }
   while (pending_data_bl.length()) {
     void *handle;
+    rgw_obj obj;
     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);
+    int r = write_data(bl, data_ofs, &handle, &obj, 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);
+    r = throttle_data(handle, obj, false);
     if (r < 0) {
       ldout(store->ctx(), 0) << "ERROR: throttle_data() returned " << r << dendl;
       return r;
@@ -6190,7 +6197,8 @@ public:
 
     do {
       void *handle;
-      int ret = processor->handle_data(bl, ofs, NULL, &handle, &again);
+      rgw_obj obj;
+      int ret = processor->handle_data(bl, ofs, NULL, &handle, &obj, &again);
       if (ret < 0)
         return ret;
 
@@ -6201,7 +6209,7 @@ public:
         ret = opstate->renew_state();
         if (ret < 0) {
           ldout(processor->ctx(), 0) << "ERROR: RGWRadosPutObj::handle_data(): failed to renew op state ret=" << ret << dendl;
-          int r = processor->throttle_data(handle, false);
+          int r = processor->throttle_data(handle, obj, false);
           if (r < 0) {
             ldout(processor->ctx(), 0) << "ERROR: RGWRadosPutObj::handle_data(): processor->throttle_data() returned " << r << dendl;
           }
@@ -6212,7 +6220,7 @@ public:
         need_opstate = false;
       }
 
-      ret = processor->throttle_data(handle, false);
+      ret = processor->throttle_data(handle, obj, false);
       if (ret < 0)
         return ret;
     } while (again);
@@ -6963,12 +6971,13 @@ int RGWRados::copy_obj_data(RGWObjectCtx& obj_ctx,
 
     do {
       void *handle;
+      rgw_obj obj;
 
-      ret = processor.handle_data(bl, ofs, NULL, &handle, &again);
+      ret = processor.handle_data(bl, ofs, NULL, &handle, &obj, &again);
       if (ret < 0) {
         return ret;
       }
-      ret = processor.throttle_data(handle, false);
+      ret = processor.throttle_data(handle, obj, false);
       if (ret < 0)
         return ret;
     } while (again);
@@ -7592,7 +7601,7 @@ int RGWRados::Object::Delete::delete_obj()
 
 int RGWRados::delete_obj(RGWObjectCtx& obj_ctx,
                          RGWBucketInfo& bucket_info,
-                         rgw_obj& obj,
+                         const rgw_obj& obj,
                          int versioning_status,
                          uint16_t bilog_flags,
                          const real_time& expiration_time)
diff --git a/src/rgw/rgw_rados.h b/src/rgw/rgw_rados.h
index 0ced215..af290cb 100644
--- a/src/rgw/rgw_rados.h
+++ b/src/rgw/rgw_rados.h
@@ -2153,7 +2153,7 @@ public:
     int complete_atomic_modification();
 
   public:
-    Object(RGWRados *_store, RGWBucketInfo& _bucket_info, RGWObjectCtx& _ctx, rgw_obj& _obj) : store(_store), bucket_info(_bucket_info),
+    Object(RGWRados *_store, RGWBucketInfo& _bucket_info, RGWObjectCtx& _ctx, const rgw_obj& _obj) : store(_store), bucket_info(_bucket_info),
                                                                                                ctx(_ctx), obj(_obj), bs(store),
                                                                                                state(NULL), versioning_disabled(false),
                                                                                                bs_initialized(false) {}
@@ -2564,7 +2564,7 @@ public:
   /** Delete an object.*/
   virtual int delete_obj(RGWObjectCtx& obj_ctx,
                          RGWBucketInfo& bucket_owner,
-                         rgw_obj& src_obj,
+                         const rgw_obj& src_obj,
                          int versioning_status,
                          uint16_t bilog_flags = 0,
                          const ceph::real_time& expiration_time = ceph::real_time());
@@ -3075,8 +3075,8 @@ public:
     store = _store;
     return 0;
   }
-  virtual int handle_data(bufferlist& bl, off_t ofs, MD5 *hash, void **phandle, bool *again) = 0;
-  virtual int throttle_data(void *handle, bool need_to_wait) = 0;
+  virtual int handle_data(bufferlist& bl, off_t ofs, MD5 *hash, void **phandle, rgw_obj *pobj, bool *again) = 0;
+  virtual int throttle_data(void *handle, const rgw_obj& obj, bool need_to_wait) = 0;
   virtual void complete_hash(MD5 *hash) {
     assert(0);
   }
@@ -3091,6 +3091,7 @@ public:
 
 struct put_obj_aio_info {
   void *handle;
+  rgw_obj obj;
 };
 
 class RGWPutObjProcessor_Aio : public RGWPutObjProcessor
@@ -3107,17 +3108,17 @@ class RGWPutObjProcessor_Aio : public RGWPutObjProcessor
 protected:
   uint64_t obj_len;
 
-  list<rgw_obj> written_objs;
+  set<rgw_obj> written_objs;
 
   void add_written_obj(const rgw_obj& obj) {
-    written_objs.push_back(obj);
+    written_objs.insert(obj);
   }
 
   int drain_pending();
   int handle_obj_data(rgw_obj& obj, bufferlist& bl, off_t ofs, off_t abs_ofs, void **phandle, bool exclusive);
 
 public:
-  int throttle_data(void *handle, bool need_to_wait);
+  int throttle_data(void *handle, const rgw_obj& obj, bool need_to_wait);
 
   RGWPutObjProcessor_Aio(RGWObjectCtx& obj_ctx, RGWBucketInfo& bucket_info) : RGWPutObjProcessor(obj_ctx, bucket_info), max_chunks(RGW_MAX_PENDING_CHUNKS), obj_len(0) {}
   virtual ~RGWPutObjProcessor_Aio();
@@ -3152,7 +3153,7 @@ protected:
   RGWObjManifest manifest;
   RGWObjManifest::generator manifest_gen;
 
-  int write_data(bufferlist& bl, off_t ofs, void **phandle, bool exclusive);
+  int write_data(bufferlist& bl, off_t ofs, void **phandle, rgw_obj *pobj, bool exclusive);
   virtual int do_complete(string& etag, ceph::real_time *mtime, ceph::real_time set_mtime,
                           map<string, bufferlist>& attrs, ceph::real_time delete_at,
                           const char *if_match = NULL, const char *if_nomatch = NULL);
@@ -3185,7 +3186,7 @@ public:
   void set_extra_data_len(uint64_t len) {
     extra_data_len = len;
   }
-  virtual int handle_data(bufferlist& bl, off_t ofs, MD5 *hash, void **phandle, bool *again);
+  virtual int handle_data(bufferlist& bl, off_t ofs, MD5 *hash, void **phandle, rgw_obj *pobj, bool *again);
   virtual void complete_hash(MD5 *hash);
   bufferlist& get_extra_data() { return extra_data_bl; }
 
diff --git a/src/rgw/rgw_rest.cc b/src/rgw/rgw_rest.cc
index a165b65..538d5c7 100644
--- a/src/rgw/rgw_rest.cc
+++ b/src/rgw/rgw_rest.cc
@@ -1539,9 +1539,6 @@ RGWRESTMgr *RGWRESTMgr::get_resource_mgr(struct req_state *s, const string& uri,
 {
   *out_uri = uri;
 
-  if (resources_by_size.empty())
-    return this;
-
   multimap<size_t, string>::reverse_iterator iter;
 
   for (iter = resources_by_size.rbegin(); iter != resources_by_size.rend(); ++iter) {
diff --git a/src/test/Makefile-client.am b/src/test/Makefile-client.am
index 71be16e..153f58d 100644
--- a/src/test/Makefile-client.am
+++ b/src/test/Makefile-client.am
@@ -460,6 +460,7 @@ librbd_mirror_test_la_SOURCES = \
 	test/rbd_mirror/test_ClusterWatcher.cc \
 	test/rbd_mirror/test_PoolWatcher.cc \
 	test/rbd_mirror/test_ImageReplayer.cc \
+        test/rbd_mirror/test_ImageDeleter.cc \
 	test/rbd_mirror/test_ImageSync.cc \
 	test/rbd_mirror/test_fixture.cc
 
@@ -476,6 +477,7 @@ unittest_rbd_mirror_SOURCES = \
 	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_replayer/test_mock_CreateImageRequest.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 \
@@ -520,6 +522,13 @@ ceph_test_rbd_mirror_LDADD = \
 	$(CEPH_GLOBAL) $(RADOS_TEST_LDADD)
 bin_DEBUGPROGRAMS += ceph_test_rbd_mirror
 
+ceph_test_rbd_mirror_random_write_SOURCES = \
+	test/rbd_mirror/random_write.cc
+ceph_test_rbd_mirror_random_write_CXXFLAGS = $(UNITTEST_CXXFLAGS)
+ceph_test_rbd_mirror_random_write_LDADD = \
+	$(LIBRBD) $(LIBRADOS) $(CEPH_GLOBAL)
+bin_DEBUGPROGRAMS += ceph_test_rbd_mirror_random_write
+
 ceph_test_rbd_mirror_image_replay_SOURCES = \
 	test/rbd_mirror/image_replay.cc
 ceph_test_rbd_mirror_image_replay_LDADD = \
diff --git a/src/test/centos-6/ceph.spec.in b/src/test/centos-6/ceph.spec.in
index 34e4caa..3cf6307 100644
--- a/src/test/centos-6/ceph.spec.in
+++ b/src/test/centos-6/ceph.spec.in
@@ -17,6 +17,7 @@
 %bcond_with ocf
 %bcond_without cephfs_java
 %bcond_with tests
+%bcond_with xio
 %bcond_without tcmalloc
 %bcond_without libs_compat
 %bcond_with lowmem_builder
@@ -32,39 +33,13 @@
 %bcond_without lttng
 %endif
 
-%if (0%{?el5} || (0%{?rhel_version} >= 500 && 0%{?rhel_version} <= 600))
-%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")}
-%{!?python_sitearch: %global python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")}
-%endif
-
 %if %{with selinux}
 # get selinux policy version
 %{!?_selinux_policy_version: %global _selinux_policy_version %(sed -e 's,.*selinux-policy-\\([^/]*\\)/.*,\\1,' /usr/share/selinux/devel/policyhelp 2>/dev/null || echo 0.0.0)}
-
-%define relabel_files() \
-restorecon -R /usr/bin/ceph-mon > /dev/null 2>&1; \
-restorecon -R /usr/bin/ceph-osd > /dev/null 2>&1; \
-restorecon -R /usr/bin/ceph-mds > /dev/null 2>&1; \
-restorecon -R /usr/bin/radosgw > /dev/null 2>&1; \
-restorecon -R /etc/rc\.d/init\.d/ceph > /dev/null 2>&1; \
-restorecon -R /etc/rc\.d/init\.d/radosgw > /dev/null 2>&1; \
-restorecon -R /var/run/ceph > /dev/null 2>&1; \
-restorecon -R /var/lib/ceph > /dev/null 2>&1; \
-restorecon -R /var/log/ceph > /dev/null 2>&1; \
-restorecon -R /var/log/radosgw > /dev/null 2>&1;
 %endif
 
 %{!?_udevrulesdir: %global _udevrulesdir /lib/udev/rules.d}
-
-# Use systemd files on RHEL 7 and above and in SUSE/openSUSE.
-# Note: We don't install unit files for the services yet. For now,
-# the _with_systemd variable only implies that we'll install
-# /etc/tmpfiles.d/ceph.conf in order to set up the socket directory in
-# /var/run/ceph.
-%if 0%{?fedora} || 0%{?rhel} >= 7 || 0%{?suse_version}
-%global _with_systemd 1
 %{!?tmpfiles_create: %global tmpfiles_create systemd-tmpfiles --create}
-%endif
 
 # unify libexec for all targets
 %global _libexecdir %{_exec_prefix}/lib
@@ -84,9 +59,6 @@ Group:         System/Filesystems
 %endif
 URL:		http://ceph.com/
 Source0:	http://ceph.com/download/%{name}-%{version}.tar.bz2
-%if 0%{?fedora} || 0%{?rhel}
-Patch0:		init-ceph.in-fedora.patch
-%endif
 #################################################################################
 # dependencies that apply across all distro families
 #################################################################################
@@ -103,20 +75,20 @@ BuildRequires:	checkpolicy
 BuildRequires:	selinux-policy-devel
 BuildRequires:	/usr/share/selinux/devel/policyhelp
 %endif
-BuildRequires:	gcc-c++
 BuildRequires:	boost-devel
 BuildRequires:  cmake
 BuildRequires:	cryptsetup
 BuildRequires:	fuse-devel
+BuildRequires:	gcc-c++
 BuildRequires:	gdbm
 BuildRequires:	hdparm
 BuildRequires:	leveldb-devel > 1.2
 BuildRequires:	libaio-devel
-BuildRequires:	libcurl-devel
-BuildRequires:	libxml2-devel
 BuildRequires:	libblkid-devel >= 2.17
+BuildRequires:	libcurl-devel
 BuildRequires:	libudev-devel
 BuildRequires:	libtool
+BuildRequires:	libxml2-devel
 BuildRequires:	make
 BuildRequires:	parted
 BuildRequires:	perl
@@ -125,6 +97,7 @@ BuildRequires:	python
 BuildRequires:	python-devel
 BuildRequires:	python-nose
 BuildRequires:	python-requests
+BuildRequires:	python-sphinx
 BuildRequires:	python-virtualenv
 BuildRequires:	snappy-devel
 BuildRequires:	util-linux
@@ -138,12 +111,10 @@ BuildRequires:	yasm
 # distro-conditional dependencies
 #################################################################################
 %if 0%{?suse_version}
-%if 0%{?_with_systemd}
 BuildRequires:  pkgconfig(systemd)
 BuildRequires:	systemd-rpm-macros
 BuildRequires:	systemd
 %{?systemd_requires}
-%endif
 PreReq:		%fillup_prereq
 BuildRequires:	net-tools
 BuildRequires:	libbz2-devel
@@ -160,30 +131,18 @@ BuildRequires:  openldap2-devel
 BuildRequires:	python-Cython
 %endif
 %if 0%{?fedora} || 0%{?rhel} 
-%if 0%{?_with_systemd}
 Requires:	systemd
-%endif
+BuildRequires:  boost-random
 BuildRequires:	btrfs-progs
 BuildRequires:	nss-devel
 BuildRequires:	keyutils-libs-devel
 BuildRequires:	libatomic_ops-devel
-Requires(post):	chkconfig
-Requires(preun):	chkconfig
-Requires(preun):	initscripts
 BuildRequires:	gperftools-devel
 BuildRequires:  openldap-devel
 BuildRequires:  openssl-devel
 BuildRequires:  redhat-lsb-core
 BuildRequires:	Cython
 %endif
-# boost
-%if 0%{?fedora} || 0%{?rhel} 
-BuildRequires:  boost-random
-%endif
-# python-argparse for distros with Python 2.6 or lower
-%if (0%{?rhel} && 0%{?rhel} <= 6)
-BuildRequires:	python-argparse
-%endif
 # lttng and babeltrace for rbd-replay-prep
 %if %{with lttng}
 %if 0%{?fedora} || 0%{?rhel}
@@ -204,17 +163,14 @@ BuildRequires:	FastCGI-devel
 BuildRequires:	expat-devel
 BuildRequires:	fcgi-devel
 %endif
-# python-sphinx
-%if 0%{?rhel} > 0 && 0%{?rhel} < 7
-BuildRequires:	python-sphinx10
-%endif
-%if 0%{?fedora} || 0%{?suse_version} || 0%{?rhel} >= 7
-BuildRequires:	python-sphinx
-%endif
 #hardened-cc1
 %if 0%{?fedora} || 0%{?rhel}
 BuildRequires:  redhat-rpm-config
 %endif
+# Accelio IB/RDMA
+%if 0%{with xio}
+BuildRequires:  libxio-devel
+%endif
 
 %description
 Ceph is a massively scalable, open-source, distributed storage system that runs
@@ -249,10 +205,14 @@ Requires:      findutils
 Requires:      which
 %if 0%{?suse_version}
 Requires:      lsb-release
+Recommends:    ntp-daemon
 %endif
 %if 0%{?fedora} || 0%{?rhel}
 Requires:      redhat-lsb-core
 %endif
+%if 0%{with xio}
+Requires:      libxio
+%endif
 %description base
 Base is the package that includes all the files shared amongst ceph servers
 
@@ -266,15 +226,12 @@ Requires:	python-rados = %{epoch}:%{version}-%{release}
 Requires:	python-rbd = %{epoch}:%{version}-%{release}
 Requires:	python-cephfs = %{epoch}:%{version}-%{release}
 Requires:	python-requests
-%if 0%{?_with_systemd}
 %{?systemd_requires}
-%endif
 %if 0%{?suse_version}
 Requires(pre):	pwdutils
 %endif
-# python-argparse is only needed in distros with Python 2.6 or lower
-%if (0%{?rhel} && 0%{?rhel} <= 6)
-Requires:	python-argparse
+%if 0%{with xio}
+Requires:       libxio
 %endif
 %description -n ceph-common
 Common utilities to mount and interact with a ceph storage cluster.
@@ -572,13 +529,8 @@ Group:		System Environment/Libraries
 License:	LGPL-2.0
 Requires:	java
 Requires:	libcephfs_jni1 = %{epoch}:%{version}-%{release}
-%if 0%{?el6}
-Requires:	junit4
-BuildRequires:	junit4
-%else
 Requires:       junit
 BuildRequires:  junit
-%endif
 %description -n cephfs-java
 This package contains the Java libraries for the Ceph File System.
 
@@ -660,9 +612,6 @@ python-cephfs instead.
 #################################################################################
 %prep
 %setup -q
-%if 0%{?fedora} || 0%{?rhel}
-%patch0 -p1 -b .init
-%endif
 
 %build
 %if 0%{with cephfs_java}
@@ -682,14 +631,12 @@ export RPM_OPT_FLAGS=`echo $RPM_OPT_FLAGS | sed -e 's/i386/i486/'`
 %{configure}	CPPFLAGS="$java_inc" \
 		--prefix=/usr \
                 --libexecdir=%{_libexecdir} \
-		--localstatedir=/var \
-		--sysconfdir=/etc \
+		--localstatedir=%{_localstatedir} \
+		--sysconfdir=%{_sysconfdir} \
 %if 0%{?rhel} && ! 0%{?centos}
                 --enable-subman \
 %endif
-%if 0%{?_with_systemd}
 		--with-systemdsystemunitdir=%_unitdir \
-%endif
 		--docdir=%{_docdir}/ceph \
 		--with-man-pages \
 		--mandir="%_mandir" \
@@ -699,6 +646,9 @@ export RPM_OPT_FLAGS=`echo $RPM_OPT_FLAGS | sed -e 's/i386/i486/'`
 %if 0%{with cephfs_java}
 		--enable-cephfs-java \
 %endif
+%if 0%{with xio}
+		--enable-xio \
+%endif
 %if 0%{with selinux}
 		--with-selinux \
 %endif
@@ -725,66 +675,30 @@ make %{?_smp_mflags}
 %if 0%{with tests}
 %check
 # run in-tree unittests
-make %{?_smp_mflags} check-local
+make %{?_smp_mflags} check
 
 %endif
 
 
 
 %install
-make DESTDIR=$RPM_BUILD_ROOT install
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_example.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_fail_to_initialize.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_fail_to_register.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_hangs.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_missing_entry_point.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_missing_version.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_jerasure_generic.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_jerasure_neon.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_jerasure_sse3.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_jerasure_sse4.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_shec_generic.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_shec_neon.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_shec_sse3.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_shec_sse4.so
-find $RPM_BUILD_ROOT -type f -name "*.la" -exec rm -f {} ';'
-find $RPM_BUILD_ROOT -type f -name "*.a" -exec rm -f {} ';'
-install -D src/etc-rbdmap $RPM_BUILD_ROOT%{_sysconfdir}/ceph/rbdmap
+make DESTDIR=%{buildroot} install
+find %{buildroot} -type f -name "*.la" -exec rm -f {} ';'
+find %{buildroot} -type f -name "*.a" -exec rm -f {} ';'
+install -D src/etc-rbdmap %{buildroot}%{_sysconfdir}/ceph/rbdmap
 %if 0%{?fedora} || 0%{?rhel}
-install -m 0644 -D etc/sysconfig/ceph $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig/ceph
+install -m 0644 -D etc/sysconfig/ceph %{buildroot}%{_sysconfdir}/sysconfig/ceph
 %endif
 %if 0%{?suse_version}
-install -m 0644 -D etc/sysconfig/ceph $RPM_BUILD_ROOT%{_localstatedir}/adm/fillup-templates/sysconfig.%{name}
-%endif
-%if 0%{?_with_systemd}
-  install -m 0644 -D systemd/ceph.tmpfiles.d $RPM_BUILD_ROOT%{_tmpfilesdir}/ceph-common.conf
-  install -m 0644 -D systemd/rbdmap.service $RPM_BUILD_ROOT%{_unitdir}/rbdmap.service
-  install -m 0644 -D systemd/ceph-osd at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-osd at .service
-  install -m 0644 -D systemd/ceph-mon at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-mon at .service
-  install -m 0644 -D systemd/ceph-create-keys at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-create-keys at .service
-  install -m 0644 -D systemd/ceph-mds at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-mds at .service
-  install -m 0644 -D systemd/ceph-radosgw at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-radosgw at .service
-  install -m 0644 -D systemd/ceph-rbd-mirror at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-rbd-mirror at .service
-  install -m 0644 -D systemd/ceph.target $RPM_BUILD_ROOT%{_unitdir}/ceph.target
-  install -m 0644 -D systemd/ceph-osd.target $RPM_BUILD_ROOT%{_unitdir}/ceph-osd.target
-  install -m 0644 -D systemd/ceph-mon.target $RPM_BUILD_ROOT%{_unitdir}/ceph-mon.target
-  install -m 0644 -D systemd/ceph-mds.target $RPM_BUILD_ROOT%{_unitdir}/ceph-mds.target
-  install -m 0644 -D systemd/ceph-radosgw.target $RPM_BUILD_ROOT%{_unitdir}/ceph-radosgw.target
-  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
-  install -D src/init-radosgw $RPM_BUILD_ROOT%{_initrddir}/ceph-radosgw
-  ln -sf ../../etc/init.d/ceph %{buildroot}/%{_sbindir}/rcceph
-  ln -sf ../../etc/init.d/ceph-radosgw %{buildroot}/%{_sbindir}/rcceph-radosgw
-%endif
-mkdir -p $RPM_BUILD_ROOT%{_sbindir}
-install -m 0644 -D src/logrotate.conf $RPM_BUILD_ROOT%{_sysconfdir}/logrotate.d/ceph
-chmod 0644 $RPM_BUILD_ROOT%{_docdir}/ceph/sample.ceph.conf
-chmod 0644 $RPM_BUILD_ROOT%{_docdir}/ceph/sample.fetch_config
+install -m 0644 -D etc/sysconfig/ceph %{buildroot}%{_localstatedir}/adm/fillup-templates/sysconfig.%{name}
+%endif
+install -m 0644 -D systemd/ceph.tmpfiles.d %{buildroot}%{_tmpfilesdir}/ceph-common.conf
+install -m 0755 -D systemd/ceph %{buildroot}%{_sbindir}/rcceph
+install -m 0644 -D systemd/50-ceph.preset %{buildroot}%{_libexecdir}/systemd/system-preset/50-ceph.preset
+mkdir -p %{buildroot}%{_sbindir}
+install -m 0644 -D src/logrotate.conf %{buildroot}%{_sysconfdir}/logrotate.d/ceph
+chmod 0644 %{buildroot}%{_docdir}/ceph/sample.ceph.conf
+chmod 0644 %{buildroot}%{_docdir}/ceph/sample.fetch_config
 
 # firewall templates
 %if 0%{?suse_version}
@@ -793,38 +707,26 @@ install -m 0644 -D etc/sysconfig/SuSEfirewall2.d/services/ceph-osd-mds %{buildro
 %endif
 
 # udev rules
-install -m 0644 -D udev/50-rbd.rules $RPM_BUILD_ROOT%{_udevrulesdir}/50-rbd.rules
-install -m 0644 -D udev/60-ceph-partuuid-workaround.rules $RPM_BUILD_ROOT%{_udevrulesdir}/60-ceph-partuuid-workaround.rules
-
-%if (0%{?rhel} && 0%{?rhel} < 7)
-install -m 0644 -D udev/95-ceph-osd-alt.rules $RPM_BUILD_ROOT/lib/udev/rules.d/95-ceph-osd.rules
-%else
-install -m 0644 -D udev/95-ceph-osd.rules $RPM_BUILD_ROOT/lib/udev/rules.d/95-ceph-osd.rules
-%endif
-
-%if 0%{?rhel} >= 7 || 0%{?fedora} || 0%{?suse_version}
-mv $RPM_BUILD_ROOT/lib/udev/rules.d/95-ceph-osd.rules $RPM_BUILD_ROOT/usr/lib/udev/rules.d/95-ceph-osd.rules
-mv $RPM_BUILD_ROOT/sbin/mount.ceph $RPM_BUILD_ROOT/usr/sbin/mount.ceph
-mv $RPM_BUILD_ROOT/sbin/mount.fuse.ceph $RPM_BUILD_ROOT/usr/sbin/mount.fuse.ceph
-%endif
+install -m 0644 -D udev/50-rbd.rules %{buildroot}%{_udevrulesdir}/50-rbd.rules
+install -m 0644 -D udev/95-ceph-osd.rules %{buildroot}%{_udevrulesdir}/95-ceph-osd.rules
+mv %{buildroot}/sbin/mount.ceph %{buildroot}/usr/sbin/mount.ceph
+mv %{buildroot}/sbin/mount.fuse.ceph %{buildroot}/usr/sbin/mount.fuse.ceph
 
 #set up placeholder directories
-mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/ceph
-%if ! 0%{?_with_systemd}
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/run/ceph
-%endif
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/log/ceph
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/tmp
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/mon
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/osd
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/mds
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/radosgw
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/bootstrap-osd
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/bootstrap-mds
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/bootstrap-rgw
+mkdir -p %{buildroot}%{_sysconfdir}/ceph
+mkdir -p %{buildroot}%{_localstatedir}/run/ceph
+mkdir -p %{buildroot}%{_localstatedir}/log/ceph
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/tmp
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/mon
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/osd
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/mds
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/radosgw
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/bootstrap-osd
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/bootstrap-mds
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/bootstrap-rgw
 
 %clean
-rm -rf $RPM_BUILD_ROOT
+rm -rf %{buildroot}
 
 #################################################################################
 # files and systemd scriptlets
@@ -844,19 +746,11 @@ rm -rf $RPM_BUILD_ROOT
 %{_bindir}/ceph-detect-init
 %{_bindir}/ceph-client-debug
 %{_bindir}/cephfs
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-create-keys at .service
 %{_libexecdir}/systemd/system-preset/50-ceph.preset
-%else
-%{_initrddir}/ceph
-%endif
 %{_sbindir}/ceph-create-keys
 %{_sbindir}/rcceph
-%if 0%{?rhel} >= 7 || 0%{?fedora} || 0%{?suse_version}
 %{_sbindir}/mount.ceph
-%else
-/sbin/mount.ceph
-%endif
 %dir %{_libexecdir}/ceph
 %{_libexecdir}/ceph/ceph_common.sh
 %dir %{_libdir}/rados-classes
@@ -897,9 +791,6 @@ rm -rf $RPM_BUILD_ROOT
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/bootstrap-osd
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/bootstrap-mds
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/bootstrap-rgw
-%if ! 0%{?_with_systemd}
-%attr(770,ceph,ceph) %dir %{_localstatedir}/run/ceph
-%endif
 
 %post base
 /sbin/ldconfig
@@ -955,9 +846,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 %endif
 %{_bindir}/ceph-post-file
 %{_bindir}/ceph-brag
-%if 0%{?_with_systemd}
 %{_tmpfilesdir}/ceph-common.conf
-%endif
 %{_mandir}/man8/ceph-authtool.8*
 %{_mandir}/man8/ceph-conf.8*
 %{_mandir}/man8/ceph-dencoder.8*
@@ -979,11 +868,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 %config %{_sysconfdir}/bash_completion.d/rados
 %config %{_sysconfdir}/bash_completion.d/rbd
 %config(noreplace) %{_sysconfdir}/ceph/rbdmap
-%if 0%{?_with_systemd}
 %{_unitdir}/rbdmap.service
-%else
-%{_initrddir}/rbdmap
-%endif
 %{python_sitelib}/ceph_argparse.py*
 %{python_sitelib}/ceph_daemon.py*
 %{_udevrulesdir}/50-rbd.rules
@@ -994,8 +879,8 @@ DISABLE_RESTART_ON_UPDATE="yes"
 CEPH_GROUP_ID=167
 CEPH_USER_ID=167
 %if 0%{?rhel} || 0%{?fedora}
-%{_sbindir}/groupadd ceph -g $CEPH_GROUP_ID -o -r 2>/dev/null || :
-%{_sbindir}/useradd ceph -u $CEPH_USER_ID -o -r -g ceph -s /sbin/nologin -c "Ceph daemons" -d %{_localstatedir}/lib/ceph 2>/dev/null || :
+/usr/sbin/groupadd ceph -g $CEPH_GROUP_ID -o -r 2>/dev/null || :
+/usr/sbin/useradd ceph -u $CEPH_USER_ID -o -r -g ceph -s /sbin/nologin -c "Ceph daemons" -d %{_localstatedir}/lib/ceph 2>/dev/null || :
 %endif
 %if 0%{?suse_version}
 if ! getent group ceph >/dev/null ; then
@@ -1006,33 +891,32 @@ fi
 if ! getent passwd ceph >/dev/null ; then
     CEPH_USER_ID_OPTION=""
     getent passwd $CEPH_USER_ID >/dev/null || CEPH_USER_ID_OPTION="-u $CEPH_USER_ID"
-    useradd ceph $CEPH_USER_ID_OPTION -r -g ceph -s /sbin/nologin -c "Ceph daemons" -d %{_localstatedir}/lib/ceph 2>/dev/null || :
+    useradd ceph $CEPH_USER_ID_OPTION -r -g ceph -s /sbin/nologin 2>/dev/null || :
 fi
+usermod -c "Ceph storage service" \
+        -d %{_localstatedir}/lib/ceph \   
+        -g ceph \
+        -s /sbin/nologin \
+        ceph
 %endif
 exit 0
 
 %post common
-%if 0%{?_with_systemd}
 %tmpfiles_create %{_tmpfilesdir}/ceph-common.conf
-%endif
 
 %postun common
 # Package removal cleanup
 if [ "$1" -eq "0" ] ; then
-    rm -rf /var/log/ceph
-    rm -rf /etc/ceph
+    rm -rf %{_localstatedir}/log/ceph
+    rm -rf %{_sysconfdir}/ceph
 fi
 
 #################################################################################
 %files mds
 %{_bindir}/ceph-mds
 %{_mandir}/man8/ceph-mds.8*
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-mds at .service
 %{_unitdir}/ceph-mds.target
-%else
-%{_initrddir}/ceph
-%endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/mds
 
 %post mds
@@ -1066,7 +950,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1082,12 +966,8 @@ fi
 %{_mandir}/man8/ceph-mon.8*
 %{_mandir}/man8/ceph-rest-api.8*
 %{python_sitelib}/ceph_rest_api.py*
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-mon at .service
 %{_unitdir}/ceph-mon.target
-%else
-%{_initrddir}/ceph
-%endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/mon
 
 %post mon
@@ -1121,7 +1001,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1135,11 +1015,7 @@ fi
 %defattr(-,root,root,-)
 %{_bindir}/ceph-fuse
 %{_mandir}/man8/ceph-fuse.8*
-%if 0%{?rhel} >= 7 || 0%{?fedora} || 0%{?suse_version}
 %{_sbindir}/mount.fuse.ceph
-%else
-/sbin/mount.fuse.ceph
-%endif
 
 #################################################################################
 %files -n rbd-fuse
@@ -1152,10 +1028,8 @@ fi
 %defattr(-,root,root,-)
 %{_bindir}/rbd-mirror
 %{_mandir}/man8/rbd-mirror.8*
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-rbd-mirror at .service
 %{_unitdir}/ceph-rbd-mirror.target
-%endif
 
 %post -n rbd-mirror
 %if 0%{?suse_version}
@@ -1188,7 +1062,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1214,13 +1088,8 @@ fi
 %{_mandir}/man8/radosgw-admin.8*
 %config %{_sysconfdir}/bash_completion.d/radosgw-admin
 %dir %{_localstatedir}/lib/ceph/radosgw
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-radosgw at .service
 %{_unitdir}/ceph-radosgw.target
-%else
-%{_initrddir}/ceph-radosgw
-%{_sbindir}/rcceph-radosgw
-%endif
 
 %post radosgw
 %if 0%{?suse_version}
@@ -1253,7 +1122,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1271,21 +1140,16 @@ fi
 %{_sbindir}/ceph-disk
 %{_sbindir}/ceph-disk-udev
 %{_libexecdir}/ceph/ceph-osd-prestart.sh
-%{_udevrulesdir}/60-ceph-partuuid-workaround.rules
 %{_udevrulesdir}/95-ceph-osd.rules
 %{_mandir}/man8/ceph-clsinfo.8*
 %{_mandir}/man8/ceph-disk.8*
 %{_mandir}/man8/ceph-osd.8*
 %if 0%{?rhel} && ! 0%{?centos}
-/etc/cron.hourly/subman
+%{_sysconfdir}/cron.hourly/subman
 %endif
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-osd at .service
 %{_unitdir}/ceph-osd.target
 %{_unitdir}/ceph-disk at .service
-%else
-%{_initrddir}/ceph
-%endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/osd
 
 %post osd
@@ -1319,7 +1183,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1337,18 +1201,10 @@ fi
 %dir %{_prefix}/lib/ocf
 %dir %{_prefix}/lib/ocf/resource.d
 %dir %{_prefix}/lib/ocf/resource.d/ceph
-%if 0%{_with_systemd}
 %exclude %{_prefix}/lib/ocf/resource.d/ceph/ceph
 %exclude %{_prefix}/lib/ocf/resource.d/ceph/mds
 %exclude %{_prefix}/lib/ocf/resource.d/ceph/mon
 %exclude %{_prefix}/lib/ocf/resource.d/ceph/osd
-%endif
-%if ! 0%{_with_systemd}
-%{_prefix}/lib/ocf/resource.d/ceph/ceph
-%{_prefix}/lib/ocf/resource.d/ceph/mds
-%{_prefix}/lib/ocf/resource.d/ceph/mon
-%{_prefix}/lib/ocf/resource.d/ceph/osd
-%endif
 %{_prefix}/lib/ocf/resource.d/ceph/rbd
 
 %endif
@@ -1560,95 +1416,79 @@ ln -sf %{_libdir}/librbd.so.1 /usr/lib64/qemu/librbd.so.1
 %{_mandir}/man8/ceph_selinux.8*
 
 %post selinux
+# backup file_contexts before update
+. /etc/selinux/config
+FILE_CONTEXT=/etc/selinux/${SELINUXTYPE}/contexts/files/file_contexts
+cp ${FILE_CONTEXT} ${FILE_CONTEXT}.pre
+
 # Install the policy
-OLD_POLVER=$(%{_sbindir}/semodule -l | grep -P '^ceph[\t ]' | awk '{print $2}')
-%{_sbindir}/semodule -n -i %{_datadir}/selinux/packages/ceph.pp
-NEW_POLVER=$(%{_sbindir}/semodule -l | grep -P '^ceph[\t ]' | awk '{print $2}')
+/usr/sbin/semodule -i %{_datadir}/selinux/packages/ceph.pp
 
 # Load the policy if SELinux is enabled
-if %{_sbindir}/selinuxenabled; then
-    %{_sbindir}/load_policy
-else
+if ! /usr/sbin/selinuxenabled; then
     # Do not relabel if selinux is not enabled
     exit 0
 fi
 
-if test "$OLD_POLVER" = "$NEW_POLVER"; then
-   # Do not relabel if policy version did not change
+if diff ${FILE_CONTEXT} ${FILE_CONTEXT}.pre > /dev/null 2>&1; then
+   # Do not relabel if file contexts did not change
    exit 0
 fi
 
 # Check whether the daemons are running
-%if 0%{?_with_systemd}
-    /usr/bin/systemctl status ceph.target > /dev/null 2>&1
-%else
-    /sbin/service ceph status >/dev/null 2>&1
-%endif
+/usr/bin/systemctl status ceph.target > /dev/null 2>&1
 STATUS=$?
 
 # Stop the daemons if they were running
 if test $STATUS -eq 0; then
-%if 0%{?_with_systemd}
     /usr/bin/systemctl stop ceph.target > /dev/null 2>&1
-%else
-    /sbin/service ceph stop >/dev/null 2>&1
-%endif
 fi
 
 # Now, relabel the files
-%relabel_files
+/usr/sbin/fixfiles -C ${FILE_CONTEXT}.pre restore 2> /dev/null
+rm -f ${FILE_CONTEXT}.pre
+# The fixfiles command won't fix label for /var/run/ceph
+/usr/sbin/restorecon -R /var/run/ceph > /dev/null 2>&1
 
 # Start the daemons iff they were running before
 if test $STATUS -eq 0; then
-%if 0%{?_with_systemd}
     /usr/bin/systemctl start ceph.target > /dev/null 2>&1 || :
-%else
-    /sbin/service ceph start >/dev/null 2>&1 || :
-%endif
 fi
-
 exit 0
 
 %postun selinux
 if [ $1 -eq 0 ]; then
+    # backup file_contexts before update
+    . /etc/selinux/config
+    FILE_CONTEXT=/etc/selinux/${SELINUXTYPE}/contexts/files/file_contexts
+    cp ${FILE_CONTEXT} ${FILE_CONTEXT}.pre
+
     # Remove the module
-    %{_sbindir}/semodule -n -r ceph
+    /usr/sbin/semodule -n -r ceph > /dev/null 2>&1
 
     # Reload the policy if SELinux is enabled
-    if %{_sbindir}/selinuxenabled ; then
-        %{_sbindir}/load_policy
-    else
+    if ! /usr/sbin/selinuxenabled ; then
         # Do not relabel if SELinux is not enabled
         exit 0
     fi
 
     # Check whether the daemons are running
-    %if 0%{?_with_systemd}
-        /usr/bin/systemctl status ceph.target > /dev/null 2>&1
-    %else
-        /sbin/service ceph status >/dev/null 2>&1
-    %endif
+    /usr/bin/systemctl status ceph.target > /dev/null 2>&1
     STATUS=$?
 
     # Stop the daemons if they were running
     if test $STATUS -eq 0; then
-    %if 0%{?_with_systemd}
         /usr/bin/systemctl stop ceph.target > /dev/null 2>&1
-    %else
-        /sbin/service ceph stop >/dev/null 2>&1
-    %endif
     fi
 
-    # Now, relabel the files
-    %relabel_files
+    /usr/sbin/fixfiles -C ${FILE_CONTEXT}.pre restore 2> /dev/null
+    rm -f ${FILE_CONTEXT}.pre
+    # The fixfiles command won't fix label for /var/run/ceph
+    /usr/sbin/restorecon -R /var/run/ceph > /dev/null 2>&1
 
     # Start the daemons if they were running before
     if test $STATUS -eq 0; then
-    %if 0%{?_with_systemd}
 	/usr/bin/systemctl start ceph.target > /dev/null 2>&1 || :
-    %else
-	/sbin/service ceph start >/dev/null 2>&1 || :
-    %endif
     fi
 fi
 exit 0
diff --git a/src/test/centos-6/install-deps.sh b/src/test/centos-6/install-deps.sh
index 21e71ee..03ca760 100755
--- a/src/test/centos-6/install-deps.sh
+++ b/src/test/centos-6/install-deps.sh
@@ -28,7 +28,7 @@ if type apt-get > /dev/null 2>&1 ; then
 fi
 
 if type zypper > /dev/null 2>&1 ; then
-    $SUDO zypper --gpg-auto-import-keys --non-interactive install lsb-release
+    $SUDO zypper --gpg-auto-import-keys --non-interactive install lsb-release systemd-rpm-macros
 fi
 
 case $(lsb_release -si) in
diff --git a/src/test/centos-7/ceph.spec.in b/src/test/centos-7/ceph.spec.in
index 34e4caa..3cf6307 100644
--- a/src/test/centos-7/ceph.spec.in
+++ b/src/test/centos-7/ceph.spec.in
@@ -17,6 +17,7 @@
 %bcond_with ocf
 %bcond_without cephfs_java
 %bcond_with tests
+%bcond_with xio
 %bcond_without tcmalloc
 %bcond_without libs_compat
 %bcond_with lowmem_builder
@@ -32,39 +33,13 @@
 %bcond_without lttng
 %endif
 
-%if (0%{?el5} || (0%{?rhel_version} >= 500 && 0%{?rhel_version} <= 600))
-%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")}
-%{!?python_sitearch: %global python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")}
-%endif
-
 %if %{with selinux}
 # get selinux policy version
 %{!?_selinux_policy_version: %global _selinux_policy_version %(sed -e 's,.*selinux-policy-\\([^/]*\\)/.*,\\1,' /usr/share/selinux/devel/policyhelp 2>/dev/null || echo 0.0.0)}
-
-%define relabel_files() \
-restorecon -R /usr/bin/ceph-mon > /dev/null 2>&1; \
-restorecon -R /usr/bin/ceph-osd > /dev/null 2>&1; \
-restorecon -R /usr/bin/ceph-mds > /dev/null 2>&1; \
-restorecon -R /usr/bin/radosgw > /dev/null 2>&1; \
-restorecon -R /etc/rc\.d/init\.d/ceph > /dev/null 2>&1; \
-restorecon -R /etc/rc\.d/init\.d/radosgw > /dev/null 2>&1; \
-restorecon -R /var/run/ceph > /dev/null 2>&1; \
-restorecon -R /var/lib/ceph > /dev/null 2>&1; \
-restorecon -R /var/log/ceph > /dev/null 2>&1; \
-restorecon -R /var/log/radosgw > /dev/null 2>&1;
 %endif
 
 %{!?_udevrulesdir: %global _udevrulesdir /lib/udev/rules.d}
-
-# Use systemd files on RHEL 7 and above and in SUSE/openSUSE.
-# Note: We don't install unit files for the services yet. For now,
-# the _with_systemd variable only implies that we'll install
-# /etc/tmpfiles.d/ceph.conf in order to set up the socket directory in
-# /var/run/ceph.
-%if 0%{?fedora} || 0%{?rhel} >= 7 || 0%{?suse_version}
-%global _with_systemd 1
 %{!?tmpfiles_create: %global tmpfiles_create systemd-tmpfiles --create}
-%endif
 
 # unify libexec for all targets
 %global _libexecdir %{_exec_prefix}/lib
@@ -84,9 +59,6 @@ Group:         System/Filesystems
 %endif
 URL:		http://ceph.com/
 Source0:	http://ceph.com/download/%{name}-%{version}.tar.bz2
-%if 0%{?fedora} || 0%{?rhel}
-Patch0:		init-ceph.in-fedora.patch
-%endif
 #################################################################################
 # dependencies that apply across all distro families
 #################################################################################
@@ -103,20 +75,20 @@ BuildRequires:	checkpolicy
 BuildRequires:	selinux-policy-devel
 BuildRequires:	/usr/share/selinux/devel/policyhelp
 %endif
-BuildRequires:	gcc-c++
 BuildRequires:	boost-devel
 BuildRequires:  cmake
 BuildRequires:	cryptsetup
 BuildRequires:	fuse-devel
+BuildRequires:	gcc-c++
 BuildRequires:	gdbm
 BuildRequires:	hdparm
 BuildRequires:	leveldb-devel > 1.2
 BuildRequires:	libaio-devel
-BuildRequires:	libcurl-devel
-BuildRequires:	libxml2-devel
 BuildRequires:	libblkid-devel >= 2.17
+BuildRequires:	libcurl-devel
 BuildRequires:	libudev-devel
 BuildRequires:	libtool
+BuildRequires:	libxml2-devel
 BuildRequires:	make
 BuildRequires:	parted
 BuildRequires:	perl
@@ -125,6 +97,7 @@ BuildRequires:	python
 BuildRequires:	python-devel
 BuildRequires:	python-nose
 BuildRequires:	python-requests
+BuildRequires:	python-sphinx
 BuildRequires:	python-virtualenv
 BuildRequires:	snappy-devel
 BuildRequires:	util-linux
@@ -138,12 +111,10 @@ BuildRequires:	yasm
 # distro-conditional dependencies
 #################################################################################
 %if 0%{?suse_version}
-%if 0%{?_with_systemd}
 BuildRequires:  pkgconfig(systemd)
 BuildRequires:	systemd-rpm-macros
 BuildRequires:	systemd
 %{?systemd_requires}
-%endif
 PreReq:		%fillup_prereq
 BuildRequires:	net-tools
 BuildRequires:	libbz2-devel
@@ -160,30 +131,18 @@ BuildRequires:  openldap2-devel
 BuildRequires:	python-Cython
 %endif
 %if 0%{?fedora} || 0%{?rhel} 
-%if 0%{?_with_systemd}
 Requires:	systemd
-%endif
+BuildRequires:  boost-random
 BuildRequires:	btrfs-progs
 BuildRequires:	nss-devel
 BuildRequires:	keyutils-libs-devel
 BuildRequires:	libatomic_ops-devel
-Requires(post):	chkconfig
-Requires(preun):	chkconfig
-Requires(preun):	initscripts
 BuildRequires:	gperftools-devel
 BuildRequires:  openldap-devel
 BuildRequires:  openssl-devel
 BuildRequires:  redhat-lsb-core
 BuildRequires:	Cython
 %endif
-# boost
-%if 0%{?fedora} || 0%{?rhel} 
-BuildRequires:  boost-random
-%endif
-# python-argparse for distros with Python 2.6 or lower
-%if (0%{?rhel} && 0%{?rhel} <= 6)
-BuildRequires:	python-argparse
-%endif
 # lttng and babeltrace for rbd-replay-prep
 %if %{with lttng}
 %if 0%{?fedora} || 0%{?rhel}
@@ -204,17 +163,14 @@ BuildRequires:	FastCGI-devel
 BuildRequires:	expat-devel
 BuildRequires:	fcgi-devel
 %endif
-# python-sphinx
-%if 0%{?rhel} > 0 && 0%{?rhel} < 7
-BuildRequires:	python-sphinx10
-%endif
-%if 0%{?fedora} || 0%{?suse_version} || 0%{?rhel} >= 7
-BuildRequires:	python-sphinx
-%endif
 #hardened-cc1
 %if 0%{?fedora} || 0%{?rhel}
 BuildRequires:  redhat-rpm-config
 %endif
+# Accelio IB/RDMA
+%if 0%{with xio}
+BuildRequires:  libxio-devel
+%endif
 
 %description
 Ceph is a massively scalable, open-source, distributed storage system that runs
@@ -249,10 +205,14 @@ Requires:      findutils
 Requires:      which
 %if 0%{?suse_version}
 Requires:      lsb-release
+Recommends:    ntp-daemon
 %endif
 %if 0%{?fedora} || 0%{?rhel}
 Requires:      redhat-lsb-core
 %endif
+%if 0%{with xio}
+Requires:      libxio
+%endif
 %description base
 Base is the package that includes all the files shared amongst ceph servers
 
@@ -266,15 +226,12 @@ Requires:	python-rados = %{epoch}:%{version}-%{release}
 Requires:	python-rbd = %{epoch}:%{version}-%{release}
 Requires:	python-cephfs = %{epoch}:%{version}-%{release}
 Requires:	python-requests
-%if 0%{?_with_systemd}
 %{?systemd_requires}
-%endif
 %if 0%{?suse_version}
 Requires(pre):	pwdutils
 %endif
-# python-argparse is only needed in distros with Python 2.6 or lower
-%if (0%{?rhel} && 0%{?rhel} <= 6)
-Requires:	python-argparse
+%if 0%{with xio}
+Requires:       libxio
 %endif
 %description -n ceph-common
 Common utilities to mount and interact with a ceph storage cluster.
@@ -572,13 +529,8 @@ Group:		System Environment/Libraries
 License:	LGPL-2.0
 Requires:	java
 Requires:	libcephfs_jni1 = %{epoch}:%{version}-%{release}
-%if 0%{?el6}
-Requires:	junit4
-BuildRequires:	junit4
-%else
 Requires:       junit
 BuildRequires:  junit
-%endif
 %description -n cephfs-java
 This package contains the Java libraries for the Ceph File System.
 
@@ -660,9 +612,6 @@ python-cephfs instead.
 #################################################################################
 %prep
 %setup -q
-%if 0%{?fedora} || 0%{?rhel}
-%patch0 -p1 -b .init
-%endif
 
 %build
 %if 0%{with cephfs_java}
@@ -682,14 +631,12 @@ export RPM_OPT_FLAGS=`echo $RPM_OPT_FLAGS | sed -e 's/i386/i486/'`
 %{configure}	CPPFLAGS="$java_inc" \
 		--prefix=/usr \
                 --libexecdir=%{_libexecdir} \
-		--localstatedir=/var \
-		--sysconfdir=/etc \
+		--localstatedir=%{_localstatedir} \
+		--sysconfdir=%{_sysconfdir} \
 %if 0%{?rhel} && ! 0%{?centos}
                 --enable-subman \
 %endif
-%if 0%{?_with_systemd}
 		--with-systemdsystemunitdir=%_unitdir \
-%endif
 		--docdir=%{_docdir}/ceph \
 		--with-man-pages \
 		--mandir="%_mandir" \
@@ -699,6 +646,9 @@ export RPM_OPT_FLAGS=`echo $RPM_OPT_FLAGS | sed -e 's/i386/i486/'`
 %if 0%{with cephfs_java}
 		--enable-cephfs-java \
 %endif
+%if 0%{with xio}
+		--enable-xio \
+%endif
 %if 0%{with selinux}
 		--with-selinux \
 %endif
@@ -725,66 +675,30 @@ make %{?_smp_mflags}
 %if 0%{with tests}
 %check
 # run in-tree unittests
-make %{?_smp_mflags} check-local
+make %{?_smp_mflags} check
 
 %endif
 
 
 
 %install
-make DESTDIR=$RPM_BUILD_ROOT install
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_example.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_fail_to_initialize.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_fail_to_register.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_hangs.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_missing_entry_point.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_missing_version.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_jerasure_generic.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_jerasure_neon.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_jerasure_sse3.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_jerasure_sse4.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_shec_generic.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_shec_neon.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_shec_sse3.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_shec_sse4.so
-find $RPM_BUILD_ROOT -type f -name "*.la" -exec rm -f {} ';'
-find $RPM_BUILD_ROOT -type f -name "*.a" -exec rm -f {} ';'
-install -D src/etc-rbdmap $RPM_BUILD_ROOT%{_sysconfdir}/ceph/rbdmap
+make DESTDIR=%{buildroot} install
+find %{buildroot} -type f -name "*.la" -exec rm -f {} ';'
+find %{buildroot} -type f -name "*.a" -exec rm -f {} ';'
+install -D src/etc-rbdmap %{buildroot}%{_sysconfdir}/ceph/rbdmap
 %if 0%{?fedora} || 0%{?rhel}
-install -m 0644 -D etc/sysconfig/ceph $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig/ceph
+install -m 0644 -D etc/sysconfig/ceph %{buildroot}%{_sysconfdir}/sysconfig/ceph
 %endif
 %if 0%{?suse_version}
-install -m 0644 -D etc/sysconfig/ceph $RPM_BUILD_ROOT%{_localstatedir}/adm/fillup-templates/sysconfig.%{name}
-%endif
-%if 0%{?_with_systemd}
-  install -m 0644 -D systemd/ceph.tmpfiles.d $RPM_BUILD_ROOT%{_tmpfilesdir}/ceph-common.conf
-  install -m 0644 -D systemd/rbdmap.service $RPM_BUILD_ROOT%{_unitdir}/rbdmap.service
-  install -m 0644 -D systemd/ceph-osd at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-osd at .service
-  install -m 0644 -D systemd/ceph-mon at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-mon at .service
-  install -m 0644 -D systemd/ceph-create-keys at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-create-keys at .service
-  install -m 0644 -D systemd/ceph-mds at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-mds at .service
-  install -m 0644 -D systemd/ceph-radosgw at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-radosgw at .service
-  install -m 0644 -D systemd/ceph-rbd-mirror at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-rbd-mirror at .service
-  install -m 0644 -D systemd/ceph.target $RPM_BUILD_ROOT%{_unitdir}/ceph.target
-  install -m 0644 -D systemd/ceph-osd.target $RPM_BUILD_ROOT%{_unitdir}/ceph-osd.target
-  install -m 0644 -D systemd/ceph-mon.target $RPM_BUILD_ROOT%{_unitdir}/ceph-mon.target
-  install -m 0644 -D systemd/ceph-mds.target $RPM_BUILD_ROOT%{_unitdir}/ceph-mds.target
-  install -m 0644 -D systemd/ceph-radosgw.target $RPM_BUILD_ROOT%{_unitdir}/ceph-radosgw.target
-  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
-  install -D src/init-radosgw $RPM_BUILD_ROOT%{_initrddir}/ceph-radosgw
-  ln -sf ../../etc/init.d/ceph %{buildroot}/%{_sbindir}/rcceph
-  ln -sf ../../etc/init.d/ceph-radosgw %{buildroot}/%{_sbindir}/rcceph-radosgw
-%endif
-mkdir -p $RPM_BUILD_ROOT%{_sbindir}
-install -m 0644 -D src/logrotate.conf $RPM_BUILD_ROOT%{_sysconfdir}/logrotate.d/ceph
-chmod 0644 $RPM_BUILD_ROOT%{_docdir}/ceph/sample.ceph.conf
-chmod 0644 $RPM_BUILD_ROOT%{_docdir}/ceph/sample.fetch_config
+install -m 0644 -D etc/sysconfig/ceph %{buildroot}%{_localstatedir}/adm/fillup-templates/sysconfig.%{name}
+%endif
+install -m 0644 -D systemd/ceph.tmpfiles.d %{buildroot}%{_tmpfilesdir}/ceph-common.conf
+install -m 0755 -D systemd/ceph %{buildroot}%{_sbindir}/rcceph
+install -m 0644 -D systemd/50-ceph.preset %{buildroot}%{_libexecdir}/systemd/system-preset/50-ceph.preset
+mkdir -p %{buildroot}%{_sbindir}
+install -m 0644 -D src/logrotate.conf %{buildroot}%{_sysconfdir}/logrotate.d/ceph
+chmod 0644 %{buildroot}%{_docdir}/ceph/sample.ceph.conf
+chmod 0644 %{buildroot}%{_docdir}/ceph/sample.fetch_config
 
 # firewall templates
 %if 0%{?suse_version}
@@ -793,38 +707,26 @@ install -m 0644 -D etc/sysconfig/SuSEfirewall2.d/services/ceph-osd-mds %{buildro
 %endif
 
 # udev rules
-install -m 0644 -D udev/50-rbd.rules $RPM_BUILD_ROOT%{_udevrulesdir}/50-rbd.rules
-install -m 0644 -D udev/60-ceph-partuuid-workaround.rules $RPM_BUILD_ROOT%{_udevrulesdir}/60-ceph-partuuid-workaround.rules
-
-%if (0%{?rhel} && 0%{?rhel} < 7)
-install -m 0644 -D udev/95-ceph-osd-alt.rules $RPM_BUILD_ROOT/lib/udev/rules.d/95-ceph-osd.rules
-%else
-install -m 0644 -D udev/95-ceph-osd.rules $RPM_BUILD_ROOT/lib/udev/rules.d/95-ceph-osd.rules
-%endif
-
-%if 0%{?rhel} >= 7 || 0%{?fedora} || 0%{?suse_version}
-mv $RPM_BUILD_ROOT/lib/udev/rules.d/95-ceph-osd.rules $RPM_BUILD_ROOT/usr/lib/udev/rules.d/95-ceph-osd.rules
-mv $RPM_BUILD_ROOT/sbin/mount.ceph $RPM_BUILD_ROOT/usr/sbin/mount.ceph
-mv $RPM_BUILD_ROOT/sbin/mount.fuse.ceph $RPM_BUILD_ROOT/usr/sbin/mount.fuse.ceph
-%endif
+install -m 0644 -D udev/50-rbd.rules %{buildroot}%{_udevrulesdir}/50-rbd.rules
+install -m 0644 -D udev/95-ceph-osd.rules %{buildroot}%{_udevrulesdir}/95-ceph-osd.rules
+mv %{buildroot}/sbin/mount.ceph %{buildroot}/usr/sbin/mount.ceph
+mv %{buildroot}/sbin/mount.fuse.ceph %{buildroot}/usr/sbin/mount.fuse.ceph
 
 #set up placeholder directories
-mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/ceph
-%if ! 0%{?_with_systemd}
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/run/ceph
-%endif
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/log/ceph
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/tmp
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/mon
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/osd
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/mds
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/radosgw
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/bootstrap-osd
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/bootstrap-mds
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/bootstrap-rgw
+mkdir -p %{buildroot}%{_sysconfdir}/ceph
+mkdir -p %{buildroot}%{_localstatedir}/run/ceph
+mkdir -p %{buildroot}%{_localstatedir}/log/ceph
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/tmp
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/mon
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/osd
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/mds
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/radosgw
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/bootstrap-osd
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/bootstrap-mds
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/bootstrap-rgw
 
 %clean
-rm -rf $RPM_BUILD_ROOT
+rm -rf %{buildroot}
 
 #################################################################################
 # files and systemd scriptlets
@@ -844,19 +746,11 @@ rm -rf $RPM_BUILD_ROOT
 %{_bindir}/ceph-detect-init
 %{_bindir}/ceph-client-debug
 %{_bindir}/cephfs
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-create-keys at .service
 %{_libexecdir}/systemd/system-preset/50-ceph.preset
-%else
-%{_initrddir}/ceph
-%endif
 %{_sbindir}/ceph-create-keys
 %{_sbindir}/rcceph
-%if 0%{?rhel} >= 7 || 0%{?fedora} || 0%{?suse_version}
 %{_sbindir}/mount.ceph
-%else
-/sbin/mount.ceph
-%endif
 %dir %{_libexecdir}/ceph
 %{_libexecdir}/ceph/ceph_common.sh
 %dir %{_libdir}/rados-classes
@@ -897,9 +791,6 @@ rm -rf $RPM_BUILD_ROOT
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/bootstrap-osd
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/bootstrap-mds
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/bootstrap-rgw
-%if ! 0%{?_with_systemd}
-%attr(770,ceph,ceph) %dir %{_localstatedir}/run/ceph
-%endif
 
 %post base
 /sbin/ldconfig
@@ -955,9 +846,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 %endif
 %{_bindir}/ceph-post-file
 %{_bindir}/ceph-brag
-%if 0%{?_with_systemd}
 %{_tmpfilesdir}/ceph-common.conf
-%endif
 %{_mandir}/man8/ceph-authtool.8*
 %{_mandir}/man8/ceph-conf.8*
 %{_mandir}/man8/ceph-dencoder.8*
@@ -979,11 +868,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 %config %{_sysconfdir}/bash_completion.d/rados
 %config %{_sysconfdir}/bash_completion.d/rbd
 %config(noreplace) %{_sysconfdir}/ceph/rbdmap
-%if 0%{?_with_systemd}
 %{_unitdir}/rbdmap.service
-%else
-%{_initrddir}/rbdmap
-%endif
 %{python_sitelib}/ceph_argparse.py*
 %{python_sitelib}/ceph_daemon.py*
 %{_udevrulesdir}/50-rbd.rules
@@ -994,8 +879,8 @@ DISABLE_RESTART_ON_UPDATE="yes"
 CEPH_GROUP_ID=167
 CEPH_USER_ID=167
 %if 0%{?rhel} || 0%{?fedora}
-%{_sbindir}/groupadd ceph -g $CEPH_GROUP_ID -o -r 2>/dev/null || :
-%{_sbindir}/useradd ceph -u $CEPH_USER_ID -o -r -g ceph -s /sbin/nologin -c "Ceph daemons" -d %{_localstatedir}/lib/ceph 2>/dev/null || :
+/usr/sbin/groupadd ceph -g $CEPH_GROUP_ID -o -r 2>/dev/null || :
+/usr/sbin/useradd ceph -u $CEPH_USER_ID -o -r -g ceph -s /sbin/nologin -c "Ceph daemons" -d %{_localstatedir}/lib/ceph 2>/dev/null || :
 %endif
 %if 0%{?suse_version}
 if ! getent group ceph >/dev/null ; then
@@ -1006,33 +891,32 @@ fi
 if ! getent passwd ceph >/dev/null ; then
     CEPH_USER_ID_OPTION=""
     getent passwd $CEPH_USER_ID >/dev/null || CEPH_USER_ID_OPTION="-u $CEPH_USER_ID"
-    useradd ceph $CEPH_USER_ID_OPTION -r -g ceph -s /sbin/nologin -c "Ceph daemons" -d %{_localstatedir}/lib/ceph 2>/dev/null || :
+    useradd ceph $CEPH_USER_ID_OPTION -r -g ceph -s /sbin/nologin 2>/dev/null || :
 fi
+usermod -c "Ceph storage service" \
+        -d %{_localstatedir}/lib/ceph \   
+        -g ceph \
+        -s /sbin/nologin \
+        ceph
 %endif
 exit 0
 
 %post common
-%if 0%{?_with_systemd}
 %tmpfiles_create %{_tmpfilesdir}/ceph-common.conf
-%endif
 
 %postun common
 # Package removal cleanup
 if [ "$1" -eq "0" ] ; then
-    rm -rf /var/log/ceph
-    rm -rf /etc/ceph
+    rm -rf %{_localstatedir}/log/ceph
+    rm -rf %{_sysconfdir}/ceph
 fi
 
 #################################################################################
 %files mds
 %{_bindir}/ceph-mds
 %{_mandir}/man8/ceph-mds.8*
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-mds at .service
 %{_unitdir}/ceph-mds.target
-%else
-%{_initrddir}/ceph
-%endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/mds
 
 %post mds
@@ -1066,7 +950,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1082,12 +966,8 @@ fi
 %{_mandir}/man8/ceph-mon.8*
 %{_mandir}/man8/ceph-rest-api.8*
 %{python_sitelib}/ceph_rest_api.py*
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-mon at .service
 %{_unitdir}/ceph-mon.target
-%else
-%{_initrddir}/ceph
-%endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/mon
 
 %post mon
@@ -1121,7 +1001,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1135,11 +1015,7 @@ fi
 %defattr(-,root,root,-)
 %{_bindir}/ceph-fuse
 %{_mandir}/man8/ceph-fuse.8*
-%if 0%{?rhel} >= 7 || 0%{?fedora} || 0%{?suse_version}
 %{_sbindir}/mount.fuse.ceph
-%else
-/sbin/mount.fuse.ceph
-%endif
 
 #################################################################################
 %files -n rbd-fuse
@@ -1152,10 +1028,8 @@ fi
 %defattr(-,root,root,-)
 %{_bindir}/rbd-mirror
 %{_mandir}/man8/rbd-mirror.8*
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-rbd-mirror at .service
 %{_unitdir}/ceph-rbd-mirror.target
-%endif
 
 %post -n rbd-mirror
 %if 0%{?suse_version}
@@ -1188,7 +1062,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1214,13 +1088,8 @@ fi
 %{_mandir}/man8/radosgw-admin.8*
 %config %{_sysconfdir}/bash_completion.d/radosgw-admin
 %dir %{_localstatedir}/lib/ceph/radosgw
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-radosgw at .service
 %{_unitdir}/ceph-radosgw.target
-%else
-%{_initrddir}/ceph-radosgw
-%{_sbindir}/rcceph-radosgw
-%endif
 
 %post radosgw
 %if 0%{?suse_version}
@@ -1253,7 +1122,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1271,21 +1140,16 @@ fi
 %{_sbindir}/ceph-disk
 %{_sbindir}/ceph-disk-udev
 %{_libexecdir}/ceph/ceph-osd-prestart.sh
-%{_udevrulesdir}/60-ceph-partuuid-workaround.rules
 %{_udevrulesdir}/95-ceph-osd.rules
 %{_mandir}/man8/ceph-clsinfo.8*
 %{_mandir}/man8/ceph-disk.8*
 %{_mandir}/man8/ceph-osd.8*
 %if 0%{?rhel} && ! 0%{?centos}
-/etc/cron.hourly/subman
+%{_sysconfdir}/cron.hourly/subman
 %endif
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-osd at .service
 %{_unitdir}/ceph-osd.target
 %{_unitdir}/ceph-disk at .service
-%else
-%{_initrddir}/ceph
-%endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/osd
 
 %post osd
@@ -1319,7 +1183,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1337,18 +1201,10 @@ fi
 %dir %{_prefix}/lib/ocf
 %dir %{_prefix}/lib/ocf/resource.d
 %dir %{_prefix}/lib/ocf/resource.d/ceph
-%if 0%{_with_systemd}
 %exclude %{_prefix}/lib/ocf/resource.d/ceph/ceph
 %exclude %{_prefix}/lib/ocf/resource.d/ceph/mds
 %exclude %{_prefix}/lib/ocf/resource.d/ceph/mon
 %exclude %{_prefix}/lib/ocf/resource.d/ceph/osd
-%endif
-%if ! 0%{_with_systemd}
-%{_prefix}/lib/ocf/resource.d/ceph/ceph
-%{_prefix}/lib/ocf/resource.d/ceph/mds
-%{_prefix}/lib/ocf/resource.d/ceph/mon
-%{_prefix}/lib/ocf/resource.d/ceph/osd
-%endif
 %{_prefix}/lib/ocf/resource.d/ceph/rbd
 
 %endif
@@ -1560,95 +1416,79 @@ ln -sf %{_libdir}/librbd.so.1 /usr/lib64/qemu/librbd.so.1
 %{_mandir}/man8/ceph_selinux.8*
 
 %post selinux
+# backup file_contexts before update
+. /etc/selinux/config
+FILE_CONTEXT=/etc/selinux/${SELINUXTYPE}/contexts/files/file_contexts
+cp ${FILE_CONTEXT} ${FILE_CONTEXT}.pre
+
 # Install the policy
-OLD_POLVER=$(%{_sbindir}/semodule -l | grep -P '^ceph[\t ]' | awk '{print $2}')
-%{_sbindir}/semodule -n -i %{_datadir}/selinux/packages/ceph.pp
-NEW_POLVER=$(%{_sbindir}/semodule -l | grep -P '^ceph[\t ]' | awk '{print $2}')
+/usr/sbin/semodule -i %{_datadir}/selinux/packages/ceph.pp
 
 # Load the policy if SELinux is enabled
-if %{_sbindir}/selinuxenabled; then
-    %{_sbindir}/load_policy
-else
+if ! /usr/sbin/selinuxenabled; then
     # Do not relabel if selinux is not enabled
     exit 0
 fi
 
-if test "$OLD_POLVER" = "$NEW_POLVER"; then
-   # Do not relabel if policy version did not change
+if diff ${FILE_CONTEXT} ${FILE_CONTEXT}.pre > /dev/null 2>&1; then
+   # Do not relabel if file contexts did not change
    exit 0
 fi
 
 # Check whether the daemons are running
-%if 0%{?_with_systemd}
-    /usr/bin/systemctl status ceph.target > /dev/null 2>&1
-%else
-    /sbin/service ceph status >/dev/null 2>&1
-%endif
+/usr/bin/systemctl status ceph.target > /dev/null 2>&1
 STATUS=$?
 
 # Stop the daemons if they were running
 if test $STATUS -eq 0; then
-%if 0%{?_with_systemd}
     /usr/bin/systemctl stop ceph.target > /dev/null 2>&1
-%else
-    /sbin/service ceph stop >/dev/null 2>&1
-%endif
 fi
 
 # Now, relabel the files
-%relabel_files
+/usr/sbin/fixfiles -C ${FILE_CONTEXT}.pre restore 2> /dev/null
+rm -f ${FILE_CONTEXT}.pre
+# The fixfiles command won't fix label for /var/run/ceph
+/usr/sbin/restorecon -R /var/run/ceph > /dev/null 2>&1
 
 # Start the daemons iff they were running before
 if test $STATUS -eq 0; then
-%if 0%{?_with_systemd}
     /usr/bin/systemctl start ceph.target > /dev/null 2>&1 || :
-%else
-    /sbin/service ceph start >/dev/null 2>&1 || :
-%endif
 fi
-
 exit 0
 
 %postun selinux
 if [ $1 -eq 0 ]; then
+    # backup file_contexts before update
+    . /etc/selinux/config
+    FILE_CONTEXT=/etc/selinux/${SELINUXTYPE}/contexts/files/file_contexts
+    cp ${FILE_CONTEXT} ${FILE_CONTEXT}.pre
+
     # Remove the module
-    %{_sbindir}/semodule -n -r ceph
+    /usr/sbin/semodule -n -r ceph > /dev/null 2>&1
 
     # Reload the policy if SELinux is enabled
-    if %{_sbindir}/selinuxenabled ; then
-        %{_sbindir}/load_policy
-    else
+    if ! /usr/sbin/selinuxenabled ; then
         # Do not relabel if SELinux is not enabled
         exit 0
     fi
 
     # Check whether the daemons are running
-    %if 0%{?_with_systemd}
-        /usr/bin/systemctl status ceph.target > /dev/null 2>&1
-    %else
-        /sbin/service ceph status >/dev/null 2>&1
-    %endif
+    /usr/bin/systemctl status ceph.target > /dev/null 2>&1
     STATUS=$?
 
     # Stop the daemons if they were running
     if test $STATUS -eq 0; then
-    %if 0%{?_with_systemd}
         /usr/bin/systemctl stop ceph.target > /dev/null 2>&1
-    %else
-        /sbin/service ceph stop >/dev/null 2>&1
-    %endif
     fi
 
-    # Now, relabel the files
-    %relabel_files
+    /usr/sbin/fixfiles -C ${FILE_CONTEXT}.pre restore 2> /dev/null
+    rm -f ${FILE_CONTEXT}.pre
+    # The fixfiles command won't fix label for /var/run/ceph
+    /usr/sbin/restorecon -R /var/run/ceph > /dev/null 2>&1
 
     # Start the daemons if they were running before
     if test $STATUS -eq 0; then
-    %if 0%{?_with_systemd}
 	/usr/bin/systemctl start ceph.target > /dev/null 2>&1 || :
-    %else
-	/sbin/service ceph start >/dev/null 2>&1 || :
-    %endif
     fi
 fi
 exit 0
diff --git a/src/test/centos-7/install-deps.sh b/src/test/centos-7/install-deps.sh
index 21e71ee..03ca760 100755
--- a/src/test/centos-7/install-deps.sh
+++ b/src/test/centos-7/install-deps.sh
@@ -28,7 +28,7 @@ if type apt-get > /dev/null 2>&1 ; then
 fi
 
 if type zypper > /dev/null 2>&1 ; then
-    $SUDO zypper --gpg-auto-import-keys --non-interactive install lsb-release
+    $SUDO zypper --gpg-auto-import-keys --non-interactive install lsb-release systemd-rpm-macros
 fi
 
 case $(lsb_release -si) in
diff --git a/src/test/cls_rbd/test_cls_rbd.cc b/src/test/cls_rbd/test_cls_rbd.cc
index 132af04..b698965 100644
--- a/src/test/cls_rbd/test_cls_rbd.cc
+++ b/src/test/cls_rbd/test_cls_rbd.cc
@@ -1383,6 +1383,10 @@ TEST_F(TestClsRbd, mirror_image) {
   cls::rbd::MirrorImage image3("uuid3", cls::rbd::MIRROR_IMAGE_STATE_ENABLED);
 
   ASSERT_EQ(0, mirror_image_set(&ioctx, "image_id1", image1));
+  ASSERT_EQ(-ENOENT, mirror_image_set(&ioctx, "image_id2", image2));
+  image2.state = cls::rbd::MIRROR_IMAGE_STATE_ENABLED;
+  ASSERT_EQ(0, mirror_image_set(&ioctx, "image_id2", image2));
+  image2.state = cls::rbd::MIRROR_IMAGE_STATE_DISABLING;
   ASSERT_EQ(0, mirror_image_set(&ioctx, "image_id2", image2));
   ASSERT_EQ(-EINVAL, mirror_image_set(&ioctx, "image_id1", image2));
   ASSERT_EQ(-EEXIST, mirror_image_set(&ioctx, "image_id3", image2));
@@ -1631,4 +1635,67 @@ TEST_F(TestClsRbd, mirror_image_status) {
   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]);
+
+  // Remove images
+
+  image1.state = cls::rbd::MIRROR_IMAGE_STATE_DISABLING;
+  image2.state = cls::rbd::MIRROR_IMAGE_STATE_DISABLING;
+  image3.state = cls::rbd::MIRROR_IMAGE_STATE_DISABLING;
+
+  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));
+
+  ASSERT_EQ(0, mirror_image_remove(&ioctx, "image_id1"));
+  ASSERT_EQ(0, mirror_image_remove(&ioctx, "image_id2"));
+  ASSERT_EQ(0, mirror_image_remove(&ioctx, "image_id3"));
+
+  states.clear();
+  ASSERT_EQ(0, mirror_image_status_get_summary(&ioctx, &states));
+  ASSERT_EQ(0U, states.size());
+
+  // Test status list with large number of images
+
+  size_t N = 1024;
+  ASSERT_EQ(0U, N % 2);
+
+  for (size_t i = 0; i < N; i++) {
+    std::string id = "id" + stringify(i);
+    std::string uuid = "uuid" + stringify(i);
+    cls::rbd::MirrorImage image(uuid, cls::rbd::MIRROR_IMAGE_STATE_ENABLED);
+    cls::rbd::MirrorImageStatus status(cls::rbd::MIRROR_IMAGE_STATUS_STATE_UNKNOWN);
+    ASSERT_EQ(0, mirror_image_set(&ioctx, id, image));
+    ASSERT_EQ(0, mirror_image_status_set(&ioctx, uuid, status));
+  }
+
+  std::string last_read = "";
+  images.clear();
+  statuses.clear();
+  ASSERT_EQ(0, mirror_image_status_list(&ioctx, last_read, N * 2, &images,
+	  &statuses));
+  ASSERT_EQ(N, images.size());
+  ASSERT_EQ(N, statuses.size());
+
+  images.clear();
+  statuses.clear();
+  ASSERT_EQ(0, mirror_image_status_list(&ioctx, last_read, N / 2, &images,
+	  &statuses));
+  ASSERT_EQ(N / 2, images.size());
+  ASSERT_EQ(N / 2, statuses.size());
+
+  last_read = images.rbegin()->first;
+  images.clear();
+  statuses.clear();
+  ASSERT_EQ(0, mirror_image_status_list(&ioctx, last_read, N / 2, &images,
+	  &statuses));
+  ASSERT_EQ(N / 2, images.size());
+  ASSERT_EQ(N / 2, statuses.size());
+
+  last_read = images.rbegin()->first;
+  images.clear();
+  statuses.clear();
+  ASSERT_EQ(0, mirror_image_status_list(&ioctx, last_read, N / 2, &images,
+	  &statuses));
+  ASSERT_EQ(0U, images.size());
+  ASSERT_EQ(0U, statuses.size());
 }
diff --git a/src/test/debian-jessie/install-deps.sh b/src/test/debian-jessie/install-deps.sh
index 21e71ee..03ca760 100755
--- a/src/test/debian-jessie/install-deps.sh
+++ b/src/test/debian-jessie/install-deps.sh
@@ -28,7 +28,7 @@ if type apt-get > /dev/null 2>&1 ; then
 fi
 
 if type zypper > /dev/null 2>&1 ; then
-    $SUDO zypper --gpg-auto-import-keys --non-interactive install lsb-release
+    $SUDO zypper --gpg-auto-import-keys --non-interactive install lsb-release systemd-rpm-macros
 fi
 
 case $(lsb_release -si) in
diff --git a/src/test/erasure-code/Makefile.am b/src/test/erasure-code/Makefile.am
index bdce080..3c45c67 100644
--- a/src/test/erasure-code/Makefile.am
+++ b/src/test/erasure-code/Makefile.am
@@ -42,109 +42,109 @@ test/erasure-code/ErasureCodePluginExample.cc: ./ceph_ver.h
 libec_example_la_CFLAGS = ${AM_CFLAGS}
 libec_example_la_CXXFLAGS= ${AM_CXXFLAGS}
 libec_example_la_LIBADD = $(LIBCRUSH) $(PTHREAD_LIBS) $(EXTRALIBS)
-libec_example_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared
+libec_example_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared -rpath /nowhere
 if LINUX
 libec_example_la_LDFLAGS += -export-symbols-regex '.*__erasure_code_.*'
 endif
-erasure_codelib_LTLIBRARIES += libec_example.la
+check_LTLIBRARIES += libec_example.la
 
 libec_missing_entry_point_la_SOURCES = test/erasure-code/ErasureCodePluginMissingEntryPoint.cc
 test/erasure-code/ErasureCodePluginMissingEntryPoint.cc: ./ceph_ver.h
 libec_missing_entry_point_la_CFLAGS = ${AM_CFLAGS}
 libec_missing_entry_point_la_CXXFLAGS= ${AM_CXXFLAGS}
 libec_missing_entry_point_la_LIBADD = $(PTHREAD_LIBS) $(EXTRALIBS)
-libec_missing_entry_point_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared
+libec_missing_entry_point_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared -rpath /nowhere
 if LINUX
 libec_missing_entry_point_la_LDFLAGS += -export-symbols-regex '.*__erasure_code_.*'
 endif
-erasure_codelib_LTLIBRARIES += libec_missing_entry_point.la
+check_LTLIBRARIES += libec_missing_entry_point.la
 
 libec_missing_version_la_SOURCES = test/erasure-code/ErasureCodePluginMissingVersion.cc
 libec_missing_version_la_CFLAGS = ${AM_CFLAGS}
 libec_missing_version_la_CXXFLAGS= ${AM_CXXFLAGS}
 libec_missing_version_la_LIBADD = $(PTHREAD_LIBS) $(EXTRALIBS)
-libec_missing_version_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared
+libec_missing_version_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared -rpath /nowhere
 if LINUX
 libec_missing_version_la_LDFLAGS += -export-symbols-regex '.*__erasure_code_.*'
 endif
-erasure_codelib_LTLIBRARIES += libec_missing_version.la
+check_LTLIBRARIES += libec_missing_version.la
 
 libec_hangs_la_SOURCES = test/erasure-code/ErasureCodePluginHangs.cc
 test/erasure-code/ErasureCodePluginHangs.cc: ./ceph_ver.h
 libec_hangs_la_CFLAGS = ${AM_CFLAGS}
 libec_hangs_la_CXXFLAGS= ${AM_CXXFLAGS}
 libec_hangs_la_LIBADD = $(PTHREAD_LIBS) $(EXTRALIBS)
-libec_hangs_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared
+libec_hangs_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared -rpath /nowhere
 if LINUX
 libec_hangs_la_LDFLAGS += -export-symbols-regex '.*__erasure_code_.*'
 endif
-erasure_codelib_LTLIBRARIES += libec_hangs.la
+check_LTLIBRARIES += libec_hangs.la
 
 libec_fail_to_initialize_la_SOURCES = test/erasure-code/ErasureCodePluginFailToInitialize.cc
 test/erasure-code/ErasureCodePluginFailToInitialize.cc: ./ceph_ver.h
 libec_fail_to_initialize_la_CFLAGS = ${AM_CFLAGS}
 libec_fail_to_initialize_la_CXXFLAGS= ${AM_CXXFLAGS}
 libec_fail_to_initialize_la_LIBADD = $(PTHREAD_LIBS) $(EXTRALIBS)
-libec_fail_to_initialize_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared
+libec_fail_to_initialize_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared -rpath /nowhere
 if LINUX
 libec_fail_to_initialize_la_LDFLAGS += -export-symbols-regex '.*__erasure_code_.*'
 endif
-erasure_codelib_LTLIBRARIES += libec_fail_to_initialize.la
+check_LTLIBRARIES += libec_fail_to_initialize.la
 
 libec_fail_to_register_la_SOURCES = test/erasure-code/ErasureCodePluginFailToRegister.cc
 test/erasure-code/ErasureCodePluginFailToRegister.cc: ./ceph_ver.h
 libec_fail_to_register_la_CFLAGS = ${AM_CFLAGS}
 libec_fail_to_register_la_CXXFLAGS= ${AM_CXXFLAGS}
 libec_fail_to_register_la_LIBADD = $(PTHREAD_LIBS) $(EXTRALIBS)
-libec_fail_to_register_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared
+libec_fail_to_register_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared -rpath /nowhere
 if LINUX
 libec_fail_to_register_la_LDFLAGS += -export-symbols-regex '.*__erasure_code_.*'
 endif
-erasure_codelib_LTLIBRARIES += libec_fail_to_register.la
+check_LTLIBRARIES += libec_fail_to_register.la
 
 libec_test_jerasure_neon_la_SOURCES = test/erasure-code/TestJerasurePluginNEON.cc
 test/erasure-code/TestJerasurePluginNEON.cc: ./ceph_ver.h
 libec_test_jerasure_neon_la_CFLAGS = ${AM_CFLAGS}
 libec_test_jerasure_neon_la_CXXFLAGS= ${AM_CXXFLAGS}
 libec_test_jerasure_neon_la_LIBADD = $(PTHREAD_LIBS) $(EXTRALIBS)
-libec_test_jerasure_neon_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared
+libec_test_jerasure_neon_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared -rpath /nowhere
 if LINUX
 libec_test_jerasure_neon_la_LDFLAGS += -export-symbols-regex '.*__erasure_code_.*'
 endif
-erasure_codelib_LTLIBRARIES += libec_test_jerasure_neon.la
+check_LTLIBRARIES += libec_test_jerasure_neon.la
 
 libec_test_jerasure_sse4_la_SOURCES = test/erasure-code/TestJerasurePluginSSE4.cc
 test/erasure-code/TestJerasurePluginSSE4.cc: ./ceph_ver.h
 libec_test_jerasure_sse4_la_CFLAGS = ${AM_CFLAGS}
 libec_test_jerasure_sse4_la_CXXFLAGS= ${AM_CXXFLAGS}
 libec_test_jerasure_sse4_la_LIBADD = $(PTHREAD_LIBS) $(EXTRALIBS)
-libec_test_jerasure_sse4_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared
+libec_test_jerasure_sse4_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared -rpath /nowhere
 if LINUX
 libec_test_jerasure_sse4_la_LDFLAGS += -export-symbols-regex '.*__erasure_code_.*'
 endif
-erasure_codelib_LTLIBRARIES += libec_test_jerasure_sse4.la
+check_LTLIBRARIES += libec_test_jerasure_sse4.la
 
 libec_test_jerasure_sse3_la_SOURCES = test/erasure-code/TestJerasurePluginSSE3.cc
 test/erasure-code/TestJerasurePluginSSE3.cc: ./ceph_ver.h
 libec_test_jerasure_sse3_la_CFLAGS = ${AM_CFLAGS}
 libec_test_jerasure_sse3_la_CXXFLAGS= ${AM_CXXFLAGS}
 libec_test_jerasure_sse3_la_LIBADD = $(PTHREAD_LIBS) $(EXTRALIBS)
-libec_test_jerasure_sse3_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared
+libec_test_jerasure_sse3_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared -rpath /nowhere
 if LINUX
 libec_test_jerasure_sse3_la_LDFLAGS += -export-symbols-regex '.*__erasure_code_.*'
 endif
-erasure_codelib_LTLIBRARIES += libec_test_jerasure_sse3.la
+check_LTLIBRARIES += libec_test_jerasure_sse3.la
 
 libec_test_jerasure_generic_la_SOURCES = test/erasure-code/TestJerasurePluginGeneric.cc
 test/erasure-code/TestJerasurePluginGeneric.cc: ./ceph_ver.h
 libec_test_jerasure_generic_la_CFLAGS = ${AM_CFLAGS}
 libec_test_jerasure_generic_la_CXXFLAGS= ${AM_CXXFLAGS}
 libec_test_jerasure_generic_la_LIBADD = $(PTHREAD_LIBS) $(EXTRALIBS)
-libec_test_jerasure_generic_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared
+libec_test_jerasure_generic_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared -rpath /nowhere
 if LINUX
 libec_test_jerasure_generic_la_LDFLAGS += -export-symbols-regex '.*__erasure_code_.*'
 endif
-erasure_codelib_LTLIBRARIES += libec_test_jerasure_generic.la
+check_LTLIBRARIES += libec_test_jerasure_generic.la
 
 unittest_erasure_code_plugin_SOURCES = \
 	erasure-code/ErasureCode.cc \
@@ -318,44 +318,44 @@ test/erasure-code/TestShecPluginNEON.cc: ./ceph_ver.h
 libec_test_shec_neon_la_CFLAGS = ${AM_CFLAGS}
 libec_test_shec_neon_la_CXXFLAGS= ${AM_CXXFLAGS}
 libec_test_shec_neon_la_LIBADD = $(PTHREAD_LIBS) $(EXTRALIBS)
-libec_test_shec_neon_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared
+libec_test_shec_neon_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared -rpath /nowhere
 if LINUX
 libec_test_shec_neon_la_LDFLAGS += -export-symbols-regex '.*__erasure_code_.*'
 endif
-erasure_codelib_LTLIBRARIES += libec_test_shec_neon.la
+check_LTLIBRARIES += libec_test_shec_neon.la
 
 libec_test_shec_sse4_la_SOURCES = test/erasure-code/TestShecPluginSSE4.cc
 test/erasure-code/TestShecPluginSSE4.cc: ./ceph_ver.h
 libec_test_shec_sse4_la_CFLAGS = ${AM_CFLAGS}
 libec_test_shec_sse4_la_CXXFLAGS= ${AM_CXXFLAGS}
 libec_test_shec_sse4_la_LIBADD = $(PTHREAD_LIBS) $(EXTRALIBS)
-libec_test_shec_sse4_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared
+libec_test_shec_sse4_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared -rpath /nowhere
 if LINUX
 libec_test_shec_sse4_la_LDFLAGS += -export-symbols-regex '.*__erasure_code_.*'
 endif
-erasure_codelib_LTLIBRARIES += libec_test_shec_sse4.la
+check_LTLIBRARIES += libec_test_shec_sse4.la
 
 libec_test_shec_sse3_la_SOURCES = test/erasure-code/TestShecPluginSSE3.cc
 test/erasure-code/TestShecPluginSSE3.cc: ./ceph_ver.h
 libec_test_shec_sse3_la_CFLAGS = ${AM_CFLAGS}
 libec_test_shec_sse3_la_CXXFLAGS= ${AM_CXXFLAGS}
 libec_test_shec_sse3_la_LIBADD = $(PTHREAD_LIBS) $(EXTRALIBS)
-libec_test_shec_sse3_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared
+libec_test_shec_sse3_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared -rpath /nowhere
 if LINUX
 libec_test_shec_sse3_la_LDFLAGS += -export-symbols-regex '.*__erasure_code_.*'
 endif
-erasure_codelib_LTLIBRARIES += libec_test_shec_sse3.la
+check_LTLIBRARIES += libec_test_shec_sse3.la
 
 libec_test_shec_generic_la_SOURCES = test/erasure-code/TestShecPluginGeneric.cc
 test/erasure-code/TestShecPluginGeneric.cc: ./ceph_ver.h
 libec_test_shec_generic_la_CFLAGS = ${AM_CFLAGS}
 libec_test_shec_generic_la_CXXFLAGS= ${AM_CXXFLAGS}
 libec_test_shec_generic_la_LIBADD = $(PTHREAD_LIBS) $(EXTRALIBS)
-libec_test_shec_generic_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared
+libec_test_shec_generic_la_LDFLAGS = ${AM_LDFLAGS} -module -avoid-version -shared -rpath /nowhere
 if LINUX
 libec_test_shec_generic_la_LDFLAGS += -export-symbols-regex '.*__erasure_code_.*'
 endif
-erasure_codelib_LTLIBRARIES += libec_test_shec_generic.la
+check_LTLIBRARIES += libec_test_shec_generic.la
 
 unittest_erasure_code_example_SOURCES = \
 	erasure-code/ErasureCode.cc \
diff --git a/src/test/fedora-21/ceph.spec.in b/src/test/fedora-21/ceph.spec.in
index 34e4caa..3cf6307 100644
--- a/src/test/fedora-21/ceph.spec.in
+++ b/src/test/fedora-21/ceph.spec.in
@@ -17,6 +17,7 @@
 %bcond_with ocf
 %bcond_without cephfs_java
 %bcond_with tests
+%bcond_with xio
 %bcond_without tcmalloc
 %bcond_without libs_compat
 %bcond_with lowmem_builder
@@ -32,39 +33,13 @@
 %bcond_without lttng
 %endif
 
-%if (0%{?el5} || (0%{?rhel_version} >= 500 && 0%{?rhel_version} <= 600))
-%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")}
-%{!?python_sitearch: %global python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")}
-%endif
-
 %if %{with selinux}
 # get selinux policy version
 %{!?_selinux_policy_version: %global _selinux_policy_version %(sed -e 's,.*selinux-policy-\\([^/]*\\)/.*,\\1,' /usr/share/selinux/devel/policyhelp 2>/dev/null || echo 0.0.0)}
-
-%define relabel_files() \
-restorecon -R /usr/bin/ceph-mon > /dev/null 2>&1; \
-restorecon -R /usr/bin/ceph-osd > /dev/null 2>&1; \
-restorecon -R /usr/bin/ceph-mds > /dev/null 2>&1; \
-restorecon -R /usr/bin/radosgw > /dev/null 2>&1; \
-restorecon -R /etc/rc\.d/init\.d/ceph > /dev/null 2>&1; \
-restorecon -R /etc/rc\.d/init\.d/radosgw > /dev/null 2>&1; \
-restorecon -R /var/run/ceph > /dev/null 2>&1; \
-restorecon -R /var/lib/ceph > /dev/null 2>&1; \
-restorecon -R /var/log/ceph > /dev/null 2>&1; \
-restorecon -R /var/log/radosgw > /dev/null 2>&1;
 %endif
 
 %{!?_udevrulesdir: %global _udevrulesdir /lib/udev/rules.d}
-
-# Use systemd files on RHEL 7 and above and in SUSE/openSUSE.
-# Note: We don't install unit files for the services yet. For now,
-# the _with_systemd variable only implies that we'll install
-# /etc/tmpfiles.d/ceph.conf in order to set up the socket directory in
-# /var/run/ceph.
-%if 0%{?fedora} || 0%{?rhel} >= 7 || 0%{?suse_version}
-%global _with_systemd 1
 %{!?tmpfiles_create: %global tmpfiles_create systemd-tmpfiles --create}
-%endif
 
 # unify libexec for all targets
 %global _libexecdir %{_exec_prefix}/lib
@@ -84,9 +59,6 @@ Group:         System/Filesystems
 %endif
 URL:		http://ceph.com/
 Source0:	http://ceph.com/download/%{name}-%{version}.tar.bz2
-%if 0%{?fedora} || 0%{?rhel}
-Patch0:		init-ceph.in-fedora.patch
-%endif
 #################################################################################
 # dependencies that apply across all distro families
 #################################################################################
@@ -103,20 +75,20 @@ BuildRequires:	checkpolicy
 BuildRequires:	selinux-policy-devel
 BuildRequires:	/usr/share/selinux/devel/policyhelp
 %endif
-BuildRequires:	gcc-c++
 BuildRequires:	boost-devel
 BuildRequires:  cmake
 BuildRequires:	cryptsetup
 BuildRequires:	fuse-devel
+BuildRequires:	gcc-c++
 BuildRequires:	gdbm
 BuildRequires:	hdparm
 BuildRequires:	leveldb-devel > 1.2
 BuildRequires:	libaio-devel
-BuildRequires:	libcurl-devel
-BuildRequires:	libxml2-devel
 BuildRequires:	libblkid-devel >= 2.17
+BuildRequires:	libcurl-devel
 BuildRequires:	libudev-devel
 BuildRequires:	libtool
+BuildRequires:	libxml2-devel
 BuildRequires:	make
 BuildRequires:	parted
 BuildRequires:	perl
@@ -125,6 +97,7 @@ BuildRequires:	python
 BuildRequires:	python-devel
 BuildRequires:	python-nose
 BuildRequires:	python-requests
+BuildRequires:	python-sphinx
 BuildRequires:	python-virtualenv
 BuildRequires:	snappy-devel
 BuildRequires:	util-linux
@@ -138,12 +111,10 @@ BuildRequires:	yasm
 # distro-conditional dependencies
 #################################################################################
 %if 0%{?suse_version}
-%if 0%{?_with_systemd}
 BuildRequires:  pkgconfig(systemd)
 BuildRequires:	systemd-rpm-macros
 BuildRequires:	systemd
 %{?systemd_requires}
-%endif
 PreReq:		%fillup_prereq
 BuildRequires:	net-tools
 BuildRequires:	libbz2-devel
@@ -160,30 +131,18 @@ BuildRequires:  openldap2-devel
 BuildRequires:	python-Cython
 %endif
 %if 0%{?fedora} || 0%{?rhel} 
-%if 0%{?_with_systemd}
 Requires:	systemd
-%endif
+BuildRequires:  boost-random
 BuildRequires:	btrfs-progs
 BuildRequires:	nss-devel
 BuildRequires:	keyutils-libs-devel
 BuildRequires:	libatomic_ops-devel
-Requires(post):	chkconfig
-Requires(preun):	chkconfig
-Requires(preun):	initscripts
 BuildRequires:	gperftools-devel
 BuildRequires:  openldap-devel
 BuildRequires:  openssl-devel
 BuildRequires:  redhat-lsb-core
 BuildRequires:	Cython
 %endif
-# boost
-%if 0%{?fedora} || 0%{?rhel} 
-BuildRequires:  boost-random
-%endif
-# python-argparse for distros with Python 2.6 or lower
-%if (0%{?rhel} && 0%{?rhel} <= 6)
-BuildRequires:	python-argparse
-%endif
 # lttng and babeltrace for rbd-replay-prep
 %if %{with lttng}
 %if 0%{?fedora} || 0%{?rhel}
@@ -204,17 +163,14 @@ BuildRequires:	FastCGI-devel
 BuildRequires:	expat-devel
 BuildRequires:	fcgi-devel
 %endif
-# python-sphinx
-%if 0%{?rhel} > 0 && 0%{?rhel} < 7
-BuildRequires:	python-sphinx10
-%endif
-%if 0%{?fedora} || 0%{?suse_version} || 0%{?rhel} >= 7
-BuildRequires:	python-sphinx
-%endif
 #hardened-cc1
 %if 0%{?fedora} || 0%{?rhel}
 BuildRequires:  redhat-rpm-config
 %endif
+# Accelio IB/RDMA
+%if 0%{with xio}
+BuildRequires:  libxio-devel
+%endif
 
 %description
 Ceph is a massively scalable, open-source, distributed storage system that runs
@@ -249,10 +205,14 @@ Requires:      findutils
 Requires:      which
 %if 0%{?suse_version}
 Requires:      lsb-release
+Recommends:    ntp-daemon
 %endif
 %if 0%{?fedora} || 0%{?rhel}
 Requires:      redhat-lsb-core
 %endif
+%if 0%{with xio}
+Requires:      libxio
+%endif
 %description base
 Base is the package that includes all the files shared amongst ceph servers
 
@@ -266,15 +226,12 @@ Requires:	python-rados = %{epoch}:%{version}-%{release}
 Requires:	python-rbd = %{epoch}:%{version}-%{release}
 Requires:	python-cephfs = %{epoch}:%{version}-%{release}
 Requires:	python-requests
-%if 0%{?_with_systemd}
 %{?systemd_requires}
-%endif
 %if 0%{?suse_version}
 Requires(pre):	pwdutils
 %endif
-# python-argparse is only needed in distros with Python 2.6 or lower
-%if (0%{?rhel} && 0%{?rhel} <= 6)
-Requires:	python-argparse
+%if 0%{with xio}
+Requires:       libxio
 %endif
 %description -n ceph-common
 Common utilities to mount and interact with a ceph storage cluster.
@@ -572,13 +529,8 @@ Group:		System Environment/Libraries
 License:	LGPL-2.0
 Requires:	java
 Requires:	libcephfs_jni1 = %{epoch}:%{version}-%{release}
-%if 0%{?el6}
-Requires:	junit4
-BuildRequires:	junit4
-%else
 Requires:       junit
 BuildRequires:  junit
-%endif
 %description -n cephfs-java
 This package contains the Java libraries for the Ceph File System.
 
@@ -660,9 +612,6 @@ python-cephfs instead.
 #################################################################################
 %prep
 %setup -q
-%if 0%{?fedora} || 0%{?rhel}
-%patch0 -p1 -b .init
-%endif
 
 %build
 %if 0%{with cephfs_java}
@@ -682,14 +631,12 @@ export RPM_OPT_FLAGS=`echo $RPM_OPT_FLAGS | sed -e 's/i386/i486/'`
 %{configure}	CPPFLAGS="$java_inc" \
 		--prefix=/usr \
                 --libexecdir=%{_libexecdir} \
-		--localstatedir=/var \
-		--sysconfdir=/etc \
+		--localstatedir=%{_localstatedir} \
+		--sysconfdir=%{_sysconfdir} \
 %if 0%{?rhel} && ! 0%{?centos}
                 --enable-subman \
 %endif
-%if 0%{?_with_systemd}
 		--with-systemdsystemunitdir=%_unitdir \
-%endif
 		--docdir=%{_docdir}/ceph \
 		--with-man-pages \
 		--mandir="%_mandir" \
@@ -699,6 +646,9 @@ export RPM_OPT_FLAGS=`echo $RPM_OPT_FLAGS | sed -e 's/i386/i486/'`
 %if 0%{with cephfs_java}
 		--enable-cephfs-java \
 %endif
+%if 0%{with xio}
+		--enable-xio \
+%endif
 %if 0%{with selinux}
 		--with-selinux \
 %endif
@@ -725,66 +675,30 @@ make %{?_smp_mflags}
 %if 0%{with tests}
 %check
 # run in-tree unittests
-make %{?_smp_mflags} check-local
+make %{?_smp_mflags} check
 
 %endif
 
 
 
 %install
-make DESTDIR=$RPM_BUILD_ROOT install
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_example.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_fail_to_initialize.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_fail_to_register.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_hangs.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_missing_entry_point.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_missing_version.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_jerasure_generic.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_jerasure_neon.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_jerasure_sse3.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_jerasure_sse4.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_shec_generic.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_shec_neon.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_shec_sse3.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_shec_sse4.so
-find $RPM_BUILD_ROOT -type f -name "*.la" -exec rm -f {} ';'
-find $RPM_BUILD_ROOT -type f -name "*.a" -exec rm -f {} ';'
-install -D src/etc-rbdmap $RPM_BUILD_ROOT%{_sysconfdir}/ceph/rbdmap
+make DESTDIR=%{buildroot} install
+find %{buildroot} -type f -name "*.la" -exec rm -f {} ';'
+find %{buildroot} -type f -name "*.a" -exec rm -f {} ';'
+install -D src/etc-rbdmap %{buildroot}%{_sysconfdir}/ceph/rbdmap
 %if 0%{?fedora} || 0%{?rhel}
-install -m 0644 -D etc/sysconfig/ceph $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig/ceph
+install -m 0644 -D etc/sysconfig/ceph %{buildroot}%{_sysconfdir}/sysconfig/ceph
 %endif
 %if 0%{?suse_version}
-install -m 0644 -D etc/sysconfig/ceph $RPM_BUILD_ROOT%{_localstatedir}/adm/fillup-templates/sysconfig.%{name}
-%endif
-%if 0%{?_with_systemd}
-  install -m 0644 -D systemd/ceph.tmpfiles.d $RPM_BUILD_ROOT%{_tmpfilesdir}/ceph-common.conf
-  install -m 0644 -D systemd/rbdmap.service $RPM_BUILD_ROOT%{_unitdir}/rbdmap.service
-  install -m 0644 -D systemd/ceph-osd at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-osd at .service
-  install -m 0644 -D systemd/ceph-mon at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-mon at .service
-  install -m 0644 -D systemd/ceph-create-keys at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-create-keys at .service
-  install -m 0644 -D systemd/ceph-mds at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-mds at .service
-  install -m 0644 -D systemd/ceph-radosgw at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-radosgw at .service
-  install -m 0644 -D systemd/ceph-rbd-mirror at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-rbd-mirror at .service
-  install -m 0644 -D systemd/ceph.target $RPM_BUILD_ROOT%{_unitdir}/ceph.target
-  install -m 0644 -D systemd/ceph-osd.target $RPM_BUILD_ROOT%{_unitdir}/ceph-osd.target
-  install -m 0644 -D systemd/ceph-mon.target $RPM_BUILD_ROOT%{_unitdir}/ceph-mon.target
-  install -m 0644 -D systemd/ceph-mds.target $RPM_BUILD_ROOT%{_unitdir}/ceph-mds.target
-  install -m 0644 -D systemd/ceph-radosgw.target $RPM_BUILD_ROOT%{_unitdir}/ceph-radosgw.target
-  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
-  install -D src/init-radosgw $RPM_BUILD_ROOT%{_initrddir}/ceph-radosgw
-  ln -sf ../../etc/init.d/ceph %{buildroot}/%{_sbindir}/rcceph
-  ln -sf ../../etc/init.d/ceph-radosgw %{buildroot}/%{_sbindir}/rcceph-radosgw
-%endif
-mkdir -p $RPM_BUILD_ROOT%{_sbindir}
-install -m 0644 -D src/logrotate.conf $RPM_BUILD_ROOT%{_sysconfdir}/logrotate.d/ceph
-chmod 0644 $RPM_BUILD_ROOT%{_docdir}/ceph/sample.ceph.conf
-chmod 0644 $RPM_BUILD_ROOT%{_docdir}/ceph/sample.fetch_config
+install -m 0644 -D etc/sysconfig/ceph %{buildroot}%{_localstatedir}/adm/fillup-templates/sysconfig.%{name}
+%endif
+install -m 0644 -D systemd/ceph.tmpfiles.d %{buildroot}%{_tmpfilesdir}/ceph-common.conf
+install -m 0755 -D systemd/ceph %{buildroot}%{_sbindir}/rcceph
+install -m 0644 -D systemd/50-ceph.preset %{buildroot}%{_libexecdir}/systemd/system-preset/50-ceph.preset
+mkdir -p %{buildroot}%{_sbindir}
+install -m 0644 -D src/logrotate.conf %{buildroot}%{_sysconfdir}/logrotate.d/ceph
+chmod 0644 %{buildroot}%{_docdir}/ceph/sample.ceph.conf
+chmod 0644 %{buildroot}%{_docdir}/ceph/sample.fetch_config
 
 # firewall templates
 %if 0%{?suse_version}
@@ -793,38 +707,26 @@ install -m 0644 -D etc/sysconfig/SuSEfirewall2.d/services/ceph-osd-mds %{buildro
 %endif
 
 # udev rules
-install -m 0644 -D udev/50-rbd.rules $RPM_BUILD_ROOT%{_udevrulesdir}/50-rbd.rules
-install -m 0644 -D udev/60-ceph-partuuid-workaround.rules $RPM_BUILD_ROOT%{_udevrulesdir}/60-ceph-partuuid-workaround.rules
-
-%if (0%{?rhel} && 0%{?rhel} < 7)
-install -m 0644 -D udev/95-ceph-osd-alt.rules $RPM_BUILD_ROOT/lib/udev/rules.d/95-ceph-osd.rules
-%else
-install -m 0644 -D udev/95-ceph-osd.rules $RPM_BUILD_ROOT/lib/udev/rules.d/95-ceph-osd.rules
-%endif
-
-%if 0%{?rhel} >= 7 || 0%{?fedora} || 0%{?suse_version}
-mv $RPM_BUILD_ROOT/lib/udev/rules.d/95-ceph-osd.rules $RPM_BUILD_ROOT/usr/lib/udev/rules.d/95-ceph-osd.rules
-mv $RPM_BUILD_ROOT/sbin/mount.ceph $RPM_BUILD_ROOT/usr/sbin/mount.ceph
-mv $RPM_BUILD_ROOT/sbin/mount.fuse.ceph $RPM_BUILD_ROOT/usr/sbin/mount.fuse.ceph
-%endif
+install -m 0644 -D udev/50-rbd.rules %{buildroot}%{_udevrulesdir}/50-rbd.rules
+install -m 0644 -D udev/95-ceph-osd.rules %{buildroot}%{_udevrulesdir}/95-ceph-osd.rules
+mv %{buildroot}/sbin/mount.ceph %{buildroot}/usr/sbin/mount.ceph
+mv %{buildroot}/sbin/mount.fuse.ceph %{buildroot}/usr/sbin/mount.fuse.ceph
 
 #set up placeholder directories
-mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/ceph
-%if ! 0%{?_with_systemd}
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/run/ceph
-%endif
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/log/ceph
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/tmp
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/mon
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/osd
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/mds
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/radosgw
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/bootstrap-osd
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/bootstrap-mds
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/bootstrap-rgw
+mkdir -p %{buildroot}%{_sysconfdir}/ceph
+mkdir -p %{buildroot}%{_localstatedir}/run/ceph
+mkdir -p %{buildroot}%{_localstatedir}/log/ceph
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/tmp
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/mon
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/osd
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/mds
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/radosgw
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/bootstrap-osd
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/bootstrap-mds
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/bootstrap-rgw
 
 %clean
-rm -rf $RPM_BUILD_ROOT
+rm -rf %{buildroot}
 
 #################################################################################
 # files and systemd scriptlets
@@ -844,19 +746,11 @@ rm -rf $RPM_BUILD_ROOT
 %{_bindir}/ceph-detect-init
 %{_bindir}/ceph-client-debug
 %{_bindir}/cephfs
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-create-keys at .service
 %{_libexecdir}/systemd/system-preset/50-ceph.preset
-%else
-%{_initrddir}/ceph
-%endif
 %{_sbindir}/ceph-create-keys
 %{_sbindir}/rcceph
-%if 0%{?rhel} >= 7 || 0%{?fedora} || 0%{?suse_version}
 %{_sbindir}/mount.ceph
-%else
-/sbin/mount.ceph
-%endif
 %dir %{_libexecdir}/ceph
 %{_libexecdir}/ceph/ceph_common.sh
 %dir %{_libdir}/rados-classes
@@ -897,9 +791,6 @@ rm -rf $RPM_BUILD_ROOT
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/bootstrap-osd
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/bootstrap-mds
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/bootstrap-rgw
-%if ! 0%{?_with_systemd}
-%attr(770,ceph,ceph) %dir %{_localstatedir}/run/ceph
-%endif
 
 %post base
 /sbin/ldconfig
@@ -955,9 +846,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 %endif
 %{_bindir}/ceph-post-file
 %{_bindir}/ceph-brag
-%if 0%{?_with_systemd}
 %{_tmpfilesdir}/ceph-common.conf
-%endif
 %{_mandir}/man8/ceph-authtool.8*
 %{_mandir}/man8/ceph-conf.8*
 %{_mandir}/man8/ceph-dencoder.8*
@@ -979,11 +868,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 %config %{_sysconfdir}/bash_completion.d/rados
 %config %{_sysconfdir}/bash_completion.d/rbd
 %config(noreplace) %{_sysconfdir}/ceph/rbdmap
-%if 0%{?_with_systemd}
 %{_unitdir}/rbdmap.service
-%else
-%{_initrddir}/rbdmap
-%endif
 %{python_sitelib}/ceph_argparse.py*
 %{python_sitelib}/ceph_daemon.py*
 %{_udevrulesdir}/50-rbd.rules
@@ -994,8 +879,8 @@ DISABLE_RESTART_ON_UPDATE="yes"
 CEPH_GROUP_ID=167
 CEPH_USER_ID=167
 %if 0%{?rhel} || 0%{?fedora}
-%{_sbindir}/groupadd ceph -g $CEPH_GROUP_ID -o -r 2>/dev/null || :
-%{_sbindir}/useradd ceph -u $CEPH_USER_ID -o -r -g ceph -s /sbin/nologin -c "Ceph daemons" -d %{_localstatedir}/lib/ceph 2>/dev/null || :
+/usr/sbin/groupadd ceph -g $CEPH_GROUP_ID -o -r 2>/dev/null || :
+/usr/sbin/useradd ceph -u $CEPH_USER_ID -o -r -g ceph -s /sbin/nologin -c "Ceph daemons" -d %{_localstatedir}/lib/ceph 2>/dev/null || :
 %endif
 %if 0%{?suse_version}
 if ! getent group ceph >/dev/null ; then
@@ -1006,33 +891,32 @@ fi
 if ! getent passwd ceph >/dev/null ; then
     CEPH_USER_ID_OPTION=""
     getent passwd $CEPH_USER_ID >/dev/null || CEPH_USER_ID_OPTION="-u $CEPH_USER_ID"
-    useradd ceph $CEPH_USER_ID_OPTION -r -g ceph -s /sbin/nologin -c "Ceph daemons" -d %{_localstatedir}/lib/ceph 2>/dev/null || :
+    useradd ceph $CEPH_USER_ID_OPTION -r -g ceph -s /sbin/nologin 2>/dev/null || :
 fi
+usermod -c "Ceph storage service" \
+        -d %{_localstatedir}/lib/ceph \   
+        -g ceph \
+        -s /sbin/nologin \
+        ceph
 %endif
 exit 0
 
 %post common
-%if 0%{?_with_systemd}
 %tmpfiles_create %{_tmpfilesdir}/ceph-common.conf
-%endif
 
 %postun common
 # Package removal cleanup
 if [ "$1" -eq "0" ] ; then
-    rm -rf /var/log/ceph
-    rm -rf /etc/ceph
+    rm -rf %{_localstatedir}/log/ceph
+    rm -rf %{_sysconfdir}/ceph
 fi
 
 #################################################################################
 %files mds
 %{_bindir}/ceph-mds
 %{_mandir}/man8/ceph-mds.8*
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-mds at .service
 %{_unitdir}/ceph-mds.target
-%else
-%{_initrddir}/ceph
-%endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/mds
 
 %post mds
@@ -1066,7 +950,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1082,12 +966,8 @@ fi
 %{_mandir}/man8/ceph-mon.8*
 %{_mandir}/man8/ceph-rest-api.8*
 %{python_sitelib}/ceph_rest_api.py*
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-mon at .service
 %{_unitdir}/ceph-mon.target
-%else
-%{_initrddir}/ceph
-%endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/mon
 
 %post mon
@@ -1121,7 +1001,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1135,11 +1015,7 @@ fi
 %defattr(-,root,root,-)
 %{_bindir}/ceph-fuse
 %{_mandir}/man8/ceph-fuse.8*
-%if 0%{?rhel} >= 7 || 0%{?fedora} || 0%{?suse_version}
 %{_sbindir}/mount.fuse.ceph
-%else
-/sbin/mount.fuse.ceph
-%endif
 
 #################################################################################
 %files -n rbd-fuse
@@ -1152,10 +1028,8 @@ fi
 %defattr(-,root,root,-)
 %{_bindir}/rbd-mirror
 %{_mandir}/man8/rbd-mirror.8*
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-rbd-mirror at .service
 %{_unitdir}/ceph-rbd-mirror.target
-%endif
 
 %post -n rbd-mirror
 %if 0%{?suse_version}
@@ -1188,7 +1062,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1214,13 +1088,8 @@ fi
 %{_mandir}/man8/radosgw-admin.8*
 %config %{_sysconfdir}/bash_completion.d/radosgw-admin
 %dir %{_localstatedir}/lib/ceph/radosgw
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-radosgw at .service
 %{_unitdir}/ceph-radosgw.target
-%else
-%{_initrddir}/ceph-radosgw
-%{_sbindir}/rcceph-radosgw
-%endif
 
 %post radosgw
 %if 0%{?suse_version}
@@ -1253,7 +1122,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1271,21 +1140,16 @@ fi
 %{_sbindir}/ceph-disk
 %{_sbindir}/ceph-disk-udev
 %{_libexecdir}/ceph/ceph-osd-prestart.sh
-%{_udevrulesdir}/60-ceph-partuuid-workaround.rules
 %{_udevrulesdir}/95-ceph-osd.rules
 %{_mandir}/man8/ceph-clsinfo.8*
 %{_mandir}/man8/ceph-disk.8*
 %{_mandir}/man8/ceph-osd.8*
 %if 0%{?rhel} && ! 0%{?centos}
-/etc/cron.hourly/subman
+%{_sysconfdir}/cron.hourly/subman
 %endif
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-osd at .service
 %{_unitdir}/ceph-osd.target
 %{_unitdir}/ceph-disk at .service
-%else
-%{_initrddir}/ceph
-%endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/osd
 
 %post osd
@@ -1319,7 +1183,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1337,18 +1201,10 @@ fi
 %dir %{_prefix}/lib/ocf
 %dir %{_prefix}/lib/ocf/resource.d
 %dir %{_prefix}/lib/ocf/resource.d/ceph
-%if 0%{_with_systemd}
 %exclude %{_prefix}/lib/ocf/resource.d/ceph/ceph
 %exclude %{_prefix}/lib/ocf/resource.d/ceph/mds
 %exclude %{_prefix}/lib/ocf/resource.d/ceph/mon
 %exclude %{_prefix}/lib/ocf/resource.d/ceph/osd
-%endif
-%if ! 0%{_with_systemd}
-%{_prefix}/lib/ocf/resource.d/ceph/ceph
-%{_prefix}/lib/ocf/resource.d/ceph/mds
-%{_prefix}/lib/ocf/resource.d/ceph/mon
-%{_prefix}/lib/ocf/resource.d/ceph/osd
-%endif
 %{_prefix}/lib/ocf/resource.d/ceph/rbd
 
 %endif
@@ -1560,95 +1416,79 @@ ln -sf %{_libdir}/librbd.so.1 /usr/lib64/qemu/librbd.so.1
 %{_mandir}/man8/ceph_selinux.8*
 
 %post selinux
+# backup file_contexts before update
+. /etc/selinux/config
+FILE_CONTEXT=/etc/selinux/${SELINUXTYPE}/contexts/files/file_contexts
+cp ${FILE_CONTEXT} ${FILE_CONTEXT}.pre
+
 # Install the policy
-OLD_POLVER=$(%{_sbindir}/semodule -l | grep -P '^ceph[\t ]' | awk '{print $2}')
-%{_sbindir}/semodule -n -i %{_datadir}/selinux/packages/ceph.pp
-NEW_POLVER=$(%{_sbindir}/semodule -l | grep -P '^ceph[\t ]' | awk '{print $2}')
+/usr/sbin/semodule -i %{_datadir}/selinux/packages/ceph.pp
 
 # Load the policy if SELinux is enabled
-if %{_sbindir}/selinuxenabled; then
-    %{_sbindir}/load_policy
-else
+if ! /usr/sbin/selinuxenabled; then
     # Do not relabel if selinux is not enabled
     exit 0
 fi
 
-if test "$OLD_POLVER" = "$NEW_POLVER"; then
-   # Do not relabel if policy version did not change
+if diff ${FILE_CONTEXT} ${FILE_CONTEXT}.pre > /dev/null 2>&1; then
+   # Do not relabel if file contexts did not change
    exit 0
 fi
 
 # Check whether the daemons are running
-%if 0%{?_with_systemd}
-    /usr/bin/systemctl status ceph.target > /dev/null 2>&1
-%else
-    /sbin/service ceph status >/dev/null 2>&1
-%endif
+/usr/bin/systemctl status ceph.target > /dev/null 2>&1
 STATUS=$?
 
 # Stop the daemons if they were running
 if test $STATUS -eq 0; then
-%if 0%{?_with_systemd}
     /usr/bin/systemctl stop ceph.target > /dev/null 2>&1
-%else
-    /sbin/service ceph stop >/dev/null 2>&1
-%endif
 fi
 
 # Now, relabel the files
-%relabel_files
+/usr/sbin/fixfiles -C ${FILE_CONTEXT}.pre restore 2> /dev/null
+rm -f ${FILE_CONTEXT}.pre
+# The fixfiles command won't fix label for /var/run/ceph
+/usr/sbin/restorecon -R /var/run/ceph > /dev/null 2>&1
 
 # Start the daemons iff they were running before
 if test $STATUS -eq 0; then
-%if 0%{?_with_systemd}
     /usr/bin/systemctl start ceph.target > /dev/null 2>&1 || :
-%else
-    /sbin/service ceph start >/dev/null 2>&1 || :
-%endif
 fi
-
 exit 0
 
 %postun selinux
 if [ $1 -eq 0 ]; then
+    # backup file_contexts before update
+    . /etc/selinux/config
+    FILE_CONTEXT=/etc/selinux/${SELINUXTYPE}/contexts/files/file_contexts
+    cp ${FILE_CONTEXT} ${FILE_CONTEXT}.pre
+
     # Remove the module
-    %{_sbindir}/semodule -n -r ceph
+    /usr/sbin/semodule -n -r ceph > /dev/null 2>&1
 
     # Reload the policy if SELinux is enabled
-    if %{_sbindir}/selinuxenabled ; then
-        %{_sbindir}/load_policy
-    else
+    if ! /usr/sbin/selinuxenabled ; then
         # Do not relabel if SELinux is not enabled
         exit 0
     fi
 
     # Check whether the daemons are running
-    %if 0%{?_with_systemd}
-        /usr/bin/systemctl status ceph.target > /dev/null 2>&1
-    %else
-        /sbin/service ceph status >/dev/null 2>&1
-    %endif
+    /usr/bin/systemctl status ceph.target > /dev/null 2>&1
     STATUS=$?
 
     # Stop the daemons if they were running
     if test $STATUS -eq 0; then
-    %if 0%{?_with_systemd}
         /usr/bin/systemctl stop ceph.target > /dev/null 2>&1
-    %else
-        /sbin/service ceph stop >/dev/null 2>&1
-    %endif
     fi
 
-    # Now, relabel the files
-    %relabel_files
+    /usr/sbin/fixfiles -C ${FILE_CONTEXT}.pre restore 2> /dev/null
+    rm -f ${FILE_CONTEXT}.pre
+    # The fixfiles command won't fix label for /var/run/ceph
+    /usr/sbin/restorecon -R /var/run/ceph > /dev/null 2>&1
 
     # Start the daemons if they were running before
     if test $STATUS -eq 0; then
-    %if 0%{?_with_systemd}
 	/usr/bin/systemctl start ceph.target > /dev/null 2>&1 || :
-    %else
-	/sbin/service ceph start >/dev/null 2>&1 || :
-    %endif
     fi
 fi
 exit 0
diff --git a/src/test/fedora-21/install-deps.sh b/src/test/fedora-21/install-deps.sh
index 21e71ee..03ca760 100755
--- a/src/test/fedora-21/install-deps.sh
+++ b/src/test/fedora-21/install-deps.sh
@@ -28,7 +28,7 @@ if type apt-get > /dev/null 2>&1 ; then
 fi
 
 if type zypper > /dev/null 2>&1 ; then
-    $SUDO zypper --gpg-auto-import-keys --non-interactive install lsb-release
+    $SUDO zypper --gpg-auto-import-keys --non-interactive install lsb-release systemd-rpm-macros
 fi
 
 case $(lsb_release -si) in
diff --git a/src/test/journal/RadosTestFixture.cc b/src/test/journal/RadosTestFixture.cc
index 6fa8759..f57e5ae 100644
--- a/src/test/journal/RadosTestFixture.cc
+++ b/src/test/journal/RadosTestFixture.cc
@@ -44,6 +44,12 @@ void RadosTestFixture::SetUp() {
 }
 
 void RadosTestFixture::TearDown() {
+  for (auto metadata : m_metadatas) {
+    C_SaferCond ctx;
+    metadata->shut_down(&ctx);
+    ASSERT_EQ(0, ctx.wait());
+  }
+
   {
     Mutex::Locker locker(m_timer_lock);
     m_timer->shutdown();
@@ -65,6 +71,7 @@ journal::JournalMetadataPtr RadosTestFixture::create_metadata(
   journal::JournalMetadataPtr metadata(new journal::JournalMetadata(
     m_work_queue, m_timer, &m_timer_lock, m_ioctx, oid, client_id,
     commit_internal));
+  m_metadatas.push_back(metadata);
   return metadata;
 }
 
diff --git a/src/test/journal/RadosTestFixture.h b/src/test/journal/RadosTestFixture.h
index 3415b0b..d7cd1a8 100644
--- a/src/test/journal/RadosTestFixture.h
+++ b/src/test/journal/RadosTestFixture.h
@@ -68,4 +68,6 @@ public:
   SafeTimer *m_timer;
 
   Listener m_listener;
+
+  std::list<journal::JournalMetadataPtr> m_metadatas;
 };
diff --git a/src/test/journal/mock/MockJournaler.h b/src/test/journal/mock/MockJournaler.h
index 4d9053f..05efb42 100644
--- a/src/test/journal/mock/MockJournaler.h
+++ b/src/test/journal/mock/MockJournaler.h
@@ -91,6 +91,7 @@ struct MockJournaler {
 
   MOCK_METHOD1(init, void(Context *));
   MOCK_METHOD0(shut_down, void());
+  MOCK_METHOD1(shut_down, void(Context *));
   MOCK_CONST_METHOD0(is_initialized, bool());
 
   MOCK_METHOD3(get_metadata, void(uint8_t *order, uint8_t *splay_width,
@@ -113,6 +114,7 @@ struct MockJournaler {
   MOCK_METHOD1(try_pop_front, bool(MockReplayEntryProxy *));
   MOCK_METHOD2(try_pop_front, bool(MockReplayEntryProxy *, uint64_t *));
   MOCK_METHOD0(stop_replay, void());
+  MOCK_METHOD1(stop_replay, void(Context *on_finish));
 
   MOCK_METHOD3(start_append, void(int flush_interval, uint64_t flush_bytes,
                                   double flush_age));
@@ -164,6 +166,9 @@ struct MockJournalerProxy {
   void shut_down() {
     MockJournaler::get_instance().shut_down();
   }
+  void shut_down(Context *on_finish) {
+    MockJournaler::get_instance().shut_down(on_finish);
+  }
   bool is_initialized() const {
     return MockJournaler::get_instance().is_initialized();
   }
@@ -225,6 +230,9 @@ struct MockJournalerProxy {
   void stop_replay() {
     MockJournaler::get_instance().stop_replay();
   }
+  void stop_replay(Context *on_finish) {
+    MockJournaler::get_instance().stop_replay(on_finish);
+  }
 
   void start_append(int flush_interval, uint64_t flush_bytes, double flush_age) { 
     MockJournaler::get_instance().start_append(flush_interval, flush_bytes,
diff --git a/src/test/journal/test_JournalMetadata.cc b/src/test/journal/test_JournalMetadata.cc
index 850263d..b8f5593 100644
--- a/src/test/journal/test_JournalMetadata.cc
+++ b/src/test/journal/test_JournalMetadata.cc
@@ -110,8 +110,37 @@ TEST_F(TestJournalMetadata, UpdateActiveObject) {
 
   ASSERT_EQ(0U, metadata1->get_active_set());
 
-  metadata1->set_active_set(123);
+  ASSERT_EQ(0, metadata1->set_active_set(123));
   ASSERT_TRUE(wait_for_update(metadata1));
 
   ASSERT_EQ(123U, metadata1->get_active_set());
 }
+
+TEST_F(TestJournalMetadata, AssertActiveTag) {
+  std::string oid = get_temp_oid();
+
+  ASSERT_EQ(0, create(oid));
+  ASSERT_EQ(0, client_register(oid, "client1", ""));
+
+  journal::JournalMetadataPtr metadata = create_metadata(oid, "client1");
+  ASSERT_EQ(0, init_metadata(metadata));
+  ASSERT_TRUE(wait_for_update(metadata));
+
+  C_SaferCond ctx1;
+  cls::journal::Tag tag1;
+  metadata->allocate_tag(cls::journal::Tag::TAG_CLASS_NEW, {}, &tag1, &ctx1);
+  ASSERT_EQ(0, ctx1.wait());
+
+  C_SaferCond ctx2;
+  metadata->assert_active_tag(tag1.tid, &ctx2);
+  ASSERT_EQ(0, ctx2.wait());
+
+  C_SaferCond ctx3;
+  cls::journal::Tag tag2;
+  metadata->allocate_tag(tag1.tag_class, {}, &tag2, &ctx3);
+  ASSERT_EQ(0, ctx3.wait());
+
+  C_SaferCond ctx4;
+  metadata->assert_active_tag(tag1.tid, &ctx4);
+  ASSERT_EQ(-ESTALE, ctx4.wait());
+}
diff --git a/src/test/journal/test_JournalPlayer.cc b/src/test/journal/test_JournalPlayer.cc
index f989dd5..000f13b 100644
--- a/src/test/journal/test_JournalPlayer.cc
+++ b/src/test/journal/test_JournalPlayer.cc
@@ -11,6 +11,7 @@
 #include "gtest/gtest.h"
 #include "test/journal/RadosTestFixture.h"
 #include <list>
+#include <boost/scope_exit.hpp>
 
 class TestJournalPlayer : public RadosTestFixture {
 public:
@@ -142,6 +143,11 @@ TEST_F(TestJournalPlayer, Prefetch) {
   ASSERT_EQ(0, init_metadata(metadata));
 
   journal::JournalPlayer *player = create_player(oid, metadata);
+  BOOST_SCOPE_EXIT_ALL( (player) ) {
+    C_SaferCond unwatch_ctx;
+    player->shut_down(&unwatch_ctx);
+    ASSERT_EQ(0, unwatch_ctx.wait());
+  };
 
   ASSERT_EQ(0, write_entry(oid, 0, 234, 122));
   ASSERT_EQ(0, write_entry(oid, 1, 234, 123));
@@ -183,6 +189,11 @@ TEST_F(TestJournalPlayer, PrefetchSkip) {
   ASSERT_EQ(0, init_metadata(metadata));
 
   journal::JournalPlayer *player = create_player(oid, metadata);
+  BOOST_SCOPE_EXIT_ALL( (player) ) {
+    C_SaferCond unwatch_ctx;
+    player->shut_down(&unwatch_ctx);
+    ASSERT_EQ(0, unwatch_ctx.wait());
+  };
 
   ASSERT_EQ(0, write_entry(oid, 0, 234, 122));
   ASSERT_EQ(0, write_entry(oid, 1, 234, 123));
@@ -213,6 +224,11 @@ TEST_F(TestJournalPlayer, PrefetchWithoutCommit) {
   ASSERT_EQ(0, init_metadata(metadata));
 
   journal::JournalPlayer *player = create_player(oid, metadata);
+  BOOST_SCOPE_EXIT_ALL( (player) ) {
+    C_SaferCond unwatch_ctx;
+    player->shut_down(&unwatch_ctx);
+    ASSERT_EQ(0, unwatch_ctx.wait());
+  };
 
   ASSERT_EQ(0, write_entry(oid, 0, 234, 122));
   ASSERT_EQ(0, write_entry(oid, 1, 234, 123));
@@ -248,6 +264,11 @@ TEST_F(TestJournalPlayer, PrefetchMultipleTags) {
   ASSERT_EQ(0, init_metadata(metadata));
 
   journal::JournalPlayer *player = create_player(oid, metadata);
+  BOOST_SCOPE_EXIT_ALL( (player) ) {
+    C_SaferCond unwatch_ctx;
+    player->shut_down(&unwatch_ctx);
+    ASSERT_EQ(0, unwatch_ctx.wait());
+  };
 
   ASSERT_EQ(0, write_entry(oid, 0, 234, 120));
   ASSERT_EQ(0, write_entry(oid, 1, 234, 121));
@@ -282,6 +303,11 @@ TEST_F(TestJournalPlayer, PrefetchCorruptSequence) {
   ASSERT_EQ(0, init_metadata(metadata));
 
   journal::JournalPlayer *player = create_player(oid, metadata);
+  BOOST_SCOPE_EXIT_ALL( (player) ) {
+    C_SaferCond unwatch_ctx;
+    player->shut_down(&unwatch_ctx);
+    ASSERT_EQ(0, unwatch_ctx.wait());
+  };
 
   ASSERT_EQ(0, write_entry(oid, 0, 234, 120));
   ASSERT_EQ(0, write_entry(oid, 1, 234, 121));
@@ -298,6 +324,167 @@ TEST_F(TestJournalPlayer, PrefetchCorruptSequence) {
   ASSERT_EQ(-ENOMSG, m_replay_hander.complete_result);
 }
 
+TEST_F(TestJournalPlayer, PrefetchMissingSequence) {
+  std::string oid = get_temp_oid();
+
+  cls::journal::ObjectSetPosition commit_position;
+
+  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));
+
+  journal::JournalPlayer *player = create_player(oid, metadata);
+  BOOST_SCOPE_EXIT_ALL( (player) ) {
+    C_SaferCond unwatch_ctx;
+    player->shut_down(&unwatch_ctx);
+    ASSERT_EQ(0, unwatch_ctx.wait());
+  };
+
+  ASSERT_EQ(0, metadata->set_active_set(1));
+  ASSERT_EQ(0, write_entry(oid, 0, 2, 852));
+  ASSERT_EQ(0, write_entry(oid, 0, 2, 856));
+  ASSERT_EQ(0, write_entry(oid, 0, 2, 860));
+  ASSERT_EQ(0, write_entry(oid, 1, 2, 853));
+  ASSERT_EQ(0, write_entry(oid, 1, 2, 857));
+  ASSERT_EQ(0, write_entry(oid, 5, 2, 861));
+  ASSERT_EQ(0, write_entry(oid, 2, 2, 854));
+  ASSERT_EQ(0, write_entry(oid, 0, 3, 0));
+  ASSERT_EQ(0, write_entry(oid, 5, 3, 1));
+  ASSERT_EQ(0, write_entry(oid, 2, 3, 2));
+  ASSERT_EQ(0, write_entry(oid, 3, 3, 3));
+
+  player->prefetch();
+  Entries entries;
+  ASSERT_TRUE(wait_for_entries(player, 7, &entries));
+
+  Entries expected_entries = {
+    create_entry(2, 852),
+    create_entry(2, 853),
+    create_entry(2, 854),
+    create_entry(3, 0),
+    create_entry(3, 1),
+    create_entry(3, 2),
+    create_entry(3, 3)};
+  ASSERT_EQ(expected_entries, entries);
+
+  ASSERT_TRUE(wait_for_complete(player));
+  ASSERT_EQ(0, m_replay_hander.complete_result);
+}
+
+TEST_F(TestJournalPlayer, PrefetchLargeMissingSequence) {
+  std::string oid = get_temp_oid();
+
+  cls::journal::ObjectSetPosition commit_position;
+
+  ASSERT_EQ(0, create(oid));
+  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));
+
+  journal::JournalPlayer *player = create_player(oid, metadata);
+  BOOST_SCOPE_EXIT_ALL( (player) ) {
+    C_SaferCond unwatch_ctx;
+    player->shut_down(&unwatch_ctx);
+    ASSERT_EQ(0, unwatch_ctx.wait());
+  };
+
+  ASSERT_EQ(0, metadata->set_active_set(2));
+  ASSERT_EQ(0, write_entry(oid, 0, 0, 0));
+  ASSERT_EQ(0, write_entry(oid, 1, 0, 1));
+  ASSERT_EQ(0, write_entry(oid, 3, 0, 3));
+  ASSERT_EQ(0, write_entry(oid, 4, 1, 0));
+
+  player->prefetch();
+  Entries entries;
+  ASSERT_TRUE(wait_for_entries(player, 3, &entries));
+
+  Entries expected_entries = {
+    create_entry(0, 0),
+    create_entry(0, 1),
+    create_entry(1, 0)};
+  ASSERT_EQ(expected_entries, entries);
+}
+
+TEST_F(TestJournalPlayer, PrefetchBlockedNewTag) {
+  std::string oid = get_temp_oid();
+
+  cls::journal::ObjectSetPosition commit_position;
+
+  ASSERT_EQ(0, create(oid));
+  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));
+
+  journal::JournalPlayer *player = create_player(oid, metadata);
+  BOOST_SCOPE_EXIT_ALL( (player) ) {
+    C_SaferCond unwatch_ctx;
+    player->shut_down(&unwatch_ctx);
+    ASSERT_EQ(0, unwatch_ctx.wait());
+  };
+
+  ASSERT_EQ(0, write_entry(oid, 0, 0, 0));
+  ASSERT_EQ(0, write_entry(oid, 1, 0, 1));
+  ASSERT_EQ(0, write_entry(oid, 0, 0, 2));
+  ASSERT_EQ(0, write_entry(oid, 0, 0, 4));
+  ASSERT_EQ(0, write_entry(oid, 0, 1, 0));
+
+  player->prefetch();
+  Entries entries;
+  ASSERT_TRUE(wait_for_entries(player, 4, &entries));
+
+  Entries expected_entries = {
+    create_entry(0, 0),
+    create_entry(0, 1),
+    create_entry(0, 2),
+    create_entry(1, 0)};
+  ASSERT_EQ(expected_entries, entries);
+}
+
+TEST_F(TestJournalPlayer, PrefetchStaleEntries) {
+  std::string oid = get_temp_oid();
+
+  journal::JournalPlayer::ObjectPositions positions = {
+    cls::journal::ObjectPosition(0, 1, 0) };
+  cls::journal::ObjectSetPosition commit_position(positions);
+
+  ASSERT_EQ(0, create(oid));
+  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));
+
+  journal::JournalPlayer *player = create_player(oid, metadata);
+  BOOST_SCOPE_EXIT_ALL( (player) ) {
+    C_SaferCond unwatch_ctx;
+    player->shut_down(&unwatch_ctx);
+    ASSERT_EQ(0, unwatch_ctx.wait());
+  };
+
+  ASSERT_EQ(0, write_entry(oid, 1, 0, 1));
+  ASSERT_EQ(0, write_entry(oid, 1, 0, 3));
+  ASSERT_EQ(0, write_entry(oid, 0, 1, 0));
+  ASSERT_EQ(0, write_entry(oid, 1, 1, 1));
+
+  player->prefetch();
+  Entries entries;
+  ASSERT_TRUE(wait_for_entries(player, 1, &entries));
+
+  Entries expected_entries = {
+    create_entry(1, 1)};
+  ASSERT_EQ(expected_entries, entries);
+
+  ASSERT_TRUE(wait_for_complete(player));
+  ASSERT_EQ(0, m_replay_hander.complete_result);
+}
+
 TEST_F(TestJournalPlayer, PrefetchUnexpectedTag) {
   std::string oid = get_temp_oid();
 
@@ -311,6 +498,11 @@ TEST_F(TestJournalPlayer, PrefetchUnexpectedTag) {
   ASSERT_EQ(0, init_metadata(metadata));
 
   journal::JournalPlayer *player = create_player(oid, metadata);
+  BOOST_SCOPE_EXIT_ALL( (player) ) {
+    C_SaferCond unwatch_ctx;
+    player->shut_down(&unwatch_ctx);
+    ASSERT_EQ(0, unwatch_ctx.wait());
+  };
 
   ASSERT_EQ(0, write_entry(oid, 0, 234, 120));
   ASSERT_EQ(0, write_entry(oid, 1, 235, 121));
@@ -324,7 +516,7 @@ TEST_F(TestJournalPlayer, PrefetchUnexpectedTag) {
   uint64_t commit_tid;
   ASSERT_FALSE(player->try_pop_front(&entry, &commit_tid));
   ASSERT_TRUE(wait_for_complete(player));
-  ASSERT_EQ(-ENOMSG, m_replay_hander.complete_result);
+  ASSERT_EQ(0, m_replay_hander.complete_result);
 }
 
 TEST_F(TestJournalPlayer, PrefetchAndWatch) {
@@ -343,6 +535,11 @@ TEST_F(TestJournalPlayer, PrefetchAndWatch) {
   ASSERT_EQ(0, init_metadata(metadata));
 
   journal::JournalPlayer *player = create_player(oid, metadata);
+  BOOST_SCOPE_EXIT_ALL( (player) ) {
+    C_SaferCond unwatch_ctx;
+    player->shut_down(&unwatch_ctx);
+    ASSERT_EQ(0, unwatch_ctx.wait());
+  };
 
   ASSERT_EQ(0, write_entry(oid, 0, 234, 122));
 
@@ -374,9 +571,14 @@ TEST_F(TestJournalPlayer, PrefetchSkippedObject) {
 
   journal::JournalMetadataPtr metadata = create_metadata(oid);
   ASSERT_EQ(0, init_metadata(metadata));
-  metadata->set_active_set(2);
+  ASSERT_EQ(0, metadata->set_active_set(2));
 
   journal::JournalPlayer *player = create_player(oid, metadata);
+  BOOST_SCOPE_EXIT_ALL( (player) ) {
+    C_SaferCond unwatch_ctx;
+    player->shut_down(&unwatch_ctx);
+    ASSERT_EQ(0, unwatch_ctx.wait());
+  };
 
   ASSERT_EQ(0, write_entry(oid, 0, 234, 122));
   ASSERT_EQ(0, write_entry(oid, 1, 234, 123));
@@ -420,10 +622,15 @@ TEST_F(TestJournalPlayer, ImbalancedJournal) {
 
   journal::JournalMetadataPtr metadata = create_metadata(oid);
   ASSERT_EQ(0, init_metadata(metadata));
-  metadata->set_active_set(2);
+  ASSERT_EQ(0, metadata->set_active_set(2));
   metadata->set_minimum_set(2);
 
   journal::JournalPlayer *player = create_player(oid, metadata);
+  BOOST_SCOPE_EXIT_ALL( (player) ) {
+    C_SaferCond unwatch_ctx;
+    player->shut_down(&unwatch_ctx);
+    ASSERT_EQ(0, unwatch_ctx.wait());
+  };
 
   ASSERT_EQ(0, write_entry(oid, 8, 300, 0));
   ASSERT_EQ(0, write_entry(oid, 8, 301, 0));
@@ -452,3 +659,322 @@ TEST_F(TestJournalPlayer, ImbalancedJournal) {
   ASSERT_TRUE(metadata->get_last_allocated_entry_tid(301, &last_tid));
   ASSERT_EQ(3U, last_tid);
 }
+
+TEST_F(TestJournalPlayer, LiveReplayLaggyAppend) {
+  std::string oid = get_temp_oid();
+
+  cls::journal::ObjectSetPosition commit_position;
+
+  ASSERT_EQ(0, create(oid));
+  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));
+
+  journal::JournalPlayer *player = create_player(oid, metadata);
+  BOOST_SCOPE_EXIT_ALL( (player) ) {
+    C_SaferCond unwatch_ctx;
+    player->shut_down(&unwatch_ctx);
+    ASSERT_EQ(0, unwatch_ctx.wait());
+  };
+
+  ASSERT_EQ(0, write_entry(oid, 0, 0, 0));
+  ASSERT_EQ(0, write_entry(oid, 1, 0, 1));
+  ASSERT_EQ(0, write_entry(oid, 0, 0, 2));
+  ASSERT_EQ(0, write_entry(oid, 0, 0, 4));
+  ASSERT_EQ(0, write_entry(oid, 3, 0, 5)); // laggy entry 0/3 in object 1
+  player->prefetch_and_watch(0.25);
+
+  Entries entries;
+  ASSERT_TRUE(wait_for_entries(player, 3, &entries));
+
+  Entries expected_entries = {
+    create_entry(0, 0),
+    create_entry(0, 1),
+    create_entry(0, 2)};
+  ASSERT_EQ(expected_entries, entries);
+
+  journal::Entry entry;
+  uint64_t commit_tid;
+  ASSERT_FALSE(player->try_pop_front(&entry, &commit_tid));
+
+  ASSERT_EQ(0, write_entry(oid, 1, 0, 3));
+  ASSERT_EQ(0, metadata->set_active_set(1));
+  ASSERT_TRUE(wait_for_entries(player, 3, &entries));
+
+  expected_entries = {
+    create_entry(0, 3),
+    create_entry(0, 4),
+    create_entry(0, 5)};
+  ASSERT_EQ(expected_entries, entries);
+}
+
+TEST_F(TestJournalPlayer, LiveReplayMissingSequence) {
+  std::string oid = get_temp_oid();
+
+  cls::journal::ObjectSetPosition commit_position;
+
+  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));
+
+  journal::JournalPlayer *player = create_player(oid, metadata);
+  BOOST_SCOPE_EXIT_ALL( (player) ) {
+    C_SaferCond unwatch_ctx;
+    player->shut_down(&unwatch_ctx);
+    ASSERT_EQ(0, unwatch_ctx.wait());
+  };
+
+  ASSERT_EQ(0, write_entry(oid, 0, 2, 852));
+  ASSERT_EQ(0, write_entry(oid, 0, 2, 856));
+  ASSERT_EQ(0, write_entry(oid, 0, 2, 860));
+  ASSERT_EQ(0, write_entry(oid, 1, 2, 853));
+  ASSERT_EQ(0, write_entry(oid, 1, 2, 857));
+  ASSERT_EQ(0, write_entry(oid, 2, 2, 854));
+  ASSERT_EQ(0, write_entry(oid, 0, 2, 856));
+  player->prefetch_and_watch(0.25);
+
+  Entries entries;
+  ASSERT_TRUE(wait_for_entries(player, 3, &entries));
+
+  Entries expected_entries = {
+    create_entry(2, 852),
+    create_entry(2, 853),
+    create_entry(2, 854)};
+  ASSERT_EQ(expected_entries, entries);
+
+  journal::Entry entry;
+  uint64_t commit_tid;
+  ASSERT_FALSE(player->try_pop_front(&entry, &commit_tid));
+
+  ASSERT_EQ(0, write_entry(oid, 3, 3, 3));
+  ASSERT_EQ(0, write_entry(oid, 2, 3, 2));
+  ASSERT_EQ(0, write_entry(oid, 1, 3, 1));
+  ASSERT_EQ(0, write_entry(oid, 0, 3, 0));
+  ASSERT_TRUE(wait_for_entries(player, 4, &entries));
+
+  expected_entries = {
+    create_entry(3, 0),
+    create_entry(3, 1),
+    create_entry(3, 2),
+    create_entry(3, 3)};
+  ASSERT_EQ(expected_entries, entries);
+}
+
+TEST_F(TestJournalPlayer, LiveReplayLargeMissingSequence) {
+  std::string oid = get_temp_oid();
+
+  cls::journal::ObjectSetPosition commit_position;
+
+  ASSERT_EQ(0, create(oid));
+  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));
+
+  journal::JournalPlayer *player = create_player(oid, metadata);
+  BOOST_SCOPE_EXIT_ALL( (player) ) {
+    C_SaferCond unwatch_ctx;
+    player->shut_down(&unwatch_ctx);
+    ASSERT_EQ(0, unwatch_ctx.wait());
+  };
+
+  ASSERT_EQ(0, metadata->set_active_set(2));
+  ASSERT_EQ(0, write_entry(oid, 0, 0, 0));
+  ASSERT_EQ(0, write_entry(oid, 1, 0, 1));
+  ASSERT_EQ(0, write_entry(oid, 3, 0, 3));
+  ASSERT_EQ(0, write_entry(oid, 4, 1, 0));
+  player->prefetch_and_watch(0.25);
+
+  Entries entries;
+  ASSERT_TRUE(wait_for_entries(player, 3, &entries));
+
+  Entries expected_entries = {
+    create_entry(0, 0),
+    create_entry(0, 1),
+    create_entry(1, 0)};
+  ASSERT_EQ(expected_entries, entries);
+}
+
+TEST_F(TestJournalPlayer, LiveReplayBlockedNewTag) {
+  std::string oid = get_temp_oid();
+
+  cls::journal::ObjectSetPosition commit_position;
+
+  ASSERT_EQ(0, create(oid));
+  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));
+
+  journal::JournalPlayer *player = create_player(oid, metadata);
+  BOOST_SCOPE_EXIT_ALL( (player) ) {
+    C_SaferCond unwatch_ctx;
+    player->shut_down(&unwatch_ctx);
+    ASSERT_EQ(0, unwatch_ctx.wait());
+  };
+
+  C_SaferCond ctx1;
+  cls::journal::Tag tag1;
+  metadata->allocate_tag(cls::journal::Tag::TAG_CLASS_NEW, {}, &tag1, &ctx1);
+  ASSERT_EQ(0, ctx1.wait());
+
+  ASSERT_EQ(0, metadata->set_active_set(0));
+  ASSERT_EQ(0, write_entry(oid, 0, tag1.tid, 0));
+  ASSERT_EQ(0, write_entry(oid, 1, tag1.tid, 1));
+  ASSERT_EQ(0, write_entry(oid, 0, tag1.tid, 2));
+  ASSERT_EQ(0, write_entry(oid, 0, tag1.tid, 4));
+  player->prefetch_and_watch(0.25);
+
+  Entries entries;
+  ASSERT_TRUE(wait_for_entries(player, 3, &entries));
+
+  Entries expected_entries = {
+    create_entry(tag1.tid, 0),
+    create_entry(tag1.tid, 1),
+    create_entry(tag1.tid, 2)};
+  ASSERT_EQ(expected_entries, entries);
+
+  journal::Entry entry;
+  uint64_t commit_tid;
+  ASSERT_FALSE(player->try_pop_front(&entry, &commit_tid));
+
+  C_SaferCond ctx2;
+  cls::journal::Tag tag2;
+  metadata->allocate_tag(tag1.tag_class, {}, &tag2, &ctx2);
+  ASSERT_EQ(0, ctx2.wait());
+
+  ASSERT_EQ(0, write_entry(oid, 0, tag2.tid, 0));
+  ASSERT_TRUE(wait_for_entries(player, 1, &entries));
+
+  expected_entries = {
+    create_entry(tag2.tid, 0)};
+  ASSERT_EQ(expected_entries, entries);
+}
+
+TEST_F(TestJournalPlayer, LiveReplayStaleEntries) {
+  std::string oid = get_temp_oid();
+
+  journal::JournalPlayer::ObjectPositions positions = {
+    cls::journal::ObjectPosition(0, 1, 0) };
+  cls::journal::ObjectSetPosition commit_position(positions);
+
+  ASSERT_EQ(0, create(oid));
+  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));
+
+  journal::JournalPlayer *player = create_player(oid, metadata);
+  BOOST_SCOPE_EXIT_ALL( (player) ) {
+    C_SaferCond unwatch_ctx;
+    player->shut_down(&unwatch_ctx);
+    ASSERT_EQ(0, unwatch_ctx.wait());
+  };
+
+  ASSERT_EQ(0, write_entry(oid, 1, 0, 1));
+  ASSERT_EQ(0, write_entry(oid, 1, 0, 3));
+  ASSERT_EQ(0, write_entry(oid, 0, 1, 0));
+  ASSERT_EQ(0, write_entry(oid, 1, 1, 1));
+  player->prefetch_and_watch(0.25);
+
+  Entries entries;
+  ASSERT_TRUE(wait_for_entries(player, 1, &entries));
+
+  Entries expected_entries = {
+    create_entry(1, 1)};
+  ASSERT_EQ(expected_entries, entries);
+}
+
+TEST_F(TestJournalPlayer, LiveReplayRefetchRemoveEmpty) {
+  std::string oid = get_temp_oid();
+
+  journal::JournalPlayer::ObjectPositions positions = {
+    cls::journal::ObjectPosition(1, 0, 1),
+    cls::journal::ObjectPosition(0, 0, 0)};
+  cls::journal::ObjectSetPosition commit_position(positions);
+
+  ASSERT_EQ(0, create(oid));
+  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));
+
+  journal::JournalPlayer *player = create_player(oid, metadata);
+  BOOST_SCOPE_EXIT_ALL( (player) ) {
+    C_SaferCond unwatch_ctx;
+    player->shut_down(&unwatch_ctx);
+    ASSERT_EQ(0, unwatch_ctx.wait());
+  };
+
+  ASSERT_EQ(0, metadata->set_active_set(1));
+  ASSERT_EQ(0, write_entry(oid, 0, 0, 0));
+  ASSERT_EQ(0, write_entry(oid, 1, 0, 1));
+  ASSERT_EQ(0, write_entry(oid, 3, 0, 3));
+  ASSERT_EQ(0, write_entry(oid, 2, 1, 0));
+  player->prefetch_and_watch(0.25);
+
+  Entries entries;
+  ASSERT_TRUE(wait_for_entries(player, 1, &entries));
+
+  Entries expected_entries = {
+    create_entry(1, 0)};
+  ASSERT_EQ(expected_entries, entries);
+
+  // should remove player for offset 3 after refetching
+  ASSERT_EQ(0, metadata->set_active_set(3));
+  ASSERT_EQ(0, write_entry(oid, 7, 1, 1));
+
+  ASSERT_TRUE(wait_for_entries(player, 1, &entries));
+
+  expected_entries = {
+    create_entry(1, 1)};
+  ASSERT_EQ(expected_entries, entries);
+}
+
+TEST_F(TestJournalPlayer, PrefechShutDown) {
+  std::string oid = get_temp_oid();
+
+  ASSERT_EQ(0, create(oid));
+  ASSERT_EQ(0, client_register(oid));
+  ASSERT_EQ(0, client_commit(oid, {}));
+
+  journal::JournalMetadataPtr metadata = create_metadata(oid);
+  ASSERT_EQ(0, init_metadata(metadata));
+
+  journal::JournalPlayer *player = create_player(oid, metadata);
+  BOOST_SCOPE_EXIT_ALL( (player) ) {
+    C_SaferCond unwatch_ctx;
+    player->shut_down(&unwatch_ctx);
+    ASSERT_EQ(0, unwatch_ctx.wait());
+  };
+  player->prefetch();
+}
+
+TEST_F(TestJournalPlayer, LiveReplayShutDown) {
+  std::string oid = get_temp_oid();
+
+  ASSERT_EQ(0, create(oid));
+  ASSERT_EQ(0, client_register(oid));
+  ASSERT_EQ(0, client_commit(oid, {}));
+
+  journal::JournalMetadataPtr metadata = create_metadata(oid);
+  ASSERT_EQ(0, init_metadata(metadata));
+
+  journal::JournalPlayer *player = create_player(oid, metadata);
+  BOOST_SCOPE_EXIT_ALL( (player) ) {
+    C_SaferCond unwatch_ctx;
+    player->shut_down(&unwatch_ctx);
+    ASSERT_EQ(0, unwatch_ctx.wait());
+  };
+  player->prefetch_and_watch(0.25);
+}
+
diff --git a/src/test/journal/test_JournalRecorder.cc b/src/test/journal/test_JournalRecorder.cc
index 149e63b..c06ea6d 100644
--- a/src/test/journal/test_JournalRecorder.cc
+++ b/src/test/journal/test_JournalRecorder.cc
@@ -139,3 +139,38 @@ TEST_F(TestJournalRecorder, Flush) {
   ASSERT_TRUE(future2.is_complete());
 }
 
+TEST_F(TestJournalRecorder, OverflowCommitObjectNumber) {
+  std::string oid = get_temp_oid();
+  ASSERT_EQ(0, create(oid, 12, 2));
+  ASSERT_EQ(0, client_register(oid));
+
+  journal::JournalMetadataPtr metadata = create_metadata(oid);
+  ASSERT_EQ(0, init_metadata(metadata));
+  ASSERT_EQ(0U, metadata->get_active_set());
+
+  journal::JournalRecorder *recorder = create_recorder(oid, metadata);
+
+  recorder->append(123, create_payload(std::string(metadata->get_object_size() -
+                                                   journal::Entry::get_fixed_size(), '1')));
+  journal::Future future2 = recorder->append(124, create_payload(std::string(1, '2')));
+
+  C_SaferCond cond;
+  future2.flush(&cond);
+  ASSERT_EQ(0, cond.wait());
+
+  ASSERT_EQ(1U, metadata->get_active_set());
+
+  uint64_t object_num;
+  uint64_t tag_tid;
+  uint64_t entry_tid;
+  metadata->get_commit_entry(1, &object_num, &tag_tid, &entry_tid);
+  ASSERT_EQ(0U, object_num);
+  ASSERT_EQ(123U, tag_tid);
+  ASSERT_EQ(0U, entry_tid);
+
+  metadata->get_commit_entry(2, &object_num, &tag_tid, &entry_tid);
+  ASSERT_EQ(2U, object_num);
+  ASSERT_EQ(124U, tag_tid);
+  ASSERT_EQ(0U, entry_tid);
+}
+
diff --git a/src/test/journal/test_JournalTrimmer.cc b/src/test/journal/test_JournalTrimmer.cc
index 9a9291f..2849019 100644
--- a/src/test/journal/test_JournalTrimmer.cc
+++ b/src/test/journal/test_JournalTrimmer.cc
@@ -20,6 +20,9 @@ public:
 
     for (std::list<journal::JournalTrimmer*>::iterator it = m_trimmers.begin();
          it != m_trimmers.end(); ++it) {
+      C_SaferCond ctx;
+      (*it)->shut_down(&ctx);
+      ASSERT_EQ(0, ctx.wait());
       delete *it;
     }
     RadosTestFixture::TearDown();
@@ -72,7 +75,7 @@ TEST_F(TestJournalTrimmer, Committed) {
   ASSERT_EQ(0, init_metadata(metadata));
   ASSERT_TRUE(wait_for_update(metadata));
 
-  metadata->set_active_set(10);
+  ASSERT_EQ(0, metadata->set_active_set(10));
   ASSERT_TRUE(wait_for_update(metadata));
 
   uint64_t commit_tid1;
@@ -115,7 +118,7 @@ TEST_F(TestJournalTrimmer, CommittedWithOtherClient) {
   ASSERT_EQ(0, init_metadata(metadata));
   ASSERT_TRUE(wait_for_update(metadata));
 
-  metadata->set_active_set(10);
+  ASSERT_EQ(0, metadata->set_active_set(10));
   ASSERT_TRUE(wait_for_update(metadata));
 
   uint64_t commit_tid1;
@@ -150,7 +153,7 @@ TEST_F(TestJournalTrimmer, RemoveObjects) {
   ASSERT_EQ(0, init_metadata(metadata));
   ASSERT_TRUE(wait_for_update(metadata));
 
-  metadata->set_active_set(10);
+  ASSERT_EQ(0, metadata->set_active_set(10));
   ASSERT_TRUE(wait_for_update(metadata));
 
   ASSERT_EQ(0, append(oid + ".0", create_payload("payload")));
diff --git a/src/test/journal/test_Journaler.cc b/src/test/journal/test_Journaler.cc
index df029a5..4a4ecba 100644
--- a/src/test/journal/test_Journaler.cc
+++ b/src/test/journal/test_Journaler.cc
@@ -39,6 +39,12 @@ public:
     return cond.wait();
   }
 
+  int shut_down_journaler() {
+    C_SaferCond ctx;
+    m_journaler->shut_down(&ctx);
+    return ctx.wait();
+  }
+
   int register_client(const std::string &client_id, const std::string &desc) {
     journal::Journaler journaler(m_work_queue, m_timer, &m_timer_lock,
                                  m_ioctx, m_journal_id, client_id, 5);
@@ -95,10 +101,12 @@ TEST_F(TestJournaler, Init) {
   ASSERT_EQ(0, create_journal(12, 8));
   ASSERT_EQ(0, register_client(CLIENT_ID, "foo"));
   ASSERT_EQ(0, init_journaler());
+  ASSERT_EQ(0, shut_down_journaler());
 }
 
 TEST_F(TestJournaler, InitDNE) {
   ASSERT_EQ(-ENOENT, init_journaler());
+  ASSERT_EQ(0, shut_down_journaler());
 }
 
 TEST_F(TestJournaler, RegisterClientDuplicate) {
diff --git a/src/test/journal/test_ObjectPlayer.cc b/src/test/journal/test_ObjectPlayer.cc
index 6103ee6..67c35a1 100644
--- a/src/test/journal/test_ObjectPlayer.cc
+++ b/src/test/journal/test_ObjectPlayer.cc
@@ -262,14 +262,11 @@ TEST_F(TestObjectPlayer, Unwatch) {
   std::string oid = get_temp_oid();
   journal::ObjectPlayerPtr object = create_object(oid, 14);
 
-  Mutex mutex("lock");
-  Cond cond;
-  bool done = false;
-  int rval = 0;
-  C_SafeCond *ctx = new C_SafeCond(&mutex, &cond, &done, &rval);
-  object->watch(ctx, 600);
+  C_SaferCond watch_ctx;
+  object->watch(&watch_ctx, 600);
 
   usleep(200000);
-  ASSERT_FALSE(done);
+
   object->unwatch();
+  ASSERT_EQ(-ECANCELED, watch_ctx.wait());
 }
diff --git a/src/test/journal/test_ObjectRecorder.cc b/src/test/journal/test_ObjectRecorder.cc
index f26e526..de82d06 100644
--- a/src/test/journal/test_ObjectRecorder.cc
+++ b/src/test/journal/test_ObjectRecorder.cc
@@ -19,13 +19,20 @@ public:
   {
   }
 
-  struct OverflowHandler : public journal::ObjectRecorder::OverflowHandler {
+  struct Handler : public journal::ObjectRecorder::Handler {
     Mutex lock;
     Cond cond;
-    uint32_t overflows;
+    bool is_closed = false;
+    uint32_t overflows = 0;
 
-    OverflowHandler() : lock("lock"), overflows(0) {}
+    Handler() : lock("lock") {
+    }
 
+    virtual void closed(journal::ObjectRecorder *object_recorder) {
+      Mutex::Locker locker(lock);
+      is_closed = true;
+      cond.Signal();
+    }
     virtual void overflow(journal::ObjectRecorder *object_recorder) {
       Mutex::Locker locker(lock);
       journal::AppendBuffers append_buffers;
@@ -43,7 +50,7 @@ public:
   uint32_t m_flush_interval;
   uint64_t m_flush_bytes;
   double m_flush_age;
-  OverflowHandler m_overflow_handler;
+  Handler m_handler;
 
   void TearDown() {
     for (ObjectRecorders::iterator it = m_object_recorders.begin();
@@ -81,7 +88,7 @@ public:
   journal::ObjectRecorderPtr create_object(const std::string &oid,
                                            uint8_t order) {
     journal::ObjectRecorderPtr object(new journal::ObjectRecorder(
-      m_ioctx, oid, 0, *m_timer, m_timer_lock, &m_overflow_handler, order,
+      m_ioctx, oid, 0, *m_timer, m_timer_lock, &m_handler, order,
       m_flush_interval, m_flush_bytes, m_flush_age));
     m_object_recorders.push_back(object);
     return object;
@@ -301,6 +308,40 @@ TEST_F(TestObjectRecorder, FlushDetachedFuture) {
   ASSERT_EQ(0, cond.wait());
 }
 
+TEST_F(TestObjectRecorder, Close) {
+  std::string oid = get_temp_oid();
+  ASSERT_EQ(0, create(oid));
+  ASSERT_EQ(0, client_register(oid));
+  journal::JournalMetadataPtr metadata = create_metadata(oid);
+  ASSERT_EQ(0, init_metadata(metadata));
+
+  set_flush_interval(2);
+  journal::ObjectRecorderPtr object = create_object(oid, 24);
+
+  journal::AppendBuffer append_buffer1 = create_append_buffer(234, 123,
+                                                              "payload");
+  journal::AppendBuffers append_buffers;
+  append_buffers = {append_buffer1};
+  ASSERT_FALSE(object->append(append_buffers));
+  ASSERT_EQ(1U, object->get_pending_appends());
+
+  ASSERT_FALSE(object->close());
+
+  {
+    Mutex::Locker locker(m_handler.lock);
+    while (!m_handler.is_closed) {
+      if (m_handler.cond.WaitInterval(
+            reinterpret_cast<CephContext*>(m_ioctx.cct()),
+            m_handler.lock, utime_t(10, 0)) != 0) {
+        break;
+      }
+    }
+  }
+
+  ASSERT_TRUE(m_handler.is_closed);
+  ASSERT_EQ(0U, object->get_pending_appends());
+}
+
 TEST_F(TestObjectRecorder, Overflow) {
   std::string oid = get_temp_oid();
   ASSERT_EQ(0, create(oid));
@@ -334,15 +375,15 @@ TEST_F(TestObjectRecorder, Overflow) {
 
   bool overflowed = false;
   {
-    Mutex::Locker locker(m_overflow_handler.lock);
-    while (m_overflow_handler.overflows == 0) {
-      if (m_overflow_handler.cond.WaitInterval(
+    Mutex::Locker locker(m_handler.lock);
+    while (m_handler.overflows == 0) {
+      if (m_handler.cond.WaitInterval(
             reinterpret_cast<CephContext*>(m_ioctx.cct()),
-            m_overflow_handler.lock, utime_t(10, 0)) != 0) {
+            m_handler.lock, utime_t(10, 0)) != 0) {
         break;
       }
     }
-    if (m_overflow_handler.overflows != 0) {
+    if (m_handler.overflows != 0) {
       overflowed = true;
     }
   }
diff --git a/src/test/libcephfs/test.cc b/src/test/libcephfs/test.cc
index 06061a6..b0314b6 100644
--- a/src/test/libcephfs/test.cc
+++ b/src/test/libcephfs/test.cc
@@ -27,6 +27,9 @@
 #include <limits.h>
 #endif
 
+#include <map>
+#include <vector>
+
 TEST(LibCephFS, OpenEmptyComponent) {
 
   pid_t mypid = getpid();
@@ -304,11 +307,38 @@ TEST(LibCephFS, DirLs) {
   ASSERT_TRUE(result != NULL);
   ASSERT_STREQ(result->d_name, "..");
 
-  for(i = 0; i < r; ++i)
-    ASSERT_TRUE(ceph_readdir(cmount, ls_dir) != NULL);
+  std::vector<std::string> entries;
+  std::map<std::string, int64_t> offset_map;
+  int64_t offset = ceph_telldir(cmount, ls_dir);
+  for(i = 0; i < r; ++i) {
+    result = ceph_readdir(cmount, ls_dir);
+    ASSERT_TRUE(result != NULL);
+    entries.push_back(result->d_name);
+    offset_map[result->d_name] = offset;
+    offset = ceph_telldir(cmount, ls_dir);
+  }
+
+  ASSERT_TRUE(ceph_readdir(cmount, ls_dir) == NULL);
+  offset = ceph_telldir(cmount, ls_dir);
+
+  ASSERT_EQ(offset_map.size(), entries.size());
+  for(i = 0; i < r; ++i) {
+    sprintf(bazstr, "dirf%d", i);
+    ASSERT_TRUE(offset_map.count(bazstr) == 1);
+  }
 
+  // test seekdir
+  ceph_seekdir(cmount, ls_dir, offset);
   ASSERT_TRUE(ceph_readdir(cmount, ls_dir) == NULL);
 
+  for (auto p = offset_map.begin(); p != offset_map.end(); ++p) {
+    ceph_seekdir(cmount, ls_dir, p->second);
+    result = ceph_readdir(cmount, ls_dir);
+    ASSERT_TRUE(result != NULL);
+    std::string d_name(result->d_name);
+    ASSERT_EQ(p->first, d_name);
+  }
+
   // test rewinddir
   ceph_rewinddir(cmount, ls_dir);
 
@@ -334,7 +364,7 @@ TEST(LibCephFS, DirLs) {
   getdents_entries = (struct dirent *)malloc(r * sizeof(*getdents_entries));
 
   int count = 0;
-  std::set<std::string> found;
+  std::vector<std::string> found;
   while (true) {
     int len = ceph_getdents(cmount, ls_dir, (char *)getdents_entries, r * sizeof(*getdents_entries));
     if (len == 0)
@@ -353,10 +383,10 @@ TEST(LibCephFS, DirLs) {
     count += n;
     for(; j < n; ++i, ++j) {
       const char *name = getdents_entries[j].d_name;
-      found.insert(name);
+      found.push_back(name);
     }
   }
-  ASSERT_EQ(found.size(), (unsigned)r);
+  ASSERT_EQ(found, entries);
   free(getdents_entries);
 
   // test readdir_r
@@ -376,9 +406,9 @@ TEST(LibCephFS, DirLs) {
     if (len == 0)
       break;
     ASSERT_EQ(len, 1);
-    found.insert(rdent.d_name);
+    found.push_back(rdent.d_name);
   }
-  ASSERT_EQ(found.size(), (unsigned)r);
+  ASSERT_EQ(found, entries);
 
   // test readdirplus
   ceph_rewinddir(cmount, ls_dir);
@@ -400,14 +430,14 @@ TEST(LibCephFS, DirLs) {
       break;
     ASSERT_EQ(len, 1);
     const char *name = rdent.d_name;
-    found.insert(name);
+    found.push_back(name);
     int size;
     sscanf(name, "dirf%d", &size);
     ASSERT_EQ(st.st_size, size);
     ASSERT_EQ(st.st_ino, rdent.d_ino);
     //ASSERT_EQ(st.st_mode, (mode_t)0666);
   }
-  ASSERT_EQ(found.size(), (unsigned)r);
+  ASSERT_EQ(found, entries);
 
   ASSERT_EQ(ceph_closedir(cmount, ls_dir), 0);
 
diff --git a/src/test/librbd/exclusive_lock/test_mock_AcquireRequest.cc b/src/test/librbd/exclusive_lock/test_mock_AcquireRequest.cc
index e4ba0ec..c98d694 100644
--- a/src/test/librbd/exclusive_lock/test_mock_AcquireRequest.cc
+++ b/src/test/librbd/exclusive_lock/test_mock_AcquireRequest.cc
@@ -4,6 +4,7 @@
 #include "test/librbd/test_mock_fixture.h"
 #include "test/librbd/test_support.h"
 #include "test/librbd/mock/MockImageCtx.h"
+#include "test/librbd/mock/MockImageState.h"
 #include "test/librbd/mock/MockJournal.h"
 #include "test/librbd/mock/MockJournalPolicy.h"
 #include "test/librbd/mock/MockObjectMap.h"
@@ -58,6 +59,16 @@ public:
                   .WillOnce(Return(r));
   }
 
+  void expect_is_refresh_required(MockImageCtx &mock_image_ctx, bool required) {
+    EXPECT_CALL(*mock_image_ctx.state, is_refresh_required())
+      .WillOnce(Return(required));
+  }
+
+  void expect_refresh(MockImageCtx &mock_image_ctx, int r) {
+    EXPECT_CALL(*mock_image_ctx.state, acquire_lock_refresh(_))
+                  .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue));
+  }
+
   void expect_create_object_map(MockImageCtx &mock_image_ctx,
                                 MockObjectMap *mock_object_map) {
     EXPECT_CALL(mock_image_ctx, create_object_map(_))
@@ -188,6 +199,7 @@ TEST_F(TestMockExclusiveLockAcquireRequest, Success) {
   InSequence seq;
   expect_flush_notifies(mock_image_ctx);
   expect_lock(mock_image_ctx, 0);
+  expect_is_refresh_required(mock_image_ctx, false);
 
   MockObjectMap mock_object_map;
   expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, true);
@@ -212,6 +224,35 @@ TEST_F(TestMockExclusiveLockAcquireRequest, Success) {
   ASSERT_EQ(0, ctx.wait());
 }
 
+TEST_F(TestMockExclusiveLockAcquireRequest, SuccessRefresh) {
+  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);
+  expect_is_refresh_required(mock_image_ctx, true);
+  expect_refresh(mock_image_ctx, 0);
+
+  MockObjectMap mock_object_map;
+  expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, false);
+  expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, false);
+
+  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());
+}
+
 TEST_F(TestMockExclusiveLockAcquireRequest, SuccessJournalDisabled) {
   REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
 
@@ -224,6 +265,7 @@ TEST_F(TestMockExclusiveLockAcquireRequest, SuccessJournalDisabled) {
   InSequence seq;
   expect_flush_notifies(mock_image_ctx);
   expect_lock(mock_image_ctx, 0);
+  expect_is_refresh_required(mock_image_ctx, false);
 
   MockObjectMap mock_object_map;
   expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, true);
@@ -254,6 +296,7 @@ TEST_F(TestMockExclusiveLockAcquireRequest, SuccessObjectMapDisabled) {
   InSequence seq;
   expect_flush_notifies(mock_image_ctx);
   expect_lock(mock_image_ctx, 0);
+  expect_is_refresh_required(mock_image_ctx, false);
 
   expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, false);
 
@@ -275,6 +318,31 @@ TEST_F(TestMockExclusiveLockAcquireRequest, SuccessObjectMapDisabled) {
   ASSERT_EQ(0, ctx.wait());
 }
 
+TEST_F(TestMockExclusiveLockAcquireRequest, RefreshError) {
+  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);
+  expect_is_refresh_required(mock_image_ctx, true);
+  expect_refresh(mock_image_ctx, -EINVAL);
+  expect_unlock(mock_image_ctx, 0);
+
+  C_SaferCond *acquire_ctx = new C_SaferCond();
+  C_SaferCond ctx;
+  MockAcquireRequest *req = MockAcquireRequest::create(mock_image_ctx,
+                                                       TEST_COOKIE,
+                                                       acquire_ctx, &ctx);
+  req->send();
+  ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
 TEST_F(TestMockExclusiveLockAcquireRequest, JournalError) {
   REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
 
@@ -287,6 +355,7 @@ TEST_F(TestMockExclusiveLockAcquireRequest, JournalError) {
   InSequence seq;
   expect_flush_notifies(mock_image_ctx);
   expect_lock(mock_image_ctx, 0);
+  expect_is_refresh_required(mock_image_ctx, false);
 
   MockObjectMap *mock_object_map = new MockObjectMap();
   expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, true);
@@ -322,6 +391,7 @@ TEST_F(TestMockExclusiveLockAcquireRequest, AllocateJournalTagError) {
   InSequence seq;
   expect_flush_notifies(mock_image_ctx);
   expect_lock(mock_image_ctx, 0);
+  expect_is_refresh_required(mock_image_ctx, false);
 
   MockObjectMap *mock_object_map = new MockObjectMap();
   expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, true);
@@ -665,6 +735,7 @@ TEST_F(TestMockExclusiveLockAcquireRequest, OpenObjectMapError) {
   InSequence seq;
   expect_flush_notifies(mock_image_ctx);
   expect_lock(mock_image_ctx, 0);
+  expect_is_refresh_required(mock_image_ctx, false);
 
   MockObjectMap *mock_object_map = new MockObjectMap();
   expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, true);
diff --git a/src/test/librbd/fsx.cc b/src/test/librbd/fsx.cc
index f0519ec..2a06b34 100644
--- a/src/test/librbd/fsx.cc
+++ b/src/test/librbd/fsx.cc
@@ -54,6 +54,8 @@
 #include "journal/ReplayEntry.h"
 #include "journal/ReplayHandler.h"
 
+#include <boost/scope_exit.hpp>
+
 #define NUMPRINTCOLUMNS 32	/* # columns of data to print on each line */
 
 /*
@@ -394,6 +396,10 @@ int replay_journal(rados_ioctx_t ioctx, const char *image_name,
         journal::Journaler journaler(io_ctx, image_id, JOURNAL_CLIENT_ID, 0);
         C_SaferCond init_ctx;
         journaler.init(&init_ctx);
+        BOOST_SCOPE_EXIT_ALL( (&journaler) ) {
+                journaler.shut_down();
+        };
+
         r = init_ctx.wait();
         if (r < 0) {
                 simple_err("failed to initialize journal", r);
@@ -401,8 +407,13 @@ int replay_journal(rados_ioctx_t ioctx, const char *image_name,
         }
 
         journal::Journaler replay_journaler(io_ctx, replay_image_id, "", 0);
+
         C_SaferCond replay_init_ctx;
         replay_journaler.init(&replay_init_ctx);
+        BOOST_SCOPE_EXIT_ALL( (&replay_journaler) ) {
+                replay_journaler.shut_down();
+        };
+
         r = replay_init_ctx.wait();
         if (r < 0) {
                 simple_err("failed to initialize replay journal", r);
diff --git a/src/test/librbd/image/test_mock_RefreshRequest.cc b/src/test/librbd/image/test_mock_RefreshRequest.cc
index 01a7381..0503491 100644
--- a/src/test/librbd/image/test_mock_RefreshRequest.cc
+++ b/src/test/librbd/image/test_mock_RefreshRequest.cc
@@ -308,7 +308,7 @@ TEST_F(TestMockImageRefreshRequest, SuccessV1) {
   expect_init_layout(mock_image_ctx);
 
   C_SaferCond ctx;
-  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &ctx);
+  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, &ctx);
   req->send();
 
   ASSERT_EQ(0, ctx.wait());
@@ -334,7 +334,7 @@ TEST_F(TestMockImageRefreshRequest, SuccessSnapshotV1) {
   expect_add_snap(mock_image_ctx, "snap", ictx->snap_ids.begin()->second);
 
   C_SaferCond ctx;
-  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &ctx);
+  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, &ctx);
   req->send();
 
   ASSERT_EQ(0, ctx.wait());
@@ -361,7 +361,7 @@ TEST_F(TestMockImageRefreshRequest, SuccessV2) {
   }
 
   C_SaferCond ctx;
-  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &ctx);
+  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, &ctx);
   req->send();
 
   ASSERT_EQ(0, ctx.wait());
@@ -392,7 +392,7 @@ TEST_F(TestMockImageRefreshRequest, SuccessSnapshotV2) {
   expect_add_snap(mock_image_ctx, "snap", ictx->snap_ids.begin()->second);
 
   C_SaferCond ctx;
-  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &ctx);
+  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, &ctx);
   req->send();
 
   ASSERT_EQ(0, ctx.wait());
@@ -425,7 +425,7 @@ TEST_F(TestMockImageRefreshRequest, SuccessSetSnapshotV2) {
   expect_get_snap_id(mock_image_ctx, "snap", ictx->snap_ids.begin()->second);
 
   C_SaferCond ctx;
-  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &ctx);
+  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, &ctx);
   req->send();
 
   ASSERT_EQ(0, ctx.wait());
@@ -475,7 +475,7 @@ TEST_F(TestMockImageRefreshRequest, SuccessChild) {
   expect_refresh_parent_finalize(mock_image_ctx, *mock_refresh_parent_request, 0);
 
   C_SaferCond ctx;
-  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &ctx);
+  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, &ctx);
   req->send();
 
   ASSERT_EQ(0, ctx.wait());
@@ -521,12 +521,46 @@ TEST_F(TestMockImageRefreshRequest, DisableExclusiveLock) {
   expect_shut_down_exclusive_lock(mock_image_ctx, *mock_exclusive_lock, 0);
 
   C_SaferCond ctx;
-  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &ctx);
+  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, &ctx);
   req->send();
 
   ASSERT_EQ(0, ctx.wait());
 }
 
+TEST_F(TestMockImageRefreshRequest, DisableExclusiveLockWhileAcquiringLock) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockRefreshImageCtx mock_image_ctx(*ictx);
+  MockRefreshParentRequest mock_refresh_parent_request;
+
+  MockExclusiveLock mock_exclusive_lock;
+  mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+
+  ASSERT_EQ(0, update_features(ictx,
+                               RBD_FEATURE_EXCLUSIVE_LOCK |
+                               RBD_FEATURE_OBJECT_MAP |
+                               RBD_FEATURE_FAST_DIFF |
+                               RBD_FEATURE_JOURNALING, false));
+
+  expect_op_work_queue(mock_image_ctx);
+  expect_test_features(mock_image_ctx);
+
+  // verify that exclusive lock is properly handled when object map
+  // and journaling were never enabled (or active)
+  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);
+
+  C_SaferCond ctx;
+  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, true, &ctx);
+  req->send();
+
+  ASSERT_EQ(-ERESTART, ctx.wait());
+}
 TEST_F(TestMockImageRefreshRequest, EnableJournalWithExclusiveLock) {
   REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
 
@@ -557,7 +591,7 @@ TEST_F(TestMockImageRefreshRequest, EnableJournalWithExclusiveLock) {
   expect_open_journal(mock_image_ctx, mock_journal, 0);
 
   C_SaferCond ctx;
-  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &ctx);
+  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, &ctx);
   req->send();
 
   ASSERT_EQ(0, ctx.wait());
@@ -591,7 +625,7 @@ TEST_F(TestMockImageRefreshRequest, EnableJournalWithoutExclusiveLock) {
   expect_set_require_lock_on_read(mock_image_ctx);
 
   C_SaferCond ctx;
-  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &ctx);
+  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, &ctx);
   req->send();
 
   ASSERT_EQ(0, ctx.wait());
@@ -633,7 +667,7 @@ TEST_F(TestMockImageRefreshRequest, DisableJournal) {
   expect_unblock_writes(mock_image_ctx);
 
   C_SaferCond ctx;
-  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &ctx);
+  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, &ctx);
   req->send();
 
   ASSERT_EQ(0, ctx.wait());
@@ -667,7 +701,7 @@ TEST_F(TestMockImageRefreshRequest, EnableObjectMapWithExclusiveLock) {
   expect_open_object_map(mock_image_ctx, &mock_object_map, 0);
 
   C_SaferCond ctx;
-  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &ctx);
+  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, &ctx);
   req->send();
 
   ASSERT_EQ(0, ctx.wait());
@@ -698,7 +732,7 @@ TEST_F(TestMockImageRefreshRequest, EnableObjectMapWithoutExclusiveLock) {
   expect_refresh_parent_is_required(mock_refresh_parent_request, false);
 
   C_SaferCond ctx;
-  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &ctx);
+  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, &ctx);
   req->send();
 
   ASSERT_EQ(0, ctx.wait());
@@ -739,7 +773,7 @@ TEST_F(TestMockImageRefreshRequest, DisableObjectMap) {
   expect_close_object_map(mock_image_ctx, *mock_object_map, 0);
 
   C_SaferCond ctx;
-  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &ctx);
+  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, &ctx);
   req->send();
 
   ASSERT_EQ(0, ctx.wait());
@@ -773,7 +807,7 @@ TEST_F(TestMockImageRefreshRequest, OpenObjectMapError) {
   expect_open_object_map(mock_image_ctx, mock_object_map, -EFBIG);
 
   C_SaferCond ctx;
-  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &ctx);
+  MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, &ctx);
   req->send();
 
   ASSERT_EQ(0, ctx.wait());
diff --git a/src/test/librbd/journal/test_Entries.cc b/src/test/librbd/journal/test_Entries.cc
index ec6c689..bd984fd 100644
--- a/src/test/librbd/journal/test_Entries.cc
+++ b/src/test/librbd/journal/test_Entries.cc
@@ -57,6 +57,7 @@ public:
          it != m_journalers.end(); ++it) {
       journal::Journaler *journaler = *it;
       journaler->stop_replay();
+      journaler->shut_down();
       delete journaler;
     }
 
diff --git a/src/test/librbd/journal/test_Replay.cc b/src/test/librbd/journal/test_Replay.cc
index f7e20f7..cbde9ae 100644
--- a/src/test/librbd/journal/test_Replay.cc
+++ b/src/test/librbd/journal/test_Replay.cc
@@ -47,8 +47,8 @@ public:
     librbd::Journal<>::AioObjectRequests requests;
     {
       RWLock::RLocker owner_locker(ictx->owner_lock);
-      ictx->journal->append_io_event(NULL, std::move(event_entry), requests, 0,
-				     0, true);
+      ictx->journal->append_io_event(std::move(event_entry), requests, 0, 0,
+                                     true);
     }
   }
 
diff --git a/src/test/librbd/journal/test_mock_Replay.cc b/src/test/librbd/journal/test_mock_Replay.cc
index f4145e1..43f2909 100644
--- a/src/test/librbd/journal/test_mock_Replay.cc
+++ b/src/test/librbd/journal/test_mock_Replay.cc
@@ -77,11 +77,11 @@ ACTION_P2(NotifyInvoke, lock, cond) {
 }
 
 ACTION_P2(CompleteAioCompletion, r, image_ctx) {
-  CephContext *cct = image_ctx->cct;
-  image_ctx->op_work_queue->queue(new FunctionContext([cct, arg0](int r) {
+  image_ctx->op_work_queue->queue(new FunctionContext([this, arg0](int r) {
       arg0->get();
-      arg0->set_request_count(cct, 1);
-      arg0->complete_request(cct, r);
+      arg0->init_time(image_ctx, librbd::AIO_TYPE_NONE);
+      arg0->set_request_count(1);
+      arg0->complete_request(r);
     }), r);
 }
 
@@ -217,8 +217,9 @@ public:
   void when_complete(MockReplayImageCtx &mock_image_ctx, AioCompletion *aio_comp,
                      int r) {
     aio_comp->get();
-    aio_comp->set_request_count(mock_image_ctx.cct, 1);
-    aio_comp->complete_request(mock_image_ctx.cct, r);
+    aio_comp->init_time(mock_image_ctx.image_ctx, librbd::AIO_TYPE_NONE);
+    aio_comp->set_request_count(1);
+    aio_comp->complete_request(r);
   }
 
   int when_flush(MockJournalReplay &mock_journal_replay) {
@@ -460,7 +461,7 @@ TEST_F(TestMockJournalReplay, Flush) {
   expect_op_work_queue(mock_image_ctx);
 
   InSequence seq;
-  AioCompletion *aio_comp;
+  AioCompletion *aio_comp = nullptr;
   C_SaferCond on_ready;
   C_SaferCond on_safe;
   expect_aio_discard(mock_aio_image_request, &aio_comp, 123, 456);
@@ -614,9 +615,15 @@ TEST_F(TestMockJournalReplay, MissingOpFinishEventCancelOps) {
   when_replay_op_ready(mock_journal_replay, 123, &on_resume);
   ASSERT_EQ(0, on_snap_create_ready.wait());
 
-  ASSERT_EQ(0, when_shut_down(mock_journal_replay, true));
-  ASSERT_EQ(-ERESTART, on_snap_remove_safe.wait());
+  C_SaferCond on_shut_down;
+  mock_journal_replay.shut_down(true, &on_shut_down);
+
+  ASSERT_EQ(-ERESTART, on_resume.wait());
+  on_snap_create_finish->complete(-ERESTART);
   ASSERT_EQ(-ERESTART, on_snap_create_safe.wait());
+
+  ASSERT_EQ(-ERESTART, on_snap_remove_safe.wait());
+  ASSERT_EQ(0, on_shut_down.wait());
 }
 
 TEST_F(TestMockJournalReplay, UnknownOpFinishEvent) {
diff --git a/src/test/librbd/mock/MockImageState.h b/src/test/librbd/mock/MockImageState.h
index 8d2ad11..3ba1ee5 100644
--- a/src/test/librbd/mock/MockImageState.h
+++ b/src/test/librbd/mock/MockImageState.h
@@ -13,11 +13,14 @@ namespace librbd {
 struct MockImageState {
   MOCK_CONST_METHOD0(is_refresh_required, bool());
   MOCK_METHOD1(refresh, void(Context*));
+  MOCK_METHOD1(acquire_lock_refresh, void(Context*));
 
   MOCK_METHOD1(open, void(Context*));
 
   MOCK_METHOD0(close, int());
   MOCK_METHOD1(close, void(Context*));
+
+  MOCK_METHOD2(snap_set, void(const std::string &, Context*));
 };
 
 } // namespace librbd
diff --git a/src/test/librbd/operation/test_mock_SnapshotRemoveRequest.cc b/src/test/librbd/operation/test_mock_SnapshotRemoveRequest.cc
index 4dcf3c8..67a0e6d 100644
--- a/src/test/librbd/operation/test_mock_SnapshotRemoveRequest.cc
+++ b/src/test/librbd/operation/test_mock_SnapshotRemoveRequest.cc
@@ -356,5 +356,36 @@ TEST_F(TestMockOperationSnapshotRemoveRequest, RemoveSnapError) {
   ASSERT_EQ(-ENOENT, cond_ctx.wait());
 }
 
+TEST_F(TestMockOperationSnapshotRemoveRequest, MissingSnap) {
+  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;
+  if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) {
+    mock_image_ctx.object_map = &mock_object_map;
+  }
+
+  expect_op_work_queue(mock_image_ctx);
+
+  ::testing::InSequence seq;
+  uint64_t snap_id = 456;
+
+  C_SaferCond cond_ctx;
+  MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest(
+    mock_image_ctx, &cond_ctx, "snap1", snap_id);
+  {
+    RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+    req->send();
+  }
+  ASSERT_EQ(-ENOENT, cond_ctx.wait());
+}
+
 } // namespace operation
 } // namespace librbd
diff --git a/src/test/librbd/test_librbd.cc b/src/test/librbd/test_librbd.cc
index 52b5d45..901abc8 100644
--- a/src/test/librbd/test_librbd.cc
+++ b/src/test/librbd/test_librbd.cc
@@ -81,22 +81,6 @@ static int get_features(bool *old_format, uint64_t *features)
   return 0;
 }
 
-static int get_image_id(librbd::Image &image, std::string *image_id)
-{
-  librbd::image_info_t info;
-  int r = image.stat(info, sizeof(info));
-  if (r < 0) {
-    return r;
-  }
-
-  char prefix[RBD_MAX_BLOCK_NAME_SIZE + 1];
-  strncpy(prefix, info.block_name_prefix, RBD_MAX_BLOCK_NAME_SIZE);
-  prefix[RBD_MAX_BLOCK_NAME_SIZE] = '\0';
-
-  *image_id = std::string(prefix + strlen(RBD_DATA_PREFIX));
-  return 0;
-}
-
 static int create_image_full(rados_ioctx_t ioctx, const char *name,
 			      uint64_t size, int *order, int old_format,
 			      uint64_t features)
diff --git a/src/test/librbd/test_mirroring.cc b/src/test/librbd/test_mirroring.cc
index ece07b1..758fc45 100644
--- a/src/test/librbd/test_mirroring.cc
+++ b/src/test/librbd/test_mirroring.cc
@@ -22,6 +22,8 @@
 #include "librbd/internal.h"
 #include "librbd/ObjectMap.h"
 #include "librbd/Operations.h"
+#include "librbd/journal/Types.h"
+#include "journal/Journaler.h"
 #include <boost/scope_exit.hpp>
 #include <boost/assign/list_of.hpp>
 #include <utility>
@@ -255,6 +257,30 @@ public:
     ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_DISABLED));
   }
 
+  void setup_mirror_peer(librados::IoCtx &io_ctx, librbd::Image &image) {
+    ASSERT_EQ(0, image.snap_create("sync-point-snap"));
+
+    std::string image_id;
+    ASSERT_EQ(0, get_image_id(image, &image_id));
+
+    librbd::journal::MirrorPeerClientMeta peer_client_meta(
+      "remote-image-id", {{"sync-point-snap", boost::none}}, {});
+    librbd::journal::ClientData client_data(peer_client_meta);
+
+    journal::Journaler journaler(io_ctx, image_id, "peer-client", 5);
+    C_SaferCond init_ctx;
+    journaler.init(&init_ctx);
+    ASSERT_EQ(-ENOENT, init_ctx.wait());
+
+    bufferlist client_data_bl;
+    ::encode(client_data, client_data_bl);
+    ASSERT_EQ(0, journaler.register_client(client_data_bl));
+
+    C_SaferCond shut_down_ctx;
+    journaler.shut_down(&shut_down_ctx);
+    ASSERT_EQ(0, shut_down_ctx.wait());
+  }
+
 };
 
 TEST_F(TestMirroring, EnableImageMirror_In_MirrorModeImage) {
@@ -311,6 +337,77 @@ TEST_F(TestMirroring, DisableImageMirror_In_MirrorModeDisabled) {
       RBD_MIRROR_IMAGE_DISABLED);
 }
 
+TEST_F(TestMirroring, DisableImageMirrorWithPeer) {
+  REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+  ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_IMAGE));
+
+  uint64_t features = RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_JOURNALING;
+  int order = 20;
+  ASSERT_EQ(0, m_rbd.create2(m_ioctx, image_name.c_str(), 4096, features,
+                             &order));
+
+  librbd::Image image;
+  ASSERT_EQ(0, m_rbd.open(m_ioctx, image, image_name.c_str()));
+  ASSERT_EQ(0, image.mirror_image_enable());
+
+  setup_mirror_peer(m_ioctx, image);
+
+  ASSERT_EQ(0, image.mirror_image_disable(false));
+
+  std::vector<librbd::snap_info_t> snaps;
+  ASSERT_EQ(0, image.snap_list(snaps));
+  ASSERT_TRUE(snaps.empty());
+
+  librbd::mirror_image_info_t mirror_image;
+  ASSERT_EQ(0, image.mirror_image_get_info(&mirror_image,
+                                           sizeof(mirror_image)));
+  ASSERT_EQ(RBD_MIRROR_IMAGE_DISABLED, 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));
+}
+
+TEST_F(TestMirroring, DisableJournalingWithPeer) {
+  REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+  ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_POOL));
+
+  uint64_t features = RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_JOURNALING;
+  int order = 20;
+  ASSERT_EQ(0, m_rbd.create2(m_ioctx, image_name.c_str(), 4096, features,
+                             &order));
+
+  librbd::Image image;
+  ASSERT_EQ(0, m_rbd.open(m_ioctx, image, image_name.c_str()));
+
+  setup_mirror_peer(m_ioctx, image);
+
+  ASSERT_EQ(0, image.update_features(RBD_FEATURE_JOURNALING, false));
+
+  std::vector<librbd::snap_info_t> snaps;
+  ASSERT_EQ(0, image.snap_list(snaps));
+  ASSERT_TRUE(snaps.empty());
+
+  librbd::mirror_image_info_t mirror_image;
+  ASSERT_EQ(0, image.mirror_image_get_info(&mirror_image,
+                                           sizeof(mirror_image)));
+  ASSERT_EQ(RBD_MIRROR_IMAGE_DISABLED, 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));
+}
+
 TEST_F(TestMirroring, EnableImageMirror_WithoutJournaling) {
   uint64_t features = 0;
   features |= RBD_FEATURE_OBJECT_MAP;
diff --git a/src/test/librbd/test_mock_Journal.cc b/src/test/librbd/test_mock_Journal.cc
index d8c5451..b3ed6fd 100644
--- a/src/test/librbd/test_mock_Journal.cc
+++ b/src/test/librbd/test_mock_Journal.cc
@@ -9,6 +9,8 @@
 #include "common/Mutex.h"
 #include "cls/journal/cls_journal_types.h"
 #include "journal/Journaler.h"
+#include "librbd/AioCompletion.h"
+#include "librbd/AioObjectRequest.h"
 #include "librbd/Journal.h"
 #include "librbd/Utils.h"
 #include "librbd/journal/Replay.h"
@@ -20,6 +22,8 @@
 #include <list>
 #include <boost/scope_exit.hpp>
 
+#define dout_subsys ceph_subsys_rbd
+
 namespace librbd {
 
 namespace {
@@ -148,6 +152,11 @@ public:
                   .WillOnce(CompleteContext(r, NULL));
   }
 
+  void expect_shut_down_journaler(::journal::MockJournaler &mock_journaler) {
+    EXPECT_CALL(mock_journaler, shut_down(_))
+                  .WillOnce(CompleteContext(0, NULL));
+  }
+
   void expect_get_max_append_size(::journal::MockJournaler &mock_journaler,
                                   uint32_t max_size) {
     EXPECT_CALL(mock_journaler, get_max_append_size())
@@ -193,7 +202,8 @@ public:
   }
 
   void expect_stop_replay(::journal::MockJournaler &mock_journaler) {
-    EXPECT_CALL(mock_journaler, stop_replay());
+    EXPECT_CALL(mock_journaler, stop_replay(_))
+                  .WillOnce(CompleteContext(0, NULL));
   }
 
   void expect_shut_down_replay(MockJournalImageCtx &mock_image_ctx,
@@ -283,15 +293,20 @@ public:
     bl.append_zero(length);
 
     RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
-    return mock_journal.append_write_event(nullptr, 0, length, bl, {}, false);
+    return mock_journal.append_write_event(0, length, bl, {}, false);
   }
 
   uint64_t when_append_io_event(MockJournalImageCtx &mock_image_ctx,
                                 MockJournal &mock_journal,
-                                AioCompletion *aio_comp = nullptr) {
+                                AioObjectRequest *object_request = nullptr) {
     RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+    MockJournal::AioObjectRequests object_requests;
+    if (object_request != nullptr) {
+      object_requests.push_back(object_request);
+    }
     return mock_journal.append_io_event(
-      aio_comp, journal::EventEntry{journal::AioFlushEvent{}}, {}, 0, 0, false);
+      journal::EventEntry{journal::AioFlushEvent{}}, object_requests, 0, 0,
+      false);
   }
 
   void save_commit_context(Context *ctx) {
@@ -310,10 +325,18 @@ public:
     Contexts commit_contexts;
     std::swap(commit_contexts, m_commit_contexts);
 
+    derr << "SHUT DOWN REPLAY START" << dendl;
     for (auto ctx : commit_contexts) {
       mock_image_ctx.image_ctx->op_work_queue->queue(ctx, r);
     }
+
+    on_flush = new FunctionContext([on_flush](int r) {
+        derr << "FLUSH START" << dendl;
+        on_flush->complete(r);
+        derr << "FLUSH FINISH" << dendl;
+      });
     mock_image_ctx.image_ctx->op_work_queue->queue(on_flush, 0);
+    derr << "SHUT DOWN REPLAY FINISH" << dendl;
   }
 
   void open_journal(MockJournalImageCtx &mock_image_ctx,
@@ -400,6 +423,7 @@ TEST_F(TestMockJournal, StateTransitions) {
   ASSERT_EQ(0, when_open(mock_journal));
 
   expect_stop_append(mock_journaler, 0);
+  expect_shut_down_journaler(mock_journaler);
   ASSERT_EQ(0, when_close(mock_journal));
 }
 
@@ -418,6 +442,7 @@ TEST_F(TestMockJournal, InitError) {
   ::journal::MockJournaler mock_journaler;
   expect_construct_journaler(mock_journaler);
   expect_init_journaler(mock_journaler, -EINVAL);
+  expect_shut_down_journaler(mock_journaler);
   ASSERT_EQ(-EINVAL, when_open(mock_journal));
 }
 
@@ -438,6 +463,7 @@ TEST_F(TestMockJournal, GetCachedClientError) {
   expect_init_journaler(mock_journaler, 0);
   expect_get_max_append_size(mock_journaler, 1 << 16);
   expect_get_journaler_cached_client(mock_journaler, -ENOENT);
+  expect_shut_down_journaler(mock_journaler);
   ASSERT_EQ(-ENOENT, when_open(mock_journal));
 }
 
@@ -459,6 +485,7 @@ TEST_F(TestMockJournal, GetTagsError) {
   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);
+  expect_shut_down_journaler(mock_journaler);
   ASSERT_EQ(-EBADMSG, when_open(mock_journal));
 }
 
@@ -488,6 +515,7 @@ TEST_F(TestMockJournal, ReplayCompleteError) {
   MockJournalReplay mock_journal_replay;
   expect_stop_replay(mock_journaler);
   expect_shut_down_replay(mock_image_ctx, mock_journal_replay, 0, true);
+  expect_shut_down_journaler(mock_journaler);
 
   // replay failure should result in replay-restart
   expect_construct_journaler(mock_journaler);
@@ -506,6 +534,7 @@ TEST_F(TestMockJournal, ReplayCompleteError) {
   ASSERT_EQ(0, when_open(mock_journal));
 
   expect_stop_append(mock_journaler, 0);
+  expect_shut_down_journaler(mock_journaler);
   ASSERT_EQ(0, when_close(mock_journal));
 }
 
@@ -540,6 +569,7 @@ TEST_F(TestMockJournal, FlushReplayError) {
   expect_try_pop_front(mock_journaler, false, mock_replay_entry);
   expect_stop_replay(mock_journaler);
   expect_shut_down_replay(mock_image_ctx, mock_journal_replay, -EINVAL);
+  expect_shut_down_journaler(mock_journaler);
 
   // replay flush failure should result in replay-restart
   expect_construct_journaler(mock_journaler);
@@ -558,6 +588,7 @@ TEST_F(TestMockJournal, FlushReplayError) {
   ASSERT_EQ(0, when_open(mock_journal));
 
   expect_stop_append(mock_journaler, 0);
+  expect_shut_down_journaler(mock_journaler);
   ASSERT_EQ(0, when_close(mock_journal));
 }
 
@@ -591,6 +622,7 @@ TEST_F(TestMockJournal, StopError) {
   ASSERT_EQ(0, when_open(mock_journal));
 
   expect_stop_append(mock_journaler, -EINVAL);
+  expect_shut_down_journaler(mock_journaler);
   ASSERT_EQ(-EINVAL, when_close(mock_journal));
 }
 
@@ -631,6 +663,7 @@ TEST_F(TestMockJournal, ReplayOnDiskPreFlushError) {
   expect_try_pop_front(mock_journaler, false, mock_replay_entry);
   expect_stop_replay(mock_journaler);
   expect_shut_down_replay(mock_image_ctx, mock_journal_replay, 0, true);
+  expect_shut_down_journaler(mock_journaler);
 
   // replay write-to-disk failure should result in replay-restart
   expect_construct_journaler(mock_journaler);
@@ -670,6 +703,7 @@ TEST_F(TestMockJournal, ReplayOnDiskPreFlushError) {
   ASSERT_EQ(0, ctx.wait());
 
   expect_stop_append(mock_journaler, 0);
+  expect_shut_down_journaler(mock_journaler);
   ASSERT_EQ(0, when_close(mock_journal));
 }
 
@@ -710,6 +744,7 @@ TEST_F(TestMockJournal, ReplayOnDiskPostFlushError) {
                     InvokeWithoutArgs(this, &TestMockJournal::wake_up)));
 
   // replay write-to-disk failure should result in replay-restart
+  expect_shut_down_journaler(mock_journaler);
   expect_construct_journaler(mock_journaler);
   expect_init_journaler(mock_journaler, 0);
   expect_get_max_append_size(mock_journaler, 1 << 16);
@@ -750,6 +785,7 @@ TEST_F(TestMockJournal, ReplayOnDiskPostFlushError) {
   ASSERT_EQ(0, ctx.wait());
 
   expect_stop_append(mock_journaler, 0);
+  expect_shut_down_journaler(mock_journaler);
   ASSERT_EQ(0, when_close(mock_journal));
 }
 
@@ -793,6 +829,8 @@ TEST_F(TestMockJournal, EventAndIOCommitOrder) {
   on_journal_safe2->complete(0);
   ictx->op_work_queue->drain();
   ASSERT_EQ(0, event_ctx.wait());
+
+  expect_shut_down_journaler(mock_journaler);
 }
 
 TEST_F(TestMockJournal, AppendWriteEvent) {
@@ -829,6 +867,8 @@ TEST_F(TestMockJournal, AppendWriteEvent) {
   expect_future_committed(mock_journaler);
   mock_journal.commit_io_event(1U, 0);
   ictx->op_work_queue->drain();
+
+  expect_shut_down_journaler(mock_journaler);
 }
 
 TEST_F(TestMockJournal, EventCommitError) {
@@ -845,27 +885,29 @@ TEST_F(TestMockJournal, EventCommitError) {
     close_journal(mock_journal, mock_journaler);
   };
 
-  AioCompletion *comp = new AioCompletion();
-  comp->get();
+  C_SaferCond object_request_ctx;
+  AioObjectRemove *object_request = new AioObjectRemove(
+    ictx, "oid", 0, {}, &object_request_ctx);
 
   ::journal::MockFuture mock_future;
   Context *on_journal_safe;
   expect_append_journaler(mock_journaler);
   expect_wait_future(mock_future, &on_journal_safe);
-  ASSERT_EQ(1U, when_append_io_event(mock_image_ctx, mock_journal, comp));
+  ASSERT_EQ(1U, when_append_io_event(mock_image_ctx, mock_journal,
+                                     object_request));
 
   // commit the event in the journal w/o waiting writeback
   expect_future_committed(mock_journaler);
   on_journal_safe->complete(-EINVAL);
-  ASSERT_EQ(0, comp->wait_for_complete());
-  ASSERT_EQ(-EINVAL, comp->get_return_value());
-  comp->put();
+  ASSERT_EQ(-EINVAL, object_request_ctx.wait());
 
   // cache should receive the error after attempting writeback
   expect_future_is_valid(mock_future);
   C_SaferCond flush_ctx;
   mock_journal.flush_event(1U, &flush_ctx);
   ASSERT_EQ(-EINVAL, flush_ctx.wait());
+
+  expect_shut_down_journaler(mock_journaler);
 }
 
 TEST_F(TestMockJournal, EventCommitErrorWithPendingWriteback) {
@@ -882,14 +924,16 @@ TEST_F(TestMockJournal, EventCommitErrorWithPendingWriteback) {
     close_journal(mock_journal, mock_journaler);
   };
 
-  AioCompletion *comp = new AioCompletion();
-  comp->get();
+  C_SaferCond object_request_ctx;
+  AioObjectRemove *object_request = new AioObjectRemove(
+    ictx, "oid", 0, {}, &object_request_ctx);
 
   ::journal::MockFuture mock_future;
   Context *on_journal_safe;
   expect_append_journaler(mock_journaler);
   expect_wait_future(mock_future, &on_journal_safe);
-  ASSERT_EQ(1U, when_append_io_event(mock_image_ctx, mock_journal, comp));
+  ASSERT_EQ(1U, when_append_io_event(mock_image_ctx, mock_journal,
+                                     object_request));
 
   expect_future_is_valid(mock_future);
   C_SaferCond flush_ctx;
@@ -898,12 +942,12 @@ TEST_F(TestMockJournal, EventCommitErrorWithPendingWriteback) {
   // commit the event in the journal w/ waiting cache writeback
   expect_future_committed(mock_journaler);
   on_journal_safe->complete(-EINVAL);
-  ASSERT_EQ(0, comp->wait_for_complete());
-  ASSERT_EQ(-EINVAL, comp->get_return_value());
-  comp->put();
+  ASSERT_EQ(-EINVAL, object_request_ctx.wait());
 
   // cache should receive the error if waiting
   ASSERT_EQ(-EINVAL, flush_ctx.wait());
+
+  expect_shut_down_journaler(mock_journaler);
 }
 
 TEST_F(TestMockJournal, IOCommitError) {
@@ -930,6 +974,8 @@ TEST_F(TestMockJournal, IOCommitError) {
   on_journal_safe->complete(0);
   ictx->op_work_queue->drain();
   mock_journal.commit_io_event(1U, -EINVAL);
+
+  expect_shut_down_journaler(mock_journaler);
 }
 
 TEST_F(TestMockJournal, FlushCommitPosition) {
@@ -950,6 +996,8 @@ TEST_F(TestMockJournal, FlushCommitPosition) {
   C_SaferCond ctx;
   mock_journal.flush_commit_position(&ctx);
   ASSERT_EQ(0, ctx.wait());
+
+  expect_shut_down_journaler(mock_journaler);
 }
 
 } // namespace librbd
diff --git a/src/test/librbd/test_support.cc b/src/test/librbd/test_support.cc
index db573ef..5b5adf3 100644
--- a/src/test/librbd/test_support.cc
+++ b/src/test/librbd/test_support.cc
@@ -1,6 +1,7 @@
 // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
 // vim: ts=8 sw=2 smarttab
 #include "test/librbd/test_support.h"
+#include "include/rbd_types.h"
 #include <sstream>
 
 bool get_features(uint64_t *features) {
@@ -37,3 +38,20 @@ int create_image_pp(librbd::RBD &rbd, librados::IoCtx &ioctx,
     return rbd.create2(ioctx, name.c_str(), size, features, &order);
   }
 }
+
+int get_image_id(librbd::Image &image, std::string *image_id)
+{
+  librbd::image_info_t info;
+  int r = image.stat(info, sizeof(info));
+  if (r < 0) {
+    return r;
+  }
+
+  char prefix[RBD_MAX_BLOCK_NAME_SIZE + 1];
+  strncpy(prefix, info.block_name_prefix, RBD_MAX_BLOCK_NAME_SIZE);
+  prefix[RBD_MAX_BLOCK_NAME_SIZE] = '\0';
+
+  *image_id = std::string(prefix + strlen(RBD_DATA_PREFIX));
+  return 0;
+}
+
diff --git a/src/test/librbd/test_support.h b/src/test/librbd/test_support.h
index 63c5e3a..3a2298e 100644
--- a/src/test/librbd/test_support.h
+++ b/src/test/librbd/test_support.h
@@ -9,6 +9,7 @@ bool get_features(uint64_t *features);
 bool is_feature_enabled(uint64_t feature);
 int create_image_pp(librbd::RBD &rbd, librados::IoCtx &ioctx,
                     const std::string &name, uint64_t size);
+int get_image_id(librbd::Image &image, std::string *image_id);
 
 #define REQUIRE_FEATURE(feature) { 	  \
   if (!is_feature_enabled(feature)) { 	  \
diff --git a/src/test/opensuse-13.2/ceph.spec.in b/src/test/opensuse-13.2/ceph.spec.in
index 34e4caa..3cf6307 100644
--- a/src/test/opensuse-13.2/ceph.spec.in
+++ b/src/test/opensuse-13.2/ceph.spec.in
@@ -17,6 +17,7 @@
 %bcond_with ocf
 %bcond_without cephfs_java
 %bcond_with tests
+%bcond_with xio
 %bcond_without tcmalloc
 %bcond_without libs_compat
 %bcond_with lowmem_builder
@@ -32,39 +33,13 @@
 %bcond_without lttng
 %endif
 
-%if (0%{?el5} || (0%{?rhel_version} >= 500 && 0%{?rhel_version} <= 600))
-%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")}
-%{!?python_sitearch: %global python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")}
-%endif
-
 %if %{with selinux}
 # get selinux policy version
 %{!?_selinux_policy_version: %global _selinux_policy_version %(sed -e 's,.*selinux-policy-\\([^/]*\\)/.*,\\1,' /usr/share/selinux/devel/policyhelp 2>/dev/null || echo 0.0.0)}
-
-%define relabel_files() \
-restorecon -R /usr/bin/ceph-mon > /dev/null 2>&1; \
-restorecon -R /usr/bin/ceph-osd > /dev/null 2>&1; \
-restorecon -R /usr/bin/ceph-mds > /dev/null 2>&1; \
-restorecon -R /usr/bin/radosgw > /dev/null 2>&1; \
-restorecon -R /etc/rc\.d/init\.d/ceph > /dev/null 2>&1; \
-restorecon -R /etc/rc\.d/init\.d/radosgw > /dev/null 2>&1; \
-restorecon -R /var/run/ceph > /dev/null 2>&1; \
-restorecon -R /var/lib/ceph > /dev/null 2>&1; \
-restorecon -R /var/log/ceph > /dev/null 2>&1; \
-restorecon -R /var/log/radosgw > /dev/null 2>&1;
 %endif
 
 %{!?_udevrulesdir: %global _udevrulesdir /lib/udev/rules.d}
-
-# Use systemd files on RHEL 7 and above and in SUSE/openSUSE.
-# Note: We don't install unit files for the services yet. For now,
-# the _with_systemd variable only implies that we'll install
-# /etc/tmpfiles.d/ceph.conf in order to set up the socket directory in
-# /var/run/ceph.
-%if 0%{?fedora} || 0%{?rhel} >= 7 || 0%{?suse_version}
-%global _with_systemd 1
 %{!?tmpfiles_create: %global tmpfiles_create systemd-tmpfiles --create}
-%endif
 
 # unify libexec for all targets
 %global _libexecdir %{_exec_prefix}/lib
@@ -84,9 +59,6 @@ Group:         System/Filesystems
 %endif
 URL:		http://ceph.com/
 Source0:	http://ceph.com/download/%{name}-%{version}.tar.bz2
-%if 0%{?fedora} || 0%{?rhel}
-Patch0:		init-ceph.in-fedora.patch
-%endif
 #################################################################################
 # dependencies that apply across all distro families
 #################################################################################
@@ -103,20 +75,20 @@ BuildRequires:	checkpolicy
 BuildRequires:	selinux-policy-devel
 BuildRequires:	/usr/share/selinux/devel/policyhelp
 %endif
-BuildRequires:	gcc-c++
 BuildRequires:	boost-devel
 BuildRequires:  cmake
 BuildRequires:	cryptsetup
 BuildRequires:	fuse-devel
+BuildRequires:	gcc-c++
 BuildRequires:	gdbm
 BuildRequires:	hdparm
 BuildRequires:	leveldb-devel > 1.2
 BuildRequires:	libaio-devel
-BuildRequires:	libcurl-devel
-BuildRequires:	libxml2-devel
 BuildRequires:	libblkid-devel >= 2.17
+BuildRequires:	libcurl-devel
 BuildRequires:	libudev-devel
 BuildRequires:	libtool
+BuildRequires:	libxml2-devel
 BuildRequires:	make
 BuildRequires:	parted
 BuildRequires:	perl
@@ -125,6 +97,7 @@ BuildRequires:	python
 BuildRequires:	python-devel
 BuildRequires:	python-nose
 BuildRequires:	python-requests
+BuildRequires:	python-sphinx
 BuildRequires:	python-virtualenv
 BuildRequires:	snappy-devel
 BuildRequires:	util-linux
@@ -138,12 +111,10 @@ BuildRequires:	yasm
 # distro-conditional dependencies
 #################################################################################
 %if 0%{?suse_version}
-%if 0%{?_with_systemd}
 BuildRequires:  pkgconfig(systemd)
 BuildRequires:	systemd-rpm-macros
 BuildRequires:	systemd
 %{?systemd_requires}
-%endif
 PreReq:		%fillup_prereq
 BuildRequires:	net-tools
 BuildRequires:	libbz2-devel
@@ -160,30 +131,18 @@ BuildRequires:  openldap2-devel
 BuildRequires:	python-Cython
 %endif
 %if 0%{?fedora} || 0%{?rhel} 
-%if 0%{?_with_systemd}
 Requires:	systemd
-%endif
+BuildRequires:  boost-random
 BuildRequires:	btrfs-progs
 BuildRequires:	nss-devel
 BuildRequires:	keyutils-libs-devel
 BuildRequires:	libatomic_ops-devel
-Requires(post):	chkconfig
-Requires(preun):	chkconfig
-Requires(preun):	initscripts
 BuildRequires:	gperftools-devel
 BuildRequires:  openldap-devel
 BuildRequires:  openssl-devel
 BuildRequires:  redhat-lsb-core
 BuildRequires:	Cython
 %endif
-# boost
-%if 0%{?fedora} || 0%{?rhel} 
-BuildRequires:  boost-random
-%endif
-# python-argparse for distros with Python 2.6 or lower
-%if (0%{?rhel} && 0%{?rhel} <= 6)
-BuildRequires:	python-argparse
-%endif
 # lttng and babeltrace for rbd-replay-prep
 %if %{with lttng}
 %if 0%{?fedora} || 0%{?rhel}
@@ -204,17 +163,14 @@ BuildRequires:	FastCGI-devel
 BuildRequires:	expat-devel
 BuildRequires:	fcgi-devel
 %endif
-# python-sphinx
-%if 0%{?rhel} > 0 && 0%{?rhel} < 7
-BuildRequires:	python-sphinx10
-%endif
-%if 0%{?fedora} || 0%{?suse_version} || 0%{?rhel} >= 7
-BuildRequires:	python-sphinx
-%endif
 #hardened-cc1
 %if 0%{?fedora} || 0%{?rhel}
 BuildRequires:  redhat-rpm-config
 %endif
+# Accelio IB/RDMA
+%if 0%{with xio}
+BuildRequires:  libxio-devel
+%endif
 
 %description
 Ceph is a massively scalable, open-source, distributed storage system that runs
@@ -249,10 +205,14 @@ Requires:      findutils
 Requires:      which
 %if 0%{?suse_version}
 Requires:      lsb-release
+Recommends:    ntp-daemon
 %endif
 %if 0%{?fedora} || 0%{?rhel}
 Requires:      redhat-lsb-core
 %endif
+%if 0%{with xio}
+Requires:      libxio
+%endif
 %description base
 Base is the package that includes all the files shared amongst ceph servers
 
@@ -266,15 +226,12 @@ Requires:	python-rados = %{epoch}:%{version}-%{release}
 Requires:	python-rbd = %{epoch}:%{version}-%{release}
 Requires:	python-cephfs = %{epoch}:%{version}-%{release}
 Requires:	python-requests
-%if 0%{?_with_systemd}
 %{?systemd_requires}
-%endif
 %if 0%{?suse_version}
 Requires(pre):	pwdutils
 %endif
-# python-argparse is only needed in distros with Python 2.6 or lower
-%if (0%{?rhel} && 0%{?rhel} <= 6)
-Requires:	python-argparse
+%if 0%{with xio}
+Requires:       libxio
 %endif
 %description -n ceph-common
 Common utilities to mount and interact with a ceph storage cluster.
@@ -572,13 +529,8 @@ Group:		System Environment/Libraries
 License:	LGPL-2.0
 Requires:	java
 Requires:	libcephfs_jni1 = %{epoch}:%{version}-%{release}
-%if 0%{?el6}
-Requires:	junit4
-BuildRequires:	junit4
-%else
 Requires:       junit
 BuildRequires:  junit
-%endif
 %description -n cephfs-java
 This package contains the Java libraries for the Ceph File System.
 
@@ -660,9 +612,6 @@ python-cephfs instead.
 #################################################################################
 %prep
 %setup -q
-%if 0%{?fedora} || 0%{?rhel}
-%patch0 -p1 -b .init
-%endif
 
 %build
 %if 0%{with cephfs_java}
@@ -682,14 +631,12 @@ export RPM_OPT_FLAGS=`echo $RPM_OPT_FLAGS | sed -e 's/i386/i486/'`
 %{configure}	CPPFLAGS="$java_inc" \
 		--prefix=/usr \
                 --libexecdir=%{_libexecdir} \
-		--localstatedir=/var \
-		--sysconfdir=/etc \
+		--localstatedir=%{_localstatedir} \
+		--sysconfdir=%{_sysconfdir} \
 %if 0%{?rhel} && ! 0%{?centos}
                 --enable-subman \
 %endif
-%if 0%{?_with_systemd}
 		--with-systemdsystemunitdir=%_unitdir \
-%endif
 		--docdir=%{_docdir}/ceph \
 		--with-man-pages \
 		--mandir="%_mandir" \
@@ -699,6 +646,9 @@ export RPM_OPT_FLAGS=`echo $RPM_OPT_FLAGS | sed -e 's/i386/i486/'`
 %if 0%{with cephfs_java}
 		--enable-cephfs-java \
 %endif
+%if 0%{with xio}
+		--enable-xio \
+%endif
 %if 0%{with selinux}
 		--with-selinux \
 %endif
@@ -725,66 +675,30 @@ make %{?_smp_mflags}
 %if 0%{with tests}
 %check
 # run in-tree unittests
-make %{?_smp_mflags} check-local
+make %{?_smp_mflags} check
 
 %endif
 
 
 
 %install
-make DESTDIR=$RPM_BUILD_ROOT install
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_example.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_fail_to_initialize.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_fail_to_register.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_hangs.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_missing_entry_point.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_missing_version.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_jerasure_generic.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_jerasure_neon.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_jerasure_sse3.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_jerasure_sse4.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_shec_generic.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_shec_neon.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_shec_sse3.so
-rm -f $RPM_BUILD_ROOT%{_libdir}/ceph/erasure-code/libec_test_shec_sse4.so
-find $RPM_BUILD_ROOT -type f -name "*.la" -exec rm -f {} ';'
-find $RPM_BUILD_ROOT -type f -name "*.a" -exec rm -f {} ';'
-install -D src/etc-rbdmap $RPM_BUILD_ROOT%{_sysconfdir}/ceph/rbdmap
+make DESTDIR=%{buildroot} install
+find %{buildroot} -type f -name "*.la" -exec rm -f {} ';'
+find %{buildroot} -type f -name "*.a" -exec rm -f {} ';'
+install -D src/etc-rbdmap %{buildroot}%{_sysconfdir}/ceph/rbdmap
 %if 0%{?fedora} || 0%{?rhel}
-install -m 0644 -D etc/sysconfig/ceph $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig/ceph
+install -m 0644 -D etc/sysconfig/ceph %{buildroot}%{_sysconfdir}/sysconfig/ceph
 %endif
 %if 0%{?suse_version}
-install -m 0644 -D etc/sysconfig/ceph $RPM_BUILD_ROOT%{_localstatedir}/adm/fillup-templates/sysconfig.%{name}
-%endif
-%if 0%{?_with_systemd}
-  install -m 0644 -D systemd/ceph.tmpfiles.d $RPM_BUILD_ROOT%{_tmpfilesdir}/ceph-common.conf
-  install -m 0644 -D systemd/rbdmap.service $RPM_BUILD_ROOT%{_unitdir}/rbdmap.service
-  install -m 0644 -D systemd/ceph-osd at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-osd at .service
-  install -m 0644 -D systemd/ceph-mon at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-mon at .service
-  install -m 0644 -D systemd/ceph-create-keys at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-create-keys at .service
-  install -m 0644 -D systemd/ceph-mds at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-mds at .service
-  install -m 0644 -D systemd/ceph-radosgw at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-radosgw at .service
-  install -m 0644 -D systemd/ceph-rbd-mirror at .service $RPM_BUILD_ROOT%{_unitdir}/ceph-rbd-mirror at .service
-  install -m 0644 -D systemd/ceph.target $RPM_BUILD_ROOT%{_unitdir}/ceph.target
-  install -m 0644 -D systemd/ceph-osd.target $RPM_BUILD_ROOT%{_unitdir}/ceph-osd.target
-  install -m 0644 -D systemd/ceph-mon.target $RPM_BUILD_ROOT%{_unitdir}/ceph-mon.target
-  install -m 0644 -D systemd/ceph-mds.target $RPM_BUILD_ROOT%{_unitdir}/ceph-mds.target
-  install -m 0644 -D systemd/ceph-radosgw.target $RPM_BUILD_ROOT%{_unitdir}/ceph-radosgw.target
-  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
-  install -D src/init-radosgw $RPM_BUILD_ROOT%{_initrddir}/ceph-radosgw
-  ln -sf ../../etc/init.d/ceph %{buildroot}/%{_sbindir}/rcceph
-  ln -sf ../../etc/init.d/ceph-radosgw %{buildroot}/%{_sbindir}/rcceph-radosgw
-%endif
-mkdir -p $RPM_BUILD_ROOT%{_sbindir}
-install -m 0644 -D src/logrotate.conf $RPM_BUILD_ROOT%{_sysconfdir}/logrotate.d/ceph
-chmod 0644 $RPM_BUILD_ROOT%{_docdir}/ceph/sample.ceph.conf
-chmod 0644 $RPM_BUILD_ROOT%{_docdir}/ceph/sample.fetch_config
+install -m 0644 -D etc/sysconfig/ceph %{buildroot}%{_localstatedir}/adm/fillup-templates/sysconfig.%{name}
+%endif
+install -m 0644 -D systemd/ceph.tmpfiles.d %{buildroot}%{_tmpfilesdir}/ceph-common.conf
+install -m 0755 -D systemd/ceph %{buildroot}%{_sbindir}/rcceph
+install -m 0644 -D systemd/50-ceph.preset %{buildroot}%{_libexecdir}/systemd/system-preset/50-ceph.preset
+mkdir -p %{buildroot}%{_sbindir}
+install -m 0644 -D src/logrotate.conf %{buildroot}%{_sysconfdir}/logrotate.d/ceph
+chmod 0644 %{buildroot}%{_docdir}/ceph/sample.ceph.conf
+chmod 0644 %{buildroot}%{_docdir}/ceph/sample.fetch_config
 
 # firewall templates
 %if 0%{?suse_version}
@@ -793,38 +707,26 @@ install -m 0644 -D etc/sysconfig/SuSEfirewall2.d/services/ceph-osd-mds %{buildro
 %endif
 
 # udev rules
-install -m 0644 -D udev/50-rbd.rules $RPM_BUILD_ROOT%{_udevrulesdir}/50-rbd.rules
-install -m 0644 -D udev/60-ceph-partuuid-workaround.rules $RPM_BUILD_ROOT%{_udevrulesdir}/60-ceph-partuuid-workaround.rules
-
-%if (0%{?rhel} && 0%{?rhel} < 7)
-install -m 0644 -D udev/95-ceph-osd-alt.rules $RPM_BUILD_ROOT/lib/udev/rules.d/95-ceph-osd.rules
-%else
-install -m 0644 -D udev/95-ceph-osd.rules $RPM_BUILD_ROOT/lib/udev/rules.d/95-ceph-osd.rules
-%endif
-
-%if 0%{?rhel} >= 7 || 0%{?fedora} || 0%{?suse_version}
-mv $RPM_BUILD_ROOT/lib/udev/rules.d/95-ceph-osd.rules $RPM_BUILD_ROOT/usr/lib/udev/rules.d/95-ceph-osd.rules
-mv $RPM_BUILD_ROOT/sbin/mount.ceph $RPM_BUILD_ROOT/usr/sbin/mount.ceph
-mv $RPM_BUILD_ROOT/sbin/mount.fuse.ceph $RPM_BUILD_ROOT/usr/sbin/mount.fuse.ceph
-%endif
+install -m 0644 -D udev/50-rbd.rules %{buildroot}%{_udevrulesdir}/50-rbd.rules
+install -m 0644 -D udev/95-ceph-osd.rules %{buildroot}%{_udevrulesdir}/95-ceph-osd.rules
+mv %{buildroot}/sbin/mount.ceph %{buildroot}/usr/sbin/mount.ceph
+mv %{buildroot}/sbin/mount.fuse.ceph %{buildroot}/usr/sbin/mount.fuse.ceph
 
 #set up placeholder directories
-mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/ceph
-%if ! 0%{?_with_systemd}
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/run/ceph
-%endif
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/log/ceph
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/tmp
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/mon
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/osd
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/mds
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/radosgw
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/bootstrap-osd
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/bootstrap-mds
-mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/lib/ceph/bootstrap-rgw
+mkdir -p %{buildroot}%{_sysconfdir}/ceph
+mkdir -p %{buildroot}%{_localstatedir}/run/ceph
+mkdir -p %{buildroot}%{_localstatedir}/log/ceph
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/tmp
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/mon
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/osd
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/mds
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/radosgw
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/bootstrap-osd
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/bootstrap-mds
+mkdir -p %{buildroot}%{_localstatedir}/lib/ceph/bootstrap-rgw
 
 %clean
-rm -rf $RPM_BUILD_ROOT
+rm -rf %{buildroot}
 
 #################################################################################
 # files and systemd scriptlets
@@ -844,19 +746,11 @@ rm -rf $RPM_BUILD_ROOT
 %{_bindir}/ceph-detect-init
 %{_bindir}/ceph-client-debug
 %{_bindir}/cephfs
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-create-keys at .service
 %{_libexecdir}/systemd/system-preset/50-ceph.preset
-%else
-%{_initrddir}/ceph
-%endif
 %{_sbindir}/ceph-create-keys
 %{_sbindir}/rcceph
-%if 0%{?rhel} >= 7 || 0%{?fedora} || 0%{?suse_version}
 %{_sbindir}/mount.ceph
-%else
-/sbin/mount.ceph
-%endif
 %dir %{_libexecdir}/ceph
 %{_libexecdir}/ceph/ceph_common.sh
 %dir %{_libdir}/rados-classes
@@ -897,9 +791,6 @@ rm -rf $RPM_BUILD_ROOT
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/bootstrap-osd
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/bootstrap-mds
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/bootstrap-rgw
-%if ! 0%{?_with_systemd}
-%attr(770,ceph,ceph) %dir %{_localstatedir}/run/ceph
-%endif
 
 %post base
 /sbin/ldconfig
@@ -955,9 +846,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 %endif
 %{_bindir}/ceph-post-file
 %{_bindir}/ceph-brag
-%if 0%{?_with_systemd}
 %{_tmpfilesdir}/ceph-common.conf
-%endif
 %{_mandir}/man8/ceph-authtool.8*
 %{_mandir}/man8/ceph-conf.8*
 %{_mandir}/man8/ceph-dencoder.8*
@@ -979,11 +868,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 %config %{_sysconfdir}/bash_completion.d/rados
 %config %{_sysconfdir}/bash_completion.d/rbd
 %config(noreplace) %{_sysconfdir}/ceph/rbdmap
-%if 0%{?_with_systemd}
 %{_unitdir}/rbdmap.service
-%else
-%{_initrddir}/rbdmap
-%endif
 %{python_sitelib}/ceph_argparse.py*
 %{python_sitelib}/ceph_daemon.py*
 %{_udevrulesdir}/50-rbd.rules
@@ -994,8 +879,8 @@ DISABLE_RESTART_ON_UPDATE="yes"
 CEPH_GROUP_ID=167
 CEPH_USER_ID=167
 %if 0%{?rhel} || 0%{?fedora}
-%{_sbindir}/groupadd ceph -g $CEPH_GROUP_ID -o -r 2>/dev/null || :
-%{_sbindir}/useradd ceph -u $CEPH_USER_ID -o -r -g ceph -s /sbin/nologin -c "Ceph daemons" -d %{_localstatedir}/lib/ceph 2>/dev/null || :
+/usr/sbin/groupadd ceph -g $CEPH_GROUP_ID -o -r 2>/dev/null || :
+/usr/sbin/useradd ceph -u $CEPH_USER_ID -o -r -g ceph -s /sbin/nologin -c "Ceph daemons" -d %{_localstatedir}/lib/ceph 2>/dev/null || :
 %endif
 %if 0%{?suse_version}
 if ! getent group ceph >/dev/null ; then
@@ -1006,33 +891,32 @@ fi
 if ! getent passwd ceph >/dev/null ; then
     CEPH_USER_ID_OPTION=""
     getent passwd $CEPH_USER_ID >/dev/null || CEPH_USER_ID_OPTION="-u $CEPH_USER_ID"
-    useradd ceph $CEPH_USER_ID_OPTION -r -g ceph -s /sbin/nologin -c "Ceph daemons" -d %{_localstatedir}/lib/ceph 2>/dev/null || :
+    useradd ceph $CEPH_USER_ID_OPTION -r -g ceph -s /sbin/nologin 2>/dev/null || :
 fi
+usermod -c "Ceph storage service" \
+        -d %{_localstatedir}/lib/ceph \   
+        -g ceph \
+        -s /sbin/nologin \
+        ceph
 %endif
 exit 0
 
 %post common
-%if 0%{?_with_systemd}
 %tmpfiles_create %{_tmpfilesdir}/ceph-common.conf
-%endif
 
 %postun common
 # Package removal cleanup
 if [ "$1" -eq "0" ] ; then
-    rm -rf /var/log/ceph
-    rm -rf /etc/ceph
+    rm -rf %{_localstatedir}/log/ceph
+    rm -rf %{_sysconfdir}/ceph
 fi
 
 #################################################################################
 %files mds
 %{_bindir}/ceph-mds
 %{_mandir}/man8/ceph-mds.8*
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-mds at .service
 %{_unitdir}/ceph-mds.target
-%else
-%{_initrddir}/ceph
-%endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/mds
 
 %post mds
@@ -1066,7 +950,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1082,12 +966,8 @@ fi
 %{_mandir}/man8/ceph-mon.8*
 %{_mandir}/man8/ceph-rest-api.8*
 %{python_sitelib}/ceph_rest_api.py*
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-mon at .service
 %{_unitdir}/ceph-mon.target
-%else
-%{_initrddir}/ceph
-%endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/mon
 
 %post mon
@@ -1121,7 +1001,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1135,11 +1015,7 @@ fi
 %defattr(-,root,root,-)
 %{_bindir}/ceph-fuse
 %{_mandir}/man8/ceph-fuse.8*
-%if 0%{?rhel} >= 7 || 0%{?fedora} || 0%{?suse_version}
 %{_sbindir}/mount.fuse.ceph
-%else
-/sbin/mount.fuse.ceph
-%endif
 
 #################################################################################
 %files -n rbd-fuse
@@ -1152,10 +1028,8 @@ fi
 %defattr(-,root,root,-)
 %{_bindir}/rbd-mirror
 %{_mandir}/man8/rbd-mirror.8*
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-rbd-mirror at .service
 %{_unitdir}/ceph-rbd-mirror.target
-%endif
 
 %post -n rbd-mirror
 %if 0%{?suse_version}
@@ -1188,7 +1062,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1214,13 +1088,8 @@ fi
 %{_mandir}/man8/radosgw-admin.8*
 %config %{_sysconfdir}/bash_completion.d/radosgw-admin
 %dir %{_localstatedir}/lib/ceph/radosgw
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-radosgw at .service
 %{_unitdir}/ceph-radosgw.target
-%else
-%{_initrddir}/ceph-radosgw
-%{_sbindir}/rcceph-radosgw
-%endif
 
 %post radosgw
 %if 0%{?suse_version}
@@ -1253,7 +1122,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1271,21 +1140,16 @@ fi
 %{_sbindir}/ceph-disk
 %{_sbindir}/ceph-disk-udev
 %{_libexecdir}/ceph/ceph-osd-prestart.sh
-%{_udevrulesdir}/60-ceph-partuuid-workaround.rules
 %{_udevrulesdir}/95-ceph-osd.rules
 %{_mandir}/man8/ceph-clsinfo.8*
 %{_mandir}/man8/ceph-disk.8*
 %{_mandir}/man8/ceph-osd.8*
 %if 0%{?rhel} && ! 0%{?centos}
-/etc/cron.hourly/subman
+%{_sysconfdir}/cron.hourly/subman
 %endif
-%if 0%{?_with_systemd}
 %{_unitdir}/ceph-osd at .service
 %{_unitdir}/ceph-osd.target
 %{_unitdir}/ceph-disk at .service
-%else
-%{_initrddir}/ceph
-%endif
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/osd
 
 %post osd
@@ -1319,7 +1183,7 @@ DISABLE_RESTART_ON_UPDATE="yes"
 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
+  SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
   if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
     source $SYSCONF_CEPH
   fi
@@ -1337,18 +1201,10 @@ fi
 %dir %{_prefix}/lib/ocf
 %dir %{_prefix}/lib/ocf/resource.d
 %dir %{_prefix}/lib/ocf/resource.d/ceph
-%if 0%{_with_systemd}
 %exclude %{_prefix}/lib/ocf/resource.d/ceph/ceph
 %exclude %{_prefix}/lib/ocf/resource.d/ceph/mds
 %exclude %{_prefix}/lib/ocf/resource.d/ceph/mon
 %exclude %{_prefix}/lib/ocf/resource.d/ceph/osd
-%endif
-%if ! 0%{_with_systemd}
-%{_prefix}/lib/ocf/resource.d/ceph/ceph
-%{_prefix}/lib/ocf/resource.d/ceph/mds
-%{_prefix}/lib/ocf/resource.d/ceph/mon
-%{_prefix}/lib/ocf/resource.d/ceph/osd
-%endif
 %{_prefix}/lib/ocf/resource.d/ceph/rbd
 
 %endif
@@ -1560,95 +1416,79 @@ ln -sf %{_libdir}/librbd.so.1 /usr/lib64/qemu/librbd.so.1
 %{_mandir}/man8/ceph_selinux.8*
 
 %post selinux
+# backup file_contexts before update
+. /etc/selinux/config
+FILE_CONTEXT=/etc/selinux/${SELINUXTYPE}/contexts/files/file_contexts
+cp ${FILE_CONTEXT} ${FILE_CONTEXT}.pre
+
 # Install the policy
-OLD_POLVER=$(%{_sbindir}/semodule -l | grep -P '^ceph[\t ]' | awk '{print $2}')
-%{_sbindir}/semodule -n -i %{_datadir}/selinux/packages/ceph.pp
-NEW_POLVER=$(%{_sbindir}/semodule -l | grep -P '^ceph[\t ]' | awk '{print $2}')
+/usr/sbin/semodule -i %{_datadir}/selinux/packages/ceph.pp
 
 # Load the policy if SELinux is enabled
-if %{_sbindir}/selinuxenabled; then
-    %{_sbindir}/load_policy
-else
+if ! /usr/sbin/selinuxenabled; then
     # Do not relabel if selinux is not enabled
     exit 0
 fi
 
-if test "$OLD_POLVER" = "$NEW_POLVER"; then
-   # Do not relabel if policy version did not change
+if diff ${FILE_CONTEXT} ${FILE_CONTEXT}.pre > /dev/null 2>&1; then
+   # Do not relabel if file contexts did not change
    exit 0
 fi
 
 # Check whether the daemons are running
-%if 0%{?_with_systemd}
-    /usr/bin/systemctl status ceph.target > /dev/null 2>&1
-%else
-    /sbin/service ceph status >/dev/null 2>&1
-%endif
+/usr/bin/systemctl status ceph.target > /dev/null 2>&1
 STATUS=$?
 
 # Stop the daemons if they were running
 if test $STATUS -eq 0; then
-%if 0%{?_with_systemd}
     /usr/bin/systemctl stop ceph.target > /dev/null 2>&1
-%else
-    /sbin/service ceph stop >/dev/null 2>&1
-%endif
 fi
 
 # Now, relabel the files
-%relabel_files
+/usr/sbin/fixfiles -C ${FILE_CONTEXT}.pre restore 2> /dev/null
+rm -f ${FILE_CONTEXT}.pre
+# The fixfiles command won't fix label for /var/run/ceph
+/usr/sbin/restorecon -R /var/run/ceph > /dev/null 2>&1
 
 # Start the daemons iff they were running before
 if test $STATUS -eq 0; then
-%if 0%{?_with_systemd}
     /usr/bin/systemctl start ceph.target > /dev/null 2>&1 || :
-%else
-    /sbin/service ceph start >/dev/null 2>&1 || :
-%endif
 fi
-
 exit 0
 
 %postun selinux
 if [ $1 -eq 0 ]; then
+    # backup file_contexts before update
+    . /etc/selinux/config
+    FILE_CONTEXT=/etc/selinux/${SELINUXTYPE}/contexts/files/file_contexts
+    cp ${FILE_CONTEXT} ${FILE_CONTEXT}.pre
+
     # Remove the module
-    %{_sbindir}/semodule -n -r ceph
+    /usr/sbin/semodule -n -r ceph > /dev/null 2>&1
 
     # Reload the policy if SELinux is enabled
-    if %{_sbindir}/selinuxenabled ; then
-        %{_sbindir}/load_policy
-    else
+    if ! /usr/sbin/selinuxenabled ; then
         # Do not relabel if SELinux is not enabled
         exit 0
     fi
 
     # Check whether the daemons are running
-    %if 0%{?_with_systemd}
-        /usr/bin/systemctl status ceph.target > /dev/null 2>&1
-    %else
-        /sbin/service ceph status >/dev/null 2>&1
-    %endif
+    /usr/bin/systemctl status ceph.target > /dev/null 2>&1
     STATUS=$?
 
     # Stop the daemons if they were running
     if test $STATUS -eq 0; then
-    %if 0%{?_with_systemd}
         /usr/bin/systemctl stop ceph.target > /dev/null 2>&1
-    %else
-        /sbin/service ceph stop >/dev/null 2>&1
-    %endif
     fi
 
-    # Now, relabel the files
-    %relabel_files
+    /usr/sbin/fixfiles -C ${FILE_CONTEXT}.pre restore 2> /dev/null
+    rm -f ${FILE_CONTEXT}.pre
+    # The fixfiles command won't fix label for /var/run/ceph
+    /usr/sbin/restorecon -R /var/run/ceph > /dev/null 2>&1
 
     # Start the daemons if they were running before
     if test $STATUS -eq 0; then
-    %if 0%{?_with_systemd}
 	/usr/bin/systemctl start ceph.target > /dev/null 2>&1 || :
-    %else
-	/sbin/service ceph start >/dev/null 2>&1 || :
-    %endif
     fi
 fi
 exit 0
diff --git a/src/test/opensuse-13.2/install-deps.sh b/src/test/opensuse-13.2/install-deps.sh
index 21e71ee..03ca760 100755
--- a/src/test/opensuse-13.2/install-deps.sh
+++ b/src/test/opensuse-13.2/install-deps.sh
@@ -28,7 +28,7 @@ if type apt-get > /dev/null 2>&1 ; then
 fi
 
 if type zypper > /dev/null 2>&1 ; then
-    $SUDO zypper --gpg-auto-import-keys --non-interactive install lsb-release
+    $SUDO zypper --gpg-auto-import-keys --non-interactive install lsb-release systemd-rpm-macros
 fi
 
 case $(lsb_release -si) in
diff --git a/src/test/rbd_mirror/image_replayer/test_mock_BootstrapRequest.cc b/src/test/rbd_mirror/image_replayer/test_mock_BootstrapRequest.cc
index 0cec3a2..7ca0436 100644
--- a/src/test/rbd_mirror/image_replayer/test_mock_BootstrapRequest.cc
+++ b/src/test/rbd_mirror/image_replayer/test_mock_BootstrapRequest.cc
@@ -6,15 +6,25 @@
 #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/CreateImageRequest.h"
+#include "tools/rbd_mirror/image_replayer/OpenImageRequest.h"
 #include "tools/rbd_mirror/image_replayer/OpenLocalImageRequest.h"
 #include "test/journal/mock/MockJournaler.h"
 #include "test/librbd/mock/MockImageCtx.h"
 
 namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public librbd::MockImageCtx {
+};
+
+} // anonymous namespace
+
 namespace journal {
 
 template <>
-struct TypeTraits<librbd::MockImageCtx> {
+struct TypeTraits<librbd::MockTestImageCtx> {
   typedef ::journal::MockJournaler Journaler;
 };
 
@@ -27,17 +37,17 @@ namespace mirror {
 class ProgressContext;
 
 template<>
-struct ImageSync<librbd::MockImageCtx> {
+struct ImageSync<librbd::MockTestImageCtx> {
   static ImageSync* s_instance;
   Context *on_finish = nullptr;
 
-  static ImageSync* create(librbd::MockImageCtx *local_image_ctx,
-                           librbd::MockImageCtx *remote_image_ctx,
+  static ImageSync* create(librbd::MockTestImageCtx *local_image_ctx,
+                           librbd::MockTestImageCtx *remote_image_ctx,
                            SafeTimer *timer, Mutex *timer_lock,
                            const std::string &mirror_uuid,
                            ::journal::MockJournaler *journaler,
                            librbd::journal::MirrorPeerClientMeta *client_meta,
-                           Context *on_finish,
+                           ContextWQ *work_queue, Context *on_finish,
                            ProgressContext *progress_ctx = nullptr) {
     assert(s_instance != nullptr);
     return s_instance;
@@ -48,19 +58,26 @@ struct ImageSync<librbd::MockImageCtx> {
     s_instance = this;
   }
 
-  MOCK_METHOD0(start, void());
+  void put() {
+  }
+
+  void get() {
+  }
+
+  MOCK_METHOD0(send, void());
+  MOCK_METHOD0(cancel, void());
 };
 
-ImageSync<librbd::MockImageCtx>* ImageSync<librbd::MockImageCtx>::s_instance = nullptr;
+ImageSync<librbd::MockTestImageCtx>* ImageSync<librbd::MockTestImageCtx>::s_instance = nullptr;
 
 namespace image_replayer {
 
 template<>
-struct CloseImageRequest<librbd::MockImageCtx> {
+struct CloseImageRequest<librbd::MockTestImageCtx> {
   static CloseImageRequest* s_instance;
   Context *on_finish = nullptr;
 
-  static CloseImageRequest* create(librbd::MockImageCtx **image_ctx,
+  static CloseImageRequest* create(librbd::MockTestImageCtx **image_ctx,
                                    ContextWQ *work_queue, bool destroy_only,
                                    Context *on_finish) {
     assert(s_instance != nullptr);
@@ -77,12 +94,60 @@ struct CloseImageRequest<librbd::MockImageCtx> {
 };
 
 template<>
-struct OpenLocalImageRequest<librbd::MockImageCtx> {
+struct CreateImageRequest<librbd::MockTestImageCtx> {
+  static CreateImageRequest* s_instance;
+  Context *on_finish = nullptr;
+
+  static CreateImageRequest* create(librados::IoCtx &local_io_ctx,
+                                    ContextWQ *work_queue,
+                                    const std::string &global_image_id,
+                                    const std::string &remote_mirror_uuid,
+                                    const std::string &local_image_name,
+                                    librbd::MockTestImageCtx *remote_image_ctx,
+                                    Context *on_finish) {
+    assert(s_instance != nullptr);
+    s_instance->on_finish = on_finish;
+    return s_instance;
+  }
+
+  CreateImageRequest() {
+    assert(s_instance == nullptr);
+    s_instance = this;
+  }
+
+  MOCK_METHOD0(send, void());
+};
+
+template<>
+struct OpenImageRequest<librbd::MockTestImageCtx> {
+  static OpenImageRequest* s_instance;
+  Context *on_finish = nullptr;
+
+  static OpenImageRequest* create(librados::IoCtx &io_ctx,
+                                  librbd::MockTestImageCtx **image_ctx,
+                                  const std::string &local_image_id,
+                                  bool read_only, ContextWQ *work_queue,
+                                  Context *on_finish) {
+    assert(s_instance != nullptr);
+    s_instance->on_finish = on_finish;
+    return s_instance;
+  }
+
+  OpenImageRequest() {
+    assert(s_instance == nullptr);
+    s_instance = this;
+  }
+
+  MOCK_METHOD0(send, void());
+};
+
+template<>
+struct OpenLocalImageRequest<librbd::MockTestImageCtx> {
   static OpenLocalImageRequest* s_instance;
   Context *on_finish = nullptr;
 
   static OpenLocalImageRequest* create(librados::IoCtx &local_io_ctx,
-                                       librbd::MockImageCtx **local_image_ctx,
+                                       librbd::MockTestImageCtx **local_image_ctx,
                                        const std::string &local_image_name,
                                        const std::string &local_image_id,
                                        ContextWQ *work_queue,
@@ -100,8 +165,14 @@ struct OpenLocalImageRequest<librbd::MockImageCtx> {
   MOCK_METHOD0(send, void());
 };
 
-CloseImageRequest<librbd::MockImageCtx>* CloseImageRequest<librbd::MockImageCtx>::s_instance = nullptr;
-OpenLocalImageRequest<librbd::MockImageCtx>* OpenLocalImageRequest<librbd::MockImageCtx>::s_instance = nullptr;
+CloseImageRequest<librbd::MockTestImageCtx>*
+  CloseImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+CreateImageRequest<librbd::MockTestImageCtx>*
+  CreateImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+OpenImageRequest<librbd::MockTestImageCtx>*
+  OpenImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+OpenLocalImageRequest<librbd::MockTestImageCtx>*
+  OpenLocalImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
 
 } // namespace image_replayer
 } // namespace mirror
@@ -109,7 +180,7 @@ OpenLocalImageRequest<librbd::MockImageCtx>* OpenLocalImageRequest<librbd::MockI
 
 // template definitions
 #include "tools/rbd_mirror/image_replayer/BootstrapRequest.cc"
-template class rbd::mirror::image_replayer::BootstrapRequest<librbd::MockImageCtx>;
+template class rbd::mirror::image_replayer::BootstrapRequest<librbd::MockTestImageCtx>;
 
 namespace rbd {
 namespace mirror {
@@ -117,7 +188,7 @@ namespace image_replayer {
 
 class TestMockImageReplayerBootstrapRequest : public TestMockFixture {
 public:
-  typedef BootstrapRequest<librbd::MockImageCtx> MockBootstrapRequest;
+  typedef BootstrapRequest<librbd::MockTestImageCtx> MockBootstrapRequest;
 
 };
 
diff --git a/src/test/rbd_mirror/image_replayer/test_mock_CreateImageRequest.cc b/src/test/rbd_mirror/image_replayer/test_mock_CreateImageRequest.cc
new file mode 100644
index 0000000..8191652
--- /dev/null
+++ b/src/test/rbd_mirror/image_replayer/test_mock_CreateImageRequest.cc
@@ -0,0 +1,692 @@
+// -*- 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 "include/rbd/librbd.hpp"
+#include "librbd/ImageState.h"
+#include "librbd/Operations.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "test/librados_test_stub/MockTestMemRadosClient.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "tools/rbd_mirror/image_replayer/CreateImageRequest.h"
+#include "tools/rbd_mirror/image_replayer/CloseImageRequest.h"
+#include "tools/rbd_mirror/image_replayer/OpenImageRequest.h"
+#include "tools/rbd_mirror/image_replayer/OpenLocalImageRequest.h"
+#include "tools/rbd_mirror/image_replayer/Utils.h"
+#include "tools/rbd_mirror/Threads.h"
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public librbd::MockImageCtx {
+  MockTestImageCtx(librbd::ImageCtx &image_ctx)
+    : librbd::MockImageCtx(image_ctx) {
+  }
+};
+
+} // anonymous namespace
+
+} // namespace librbd
+
+namespace rbd {
+namespace mirror {
+namespace image_replayer {
+
+struct CreateCloneImage {
+  static CreateCloneImage *s_instance;
+  static CreateCloneImage *get_instance() {
+    assert(s_instance != nullptr);
+    return s_instance;
+  }
+
+  CreateCloneImage() {
+    assert(s_instance == nullptr);
+    s_instance = this;
+  }
+  ~CreateCloneImage() {
+    s_instance = nullptr;
+  }
+
+  MOCK_METHOD3(create, int(const std::string &image_name,
+                           const std::string &non_primary_global_image_id,
+                           const std::string &primary_mirror_uuid));
+  MOCK_METHOD3(clone, int(const std::string &image_name,
+                          const std::string &non_primary_global_image_id,
+                          const std::string &primary_mirror_uuid));
+};
+
+CreateCloneImage *CreateCloneImage::s_instance = nullptr;
+
+namespace utils {
+
+template <>
+int create_image<librbd::MockTestImageCtx>(librados::IoCtx& io_ctx,
+                                           librbd::MockTestImageCtx *_image_ctx,
+                                           const char *imgname, uint64_t bid,
+                                           uint64_t size, int order,
+                                           uint64_t features,
+                                           uint64_t stripe_unit,
+                                           uint64_t stripe_count,
+                                           uint8_t journal_order,
+                                           uint8_t journal_splay_width,
+                                           const std::string &journal_pool,
+                                           const std::string &non_primary_global_image_id,
+                                           const std::string &primary_mirror_uuid) {
+  return CreateCloneImage::get_instance()->create(imgname,
+                                                  non_primary_global_image_id,
+                                                  primary_mirror_uuid);
+}
+
+template <>
+int clone_image<librbd::MockTestImageCtx>(librbd::MockTestImageCtx *p_imctx,
+                                          librados::IoCtx& c_ioctx,
+                                          const char *c_name,
+                                          librbd::ImageOptions& c_opts,
+                                          const std::string &non_primary_global_image_id,
+                                          const std::string &remote_mirror_uuid) {
+  return CreateCloneImage::get_instance()->clone(c_name,
+                                                 non_primary_global_image_id,
+                                                 remote_mirror_uuid);
+}
+
+} // namespace utils
+
+template<>
+struct CloseImageRequest<librbd::MockTestImageCtx> {
+  static CloseImageRequest* s_instance;
+  Context *on_finish = nullptr;
+
+  static CloseImageRequest* create(librbd::MockTestImageCtx **image_ctx,
+                                   ContextWQ *work_queue, bool destroy_only,
+                                   Context *on_finish) {
+    assert(s_instance != nullptr);
+    s_instance->construct(*image_ctx);
+    s_instance->on_finish = on_finish;
+    return s_instance;
+  }
+
+  CloseImageRequest() {
+    assert(s_instance == nullptr);
+    s_instance = this;
+  }
+  ~CloseImageRequest() {
+    s_instance = nullptr;
+  }
+
+  MOCK_METHOD1(construct, void(librbd::MockTestImageCtx *image_ctx));
+  MOCK_METHOD0(send, void());
+};
+
+template<>
+struct OpenImageRequest<librbd::MockTestImageCtx> {
+  static OpenImageRequest* s_instance;
+  librbd::MockTestImageCtx **image_ctx = nullptr;
+  Context *on_finish = nullptr;
+
+  static OpenImageRequest* create(librados::IoCtx &io_ctx,
+                                  librbd::MockTestImageCtx **image_ctx,
+                                  const std::string &image_id,
+                                  bool read_only, ContextWQ *work_queue,
+                                  Context *on_finish) {
+    assert(s_instance != nullptr);
+    s_instance->image_ctx = image_ctx;
+    s_instance->on_finish = on_finish;
+    s_instance->construct(io_ctx, image_id);
+    return s_instance;
+  }
+
+  OpenImageRequest() {
+    assert(s_instance == nullptr);
+    s_instance = this;
+  }
+  ~OpenImageRequest() {
+    s_instance = nullptr;
+  }
+
+  MOCK_METHOD2(construct, void(librados::IoCtx &io_ctx,
+                               const std::string &image_id));
+  MOCK_METHOD0(send, void());
+};
+
+CloseImageRequest<librbd::MockTestImageCtx>*
+  CloseImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+OpenImageRequest<librbd::MockTestImageCtx>*
+  OpenImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
+
+// template definitions
+#include "tools/rbd_mirror/image_replayer/CreateImageRequest.cc"
+template class rbd::mirror::image_replayer::CreateImageRequest<librbd::MockTestImageCtx>;
+
+namespace rbd {
+namespace mirror {
+namespace image_replayer {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::WithArg;
+
+MATCHER_P(IsSameIoCtx, io_ctx, "") {
+  return &get_mock_io_ctx(arg) == &get_mock_io_ctx(*io_ctx);
+}
+
+class TestMockImageReplayerCreateImageRequest : public TestMockFixture {
+public:
+  typedef CreateImageRequest<librbd::MockTestImageCtx> MockCreateImageRequest;
+  typedef OpenImageRequest<librbd::MockTestImageCtx> MockOpenImageRequest;
+  typedef CloseImageRequest<librbd::MockTestImageCtx> MockCloseImageRequest;
+
+  virtual void SetUp() {
+    TestMockFixture::SetUp();
+
+    librbd::RBD rbd;
+    ASSERT_EQ(0, create_image(rbd, m_remote_io_ctx, m_image_name, m_image_size));
+    ASSERT_EQ(0, open_image(m_remote_io_ctx, m_image_name, &m_remote_image_ctx));
+  }
+
+  int clone_image(librbd::ImageCtx *parent_image_ctx,
+                  const std::string &snap_name, const std::string &clone_name) {
+    {
+      librbd::ImageCtx *ictx = new librbd::ImageCtx(parent_image_ctx->name,
+						    "", "", m_remote_io_ctx,
+                                                    false);
+      ictx->state->open();
+      EXPECT_EQ(0, ictx->operations->snap_create(snap_name.c_str()));
+      EXPECT_EQ(0, ictx->operations->snap_protect(snap_name.c_str()));
+      ictx->state->close();
+    }
+
+    EXPECT_EQ(0, parent_image_ctx->state->refresh());
+
+    int order = 0;
+    return librbd::clone(m_remote_io_ctx, parent_image_ctx->name.c_str(),
+                         snap_name.c_str(), m_remote_io_ctx,
+                         clone_name.c_str(), parent_image_ctx->features,
+                         &order, 0, 0);
+  }
+
+  void expect_create_image(CreateCloneImage &create_clone_image,
+                           const std::string &local_image_name,
+                           const std::string &global_image_id,
+                           const std::string &remote_mirror_uuid, int r) {
+    EXPECT_CALL(create_clone_image, create(local_image_name, global_image_id,
+                                           remote_mirror_uuid))
+      .WillOnce(Return(r));
+  }
+
+  void expect_ioctx_create(librados::IoCtx &io_ctx) {
+    EXPECT_CALL(*get_mock_io_ctx(io_ctx).get_mock_rados_client(), create_ioctx(_, _))
+      .WillOnce(Return(&get_mock_io_ctx(io_ctx)));
+  }
+
+  void expect_get_parent_global_image_id(librados::IoCtx &io_ctx,
+                                         const std::string &global_id, int r) {
+    cls::rbd::MirrorImage mirror_image;
+    mirror_image.global_image_id = global_id;
+
+    bufferlist bl;
+    ::encode(mirror_image, bl);
+
+    EXPECT_CALL(get_mock_io_ctx(io_ctx),
+                exec(RBD_MIRRORING, _, StrEq("rbd"), StrEq("mirror_image_get"), _, _, _))
+      .WillOnce(DoAll(WithArg<5>(Invoke([bl](bufferlist *out_bl) {
+                                          *out_bl = bl;
+                                        })),
+                      Return(r)));
+  }
+
+  void expect_mirror_image_get_image_id(librados::IoCtx &io_ctx,
+                                        const std::string &image_id, int r) {
+    bufferlist bl;
+    ::encode(image_id, bl);
+
+    EXPECT_CALL(get_mock_io_ctx(io_ctx),
+                exec(RBD_MIRRORING, _, StrEq("rbd"), StrEq("mirror_image_get_image_id"), _, _, _))
+      .WillOnce(DoAll(WithArg<5>(Invoke([bl](bufferlist *out_bl) {
+                                          *out_bl = bl;
+                                        })),
+                      Return(r)));
+  }
+
+  void expect_open_image(MockOpenImageRequest &mock_open_image_request,
+                         librados::IoCtx &io_ctx, const std::string &image_id,
+                         librbd::MockTestImageCtx &mock_image_ctx, int r) {
+    EXPECT_CALL(mock_open_image_request, construct(IsSameIoCtx(&io_ctx), image_id));
+    EXPECT_CALL(mock_open_image_request, send())
+      .WillOnce(Invoke([this, &mock_open_image_request, &mock_image_ctx, r]() {
+          *mock_open_image_request.image_ctx = &mock_image_ctx;
+          m_threads->work_queue->queue(mock_open_image_request.on_finish, r);
+        }));
+  }
+
+  void expect_snap_set(librbd::MockTestImageCtx &mock_image_ctx,
+                       const std::string &snap_name, int r) {
+    EXPECT_CALL(*mock_image_ctx.state, snap_set(StrEq(snap_name), _))
+      .WillOnce(WithArg<1>(Invoke([this, r](Context *on_finish) {
+          m_threads->work_queue->queue(on_finish, r);
+        })));
+  }
+
+  void expect_clone_image(CreateCloneImage &create_clone_image,
+                          const std::string &local_image_name,
+                          const std::string &global_image_id,
+                          const std::string &remote_mirror_uuid, int r) {
+    EXPECT_CALL(create_clone_image, clone(local_image_name, global_image_id,
+                                          remote_mirror_uuid))
+      .WillOnce(Return(r));
+  }
+
+  void expect_close_image(MockCloseImageRequest &mock_close_image_request,
+                          librbd::MockTestImageCtx &mock_image_ctx, int r) {
+    EXPECT_CALL(mock_close_image_request, construct(&mock_image_ctx));
+    EXPECT_CALL(mock_close_image_request, send())
+      .WillOnce(Invoke([this, &mock_close_image_request, r]() {
+          m_threads->work_queue->queue(mock_close_image_request.on_finish, r);
+        }));
+  }
+
+  MockCreateImageRequest *create_request(const std::string &global_image_id,
+                                         const std::string &remote_mirror_uuid,
+                                         const std::string &local_image_name,
+                                         librbd::MockTestImageCtx &mock_remote_image_ctx,
+                                         Context *on_finish) {
+    return new MockCreateImageRequest(m_local_io_ctx, m_threads->work_queue,
+                                      global_image_id, remote_mirror_uuid,
+                                      local_image_name, &mock_remote_image_ctx,
+                                      on_finish);
+  }
+
+  librbd::ImageCtx *m_remote_image_ctx;
+};
+
+TEST_F(TestMockImageReplayerCreateImageRequest, Create) {
+  librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+
+  CreateCloneImage create_clone_image;
+
+  InSequence seq;
+  expect_create_image(create_clone_image, "image name", "global uuid",
+                      "remote uuid", 0);
+
+  C_SaferCond ctx;
+  MockCreateImageRequest *request = create_request("global uuid", "remote uuid",
+                                                   "image name",
+                                                   mock_remote_image_ctx, &ctx);
+  request->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerCreateImageRequest, CreateError) {
+  librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+
+  CreateCloneImage create_clone_image;
+
+  InSequence seq;
+  expect_create_image(create_clone_image, "image name", "global uuid",
+                      "remote uuid", -EINVAL);
+
+  C_SaferCond ctx;
+  MockCreateImageRequest *request = create_request("global uuid", "remote uuid",
+                                                   "image name",
+                                                   mock_remote_image_ctx, &ctx);
+  request->send();
+  ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerCreateImageRequest, Clone) {
+  librbd::RBD rbd;
+  librbd::ImageCtx *local_image_ctx;
+  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, &local_image_ctx));
+
+  std::string clone_image_name = get_temp_image_name();
+  ASSERT_EQ(0, clone_image(m_remote_image_ctx, "snap", clone_image_name));
+
+  librbd::ImageCtx *remote_clone_image_ctx;
+  ASSERT_EQ(0, open_image(m_remote_io_ctx, clone_image_name,
+               &remote_clone_image_ctx));
+
+  librbd::MockTestImageCtx mock_remote_parent_image_ctx(*m_remote_image_ctx);
+  librbd::MockTestImageCtx mock_local_parent_image_ctx(*local_image_ctx);
+  librbd::MockTestImageCtx mock_remote_clone_image_ctx(*remote_clone_image_ctx);
+  CreateCloneImage create_clone_image;
+  MockOpenImageRequest mock_open_image_request;
+  MockCloseImageRequest mock_close_image_request;
+
+  InSequence seq;
+  expect_ioctx_create(m_remote_io_ctx);
+  expect_ioctx_create(m_local_io_ctx);
+  expect_get_parent_global_image_id(m_remote_io_ctx, "global uuid", 0);
+  expect_mirror_image_get_image_id(m_local_io_ctx, "local parent id", 0);
+
+  expect_open_image(mock_open_image_request, m_remote_io_ctx,
+                    m_remote_image_ctx->id, mock_remote_parent_image_ctx, 0);
+  expect_open_image(mock_open_image_request, m_local_io_ctx,
+                    "local parent id", mock_local_parent_image_ctx, 0);
+  expect_snap_set(mock_local_parent_image_ctx, "snap", 0);
+  expect_clone_image(create_clone_image, "image name", "global uuid",
+                      "remote uuid", 0);
+  expect_close_image(mock_close_image_request, mock_local_parent_image_ctx, 0);
+  expect_close_image(mock_close_image_request, mock_remote_parent_image_ctx, 0);
+
+  C_SaferCond ctx;
+  MockCreateImageRequest *request = create_request("global uuid", "remote uuid",
+                                                   "image name",
+                                                   mock_remote_clone_image_ctx,
+                                                   &ctx);
+  request->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerCreateImageRequest, CloneGetGlobalImageIdError) {
+  std::string clone_image_name = get_temp_image_name();
+  ASSERT_EQ(0, clone_image(m_remote_image_ctx, "snap", clone_image_name));
+
+  librbd::ImageCtx *remote_clone_image_ctx;
+  ASSERT_EQ(0, open_image(m_remote_io_ctx, clone_image_name,
+               &remote_clone_image_ctx));
+
+  librbd::MockTestImageCtx mock_remote_clone_image_ctx(*remote_clone_image_ctx);
+  CreateCloneImage create_clone_image;
+
+  InSequence seq;
+  expect_ioctx_create(m_remote_io_ctx);
+  expect_ioctx_create(m_local_io_ctx);
+  expect_get_parent_global_image_id(m_remote_io_ctx, "global uuid", -ENOENT);
+
+  C_SaferCond ctx;
+  MockCreateImageRequest *request = create_request("global uuid", "remote uuid",
+                                                   "image name",
+                                                   mock_remote_clone_image_ctx,
+                                                   &ctx);
+  request->send();
+  ASSERT_EQ(-ENOENT, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerCreateImageRequest, CloneGetLocalParentImageIdError) {
+  std::string clone_image_name = get_temp_image_name();
+  ASSERT_EQ(0, clone_image(m_remote_image_ctx, "snap", clone_image_name));
+
+  librbd::ImageCtx *remote_clone_image_ctx;
+  ASSERT_EQ(0, open_image(m_remote_io_ctx, clone_image_name,
+               &remote_clone_image_ctx));
+
+  librbd::MockTestImageCtx mock_remote_clone_image_ctx(*remote_clone_image_ctx);
+  CreateCloneImage create_clone_image;
+
+  InSequence seq;
+  expect_ioctx_create(m_remote_io_ctx);
+  expect_ioctx_create(m_local_io_ctx);
+  expect_get_parent_global_image_id(m_remote_io_ctx, "global uuid", 0);
+  expect_mirror_image_get_image_id(m_local_io_ctx, "local parent id", -ENOENT);
+
+  C_SaferCond ctx;
+  MockCreateImageRequest *request = create_request("global uuid", "remote uuid",
+                                                   "image name",
+                                                   mock_remote_clone_image_ctx,
+                                                   &ctx);
+  request->send();
+  ASSERT_EQ(-ENOENT, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerCreateImageRequest, CloneOpenRemoteParentError) {
+  std::string clone_image_name = get_temp_image_name();
+  ASSERT_EQ(0, clone_image(m_remote_image_ctx, "snap", clone_image_name));
+
+  librbd::ImageCtx *remote_clone_image_ctx;
+  ASSERT_EQ(0, open_image(m_remote_io_ctx, clone_image_name,
+               &remote_clone_image_ctx));
+
+  librbd::MockTestImageCtx mock_remote_parent_image_ctx(*m_remote_image_ctx);
+  librbd::MockTestImageCtx mock_remote_clone_image_ctx(*remote_clone_image_ctx);
+  CreateCloneImage create_clone_image;
+  MockOpenImageRequest mock_open_image_request;
+
+  InSequence seq;
+  expect_ioctx_create(m_remote_io_ctx);
+  expect_ioctx_create(m_local_io_ctx);
+  expect_get_parent_global_image_id(m_remote_io_ctx, "global uuid", 0);
+  expect_mirror_image_get_image_id(m_local_io_ctx, "local parent id", 0);
+
+  expect_open_image(mock_open_image_request, m_remote_io_ctx,
+                    m_remote_image_ctx->id, mock_remote_parent_image_ctx, -ENOENT);
+
+  C_SaferCond ctx;
+  MockCreateImageRequest *request = create_request("global uuid", "remote uuid",
+                                                   "image name",
+                                                   mock_remote_clone_image_ctx,
+                                                   &ctx);
+  request->send();
+  ASSERT_EQ(-ENOENT, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerCreateImageRequest, CloneOpenLocalParentError) {
+  librbd::RBD rbd;
+  librbd::ImageCtx *local_image_ctx;
+  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, &local_image_ctx));
+
+  std::string clone_image_name = get_temp_image_name();
+  ASSERT_EQ(0, clone_image(m_remote_image_ctx, "snap", clone_image_name));
+
+  librbd::ImageCtx *remote_clone_image_ctx;
+  ASSERT_EQ(0, open_image(m_remote_io_ctx, clone_image_name,
+               &remote_clone_image_ctx));
+
+  librbd::MockTestImageCtx mock_remote_parent_image_ctx(*m_remote_image_ctx);
+  librbd::MockTestImageCtx mock_local_parent_image_ctx(*local_image_ctx);
+  librbd::MockTestImageCtx mock_remote_clone_image_ctx(*remote_clone_image_ctx);
+  CreateCloneImage create_clone_image;
+  MockOpenImageRequest mock_open_image_request;
+  MockCloseImageRequest mock_close_image_request;
+
+  InSequence seq;
+  expect_ioctx_create(m_remote_io_ctx);
+  expect_ioctx_create(m_local_io_ctx);
+  expect_get_parent_global_image_id(m_remote_io_ctx, "global uuid", 0);
+  expect_mirror_image_get_image_id(m_local_io_ctx, "local parent id", 0);
+
+  expect_open_image(mock_open_image_request, m_remote_io_ctx,
+                    m_remote_image_ctx->id, mock_remote_parent_image_ctx, 0);
+  expect_open_image(mock_open_image_request, m_local_io_ctx,
+                    "local parent id", mock_local_parent_image_ctx, -ENOENT);
+  expect_close_image(mock_close_image_request, mock_remote_parent_image_ctx, 0);
+
+  C_SaferCond ctx;
+  MockCreateImageRequest *request = create_request("global uuid", "remote uuid",
+                                                   "image name",
+                                                   mock_remote_clone_image_ctx,
+                                                   &ctx);
+  request->send();
+  ASSERT_EQ(-ENOENT, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerCreateImageRequest, CloneSnapSetError) {
+  librbd::RBD rbd;
+  librbd::ImageCtx *local_image_ctx;
+  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, &local_image_ctx));
+
+  std::string clone_image_name = get_temp_image_name();
+  ASSERT_EQ(0, clone_image(m_remote_image_ctx, "snap", clone_image_name));
+
+  librbd::ImageCtx *remote_clone_image_ctx;
+  ASSERT_EQ(0, open_image(m_remote_io_ctx, clone_image_name,
+               &remote_clone_image_ctx));
+
+  librbd::MockTestImageCtx mock_remote_parent_image_ctx(*m_remote_image_ctx);
+  librbd::MockTestImageCtx mock_local_parent_image_ctx(*local_image_ctx);
+  librbd::MockTestImageCtx mock_remote_clone_image_ctx(*remote_clone_image_ctx);
+  CreateCloneImage create_clone_image;
+  MockOpenImageRequest mock_open_image_request;
+  MockCloseImageRequest mock_close_image_request;
+
+  InSequence seq;
+  expect_ioctx_create(m_remote_io_ctx);
+  expect_ioctx_create(m_local_io_ctx);
+  expect_get_parent_global_image_id(m_remote_io_ctx, "global uuid", 0);
+  expect_mirror_image_get_image_id(m_local_io_ctx, "local parent id", 0);
+
+  expect_open_image(mock_open_image_request, m_remote_io_ctx,
+                    m_remote_image_ctx->id, mock_remote_parent_image_ctx, 0);
+  expect_open_image(mock_open_image_request, m_local_io_ctx,
+                    "local parent id", mock_local_parent_image_ctx, 0);
+  expect_snap_set(mock_local_parent_image_ctx, "snap", -ENOENT);
+  expect_close_image(mock_close_image_request, mock_local_parent_image_ctx, 0);
+  expect_close_image(mock_close_image_request, mock_remote_parent_image_ctx, 0);
+
+  C_SaferCond ctx;
+  MockCreateImageRequest *request = create_request("global uuid", "remote uuid",
+                                                   "image name",
+                                                   mock_remote_clone_image_ctx,
+                                                   &ctx);
+  request->send();
+  ASSERT_EQ(-ENOENT, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerCreateImageRequest, CloneError) {
+  librbd::RBD rbd;
+  librbd::ImageCtx *local_image_ctx;
+  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, &local_image_ctx));
+
+  std::string clone_image_name = get_temp_image_name();
+  ASSERT_EQ(0, clone_image(m_remote_image_ctx, "snap", clone_image_name));
+
+  librbd::ImageCtx *remote_clone_image_ctx;
+  ASSERT_EQ(0, open_image(m_remote_io_ctx, clone_image_name,
+               &remote_clone_image_ctx));
+
+  librbd::MockTestImageCtx mock_remote_parent_image_ctx(*m_remote_image_ctx);
+  librbd::MockTestImageCtx mock_local_parent_image_ctx(*local_image_ctx);
+  librbd::MockTestImageCtx mock_remote_clone_image_ctx(*remote_clone_image_ctx);
+  CreateCloneImage create_clone_image;
+  MockOpenImageRequest mock_open_image_request;
+  MockCloseImageRequest mock_close_image_request;
+
+  InSequence seq;
+  expect_ioctx_create(m_remote_io_ctx);
+  expect_ioctx_create(m_local_io_ctx);
+  expect_get_parent_global_image_id(m_remote_io_ctx, "global uuid", 0);
+  expect_mirror_image_get_image_id(m_local_io_ctx, "local parent id", 0);
+
+  expect_open_image(mock_open_image_request, m_remote_io_ctx,
+                    m_remote_image_ctx->id, mock_remote_parent_image_ctx, 0);
+  expect_open_image(mock_open_image_request, m_local_io_ctx,
+                    "local parent id", mock_local_parent_image_ctx, 0);
+  expect_snap_set(mock_local_parent_image_ctx, "snap", 0);
+  expect_clone_image(create_clone_image, "image name", "global uuid",
+                      "remote uuid", -EINVAL);
+  expect_close_image(mock_close_image_request, mock_local_parent_image_ctx, 0);
+  expect_close_image(mock_close_image_request, mock_remote_parent_image_ctx, 0);
+
+  C_SaferCond ctx;
+  MockCreateImageRequest *request = create_request("global uuid", "remote uuid",
+                                                   "image name",
+                                                   mock_remote_clone_image_ctx,
+                                                   &ctx);
+  request->send();
+  ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerCreateImageRequest, CloneLocalParentCloseError) {
+  librbd::RBD rbd;
+  librbd::ImageCtx *local_image_ctx;
+  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, &local_image_ctx));
+
+  std::string clone_image_name = get_temp_image_name();
+  ASSERT_EQ(0, clone_image(m_remote_image_ctx, "snap", clone_image_name));
+
+  librbd::ImageCtx *remote_clone_image_ctx;
+  ASSERT_EQ(0, open_image(m_remote_io_ctx, clone_image_name,
+               &remote_clone_image_ctx));
+
+  librbd::MockTestImageCtx mock_remote_parent_image_ctx(*m_remote_image_ctx);
+  librbd::MockTestImageCtx mock_local_parent_image_ctx(*local_image_ctx);
+  librbd::MockTestImageCtx mock_remote_clone_image_ctx(*remote_clone_image_ctx);
+  CreateCloneImage create_clone_image;
+  MockOpenImageRequest mock_open_image_request;
+  MockCloseImageRequest mock_close_image_request;
+
+  InSequence seq;
+  expect_ioctx_create(m_remote_io_ctx);
+  expect_ioctx_create(m_local_io_ctx);
+  expect_get_parent_global_image_id(m_remote_io_ctx, "global uuid", 0);
+  expect_mirror_image_get_image_id(m_local_io_ctx, "local parent id", 0);
+
+  expect_open_image(mock_open_image_request, m_remote_io_ctx,
+                    m_remote_image_ctx->id, mock_remote_parent_image_ctx, 0);
+  expect_open_image(mock_open_image_request, m_local_io_ctx,
+                    "local parent id", mock_local_parent_image_ctx, 0);
+  expect_snap_set(mock_local_parent_image_ctx, "snap", 0);
+  expect_clone_image(create_clone_image, "image name", "global uuid",
+                      "remote uuid", 0);
+  expect_close_image(mock_close_image_request, mock_local_parent_image_ctx, -EINVAL);
+  expect_close_image(mock_close_image_request, mock_remote_parent_image_ctx, 0);
+
+  C_SaferCond ctx;
+  MockCreateImageRequest *request = create_request("global uuid", "remote uuid",
+                                                   "image name",
+                                                   mock_remote_clone_image_ctx,
+                                                   &ctx);
+  request->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerCreateImageRequest, CloneRemoteParentCloseError) {
+  librbd::RBD rbd;
+  librbd::ImageCtx *local_image_ctx;
+  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, &local_image_ctx));
+
+  std::string clone_image_name = get_temp_image_name();
+  ASSERT_EQ(0, clone_image(m_remote_image_ctx, "snap", clone_image_name));
+
+  librbd::ImageCtx *remote_clone_image_ctx;
+  ASSERT_EQ(0, open_image(m_remote_io_ctx, clone_image_name,
+               &remote_clone_image_ctx));
+
+  librbd::MockTestImageCtx mock_remote_parent_image_ctx(*m_remote_image_ctx);
+  librbd::MockTestImageCtx mock_local_parent_image_ctx(*local_image_ctx);
+  librbd::MockTestImageCtx mock_remote_clone_image_ctx(*remote_clone_image_ctx);
+  CreateCloneImage create_clone_image;
+  MockOpenImageRequest mock_open_image_request;
+  MockCloseImageRequest mock_close_image_request;
+
+  InSequence seq;
+  expect_ioctx_create(m_remote_io_ctx);
+  expect_ioctx_create(m_local_io_ctx);
+  expect_get_parent_global_image_id(m_remote_io_ctx, "global uuid", 0);
+  expect_mirror_image_get_image_id(m_local_io_ctx, "local parent id", 0);
+
+  expect_open_image(mock_open_image_request, m_remote_io_ctx,
+                    m_remote_image_ctx->id, mock_remote_parent_image_ctx, 0);
+  expect_open_image(mock_open_image_request, m_local_io_ctx,
+                    "local parent id", mock_local_parent_image_ctx, 0);
+  expect_snap_set(mock_local_parent_image_ctx, "snap", 0);
+  expect_clone_image(create_clone_image, "image name", "global uuid",
+                      "remote uuid", 0);
+  expect_close_image(mock_close_image_request, mock_local_parent_image_ctx, 0);
+  expect_close_image(mock_close_image_request, mock_remote_parent_image_ctx, -EINVAL);
+
+  C_SaferCond ctx;
+  MockCreateImageRequest *request = create_request("global uuid", "remote uuid",
+                                                   "image name",
+                                                   mock_remote_clone_image_ctx,
+                                                   &ctx);
+  request->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+} // 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 0b72167..abc6cc0 100644
--- a/src/test/rbd_mirror/image_sync/test_mock_ImageCopyRequest.cc
+++ b/src/test/rbd_mirror/image_sync/test_mock_ImageCopyRequest.cc
@@ -77,6 +77,7 @@ using ::testing::InSequence;
 using ::testing::Invoke;
 using ::testing::Return;
 using ::testing::WithArg;
+using ::testing::InvokeWithoutArgs;
 
 class TestMockImageSyncImageCopyRequest : public TestMockFixture {
 public:
@@ -358,10 +359,10 @@ TEST_F(TestMockImageSyncImageCopyRequest, RestartPartialSync) {
 
 TEST_F(TestMockImageSyncImageCopyRequest, Cancel) {
   std::string max_ops_str;
-  ASSERT_EQ(0, _rados.conf_get("rbd_concurrent_management_ops", max_ops_str));
-  ASSERT_EQ(0, _rados.conf_set("rbd_concurrent_management_ops", "1"));
+  ASSERT_EQ(0, _rados->conf_get("rbd_concurrent_management_ops", max_ops_str));
+  ASSERT_EQ(0, _rados->conf_set("rbd_concurrent_management_ops", "1"));
   BOOST_SCOPE_EXIT( (max_ops_str) ) {
-    ASSERT_EQ(0, _rados.conf_set("rbd_concurrent_management_ops", max_ops_str.c_str()));
+    ASSERT_EQ(0, _rados->conf_set("rbd_concurrent_management_ops", max_ops_str.c_str()));
   } BOOST_SCOPE_EXIT_END;
 
   ASSERT_EQ(0, create_snap("snap1"));
@@ -379,7 +380,6 @@ TEST_F(TestMockImageSyncImageCopyRequest, Cancel) {
   expect_get_object_count(mock_remote_image_ctx, 2);
   expect_update_client(mock_journaler, 0);
   expect_object_copy_send(mock_object_copy_request);
-  expect_update_client(mock_journaler, 0);
 
   C_SaferCond ctx;
   MockImageCopyRequest *request = create_request(mock_remote_image_ctx,
@@ -393,7 +393,38 @@ TEST_F(TestMockImageSyncImageCopyRequest, Cancel) {
   request->cancel();
 
   ASSERT_TRUE(complete_object_copy(mock_object_copy_request, 0, 0));
-  ASSERT_EQ(0, ctx.wait());
+  ASSERT_EQ(-ECANCELED, ctx.wait());
+}
+
+TEST_F(TestMockImageSyncImageCopyRequest, Cancel1) {
+  ASSERT_EQ(0, create_snap("snap1"));
+  m_client_meta.sync_points = {{"snap1", boost::none}};
+
+  librbd::MockImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+  librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+  journal::MockJournaler mock_journaler;
+  MockObjectCopyRequest mock_object_copy_request;
+
+  C_SaferCond ctx;
+  MockImageCopyRequest *request = create_request(mock_remote_image_ctx,
+                                                 mock_local_image_ctx,
+                                                 mock_journaler,
+                                                 m_client_meta.sync_points.front(),
+                                                 &ctx);
+
+  expect_get_snap_id(mock_remote_image_ctx);
+
+  InSequence seq;
+  expect_get_object_count(mock_remote_image_ctx, 1);
+  expect_get_object_count(mock_remote_image_ctx, 0);
+  EXPECT_CALL(mock_journaler, update_client(_, _))
+    .WillOnce(DoAll(InvokeWithoutArgs([request]() {
+	    request->cancel();
+	  }),
+	WithArg<1>(CompleteContext(0))));
+
+  request->send();
+  ASSERT_EQ(-ECANCELED, ctx.wait());
 }
 
 TEST_F(TestMockImageSyncImageCopyRequest, MissingSnap) {
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 b3e4a12..bcb9fd6 100644
--- a/src/test/rbd_mirror/image_sync/test_mock_SnapshotCopyRequest.cc
+++ b/src/test/rbd_mirror/image_sync/test_mock_SnapshotCopyRequest.cc
@@ -34,7 +34,10 @@ 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) {
+                                       uint64_t size,
+                                       const librbd::parent_spec &parent_spec,
+                                       uint64_t parent_overlap,
+                                       Context *on_finish) {
     assert(s_instance != nullptr);
     s_instance->on_finish = on_finish;
     return s_instance;
@@ -157,7 +160,7 @@ public:
     return new MockSnapshotCopyRequest(&mock_local_image_ctx,
                                        &mock_remote_image_ctx, &m_snap_map,
                                        &mock_journaler, &m_client_meta,
-                                       on_finish);
+                                       m_threads->work_queue, on_finish);
   }
 
   int create_snap(librbd::ImageCtx *image_ctx, const std::string &snap_name,
@@ -231,6 +234,26 @@ TEST_F(TestMockImageSyncSnapshotCopyRequest, UpdateClientError) {
   ASSERT_EQ(-EINVAL, ctx.wait());
 }
 
+TEST_F(TestMockImageSyncSnapshotCopyRequest, UpdateClientCancel) {
+  librbd::MockImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+  librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+  journal::MockJournaler mock_journaler;
+
+  C_SaferCond ctx;
+  MockSnapshotCopyRequest *request = create_request(mock_remote_image_ctx,
+                                                    mock_local_image_ctx,
+                                                    mock_journaler, &ctx);
+  InSequence seq;
+  EXPECT_CALL(mock_journaler, update_client(_, _))
+    .WillOnce(DoAll(InvokeWithoutArgs([request]() {
+	    request->cancel();
+	  }),
+	WithArg<1>(CompleteContext(0))));
+
+  request->send();
+  ASSERT_EQ(-ECANCELED, ctx.wait());
+}
+
 TEST_F(TestMockImageSyncSnapshotCopyRequest, SnapCreate) {
   ASSERT_EQ(0, create_snap(m_remote_image_ctx, "snap1"));
   ASSERT_EQ(0, create_snap(m_remote_image_ctx, "snap2"));
@@ -280,6 +303,31 @@ TEST_F(TestMockImageSyncSnapshotCopyRequest, SnapCreateError) {
   ASSERT_EQ(-EINVAL, ctx.wait());
 }
 
+TEST_F(TestMockImageSyncSnapshotCopyRequest, SnapCreateCancel) {
+  ASSERT_EQ(0, create_snap(m_remote_image_ctx, "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;
+
+  C_SaferCond ctx;
+  MockSnapshotCopyRequest *request = create_request(mock_remote_image_ctx,
+                                                    mock_local_image_ctx,
+                                                    mock_journaler, &ctx);
+  InSequence seq;
+  EXPECT_CALL(mock_snapshot_create_request, send())
+    .WillOnce(DoAll(InvokeWithoutArgs([request]() {
+	    request->cancel();
+	  }),
+	Invoke([this, &mock_snapshot_create_request]() {
+	    m_threads->work_queue->queue(mock_snapshot_create_request.on_finish, 0);
+	  })));
+
+  request->send();
+  ASSERT_EQ(-ECANCELED, ctx.wait());
+}
+
 TEST_F(TestMockImageSyncSnapshotCopyRequest, SnapRemoveAndCreate) {
   ASSERT_EQ(0, create_snap(m_remote_image_ctx, "snap1"));
   ASSERT_EQ(0, create_snap(m_local_image_ctx, "snap1"));
@@ -385,6 +433,38 @@ TEST_F(TestMockImageSyncSnapshotCopyRequest, SnapUnprotectError) {
   ASSERT_EQ(-EBUSY, ctx.wait());
 }
 
+TEST_F(TestMockImageSyncSnapshotCopyRequest, SnapUnprotectCancel) {
+  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;
+
+  C_SaferCond ctx;
+  MockSnapshotCopyRequest *request = create_request(mock_remote_image_ctx,
+                                                    mock_local_image_ctx,
+                                                    mock_journaler, &ctx);
+  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_CALL(*mock_local_image_ctx.operations,
+	      execute_snap_unprotect(StrEq("snap1"), _))
+    .WillOnce(DoAll(InvokeWithoutArgs([request]() {
+	    request->cancel();
+	  }),
+	WithArg<1>(Invoke([this](Context *ctx) {
+	    m_threads->work_queue->queue(ctx, 0);
+	    }))));
+
+  request->send();
+  ASSERT_EQ(-ECANCELED, 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));
@@ -500,6 +580,39 @@ TEST_F(TestMockImageSyncSnapshotCopyRequest, SnapProtectError) {
   ASSERT_EQ(-EINVAL, ctx.wait());
 }
 
+TEST_F(TestMockImageSyncSnapshotCopyRequest, SnapProtectCancel) {
+  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;
+
+  C_SaferCond ctx;
+  MockSnapshotCopyRequest *request = create_request(mock_remote_image_ctx,
+                                                    mock_local_image_ctx,
+                                                    mock_journaler, &ctx);
+  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_CALL(*mock_local_image_ctx.operations,
+	      execute_snap_protect(StrEq("snap1"), _))
+    .WillOnce(DoAll(InvokeWithoutArgs([request]() {
+	    request->cancel();
+	  }),
+	WithArg<1>(Invoke([this](Context *ctx) {
+	      m_threads->work_queue->queue(ctx, 0);
+	    }))));
+
+  request->send();
+  ASSERT_EQ(-ECANCELED, 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
index dd8305d..8cf6217 100644
--- a/src/test/rbd_mirror/image_sync/test_mock_SnapshotCreateRequest.cc
+++ b/src/test/rbd_mirror/image_sync/test_mock_SnapshotCreateRequest.cc
@@ -54,6 +54,18 @@ public:
                   .WillOnce(Return(r));
   }
 
+  void expect_remove_parent(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("remove_parent"), _, _, _))
+                  .WillOnce(Return(r));
+  }
+
+  void expect_set_parent(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_parent"), _, _, _))
+                  .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))
@@ -81,9 +93,12 @@ public:
 
   MockSnapshotCreateRequest *create_request(librbd::MockImageCtx &mock_local_image_ctx,
                                             const std::string &snap_name,
-                                            uint64_t size, Context *on_finish) {
+                                            uint64_t size,
+                                            const librbd::parent_spec &spec,
+                                            uint64_t parent_overlap,
+                                            Context *on_finish) {
     return new MockSnapshotCreateRequest(&mock_local_image_ctx, snap_name, size,
-                                         on_finish);
+                                         spec, parent_overlap, on_finish);
   }
 
   librbd::ImageCtx *m_local_image_ctx;
@@ -99,7 +114,7 @@ TEST_F(TestMockImageSyncSnapshotCreateRequest, Resize) {
 
   C_SaferCond ctx;
   MockSnapshotCreateRequest *request = create_request(mock_local_image_ctx,
-                                                      "snap1", 123,
+                                                      "snap1", 123, {}, 0,
                                                       &ctx);
   request->send();
   ASSERT_EQ(0, ctx.wait());
@@ -113,16 +128,71 @@ TEST_F(TestMockImageSyncSnapshotCreateRequest, ResizeError) {
 
   C_SaferCond ctx;
   MockSnapshotCreateRequest *request = create_request(mock_local_image_ctx,
-                                                      "snap1", 123,
+                                                      "snap1", 123, {}, 0,
                                                       &ctx);
   request->send();
   ASSERT_EQ(-EINVAL, ctx.wait());
 }
 
-TEST_F(TestMockImageSyncSnapshotCreateRequest, SnapCreate) {
+TEST_F(TestMockImageSyncSnapshotCreateRequest, RemoveParent) {
+  librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+  mock_local_image_ctx.parent_md.spec.pool_id = 213;
+
+  InSequence seq;
+  expect_remove_parent(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",
+                                                      m_local_image_ctx->size,
+                                                      {}, 0, &ctx);
+  request->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageSyncSnapshotCreateRequest, RemoveParentError) {
+  librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+  mock_local_image_ctx.parent_md.spec.pool_id = 213;
+
+  InSequence seq;
+  expect_remove_parent(mock_local_image_ctx, -EINVAL);
+
+  C_SaferCond ctx;
+  MockSnapshotCreateRequest *request = create_request(mock_local_image_ctx,
+                                                      "snap1",
+                                                      m_local_image_ctx->size,
+                                                      {}, 0, &ctx);
+  request->send();
+  ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageSyncSnapshotCreateRequest, RemoveSetParent) {
+  librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+  mock_local_image_ctx.parent_md.spec.pool_id = 213;
+
+  InSequence seq;
+  expect_remove_parent(mock_local_image_ctx, 0);
+  expect_set_parent(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",
+                                                      m_local_image_ctx->size,
+                                                      {123, "test", 0}, 0,
+                                                      &ctx);
+  request->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageSyncSnapshotCreateRequest, SetParentSpec) {
   librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
 
   InSequence seq;
+  expect_set_parent(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);
 
@@ -130,11 +200,63 @@ TEST_F(TestMockImageSyncSnapshotCreateRequest, SnapCreate) {
   MockSnapshotCreateRequest *request = create_request(mock_local_image_ctx,
                                                       "snap1",
                                                       m_local_image_ctx->size,
+                                                      {123, "test", 0}, 0,
                                                       &ctx);
   request->send();
   ASSERT_EQ(0, ctx.wait());
 }
 
+TEST_F(TestMockImageSyncSnapshotCreateRequest, SetParentOverlap) {
+  librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+  mock_local_image_ctx.parent_md.spec = {123, "test", 0};
+
+  InSequence seq;
+  expect_set_parent(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",
+                                                      m_local_image_ctx->size,
+                                                      mock_local_image_ctx.parent_md.spec,
+                                                      123, &ctx);
+  request->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageSyncSnapshotCreateRequest, SetParentError) {
+  librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+
+  InSequence seq;
+  expect_set_parent(mock_local_image_ctx, -ESTALE);
+
+  C_SaferCond ctx;
+  MockSnapshotCreateRequest *request = create_request(mock_local_image_ctx,
+                                                      "snap1",
+                                                      m_local_image_ctx->size,
+                                                      {123, "test", 0}, 0,
+                                                      &ctx);
+  request->send();
+  ASSERT_EQ(-ESTALE, 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,
+                                                      {}, 0, &ctx);
+  request->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
 TEST_F(TestMockImageSyncSnapshotCreateRequest, SnapCreateError) {
   librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
 
@@ -145,7 +267,7 @@ TEST_F(TestMockImageSyncSnapshotCreateRequest, SnapCreateError) {
   MockSnapshotCreateRequest *request = create_request(mock_local_image_ctx,
                                                       "snap1",
                                                       m_local_image_ctx->size,
-                                                      &ctx);
+                                                      {}, 0, &ctx);
   request->send();
   ASSERT_EQ(-EINVAL, ctx.wait());
 }
@@ -162,7 +284,7 @@ TEST_F(TestMockImageSyncSnapshotCreateRequest, ResizeObjectMap) {
   MockSnapshotCreateRequest *request = create_request(mock_local_image_ctx,
                                                       "snap1",
                                                       m_local_image_ctx->size,
-                                                      &ctx);
+                                                      {}, 0, &ctx);
   request->send();
   ASSERT_EQ(0, ctx.wait());
 }
@@ -179,7 +301,7 @@ TEST_F(TestMockImageSyncSnapshotCreateRequest, ResizeObjectMapError) {
   MockSnapshotCreateRequest *request = create_request(mock_local_image_ctx,
                                                       "snap1",
                                                       m_local_image_ctx->size,
-                                                      &ctx);
+                                                      {}, 0, &ctx);
   request->send();
   ASSERT_EQ(-EINVAL, ctx.wait());
 }
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 75c6479..faff6a7 100644
--- a/src/test/rbd_mirror/image_sync/test_mock_SyncPointCreateRequest.cc
+++ b/src/test/rbd_mirror/image_sync/test_mock_SyncPointCreateRequest.cc
@@ -79,6 +79,7 @@ TEST_F(TestMockImageSyncSyncPointCreateRequest, Success) {
   expect_update_client(mock_journaler, 0);
   expect_image_refresh(mock_remote_image_ctx, 0);
   expect_snap_create(mock_remote_image_ctx, 0);
+  expect_image_refresh(mock_remote_image_ctx, 0);
 
   C_SaferCond ctx;
   MockSyncPointCreateRequest *req = create_request(mock_remote_image_ctx,
@@ -100,6 +101,7 @@ TEST_F(TestMockImageSyncSyncPointCreateRequest, ResyncSuccess) {
   expect_update_client(mock_journaler, 0);
   expect_image_refresh(mock_remote_image_ctx, 0);
   expect_snap_create(mock_remote_image_ctx, 0);
+  expect_image_refresh(mock_remote_image_ctx, 0);
 
   C_SaferCond ctx;
   MockSyncPointCreateRequest *req = create_request(mock_remote_image_ctx,
@@ -123,6 +125,7 @@ TEST_F(TestMockImageSyncSyncPointCreateRequest, SnapshotExists) {
   expect_update_client(mock_journaler, 0);
   expect_image_refresh(mock_remote_image_ctx, 0);
   expect_snap_create(mock_remote_image_ctx, 0);
+  expect_image_refresh(mock_remote_image_ctx, 0);
 
   C_SaferCond ctx;
   MockSyncPointCreateRequest *req = create_request(mock_remote_image_ctx,
diff --git a/src/test/rbd_mirror/random_write.cc b/src/test/rbd_mirror/random_write.cc
new file mode 100644
index 0000000..61c463b
--- /dev/null
+++ b/src/test/rbd_mirror/random_write.cc
@@ -0,0 +1,214 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "common/ceph_argparse.h"
+#include "common/config.h"
+#include "common/debug.h"
+#include "common/errno.h"
+#include "common/Cond.h"
+#include "include/rados/librados.hpp"
+#include "include/rbd/librbd.hpp"
+#include "global/global_init.h"
+#include <string>
+#include <vector>
+
+#define dout_subsys ceph_subsys_rbd_mirror
+#undef dout_prefix
+#define dout_prefix *_dout << "random-write: "
+
+namespace {
+
+const uint32_t NUM_THREADS = 8;
+const uint32_t MAX_IO_SIZE = 24576;
+const uint32_t MIN_IO_SIZE = 4;
+
+void usage() {
+  std::cout << "usage: ceph_test_rbd_mirror_random_write [options...] \\" << std::endl;
+  std::cout << "           <pool> <image>" << std::endl;
+  std::cout << std::endl;
+  std::cout << "  pool                 image pool" << std::endl;
+  std::cout << "  image         image to write" << std::endl;
+  std::cout << std::endl;
+  std::cout << "options:\n";
+  std::cout << "  -m monaddress[:port]      connect to specified monitor\n";
+  std::cout << "  --keyring=<path>          path to keyring for local cluster\n";
+  std::cout << "  --log-file=<logfile>      file to log debug output\n";
+  std::cout << "  --debug-rbd-mirror=<log-level>/<memory-level>  set rbd-mirror debug level\n";
+  generic_server_usage();
+}
+
+void rbd_bencher_completion(void *c, void *pc);
+
+struct rbd_bencher {
+  librbd::Image *image;
+  Mutex lock;
+  Cond cond;
+  int in_flight;
+
+  explicit rbd_bencher(librbd::Image *i)
+    : image(i),
+      lock("rbd_bencher::lock"),
+      in_flight(0) {
+  }
+
+  bool start_write(int max, uint64_t off, uint64_t len, bufferlist& bl,
+                   int op_flags) {
+    {
+      Mutex::Locker l(lock);
+      if (in_flight >= max)
+        return false;
+      in_flight++;
+    }
+    librbd::RBD::AioCompletion *c =
+      new librbd::RBD::AioCompletion((void *)this, rbd_bencher_completion);
+    image->aio_write2(off, len, bl, c, op_flags);
+    //cout << "start " << c << " at " << off << "~" << len << std::endl;
+    return true;
+  }
+
+  void wait_for(int max) {
+    Mutex::Locker l(lock);
+    while (in_flight > max) {
+      utime_t dur;
+      dur.set_from_double(.2);
+      cond.WaitInterval(g_ceph_context, lock, dur);
+    }
+  }
+
+};
+
+void rbd_bencher_completion(void *vc, void *pc) {
+  librbd::RBD::AioCompletion *c = (librbd::RBD::AioCompletion *)vc;
+  rbd_bencher *b = static_cast<rbd_bencher *>(pc);
+  //cout << "complete " << c << std::endl;
+  int ret = c->get_return_value();
+  if (ret != 0) {
+    cout << "write error: " << cpp_strerror(ret) << std::endl;
+    exit(ret < 0 ? -ret : ret);
+  }
+  b->lock.Lock();
+  b->in_flight--;
+  b->cond.Signal();
+  b->lock.Unlock();
+  c->release();
+}
+
+void write_image(librbd::Image &image) {
+  srand(time(NULL) % (unsigned long) -1);
+
+  uint64_t max_io_bytes = MAX_IO_SIZE * 1024;
+  bufferptr bp(max_io_bytes);
+  memset(bp.c_str(), rand() & 0xff, bp.length());
+  bufferlist bl;
+  bl.push_back(bp);
+
+  uint64_t size = 0;
+  image.size(&size);
+  assert(size != 0);
+
+  vector<uint64_t> thread_offset;
+  uint64_t i;
+  uint64_t start_pos;
+
+  // disturb all thread's offset, used by seq write
+  for (i = 0; i < NUM_THREADS; i++) {
+    start_pos = (rand() % (size / max_io_bytes)) * max_io_bytes;
+    thread_offset.push_back(start_pos);
+  }
+
+  uint64_t total_ios = 0;
+  uint64_t total_bytes = 0;
+  rbd_bencher b(&image);
+  while (true) {
+    b.wait_for(NUM_THREADS - 1);
+    for (uint32_t i = 0; i < NUM_THREADS; ++i) {
+      // mostly small writes with a small chance of large writes
+      uint32_t io_modulo = MIN_IO_SIZE + 1;
+      if (rand() % 30 == 0) {
+        io_modulo += MAX_IO_SIZE;
+      }
+
+      uint32_t io_size = (((rand() % io_modulo) + MIN_IO_SIZE) * 1024);
+      thread_offset[i] = (rand() % (size / io_size)) * io_size;
+      if (!b.start_write(NUM_THREADS, thread_offset[i], io_size, bl,
+                         LIBRADOS_OP_FLAG_FADVISE_RANDOM)) {
+        break;
+      }
+      ++i;
+
+      ++total_ios;
+      total_bytes += io_size;
+      if (total_ios % 100 == 0) {
+        std::cout << total_ios << " IOs, " << total_bytes << " bytes"
+                  << std::endl;
+      }
+    }
+  }
+  b.wait_for(0);
+}
+
+} // anonymous namespace
+
+int main(int argc, const char **argv)
+{
+  std::vector<const char*> args;
+  argv_to_vec(argc, argv, args);
+  env_to_vec(args);
+
+  global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT, CODE_ENVIRONMENT_UTILITY, 0);
+
+  for (auto i = args.begin(); i != args.end(); ++i) {
+    if (ceph_argparse_flag(args, i, "-h", "--help", (char*)NULL)) {
+      usage();
+      return EXIT_SUCCESS;
+    }
+  }
+
+  if (args.size() < 2) {
+    usage();
+    return EXIT_FAILURE;
+  }
+
+  std::string pool_name = args[0];
+  std::string image_name = args[1];
+
+  common_init_finish(g_ceph_context);
+
+  dout(5) << "connecting to cluster" << dendl;
+  librados::Rados rados;
+  librados::IoCtx io_ctx;
+  librbd::RBD rbd;
+  librbd::Image image;
+  int r = rados.init_with_context(g_ceph_context);
+  if (r < 0) {
+    derr << "could not initialize RADOS handle" << dendl;
+    goto cleanup;
+  }
+
+  r = rados.connect();
+  if (r < 0) {
+    derr << "error connecting to local cluster" << dendl;
+    goto cleanup;
+  }
+
+  r = rados.ioctx_create(pool_name.c_str(), io_ctx);
+  if (r < 0) {
+    derr << "error finding local pool " << pool_name << ": "
+	 << cpp_strerror(r) << dendl;
+    goto cleanup;
+  }
+
+  r = rbd.open(io_ctx, image, image_name.c_str());
+  if (r < 0) {
+    derr << "error opening image " << image_name << ": "
+         << cpp_strerror(r) << dendl;
+    goto cleanup;
+  }
+
+  write_image(image);
+
+ cleanup:
+  g_ceph_context->put();
+
+  return r < 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/test/rbd_mirror/test_ClusterWatcher.cc b/src/test/rbd_mirror/test_ClusterWatcher.cc
index 2d7d3f2..204064c 100644
--- a/src/test/rbd_mirror/test_ClusterWatcher.cc
+++ b/src/test/rbd_mirror/test_ClusterWatcher.cc
@@ -60,7 +60,7 @@ public:
                                            uuid != nullptr ? uuid : &gen_uuid,
 					   peer.cluster_name,
 					   peer.client_name));
-      m_peer_configs[peer].insert(pool_id);
+      m_pool_peers[pool_id].insert(peer);
       m_mirrored_pools.insert(pool_name);
     }
     if (name != nullptr) {
@@ -71,11 +71,11 @@ public:
   void delete_pool(const string &name, const peer_t &peer) {
     int64_t pool_id = m_cluster->pool_lookup(name.c_str());
     ASSERT_GE(pool_id, 0);
-    if (m_peer_configs.find(peer) != m_peer_configs.end()) {
-      m_peer_configs[peer].erase(pool_id);
+    if (m_pool_peers.find(pool_id) != m_pool_peers.end()) {
+      m_pool_peers[pool_id].erase(peer);
       m_mirrored_pools.erase(name);
-      if (m_peer_configs[peer].empty()) {
-	m_peer_configs.erase(peer);
+      if (m_pool_peers[pool_id].empty()) {
+	m_pool_peers.erase(pool_id);
       }
     }
     m_pools.erase(name);
@@ -121,7 +121,7 @@ public:
   void check_peers() {
     m_cluster_watcher->refresh_pools();
     Mutex::Locker l(m_lock);
-    ASSERT_EQ(m_peer_configs, m_cluster_watcher->get_peer_configs());
+    ASSERT_EQ(m_pool_peers, m_cluster_watcher->get_pool_peers());
     ASSERT_EQ(m_mirrored_pools, m_cluster_watcher->get_pool_names());
   }
 
@@ -131,7 +131,7 @@ public:
 
   set<string> m_pools;
   set<string> m_mirrored_pools;
-  map<peer_t, set<int64_t> > m_peer_configs;
+  ClusterWatcher::PoolPeers m_pool_peers;
 };
 
 TEST_F(TestClusterWatcher, NoPools) {
diff --git a/src/test/rbd_mirror/test_ImageDeleter.cc b/src/test/rbd_mirror/test_ImageDeleter.cc
new file mode 100644
index 0000000..1386977
--- /dev/null
+++ b/src/test/rbd_mirror/test_ImageDeleter.cc
@@ -0,0 +1,471 @@
+// -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2016 SUSE LINUX GmbH
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation.  See file COPYING.
+ *
+ */
+#include "include/rados/librados.hpp"
+#include "include/rbd/librbd.hpp"
+#include "include/stringify.h"
+#include "cls/rbd/cls_rbd_client.h"
+#include "tools/rbd_mirror/ImageDeleter.h"
+#include "tools/rbd_mirror/ImageReplayer.h"
+#include "tools/rbd_mirror/Threads.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/ImageState.h"
+#include "librbd/Operations.h"
+#include "librbd/Journal.h"
+#include "librbd/internal.h"
+#include "librbd/Utils.h"
+#include "test/rbd_mirror/test_fixture.h"
+
+#include "test/librados/test.h"
+#include "gtest/gtest.h"
+
+#define GLOBAL_IMAGE_ID "global_image_id"
+#define GLOBAL_CLONE_IMAGE_ID "global_image_id_clone"
+
+#define dout_subsys ceph_subsys_rbd_mirror
+
+using rbd::mirror::RadosRef;
+using rbd::mirror::TestFixture;
+using namespace librbd;
+using cls::rbd::MirrorImageState;
+
+
+void register_test_rbd_mirror_image_deleter() {
+}
+
+class TestImageDeleter : public TestFixture {
+public:
+
+  static int64_t m_local_pool_id;
+
+  const std::string m_local_mirror_uuid = "local mirror uuid";
+  const std::string m_remote_mirror_uuid = "remote mirror uuid";
+
+  static void SetUpTestCase() {
+    TestFixture::SetUpTestCase();
+
+    m_local_pool_id = _rados->pool_lookup(_local_pool_name.c_str());
+  }
+
+  void SetUp() {
+    TestFixture::SetUp();
+
+    librbd::mirror_mode_set(m_local_io_ctx, RBD_MIRROR_MODE_IMAGE);
+
+    m_deleter = new rbd::mirror::ImageDeleter(_rados,
+        m_threads->timer, &m_threads->timer_lock);
+
+    EXPECT_EQ(0, create_image(rbd, m_local_io_ctx, m_image_name, 1 << 20));
+    ImageCtx *ictx = new ImageCtx(m_image_name, "", "", m_local_io_ctx,
+                                  false);
+    EXPECT_EQ(0, ictx->state->open());
+    m_local_image_id = ictx->id;
+
+    cls::rbd::MirrorImage mirror_image(GLOBAL_IMAGE_ID,
+                                MirrorImageState::MIRROR_IMAGE_STATE_ENABLED);
+    EXPECT_EQ(0, cls_client::mirror_image_set(&m_local_io_ctx, ictx->id,
+                                              mirror_image));
+
+    demote_image(ictx);
+    EXPECT_EQ(0, ictx->state->close());
+  }
+
+  void TearDown() {
+    remove_image();
+    TestFixture::TearDown();
+    delete m_deleter;
+  }
+
+  void remove_image(bool force=false) {
+    if (!force) {
+      cls::rbd::MirrorImage mirror_image;
+      int r = cls_client::mirror_image_get(&m_local_io_ctx, m_local_image_id,
+                                           &mirror_image);
+      EXPECT_EQ(1, r == 0 || r == -ENOENT);
+      if (r != -ENOENT) {
+        mirror_image.state = MirrorImageState::MIRROR_IMAGE_STATE_ENABLED;
+        EXPECT_EQ(0, cls_client::mirror_image_set(&m_local_io_ctx,
+                                                  m_local_image_id,
+                                                  mirror_image));
+      }
+      promote_image();
+    }
+    NoOpProgressContext ctx;
+    int r = remove(m_local_io_ctx, m_image_name.c_str(), ctx, force);
+    EXPECT_EQ(1, r == 0 || r == -ENOENT);
+  }
+
+  void promote_image(ImageCtx *ictx=nullptr) {
+    bool close = false;
+    int r = 0;
+    if (!ictx) {
+      ictx = new ImageCtx("", m_local_image_id, "", m_local_io_ctx,
+                          false);
+      r = ictx->state->open();
+      close = (r == 0);
+    }
+
+    EXPECT_EQ(1, r == 0 || r == -ENOENT);
+
+    if (r == 0) {
+        int r2 = librbd::mirror_image_promote(ictx, true);
+        EXPECT_EQ(1, r2 == 0 || r2 == -EINVAL);
+    }
+
+    if (close) {
+      EXPECT_EQ(0, ictx->state->close());
+    }
+  }
+
+  void demote_image(ImageCtx *ictx=nullptr) {
+    bool close = false;
+    if (!ictx) {
+      ictx = new ImageCtx("", m_local_image_id, "", m_local_io_ctx,
+                          false);
+      EXPECT_EQ(0, ictx->state->open());
+      close = true;
+    }
+
+    EXPECT_EQ(0, librbd::mirror_image_demote(ictx));
+
+    if (close) {
+      EXPECT_EQ(0, ictx->state->close());
+    }
+  }
+
+  void create_snapshot(std::string snap_name="snap1", bool protect=false) {
+    ImageCtx *ictx = new ImageCtx("", m_local_image_id, "", m_local_io_ctx,
+                                  false);
+    EXPECT_EQ(0, ictx->state->open());
+    promote_image(ictx);
+
+    EXPECT_EQ(0, ictx->operations->snap_create(snap_name.c_str()));
+
+    if (protect) {
+      EXPECT_EQ(0, ictx->operations->snap_protect(snap_name.c_str()));
+    }
+
+    demote_image(ictx);
+    EXPECT_EQ(0, ictx->state->close());
+  }
+
+  std::string create_clone() {
+    ImageCtx *ictx = new ImageCtx("", m_local_image_id, "", m_local_io_ctx,
+                                  false);
+    EXPECT_EQ(0, ictx->state->open());
+    promote_image(ictx);
+
+    EXPECT_EQ(0, ictx->operations->snap_create("snap1"));
+    EXPECT_EQ(0, ictx->operations->snap_protect("snap1"));
+    int order = 20;
+    EXPECT_EQ(0, librbd::clone(m_local_io_ctx, ictx->name.c_str(), "snap1",
+                               m_local_io_ctx,  "clone1", ictx->features,
+                               &order, 0, 0));
+    std::string clone_id;
+    ImageCtx *ictx_clone = new ImageCtx("clone1", "", "", m_local_io_ctx,
+                                        false);
+    EXPECT_EQ(0, ictx_clone->state->open());
+    clone_id = ictx_clone->id;
+    cls::rbd::MirrorImage mirror_image(GLOBAL_CLONE_IMAGE_ID,
+                                MirrorImageState::MIRROR_IMAGE_STATE_ENABLED);
+    EXPECT_EQ(0, cls_client::mirror_image_set(&m_local_io_ctx, clone_id,
+                                              mirror_image));
+    demote_image(ictx_clone);
+    EXPECT_EQ(0, ictx_clone->state->close());
+
+    demote_image(ictx);
+    EXPECT_EQ(0, ictx->state->close());
+
+    return clone_id;
+  }
+
+  void check_image_deleted() {
+    ImageCtx *ictx = new ImageCtx("", m_local_image_id, "", m_local_io_ctx,
+                                  false);
+    EXPECT_EQ(-ENOENT, ictx->state->open());
+    delete ictx;
+
+    cls::rbd::MirrorImage mirror_image;
+    EXPECT_EQ(-ENOENT, cls_client::mirror_image_get(&m_local_io_ctx,
+                                                    m_local_image_id,
+                                                    &mirror_image));
+  }
+
+
+  librbd::RBD rbd;
+  std::string m_local_image_id;
+  rbd::mirror::ImageDeleter *m_deleter;
+};
+
+int64_t TestImageDeleter::m_local_pool_id;
+
+
+TEST_F(TestImageDeleter, Delete_NonPrimary_Image) {
+  m_deleter->schedule_image_delete(m_local_pool_id, m_local_image_id,
+      m_image_name, GLOBAL_IMAGE_ID);
+
+  C_SaferCond ctx;
+  m_deleter->wait_for_scheduled_deletion(m_image_name, &ctx);
+  EXPECT_EQ(0, ctx.wait());
+
+  ASSERT_EQ(0u, m_deleter->get_delete_queue_items().size());
+  ASSERT_EQ(0u, m_deleter->get_failed_queue_items().size());
+
+  check_image_deleted();
+}
+
+TEST_F(TestImageDeleter, Fail_Delete_Primary_Image) {
+  promote_image();
+
+  m_deleter->schedule_image_delete(m_local_pool_id, m_local_image_id,
+      m_image_name, GLOBAL_IMAGE_ID);
+
+  C_SaferCond ctx;
+  m_deleter->wait_for_scheduled_deletion(m_image_name, &ctx);
+  EXPECT_EQ(-rbd::mirror::ImageDeleter::EISPRM, ctx.wait());
+
+  ASSERT_EQ(0u, m_deleter->get_delete_queue_items().size());
+  ASSERT_EQ(0u, m_deleter->get_failed_queue_items().size());
+}
+
+TEST_F(TestImageDeleter, Fail_Delete_Diff_GlobalId) {
+  // This test case represents a case that should never happen, unless
+  // there is bug in the implementation
+
+  m_deleter->schedule_image_delete(m_local_pool_id, m_local_image_id,
+      m_image_name, "diff global id");
+
+  C_SaferCond ctx;
+  m_deleter->wait_for_scheduled_deletion(m_image_name, &ctx);
+  EXPECT_EQ(-EINVAL, ctx.wait());
+
+  ASSERT_EQ(0u, m_deleter->get_delete_queue_items().size());
+  ASSERT_EQ(0u, m_deleter->get_failed_queue_items().size());
+}
+
+TEST_F(TestImageDeleter, Delete_Image_With_Child) {
+  create_snapshot();
+
+  m_deleter->schedule_image_delete(m_local_pool_id, m_local_image_id,
+      m_image_name, GLOBAL_IMAGE_ID);
+
+  C_SaferCond ctx;
+  m_deleter->wait_for_scheduled_deletion(m_image_name, &ctx);
+  EXPECT_EQ(0, ctx.wait());
+
+  ASSERT_EQ(0u, m_deleter->get_delete_queue_items().size());
+  ASSERT_EQ(0u, m_deleter->get_failed_queue_items().size());
+}
+
+TEST_F(TestImageDeleter, Delete_Image_With_Children) {
+  create_snapshot("snap1");
+  create_snapshot("snap2");
+
+  m_deleter->schedule_image_delete(m_local_pool_id, m_local_image_id,
+      m_image_name, GLOBAL_IMAGE_ID);
+
+  C_SaferCond ctx;
+  m_deleter->wait_for_scheduled_deletion(m_image_name, &ctx);
+  EXPECT_EQ(0, ctx.wait());
+
+  ASSERT_EQ(0u, m_deleter->get_delete_queue_items().size());
+  ASSERT_EQ(0u, m_deleter->get_failed_queue_items().size());
+}
+
+TEST_F(TestImageDeleter, Delete_Image_With_ProtectedChild) {
+  create_snapshot("snap1", true);
+
+  m_deleter->schedule_image_delete(m_local_pool_id, m_local_image_id,
+      m_image_name, GLOBAL_IMAGE_ID);
+
+  C_SaferCond ctx;
+  m_deleter->wait_for_scheduled_deletion(m_image_name, &ctx);
+  EXPECT_EQ(0, ctx.wait());
+
+  ASSERT_EQ(0u, m_deleter->get_delete_queue_items().size());
+  ASSERT_EQ(0u, m_deleter->get_failed_queue_items().size());
+}
+
+TEST_F(TestImageDeleter, Delete_Image_With_ProtectedChildren) {
+  create_snapshot("snap1", true);
+  create_snapshot("snap2", true);
+
+  m_deleter->schedule_image_delete(m_local_pool_id, m_local_image_id,
+      m_image_name, GLOBAL_IMAGE_ID);
+
+  C_SaferCond ctx;
+  m_deleter->wait_for_scheduled_deletion(m_image_name, &ctx);
+  EXPECT_EQ(0, ctx.wait());
+
+  ASSERT_EQ(0u, m_deleter->get_delete_queue_items().size());
+  ASSERT_EQ(0u, m_deleter->get_failed_queue_items().size());
+}
+
+TEST_F(TestImageDeleter, Delete_Image_With_Clone) {
+  std::string clone_id = create_clone();
+
+  m_deleter->schedule_image_delete(m_local_pool_id, m_local_image_id,
+      m_image_name, GLOBAL_IMAGE_ID);
+
+  C_SaferCond ctx;
+  m_deleter->wait_for_scheduled_deletion(m_image_name, &ctx);
+  EXPECT_EQ(-EBUSY, ctx.wait());
+
+  ASSERT_EQ(1u, m_deleter->get_delete_queue_items().size());
+  ASSERT_EQ(0u, m_deleter->get_failed_queue_items().size());
+
+  m_deleter->schedule_image_delete(m_local_pool_id, clone_id,
+      "clone1", GLOBAL_CLONE_IMAGE_ID);
+
+  C_SaferCond ctx2;
+  m_deleter->wait_for_scheduled_deletion("clone1", &ctx2);
+  EXPECT_EQ(0, ctx2.wait());
+
+  C_SaferCond ctx3;
+  m_deleter->wait_for_scheduled_deletion(m_image_name, &ctx3);
+  EXPECT_EQ(0, ctx3.wait());
+
+  ASSERT_EQ(0u, m_deleter->get_delete_queue_items().size());
+  ASSERT_EQ(0u, m_deleter->get_failed_queue_items().size());
+}
+
+TEST_F(TestImageDeleter, Delete_NonExistent_Image) {
+  remove_image();
+
+  cls::rbd::MirrorImage mirror_image(GLOBAL_IMAGE_ID,
+                              MirrorImageState::MIRROR_IMAGE_STATE_ENABLED);
+  EXPECT_EQ(0, cls_client::mirror_image_set(&m_local_io_ctx, m_local_image_id,
+                                            mirror_image));
+
+  m_deleter->schedule_image_delete(m_local_pool_id, m_local_image_id,
+      m_image_name, GLOBAL_IMAGE_ID);
+
+  C_SaferCond ctx;
+  m_deleter->wait_for_scheduled_deletion(m_image_name, &ctx);
+  EXPECT_EQ(0, ctx.wait());
+
+  ASSERT_EQ(0u, m_deleter->get_delete_queue_items().size());
+  ASSERT_EQ(0u, m_deleter->get_failed_queue_items().size());
+
+  check_image_deleted();
+}
+
+TEST_F(TestImageDeleter, Delete_NonExistent_Image_With_MirroringState) {
+  remove_image(true);
+
+  cls::rbd::MirrorImage mirror_image(GLOBAL_IMAGE_ID,
+                              MirrorImageState::MIRROR_IMAGE_STATE_ENABLED);
+  EXPECT_EQ(0, cls_client::mirror_image_set(&m_local_io_ctx, m_local_image_id,
+                                            mirror_image));
+  mirror_image.state = MirrorImageState::MIRROR_IMAGE_STATE_DISABLING;
+  EXPECT_EQ(0, cls_client::mirror_image_set(&m_local_io_ctx, m_local_image_id,
+                                            mirror_image));
+
+  m_deleter->schedule_image_delete(m_local_pool_id, m_local_image_id,
+      m_image_name, GLOBAL_IMAGE_ID);
+
+  C_SaferCond ctx;
+  m_deleter->wait_for_scheduled_deletion(m_image_name, &ctx);
+  EXPECT_EQ(0, ctx.wait());
+
+  ASSERT_EQ(0u, m_deleter->get_delete_queue_items().size());
+  ASSERT_EQ(0u, m_deleter->get_failed_queue_items().size());
+
+  check_image_deleted();
+}
+
+TEST_F(TestImageDeleter, Delete_NonExistent_Image_Without_MirroringState) {
+  remove_image();
+
+  m_deleter->schedule_image_delete(m_local_pool_id, m_local_image_id,
+      m_image_name, GLOBAL_IMAGE_ID);
+
+  C_SaferCond ctx;
+  m_deleter->wait_for_scheduled_deletion(m_image_name, &ctx);
+  EXPECT_EQ(-ENOENT, ctx.wait());
+
+  ASSERT_EQ(0u, m_deleter->get_delete_queue_items().size());
+  ASSERT_EQ(0u, m_deleter->get_failed_queue_items().size());
+
+  check_image_deleted();
+}
+
+TEST_F(TestImageDeleter, Fail_Delete_NonPrimary_Image) {
+  ImageCtx *ictx = new ImageCtx("", m_local_image_id, "", m_local_io_ctx,
+                                false);
+  EXPECT_EQ(0, ictx->state->open());
+
+  m_deleter->schedule_image_delete(m_local_pool_id, m_local_image_id,
+      m_image_name, GLOBAL_IMAGE_ID);
+
+  C_SaferCond ctx;
+  m_deleter->wait_for_scheduled_deletion(m_image_name, &ctx);
+  EXPECT_EQ(-EBUSY, ctx.wait());
+
+  ASSERT_EQ(0u, m_deleter->get_delete_queue_items().size());
+  ASSERT_EQ(1u, m_deleter->get_failed_queue_items().size());
+
+  EXPECT_EQ(0, ictx->state->close());
+}
+
+TEST_F(TestImageDeleter, Retry_Failed_Deletes) {
+  ImageCtx *ictx = new ImageCtx("", m_local_image_id, "", m_local_io_ctx,
+                                false);
+  EXPECT_EQ(0, ictx->state->open());
+
+  m_deleter->set_failed_timer_interval(2);
+
+  m_deleter->schedule_image_delete(m_local_pool_id, m_local_image_id,
+      m_image_name, GLOBAL_IMAGE_ID);
+
+  C_SaferCond ctx;
+  m_deleter->wait_for_scheduled_deletion(m_image_name, &ctx);
+  EXPECT_EQ(-EBUSY, ctx.wait());
+
+  EXPECT_EQ(0, ictx->state->close());
+
+  C_SaferCond ctx2;
+  m_deleter->wait_for_scheduled_deletion(m_image_name, &ctx2);
+  EXPECT_EQ(0, ctx2.wait());
+
+  ASSERT_EQ(0u, m_deleter->get_delete_queue_items().size());
+  ASSERT_EQ(0u, m_deleter->get_failed_queue_items().size());
+
+  check_image_deleted();
+}
+
+TEST_F(TestImageDeleter, Delete_Is_Idempotent) {
+  ImageCtx *ictx = new ImageCtx("", m_local_image_id, "", m_local_io_ctx,
+                                false);
+  EXPECT_EQ(0, ictx->state->open());
+
+  m_deleter->schedule_image_delete(m_local_pool_id, m_local_image_id,
+      m_image_name, GLOBAL_IMAGE_ID);
+
+  C_SaferCond ctx;
+  m_deleter->wait_for_scheduled_deletion(m_image_name, &ctx);
+  EXPECT_EQ(-EBUSY, ctx.wait());
+
+  ASSERT_EQ(0u, m_deleter->get_delete_queue_items().size());
+  ASSERT_EQ(1u, m_deleter->get_failed_queue_items().size());
+
+  m_deleter->schedule_image_delete(m_local_pool_id, m_local_image_id,
+      m_image_name, GLOBAL_IMAGE_ID);
+
+  ASSERT_EQ(0u, m_deleter->get_delete_queue_items().size());
+  ASSERT_EQ(1u, m_deleter->get_failed_queue_items().size());
+
+  EXPECT_EQ(0, ictx->state->close());
+}
+
+
diff --git a/src/test/rbd_mirror/test_ImageReplayer.cc b/src/test/rbd_mirror/test_ImageReplayer.cc
index 84e4afc..c2753ba 100644
--- a/src/test/rbd_mirror/test_ImageReplayer.cc
+++ b/src/test/rbd_mirror/test_ImageReplayer.cc
@@ -74,6 +74,7 @@ public:
   TestImageReplayer() : m_watch_handle(0)
   {
     EXPECT_EQ("", connect_cluster_pp(m_local_cluster));
+    EXPECT_EQ(0, m_local_cluster.conf_set("rbd_cache", "false"));
 
     m_local_pool_name = get_temp_pool_name();
     EXPECT_EQ(0, m_local_cluster.pool_create(m_local_pool_name.c_str()));
@@ -81,6 +82,7 @@ public:
 					      m_local_ioctx));
 
     EXPECT_EQ("", connect_cluster_pp(m_remote_cluster));
+    EXPECT_EQ(0, m_remote_cluster.conf_set("rbd_cache", "false"));
 
     m_remote_pool_name = get_temp_pool_name();
     EXPECT_EQ(0, m_remote_cluster.pool_create(m_remote_pool_name.c_str()));
@@ -393,20 +395,17 @@ TEST_F(TestImageReplayer, StartInterrupted)
   m_replayer->stop(&stop_cond);
   int r = start_cond.wait();
   printf("start returned %d\n", r);
-  // TODO: improve the test to avoid this race  // TODO: improve the test to avoid this race
-  ASSERT_TRUE(r == -EINTR || r == 0);
+  // TODO: improve the test to avoid this race
+  ASSERT_TRUE(r == -ECANCELED || r == 0);
   ASSERT_EQ(0, stop_cond.wait());
 }
 
-TEST_F(TestImageReplayer, ErrorJournalReset)
+TEST_F(TestImageReplayer, JournalReset)
 {
   bootstrap();
-
   ASSERT_EQ(0, librbd::Journal<>::reset(m_remote_ioctx, m_remote_image_id));
-
-  C_SaferCond cond;
-  m_replayer->start(&cond);
-  ASSERT_EQ(-EEXIST, cond.wait());
+  // try to recover
+  bootstrap();
 }
 
 TEST_F(TestImageReplayer, ErrorNoJournal)
@@ -534,78 +533,3 @@ TEST_F(TestImageReplayer, NextTag)
 
   stop();
 }
-
-class ImageReplayer : public rbd::mirror::ImageReplayer<> {
-public:
-  ImageReplayer(rbd::mirror::Threads *threads,
-		rbd::mirror::RadosRef local, rbd::mirror::RadosRef remote,
-		const std::string &local_mirror_uuid,
-                const std::string &remote_mirror_uuid,
-                int64_t local_pool_id,
-		int64_t remote_pool_id,	const std::string &remote_image_id,
-                const std::string &global_image_id)
-    : rbd::mirror::ImageReplayer<>(threads, local, remote, local_mirror_uuid,
-				   remote_mirror_uuid, local_pool_id,
-                                   remote_pool_id, remote_image_id,
-                                   global_image_id)
-    {}
-
-  void set_error(const std::string &state, int r) {
-    m_errors[state] = r;
-  }
-
-  int get_error(const std::string &state) const {
-    std::map<std::string, int>::const_iterator i = m_errors.find(state);
-    return i == m_errors.end() ? 0 : i->second;
-  }
-
-protected:
-  virtual void on_stop_journal_replay_shut_down_finish(int r) {
-    ASSERT_EQ(0, r);
-    rbd::mirror::ImageReplayer<>::on_stop_journal_replay_shut_down_finish(
-      get_error("on_stop_journal_replay_shut_down"));
-  }
-
-  virtual void on_stop_local_image_close_finish(int r) {
-    ASSERT_EQ(0, r);
-    rbd::mirror::ImageReplayer<>::on_stop_local_image_close_finish(
-      get_error("on_stop_local_image_close"));
-  }
-
-private:
-  std::map<std::string, int> m_errors;
-};
-
-#define TEST_ON_START_ERROR(state) \
-TEST_F(TestImageReplayer, Error_on_start_##state)			\
-{									\
-  create_replayer<ImageReplayer>();					\
-  reinterpret_cast<ImageReplayer *>(m_replayer)->			\
-    set_error("on_start_" #state, -1);					\
-  rbd::mirror::ImageReplayer<>::BootstrapParams				\
-    bootstap_params(m_image_name);			                \
-  C_SaferCond cond;							\
-  m_replayer->start(&cond, &bootstap_params);				\
-  ASSERT_EQ(-1, cond.wait());						\
-}
-
-#define TEST_ON_STOP_ERROR(state) \
-TEST_F(TestImageReplayer, Error_on_stop_##state)			\
-{									\
-  create_replayer<ImageReplayer>();					\
-  reinterpret_cast<ImageReplayer *>(m_replayer)->			\
-    set_error("on_stop_" #state, -1);					\
-  rbd::mirror::ImageReplayer<>::BootstrapParams				\
-    bootstap_params(m_image_name);			                \
-  start(&bootstap_params);						\
-  /* TODO: investigate: without wait below I observe: */		\
-  /* librbd/journal/Replay.cc: 70: FAILED assert(m_op_events.empty()) */\
-  wait_for_replay_complete();						\
-  C_SaferCond cond;							\
-  m_replayer->stop(&cond);						\
-  ASSERT_EQ(0, cond.wait());						\
-}
-
-TEST_ON_STOP_ERROR(journal_replay_shut_down);
-TEST_ON_STOP_ERROR(no_error);
-
diff --git a/src/test/rbd_mirror/test_ImageSync.cc b/src/test/rbd_mirror/test_ImageSync.cc
index 5b80f97..922d499 100644
--- a/src/test/rbd_mirror/test_ImageSync.cc
+++ b/src/test/rbd_mirror/test_ImageSync.cc
@@ -88,7 +88,7 @@ public:
     return new ImageSync<>(m_local_image_ctx, m_remote_image_ctx,
                            m_threads->timer, &m_threads->timer_lock,
                            "mirror-uuid", m_remote_journaler, &m_client_meta,
-                           ctx);
+                           m_threads->work_queue, ctx);
   }
 
   librbd::ImageCtx *m_remote_image_ctx;
@@ -100,7 +100,7 @@ public:
 TEST_F(TestImageSync, Empty) {
   C_SaferCond ctx;
   ImageSync<> *request = create_request(&ctx);
-  request->start();
+  request->send();
   ASSERT_EQ(0, ctx.wait());
 
   ASSERT_EQ(0U, m_client_meta.sync_points.size());
@@ -115,7 +115,7 @@ TEST_F(TestImageSync, Simple) {
 
   C_SaferCond ctx;
   ImageSync<> *request = create_request(&ctx);
-  request->start();
+  request->send();
   ASSERT_EQ(0, ctx.wait());
 
   int64_t object_size = std::min<int64_t>(
@@ -159,7 +159,7 @@ TEST_F(TestImageSync, SnapshotStress) {
 
   C_SaferCond ctx;
   ImageSync<> *request = create_request(&ctx);
-  request->start();
+  request->send();
   ASSERT_EQ(0, ctx.wait());
 
   int64_t object_size = std::min<int64_t>(
diff --git a/src/test/rbd_mirror/test_PoolWatcher.cc b/src/test/rbd_mirror/test_PoolWatcher.cc
index 5d131d3..2a2708e 100644
--- a/src/test/rbd_mirror/test_PoolWatcher.cc
+++ b/src/test/rbd_mirror/test_PoolWatcher.cc
@@ -42,7 +42,6 @@ TestPoolWatcher() : m_lock("TestPoolWatcherLock"),
   {
     m_cluster = std::make_shared<librados::Rados>();
     EXPECT_EQ("", connect_cluster_pp(*m_cluster));
-    m_pool_watcher.reset(new PoolWatcher(m_cluster, 30, m_lock, m_cond));
   }
 
   ~TestPoolWatcher() {
@@ -59,9 +58,12 @@ TestPoolWatcher() : m_lock("TestPoolWatcherLock"),
     int64_t pool_id = m_cluster->pool_lookup(pool_name.c_str());
     ASSERT_GE(pool_id, 0);
     m_pools.insert(pool_name);
+
+    librados::IoCtx ioctx;
+    ASSERT_EQ(0, m_cluster->ioctx_create2(pool_id, ioctx));
+
+    m_pool_watcher.reset(new PoolWatcher(ioctx, 30, m_lock, m_cond));
     if (enable_mirroring) {
-      librados::IoCtx ioctx;
-      ASSERT_EQ(0, m_cluster->ioctx_create2(pool_id, ioctx));
       ASSERT_EQ(0, librbd::mirror_mode_set(ioctx, RBD_MIRROR_MODE_POOL));
       std::string uuid;
       ASSERT_EQ(0, librbd::mirror_peer_add(ioctx, &uuid,
@@ -73,50 +75,6 @@ TestPoolWatcher() : m_lock("TestPoolWatcherLock"),
     }
   }
 
-  void delete_pool(const string &name, const peer_t &peer) {
-    int64_t pool_id = m_cluster->pool_lookup(name.c_str());
-    ASSERT_GE(pool_id, 0);
-    m_pools.erase(name);
-    ASSERT_EQ(0, m_cluster->pool_delete(name.c_str()));
-    m_mirrored_images.erase(pool_id);
-  }
-
-  void create_cache_pool(const string &base_pool, string *cache_pool_name) {
-    bufferlist inbl;
-    *cache_pool_name = get_temp_pool_name("test-rbd-mirror-");
-    ASSERT_EQ(0, m_cluster->pool_create(cache_pool_name->c_str()));
-
-    ASSERT_EQ(0, m_cluster->mon_command(
-      "{\"prefix\": \"osd tier add\", \"pool\": \"" + base_pool +
-      "\", \"tierpool\": \"" + *cache_pool_name +
-      "\", \"force_nonempty\": \"--force-nonempty\" }",
-      inbl, NULL, NULL));
-    ASSERT_EQ(0, m_cluster->mon_command(
-      "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + base_pool +
-      "\", \"overlaypool\": \"" + *cache_pool_name + "\"}",
-      inbl, NULL, NULL));
-    ASSERT_EQ(0, m_cluster->mon_command(
-      "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + *cache_pool_name +
-      "\", \"mode\": \"writeback\"}",
-      inbl, NULL, NULL));
-    m_cluster->wait_for_latest_osdmap();
-  }
-
-  void remove_cache_pool(const string &base_pool, const string &cache_pool) {
-    bufferlist inbl;
-    // tear down tiers
-    ASSERT_EQ(0, m_cluster->mon_command(
-      "{\"prefix\": \"osd tier remove-overlay\", \"pool\": \"" + base_pool +
-      "\"}",
-      inbl, NULL, NULL));
-    ASSERT_EQ(0, m_cluster->mon_command(
-      "{\"prefix\": \"osd tier remove\", \"pool\": \"" + base_pool +
-      "\", \"tierpool\": \"" + cache_pool + "\"}",
-      inbl, NULL, NULL));
-    m_cluster->wait_for_latest_osdmap();
-    m_cluster->pool_delete(cache_pool.c_str());
-  }
-
   string get_image_id(librados::IoCtx *ioctx, const string &image_name) {
     string obj = librbd::util::id_obj_name(image_name);
     string id;
@@ -148,8 +106,8 @@ TestPoolWatcher() : m_lock("TestPoolWatcherLock"),
                                                sizeof(mirror_image_info)));
       image.close();
 
-      m_mirrored_images[ioctx.get_id()].insert(PoolWatcher::ImageIds(
-        get_image_id(&ioctx, name), mirror_image_info.global_id));
+      m_mirrored_images.insert(PoolWatcher::ImageId(
+        get_image_id(&ioctx, name), name, mirror_image_info.global_id));
     }
     if (image_name != nullptr)
       *image_name = name;
@@ -193,8 +151,8 @@ TestPoolWatcher() : m_lock("TestPoolWatcherLock"),
                                                sizeof(mirror_image_info)));
       image.close();
 
-      m_mirrored_images[cioctx.get_id()].insert(PoolWatcher::ImageIds(
-        get_image_id(&cioctx, name), mirror_image_info.global_id));
+      m_mirrored_images.insert(PoolWatcher::ImageId(
+        get_image_id(&cioctx, name), name, mirror_image_info.global_id));
     }
     if (image_name != nullptr)
       *image_name = name;
@@ -212,23 +170,23 @@ TestPoolWatcher() : m_lock("TestPoolWatcherLock"),
   unique_ptr<PoolWatcher> m_pool_watcher;
 
   set<string> m_pools;
-  PoolWatcher::PoolImageIds m_mirrored_images;
+  PoolWatcher::ImageIds m_mirrored_images;
 
   uint64_t m_image_number;
   uint64_t m_snap_number;
 };
 
-TEST_F(TestPoolWatcher, NoPools) {
+TEST_F(TestPoolWatcher, EmptyPool) {
+  string uuid1 = "00000000-0000-0000-0000-000000000001";
+  peer_t site1(uuid1, "site1", "mirror1");
+  create_pool(true, site1);
   check_images();
 }
 
 TEST_F(TestPoolWatcher, ReplicatedPools) {
   string uuid1 = "00000000-0000-0000-0000-000000000001";
-  string uuid2 = "20000000-2222-2222-2222-000000000002";
   peer_t site1(uuid1, "site1", "mirror1");
-  peer_t site2(uuid2, "site2", "mirror2");
   string first_pool, local_pool, last_pool;
-  check_images();
   create_pool(true, site1, &first_pool);
   check_images();
   create_image(first_pool);
@@ -242,50 +200,4 @@ TEST_F(TestPoolWatcher, ReplicatedPools) {
   check_images();
   create_image(first_pool, false);
   check_images();
-
-  create_pool(false, peer_t(), &local_pool);
-  check_images();
-  create_image(local_pool, false);
-  check_images();
-  clone_image(first_pool, parent_image2, local_pool, false);
-  check_images();
-  create_pool(true, site2);
-  check_images();
-
-  create_pool(true, site2, &last_pool);
-  check_images();
-  clone_image(first_pool, parent_image2, last_pool);
-  check_images();
-  create_image(last_pool);
-  check_images();
-  delete_pool(last_pool, site2);
-  check_images();
-  delete_pool(first_pool, site1);
-  check_images();
-}
-
-TEST_F(TestPoolWatcher, CachePools) {
-  peer_t site1("11111111-1111-1111-1111-111111111111", "site1", "mirror1");
-  string base1, base2, cache1, cache2;
-  create_pool(true, site1, &base1);
-  check_images();
-
-  create_cache_pool(base1, &cache1);
-  BOOST_SCOPE_EXIT( base1, cache1, this_ ) {
-    this_->remove_cache_pool(base1, cache1);
-  } BOOST_SCOPE_EXIT_END;
-  check_images();
-  create_image(base1);
-  check_images();
-  create_image(base1, false);
-  check_images();
-
-  create_pool(false, peer_t(), &base2);
-  create_cache_pool(base2, &cache2);
-  BOOST_SCOPE_EXIT( base2, cache2, this_ ) {
-    this_->remove_cache_pool(base2, cache2);
-  } BOOST_SCOPE_EXIT_END;
-  check_images();
-  create_image(base2, false);
-  check_images();
 }
diff --git a/src/test/rbd_mirror/test_fixture.cc b/src/test/rbd_mirror/test_fixture.cc
index 7ec0ab3..b1eb489 100644
--- a/src/test/rbd_mirror/test_fixture.cc
+++ b/src/test/rbd_mirror/test_fixture.cc
@@ -15,26 +15,28 @@ namespace mirror {
 
 std::string TestFixture::_local_pool_name;
 std::string TestFixture::_remote_pool_name;
-librados::Rados TestFixture::_rados;
+std::shared_ptr<librados::Rados> TestFixture::_rados;
 uint64_t TestFixture::_image_number = 0;
 
 TestFixture::TestFixture() {
 }
 
 void TestFixture::SetUpTestCase() {
-  ASSERT_EQ("", connect_cluster_pp(_rados));
+  _rados = std::shared_ptr<librados::Rados>(new librados::Rados());
+  ASSERT_EQ("", connect_cluster_pp(*_rados.get()));
+  ASSERT_EQ(0, _rados->conf_set("rbd_cache", "false"));
 
   _local_pool_name = get_temp_pool_name("test-rbd-mirror-");
-  ASSERT_EQ(0, _rados.pool_create(_local_pool_name.c_str()));
+  ASSERT_EQ(0, _rados->pool_create(_local_pool_name.c_str()));
 
   _remote_pool_name = get_temp_pool_name("test-rbd-mirror-");
-  ASSERT_EQ(0, _rados.pool_create(_remote_pool_name.c_str()));
+  ASSERT_EQ(0, _rados->pool_create(_remote_pool_name.c_str()));
 }
 
 void TestFixture::TearDownTestCase() {
-  ASSERT_EQ(0, _rados.pool_delete(_remote_pool_name.c_str()));
-  ASSERT_EQ(0, _rados.pool_delete(_local_pool_name.c_str()));
-  _rados.shutdown();
+  ASSERT_EQ(0, _rados->pool_delete(_remote_pool_name.c_str()));
+  ASSERT_EQ(0, _rados->pool_delete(_local_pool_name.c_str()));
+  _rados->shutdown();
 }
 
 void TestFixture::SetUp() {
@@ -46,8 +48,8 @@ void TestFixture::SetUp() {
     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));
+  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();
 
   m_threads = new rbd::mirror::Threads(reinterpret_cast<CephContext*>(
diff --git a/src/test/rbd_mirror/test_fixture.h b/src/test/rbd_mirror/test_fixture.h
index 79a6f86..c0ebd55 100644
--- a/src/test/rbd_mirror/test_fixture.h
+++ b/src/test/rbd_mirror/test_fixture.h
@@ -51,7 +51,7 @@ public:
 
   static std::string _local_pool_name;
   static std::string _remote_pool_name;
-  static librados::Rados _rados;
+  static std::shared_ptr<librados::Rados> _rados;
   static uint64_t _image_number;
 };
 
diff --git a/src/test/rbd_mirror/test_main.cc b/src/test/rbd_mirror/test_main.cc
index 71fe23a..a316b0a 100644
--- a/src/test/rbd_mirror/test_main.cc
+++ b/src/test/rbd_mirror/test_main.cc
@@ -11,6 +11,7 @@
 extern void register_test_cluster_watcher();
 extern void register_test_pool_watcher();
 extern void register_test_rbd_mirror();
+extern void register_test_rbd_mirror_image_deleter();
 extern void register_test_image_sync();
 
 int main(int argc, char **argv)
@@ -18,6 +19,7 @@ int main(int argc, char **argv)
   register_test_cluster_watcher();
   register_test_pool_watcher();
   register_test_rbd_mirror();
+  register_test_rbd_mirror_image_deleter();
   register_test_image_sync();
 
   ::testing::InitGoogleTest(&argc, argv);
diff --git a/src/test/rbd_mirror/test_mock_ImageReplayer.cc b/src/test/rbd_mirror/test_mock_ImageReplayer.cc
index 4ec594f..8ff318a 100644
--- a/src/test/rbd_mirror/test_mock_ImageReplayer.cc
+++ b/src/test/rbd_mirror/test_mock_ImageReplayer.cc
@@ -12,28 +12,33 @@
 
 namespace librbd {
 
-struct MockImageReplayerJournal;
+namespace {
 
-struct MockImageReplayerImageCtx : public MockImageCtx {
-  MockImageReplayerJournal *journal = nullptr;
+struct MockTestJournal;
+
+struct MockTestImageCtx : public MockImageCtx {
+  MockTestJournal *journal = nullptr;
 };
 
-struct MockImageReplayerJournal : public MockJournal {
-  MOCK_METHOD1(start_external_replay, int(journal::Replay<MockImageReplayerImageCtx> **));
+struct MockTestJournal : public MockJournal {
+  MOCK_METHOD2(start_external_replay, void(journal::Replay<MockTestImageCtx> **,
+                                           Context *on_finish));
   MOCK_METHOD0(stop_external_replay, void());
 };
 
+} // anonymous namespace
+
 namespace journal {
 
 template<>
-struct Replay<MockImageReplayerImageCtx> {
+struct Replay<MockTestImageCtx> {
   MOCK_METHOD3(process, void(bufferlist::iterator *, Context *, Context *));
   MOCK_METHOD1(flush, void(Context*));
   MOCK_METHOD2(shut_down, void(bool, Context*));
 };
 
 template <>
-struct TypeTraits<MockImageReplayerImageCtx> {
+struct TypeTraits<MockTestImageCtx> {
   typedef ::journal::MockJournalerProxy Journaler;
   typedef ::journal::MockReplayEntryProxy ReplayEntry;
 };
@@ -48,13 +53,13 @@ namespace mirror {
 namespace image_replayer {
 
 template<>
-struct BootstrapRequest<librbd::MockImageReplayerImageCtx> {
+struct BootstrapRequest<librbd::MockTestImageCtx> {
   static BootstrapRequest* s_instance;
   Context *on_finish = nullptr;
 
   static BootstrapRequest* create(librados::IoCtx &local_io_ctx,
                                   librados::IoCtx &remote_io_ctx,
-                                  librbd::MockImageReplayerImageCtx **local_image_ctx,
+                                  librbd::MockTestImageCtx **local_image_ctx,
                                   const std::string &local_image_name,
                                   const std::string &remote_image_id,
                                   const std::string &global_image_id,
@@ -76,15 +81,22 @@ struct BootstrapRequest<librbd::MockImageReplayerImageCtx> {
     s_instance = this;
   }
 
+  void put() {
+  }
+
+  void get() {
+  }
+
   MOCK_METHOD0(send, void());
+  MOCK_METHOD0(cancel, void());
 };
 
 template<>
-struct CloseImageRequest<librbd::MockImageReplayerImageCtx> {
+struct CloseImageRequest<librbd::MockTestImageCtx> {
   static CloseImageRequest* s_instance;
   Context *on_finish = nullptr;
 
-  static CloseImageRequest* create(librbd::MockImageReplayerImageCtx **image_ctx,
+  static CloseImageRequest* create(librbd::MockTestImageCtx **image_ctx,
                                    ContextWQ *work_queue, bool destroy_only,
                                    Context *on_finish) {
     assert(s_instance != nullptr);
@@ -101,7 +113,7 @@ struct CloseImageRequest<librbd::MockImageReplayerImageCtx> {
 };
 
 template<>
-struct ReplayStatusFormatter<librbd::MockImageReplayerImageCtx> {
+struct ReplayStatusFormatter<librbd::MockTestImageCtx> {
   static ReplayStatusFormatter* s_instance;
 
   static ReplayStatusFormatter* create(::journal::MockJournalerProxy *journaler,
@@ -118,9 +130,9 @@ struct ReplayStatusFormatter<librbd::MockImageReplayerImageCtx> {
   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;
-ReplayStatusFormatter<librbd::MockImageReplayerImageCtx>* ReplayStatusFormatter<librbd::MockImageReplayerImageCtx>::s_instance = nullptr;
+BootstrapRequest<librbd::MockTestImageCtx>* BootstrapRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+CloseImageRequest<librbd::MockTestImageCtx>* CloseImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+ReplayStatusFormatter<librbd::MockTestImageCtx>* ReplayStatusFormatter<librbd::MockTestImageCtx>::s_instance = nullptr;
 
 } // namespace image_replayer
 } // namespace mirror
@@ -128,14 +140,14 @@ ReplayStatusFormatter<librbd::MockImageReplayerImageCtx>* ReplayStatusFormatter<
 
 // template definitions
 #include "tools/rbd_mirror/ImageReplayer.cc"
-template class rbd::mirror::ImageReplayer<librbd::MockImageReplayerImageCtx>;
+template class rbd::mirror::ImageReplayer<librbd::MockTestImageCtx>;
 
 namespace rbd {
 namespace mirror {
 
 class TestMockImageReplayer : public TestMockFixture {
 public:
-  typedef ImageReplayer<librbd::MockImageReplayerImageCtx> MockImageReplayer;
+  typedef ImageReplayer<librbd::MockTestImageCtx> MockImageReplayer;
 
   virtual void SetUp() {
     TestMockFixture::SetUp();
diff --git a/src/test/rbd_mirror/test_mock_ImageSync.cc b/src/test/rbd_mirror/test_mock_ImageSync.cc
index f3c94ba..868a030 100644
--- a/src/test/rbd_mirror/test_mock_ImageSync.cc
+++ b/src/test/rbd_mirror/test_mock_ImageSync.cc
@@ -58,6 +58,13 @@ public:
   ImageCopyRequest() {
     s_instance = this;
   }
+
+  void put() {
+  }
+
+  void get() {
+  }
+
   MOCK_METHOD0(cancel, void());
   MOCK_METHOD0(send, void());
 };
@@ -73,6 +80,7 @@ public:
                                      SnapshotCopyRequest<librbd::ImageCtx>::SnapMap *snap_map,
                                      journal::MockJournaler *journaler,
                                      librbd::journal::MirrorPeerClientMeta *client_meta,
+                                     ContextWQ *work_queue,
                                      Context *on_finish) {
     assert(s_instance != nullptr);
     s_instance->on_finish = on_finish;
@@ -82,7 +90,15 @@ public:
   SnapshotCopyRequest() {
     s_instance = this;
   }
+
+  void put() {
+  }
+
+  void get() {
+  }
+
   MOCK_METHOD0(send, void());
+  MOCK_METHOD0(cancel, void());
 };
 
 template <>
@@ -143,6 +159,7 @@ using ::testing::InSequence;
 using ::testing::Invoke;
 using ::testing::Return;
 using ::testing::WithArg;
+using ::testing::InvokeWithoutArgs;
 
 class TestMockImageSync : public TestMockFixture {
 public:
@@ -238,7 +255,7 @@ public:
     return new MockImageSync(&mock_local_image_ctx, &mock_remote_image_ctx,
                              m_threads->timer, &m_threads->timer_lock,
                              "mirror-uuid", &mock_journaler, &m_client_meta,
-                             ctx);
+                             m_threads->work_queue, ctx);
   }
 
   librbd::ImageCtx *m_remote_image_ctx;
@@ -272,7 +289,7 @@ TEST_F(TestMockImageSync, SimpleSync) {
   MockImageSync *request = create_request(mock_remote_image_ctx,
                                           mock_local_image_ctx,
                                           mock_journaler, &ctx);
-  request->start();
+  request->send();
   ASSERT_EQ(0, ctx.wait());
 }
 
@@ -307,7 +324,7 @@ TEST_F(TestMockImageSync, RestartSync) {
   MockImageSync *request = create_request(mock_remote_image_ctx,
                                           mock_local_image_ctx,
                                           mock_journaler, &ctx);
-  request->start();
+  request->send();
   ASSERT_EQ(0, ctx.wait());
 }
 
@@ -336,15 +353,80 @@ TEST_F(TestMockImageSync, CancelImageCopy) {
   MockImageSync *request = create_request(mock_remote_image_ctx,
                                           mock_local_image_ctx,
                                           mock_journaler, &ctx);
-  request->start();
+  request->get();
+  request->send();
 
   // cancel the image copy once it starts
   ASSERT_EQ(0, image_copy_ctx.wait());
   request->cancel();
+  request->put();
   m_threads->work_queue->queue(mock_image_copy_request.on_finish, 0);
 
   ASSERT_EQ(-ECANCELED, ctx.wait());
 }
 
+TEST_F(TestMockImageSync, CancelAfterCopySnapshots) {
+  librbd::MockImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+  librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+  journal::MockJournaler mock_journaler;
+  MockSnapshotCopyRequest mock_snapshot_copy_request;
+  MockSyncPointCreateRequest mock_sync_point_create_request;
+
+  librbd::MockObjectMap *mock_object_map = new librbd::MockObjectMap();
+  mock_local_image_ctx.object_map = mock_object_map;
+  expect_test_features(mock_local_image_ctx);
+
+  C_SaferCond ctx;
+  MockImageSync *request = create_request(mock_remote_image_ctx,
+                                          mock_local_image_ctx,
+                                          mock_journaler, &ctx);
+  InSequence seq;
+  expect_create_sync_point(mock_local_image_ctx, mock_sync_point_create_request, 0);
+  EXPECT_CALL(mock_snapshot_copy_request, send())
+    .WillOnce((DoAll(InvokeWithoutArgs([request]() {
+	      request->cancel();
+	    }),
+	  Invoke([this, &mock_snapshot_copy_request]() {
+	      m_threads->work_queue->queue(mock_snapshot_copy_request.on_finish, 0);
+	    }))));
+  EXPECT_CALL(mock_snapshot_copy_request, cancel());
+
+  request->send();
+  ASSERT_EQ(-ECANCELED, ctx.wait());
+}
+
+TEST_F(TestMockImageSync, CancelAfterCopyImage) {
+  librbd::MockImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+  librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+  journal::MockJournaler mock_journaler;
+  MockImageCopyRequest mock_image_copy_request;
+  MockSnapshotCopyRequest mock_snapshot_copy_request;
+  MockSyncPointCreateRequest mock_sync_point_create_request;
+  MockSyncPointPruneRequest mock_sync_point_prune_request;
+
+  librbd::MockObjectMap *mock_object_map = new librbd::MockObjectMap();
+  mock_local_image_ctx.object_map = mock_object_map;
+  expect_test_features(mock_local_image_ctx);
+
+  C_SaferCond ctx;
+  MockImageSync *request = create_request(mock_remote_image_ctx,
+                                          mock_local_image_ctx,
+                                          mock_journaler, &ctx);
+  InSequence seq;
+  expect_create_sync_point(mock_local_image_ctx, mock_sync_point_create_request, 0);
+  expect_copy_snapshots(mock_snapshot_copy_request, 0);
+  EXPECT_CALL(mock_image_copy_request, send())
+    .WillOnce((DoAll(InvokeWithoutArgs([request]() {
+	      request->cancel();
+	    }),
+	  Invoke([this, &mock_image_copy_request]() {
+	      m_threads->work_queue->queue(mock_image_copy_request.on_finish, 0);
+	    }))));
+  EXPECT_CALL(mock_image_copy_request, cancel());
+
+  request->send();
+  ASSERT_EQ(-ECANCELED, ctx.wait());
+}
+
 } // namespace mirror
 } // namespace rbd
diff --git a/src/test/ubuntu-12.04/install-deps.sh b/src/test/ubuntu-12.04/install-deps.sh
index 21e71ee..03ca760 100755
--- a/src/test/ubuntu-12.04/install-deps.sh
+++ b/src/test/ubuntu-12.04/install-deps.sh
@@ -28,7 +28,7 @@ if type apt-get > /dev/null 2>&1 ; then
 fi
 
 if type zypper > /dev/null 2>&1 ; then
-    $SUDO zypper --gpg-auto-import-keys --non-interactive install lsb-release
+    $SUDO zypper --gpg-auto-import-keys --non-interactive install lsb-release systemd-rpm-macros
 fi
 
 case $(lsb_release -si) in
diff --git a/src/test/ubuntu-14.04/install-deps.sh b/src/test/ubuntu-14.04/install-deps.sh
index 21e71ee..03ca760 100755
--- a/src/test/ubuntu-14.04/install-deps.sh
+++ b/src/test/ubuntu-14.04/install-deps.sh
@@ -28,7 +28,7 @@ if type apt-get > /dev/null 2>&1 ; then
 fi
 
 if type zypper > /dev/null 2>&1 ; then
-    $SUDO zypper --gpg-auto-import-keys --non-interactive install lsb-release
+    $SUDO zypper --gpg-auto-import-keys --non-interactive install lsb-release systemd-rpm-macros
 fi
 
 case $(lsb_release -si) in
diff --git a/src/tools/Makefile-client.am b/src/tools/Makefile-client.am
index 7b80aac..7762c8b 100644
--- a/src/tools/Makefile-client.am
+++ b/src/tools/Makefile-client.am
@@ -95,10 +95,13 @@ librbd_mirror_internal_la_SOURCES = \
 	tools/rbd_mirror/Mirror.cc \
 	tools/rbd_mirror/PoolWatcher.cc \
 	tools/rbd_mirror/Replayer.cc \
+        tools/rbd_mirror/ImageDeleter.cc \
 	tools/rbd_mirror/Threads.cc \
 	tools/rbd_mirror/types.cc \
 	tools/rbd_mirror/image_replayer/BootstrapRequest.cc \
 	tools/rbd_mirror/image_replayer/CloseImageRequest.cc \
+	tools/rbd_mirror/image_replayer/CreateImageRequest.cc \
+	tools/rbd_mirror/image_replayer/OpenImageRequest.cc \
 	tools/rbd_mirror/image_replayer/OpenLocalImageRequest.cc \
 	tools/rbd_mirror/image_replayer/ReplayStatusFormatter.cc \
 	tools/rbd_mirror/image_sync/ImageCopyRequest.cc \
@@ -109,6 +112,7 @@ librbd_mirror_internal_la_SOURCES = \
 	tools/rbd_mirror/image_sync/SyncPointPruneRequest.cc
 noinst_LTLIBRARIES += librbd_mirror_internal.la
 noinst_HEADERS += \
+	tools/rbd_mirror/BaseRequest.h \
 	tools/rbd_mirror/ClusterWatcher.h \
 	tools/rbd_mirror/ImageReplayer.h \
 	tools/rbd_mirror/ImageSync.h \
@@ -116,12 +120,16 @@ noinst_HEADERS += \
 	tools/rbd_mirror/PoolWatcher.h \
 	tools/rbd_mirror/ProgressContext.h \
 	tools/rbd_mirror/Replayer.h \
+        tools/rbd_mirror/ImageDeleter.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/CreateImageRequest.h \
+	tools/rbd_mirror/image_replayer/OpenImageRequest.h \
 	tools/rbd_mirror/image_replayer/OpenLocalImageRequest.h \
 	tools/rbd_mirror/image_replayer/ReplayStatusFormatter.h \
+	tools/rbd_mirror/image_replayer/Utils.h \
 	tools/rbd_mirror/image_sync/ImageCopyRequest.h \
 	tools/rbd_mirror/image_sync/ObjectCopyRequest.h \
 	tools/rbd_mirror/image_sync/SnapshotCopyRequest.h \
diff --git a/src/tools/rbd/action/Journal.cc b/src/tools/rbd/action/Journal.cc
index e82265c..0c85c26 100644
--- a/src/tools/rbd/action/Journal.cc
+++ b/src/tools/rbd/action/Journal.cc
@@ -200,13 +200,13 @@ public:
   }
 
   int shut_down() {
-    ::journal::Journaler::shut_down();
-
     int r = unregister_client();
     if (r < 0) {
       std::cerr << "rbd: failed to unregister journal client: "
 		<< cpp_strerror(r) << std::endl;
     }
+    ::journal::Journaler::shut_down();
+
     return r;
   }
 };
@@ -235,7 +235,6 @@ public:
     m_journaler.start_replay(&replay_handler);
 
     r = m_cond.wait();
-
     if (r < 0) {
       std::cerr << "rbd: failed to process journal: " << cpp_strerror(r)
 		<< std::endl;
@@ -243,15 +242,13 @@ public:
        m_r = r;
       }
     }
-
-    r = m_journaler.shut_down();
-    if (r < 0 && m_r == 0) {
-      m_r = r;
-    }
-
     return m_r;
   }
 
+  int shut_down() {
+    return m_journaler.shut_down();
+  }
+
 protected:
   struct ReplayHandler : public ::journal::ReplayHandler {
     JournalPlayer *journal;
@@ -288,8 +285,10 @@ protected:
 			    uint64_t tag_id) = 0;
 
   void handle_replay_complete(int r) {
-    m_journaler.stop_replay();
-    m_cond.complete(r);
+    if (m_r == 0 && r < 0) {
+      m_r = r;
+    }
+    m_journaler.stop_replay(&m_cond);
   }
 
   Journaler m_journaler;
@@ -370,7 +369,18 @@ private:
 static int do_inspect_journal(librados::IoCtx& io_ctx,
 			      const std::string& journal_id,
 			      bool verbose) {
-  return JournalInspector(io_ctx, journal_id, verbose).exec();
+  JournalInspector inspector(io_ctx, journal_id, verbose);
+  int r = inspector.exec();
+  if (r < 0) {
+    inspector.shut_down();
+    return r;
+  }
+
+  r = inspector.shut_down();
+  if (r < 0) {
+    return r;
+  }
+  return 0;
 }
 
 struct ExportEntry {
@@ -504,12 +514,18 @@ static int do_export_journal(librados::IoCtx& io_ctx,
     posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL);
   }
 
-  r = JournalExporter(io_ctx, journal_id, fd, no_error, verbose).exec();
+  JournalExporter exporter(io_ctx, journal_id, fd, no_error, verbose);
+  r = exporter.exec();
 
   if (!to_stdout) {
     close(fd);
   }
 
+  int shut_down_r = exporter.shut_down();
+  if (r == 0 && shut_down_r < 0) {
+    r = shut_down_r;
+  }
+
   return r;
 }
 
@@ -673,13 +689,13 @@ public:
     if (r1 < 0 && r == 0) {
       r = r1;
     }
-    r1 = m_journaler.shut_down();
-    if (r1 < 0 && r == 0) {
-      r = r1;
-    }
     return r;
   }
 
+  int shut_down() {
+    return m_journaler.shut_down();
+  }
+
 private:
   Journaler m_journaler;
   int m_fd;
@@ -706,12 +722,18 @@ static int do_import_journal(librados::IoCtx& io_ctx,
     posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL);
   }
 
-  r = JournalImporter(io_ctx, journal_id, fd, no_error, verbose).exec();
+  JournalImporter importer(io_ctx, journal_id, fd, no_error, verbose);
+  r = importer.exec();
 
   if (!from_stdin) {
     close(fd);
   }
 
+  int shut_down_r = importer.shut_down();
+  if (r == 0 && shut_down_r < 0) {
+    r = shut_down_r;
+  }
+
   return r;
 }
 
diff --git a/src/tools/rbd_mirror/BaseRequest.h b/src/tools/rbd_mirror/BaseRequest.h
new file mode 100644
index 0000000..91e4fba
--- /dev/null
+++ b/src/tools/rbd_mirror/BaseRequest.h
@@ -0,0 +1,42 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_RBD_MIRROR_BASE_REQUEST_H
+#define CEPH_RBD_MIRROR_BASE_REQUEST_H
+
+#include "common/RefCountedObj.h"
+
+namespace rbd {
+namespace mirror {
+
+class BaseRequest : public RefCountedObject {
+public:
+  BaseRequest(const std::string& name, CephContext *cct, Context *on_finish)
+    : RefCountedObject(cct, 1), m_name(name), m_cct(cct),
+      m_on_finish(on_finish) {
+  }
+
+  virtual void send() = 0;
+  virtual void cancel() {}
+
+protected:
+  void finish(int r) {
+    if (m_cct) {
+      lsubdout(m_cct, rbd_mirror, 20) << m_name << "::finish: r=" << r << dendl;
+    }
+    if (m_on_finish) {
+      m_on_finish->complete(r);
+    }
+    put();
+  }
+
+private:
+  const std::string m_name;
+  CephContext *m_cct;
+  Context *m_on_finish;
+};
+
+} // namespace mirror
+} // namespace rbd
+
+#endif // CEPH_RBD_MIRROR_BASE_REQUEST_H
diff --git a/src/tools/rbd_mirror/ClusterWatcher.cc b/src/tools/rbd_mirror/ClusterWatcher.cc
index 7a1e14a..516e1e3 100644
--- a/src/tools/rbd_mirror/ClusterWatcher.cc
+++ b/src/tools/rbd_mirror/ClusterWatcher.cc
@@ -10,7 +10,8 @@
 
 #define dout_subsys ceph_subsys_rbd_mirror
 #undef dout_prefix
-#define dout_prefix *_dout << "rbd-mirror: ClusterWatcher::" << __func__ << ": "
+#define dout_prefix *_dout << "rbd::mirror::ClusterWatcher:" << this << " " \
+                           << __func__ << ": "
 
 using std::list;
 using std::map;
@@ -31,13 +32,13 @@ ClusterWatcher::ClusterWatcher(RadosRef cluster, Mutex &lock) :
 {
 }
 
-const map<peer_t, set<int64_t> >& ClusterWatcher::get_peer_configs() const
+const ClusterWatcher::PoolPeers& ClusterWatcher::get_pool_peers() const
 {
   assert(m_lock.is_locked());
-  return m_peer_configs;
+  return m_pool_peers;
 }
 
-const std::set<std::string>& ClusterWatcher::get_pool_names() const
+const ClusterWatcher::PoolNames& ClusterWatcher::get_pool_names() const
 {
   assert(m_lock.is_locked());
   return m_pool_names;
@@ -46,19 +47,20 @@ const std::set<std::string>& ClusterWatcher::get_pool_names() const
 void ClusterWatcher::refresh_pools()
 {
   dout(20) << "enter" << dendl;
-  map<peer_t, set<int64_t> > peer_configs;
-  set<string> pool_names;
-  read_configs(&peer_configs, &pool_names);
+
+  PoolPeers pool_peers;
+  PoolNames pool_names;
+  read_pool_peers(&pool_peers, &pool_names);
 
   Mutex::Locker l(m_lock);
-  m_peer_configs = peer_configs;
+  m_pool_peers = pool_peers;
   m_pool_names = pool_names;
   // TODO: perhaps use a workqueue instead, once we get notifications
   // about config changes for existing pools
 }
 
-void ClusterWatcher::read_configs(map<peer_t, set<int64_t> > *peer_configs,
-				  set<string> *pool_names)
+void ClusterWatcher::read_pool_peers(PoolPeers *pool_peers,
+				     PoolNames *pool_names)
 {
   list<pair<int64_t, string> > pools;
   int r = m_cluster->pool_list2(pools);
@@ -108,21 +110,14 @@ void ClusterWatcher::read_configs(map<peer_t, set<int64_t> > *peer_configs,
 
     vector<librbd::mirror_peer_t> configs;
     r = librbd::mirror_peer_list(ioctx, &configs);
-    if (r == -ENOENT)
-      continue; // raced with disabling mirroring
     if (r < 0) {
       derr << "error reading mirroring config for pool " << pool_name
 	   << cpp_strerror(r) << dendl;
       continue;
     }
 
-    for (peer_t peer : configs) {
-      dout(20) << "pool " << pool_name << " has mirroring enabled for peer "
-	       << peer << dendl;
-      (*peer_configs)[peer].insert(pool_id);
-    }
-
-    pool_names->insert(ioctx.get_pool_name());
+    pool_peers->insert({pool_id, Peers{configs.begin(), configs.end()}});
+    pool_names->insert(pool_name);
   }
 }
 
diff --git a/src/tools/rbd_mirror/ClusterWatcher.h b/src/tools/rbd_mirror/ClusterWatcher.h
index d708748..b21e49a 100644
--- a/src/tools/rbd_mirror/ClusterWatcher.h
+++ b/src/tools/rbd_mirror/ClusterWatcher.h
@@ -23,23 +23,27 @@ namespace mirror {
  */
 class ClusterWatcher {
 public:
+  typedef std::set<peer_t> Peers;
+  typedef std::map<int64_t, Peers>  PoolPeers;
+  typedef std::set<std::string> PoolNames;
+
   ClusterWatcher(RadosRef cluster, Mutex &lock);
   ~ClusterWatcher() = default;
   ClusterWatcher(const ClusterWatcher&) = delete;
   ClusterWatcher& operator=(const ClusterWatcher&) = delete;
+
   // Caller controls frequency of calls
   void refresh_pools();
-  const std::map<peer_t, std::set<int64_t> >& get_peer_configs() const;
-  const std::set<std::string>& get_pool_names() const;
+  const PoolPeers& get_pool_peers() const;
+  const PoolNames& get_pool_names() const;
 
 private:
-  void read_configs(std::map<peer_t, std::set<int64_t> > *peer_configs,
-		    std::set<std::string> *pool_names);
-
   Mutex &m_lock;
   RadosRef m_cluster;
-  std::map<peer_t, std::set<int64_t> > m_peer_configs;
-  std::set<std::string> m_pool_names;
+  PoolPeers m_pool_peers;
+  PoolNames m_pool_names;
+
+  void read_pool_peers(PoolPeers *pool_peers, PoolNames *pool_names);
 };
 
 } // namespace mirror
diff --git a/src/tools/rbd_mirror/ImageDeleter.cc b/src/tools/rbd_mirror/ImageDeleter.cc
new file mode 100644
index 0000000..528c985
--- /dev/null
+++ b/src/tools/rbd_mirror/ImageDeleter.cc
@@ -0,0 +1,570 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2016 SUSE LINUX GmbH
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation.  See file COPYING.
+ *
+ */
+#include <boost/bind.hpp>
+#include <map>
+#include <set>
+#include <sstream>
+
+#include "include/rados/librados.hpp"
+#include "common/Formatter.h"
+#include "common/admin_socket.h"
+#include "common/debug.h"
+#include "common/errno.h"
+#include "librbd/internal.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/ImageState.h"
+#include "librbd/Journal.h"
+#include "librbd/Operations.h"
+#include "librbd/journal/Policy.h"
+#include "cls/rbd/cls_rbd_client.h"
+#include "cls/rbd/cls_rbd_types.h"
+#include "librbd/Utils.h"
+#include "ImageDeleter.h"
+
+#define dout_subsys ceph_subsys_rbd_mirror
+#undef dout_prefix
+#define dout_prefix *_dout << "rbd::mirror::ImageDeleter: " << this << " " \
+                           << __func__ << ": "
+
+using std::string;
+using std::map;
+using std::stringstream;
+using std::vector;
+using std::pair;
+using std::make_pair;
+
+using librados::IoCtx;
+using namespace librbd;
+
+namespace rbd {
+namespace mirror {
+
+namespace {
+
+class ImageDeleterAdminSocketCommand {
+public:
+  virtual ~ImageDeleterAdminSocketCommand() {}
+  virtual bool call(Formatter *f, stringstream *ss) = 0;
+};
+
+class StatusCommand : public ImageDeleterAdminSocketCommand {
+public:
+  explicit StatusCommand(ImageDeleter *image_del) : image_del(image_del) {}
+
+  bool call(Formatter *f, stringstream *ss) {
+    image_del->print_status(f, ss);
+    return true;
+  }
+
+private:
+  ImageDeleter *image_del;
+};
+
+struct DeleteJournalPolicy : public librbd::journal::Policy {
+  virtual void allocate_tag_on_lock(Context *on_finish) {
+    on_finish->complete(0);
+  }
+
+  virtual void cancel_external_replay(Context *on_finish) {
+    on_finish->complete(0);
+  }
+};
+
+} // anonymous namespace
+
+class ImageDeleterAdminSocketHook : public AdminSocketHook {
+public:
+  ImageDeleterAdminSocketHook(CephContext *cct, ImageDeleter *image_del) :
+    admin_socket(cct->get_admin_socket()) {
+
+    std::string command;
+    int r;
+
+    command = "rbd mirror deletion status";
+    r = admin_socket->register_command(command, command, this,
+				       "get status for image deleter");
+    if (r == 0) {
+      commands[command] = new StatusCommand(image_del);
+    }
+
+  }
+
+  ~ImageDeleterAdminSocketHook() {
+    for (Commands::const_iterator i = commands.begin(); i != commands.end();
+	 ++i) {
+      (void)admin_socket->unregister_command(i->first);
+      delete i->second;
+    }
+  }
+
+  bool call(std::string command, cmdmap_t& cmdmap, std::string format,
+	    bufferlist& out) {
+    Commands::const_iterator i = commands.find(command);
+    assert(i != commands.end());
+    Formatter *f = Formatter::create(format);
+    stringstream ss;
+    bool r = i->second->call(f, &ss);
+    delete f;
+    out.append(ss);
+    return r;
+  }
+
+private:
+  typedef std::map<std::string, ImageDeleterAdminSocketCommand*> Commands;
+  AdminSocket *admin_socket;
+  Commands commands;
+};
+
+ImageDeleter::ImageDeleter(RadosRef local_cluster, SafeTimer *timer,
+                           Mutex *timer_lock)
+  : m_local(local_cluster),
+    m_running(1),
+    m_delete_lock("rbd::mirror::ImageDeleter::Delete"),
+    m_image_deleter_thread(this),
+    m_failed_timer(timer),
+    m_failed_timer_lock(timer_lock),
+    m_asok_hook(new ImageDeleterAdminSocketHook((CephContext *)local_cluster->cct(),
+                this))
+{
+  m_image_deleter_thread.create("image_deleter");
+}
+
+ImageDeleter::~ImageDeleter() {
+  dout(20) << "enter" << dendl;
+
+  m_running.set(0);
+  {
+    Mutex::Locker l (m_delete_lock);
+    m_delete_queue_cond.Signal();
+  }
+  if (m_image_deleter_thread.is_started()) {
+    m_image_deleter_thread.join();
+  }
+
+  delete m_asok_hook;
+  dout(20) << "return" << dendl;
+}
+
+void ImageDeleter::run() {
+  dout(20) << "enter" << dendl;
+  while(m_running.read()) {
+    m_delete_lock.Lock();
+    while (m_delete_queue.empty()) {
+      dout(20) << "waiting for delete requests" << dendl;
+      m_delete_queue_cond.Wait(m_delete_lock);
+
+      if (!m_running.read()) {
+        m_delete_lock.Unlock();
+        dout(20) << "return" << dendl;
+        return;
+      }
+    }
+
+    curr_deletion = std::move(m_delete_queue.back());
+    m_delete_queue.pop_back();
+    m_delete_lock.Unlock();
+
+    bool move_to_next = process_image_delete();
+    if (!move_to_next) {
+      if (!m_running.read()) {
+       dout(20) << "return" << dendl;
+       return;
+      }
+
+      Mutex::Locker l(m_delete_lock);
+      if (m_delete_queue.size() == 1) {
+        m_delete_queue_cond.Wait(m_delete_lock);
+      }
+    }
+  }
+}
+
+void ImageDeleter::schedule_image_delete(uint64_t local_pool_id,
+                                         const std::string& local_image_id,
+                                         const std::string& local_image_name,
+                                         const std::string& global_image_id) {
+  dout(20) << "enter" << dendl;
+
+  Mutex::Locker l(m_delete_lock);
+
+  auto del_info = find_delete_info(local_image_name);
+  if (del_info != nullptr) {
+    dout(20) << "image " << local_image_name << " was already scheduled for "
+             << "deletion" << dendl;
+    return;
+  }
+
+  m_delete_queue.push_front(unique_ptr<DeleteInfo>(
+        new DeleteInfo(local_pool_id, local_image_id, local_image_name,
+                       global_image_id)));
+  m_delete_queue_cond.Signal();
+}
+
+void ImageDeleter::wait_for_scheduled_deletion(const std::string& image_name,
+                                               Context *ctx,
+                                               bool notify_on_failed_retry) {
+  {
+    Mutex::Locker l(m_delete_lock);
+
+    auto del_info = find_delete_info(image_name);
+    if (del_info) {
+      (*del_info)->on_delete = ctx;
+      (*del_info)->notify_on_failed_retry = notify_on_failed_retry;
+      return;
+    }
+  }
+
+  // image not scheduled for deletion
+  ctx->complete(0);
+}
+
+bool ImageDeleter::process_image_delete() {
+
+  stringstream ss;
+  curr_deletion->to_string(ss);
+  std::string del_info_str = ss.str();
+  dout(10) << "start processing delete request: " << del_info_str << dendl;
+  int r;
+  cls::rbd::MirrorImage mirror_image;
+
+  // remote image was disabled, now we need to delete local image
+  IoCtx ioctx;
+  r = m_local->ioctx_create2(curr_deletion->local_pool_id, ioctx);
+  if (r < 0) {
+    derr << "error accessing local pool: " << cpp_strerror(r) << dendl;
+    enqueue_failed_delete(r);
+    return true;
+  }
+
+  dout(20) << "connected to local pool: " << ioctx.get_pool_name() << dendl;
+
+  bool is_primary = false;
+  r = Journal<>::is_tag_owner(ioctx, curr_deletion->local_image_id, &is_primary);
+  if (r < 0 && r != -ENOENT) {
+    derr << "error retrieving image primary info: " << cpp_strerror(r)
+         << dendl;
+    enqueue_failed_delete(r);
+    return true;
+  }
+  if (is_primary) {
+    dout(10) << "local image is the primary image, aborting deletion..."
+             << dendl;
+    m_delete_lock.Lock();
+    DeleteInfo *del_info = curr_deletion.release();
+    m_delete_lock.Unlock();
+    del_info->notify(-EISPRM);
+    return true;
+  }
+
+  dout(20) << "local image is not the primary" << dendl;
+
+  bool has_snapshots;
+  r = image_has_snapshots_and_children(&ioctx, curr_deletion->local_image_id,
+                                       &has_snapshots);
+  if (r < 0) {
+    enqueue_failed_delete(r);
+    return true;
+  }
+
+  mirror_image.global_image_id = curr_deletion->global_image_id;
+  mirror_image.state = cls::rbd::MIRROR_IMAGE_STATE_DISABLING;
+  r = cls_client::mirror_image_set(&ioctx, curr_deletion->local_image_id,
+                                           mirror_image);
+  if (r == -ENOENT) {
+    dout(10) << "local image is not mirrored, aborting deletion..." << dendl;
+    m_delete_lock.Lock();
+    DeleteInfo *del_info = curr_deletion.release();
+    m_delete_lock.Unlock();
+    del_info->notify(r);
+    return true;
+  } else if (r == -EEXIST || r == -EINVAL) {
+    derr << "cannot disable mirroring for image id" << curr_deletion->local_image_id
+         << ": global_image_id has changed/reused, aborting deletion: "
+         << cpp_strerror(r) << dendl;
+    m_delete_lock.Lock();
+    DeleteInfo *del_info = curr_deletion.release();
+    m_delete_lock.Unlock();
+    del_info->notify(r);
+    return true;
+  } else if (r < 0) {
+    derr << "cannot disable mirroring for image id "
+         << curr_deletion->local_image_id << ": " << cpp_strerror(r) << dendl;
+    enqueue_failed_delete(r);
+    return true;
+  }
+
+  dout(20) << "set local image mirroring to disable" << dendl;
+
+  if (has_snapshots) {
+    dout(20) << "local image has snapshots" << dendl;
+
+    ImageCtx *imgctx = new ImageCtx("", curr_deletion->local_image_id, nullptr,
+                                    ioctx, false);
+    r = imgctx->state->open();
+    if (r < 0) {
+      derr << "error opening image id " << curr_deletion->local_image_id
+           << cpp_strerror(r) << dendl;
+      enqueue_failed_delete(r);
+      delete imgctx;
+      return true;
+    }
+
+    {
+      RWLock::WLocker snap_locker(imgctx->snap_lock);
+      imgctx->set_journal_policy(new DeleteJournalPolicy());
+    }
+
+    std::vector<librbd::snap_info_t> snaps;
+    r = librbd::snap_list(imgctx, snaps);
+    if (r < 0) {
+      derr << "error listing snapshot of image " << imgctx->name
+           << cpp_strerror(r) << dendl;
+      imgctx->state->close();
+      enqueue_failed_delete(r);
+      return true;
+    }
+
+    for (const auto& snap : snaps) {
+      dout(20) << "processing deletion of snapshot " << imgctx->name << "@"
+               << snap.name << dendl;
+
+      bool is_protected;
+      r = librbd::snap_is_protected(imgctx, snap.name.c_str(), &is_protected);
+      if (r < 0) {
+        derr << "error checking snapshot protection of snapshot "
+             << imgctx->name << "@" << snap.name << ": " << cpp_strerror(r)
+             << dendl;
+        imgctx->state->close();
+        enqueue_failed_delete(r);
+        return true;
+      }
+      if (is_protected) {
+        dout(20) << "snapshot " << imgctx->name << "@" << snap.name
+                 << " is protected, issuing unprotect command" << dendl;
+
+        r = imgctx->operations->snap_unprotect(snap.name.c_str());
+        if (r == -EBUSY) {
+          // there are still clones of snapshots of this image, therefore send
+          // the delete request to the end of the queue
+          dout(10) << "local image id " << curr_deletion->local_image_id << " has "
+                   << "snapshots with cloned children, postponing deletion..."
+                   << dendl;
+          imgctx->state->close();
+          Mutex::Locker l(m_delete_lock);
+          curr_deletion->notify(r);
+          m_delete_queue.push_front(std::move(curr_deletion));
+          return false;
+        } else if (r < 0) {
+          derr << "error unprotecting snapshot " << imgctx->name << "@"
+               << snap.name << ": " << cpp_strerror(r) << dendl;
+          imgctx->state->close();
+          enqueue_failed_delete(r);
+          return true;
+        }
+      }
+
+      r = imgctx->operations->snap_remove(snap.name.c_str());
+      if (r < 0) {
+        derr << "error removing snapshot " << imgctx->name << "@"
+             << snap.name << ": " << cpp_strerror(r) << dendl;
+        imgctx->state->close();
+        enqueue_failed_delete(r);
+        return true;
+      }
+
+      dout(10) << "snapshot " << imgctx->name << "@" << snap.name
+               << " was deleted" << dendl;
+    }
+
+    imgctx->state->close();
+  }
+
+  librbd::NoOpProgressContext ctx;
+  r = librbd::remove(ioctx, curr_deletion->local_image_name.c_str(), ctx, true);
+  if (r < 0 && r != -ENOENT) {
+    derr << "error removing image " << curr_deletion->local_image_name
+         << " from local pool: " << cpp_strerror(r) << dendl;
+    enqueue_failed_delete(r);
+    return true;
+  }
+
+  // image was already deleted from rbd_directory, now we will make sure
+  // that will be also removed from rbd_mirroring
+  if (r == -ENOENT) {
+    dout(20) << "local image does not exist, removing image from rbd_mirroring"
+             << dendl;
+  }
+
+  r = cls_client::mirror_image_remove(&ioctx, curr_deletion->local_image_id);
+  if (r < 0 && r != -ENOENT) {
+    derr << "error removing image from mirroring directory: "
+         << cpp_strerror(r) << dendl;
+    enqueue_failed_delete(r);
+    return true;
+  }
+
+  dout(10) << "Successfully deleted image: " << curr_deletion->local_image_name
+           << dendl;
+
+  m_delete_lock.Lock();
+  DeleteInfo *del_info = curr_deletion.release();
+  m_delete_lock.Unlock();
+  del_info->notify(0);
+
+  return true;
+}
+
+int ImageDeleter::image_has_snapshots_and_children(IoCtx *ioctx,
+                                                   string& image_id,
+                                                   bool *has_snapshots) {
+
+  string header_oid = librbd::util::header_name(image_id);
+  ::SnapContext snapc;
+  int r = cls_client::get_snapcontext(ioctx, header_oid, &snapc);
+  if (r < 0 && r != -ENOENT) {
+    derr << "error retrieving snapshot context for image id " << image_id
+         << ": " << cpp_strerror(r) << dendl;
+    return r;
+  }
+
+  *has_snapshots = !snapc.snaps.empty();
+
+  return 0;
+}
+
+void ImageDeleter::enqueue_failed_delete(int error_code) {
+  dout(20) << "enter" << dendl;
+
+  m_delete_lock.Lock();
+  if (curr_deletion->notify_on_failed_retry) {
+    curr_deletion->notify(error_code);
+  }
+  curr_deletion->error_code = error_code;
+  bool was_empty = m_failed_queue.empty();
+  m_failed_queue.push_front(std::move(curr_deletion));
+  m_delete_lock.Unlock();
+  if (was_empty) {
+    FunctionContext *ctx = new FunctionContext(
+      boost::bind(&ImageDeleter::retry_failed_deletions, this));
+    Mutex::Locker l(*m_failed_timer_lock);
+    m_failed_timer->add_event_after(m_failed_interval, ctx);
+  }
+}
+
+void ImageDeleter::retry_failed_deletions() {
+  dout(20) << "enter" << dendl;
+
+  Mutex::Locker l(m_delete_lock);
+
+  bool empty = m_failed_queue.empty();
+  while (!m_failed_queue.empty()) {
+    m_delete_queue.push_back(std::move(m_failed_queue.back()));
+    m_delete_queue.back()->retries++;
+    m_failed_queue.pop_back();
+  }
+  if (!empty) {
+    m_delete_queue_cond.Signal();
+  }
+}
+
+void ImageDeleter::print_status(Formatter *f, stringstream *ss) {
+  dout(20) << "enter" << dendl;
+
+  if (f) {
+    f->open_object_section("image_deleter_status");
+    f->open_array_section("delete_images_queue");
+  }
+
+  Mutex::Locker l(m_delete_lock);
+  for (const auto& image : m_delete_queue) {
+    image->print_status(f, ss);
+  }
+
+  if (f) {
+    f->close_section();
+    f->open_array_section("failed_deletes_queue");
+  }
+
+  for (const auto& image : m_failed_queue) {
+    image->print_status(f, ss, true);
+  }
+
+  if (f) {
+    f->close_section();
+    f->close_section();
+    f->flush(*ss);
+  }
+}
+
+void ImageDeleter::DeleteInfo::notify(int r) {
+  if (on_delete) {
+    dout(20) << "executing image deletion handler r=" << r << dendl;
+    on_delete->complete(r);
+    on_delete = nullptr;
+  }
+}
+
+void ImageDeleter::DeleteInfo::to_string(stringstream& ss) {
+  ss << "[" << "local_pool_id=" << local_pool_id << ", ";
+  ss << "local_image_id=" << local_image_id << ", ";
+  ss << "global_image_id=" << global_image_id << "]";
+}
+
+void ImageDeleter::DeleteInfo::print_status(Formatter *f, stringstream *ss,
+                                            bool print_failure_info) {
+  if (f) {
+    f->open_object_section("delete_info");
+    f->dump_int("local_pool_id", local_pool_id);
+    f->dump_string("local_image_id", local_image_id);
+    f->dump_string("global_image_id", global_image_id);
+    if (print_failure_info) {
+      f->dump_string("error_code", cpp_strerror(error_code));
+      f->dump_int("retries", retries);
+    }
+    f->close_section();
+    f->flush(*ss);
+  } else {
+    this->to_string(*ss);
+  }
+}
+
+vector<string> ImageDeleter::get_delete_queue_items() {
+  vector<string> items;
+
+  Mutex::Locker l(m_delete_lock);
+  for (const auto& del_info : m_delete_queue) {
+    items.push_back(del_info->local_image_name);
+  }
+
+  return items;
+}
+
+vector<pair<string, int> > ImageDeleter::get_failed_queue_items() {
+  vector<pair<string, int> > items;
+
+  Mutex::Locker l(m_delete_lock);
+  for (const auto& del_info : m_failed_queue) {
+    items.push_back(make_pair(del_info->local_image_name,
+                              del_info->error_code));
+  }
+
+  return items;
+}
+
+void ImageDeleter::set_failed_timer_interval(double interval) {
+  this->m_failed_interval = interval;
+}
+
+} // namespace mirror
+} // namespace rbd
diff --git a/src/tools/rbd_mirror/ImageDeleter.h b/src/tools/rbd_mirror/ImageDeleter.h
new file mode 100644
index 0000000..591f71a
--- /dev/null
+++ b/src/tools/rbd_mirror/ImageDeleter.h
@@ -0,0 +1,154 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2016 SUSE LINUX GmbH
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation.  See file COPYING.
+ *
+ */
+
+#ifndef CEPH_RBD_MIRROR_IMAGEDELETER_H
+#define CEPH_RBD_MIRROR_IMAGEDELETER_H
+
+#include <deque>
+#include <vector>
+#include "include/atomic.h"
+#include "common/Mutex.h"
+#include "common/Cond.h"
+#include "common/Thread.h"
+#include "common/Timer.h"
+#include "types.h"
+
+namespace rbd {
+namespace mirror {
+
+class ImageDeleterAdminSocketHook;
+
+/**
+ * Manage deletion of non-primary images.
+ */
+class ImageDeleter {
+public:
+  static const int EISPRM = 1000;
+
+  ImageDeleter(RadosRef local_cluster, SafeTimer *timer, Mutex *timer_lock);
+  ~ImageDeleter();
+  ImageDeleter(const ImageDeleter&) = delete;
+  ImageDeleter& operator=(const ImageDeleter&) = delete;
+
+  void schedule_image_delete(uint64_t local_pool_id,
+                             const std::string& local_image_id,
+                             const std::string& local_image_name,
+                             const std::string& global_image_id);
+  void wait_for_scheduled_deletion(const std::string& image_name,
+                                   Context *ctx,
+                                   bool notify_on_failed_retry=true);
+
+  void print_status(Formatter *f, std::stringstream *ss);
+
+  // for testing purposes
+  std::vector<std::string> get_delete_queue_items();
+  std::vector<std::pair<std::string, int> > get_failed_queue_items();
+  void set_failed_timer_interval(double interval);
+
+private:
+
+  class ImageDeleterThread : public Thread {
+    ImageDeleter *m_image_deleter;
+  public:
+    ImageDeleterThread(ImageDeleter *image_deleter) :
+      m_image_deleter(image_deleter) {}
+    void *entry() {
+      m_image_deleter->run();
+      return 0;
+    }
+  };
+
+  struct DeleteInfo {
+    uint64_t local_pool_id;
+    std::string local_image_id;
+    std::string local_image_name;
+    std::string global_image_id;
+    int error_code;
+    int retries;
+    bool notify_on_failed_retry;
+    Context *on_delete;
+
+    DeleteInfo(uint64_t local_pool_id, const std::string& local_image_id,
+               const std::string& local_image_name,
+               const std::string& global_image_id) :
+      local_pool_id(local_pool_id), local_image_id(local_image_id),
+      local_image_name(local_image_name), global_image_id(global_image_id),
+      error_code(0), retries(0), notify_on_failed_retry(true),
+      on_delete(nullptr) {
+    }
+
+    bool match(const std::string& image_name) {
+      return local_image_name == image_name;
+    }
+    void notify(int r);
+    void to_string(std::stringstream& ss);
+    void print_status(Formatter *f, std::stringstream *ss,
+                      bool print_failure_info=false);
+  };
+
+  RadosRef m_local;
+  atomic_t m_running;
+
+  std::deque<std::unique_ptr<DeleteInfo> > m_delete_queue;
+  Mutex m_delete_lock;
+  Cond m_delete_queue_cond;
+
+  unique_ptr<DeleteInfo> curr_deletion;
+
+  ImageDeleterThread m_image_deleter_thread;
+
+  std::deque<std::unique_ptr<DeleteInfo>> m_failed_queue;
+  // TODO: make interval value configurable
+  double m_failed_interval = 30;
+  SafeTimer *m_failed_timer;
+  Mutex *m_failed_timer_lock;
+
+  ImageDeleterAdminSocketHook *m_asok_hook;
+
+  void run();
+  bool process_image_delete();
+  int image_has_snapshots_and_children(librados::IoCtx *ioctx,
+                                       std::string& image_id,
+                                       bool *has_snapshots);
+  void enqueue_failed_delete(int error_code);
+  void retry_failed_deletions();
+
+  unique_ptr<DeleteInfo> const* find_delete_info(
+                                             const std::string& image_name) {
+    assert(m_delete_lock.is_locked());
+
+    if (curr_deletion && curr_deletion->match(image_name)) {
+      return &curr_deletion;
+    }
+
+    for (const auto& del_info : m_delete_queue) {
+      if (del_info->match(image_name)) {
+        return &del_info;
+      }
+    }
+
+    for (const auto& del_info : m_failed_queue) {
+      if (del_info->match(image_name)) {
+        return &del_info;
+      }
+    }
+
+    return nullptr;
+  }
+};
+
+} // namespace mirror
+} // namespace rbd
+
+#endif // CEPH_RBD_MIRROR_IMAGEDELETER_H
diff --git a/src/tools/rbd_mirror/ImageReplayer.cc b/src/tools/rbd_mirror/ImageReplayer.cc
index 203b78c..10ad65e 100644
--- a/src/tools/rbd_mirror/ImageReplayer.cc
+++ b/src/tools/rbd_mirror/ImageReplayer.cc
@@ -26,7 +26,8 @@
 
 #define dout_subsys ceph_subsys_rbd_mirror
 #undef dout_prefix
-#define dout_prefix *_dout << "rbd-mirror: " << *this << "::" << __func__ << ": "
+#define dout_prefix *_dout << "rbd::mirror::" << *this << " " \
+                           << __func__ << ": "
 
 using std::map;
 using std::string;
@@ -50,7 +51,7 @@ 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() {
@@ -226,15 +227,10 @@ 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);
+  replayer->set_state_description(0, desc);
+  if (flush) {
+    replayer->update_mirror_image_status(false, boost::none);
+  }
 }
 
 template <typename I>
@@ -286,7 +282,8 @@ ImageReplayer<I>::~ImageReplayer()
   assert(m_replay_handler == nullptr);
   assert(m_on_start_finish == nullptr);
   assert(m_on_stop_finish == nullptr);
-
+  assert(m_bootstrap_request == nullptr);
+  assert(m_in_flight_status_updates == 0);
   delete m_asok_hook;
 }
 
@@ -339,7 +336,7 @@ void ImageReplayer<I>::start(Context *on_finish,
   if (r < 0) {
     derr << "error opening ioctx for remote pool " << m_remote_pool_id
 	 << ": " << cpp_strerror(r) << dendl;
-    on_start_fail_start(r, "error opening remote pool");
+    on_start_fail(r, "error opening remote pool");
     return;
   }
 
@@ -351,12 +348,10 @@ 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, "error opening local pool");
+    on_start_fail(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,
@@ -372,9 +367,6 @@ 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);
 
@@ -384,6 +376,16 @@ void ImageReplayer<I>::bootstrap() {
     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_progress_cxt);
+
+  {
+    Mutex::Locker locker(m_lock);
+    request->get();
+    m_bootstrap_request = request;
+  }
+
+  update_mirror_image_status(false, boost::none);
+  reschedule_update_status_task(10);
+
   request->send();
 }
 
@@ -391,12 +393,22 @@ template <typename I>
 void ImageReplayer<I>::handle_bootstrap(int r) {
   dout(20) << "r=" << r << dendl;
 
+  {
+    Mutex::Locker locker(m_lock);
+    m_bootstrap_request->put();
+    m_bootstrap_request = nullptr;
+    if (m_local_image_ctx) {
+      m_local_image_id = m_local_image_ctx->id;
+      m_local_image_name = m_local_image_ctx->name;
+    }
+  }
+
   if (r == -EREMOTEIO) {
     dout(5) << "remote image is non-primary or local image is primary" << dendl;
-    on_start_fail_start(0, "remote image is non-primary or local image is primary");
+    on_start_fail(0, "remote image is non-primary or local image is primary");
     return;
   } else if (r < 0) {
-    on_start_fail_start(r, "error bootstrapping replay");
+    on_start_fail(r, "error bootstrapping replay");
     return;
   } else if (on_start_interrupted()) {
     return;
@@ -421,8 +433,7 @@ void ImageReplayer<I>::handle_bootstrap(int r) {
     }
   }
 
-  update_mirror_image_status();
-
+  update_mirror_image_status(false, boost::none);
   init_remote_journaler();
 }
 
@@ -441,7 +452,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, "error initializing remote journal");
+    on_start_fail(r, "error initializing remote journal");
     return;
   } else if (on_start_interrupted()) {
     return;
@@ -454,11 +465,19 @@ template <typename I>
 void ImageReplayer<I>::start_replay() {
   dout(20) << dendl;
 
-  int r = m_local_image_ctx->journal->start_external_replay(&m_local_replay);
+  Context *ctx = create_context_callback<
+    ImageReplayer, &ImageReplayer<I>::handle_start_replay>(this);
+  m_local_image_ctx->journal->start_external_replay(&m_local_replay, ctx);
+}
+
+template <typename I>
+void ImageReplayer<I>::handle_start_replay(int r) {
+  dout(20) << "r=" << r << dendl;
+
   if (r < 0) {
     derr << "error starting external replay on local image "
 	 <<  m_local_image_id << ": " << cpp_strerror(r) << dendl;
-    on_start_fail_start(r, "error starting replay on local image");
+    on_start_fail(r, "error starting replay on local image");
     return;
   }
 
@@ -473,7 +492,8 @@ void ImageReplayer<I>::start_replay() {
 
   m_replay_status_formatter =
     ReplayStatusFormatter<I>::create(m_remote_journaler, m_local_mirror_uuid);
-  update_mirror_image_status();
+
+  update_mirror_image_status(true, boost::none);
   reschedule_update_status_task(30);
 
   dout(20) << "start succeeded" << dendl;
@@ -495,87 +515,30 @@ void ImageReplayer<I>::start_replay() {
 }
 
 template <typename I>
-void ImageReplayer<I>::on_start_fail_start(int r, const std::string &desc)
+void ImageReplayer<I>::on_start_fail(int r, const std::string &desc)
 {
   dout(20) << "r=" << r << dendl;
+  Context *ctx = new FunctionContext([this, r, desc](int _r) {
+      Context *on_start_finish(nullptr);
+      {
+        Mutex::Locker locker(m_lock);
+        m_state = STATE_STOPPING;
+        if (r < 0 && r != -ECANCELED) {
+          derr << "start failed: " << cpp_strerror(r) << dendl;
+        } else {
+          dout(20) << "start canceled" << dendl;
+        }
+        std::swap(m_on_start_finish, on_start_finish);
+      }
 
-  FunctionContext *ctx = new FunctionContext(
-    [this, r, desc](int r1) {
-      assert(r1 == 0);
       set_state_description(r, desc);
-      on_start_fail_finish(r);
+      update_mirror_image_status(false, boost::none);
+      shut_down(r, on_start_finish);
     });
-
   m_threads->work_queue->queue(ctx, 0);
 }
 
 template <typename I>
-void ImageReplayer<I>::on_start_fail_finish(int r)
-{
-  dout(20) << "r=" << r << dendl;
-
-  if (m_remote_journaler) {
-    if (m_remote_journaler->is_initialized()) {
-      m_remote_journaler->shut_down();
-    }
-    delete m_remote_journaler;
-    m_remote_journaler = nullptr;
-  }
-
-  if (m_local_replay) {
-    shut_down_journal_replay(true);
-    m_local_image_ctx->journal->stop_external_replay();
-    m_local_replay = nullptr;
-  }
-
-  if (m_replay_handler) {
-    delete m_replay_handler;
-    m_replay_handler = nullptr;
-  }
-
-  if (m_local_image_ctx) {
-    // TODO: switch to async close via CloseImageRequest
-    m_local_image_ctx->state->close();
-    m_local_image_ctx = nullptr;
-  }
-
-  Context *on_start_finish(nullptr);
-  Context *on_stop_finish(nullptr);
-  {
-    Mutex::Locker locker(m_lock);
-    if (m_stop_requested) {
-      assert(r == -EINTR);
-      dout(20) << "start interrupted" << dendl;
-      m_state = STATE_STOPPED;
-      m_stop_requested = false;
-    } else {
-      assert(m_state == STATE_STARTING);
-      dout(20) << "start failed" << dendl;
-      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);
-  }
-  if (on_stop_finish != nullptr) {
-    dout(20) << "on stop finish complete, r=" << r << dendl;
-    on_stop_finish->complete(0);
-  }
-}
-
-template <typename I>
 bool ImageReplayer<I>::on_start_interrupted()
 {
   Mutex::Locker locker(m_lock);
@@ -584,7 +547,7 @@ bool ImageReplayer<I>::on_start_interrupted()
     return false;
   }
 
-  on_start_fail_start(-EINTR);
+  on_start_fail(-ECANCELED);
   return true;
 }
 
@@ -602,7 +565,10 @@ void ImageReplayer<I>::stop(Context *on_finish, bool manual)
     } else {
       if (!is_stopped_()) {
 	if (m_state == STATE_STARTING) {
-	  dout(20) << "interrupting start" << dendl;
+	  dout(20) << "canceling start" << dendl;
+	  if (m_bootstrap_request) {
+	    m_bootstrap_request->cancel();
+	  }
 	} else {
 	  dout(20) << "interrupting replay" << dendl;
 	  shut_down_replay = true;
@@ -625,113 +591,29 @@ void ImageReplayer<I>::stop(Context *on_finish, bool manual)
   }
 
   if (shut_down_replay) {
-    on_stop_journal_replay_shut_down_start();
+    on_stop_journal_replay();
   } else if (on_finish != nullptr) {
     on_finish->complete(0);
   }
 }
 
 template <typename I>
-void ImageReplayer<I>::on_stop_journal_replay_shut_down_start()
+void ImageReplayer<I>::on_stop_journal_replay()
 {
   dout(20) << "enter" << dendl;
 
-  FunctionContext *ctx = new FunctionContext(
-    [this](int r) {
-      on_stop_journal_replay_shut_down_finish(r);
-    });
-
   {
     Mutex::Locker locker(m_lock);
-
-    // as we complete in-flight records, we might receive multiple stop requests
     if (m_state != STATE_REPLAYING) {
+      // might be invoked multiple times while stopping
       return;
     }
     m_state = STATE_STOPPING;
-    m_local_replay->shut_down(false, ctx);
   }
 
-  update_mirror_image_status();
-}
-
-template <typename I>
-void ImageReplayer<I>::on_stop_journal_replay_shut_down_finish(int r)
-{
-  dout(20) << "r=" << r << dendl;
-  if (r < 0) {
-    derr << "error flushing journal replay: " << cpp_strerror(r) << dendl;
-  }
-
-  {
-    Mutex::Locker locker(m_lock);
-    assert(m_state == STATE_STOPPING);
-    m_local_image_ctx->journal->stop_external_replay();
-    m_local_replay = nullptr;
-    m_replay_entry = ReplayEntry();
-    m_replay_tag_valid = false;
-  }
-
-  on_stop_local_image_close_start();
-}
-
-template <typename I>
-void ImageReplayer<I>::on_stop_local_image_close_start()
-{
-  dout(20) << "enter" << dendl;
-
-  // close and delete the image (from outside the image's thread context)
-  Context *ctx = create_context_callback<
-    ImageReplayer, &ImageReplayer<I>::on_stop_local_image_close_finish>(this);
-  CloseImageRequest<I> *request = CloseImageRequest<I>::create(
-    &m_local_image_ctx, m_threads->work_queue, false, ctx);
-  request->send();
-}
-
-template <typename I>
-void ImageReplayer<I>::on_stop_local_image_close_finish(int r)
-{
-  dout(20) << "r=" << r << dendl;
-
-  if (r < 0) {
-    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;
-  m_remote_journaler = nullptr;
-
-  delete m_replay_handler;
-  m_replay_handler = nullptr;
-
-  m_remote_ioctx.close();
-
-  Context *on_finish(nullptr);
-
-  {
-    Mutex::Locker locker(m_lock);
-    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);
-  }
-
-  dout(20) << "stop complete" << dendl;
-
-  if (on_finish != nullptr) {
-    dout(20) << "on finish complete, r=" << r << dendl;
-    on_finish->complete(r);
-  }
+  set_state_description(0, "");
+  update_mirror_image_status(false, boost::none);
+  shut_down(0, nullptr);
 }
 
 template <typename I>
@@ -845,7 +727,7 @@ void ImageReplayer<I>::on_flush_flush_commit_position_finish(Context *on_flush,
 	 << cpp_strerror(r) << dendl;
   }
 
-  update_mirror_image_status();
+  update_mirror_image_status(false, boost::none);
 
   dout(20) << "flush complete, r=" << r << dendl;
   on_flush->complete(r);
@@ -861,7 +743,7 @@ bool ImageReplayer<I>::on_replay_interrupted()
   }
 
   if (shut_down) {
-    on_stop_journal_replay_shut_down_start();
+    on_stop_journal_replay();
   }
   return shut_down;
 }
@@ -1043,208 +925,329 @@ void ImageReplayer<I>::handle_process_entry_safe(const ReplayEntry& replay_entry
 }
 
 template <typename I>
-void ImageReplayer<I>::shut_down_journal_replay(bool cancel_ops)
-{
-  C_SaferCond cond;
-  m_local_replay->shut_down(cancel_ops, &cond);
-  int r = cond.wait();
-  if (r < 0) {
-    derr << "error flushing journal replay: " << cpp_strerror(r) << dendl;
+bool ImageReplayer<I>::update_mirror_image_status(bool force,
+                                                  const OptionalState &state) {
+  dout(20) << dendl;
+  {
+    Mutex::Locker locker(m_lock);
+    if (!start_mirror_image_status_update(force, false)) {
+      return false;
+    }
   }
+
+  queue_mirror_image_status_update(state);
+  return true;
 }
 
 template <typename I>
-void ImageReplayer<I>::update_mirror_image_status(bool final,
-						  State expected_state)
-{
-  dout(20) << "final=" << final << ", expected_state=" << expected_state
-	   << dendl;
+bool ImageReplayer<I>::start_mirror_image_status_update(bool force,
+                                                        bool restarting) {
+  assert(m_lock.is_locked());
 
-  cls::rbd::MirrorImageStatus status;
+  if (!force && !is_stopped_()) {
+    if (!is_running_()) {
+      dout(20) << "shut down in-progress: ignoring update" << dendl;
+      return false;
+    } else if (m_in_flight_status_updates > (restarting ? 1 : 0)) {
+      dout(20) << "already sending update" << dendl;
+      m_update_status_requested = true;
+      return false;
+    }
+  }
 
+  dout(20) << dendl;
+  ++m_in_flight_status_updates;
+  return true;
+}
+
+template <typename I>
+void ImageReplayer<I>::finish_mirror_image_status_update() {
+  Context *on_finish = nullptr;
   {
     Mutex::Locker locker(m_lock);
+    assert(m_in_flight_status_updates > 0);
+    if (--m_in_flight_status_updates > 0) {
+      dout(20) << "waiting on " << m_in_flight_status_updates << " in-flight "
+               << "updates" << dendl;
+      return;
+    }
 
-    assert(!final || !is_running_());
+    std::swap(on_finish, m_on_update_status_finish);
+  }
 
-    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;
-      }
+  dout(20) << dendl;
+  if (on_finish != nullptr) {
+    on_finish->complete(0);
+  }
+}
 
-      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;
+template <typename I>
+void ImageReplayer<I>::queue_mirror_image_status_update(const OptionalState &state) {
+  dout(20) << dendl;
+  FunctionContext *ctx = new FunctionContext(
+    [this, state](int r) {
+      send_mirror_status_update(state);
+    });
+  m_threads->work_queue->queue(ctx, 0);
+}
+
+template <typename I>
+void ImageReplayer<I>::send_mirror_status_update(const OptionalState &opt_state) {
+  State state;
+  std::string state_desc;
+  int last_r;
+  bool bootstrapping;
+  bool stopping_replay;
+  {
+    Mutex::Locker locker(m_lock);
+    state = m_state;
+    state_desc = m_state_desc;
+    last_r = m_last_r;
+    bootstrapping = (m_bootstrap_request != nullptr);
+    stopping_replay = (m_local_image_ctx != nullptr);
+  }
+
+  if (opt_state) {
+    state = *opt_state;
+  }
+
+  cls::rbd::MirrorImageStatus status;
+  status.up = true;
+  switch (state) {
+  case STATE_STARTING:
+    if (bootstrapping) {
+      status.state = cls::rbd::MIRROR_IMAGE_STATUS_STATE_SYNCING;
+      status.description = state_desc.empty() ? "syncing" : 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;
+    {
+      Context *on_req_finish = new FunctionContext(
+        [this](int r) {
+          if (r >= 0) {
+            dout(20) << "replay status ready" << dendl;
+            send_mirror_status_update(boost::none);
+          }
+        });
 
-    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;
+      std::string desc;
+      if (!m_replay_status_formatter->get_or_send_update(&desc,
+                                                         on_req_finish)) {
+        dout(20) << "waiting for replay status" << dendl;
+        return;
       }
+      status.description = "replaying, " + desc;
+    }
+    break;
+  case STATE_STOPPING:
+    if (stopping_replay) {
+      status.state = cls::rbd::MIRROR_IMAGE_STATUS_STATE_STOPPING_REPLAY;
+      status.description = "stopping replay";
       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;
+    // FALLTHROUGH
+  case STATE_STOPPED:
+    if (last_r < 0) {
+      status.state = cls::rbd::MIRROR_IMAGE_STATUS_STATE_ERROR;
+      status.description = state_desc;
+    } else {
+      status.state = cls::rbd::MIRROR_IMAGE_STATUS_STATE_STOPPED;
+      status.description = state_desc.empty() ? "stopped" : state_desc;
     }
-    status.description = "replaying, " + desc;
+    break;
+  default:
+    assert(!"invalid state");
   }
 
   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;
+  librados::AioCompletion *aio_comp = create_rados_ack_callback<
+    ImageReplayer<I>, &ImageReplayer<I>::handle_mirror_status_update>(this);
+  int r = m_local_ioctx.aio_operate(RBD_MIRRORING, aio_comp, &op);
+  assert(r == 0);
+  aio_comp->release();
+}
+
+template <typename I>
+void ImageReplayer<I>::handle_mirror_status_update(int r) {
+  dout(20) << "r=" << r << dendl;
+
+  bool running = false;
+  bool started = false;
+  {
+    Mutex::Locker locker(m_lock);
+    bool update_status_requested = false;
+    std::swap(update_status_requested, m_update_status_requested);
+
+    running = is_running_();
+    if (running && update_status_requested) {
+      started = start_mirror_image_status_update(false, true);
     }
-    return;
   }
 
-  int r = m_local_ioctx.aio_operate(RBD_MIRRORING, m_update_status_comp, &op);
-  assert(r == 0);
+  // if a deferred update is available, send it -- otherwise reschedule
+  // the timer task
+  if (started) {
+    queue_mirror_image_status_update(boost::none);
+  } else if (running) {
+    reschedule_update_status_task();
+  }
 
-  reschedule_update_status_task();
+  // mark committed status update as no longer in-flight
+  finish_mirror_image_status_update();
 }
 
 template <typename I>
-void ImageReplayer<I>::reschedule_update_status_task(int new_interval)
-{
-  Mutex::Locker locker(m_threads->timer_lock);
+void ImageReplayer<I>::reschedule_update_status_task(int new_interval) {
+  dout(20) << dendl;
 
-  if (m_update_status_task) {
-    m_threads->timer->cancel_event(m_update_status_task);
-    m_update_status_task = nullptr;
+  bool canceled_task = false;
+  {
+    Mutex::Locker locker(m_lock);
+    Mutex::Locker timer_locker(m_threads->timer_lock);
+
+    if (m_update_status_task) {
+      canceled_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;
+    }
+
+    bool restarting = (new_interval == 0 || canceled_task);
+    if (new_interval >= 0 && is_running_() &&
+        start_mirror_image_status_update(false, restarting)) {
+      m_update_status_task = new FunctionContext(
+        [this](int r) {
+          assert(m_threads->timer_lock.is_locked());
+          m_update_status_task = nullptr;
+
+          queue_mirror_image_status_update(boost::none);
+        });
+      m_threads->timer->add_event_after(m_update_status_interval,
+                                        m_update_status_task);
+    }
   }
 
-  if (new_interval > 0) {
-    m_update_status_interval = new_interval;
+  if (canceled_task) {
+    dout(20) << "canceled task" << dendl;
+    finish_mirror_image_status_update();
   }
+}
 
-  if (new_interval < 0) {
-    return;
+template <typename I>
+void ImageReplayer<I>::shut_down(int r, Context *on_start) {
+  dout(20) << "r=" << r << dendl;
+  {
+    Mutex::Locker locker(m_lock);
+    assert(m_state == STATE_STOPPING);
   }
 
-  m_update_status_task = new FunctionContext(
-    [this](int r) {
-      start_update_status_task();
+  // chain the shut down sequence (reverse order)
+  Context *ctx = new FunctionContext(
+    [this, r, on_start](int _r) {
+      update_mirror_image_status(true, STATE_STOPPED);
+      handle_shut_down(r, on_start);
     });
-
-  m_threads->timer->add_event_after(m_update_status_interval,
-				    m_update_status_task);
+  if (m_local_image_ctx) {
+    ctx = new FunctionContext([this, ctx](int r) {
+      CloseImageRequest<I> *request = CloseImageRequest<I>::create(
+        &m_local_image_ctx, m_threads->work_queue, false, ctx);
+      request->send();
+    });
+  }
+  if (m_remote_journaler != nullptr) {
+    ctx = new FunctionContext([this, ctx](int r) {
+        delete m_remote_journaler;
+        m_remote_journaler = nullptr;
+        ctx->complete(0);
+      });
+    ctx = new FunctionContext([this, ctx](int r) {
+        m_remote_journaler->shut_down(ctx);
+      });
+  }
+  if (m_local_replay != nullptr) {
+    ctx = new FunctionContext([this, ctx](int r) {
+        if (r < 0) {
+          derr << "error flushing journal replay: " << cpp_strerror(r) << dendl;
+        }
+        m_local_image_ctx->journal->stop_external_replay();
+        m_local_replay = nullptr;
+        ctx->complete(0);
+      });
+    ctx = new FunctionContext([this, ctx](int r) {
+        m_local_replay->shut_down(true, ctx);
+      });
+  }
+  if (m_replay_handler != nullptr) {
+    ctx = new FunctionContext([this, ctx](int r) {
+        delete m_replay_handler;
+        m_replay_handler = nullptr;
+        ctx->complete(0);
+      });
+    ctx = new FunctionContext([this, ctx](int r) {
+        m_remote_journaler->stop_replay(ctx);
+      });
+  }
+  m_threads->work_queue->queue(ctx, 0);
 }
 
 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);
+void ImageReplayer<I>::handle_shut_down(int r, Context *on_start) {
+  reschedule_update_status_task(-1);
+
+  Context *on_stop = nullptr;
+  {
+    Mutex::Locker locker(m_lock);
+
+    // if status updates are in-flight, wait for them to complete
+    // before proceeding
+    if (m_in_flight_status_updates > 0) {
+      dout(20) << "waiting for in-flight status update" << dendl;
+      assert(m_on_update_status_finish == nullptr);
+      m_on_update_status_finish = new FunctionContext(
+        [this, r, on_start](int r) {
+          handle_shut_down(r, on_start);
+        });
+      return;
+    }
+
+    std::swap(on_stop, m_on_stop_finish);
+    m_stop_requested = false;
+    assert(m_state == STATE_STOPPING);
+    m_state = STATE_STOPPED;
+    m_state_desc.clear();
+    m_last_r = 0;
+  }
+  dout(20) << "stop complete" << dendl;
+
+  m_local_ioctx.close();
+  m_remote_ioctx.close();
+
+  delete m_replay_status_formatter;
+  m_replay_status_formatter = nullptr;
+
+  if (on_start != nullptr) {
+    dout(20) << "on start finish complete, r=" << r << dendl;
+    on_start->complete(r);
+    r = 0;
+  }
+  if (on_stop != nullptr) {
+    dout(20) << "on stop finish complete, r=" << r << dendl;
+    on_stop->complete(r);
+  }
 }
 
 template <typename I>
 std::string ImageReplayer<I>::to_string(const State state) {
   switch (state) {
-  case ImageReplayer<I>::STATE_UNINITIALIZED:
-    return "Uninitialized";
   case ImageReplayer<I>::STATE_STARTING:
     return "Starting";
   case ImageReplayer<I>::STATE_REPLAYING:
@@ -1262,8 +1265,8 @@ std::string ImageReplayer<I>::to_string(const State state) {
 template <typename I>
 std::ostream &operator<<(std::ostream &os, const ImageReplayer<I> &replayer)
 {
-  os << "ImageReplayer[" << replayer.get_remote_pool_id() << "/"
-     << replayer.get_remote_image_id() << "]";
+  os << "ImageReplayer: " << &replayer << " [" << replayer.get_local_pool_id()
+     << "/" << replayer.get_global_image_id() << "]";
   return os;
 }
 
diff --git a/src/tools/rbd_mirror/ImageReplayer.h b/src/tools/rbd_mirror/ImageReplayer.h
index 752eaad..6b0f993 100644
--- a/src/tools/rbd_mirror/ImageReplayer.h
+++ b/src/tools/rbd_mirror/ImageReplayer.h
@@ -8,6 +8,7 @@
 #include <string>
 #include <vector>
 
+#include "include/atomic.h"
 #include "common/Mutex.h"
 #include "common/WorkQueue.h"
 #include "include/rados/librados.hpp"
@@ -18,6 +19,7 @@
 #include "librbd/journal/TypeTraits.h"
 #include "ProgressContext.h"
 #include "types.h"
+#include <boost/optional.hpp>
 
 class AdminSocketHook;
 
@@ -40,6 +42,7 @@ namespace mirror {
 
 struct Threads;
 
+namespace image_replayer { template <typename> class BootstrapRequest; }
 namespace image_replayer { template <typename> class ReplayStatusFormatter; }
 
 /**
@@ -52,7 +55,6 @@ public:
 
   enum State {
     STATE_UNKNOWN,
-    STATE_UNINITIALIZED,
     STATE_STARTING,
     STATE_REPLAYING,
     STATE_STOPPING,
@@ -87,6 +89,27 @@ public:
   std::string get_name() { Mutex::Locker l(m_lock); return m_name; };
   void set_state_description(int r, const std::string &desc);
 
+  inline int64_t get_local_pool_id() const {
+    return m_local_pool_id;
+  }
+  inline int64_t get_remote_pool_id() const {
+    return m_remote_pool_id;
+  }
+  inline const std::string& get_global_image_id() const {
+    return m_global_image_id;
+  }
+  inline const std::string& get_remote_image_id() const {
+    return m_remote_image_id;
+  }
+  inline std::string get_local_image_id() {
+    Mutex::Locker locker(m_lock);
+    return m_local_image_id;
+  }
+  inline std::string get_local_image_name() {
+    Mutex::Locker locker(m_lock);
+    return m_local_image_name;
+  }
+
   void start(Context *on_finish = nullptr,
 	     const BootstrapParams *bootstrap_params = nullptr,
 	     bool manual = false);
@@ -99,12 +122,6 @@ public:
   virtual void handle_replay_ready();
   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;
-  }
-  inline const std::string get_remote_image_id() const {
-    return m_remote_image_id;
-  }
 protected:
   /**
    * @verbatim
@@ -167,14 +184,10 @@ protected:
    * @endverbatim
    */
 
-  virtual void on_start_fail_start(int r, const std::string &desc = "");
-  virtual void on_start_fail_finish(int r);
+  virtual void on_start_fail(int r, const std::string &desc = "");
   virtual bool on_start_interrupted();
 
-  virtual void on_stop_journal_replay_shut_down_start();
-  virtual void on_stop_journal_replay_shut_down_finish(int r);
-  virtual void on_stop_local_image_close_start();
-  virtual void on_stop_local_image_close_finish(int r);
+  virtual void on_stop_journal_replay();
 
   virtual void on_flush_local_replay_flush_start(Context *on_flush);
   virtual void on_flush_local_replay_flush_finish(Context *on_flush, int r);
@@ -187,6 +200,7 @@ protected:
 
 private:
   typedef typename librbd::journal::TypeTraits<ImageCtxT>::Journaler Journaler;
+  typedef boost::optional<State> OptionalState;
 
   class BootstrapProgressContext : public ProgressContext {
   public:
@@ -209,7 +223,7 @@ private:
   std::string m_local_image_name;
   std::string m_name;
   Mutex m_lock;
-  State m_state = STATE_UNINITIALIZED;
+  State m_state = STATE_STOPPED;
   int m_last_r = 0;
   std::string m_state_desc;
   BootstrapProgressContext m_progress_cxt;
@@ -226,12 +240,17 @@ private:
   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 = nullptr;
 
+  image_replayer::BootstrapRequest<ImageCtxT> *m_bootstrap_request = nullptr;
+
+  uint32_t m_in_flight_status_updates = 0;
+  bool m_update_status_requested = false;
+  Context *m_on_update_status_finish = nullptr;
+
   librbd::journal::MirrorPeerClientMeta m_client_meta;
 
   ReplayEntry m_replay_entry;
@@ -255,17 +274,26 @@ private:
 
   static std::string to_string(const State state);
 
-  State get_state_() const { return m_state; }
-  bool is_stopped_() const { return m_state == STATE_UNINITIALIZED ||
-                                    m_state == STATE_STOPPED; }
-  bool is_running_() const { return !is_stopped_() && m_state != STATE_STOPPING; }
-
-  void shut_down_journal_replay(bool cancel_ops);
+  State get_state_() const {
+    return m_state;
+  }
+  bool is_stopped_() const {
+    return m_state == STATE_STOPPED;
+  }
+  bool is_running_() const {
+    return !is_stopped_() && m_state != STATE_STOPPING && !m_stop_requested;
+  }
 
-  void update_mirror_image_status(bool final = false,
-				  State expected_state = STATE_UNKNOWN);
+  bool update_mirror_image_status(bool force, const OptionalState &state);
+  bool start_mirror_image_status_update(bool force, bool restarting);
+  void finish_mirror_image_status_update();
+  void queue_mirror_image_status_update(const OptionalState &state);
+  void send_mirror_status_update(const OptionalState &state);
+  void handle_mirror_status_update(int r);
   void reschedule_update_status_task(int new_interval = 0);
-  void start_update_status_task();
+
+  void shut_down(int r, Context *on_start);
+  void handle_shut_down(int r, Context *on_start);
 
   void bootstrap();
   void handle_bootstrap(int r);
@@ -274,6 +302,7 @@ private:
   void handle_init_remote_journaler(int r);
 
   void start_replay();
+  void handle_start_replay(int r);
 
   void replay_flush();
   void handle_replay_flush(int r);
diff --git a/src/tools/rbd_mirror/ImageSync.cc b/src/tools/rbd_mirror/ImageSync.cc
index f71dfdd..2f28fd4 100644
--- a/src/tools/rbd_mirror/ImageSync.cc
+++ b/src/tools/rbd_mirror/ImageSync.cc
@@ -30,17 +30,25 @@ 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,
+                        ContextWQ *work_queue, Context *on_finish,
 			ProgressContext *progress_ctx)
-  : m_local_image_ctx(local_image_ctx), m_remote_image_ctx(remote_image_ctx),
+  : BaseRequest("rbd::mirror::ImageSync", local_image_ctx->cct, on_finish),
+    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_journaler(journaler), m_client_meta(client_meta),
+    m_work_queue(work_queue), m_progress_ctx(progress_ctx),
     m_lock(unique_lock_name("ImageSync::m_lock", this)) {
 }
 
 template <typename I>
-void ImageSync<I>::start() {
+ImageSync<I>::~ImageSync() {
+  assert(m_snapshot_copy_request == nullptr);
+  assert(m_image_copy_request == nullptr);
+}
+
+template <typename I>
+void ImageSync<I>::send() {
   send_prune_catch_up_sync_point();
 }
 
@@ -48,10 +56,14 @@ template <typename I>
 void ImageSync<I>::cancel() {
   Mutex::Locker locker(m_lock);
 
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << dendl;
+  dout(20) << dendl;
 
   m_canceled = true;
+
+  if (m_snapshot_copy_request != nullptr) {
+    m_snapshot_copy_request->cancel();
+  }
+
   if (m_image_copy_request != nullptr) {
     m_image_copy_request->cancel();
   }
@@ -66,8 +78,7 @@ void ImageSync<I>::send_prune_catch_up_sync_point() {
     return;
   }
 
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << dendl;
+  dout(20) << dendl;
 
   Context *ctx = create_context_callback<
     ImageSync<I>, &ImageSync<I>::handle_prune_catch_up_sync_point>(this);
@@ -78,12 +89,11 @@ void ImageSync<I>::send_prune_catch_up_sync_point() {
 
 template <typename I>
 void ImageSync<I>::handle_prune_catch_up_sync_point(int r) {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
   if (r < 0) {
-    lderr(cct) << ": failed to prune catch-up sync point: "
-               << cpp_strerror(r) << dendl;
+    derr << ": failed to prune catch-up sync point: "
+         << cpp_strerror(r) << dendl;
     finish(r);
     return;
   }
@@ -102,8 +112,7 @@ void ImageSync<I>::send_create_sync_point() {
     return;
   }
 
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << dendl;
+  dout(20) << dendl;
 
   Context *ctx = create_context_callback<
     ImageSync<I>, &ImageSync<I>::handle_create_sync_point>(this);
@@ -114,12 +123,11 @@ void ImageSync<I>::send_create_sync_point() {
 
 template <typename I>
 void ImageSync<I>::handle_create_sync_point(int r) {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
   if (r < 0) {
-    lderr(cct) << ": failed to create sync point: " << cpp_strerror(r)
-               << dendl;
+    derr << ": failed to create sync point: " << cpp_strerror(r)
+         << dendl;
     finish(r);
     return;
   }
@@ -129,27 +137,47 @@ void ImageSync<I>::handle_create_sync_point(int r) {
 
 template <typename I>
 void ImageSync<I>::send_copy_snapshots() {
-  update_progress("COPY_SNAPSHOTS");
+  m_lock.Lock();
+  if (m_canceled) {
+    m_lock.Unlock();
+    finish(-ECANCELED);
+    return;
+  }
 
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << dendl;
+  dout(20) << dendl;
 
   Context *ctx = create_context_callback<
     ImageSync<I>, &ImageSync<I>::handle_copy_snapshots>(this);
-  SnapshotCopyRequest<I> *request = SnapshotCopyRequest<I>::create(
+  m_snapshot_copy_request = SnapshotCopyRequest<I>::create(
     m_local_image_ctx, m_remote_image_ctx, &m_snap_map, m_journaler,
-    m_client_meta, ctx);
-  request->send();
+    m_client_meta, m_work_queue, ctx);
+  m_snapshot_copy_request->get();
+  m_lock.Unlock();
+
+  update_progress("COPY_SNAPSHOTS");
+
+  m_snapshot_copy_request->send();
 }
 
 template <typename I>
 void ImageSync<I>::handle_copy_snapshots(int r) {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
-  if (r < 0) {
-    lderr(cct) << ": failed to copy snapshot metadata: "
-               << cpp_strerror(r) << dendl;
+  {
+    Mutex::Locker locker(m_lock);
+    m_snapshot_copy_request->put();
+    m_snapshot_copy_request = nullptr;
+    if (r == 0 && m_canceled) {
+      r = -ECANCELED;
+    }
+  }
+
+  if (r == -ECANCELED) {
+    dout(10) << ": snapshot copy canceled" << dendl;
+    finish(r);
+    return;
+  } else if (r < 0) {
+    derr << ": failed to copy snapshot metadata: " << cpp_strerror(r) << dendl;
     finish(r);
     return;
   }
@@ -166,10 +194,7 @@ void ImageSync<I>::send_copy_image() {
     return;
   }
 
-  update_progress("COPY_IMAGE");
-
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << dendl;
+  dout(20) << dendl;
 
   Context *ctx = create_context_callback<
     ImageSync<I>, &ImageSync<I>::handle_copy_image>(this);
@@ -177,30 +202,33 @@ void ImageSync<I>::send_copy_image() {
     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, m_progress_ctx);
+  m_image_copy_request->get();
   m_lock.Unlock();
 
+  update_progress("COPY_IMAGE");
+
   m_image_copy_request->send();
 }
 
 template <typename I>
 void ImageSync<I>::handle_copy_image(int r) {
+  dout(20) << ": r=" << r << dendl;
+
   {
     Mutex::Locker locker(m_lock);
+    m_image_copy_request->put();
     m_image_copy_request = nullptr;
     if (r == 0 && m_canceled) {
       r = -ECANCELED;
     }
   }
 
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
-
   if (r == -ECANCELED) {
-    ldout(cct, 10) << ": image copy canceled" << dendl;
+    dout(10) << ": image copy canceled" << dendl;
     finish(r);
     return;
   } else if (r < 0) {
-    lderr(cct) << ": failed to copy image: " << cpp_strerror(r) << dendl;
+    derr << ": failed to copy image: " << cpp_strerror(r) << dendl;
     finish(r);
     return;
   }
@@ -210,6 +238,8 @@ void ImageSync<I>::handle_copy_image(int r) {
 
 template <typename I>
 void ImageSync<I>::send_copy_object_map() {
+  update_progress("COPY_OBJECT_MAP");
+
   m_local_image_ctx->snap_lock.get_read();
   if (!m_local_image_ctx->test_features(RBD_FEATURE_OBJECT_MAP,
                                         m_local_image_ctx->snap_lock)) {
@@ -218,8 +248,6 @@ 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());
@@ -229,9 +257,8 @@ void ImageSync<I>::send_copy_object_map() {
   assert(snap_id_it != m_local_image_ctx->snap_ids.end());
   librados::snap_t snap_id = snap_id_it->second;
 
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": snap_id=" << snap_id << ", "
-                 << "snap_name=" << sync_point.snap_name << dendl;
+  dout(20) << ": snap_id=" << snap_id << ", "
+           << "snap_name=" << sync_point.snap_name << dendl;
 
   // rollback the object map (copy snapshot object map to HEAD)
   RWLock::WLocker object_map_locker(m_local_image_ctx->object_map_lock);
@@ -243,8 +270,7 @@ void ImageSync<I>::send_copy_object_map() {
 
 template <typename I>
 void ImageSync<I>::handle_copy_object_map(int r) {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << dendl;
+  dout(20) << dendl;
 
   assert(r == 0);
   send_refresh_object_map();
@@ -252,8 +278,7 @@ void ImageSync<I>::handle_copy_object_map(int r) {
 
 template <typename I>
 void ImageSync<I>::send_refresh_object_map() {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << dendl;
+  dout(20) << dendl;
 
   update_progress("REFRESH_OBJECT_MAP");
 
@@ -265,8 +290,7 @@ void ImageSync<I>::send_refresh_object_map() {
 
 template <typename I>
 void ImageSync<I>::handle_refresh_object_map(int r) {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << dendl;
+  dout(20) << dendl;
 
   assert(r == 0);
   {
@@ -280,8 +304,7 @@ void ImageSync<I>::handle_refresh_object_map(int r) {
 
 template <typename I>
 void ImageSync<I>::send_prune_sync_points() {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << dendl;
+  dout(20) << dendl;
 
   update_progress("PRUNE_SYNC_POINTS");
 
@@ -294,12 +317,11 @@ void ImageSync<I>::send_prune_sync_points() {
 
 template <typename I>
 void ImageSync<I>::handle_prune_sync_points(int r) {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
   if (r < 0) {
-    lderr(cct) << ": failed to prune sync point: "
-               << cpp_strerror(r) << dendl;
+    derr << ": failed to prune sync point: "
+         << cpp_strerror(r) << dendl;
     finish(r);
     return;
   }
@@ -313,15 +335,6 @@ void ImageSync<I>::handle_prune_sync_points(int r) {
 }
 
 template <typename I>
-void ImageSync<I>::finish(int r) {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
-
-  m_on_finish->complete(r);
-  delete this;
-}
-
-template <typename I>
 void ImageSync<I>::update_progress(const std::string &description) {
   dout(20) << ": " << description << dendl;
 
diff --git a/src/tools/rbd_mirror/ImageSync.h b/src/tools/rbd_mirror/ImageSync.h
index 95809ab..97bad54 100644
--- a/src/tools/rbd_mirror/ImageSync.h
+++ b/src/tools/rbd_mirror/ImageSync.h
@@ -8,10 +8,12 @@
 #include "librbd/ImageCtx.h"
 #include "librbd/journal/TypeTraits.h"
 #include "common/Mutex.h"
+#include "tools/rbd_mirror/BaseRequest.h"
 #include <map>
 #include <vector>
 
 class Context;
+class ContextWQ;
 class Mutex;
 class SafeTimer;
 namespace journal { class Journaler; }
@@ -23,9 +25,10 @@ namespace mirror {
 class ProgressContext;
 
 namespace image_sync { template <typename> class ImageCopyRequest; }
+namespace image_sync { template <typename> class SnapshotCopyRequest; }
 
 template <typename ImageCtxT = librbd::ImageCtx>
-class ImageSync {
+class ImageSync : public BaseRequest {
 public:
   typedef librbd::journal::TypeTraits<ImageCtxT> TypeTraits;
   typedef typename TypeTraits::Journaler Journaler;
@@ -36,19 +39,21 @@ public:
                            Mutex *timer_lock, const std::string &mirror_uuid,
                            Journaler *journaler,
                            MirrorPeerClientMeta *client_meta,
-                           Context *on_finish,
+                           ContextWQ *work_queue, 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,
-			 progress_ctx);
+                         mirror_uuid, journaler, client_meta, work_queue,
+                         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, ProgressContext *progress_ctx = nullptr);
+            ContextWQ *work_queue, Context *on_finish,
+            ProgressContext *progress_ctx = nullptr);
+  ~ImageSync();
 
-  void start();
+  void send();
   void cancel();
 
 private:
@@ -94,7 +99,7 @@ private:
   std::string m_mirror_uuid;
   Journaler *m_journaler;
   MirrorPeerClientMeta *m_client_meta;
-  Context *m_on_finish;
+  ContextWQ *m_work_queue;
   ProgressContext *m_progress_ctx;
 
   SnapMap m_snap_map;
@@ -102,7 +107,8 @@ private:
   Mutex m_lock;
   bool m_canceled = false;
 
-  image_sync::ImageCopyRequest<ImageCtxT> *m_image_copy_request;
+  image_sync::SnapshotCopyRequest<ImageCtxT> *m_snapshot_copy_request = nullptr;
+  image_sync::ImageCopyRequest<ImageCtxT> *m_image_copy_request = nullptr;
   decltype(ImageCtxT::object_map) m_object_map = nullptr;
 
   void send_prune_catch_up_sync_point();
@@ -126,8 +132,6 @@ private:
   void send_prune_sync_points();
   void handle_prune_sync_points(int r);
 
-  void finish(int r);
-
   void update_progress(const std::string &description);
 };
 
diff --git a/src/tools/rbd_mirror/Mirror.cc b/src/tools/rbd_mirror/Mirror.cc
index 9878780..71e64ed 100644
--- a/src/tools/rbd_mirror/Mirror.cc
+++ b/src/tools/rbd_mirror/Mirror.cc
@@ -12,7 +12,8 @@
 
 #define dout_subsys ceph_subsys_rbd_mirror
 #undef dout_prefix
-#define dout_prefix *_dout << "rbd-mirror: Mirror::" << __func__ << ": "
+#define dout_prefix *_dout << "rbd::mirror::Mirror: " << this << " " \
+                           << __func__ << ": "
 
 using std::chrono::seconds;
 using std::list;
@@ -216,6 +217,9 @@ int Mirror::init()
   // TODO: make interval configurable
   m_local_cluster_watcher.reset(new ClusterWatcher(m_local, m_lock));
 
+  m_image_deleter.reset(new ImageDeleter(m_local, m_threads->timer,
+                                         &m_threads->timer_lock));
+
   return r;
 }
 
@@ -226,7 +230,7 @@ void Mirror::run()
     m_local_cluster_watcher->refresh_pools();
     Mutex::Locker l(m_lock);
     if (!m_manual_stop) {
-      update_replayers(m_local_cluster_watcher->get_peer_configs());
+      update_replayers(m_local_cluster_watcher->get_pool_peers());
     }
     // TODO: make interval configurable
     m_cond.WaitInterval(g_ceph_context, m_lock, seconds(30));
@@ -256,6 +260,13 @@ void Mirror::print_status(Formatter *f, stringstream *ss)
 
   if (f) {
     f->close_section();
+    f->open_object_section("image_deleter");
+  }
+
+  m_image_deleter->print_status(f, ss);
+
+  if (f) {
+    f->close_section();
     f->close_section();
     f->flush(*ss);
   }
@@ -327,35 +338,42 @@ void Mirror::flush()
   }
 }
 
-void Mirror::update_replayers(const map<peer_t, set<int64_t> > &peer_configs)
+void Mirror::update_replayers(const PoolPeers &pool_peers)
 {
   dout(20) << "enter" << dendl;
   assert(m_lock.is_locked());
-  for (auto &kv : peer_configs) {
-    const peer_t &peer = kv.first;
-    if (m_replayers.find(peer) == m_replayers.end()) {
-      dout(20) << "starting replayer for " << peer << dendl;
-      unique_ptr<Replayer> replayer(new Replayer(m_threads, m_local, peer,
-						 m_args));
-      // TODO: make async, and retry connecting within replayer
-      int r = replayer->init();
-      if (r < 0) {
-	continue;
-      }
-      m_replayers.insert(std::make_pair(peer, std::move(replayer)));
-    }
-  }
 
-  // TODO: make async
+  // remove stale replayers before creating new replayers
   for (auto it = m_replayers.begin(); it != m_replayers.end();) {
-    peer_t peer = it->first;
-    if (peer_configs.find(peer) == peer_configs.end()) {
+    auto &peer = it->first.second;
+    auto pool_peer_it = pool_peers.find(it->first.first);
+    if (pool_peer_it == pool_peers.end() ||
+        pool_peer_it->second.find(peer) == pool_peer_it->second.end()) {
       dout(20) << "removing replayer for " << peer << dendl;
-      m_replayers.erase(it++);
+      // TODO: make async
+      it = m_replayers.erase(it);
     } else {
       ++it;
     }
   }
+
+  for (auto &kv : pool_peers) {
+    for (auto &peer : kv.second) {
+      PoolPeer pool_peer(kv.first, peer);
+      if (m_replayers.find(pool_peer) == m_replayers.end()) {
+        dout(20) << "starting replayer for " << peer << dendl;
+        unique_ptr<Replayer> replayer(new Replayer(m_threads, m_image_deleter,
+                                                   m_local, kv.first, peer,
+                                                   m_args));
+        // TODO: make async, and retry connecting within replayer
+        int r = replayer->init();
+        if (r < 0) {
+	  continue;
+        }
+        m_replayers.insert(std::make_pair(pool_peer, std::move(replayer)));
+      }
+    }
+  }
 }
 
 } // namespace mirror
diff --git a/src/tools/rbd_mirror/Mirror.h b/src/tools/rbd_mirror/Mirror.h
index 298f805..88f0669 100644
--- a/src/tools/rbd_mirror/Mirror.h
+++ b/src/tools/rbd_mirror/Mirror.h
@@ -14,6 +14,7 @@
 #include "include/rados/librados.hpp"
 #include "ClusterWatcher.h"
 #include "Replayer.h"
+#include "ImageDeleter.h"
 #include "types.h"
 
 namespace rbd {
@@ -46,8 +47,10 @@ public:
   void flush();
 
 private:
-  void refresh_peers(const set<peer_t> &peers);
-  void update_replayers(const map<peer_t, set<int64_t> > &peer_configs);
+  typedef ClusterWatcher::PoolPeers PoolPeers;
+  typedef std::pair<int64_t, peer_t> PoolPeer;
+
+  void update_replayers(const PoolPeers &pool_peers);
 
   CephContext *m_cct;
   std::vector<const char*> m_args;
@@ -58,7 +61,8 @@ private:
 
   // monitor local cluster for config changes in peers
   std::unique_ptr<ClusterWatcher> m_local_cluster_watcher;
-  std::map<peer_t, std::unique_ptr<Replayer> > m_replayers;
+  std::shared_ptr<ImageDeleter> m_image_deleter;
+  std::map<PoolPeer, 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/PoolWatcher.cc b/src/tools/rbd_mirror/PoolWatcher.cc
index 0117cab..21a2633 100644
--- a/src/tools/rbd_mirror/PoolWatcher.cc
+++ b/src/tools/rbd_mirror/PoolWatcher.cc
@@ -14,7 +14,8 @@
 
 #define dout_subsys ceph_subsys_rbd_mirror
 #undef dout_prefix
-#define dout_prefix *_dout << "rbd-mirror: PoolWatcher::" << __func__ << ": "
+#define dout_prefix *_dout << "rbd::mirror::PoolWatcher: " << this << " " \
+                           << __func__ << ": "
 
 using std::list;
 using std::string;
@@ -28,15 +29,15 @@ using librbd::cls_client::mirror_image_list;
 namespace rbd {
 namespace mirror {
 
-PoolWatcher::PoolWatcher(RadosRef cluster, double interval_seconds,
+PoolWatcher::PoolWatcher(librados::IoCtx &remote_io_ctx,
+                         double interval_seconds,
 			 Mutex &lock, Cond &cond) :
   m_lock(lock),
   m_refresh_cond(cond),
-  m_stopping(false),
-  m_cluster(cluster),
   m_timer(g_ceph_context, m_lock, false),
   m_interval(interval_seconds)
 {
+  m_remote_io_ctx.dup(remote_io_ctx);
   m_timer.init();
 }
 
@@ -47,7 +48,7 @@ PoolWatcher::~PoolWatcher()
   m_timer.shutdown();
 }
 
-const PoolWatcher::PoolImageIds& PoolWatcher::get_images() const
+const PoolWatcher::ImageIds& PoolWatcher::get_images() const
 {
   assert(m_lock.is_locked());
   return m_images;
@@ -55,81 +56,14 @@ const PoolWatcher::PoolImageIds& PoolWatcher::get_images() const
 
 void PoolWatcher::refresh_images(bool reschedule)
 {
-  dout(20) << "enter" << dendl;
-  PoolImageIds images;
-  list<pair<int64_t, string> > pools;
-  int r = m_cluster->pool_list2(pools);
-  if (r < 0) {
-    derr << "error listing pools: " << cpp_strerror(r) << dendl;
-    return;
-  }
-
-  for (auto kv : pools) {
-    int64_t pool_id = kv.first;
-    string pool_name = kv.second;
-    int64_t base_tier;
-    r = m_cluster->pool_get_base_tier(pool_id, &base_tier);
-    if (r == -ENOENT) {
-      dout(10) << "pool " << pool_name << " no longer exists" << dendl;
-      continue;
-    } else if (r < 0) {
-      derr << "Error retrieving base tier for pool " << pool_name << dendl;
-      continue;
-    }
-    if (pool_id != base_tier) {
-      // pool is a cache; skip it
-      continue;
-    }
-
-    IoCtx ioctx;
-    r = m_cluster->ioctx_create2(pool_id, ioctx);
-    if (r == -ENOENT) {
-      dout(10) << "pool " << pool_name << " no longer exists" << dendl;
-      continue;
-    } else if (r < 0) {
-      derr << "Error accessing pool " << pool_name << cpp_strerror(r) << dendl;
-      continue;
-    }
+  ImageIds image_ids;
+  int r = refresh(&image_ids);
 
-    rbd_mirror_mode_t mirror_mode;
-    r = librbd::mirror_mode_get(ioctx, &mirror_mode);
-    if (r < 0) {
-      derr << "could not tell whether mirroring was enabled for " << pool_name
-	   << " : " << cpp_strerror(r) << dendl;
-      continue;
-    }
-    if (mirror_mode == RBD_MIRROR_MODE_DISABLED) {
-      dout(20) << "pool " << pool_name << " has mirroring disabled" << dendl;
-      continue;
-    }
-
-    std::set<ImageIds> image_ids;
-    std::string last_read = "";
-    int max_read = 1024;
-    do {
-      std::map<std::string, std::string> mirror_images;
-      r =  mirror_image_list(&ioctx, last_read, max_read, &mirror_images);
-      if (r < 0) {
-        derr << "error listing mirrored image directory: "
-             << cpp_strerror(r) << dendl;
-        continue;
-      }
-      for (auto it = mirror_images.begin(); it != mirror_images.end(); ++it) {
-        image_ids.insert(ImageIds(it->first, it->second));
-      }
-      if (!mirror_images.empty()) {
-        last_read = mirror_images.rbegin()->first;
-      }
-      r = mirror_images.size();
-    } while (r == max_read);
-
-    if (!image_ids.empty()) {
-      images[pool_id] = std::move(image_ids);
-    }
+  Mutex::Locker l(m_lock);
+  if (r >= 0) {
+    m_images = std::move(image_ids);
   }
 
-  Mutex::Locker l(m_lock);
-  m_images = std::move(images);
   if (!m_stopping && reschedule) {
     FunctionContext *ctx = new FunctionContext(
       boost::bind(&PoolWatcher::refresh_images, this, true));
@@ -140,5 +74,62 @@ void PoolWatcher::refresh_images(bool reschedule)
   // about new/removed mirrored images
 }
 
+int PoolWatcher::refresh(ImageIds *image_ids) {
+  dout(20) << "enter" << dendl;
+
+  std::string pool_name = m_remote_io_ctx.get_pool_name();
+  rbd_mirror_mode_t mirror_mode;
+  int r = librbd::mirror_mode_get(m_remote_io_ctx, &mirror_mode);
+  if (r < 0) {
+    derr << "could not tell whether mirroring was enabled for "
+         << pool_name << ": " << cpp_strerror(r) << dendl;
+    return r;
+  }
+  if (mirror_mode == RBD_MIRROR_MODE_DISABLED) {
+    dout(20) << "pool " << pool_name << " has mirroring disabled" << dendl;
+    return 0;
+  }
+
+  std::map<std::string, std::string> images_map;
+  r = librbd::list_images_v2(m_remote_io_ctx, images_map);
+  if (r < 0) {
+    derr << "error retrieving image names from pool " << pool_name << ": "
+         << cpp_strerror(r) << dendl;
+    return r;
+  }
+
+  std::map<std::string, std::string> image_id_to_name;
+  for (const auto& img_pair : images_map) {
+    image_id_to_name.insert(std::make_pair(img_pair.second, img_pair.first));
+  }
+
+  std::string last_read = "";
+  int max_read = 1024;
+  do {
+    std::map<std::string, std::string> mirror_images;
+    r =  mirror_image_list(&m_remote_io_ctx, last_read, max_read,
+                           &mirror_images);
+    if (r < 0) {
+      derr << "error listing mirrored image directory: "
+           << cpp_strerror(r) << dendl;
+      return r;
+    }
+    for (auto it = mirror_images.begin(); it != mirror_images.end(); ++it) {
+      boost::optional<std::string> image_name(boost::none);
+      auto it2 = image_id_to_name.find(it->first);
+      if (it2 != image_id_to_name.end()) {
+        image_name = it2->second;
+      }
+      image_ids->insert(ImageId(it->first, image_name, it->second));
+    }
+    if (!mirror_images.empty()) {
+      last_read = mirror_images.rbegin()->first;
+    }
+    r = mirror_images.size();
+  } while (r == max_read);
+
+  return 0;
+}
+
 } // namespace mirror
 } // namespace rbd
diff --git a/src/tools/rbd_mirror/PoolWatcher.h b/src/tools/rbd_mirror/PoolWatcher.h
index 0ab45b4..d29a630 100644
--- a/src/tools/rbd_mirror/PoolWatcher.h
+++ b/src/tools/rbd_mirror/PoolWatcher.h
@@ -24,42 +24,47 @@ namespace mirror {
  */
 class PoolWatcher {
 public:
-  struct ImageIds {
+  struct ImageId {
     std::string id;
+    boost::optional<std::string> name;
     std::string global_id;
 
-    ImageIds(const std::string &id, const std::string &global_id = "")
-      : id(id), global_id(global_id) {
+    ImageId(const std::string &id,
+            const boost::optional<std::string> &name = boost::none,
+            const std::string &global_id = "")
+      : id(id), name(name), global_id(global_id) {
     }
 
-    inline bool operator==(const ImageIds &rhs) const {
-      return (id == rhs.id && global_id == rhs.global_id);
+    inline bool operator==(const ImageId &rhs) const {
+      return (id == rhs.id && name == rhs.name && global_id == rhs.global_id);
     }
-    inline bool operator<(const ImageIds &rhs) const {
+    inline bool operator<(const ImageId &rhs) const {
       return id < rhs.id;
     }
   };
-  typedef std::map<int64_t, std::set<ImageIds> > PoolImageIds;
+  typedef std::set<ImageId> ImageIds;
 
-  PoolWatcher(RadosRef cluster, double interval_seconds,
+  PoolWatcher(librados::IoCtx &remote_io_ctx, double interval_seconds,
 	      Mutex &lock, Cond &cond);
   ~PoolWatcher();
   PoolWatcher(const PoolWatcher&) = delete;
   PoolWatcher& operator=(const PoolWatcher&) = delete;
 
-  const PoolImageIds& get_images() const;
+  const ImageIds& get_images() const;
   void refresh_images(bool reschedule=true);
 
 private:
+  librados::IoCtx m_remote_io_ctx;
   Mutex &m_lock;
   Cond &m_refresh_cond;
-  bool m_stopping;
 
-  RadosRef m_cluster;
+  bool m_stopping = false;
   SafeTimer m_timer;
   double m_interval;
 
-  PoolImageIds m_images;
+  ImageIds m_images;
+
+  int refresh(ImageIds *image_ids);
 };
 
 } // namespace mirror
diff --git a/src/tools/rbd_mirror/Replayer.cc b/src/tools/rbd_mirror/Replayer.cc
index 54a003d..07b7991 100644
--- a/src/tools/rbd_mirror/Replayer.cc
+++ b/src/tools/rbd_mirror/Replayer.cc
@@ -13,12 +13,14 @@
 #include "include/stringify.h"
 #include "cls/rbd/cls_rbd_client.h"
 #include "librbd/ObjectWatcher.h"
+#include "librbd/internal.h"
 #include "Replayer.h"
 #include "Threads.h"
 
 #define dout_subsys ceph_subsys_rbd_mirror
 #undef dout_prefix
-#define dout_prefix *_dout << "rbd-mirror: Replayer::" << __func__ << ": "
+#define dout_prefix *_dout << "rbd::mirror::Replayer: " \
+                           << this << " " << __func__ << ": "
 
 using std::chrono::seconds;
 using std::map;
@@ -26,6 +28,8 @@ using std::string;
 using std::unique_ptr;
 using std::vector;
 
+using librbd::cls_client::dir_get_name;
+
 namespace rbd {
 namespace mirror {
 
@@ -224,19 +228,20 @@ private:
   Watcher *m_watcher;
 };
 
-Replayer::Replayer(Threads *threads, RadosRef local_cluster,
+Replayer::Replayer(Threads *threads, std::shared_ptr<ImageDeleter> image_deleter,
+                   RadosRef local_cluster, int64_t local_pool_id,
                    const peer_t &peer, const std::vector<const char*> &args) :
   m_threads(threads),
+  m_image_deleter(image_deleter),
   m_lock(stringify("rbd::mirror::Replayer ") + stringify(peer)),
   m_peer(peer),
   m_args(args),
   m_local(local_cluster),
   m_remote(new librados::Rados),
+  m_local_pool_id(local_pool_id),
   m_asok_hook(nullptr),
   m_replayer_thread(this)
 {
-  CephContext *cct = static_cast<CephContext *>(m_local->cct());
-  m_asok_hook = new ReplayerAdminSocketHook(cct, m_peer.cluster_name, this);
 }
 
 Replayer::~Replayer()
@@ -257,6 +262,13 @@ int Replayer::init()
 {
   dout(20) << "replaying for " << m_peer << dendl;
 
+  int r = m_local->ioctx_create2(m_local_pool_id, m_local_io_ctx);
+  if (r < 0) {
+    derr << "error accessing local pool " << m_local_pool_id << ": "
+         << cpp_strerror(r) << dendl;
+    return r;
+  }
+
   // NOTE: manually bootstrap a CephContext here instead of via
   // the librados API to avoid mixing global singletons between
   // the librados shared library and the daemon
@@ -273,7 +285,7 @@ int Replayer::init()
   cct->_conf->cluster = m_peer.cluster_name;
 
   // librados::Rados::conf_read_file
-  int r = cct->_conf->parse_config_files(nullptr, nullptr, 0);
+  r = cct->_conf->parse_config_files(nullptr, nullptr, 0);
   if (r < 0) {
     derr << "could not read ceph conf for " << m_peer << ": "
 	 << cpp_strerror(r) << dendl;
@@ -304,6 +316,8 @@ int Replayer::init()
     }
   }
 
+  // disable unnecessary librbd cache
+  cct->_conf->set_val_or_die("rbd_cache", "false");
   cct->_conf->apply_changes(nullptr);
   cct->_conf->complain_about_parse_errors(cct);
 
@@ -318,10 +332,22 @@ int Replayer::init()
     return r;
   }
 
+  r = m_remote->ioctx_create(m_local_io_ctx.get_pool_name().c_str(),
+                             m_remote_io_ctx);
+  if (r < 0) {
+    derr << "error accessing remote pool " << m_local_io_ctx.get_pool_name()
+         << ": " << cpp_strerror(r) << dendl;
+    return r;
+  }
+  m_remote_pool_id = m_remote_io_ctx.get_id();
+
   dout(20) << "connected to " << m_peer << dendl;
 
+  // Bootstrap existing mirroring images
+  init_local_mirroring_images();
+
   // TODO: make interval configurable
-  m_pool_watcher.reset(new PoolWatcher(m_remote, 30, m_lock, m_cond));
+  m_pool_watcher.reset(new PoolWatcher(m_remote_io_ctx, 30, m_lock, m_cond));
   m_pool_watcher->refresh_images();
 
   m_replayer_thread.create("replayer");
@@ -329,11 +355,68 @@ int Replayer::init()
   return 0;
 }
 
+void Replayer::init_local_mirroring_images() {
+  rbd_mirror_mode_t mirror_mode;
+  int r = librbd::mirror_mode_get(m_local_io_ctx, &mirror_mode);
+  if (r < 0) {
+    derr << "could not tell whether mirroring was enabled for "
+         << m_local_io_ctx.get_pool_name() << ": " << cpp_strerror(r) << dendl;
+    return;
+  }
+  if (mirror_mode == RBD_MIRROR_MODE_DISABLED) {
+    dout(20) << "pool " << m_local_io_ctx.get_pool_name() << " "
+             << "has mirroring disabled" << dendl;
+    return;
+  }
+
+  std::set<InitImageInfo> images;
+
+  std::string last_read = "";
+  int max_read = 1024;
+  do {
+    std::map<std::string, std::string> mirror_images;
+    r = librbd::cls_client::mirror_image_list(&m_local_io_ctx, last_read,
+                                              max_read, &mirror_images);
+    if (r < 0) {
+      derr << "error listing mirrored image directory: "
+           << cpp_strerror(r) << dendl;
+      continue;
+    }
+    for (auto it = mirror_images.begin(); it != mirror_images.end(); ++it) {
+      std::string image_name;
+      r = dir_get_name(&m_local_io_ctx, RBD_DIRECTORY, it->first, &image_name);
+      if (r < 0) {
+        derr << "error retrieving local image name: " << cpp_strerror(r)
+             << dendl;
+        continue;
+      }
+      images.insert(InitImageInfo(it->second, it->first, image_name));
+    }
+    if (!mirror_images.empty()) {
+      last_read = mirror_images.rbegin()->first;
+    }
+    r = mirror_images.size();
+  } while (r == max_read);
+
+  m_init_images = std::move(images);
+}
+
 void Replayer::run()
 {
   dout(20) << "enter" << dendl;
 
   while (!m_stopping.read()) {
+
+    std::string asok_hook_name = m_local_io_ctx.get_pool_name() + " " +
+                                 m_peer.cluster_name;
+    if (m_asok_hook_name != asok_hook_name || m_asok_hook == nullptr) {
+      m_asok_hook_name = asok_hook_name;
+      delete m_asok_hook;
+
+      CephContext *cct = static_cast<CephContext *>(m_local->cct());
+      m_asok_hook = new ReplayerAdminSocketHook(cct, m_asok_hook_name, this);
+    }
+
     Mutex::Locker l(m_lock);
     if (!m_manual_stop) {
       set_sources(m_pool_watcher->get_images());
@@ -341,12 +424,13 @@ void Replayer::run()
     m_cond.WaitInterval(g_ceph_context, m_lock, seconds(30));
   }
 
-  // Stopping
-  PoolImageIds empty_sources;
+  m_image_deleter.reset();
+
+  ImageIds empty_sources;
   while (true) {
     Mutex::Locker l(m_lock);
     set_sources(empty_sources);
-    if (m_images.empty()) {
+    if (m_image_replayers.empty()) {
       break;
     }
     m_cond.WaitInterval(g_ceph_context, m_lock, seconds(1));
@@ -361,16 +445,14 @@ void Replayer::print_status(Formatter *f, stringstream *ss)
 
   if (f) {
     f->open_object_section("replayer_status");
+    f->dump_string("pool", m_local_io_ctx.get_pool_name());
     f->dump_stream("peer") << m_peer;
     f->open_array_section("image_replayers");
   };
 
-  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->print_status(f, ss);
-    }
+  for (auto &kv : m_image_replayers) {
+    auto &image_replayer = kv.second;
+    image_replayer->print_status(f, ss);
   }
 
   if (f) {
@@ -392,12 +474,9 @@ void Replayer::start()
 
   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);
-    }
+  for (auto &kv : m_image_replayers) {
+    auto &image_replayer = kv.second;
+    image_replayer->start(nullptr, nullptr, true);
   }
 }
 
@@ -413,12 +492,9 @@ void Replayer::stop()
 
   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);
-    }
+  for (auto &kv : m_image_replayers) {
+    auto &image_replayer = kv.second;
+    image_replayer->stop(nullptr, true);
   }
 }
 
@@ -434,12 +510,9 @@ void Replayer::restart()
 
   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();
-    }
+  for (auto &kv : m_image_replayers) {
+    auto &image_replayer = kv.second;
+    image_replayer->restart();
   }
 }
 
@@ -453,140 +526,122 @@ void Replayer::flush()
     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++) {
-      auto &image_replayer = i->second;
-      image_replayer->flush();
-    }
+  for (auto &kv : m_image_replayers) {
+    auto &image_replayer = kv.second;
+    image_replayer->flush();
   }
 }
 
-void Replayer::set_sources(const PoolImageIds &pool_image_ids)
+void Replayer::set_sources(const ImageIds &image_ids)
 {
   dout(20) << "enter" << dendl;
 
   assert(m_lock.is_locked());
-  for (auto it = m_images.begin(); it != m_images.end();) {
-    int64_t pool_id = it->first;
-    auto &pool_images = it->second;
-
-    // pool has no mirrored images
-    if (pool_image_ids.find(pool_id) == pool_image_ids.end()) {
-      for (auto images_it = pool_images.begin();
-	   images_it != pool_images.end();) {
-	if (stop_image_replayer(images_it->second)) {
-	  images_it = pool_images.erase(images_it);
-	} else {
-          ++images_it;
-        }
-      }
-      if (pool_images.empty()) {
-	mirror_image_status_shut_down(pool_id);
-	it = m_images.erase(it);
-      } else {
-        ++it;
+
+  if (!m_init_images.empty()) {
+    dout(20) << "scanning initial local image set" << dendl;
+    for (auto &remote_image : image_ids) {
+      auto it = m_init_images.find(InitImageInfo(remote_image.global_id));
+      if (it != m_init_images.end()) {
+        m_init_images.erase(it);
       }
-      continue;
     }
 
-    // shut down replayers for non-mirrored images
-    for (auto images_it = pool_images.begin();
-	 images_it != pool_images.end();) {
-      auto &image_ids = pool_image_ids.at(pool_id);
-      if (image_ids.find(ImageIds(images_it->first)) == image_ids.end()) {
-	if (stop_image_replayer(images_it->second)) {
-	  images_it = pool_images.erase(images_it);
-	} else {
-	  ++images_it;
-	}
-      } else {
-	++images_it;
-      }
+    // the remaining images in m_init_images must be deleted
+    for (auto &image : m_init_images) {
+      dout(20) << "scheduling the deletion of init image: "
+               << image.name << dendl;
+      m_image_deleter->schedule_image_delete(m_local_pool_id, image.id,
+                                             image.name, image.global_id);
     }
-    ++it;
+    m_init_images.clear();
   }
 
-  // (re)start new image replayers
-  for (const auto &kv : pool_image_ids) {
-    int64_t pool_id = kv.first;
-
-    // TODO: clean up once remote peer -> image replayer refactored
-    librados::IoCtx remote_ioctx;
-    int r = m_remote->ioctx_create2(pool_id, remote_ioctx);
-    if (r < 0) {
-      derr << "failed to lookup remote pool " << pool_id << ": "
-           << cpp_strerror(r) << dendl;
-      continue;
+  // shut down replayers for non-mirrored images
+  bool existing_image_replayers = !m_image_replayers.empty();
+  for (auto image_it = m_image_replayers.begin();
+       image_it != m_image_replayers.end();) {
+    if (image_ids.find(ImageId(image_it->first)) == image_ids.end()) {
+      if (image_it->second->is_running()) {
+        dout(20) << "stop image replayer for "
+                 << image_it->second->get_global_image_id() << dendl;
+      }
+      if (stop_image_replayer(image_it->second)) {
+        image_it = m_image_replayers.erase(image_it);
+        continue;
+      }
     }
+    ++image_it;
+  }
 
-    librados::IoCtx local_ioctx;
-    r = m_local->ioctx_create(remote_ioctx.get_pool_name().c_str(), local_ioctx);
-    if (r < 0) {
-      derr << "failed to lookup local pool " << remote_ioctx.get_pool_name()
-           << ": " << cpp_strerror(r) << dendl;
-      continue;
+  if (image_ids.empty()) {
+    if (existing_image_replayers && m_image_replayers.empty()) {
+      mirror_image_status_shut_down();
     }
+    return;
+  }
 
-    std::string local_mirror_uuid;
-    r = librbd::cls_client::mirror_uuid_get(&local_ioctx, &local_mirror_uuid);
-    if (r < 0) {
-      derr << "failed to retrieve local mirror uuid from pool "
-        << local_ioctx.get_pool_name() << ": " << cpp_strerror(r) << dendl;
-      continue;
-    }
+  std::string local_mirror_uuid;
+  int r = librbd::cls_client::mirror_uuid_get(&m_local_io_ctx,
+                                              &local_mirror_uuid);
+  if (r < 0) {
+    derr << "failed to retrieve local mirror uuid from pool "
+         << m_local_io_ctx.get_pool_name() << ": " << cpp_strerror(r) << dendl;
+    return;
+  }
 
-    std::string remote_mirror_uuid;
-    r = librbd::cls_client::mirror_uuid_get(&remote_ioctx, &remote_mirror_uuid);
-    if (r < 0) {
-      derr << "failed to retrieve remote mirror uuid from pool "
-        << remote_ioctx.get_pool_name() << ": " << cpp_strerror(r) << dendl;
-      continue;
-    }
+  std::string remote_mirror_uuid;
+  r = librbd::cls_client::mirror_uuid_get(&m_remote_io_ctx,
+                                          &remote_mirror_uuid);
+  if (r < 0) {
+    derr << "failed to retrieve remote mirror uuid from pool "
+         << m_remote_io_ctx.get_pool_name() << ": " << cpp_strerror(r) << dendl;
+    return;
+  }
 
+  if (m_image_replayers.empty() && !existing_image_replayers) {
     // 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;
-      }
+    r = mirror_image_status_init();
+    if (r < 0) {
+      return;
     }
+  }
 
-    for (const auto &image_id : kv.second) {
-      auto it = pool_replayers.find(image_id.id);
-      if (it == pool_replayers.end()) {
-	unique_ptr<ImageReplayer<> > image_replayer(new ImageReplayer<>(
-          m_threads, m_local, m_remote, local_mirror_uuid, remote_mirror_uuid,
-          local_ioctx.get_id(), pool_id, image_id.id, image_id.global_id));
-	it = pool_replayers.insert(
-	  std::make_pair(image_id.id, std::move(image_replayer))).first;
-      }
-      start_image_replayer(it->second);
+  for (auto &image_id : image_ids) {
+    auto it = m_image_replayers.find(image_id.id);
+    if (it == m_image_replayers.end()) {
+      unique_ptr<ImageReplayer<> > image_replayer(new ImageReplayer<>(
+        m_threads, m_local, m_remote, local_mirror_uuid, remote_mirror_uuid,
+        m_local_pool_id, m_remote_pool_id, image_id.id, image_id.global_id));
+      it = m_image_replayers.insert(
+        std::make_pair(image_id.id, std::move(image_replayer))).first;
     }
+    if (!it->second->is_running()) {
+      dout(20) << "starting image replayer for "
+               << it->second->get_global_image_id() << dendl;
+    }
+    start_image_replayer(it->second, image_id.name);
   }
 }
 
-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();
+int Replayer::mirror_image_status_init() {
+  assert(!m_status_watcher);
 
-  dout(20) << "pool_id=" << pool_id << ", instance_id=" << instance_id << dendl;
+  uint64_t instance_id = librados::Rados(m_local_io_ctx).get_instance_id();
+  dout(20) << "pool_id=" << m_local_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);
+  int r = m_local_io_ctx.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));
+  unique_ptr<MirrorStatusWatchCtx> watch_ctx(
+    new MirrorStatusWatchCtx(m_local_io_ctx, m_threads->work_queue));
 
   r = watch_ctx->register_watch();
   if (r < 0) {
@@ -595,43 +650,80 @@ int Replayer::mirror_image_status_init(int64_t pool_id,
     return r;
   }
 
-  m_status_watchers.insert(std::make_pair(pool_id, std::move(watch_ctx)));
-
+  m_status_watcher = 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());
+void Replayer::mirror_image_status_shut_down() {
+  assert(m_status_watcher);
 
-  int r = watcher_it->second->unregister_watch();
+  int r = m_status_watcher->unregister_watch();
   if (r < 0) {
-    derr << "error unregistering watcher for " << watcher_it->second->get_oid()
+    derr << "error unregistering watcher for " << m_status_watcher->get_oid()
 	 << " object: " << cpp_strerror(r) << dendl;
   }
-
-  m_status_watchers.erase(watcher_it);
+  m_status_watcher.reset();
 }
 
-void Replayer::start_image_replayer(unique_ptr<ImageReplayer<> > &image_replayer)
+void Replayer::start_image_replayer(unique_ptr<ImageReplayer<> > &image_replayer,
+                                    const boost::optional<std::string>& image_name)
 {
+  dout(20) << "global_image_id=" << image_replayer->get_global_image_id()
+           << dendl;
+
   if (!image_replayer->is_stopped()) {
     return;
   }
 
-  image_replayer->start();
+  if (image_name) {
+    FunctionContext *ctx = new FunctionContext(
+        [&] (int r) {
+          if (r >= 0) {
+            image_replayer->start();
+          } else {
+            start_image_replayer(image_replayer, image_name);
+          }
+       }
+    );
+    m_image_deleter->wait_for_scheduled_deletion(image_name.get(), ctx, false);
+  }
 }
 
 bool Replayer::stop_image_replayer(unique_ptr<ImageReplayer<> > &image_replayer)
 {
+  dout(20) << "global_image_id=" << image_replayer->get_global_image_id()
+           << dendl;
+
   if (image_replayer->is_stopped()) {
+    if (m_image_deleter) {
+      dout(20) << "scheduling delete" << dendl;
+      m_image_deleter->schedule_image_delete(
+        image_replayer->get_local_pool_id(),
+        image_replayer->get_local_image_id(),
+        image_replayer->get_local_image_name(),
+        image_replayer->get_global_image_id());
+    }
     return true;
   }
 
   if (image_replayer->is_running()) {
-    image_replayer->stop();
+    if (m_image_deleter) {
+      dout(20) << "scheduling delete after image replayer stopped" << dendl;
+    }
+    FunctionContext *ctx = new FunctionContext(
+        [&image_replayer, this] (int r) {
+          if (m_image_deleter) {
+            m_image_deleter->schedule_image_delete(
+                          image_replayer->get_local_pool_id(),
+                          image_replayer->get_local_image_id(),
+                          image_replayer->get_local_image_name(),
+                          image_replayer->get_global_image_id());
+          }
+        }
+    );
+    image_replayer->stop(ctx);
   } else {
-    // TODO: check how long it is stopping and alert if it is too long.
+    // TODO: checkhow long it is stopping and alert if it is too long.
   }
 
   return false;
diff --git a/src/tools/rbd_mirror/Replayer.h b/src/tools/rbd_mirror/Replayer.h
index 0dcd5ed..cd8efa8 100644
--- a/src/tools/rbd_mirror/Replayer.h
+++ b/src/tools/rbd_mirror/Replayer.h
@@ -18,6 +18,7 @@
 #include "ClusterWatcher.h"
 #include "ImageReplayer.h"
 #include "PoolWatcher.h"
+#include "ImageDeleter.h"
 #include "types.h"
 
 namespace rbd {
@@ -32,8 +33,9 @@ class MirrorStatusWatchCtx;
  */
 class Replayer {
 public:
-  Replayer(Threads *threads, RadosRef local_cluster, const peer_t &peer,
-	   const std::vector<const char*> &args);
+  Replayer(Threads *threads, std::shared_ptr<ImageDeleter> image_deleter,
+           RadosRef local_cluster, int64_t local_pool_id, const peer_t &peer,
+           const std::vector<const char*> &args);
   ~Replayer();
   Replayer(const Replayer&) = delete;
   Replayer& operator=(const Replayer&) = delete;
@@ -48,18 +50,21 @@ public:
   void flush();
 
 private:
+  typedef PoolWatcher::ImageId ImageId;
   typedef PoolWatcher::ImageIds ImageIds;
-  typedef PoolWatcher::PoolImageIds PoolImageIds;
 
-  void set_sources(const PoolImageIds &pool_image_ids);
+  void init_local_mirroring_images();
+  void set_sources(const ImageIds &image_ids);
 
-  void start_image_replayer(unique_ptr<ImageReplayer<> > &image_replayer);
+  void start_image_replayer(unique_ptr<ImageReplayer<> > &image_replayer,
+                            const boost::optional<std::string>& image_name);
   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);
+  int mirror_image_status_init();
+  void mirror_image_status_shut_down();
 
   Threads *m_threads;
+  std::shared_ptr<ImageDeleter> m_image_deleter;
   Mutex m_lock;
   Cond m_cond;
   atomic_t m_stopping;
@@ -67,15 +72,42 @@ private:
 
   peer_t m_peer;
   std::vector<const char*> m_args;
-  RadosRef m_local, m_remote;
+  RadosRef m_local;
+  RadosRef m_remote;
+
+  librados::IoCtx m_local_io_ctx;
+  librados::IoCtx m_remote_io_ctx;
+
+  int64_t m_local_pool_id = -1;
+  int64_t m_remote_pool_id = -1;
+
   std::unique_ptr<PoolWatcher> m_pool_watcher;
-  // index by pool so it's easy to tell what is affected
-  // 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;
+  std::map<std::string, std::unique_ptr<ImageReplayer<> > > m_image_replayers;
+  std::unique_ptr<MirrorStatusWatchCtx> m_status_watcher;
+
+  std::string m_asok_hook_name;
   ReplayerAdminSocketHook *m_asok_hook;
 
+  struct InitImageInfo {
+    std::string global_id;
+    std::string id;
+    std::string name;
+
+    InitImageInfo(const std::string& global_id, const std::string &id = "",
+                  const std::string &name = "")
+      : global_id(global_id), id(id), name(name) {
+    }
+
+    inline bool operator==(const InitImageInfo &rhs) const {
+      return (global_id == rhs.global_id && id == rhs.id && name == rhs.name);
+    }
+    inline bool operator<(const InitImageInfo &rhs) const {
+      return global_id < rhs.global_id;
+    }
+  };
+
+  std::set<InitImageInfo> m_init_images;
+
   class ReplayerThread : public Thread {
     Replayer *m_replayer;
   public:
diff --git a/src/tools/rbd_mirror/image_replayer/BootstrapRequest.cc b/src/tools/rbd_mirror/image_replayer/BootstrapRequest.cc
index 6f7210e..57f0705 100644
--- a/src/tools/rbd_mirror/image_replayer/BootstrapRequest.cc
+++ b/src/tools/rbd_mirror/image_replayer/BootstrapRequest.cc
@@ -3,6 +3,8 @@
 
 #include "BootstrapRequest.h"
 #include "CloseImageRequest.h"
+#include "CreateImageRequest.h"
+#include "OpenImageRequest.h"
 #include "OpenLocalImageRequest.h"
 #include "common/debug.h"
 #include "common/dout.h"
@@ -30,58 +32,7 @@ namespace image_replayer {
 
 using librbd::util::create_context_callback;
 using librbd::util::create_rados_ack_callback;
-
-namespace {
-
-template <typename I>
-struct C_CreateImage : public Context {
-  librados::IoCtx &local_io_ctx;
-  std::string global_image_id;
-  std::string remote_mirror_uuid;
-  std::string local_image_name;
-  I *remote_image_ctx;
-  Context *on_finish;
-
-  C_CreateImage(librados::IoCtx &local_io_ctx,
-                const std::string &global_image_id,
-                const std::string &remote_mirror_uuid,
-                const std::string &local_image_name, I *remote_image_ctx,
-                Context *on_finish)
-    : local_io_ctx(local_io_ctx), global_image_id(global_image_id),
-      remote_mirror_uuid(remote_mirror_uuid),
-      local_image_name(local_image_name), remote_image_ctx(remote_image_ctx),
-      on_finish(on_finish) {
-  }
-
-  virtual void finish(int r) override {
-    assert(r == 0);
-
-    // TODO: rbd-mirror should offer a feature mask capability
-    RWLock::RLocker snap_locker(remote_image_ctx->snap_lock);
-    int order = remote_image_ctx->order;
-
-    CephContext *cct = reinterpret_cast<CephContext*>(local_io_ctx.cct());
-    uint64_t journal_order = cct->_conf->rbd_journal_order;
-    uint64_t journal_splay_width = cct->_conf->rbd_journal_splay_width;
-    std::string journal_pool = cct->_conf->rbd_journal_pool;
-
-    // NOTE: bid is 64bit but overflow will result due to
-    // RBD_MAX_BLOCK_NAME_SIZE being too small
-    librados::Rados rados(local_io_ctx);
-    uint64_t bid = rados.get_instance_id();
-
-    r = librbd::create_v2(local_io_ctx, local_image_name.c_str(), bid,
-                          remote_image_ctx->size, order,
-                          remote_image_ctx->features,
-                          remote_image_ctx->stripe_unit,
-                          remote_image_ctx->stripe_count,
-                          journal_order, journal_splay_width, journal_pool,
-                          global_image_id, remote_mirror_uuid);
-    on_finish->complete(r);
-  }
-};
-
-} // anonymous namespace
+using librbd::util::unique_lock_name;
 
 template <typename I>
 BootstrapRequest<I>::BootstrapRequest(librados::IoCtx &local_io_ctx,
@@ -98,18 +49,21 @@ BootstrapRequest<I>::BootstrapRequest(librados::IoCtx &local_io_ctx,
                                       MirrorPeerClientMeta *client_meta,
                                       Context *on_finish,
 				      rbd::mirror::ProgressContext *progress_ctx)
-  : m_local_io_ctx(local_io_ctx), m_remote_io_ctx(remote_io_ctx),
+  : BaseRequest("rbd::mirror::image_replayer::BootstrapRequest",
+		reinterpret_cast<CephContext*>(local_io_ctx.cct()), on_finish),
+    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_progress_ctx(progress_ctx) {
+    m_client_meta(client_meta), m_progress_ctx(progress_ctx),
+    m_lock(unique_lock_name("BootstrapRequest::m_lock", this)) {
 }
 
 template <typename I>
 BootstrapRequest<I>::~BootstrapRequest() {
+  assert(m_image_sync_request == nullptr);
   assert(m_remote_image_ctx == nullptr);
 }
 
@@ -119,6 +73,18 @@ void BootstrapRequest<I>::send() {
 }
 
 template <typename I>
+void BootstrapRequest<I>::cancel() {
+  dout(20) << dendl;
+
+  Mutex::Locker locker(m_lock);
+  m_canceled = true;
+
+  if (m_image_sync_request) {
+    m_image_sync_request->cancel();
+  }
+}
+
+template <typename I>
 void BootstrapRequest<I>::get_local_image_id() {
   dout(20) << dendl;
 
@@ -149,7 +115,7 @@ void BootstrapRequest<I>::handle_get_local_image_id(int r) {
   if (r == -ENOENT) {
     dout(10) << ": image not registered locally" << dendl;
   } else if (r < 0) {
-    derr << ": failed to retreive local image id: " << cpp_strerror(r) << dendl;
+    derr << ": failed to retrieve local image id: " << cpp_strerror(r) << dendl;
     finish(r);
     return;
   }
@@ -174,7 +140,7 @@ void BootstrapRequest<I>::handle_get_remote_tag_class(int r) {
   dout(20) << ": r=" << r << dendl;
 
   if (r < 0) {
-    derr << ": failed to retreive remote client: " << cpp_strerror(r) << dendl;
+    derr << ": failed to retrieve remote client: " << cpp_strerror(r) << dendl;
     finish(r);
     return;
   }
@@ -223,7 +189,7 @@ void BootstrapRequest<I>::handle_get_client(int r) {
   if (r == -ENOENT) {
     dout(10) << ": client not registered" << dendl;
   } else if (r < 0) {
-    derr << ": failed to retreive client: " << cpp_strerror(r) << dendl;
+    derr << ": failed to retrieve client: " << cpp_strerror(r) << dendl;
     finish(r);
     return;
   } else if (decode_client_meta()) {
@@ -274,12 +240,13 @@ void BootstrapRequest<I>::open_remote_image() {
 
   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<
     BootstrapRequest<I>, &BootstrapRequest<I>::handle_open_remote_image>(
       this);
-  m_remote_image_ctx->state->open(ctx);
+  OpenImageRequest<I> *request = OpenImageRequest<I>::create(
+    m_remote_io_ctx, &m_remote_image_ctx, m_remote_image_id, false,
+    m_work_queue, ctx);
+  request->send();
 }
 
 template <typename I>
@@ -291,8 +258,8 @@ void BootstrapRequest<I>::handle_open_remote_image(int r) {
 
   if (r < 0) {
     derr << ": failed to open remote image: " << cpp_strerror(r) << dendl;
-    m_ret_val = r;
-    close_remote_image();
+    assert(m_remote_image_ctx == nullptr);
+    finish(r);
     return;
   }
 
@@ -390,17 +357,16 @@ template <typename I>
 void BootstrapRequest<I>::create_local_image() {
   dout(20) << dendl;
 
+  m_local_image_id = "";
   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<
     BootstrapRequest<I>, &BootstrapRequest<I>::handle_create_local_image>(
       this);
-  m_work_queue->queue(new C_CreateImage<I>(m_local_io_ctx, m_global_image_id,
-                                           m_remote_mirror_uuid,
-                                           m_local_image_name,
-                                           m_remote_image_ctx, ctx), 0);
+  CreateImageRequest<I> *request = CreateImageRequest<I>::create(
+    m_local_io_ctx, m_work_queue, m_global_image_id, m_remote_mirror_uuid,
+    m_local_image_name, m_remote_image_ctx, ctx);
+  request->send();
 }
 
 template <typename I>
@@ -457,6 +423,13 @@ void BootstrapRequest<I>::handle_update_client(int r) {
     return;
   }
 
+  if (m_canceled) {
+    dout(10) << ": request canceled" << dendl;
+    m_ret_val = -ECANCELED;
+    close_local_image();
+    return;
+  }
+
   m_client_meta->image_id = m_local_image_id;
   get_remote_tags();
 }
@@ -486,12 +459,19 @@ void BootstrapRequest<I>::handle_get_remote_tags(int r) {
   dout(20) << ": r=" << r << dendl;
 
   if (r < 0) {
-    derr << ": failed to retreive remote tags: " << cpp_strerror(r) << dendl;
+    derr << ": failed to retrieve remote tags: " << cpp_strerror(r) << dendl;
     m_ret_val = r;
     close_local_image();
     return;
   }
 
+  if (m_canceled) {
+    dout(10) << ": request canceled" << dendl;
+    m_ret_val = -ECANCELED;
+    close_local_image();
+    return;
+  }
+
   // decode the remote tags
   librbd::journal::TagData remote_tag_data;
   for (auto &tag : m_remote_tags) {
@@ -530,11 +510,16 @@ 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<>::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))) {
+    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) {
+      dout(20) << ": local image was demoted" << dendl;
+    } else if (tag_data.mirror_uuid == m_remote_mirror_uuid &&
+	       m_client_meta->state == librbd::journal::MIRROR_PEER_STATE_REPLAYING) {
+      dout(20) << ": local image is in clean replay state" << dendl;
+    } else if (m_client_meta->state == librbd::journal::MIRROR_PEER_STATE_SYNCING) {
+      dout(20) << ": previous sync was canceled" << dendl;
+    } else {
       derr << ": split-brain detected -- skipping image replay" << dendl;
       m_ret_val = -EEXIST;
       close_local_image();
@@ -566,15 +551,32 @@ 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, m_work_queue, ctx,
 					       m_progress_ctx);
-  request->start();
+  {
+    Mutex::Locker locker(m_lock);
+    request->get();
+    m_image_sync_request = request;
+  }
+
+  request->send();
 }
 
 template <typename I>
 void BootstrapRequest<I>::handle_image_sync(int r) {
   dout(20) << ": r=" << r << dendl;
 
+  {
+    Mutex::Locker locker(m_lock);
+    m_image_sync_request->put();
+    m_image_sync_request = nullptr;
+  }
+
+  if (m_canceled) {
+    dout(10) << ": request canceled" << dendl;
+    m_ret_val = -ECANCELED;
+  }
+
   if (r < 0) {
     derr << ": failed to sync remote image: " << cpp_strerror(r) << dendl;
     m_ret_val = r;
@@ -636,14 +638,6 @@ void BootstrapRequest<I>::handle_close_remote_image(int r) {
 }
 
 template <typename I>
-void BootstrapRequest<I>::finish(int r) {
-  dout(20) << ": r=" << r << dendl;
-
-  m_on_finish->complete(r);
-  delete this;
-}
-
-template <typename I>
 bool BootstrapRequest<I>::decode_client_meta() {
   dout(20) << dendl;
 
@@ -668,7 +662,8 @@ bool BootstrapRequest<I>::decode_client_meta() {
 
   *m_client_meta = *client_meta;
 
-  dout(20) << ": client found: image_id=" << m_local_image_id << dendl;
+  dout(20) << ": client found: image_id=" << m_local_image_id
+	   << ", client_meta=" << *m_client_meta << dendl;
   return true;
 }
 
diff --git a/src/tools/rbd_mirror/image_replayer/BootstrapRequest.h b/src/tools/rbd_mirror/image_replayer/BootstrapRequest.h
index 0f6a79e..35ca883 100644
--- a/src/tools/rbd_mirror/image_replayer/BootstrapRequest.h
+++ b/src/tools/rbd_mirror/image_replayer/BootstrapRequest.h
@@ -6,8 +6,10 @@
 
 #include "include/int_types.h"
 #include "include/rados/librados.hpp"
+#include "common/Mutex.h"
 #include "cls/journal/cls_journal_types.h"
 #include "librbd/journal/TypeTraits.h"
+#include "tools/rbd_mirror/BaseRequest.h"
 #include <list>
 #include <string>
 
@@ -22,12 +24,13 @@ namespace librbd { namespace journal { struct MirrorPeerClientMeta; } }
 namespace rbd {
 namespace mirror {
 
+template <typename> class ImageSync;
 class ProgressContext;
 
 namespace image_replayer {
 
 template <typename ImageCtxT = librbd::ImageCtx>
-class BootstrapRequest {
+class BootstrapRequest : public BaseRequest {
 public:
   typedef librbd::journal::TypeTraits<ImageCtxT> TypeTraits;
   typedef typename TypeTraits::Journaler Journaler;
@@ -70,6 +73,7 @@ public:
   ~BootstrapRequest();
 
   void send();
+  void cancel();
 
 private:
   /**
@@ -142,8 +146,10 @@ private:
   std::string m_remote_mirror_uuid;
   Journaler *m_journaler;
   MirrorPeerClientMeta *m_client_meta;
-  Context *m_on_finish;
   ProgressContext *m_progress_ctx;
+  Mutex m_lock;
+  ImageSync<ImageCtxT> *m_image_sync_request = nullptr;
+  bool m_canceled = false;
 
   Tags m_remote_tags;
   cls::journal::Client m_client;
@@ -193,8 +199,6 @@ private:
   void close_remote_image();
   void handle_close_remote_image(int r);
 
-  void finish(int r);
-
   bool decode_client_meta();
 
   void update_progress(const std::string &description);
diff --git a/src/tools/rbd_mirror/image_replayer/CreateImageRequest.cc b/src/tools/rbd_mirror/image_replayer/CreateImageRequest.cc
new file mode 100644
index 0000000..5611ca2
--- /dev/null
+++ b/src/tools/rbd_mirror/image_replayer/CreateImageRequest.cc
@@ -0,0 +1,437 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "CreateImageRequest.h"
+#include "CloseImageRequest.h"
+#include "OpenImageRequest.h"
+#include "Utils.h"
+#include "common/errno.h"
+#include "common/WorkQueue.h"
+#include "cls/rbd/cls_rbd_client.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/ImageState.h"
+#include "librbd/internal.h"
+#include "librbd/Utils.h"
+
+#define dout_subsys ceph_subsys_rbd_mirror
+#undef dout_prefix
+#define dout_prefix *_dout << "rbd::mirror::image_replayer::CreateImageRequest: " \
+                           << this << " " << __func__
+
+using librbd::util::create_context_callback;
+using librbd::util::create_rados_ack_callback;
+
+namespace rbd {
+namespace mirror {
+namespace image_replayer {
+
+template <typename I>
+CreateImageRequest<I>::CreateImageRequest(librados::IoCtx &local_io_ctx,
+                                          ContextWQ *work_queue,
+                                          const std::string &global_image_id,
+                                          const std::string &remote_mirror_uuid,
+                                          const std::string &local_image_name,
+                                          I *remote_image_ctx,
+                                          Context *on_finish)
+  : m_local_io_ctx(local_io_ctx), m_work_queue(work_queue),
+    m_global_image_id(global_image_id),
+    m_remote_mirror_uuid(remote_mirror_uuid),
+    m_local_image_name(local_image_name), m_remote_image_ctx(remote_image_ctx),
+    m_on_finish(on_finish) {
+}
+
+template <typename I>
+void CreateImageRequest<I>::send() {
+  int r = validate_parent();
+  if (r < 0) {
+    error(r);
+    return;
+  }
+
+  if (m_remote_parent_spec.pool_id == -1) {
+    create_image();
+  } else {
+    get_parent_global_image_id();
+  }
+}
+
+template <typename I>
+void CreateImageRequest<I>::create_image() {
+  dout(20) << dendl;
+
+  // TODO: librbd should provide an AIO image creation method -- this is
+  //       blocking so we execute in our worker thread
+  Context *ctx = new FunctionContext([this](int r) {
+      // TODO: rbd-mirror should offer a feature mask capability
+      RWLock::RLocker snap_locker(m_remote_image_ctx->snap_lock);
+      int order = m_remote_image_ctx->order;
+
+      CephContext *cct = reinterpret_cast<CephContext*>(m_local_io_ctx.cct());
+      uint64_t journal_order = cct->_conf->rbd_journal_order;
+      uint64_t journal_splay_width = cct->_conf->rbd_journal_splay_width;
+      std::string journal_pool = cct->_conf->rbd_journal_pool;
+
+      // NOTE: bid is 64bit but overflow will result due to
+      // RBD_MAX_BLOCK_NAME_SIZE being too small
+      librados::Rados rados(m_local_io_ctx);
+      uint64_t bid = rados.get_instance_id();
+
+      r = utils::create_image(m_local_io_ctx, m_remote_image_ctx,
+                              m_local_image_name.c_str(), bid,
+                              m_remote_image_ctx->size, order,
+                              m_remote_image_ctx->features,
+                              m_remote_image_ctx->stripe_unit,
+                              m_remote_image_ctx->stripe_count,
+                              journal_order, journal_splay_width,
+                              journal_pool, m_global_image_id,
+                              m_remote_mirror_uuid);
+      handle_create_image(r);
+    });
+  m_work_queue->queue(ctx, 0);
+}
+
+template <typename I>
+void CreateImageRequest<I>::handle_create_image(int r) {
+  dout(20) << ": r=" << r << dendl;
+  if (r < 0) {
+    derr << ": failed to create local image: " << cpp_strerror(r) << dendl;
+    finish(r);
+    return;
+  }
+
+  finish(0);
+}
+
+template <typename I>
+void CreateImageRequest<I>::get_parent_global_image_id() {
+  dout(20) << dendl;
+
+  librados::ObjectReadOperation op;
+  librbd::cls_client::mirror_image_get_start(&op, m_remote_parent_spec.image_id);
+
+  librados::AioCompletion *aio_comp = create_rados_ack_callback<
+    CreateImageRequest<I>,
+    &CreateImageRequest<I>::handle_get_parent_global_image_id>(this);
+  m_out_bl.clear();
+  int r = m_remote_parent_io_ctx.aio_operate(RBD_MIRRORING, aio_comp, &op,
+                                             &m_out_bl);
+  assert(r == 0);
+  aio_comp->release();
+}
+
+template <typename I>
+void CreateImageRequest<I>::handle_get_parent_global_image_id(int r) {
+  dout(20) << ": r=" << r << dendl;
+  if (r == 0) {
+    cls::rbd::MirrorImage mirror_image;
+    bufferlist::iterator iter = m_out_bl.begin();
+    r = librbd::cls_client::mirror_image_get_finish(&iter, &mirror_image);
+    if (r == 0) {
+      m_parent_global_image_id = mirror_image.global_image_id;
+      dout(20) << ": parent_global_image_id=" << m_parent_global_image_id
+               << dendl;
+    }
+  }
+
+  if (r == -ENOENT) {
+    dout(10) << ": parent image " << m_remote_parent_spec.image_id << " not mirrored"
+             << dendl;
+    finish(r);
+    return;
+  } else if (r < 0) {
+    derr << ": failed to retrieve global image id for parent image "
+         << m_remote_parent_spec.image_id << ": " << cpp_strerror(r) << dendl;
+    finish(r);
+    return;
+  }
+
+  get_local_parent_image_id();
+}
+
+template <typename I>
+void CreateImageRequest<I>::get_local_parent_image_id() {
+  dout(20) << dendl;
+
+  librados::ObjectReadOperation op;
+  librbd::cls_client::mirror_image_get_image_id_start(
+    &op, m_parent_global_image_id);
+
+  librados::AioCompletion *aio_comp = create_rados_ack_callback<
+    CreateImageRequest<I>,
+    &CreateImageRequest<I>::handle_get_local_parent_image_id>(this);
+  m_out_bl.clear();
+  int r = m_local_parent_io_ctx.aio_operate(RBD_MIRRORING, aio_comp, &op,
+                                            &m_out_bl);
+  assert(r == 0);
+  aio_comp->release();
+}
+
+template <typename I>
+void CreateImageRequest<I>::handle_get_local_parent_image_id(int r) {
+  dout(20) << ": r=" << r << dendl;
+
+  if (r == 0) {
+    bufferlist::iterator iter = m_out_bl.begin();
+    r = librbd::cls_client::mirror_image_get_image_id_finish(
+      &iter, &m_local_parent_spec.image_id);
+  }
+
+  if (r == -ENOENT) {
+    dout(10) << ": parent image " << m_parent_global_image_id << " not "
+             << "registered locally" << dendl;
+    finish(r);
+    return;
+  } else if (r < 0) {
+    derr << ": failed to retrieve local image id for parent image "
+         << m_parent_global_image_id << ": " << cpp_strerror(r) << dendl;
+    finish(r);
+    return;
+  }
+
+  open_remote_parent_image();
+}
+
+template <typename I>
+void CreateImageRequest<I>::open_remote_parent_image() {
+  dout(20) << dendl;
+
+  Context *ctx = create_context_callback<
+    CreateImageRequest<I>,
+    &CreateImageRequest<I>::handle_open_remote_parent_image>(this);
+  OpenImageRequest<I> *request = OpenImageRequest<I>::create(
+    m_remote_parent_io_ctx, &m_remote_parent_image_ctx,
+    m_remote_parent_spec.image_id, true, m_work_queue, ctx);
+  request->send();
+}
+
+template <typename I>
+void CreateImageRequest<I>::handle_open_remote_parent_image(int r) {
+  dout(20) << ": r=" << r << dendl;
+  if (r < 0) {
+    derr << ": failed to open remote parent image " << m_parent_pool_name << "/"
+         << m_remote_parent_spec.image_id << dendl;
+    finish(r);
+    return;
+  }
+
+  open_local_parent_image();
+}
+
+template <typename I>
+void CreateImageRequest<I>::open_local_parent_image() {
+  dout(20) << dendl;
+
+  Context *ctx = create_context_callback<
+    CreateImageRequest<I>,
+    &CreateImageRequest<I>::handle_open_local_parent_image>(this);
+  OpenImageRequest<I> *request = OpenImageRequest<I>::create(
+    m_local_parent_io_ctx, &m_local_parent_image_ctx, m_local_parent_spec.image_id,
+    true, m_work_queue, ctx);
+  request->send();
+}
+
+template <typename I>
+void CreateImageRequest<I>::handle_open_local_parent_image(int r) {
+  dout(20) << ": r=" << r << dendl;
+  if (r < 0) {
+    derr << ": failed to open local parent image " << m_parent_pool_name << "/"
+         << m_local_parent_spec.image_id << dendl;
+    m_ret_val = r;
+    close_remote_parent_image();
+    return;
+  }
+
+  set_local_parent_snap();
+}
+
+template <typename I>
+void CreateImageRequest<I>::set_local_parent_snap() {
+  dout(20) << dendl;
+
+  {
+    RWLock::RLocker remote_snap_locker(m_remote_parent_image_ctx->snap_lock);
+    auto it = m_remote_parent_image_ctx->snap_info.find(
+      m_remote_parent_spec.snap_id);
+    if (it != m_remote_parent_image_ctx->snap_info.end()) {
+      m_parent_snap_name = it->second.name;
+    }
+  }
+
+  if (m_parent_snap_name.empty()) {
+    m_ret_val = -ENOENT;
+    close_local_parent_image();
+    return;
+  }
+  dout(20) << ": parent_snap_name=" << m_parent_snap_name << dendl;
+
+  Context *ctx = create_context_callback<
+    CreateImageRequest<I>,
+    &CreateImageRequest<I>::handle_set_local_parent_snap>(this);
+  m_local_parent_image_ctx->state->snap_set(m_parent_snap_name, ctx);
+}
+
+template <typename I>
+void CreateImageRequest<I>::handle_set_local_parent_snap(int r) {
+  dout(20) << ": r=" << r << dendl;
+  if (r < 0) {
+    derr << ": failed to set parent snapshot " << m_parent_snap_name
+         << ": " << cpp_strerror(r) << dendl;
+    m_ret_val = r;
+    close_local_parent_image();
+    return;
+  }
+
+  clone_image();
+}
+
+template <typename I>
+void CreateImageRequest<I>::clone_image() {
+  dout(20) << dendl;
+
+  // TODO: librbd should provide an AIO image clone method -- this is
+  //       blocking so we execute in our worker thread
+  Context *ctx = new FunctionContext([this](int r) {
+      RWLock::RLocker snap_locker(m_remote_image_ctx->snap_lock);
+
+      librbd::ImageOptions opts;
+      opts.set(RBD_IMAGE_OPTION_FEATURES, m_remote_image_ctx->features);
+      opts.set(RBD_IMAGE_OPTION_ORDER, m_remote_image_ctx->order);
+      opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, m_remote_image_ctx->stripe_unit);
+      opts.set(RBD_IMAGE_OPTION_STRIPE_COUNT, m_remote_image_ctx->stripe_count);
+
+      r = utils::clone_image(m_local_parent_image_ctx, m_local_io_ctx,
+                             m_local_image_name.c_str(), opts,
+                             m_global_image_id, m_remote_mirror_uuid);
+      handle_clone_image(r);
+    });
+  m_work_queue->queue(ctx, 0);
+}
+
+template <typename I>
+void CreateImageRequest<I>::handle_clone_image(int r) {
+  dout(20) << ": r=" << r << dendl;
+  if (r < 0) {
+    derr << ": failed to clone image " << m_parent_pool_name << "/"
+         << m_local_parent_image_ctx->name << " to "
+         << m_local_image_name << dendl;
+    m_ret_val = r;
+  }
+
+  close_local_parent_image();
+}
+
+template <typename I>
+void CreateImageRequest<I>::close_local_parent_image() {
+  dout(20) << dendl;
+  Context *ctx = create_context_callback<
+    CreateImageRequest<I>,
+    &CreateImageRequest<I>::handle_close_local_parent_image>(this);
+  CloseImageRequest<I> *request = CloseImageRequest<I>::create(
+    &m_local_parent_image_ctx, m_work_queue, false, ctx);
+  request->send();
+}
+
+template <typename I>
+void CreateImageRequest<I>::handle_close_local_parent_image(int r) {
+  dout(20) << ": r=" << r << dendl;
+  if (r < 0) {
+    derr << ": error encountered closing local parent image: "
+         << cpp_strerror(r) << dendl;
+  }
+
+  close_remote_parent_image();
+}
+
+template <typename I>
+void CreateImageRequest<I>::close_remote_parent_image() {
+  dout(20) << dendl;
+  Context *ctx = create_context_callback<
+    CreateImageRequest<I>,
+    &CreateImageRequest<I>::handle_close_remote_parent_image>(this);
+  CloseImageRequest<I> *request = CloseImageRequest<I>::create(
+    &m_remote_parent_image_ctx, m_work_queue, false, ctx);
+  request->send();
+}
+
+template <typename I>
+void CreateImageRequest<I>::handle_close_remote_parent_image(int r) {
+  dout(20) << ": r=" << r << dendl;
+  if (r < 0) {
+    derr << ": error encountered closing remote parent image: "
+         << cpp_strerror(r) << dendl;
+  }
+
+  finish(m_ret_val);
+}
+
+template <typename I>
+void CreateImageRequest<I>::error(int r) {
+  dout(20) << ": r=" << r << dendl;
+
+  m_work_queue->queue(create_context_callback<
+    CreateImageRequest<I>, &CreateImageRequest<I>::finish>(this), r);
+}
+
+template <typename I>
+void CreateImageRequest<I>::finish(int r) {
+  dout(20) << ": r=" << r << dendl;
+  m_on_finish->complete(r);
+  delete this;
+}
+
+template <typename I>
+int CreateImageRequest<I>::validate_parent() {
+  RWLock::RLocker owner_locker(m_remote_image_ctx->owner_lock);
+  RWLock::RLocker snap_locker(m_remote_image_ctx->snap_lock);
+
+  m_remote_parent_spec = m_remote_image_ctx->parent_md.spec;
+
+  // scan all remote snapshots for a linked parent
+  for (auto &snap_info_pair : m_remote_image_ctx->snap_info) {
+    auto &parent_spec = snap_info_pair.second.parent.spec;
+    if (parent_spec.pool_id == -1) {
+      continue;
+    } else if (m_remote_parent_spec.pool_id == -1) {
+      m_remote_parent_spec = parent_spec;
+      continue;
+    }
+
+    if (m_remote_parent_spec != parent_spec) {
+      derr << ": remote image parent spec mismatch" << dendl;
+      return -EINVAL;
+    }
+  }
+
+  if (m_remote_parent_spec.pool_id == -1) {
+    return 0;
+  }
+
+  // map remote parent pool to local parent pool
+  librados::Rados remote_rados(m_remote_image_ctx->md_ctx);
+  int r = remote_rados.ioctx_create2(m_remote_parent_spec.pool_id,
+                                     m_remote_parent_io_ctx);
+  if (r < 0) {
+    derr << ": failed to open remote parent pool " << m_remote_parent_spec.pool_id
+         << ": " << cpp_strerror(r) << dendl;
+    return r;
+  }
+
+  m_parent_pool_name = m_remote_parent_io_ctx.get_pool_name();
+
+  librados::Rados local_rados(m_local_io_ctx);
+  r = local_rados.ioctx_create(m_parent_pool_name.c_str(),
+                               m_local_parent_io_ctx);
+  if (r < 0) {
+    derr << ": failed to open local parent pool " << m_parent_pool_name << ": "
+         << cpp_strerror(r) << dendl;
+    return r;
+  }
+
+  return 0;
+}
+
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
+
+template class rbd::mirror::image_replayer::CreateImageRequest<librbd::ImageCtx>;
diff --git a/src/tools/rbd_mirror/image_replayer/CreateImageRequest.h b/src/tools/rbd_mirror/image_replayer/CreateImageRequest.h
new file mode 100644
index 0000000..0e5d483
--- /dev/null
+++ b/src/tools/rbd_mirror/image_replayer/CreateImageRequest.h
@@ -0,0 +1,144 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef RBD_MIRROR_IMAGE_REPLAYER_CREATE_IMAGE_REQUEST_H
+#define RBD_MIRROR_IMAGE_REPLAYER_CREATE_IMAGE_REQUEST_H
+
+#include "include/int_types.h"
+#include "include/types.h"
+#include "include/rados/librados.hpp"
+#include "librbd/parent_types.h"
+#include <string>
+
+class Context;
+class ContextWQ;
+namespace librbd { class ImageCtx; }
+
+namespace rbd {
+namespace mirror {
+namespace image_replayer {
+
+template <typename ImageCtxT = librbd::ImageCtx>
+class CreateImageRequest {
+public:
+  static CreateImageRequest *create(librados::IoCtx &local_io_ctx,
+                                    ContextWQ *work_queue,
+                                    const std::string &global_image_id,
+                                    const std::string &remote_mirror_uuid,
+                                    const std::string &local_image_name,
+                                    ImageCtxT *remote_image_ctx,
+                                    Context *on_finish) {
+    return new CreateImageRequest(local_io_ctx, work_queue, global_image_id,
+                                  remote_mirror_uuid, local_image_name,
+                                  remote_image_ctx, on_finish);
+  }
+
+  CreateImageRequest(librados::IoCtx &local_io_ctx, ContextWQ *work_queue,
+                     const std::string &global_image_id,
+                     const std::string &remote_mirror_uuid,
+                     const std::string &local_image_name,
+                     ImageCtxT *remote_image_ctx,
+                     Context *on_finish);
+
+  void send();
+
+private:
+  /**
+   * @verbatim
+   *
+   * <start>  * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+   *    |                                                           *
+   *    | (non-clone)                                               *
+   *    |\------------> CREATE_IMAGE ---------------------\         * (error)
+   *    |                                                 |         *
+   *    | (clone)                                         |         *
+   *    \-------------> GET_PARENT_GLOBAL_IMAGE_ID  * * * | * * *   *
+   *                        |                             |       * *
+   *                        v                             |         *
+   *                    GET_LOCAL_PARENT_IMAGE_ID * * * * | * * *   *
+   *                        |                             |       * *
+   *                        v                             |         *
+   *                    OPEN_REMOTE_PARENT  * * * * * * * | * * *   *
+   *                        |                             |       * *
+   *                        v                             |         *
+   *                    OPEN_LOCAL_PARENT * * * * * * *   |         *
+   *                        |                         *   |         *
+   *                        v                         *   |         *
+   *                    SET_LOCAL_PARENT_SNAP         *   |         *
+   *                        |         *               *   |         *
+   *                        v         *               *   |         *
+   *                    CLONE_IMAGE   *               *   |         *
+   *                        |         *               *   |         *
+   *                        v         v               *   |         *
+   *                    CLOSE_LOCAL_PARENT            *   |         *
+   *                        |                         *   |         *
+   *                        v                         *   |         *
+   *                    CLOSE_REMOTE_PARENT < * * * * *   |         *
+   *                        |                             v         *
+   *                        \------------------------> <finish> < * *
+   * @endverbatim
+   */
+
+  librados::IoCtx &m_local_io_ctx;
+  ContextWQ *m_work_queue;
+  std::string m_global_image_id;
+  std::string m_remote_mirror_uuid;
+  std::string m_local_image_name;
+  ImageCtxT *m_remote_image_ctx;
+  Context *m_on_finish;
+
+  librados::IoCtx m_remote_parent_io_ctx;
+  ImageCtxT *m_remote_parent_image_ctx = nullptr;
+  librbd::parent_spec m_remote_parent_spec;
+
+  librados::IoCtx m_local_parent_io_ctx;
+  ImageCtxT *m_local_parent_image_ctx = nullptr;
+  librbd::parent_spec m_local_parent_spec;
+
+  bufferlist m_out_bl;
+  std::string m_parent_global_image_id;
+  std::string m_parent_pool_name;
+  std::string m_parent_snap_name;
+  int m_ret_val = 0;
+
+  void create_image();
+  void handle_create_image(int r);
+
+  void get_parent_global_image_id();
+  void handle_get_parent_global_image_id(int r);
+
+  void get_local_parent_image_id();
+  void handle_get_local_parent_image_id(int r);
+
+  void open_remote_parent_image();
+  void handle_open_remote_parent_image(int r);
+
+  void open_local_parent_image();
+  void handle_open_local_parent_image(int r);
+
+  void set_local_parent_snap();
+  void handle_set_local_parent_snap(int r);
+
+  void clone_image();
+  void handle_clone_image(int r);
+
+  void close_local_parent_image();
+  void handle_close_local_parent_image(int r);
+
+  void close_remote_parent_image();
+  void handle_close_remote_parent_image(int r);
+
+  void error(int r);
+  void finish(int r);
+
+  int validate_parent();
+
+};
+
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
+
+extern template class rbd::mirror::image_replayer::CreateImageRequest<librbd::ImageCtx>;
+
+#endif // RBD_MIRROR_IMAGE_REPLAYER_CREATE_IMAGE_REQUEST_H
diff --git a/src/tools/rbd_mirror/image_replayer/OpenImageRequest.cc b/src/tools/rbd_mirror/image_replayer/OpenImageRequest.cc
new file mode 100644
index 0000000..9cf6af2
--- /dev/null
+++ b/src/tools/rbd_mirror/image_replayer/OpenImageRequest.cc
@@ -0,0 +1,100 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "OpenImageRequest.h"
+#include "CloseImageRequest.h"
+#include "common/errno.h"
+#include "common/WorkQueue.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/ImageState.h"
+#include "librbd/Utils.h"
+#include <type_traits>
+
+#define dout_subsys ceph_subsys_rbd_mirror
+#undef dout_prefix
+#define dout_prefix *_dout << "rbd::mirror::image_replayer::OpenImageRequest: " \
+                           << this << " " << __func__ << " "
+
+namespace rbd {
+namespace mirror {
+namespace image_replayer {
+
+using librbd::util::create_context_callback;
+
+template <typename I>
+OpenImageRequest<I>::OpenImageRequest(librados::IoCtx &io_ctx, I **image_ctx,
+                                      const std::string &image_id,
+                                      bool read_only, ContextWQ *work_queue,
+                                      Context *on_finish)
+  : m_io_ctx(io_ctx), m_image_ctx(image_ctx), m_image_id(image_id),
+    m_read_only(read_only), m_work_queue(work_queue), m_on_finish(on_finish) {
+}
+
+template <typename I>
+void OpenImageRequest<I>::send() {
+  send_open_image();
+}
+
+template <typename I>
+void OpenImageRequest<I>::send_open_image() {
+  dout(20) << dendl;
+
+  *m_image_ctx = I::create("", m_image_id, nullptr, m_io_ctx, m_read_only);
+
+  Context *ctx = create_context_callback<
+    OpenImageRequest<I>, &OpenImageRequest<I>::handle_open_image>(
+      this);
+  (*m_image_ctx)->state->open(ctx);
+}
+
+template <typename I>
+void OpenImageRequest<I>::handle_open_image(int r) {
+  dout(20) << ": r=" << r << dendl;
+
+  if (r < 0) {
+    derr << ": failed to open image '" << m_image_id << "': "
+         << cpp_strerror(r) << dendl;
+    send_close_image(r);
+    return;
+  }
+
+  finish(0);
+}
+
+template <typename I>
+void OpenImageRequest<I>::send_close_image(int r) {
+  dout(20) << dendl;
+
+  if (m_ret_val == 0 && r < 0) {
+    m_ret_val = r;
+  }
+
+  Context *ctx = create_context_callback<
+    OpenImageRequest<I>, &OpenImageRequest<I>::handle_close_image>(
+      this);
+  CloseImageRequest<I> *request = CloseImageRequest<I>::create(
+    m_image_ctx, m_work_queue, true, ctx);
+  request->send();
+}
+
+template <typename I>
+void OpenImageRequest<I>::handle_close_image(int r) {
+  dout(20) << dendl;
+
+  assert(r == 0);
+  finish(m_ret_val);
+}
+
+template <typename I>
+void OpenImageRequest<I>::finish(int r) {
+  dout(20) << ": r=" << r << dendl;
+
+  m_on_finish->complete(r);
+  delete this;
+}
+
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
+
+template class rbd::mirror::image_replayer::OpenImageRequest<librbd::ImageCtx>;
diff --git a/src/tools/rbd_mirror/image_replayer/OpenImageRequest.h b/src/tools/rbd_mirror/image_replayer/OpenImageRequest.h
new file mode 100644
index 0000000..d9407c0
--- /dev/null
+++ b/src/tools/rbd_mirror/image_replayer/OpenImageRequest.h
@@ -0,0 +1,76 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef RBD_MIRROR_IMAGE_REPLAYER_OPEN_IMAGE_REQUEST_H
+#define RBD_MIRROR_IMAGE_REPLAYER_OPEN_IMAGE_REQUEST_H
+
+#include "include/int_types.h"
+#include "librbd/ImageCtx.h"
+#include <string>
+
+class Context;
+class ContextWQ;
+namespace librbd { class ImageCtx; }
+
+namespace rbd {
+namespace mirror {
+namespace image_replayer {
+
+template <typename ImageCtxT = librbd::ImageCtx>
+class OpenImageRequest {
+public:
+  static OpenImageRequest* create(librados::IoCtx &io_ctx,
+                                  ImageCtxT **image_ctx,
+                                  const std::string &image_id,
+                                  bool read_only, ContextWQ *work_queue,
+                                  Context *on_finish) {
+    return new OpenImageRequest(io_ctx, image_ctx, image_id, read_only,
+                                work_queue, on_finish);
+  }
+
+  OpenImageRequest(librados::IoCtx &io_ctx, ImageCtxT **image_ctx,
+                   const std::string &image_id, bool read_only,
+                   ContextWQ *m_work_queue, Context *on_finish);
+
+  void send();
+
+private:
+  /**
+   * @verbatim
+   *
+   * <start>
+   *    |
+   *    v
+   * OPEN_IMAGE * * * * * * * *
+   *    |                     *
+   *    v                     v
+   * <finish> <---------- CLOSE_IMAGE
+   *
+   * @endverbatim
+   */
+  librados::IoCtx &m_io_ctx;
+  ImageCtxT **m_image_ctx;
+  std::string m_image_id;
+  bool m_read_only;
+  ContextWQ *m_work_queue;
+  Context *m_on_finish;
+
+  int m_ret_val = 0;
+
+  void send_open_image();
+  void handle_open_image(int r);
+
+  void send_close_image(int r);
+  void handle_close_image(int r);
+
+  void finish(int r);
+
+};
+
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
+
+extern template class rbd::mirror::image_replayer::OpenImageRequest<librbd::ImageCtx>;
+
+#endif // RBD_MIRROR_IMAGE_REPLAYER_OPEN_IMAGE_REQUEST_H
diff --git a/src/tools/rbd_mirror/image_replayer/ReplayStatusFormatter.cc b/src/tools/rbd_mirror/image_replayer/ReplayStatusFormatter.cc
index 303fb58..286a87b 100644
--- a/src/tools/rbd_mirror/image_replayer/ReplayStatusFormatter.cc
+++ b/src/tools/rbd_mirror/image_replayer/ReplayStatusFormatter.cc
@@ -103,7 +103,8 @@ bool ReplayStatusFormatter<I>::calculate_behind_master_or_send_update() {
 
   m_entries_behind_master = 0;
 
-  if (m_master_position == cls::journal::ObjectPosition()) {
+  if (m_master_position == cls::journal::ObjectPosition() ||
+      m_master_position.tag_tid < m_mirror_position.tag_tid) {
     return true;
   }
 
diff --git a/src/tools/rbd_mirror/image_replayer/Utils.h b/src/tools/rbd_mirror/image_replayer/Utils.h
new file mode 100644
index 0000000..2fea40e
--- /dev/null
+++ b/src/tools/rbd_mirror/image_replayer/Utils.h
@@ -0,0 +1,49 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef RBD_MIRROR_IMAGE_REPLAYER_UTILS_H
+#define RBD_MIRROR_IMAGE_REPLAYER_UTILS_H
+
+#include "include/int_types.h"
+#include "include/rados/librados.hpp"
+#include "include/rbd/librbd.hpp"
+#include "librbd/internal.h"
+#include <string>
+
+namespace rbd {
+namespace mirror {
+namespace image_replayer {
+namespace utils {
+
+// TODO: free-functions used for mocking until create/clone
+//       converted to async state machines
+template <typename I>
+int create_image(librados::IoCtx& io_ctx, I *_image_ctx, const char *imgname,
+                 uint64_t bid, uint64_t size, int order, uint64_t features,
+                 uint64_t stripe_unit, uint64_t stripe_count,
+                 uint8_t journal_order, uint8_t journal_splay_width,
+                 const std::string &journal_pool,
+                 const std::string &non_primary_global_image_id,
+                 const std::string &primary_mirror_uuid) {
+  return librbd::create_v2(io_ctx, imgname, bid, size, order, features,
+                           stripe_unit, stripe_count, journal_order,
+                           journal_splay_width, journal_pool,
+                           non_primary_global_image_id, primary_mirror_uuid);
+}
+
+template <typename I>
+int clone_image(I *p_imctx, librados::IoCtx& c_ioctx, const char *c_name,
+                librbd::ImageOptions& c_opts,
+                const std::string &non_primary_global_image_id,
+                const std::string &primary_mirror_uuid) {
+  return librbd::clone(p_imctx, c_ioctx, c_name, c_opts,
+                       non_primary_global_image_id, primary_mirror_uuid);
+}
+
+} // namespace utils
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
+
+#endif // RBD_MIRROR_IMAGE_REPLAYER_UTILS_H
+
diff --git a/src/tools/rbd_mirror/image_sync/ImageCopyRequest.cc b/src/tools/rbd_mirror/image_sync/ImageCopyRequest.cc
index df41d81..e037f88 100644
--- a/src/tools/rbd_mirror/image_sync/ImageCopyRequest.cc
+++ b/src/tools/rbd_mirror/image_sync/ImageCopyRequest.cc
@@ -29,10 +29,12 @@ ImageCopyRequest<I>::ImageCopyRequest(I *local_image_ctx, I *remote_image_ctx,
                                       MirrorPeerSyncPoint *sync_point,
                                       Context *on_finish,
 				      ProgressContext *progress_ctx)
-  : m_local_image_ctx(local_image_ctx), m_remote_image_ctx(remote_image_ctx),
+  : BaseRequest("rbd::mirror::image_sync::ImageCopyRequest",
+		local_image_ctx->cct, on_finish),
+    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_progress_ctx(progress_ctx),
+    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());
@@ -54,8 +56,7 @@ template <typename I>
 void ImageCopyRequest<I>::cancel() {
   Mutex::Locker locker(m_lock);
 
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << dendl;
+  dout(20) << dendl;
   m_canceled = true;
 }
 
@@ -79,8 +80,7 @@ void ImageCopyRequest<I>::send_update_max_object_count() {
 
   update_progress("UPDATE_MAX_OBJECT_COUNT");
 
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": sync_object_count=" << max_objects << dendl;
+  dout(20) << ": sync_object_count=" << max_objects << dendl;
 
   m_client_meta_copy = *m_client_meta;
   m_client_meta_copy.sync_object_count = max_objects;
@@ -97,12 +97,20 @@ void ImageCopyRequest<I>::send_update_max_object_count() {
 
 template <typename I>
 void ImageCopyRequest<I>::handle_update_max_object_count(int r) {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
+
+  if (r == 0) {
+    Mutex::Locker locker(m_lock);
+    if (m_canceled) {
+      dout(10) << ": image copy canceled" << dendl;
+      r = -ECANCELED;
+    }
+  }
 
   if (r < 0) {
-    lderr(cct) << ": failed to update client data: " << cpp_strerror(r)
-               << dendl;
+    if (r != -ECANCELED) {
+      derr << ": failed to update client data: " << cpp_strerror(r) << dendl;
+    }
     finish(r);
     return;
   }
@@ -147,16 +155,19 @@ void ImageCopyRequest<I>::send_object_copies() {
 template <typename I>
 void ImageCopyRequest<I>::send_next_object_copy() {
   assert(m_lock.is_locked());
-  if (m_canceled) {
-    return;
-  } else if (m_ret_val < 0 || m_object_no >= m_end_object_no) {
+
+  if (m_canceled && m_ret_val == 0) {
+    dout(10) << ": image copy canceled" << dendl;
+    m_ret_val = -ECANCELED;
+  }
+
+  if (m_ret_val < 0 || m_object_no >= m_end_object_no) {
     return;
   }
 
   uint64_t ono = m_object_no++;
 
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": object_num=" << ono << dendl;
+  dout(20) << ": object_num=" << ono << dendl;
 
   ++m_current_ops;
 
@@ -169,8 +180,7 @@ void ImageCopyRequest<I>::send_next_object_copy() {
 
 template <typename I>
 void ImageCopyRequest<I>::handle_object_copy(int r) {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
   int percent;
   bool complete;
@@ -178,10 +188,11 @@ void ImageCopyRequest<I>::handle_object_copy(int r) {
     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;
+      derr << ": object copy failed: " << cpp_strerror(r) << dendl;
       if (m_ret_val == 0) {
         m_ret_val = r;
       }
@@ -214,8 +225,7 @@ void ImageCopyRequest<I>::send_flush_sync_point() {
     m_sync_point->object_number = boost::none;
   }
 
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": sync_point=" << *m_sync_point << dendl;
+  dout(20) << ": sync_point=" << *m_sync_point << dendl;
 
   bufferlist client_data_bl;
   librbd::journal::ClientData client_data(m_client_meta_copy);
@@ -229,14 +239,13 @@ void ImageCopyRequest<I>::send_flush_sync_point() {
 
 template <typename I>
 void ImageCopyRequest<I>::handle_flush_sync_point(int r) {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
   if (r < 0) {
     *m_client_meta = m_client_meta_copy;
 
-    lderr(cct) << ": failed to update client data: " << cpp_strerror(r)
-               << dendl;
+    derr << ": failed to update client data: " << cpp_strerror(r)
+         << dendl;
     finish(r);
     return;
   }
@@ -245,17 +254,7 @@ void ImageCopyRequest<I>::handle_flush_sync_point(int r) {
 }
 
 template <typename I>
-void ImageCopyRequest<I>::finish(int r) {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
-
-  m_on_finish->complete(r);
-  delete this;
-}
-
-template <typename I>
 int ImageCopyRequest<I>::compute_snap_map() {
-  CephContext *cct = m_local_image_ctx->cct;
 
   librados::snap_t snap_id_start = 0;
   librados::snap_t snap_id_end;
@@ -263,8 +262,8 @@ int ImageCopyRequest<I>::compute_snap_map() {
     RWLock::RLocker snap_locker(m_remote_image_ctx->snap_lock);
     snap_id_end = m_remote_image_ctx->get_snap_id(m_sync_point->snap_name);
     if (snap_id_end == CEPH_NOSNAP) {
-      lderr(cct) << ": failed to locate snapshot: "
-                 << m_sync_point->snap_name << dendl;
+      derr << ": failed to locate snapshot: "
+           << m_sync_point->snap_name << dendl;
       return -ENOENT;
     }
 
@@ -272,8 +271,8 @@ int ImageCopyRequest<I>::compute_snap_map() {
       snap_id_start = m_remote_image_ctx->get_snap_id(
         m_sync_point->from_snap_name);
       if (snap_id_start == CEPH_NOSNAP) {
-        lderr(cct) << ": failed to locate from snapshot: "
-                   << m_sync_point->from_snap_name << dendl;
+        derr << ": failed to locate from snapshot: "
+             << m_sync_point->from_snap_name << dendl;
         return -ENOENT;
       }
     }
@@ -293,7 +292,7 @@ int ImageCopyRequest<I>::compute_snap_map() {
   }
 
   if (m_snap_map.empty()) {
-    lderr(cct) << ": failed to map snapshots within boundary" << dendl;
+    derr << ": failed to map snapshots within boundary" << dendl;
     return -EINVAL;
   }
 
diff --git a/src/tools/rbd_mirror/image_sync/ImageCopyRequest.h b/src/tools/rbd_mirror/image_sync/ImageCopyRequest.h
index 7198aae..118b48d 100644
--- a/src/tools/rbd_mirror/image_sync/ImageCopyRequest.h
+++ b/src/tools/rbd_mirror/image_sync/ImageCopyRequest.h
@@ -9,6 +9,7 @@
 #include "common/Mutex.h"
 #include "librbd/journal/Types.h"
 #include "librbd/journal/TypeTraits.h"
+#include "tools/rbd_mirror/BaseRequest.h"
 #include <map>
 #include <vector>
 
@@ -25,7 +26,7 @@ class ProgressContext;
 namespace image_sync {
 
 template <typename ImageCtxT = librbd::ImageCtx>
-class ImageCopyRequest {
+class ImageCopyRequest : public BaseRequest {
 public:
   typedef std::vector<librados::snap_t> SnapIds;
   typedef std::map<librados::snap_t, SnapIds> SnapMap;
@@ -87,7 +88,6 @@ private:
   Journaler *m_journaler;
   MirrorPeerClientMeta *m_client_meta;
   MirrorPeerSyncPoint *m_sync_point;
-  Context *m_on_finish;
   ProgressContext *m_progress_ctx;
 
   SnapMap m_snap_map;
@@ -112,8 +112,6 @@ private:
   void send_flush_sync_point();
   void handle_flush_sync_point(int r);
 
-  void finish(int r);
-
   int compute_snap_map();
 
   void update_progress(const std::string &description, bool flush = true);
diff --git a/src/tools/rbd_mirror/image_sync/ObjectCopyRequest.cc b/src/tools/rbd_mirror/image_sync/ObjectCopyRequest.cc
index 38567ce..89acd03 100644
--- a/src/tools/rbd_mirror/image_sync/ObjectCopyRequest.cc
+++ b/src/tools/rbd_mirror/image_sync/ObjectCopyRequest.cc
@@ -36,10 +36,9 @@ 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;
+  dout(20) << ": "
+           << "remote_oid=" << m_remote_oid << ", "
+           << "local_oid=" << m_local_oid << dendl;
 }
 
 template <typename I>
@@ -49,8 +48,7 @@ void ObjectCopyRequest<I>::send() {
 
 template <typename I>
 void ObjectCopyRequest<I>::send_list_snaps() {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << dendl;
+  dout(20) << dendl;
 
   librados::AioCompletion *rados_completion = create_rados_ack_callback<
     ObjectCopyRequest<I>, &ObjectCopyRequest<I>::handle_list_snaps>(this);
@@ -71,15 +69,14 @@ void ObjectCopyRequest<I>::handle_list_snaps(int r) {
     r = m_snap_ret;
   }
 
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
   if (r == -ENOENT) {
     finish(0);
     return;
   }
   if (r < 0) {
-    lderr(cct) << ": failed to list snaps: " << cpp_strerror(r) << dendl;
+    derr << ": failed to list snaps: " << cpp_strerror(r) << dendl;
     finish(r);
     return;
   }
@@ -90,7 +87,6 @@ void ObjectCopyRequest<I>::handle_list_snaps(int r) {
 
 template <typename I>
 void ObjectCopyRequest<I>::send_read_object() {
-  CephContext *cct = m_local_image_ctx->cct;
   if (m_snap_sync_ops.empty()) {
     // no more snapshot diffs to read from remote
     finish(0);
@@ -111,12 +107,12 @@ void ObjectCopyRequest<I>::send_read_object() {
     switch (std::get<0>(sync_op)) {
     case SYNC_OP_TYPE_WRITE:
       if (!read_required) {
-        ldout(cct, 20) << ": remote_snap_seq=" << remote_snap_seq << dendl;
+        dout(20) << ": remote_snap_seq=" << remote_snap_seq << dendl;
         read_required = true;
       }
 
-      ldout(cct, 20) << ": read op: " << std::get<1>(sync_op) << "~"
-                     << std::get<2>(sync_op) << dendl;
+      dout(20) << ": read op: " << std::get<1>(sync_op) << "~"
+               << std::get<2>(sync_op) << dendl;
       op.read(std::get<1>(sync_op), std::get<2>(sync_op),
               &std::get<3>(sync_op), nullptr);
       break;
@@ -140,12 +136,11 @@ void ObjectCopyRequest<I>::send_read_object() {
 
 template <typename I>
 void ObjectCopyRequest<I>::handle_read_object(int r) {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
   if (r < 0) {
-    lderr(cct) << ": failed to read from remote object: " << cpp_strerror(r)
-               << dendl;
+    derr << ": failed to read from remote object: " << cpp_strerror(r)
+         << dendl;
     finish(r);
     return;
   }
@@ -172,10 +167,9 @@ void ObjectCopyRequest<I>::send_write_object() {
     }
   }
 
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": "
-                 << "local_snap_seq=" << local_snap_seq << ", "
-                 << "local_snaps=" << local_snap_ids << dendl;
+  dout(20) << ": "
+           << "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());
@@ -184,16 +178,16 @@ void ObjectCopyRequest<I>::send_write_object() {
   for (auto &sync_op : sync_ops) {
     switch (std::get<0>(sync_op)) {
     case SYNC_OP_TYPE_WRITE:
-      ldout(cct, 20) << ": write op: " << std::get<1>(sync_op) << "~"
-                     << std::get<3>(sync_op).length() << dendl;
+      dout(20) << ": write op: " << std::get<1>(sync_op) << "~"
+               << std::get<3>(sync_op).length() << dendl;
       op.write(std::get<1>(sync_op), std::get<3>(sync_op));
       break;
     case SYNC_OP_TYPE_TRUNC:
-      ldout(cct, 20) << ": trunc op: " << std::get<1>(sync_op) << dendl;
+      dout(20) << ": trunc op: " << std::get<1>(sync_op) << dendl;
       op.truncate(std::get<1>(sync_op));
       break;
     case SYNC_OP_TYPE_REMOVE:
-      ldout(cct, 20) << ": remove op" << dendl;
+      dout(20) << ": remove op" << dendl;
       op.remove();
       break;
     default:
@@ -211,15 +205,14 @@ void ObjectCopyRequest<I>::send_write_object() {
 
 template <typename I>
 void ObjectCopyRequest<I>::handle_write_object(int r) {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
   if (r == -ENOENT) {
     r = 0;
   }
   if (r < 0) {
-    lderr(cct) << ": failed to write to local object: " << cpp_strerror(r)
-               << dendl;
+    derr << ": failed to write to local object: " << cpp_strerror(r)
+         << dendl;
     finish(r);
     return;
   }
@@ -249,12 +242,10 @@ void ObjectCopyRequest<I>::send_update_object_map() {
   auto snap_object_state = *m_snap_object_states.begin();
   m_snap_object_states.erase(m_snap_object_states.begin());
 
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": "
-                 << "local_snap_id=" << snap_object_state.first << ", "
-                 << "object_state=" << static_cast<uint32_t>(
-                      snap_object_state.second)
-                 << dendl;
+  dout(20) << ": "
+           << "local_snap_id=" << snap_object_state.first << ", "
+           << "object_state=" << static_cast<uint32_t>(snap_object_state.second)
+           << dendl;
 
   RWLock::WLocker object_map_locker(m_local_image_ctx->object_map_lock);
   Context *ctx = create_context_callback<
@@ -270,8 +261,7 @@ void ObjectCopyRequest<I>::send_update_object_map() {
 
 template <typename I>
 void ObjectCopyRequest<I>::handle_update_object_map(int r) {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
   assert(r == 0);
   if (!m_snap_object_states.empty()) {
@@ -299,13 +289,13 @@ void ObjectCopyRequest<I>::compute_diffs() {
     calc_snap_set_diff(cct, m_snap_set, start_remote_snap_id,
                        end_remote_snap_id, &diff, &end_size, &exists);
 
-    ldout(cct, 20) << ": "
-                   << "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;
+    dout(20) << ": "
+             << "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;
 
     if (exists) {
       // clip diff to size of object (in case it was truncated)
@@ -314,7 +304,7 @@ void ObjectCopyRequest<I>::compute_diffs() {
         trunc.insert(end_size, prev_end_size);
         trunc.intersection_of(diff);
         diff.subtract(trunc);
-        ldout(cct, 20) << ": clearing truncate diff: " << trunc << dendl;
+        dout(20) << ": clearing truncate diff: " << trunc << dendl;
       }
 
       // prepare the object map state
@@ -331,15 +321,15 @@ void ObjectCopyRequest<I>::compute_diffs() {
 
       // 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;
+        dout(20) << ": read/write op: " << it.get_start() << "~"
+                 << it.get_len() << dendl;
         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;
+        dout(20) << ": trunc op: " << end_size << dendl;
         m_snap_sync_ops[end_remote_snap_id].emplace_back(SYNC_OP_TYPE_TRUNC,
                                                          end_size, 0U,
                                                          bufferlist());
@@ -347,7 +337,7 @@ void ObjectCopyRequest<I>::compute_diffs() {
     } else {
       if (prev_exists) {
         // object remove
-        ldout(cct, 20) << ": remove op" << dendl;
+        dout(20) << ": remove op" << dendl;
         m_snap_sync_ops[end_remote_snap_id].emplace_back(SYNC_OP_TYPE_REMOVE,
                                                          0U, 0U, bufferlist());
       }
@@ -361,8 +351,7 @@ void ObjectCopyRequest<I>::compute_diffs() {
 
 template <typename I>
 void ObjectCopyRequest<I>::finish(int r) {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
   m_on_finish->complete(r);
   delete this;
diff --git a/src/tools/rbd_mirror/image_sync/SnapshotCopyRequest.cc b/src/tools/rbd_mirror/image_sync/SnapshotCopyRequest.cc
index 016e4b4..026e72f 100644
--- a/src/tools/rbd_mirror/image_sync/SnapshotCopyRequest.cc
+++ b/src/tools/rbd_mirror/image_sync/SnapshotCopyRequest.cc
@@ -4,6 +4,7 @@
 #include "SnapshotCopyRequest.h"
 #include "SnapshotCreateRequest.h"
 #include "common/errno.h"
+#include "common/WorkQueue.h"
 #include "journal/Journaler.h"
 #include "librbd/Operations.h"
 #include "librbd/Utils.h"
@@ -35,6 +36,7 @@ const std::string &get_snapshot_name(I *image_ctx, librados::snap_t snap_id) {
 } // anonymous namespace
 
 using librbd::util::create_context_callback;
+using librbd::util::unique_lock_name;
 
 template <typename I>
 SnapshotCopyRequest<I>::SnapshotCopyRequest(I *local_image_ctx,
@@ -42,10 +44,14 @@ SnapshotCopyRequest<I>::SnapshotCopyRequest(I *local_image_ctx,
                                             SnapMap *snap_map,
                                             Journaler *journaler,
                                             librbd::journal::MirrorPeerClientMeta *meta,
+                                            ContextWQ *work_queue,
                                             Context *on_finish)
-  : m_local_image_ctx(local_image_ctx), m_remote_image_ctx(remote_image_ctx),
+  : BaseRequest("rbd::mirror::image_sync::SnapshotCopyRequest",
+		local_image_ctx->cct, on_finish),
+    m_local_image_ctx(local_image_ctx), m_remote_image_ctx(remote_image_ctx),
     m_snap_map(snap_map), m_journaler(journaler), m_client_meta(meta),
-    m_on_finish(on_finish), m_snap_seqs(meta->snap_seqs) {
+    m_work_queue(work_queue), m_snap_seqs(meta->snap_seqs),
+    m_lock(unique_lock_name("SnapshotCopyRequest::m_lock", this)) {
   m_snap_map->clear();
 
   // snap ids ordered from oldest to newest
@@ -57,27 +63,34 @@ SnapshotCopyRequest<I>::SnapshotCopyRequest(I *local_image_ctx,
 
 template <typename I>
 void SnapshotCopyRequest<I>::send() {
+  librbd::parent_spec remote_parent_spec;
+  int r = validate_parent(m_remote_image_ctx, &remote_parent_spec);
+  if (r < 0) {
+    derr << ": remote image parent spec mismatch" << dendl;
+    error(r);
+    return;
+  }
+
+  r = validate_parent(m_local_image_ctx, &m_local_parent_spec);
+  if (r < 0) {
+    derr << ": local image parent spec mismatch" << dendl;
+    error(r);
+    return;
+  }
+
   send_snap_unprotect();
 }
 
 template <typename I>
-void SnapshotCopyRequest<I>::send_snap_unprotect() {
-  CephContext *cct = m_local_image_ctx->cct;
+void SnapshotCopyRequest<I>::cancel() {
+  Mutex::Locker locker(m_lock);
 
-  // TODO: issue #14937 needs to add support for cloned images
-  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();
+  dout(20) << dendl;
+  m_canceled = true;
+}
+
+template <typename I>
+void SnapshotCopyRequest<I>::send_snap_unprotect() {
 
   SnapIdSet::iterator snap_id_it = m_local_snap_ids.begin();
   if (m_prev_snap_id != CEPH_NOSNAP) {
@@ -92,8 +105,8 @@ void SnapshotCopyRequest<I>::send_snap_unprotect() {
     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;
+      derr << "failed to retrieve local snap unprotect status: "
+           << cpp_strerror(r) << dendl;
       m_local_image_ctx->snap_lock.put_read();
       finish(r);
       return;
@@ -119,8 +132,8 @@ void SnapshotCopyRequest<I>::send_snap_unprotect() {
       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;
+        derr << "failed to retrieve remote snap unprotect status: "
+             << cpp_strerror(r) << dendl;
         m_remote_image_ctx->snap_lock.put_read();
         finish(r);
         return;
@@ -147,9 +160,9 @@ void SnapshotCopyRequest<I>::send_snap_unprotect() {
   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;
+  dout(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>(
@@ -161,23 +174,24 @@ void SnapshotCopyRequest<I>::send_snap_unprotect() {
 
 template <typename I>
 void SnapshotCopyRequest<I>::handle_snap_unprotect(int r) {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
   if (r < 0) {
-    lderr(cct) << ": failed to unprotect snapshot '" << m_snap_name << "': "
-               << cpp_strerror(r) << dendl;
+    derr << ": failed to unprotect snapshot '" << m_snap_name << "': "
+         << cpp_strerror(r) << dendl;
     finish(r);
     return;
   }
+  if (handle_cancellation())
+  {
+    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);
@@ -208,9 +222,9 @@ void SnapshotCopyRequest<I>::send_snap_remove() {
   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;
+  dout(20) << ": "
+           << "snap_name=" << m_snap_name << ", "
+           << "snap_id=" << m_prev_snap_id << dendl;
 
   Context *ctx = create_context_callback<
     SnapshotCopyRequest<I>, &SnapshotCopyRequest<I>::handle_snap_remove>(
@@ -221,23 +235,24 @@ void SnapshotCopyRequest<I>::send_snap_remove() {
 
 template <typename I>
 void SnapshotCopyRequest<I>::handle_snap_remove(int r) {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
   if (r < 0) {
-    lderr(cct) << ": failed to remove snapshot '" << m_snap_name << "': "
-               << cpp_strerror(r) << dendl;
+    derr << ": failed to remove snapshot '" << m_snap_name << "': "
+         << cpp_strerror(r) << dendl;
     finish(r);
     return;
   }
+  if (handle_cancellation())
+  {
+    return;
+  }
 
   send_snap_remove();
 }
 
 template <typename I>
 void SnapshotCopyRequest<I>::send_snap_create() {
-  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);
@@ -266,38 +281,54 @@ void SnapshotCopyRequest<I>::send_snap_create() {
   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;
+    derr << "failed to retrieve remote snap info: " << m_snap_name
+         << dendl;
     finish(-ENOENT);
     return;
   }
+
   uint64_t size = snap_info_it->second.size;
+  librbd::parent_spec parent_spec;
+  uint64_t parent_overlap = 0;
+  if (snap_info_it->second.parent.spec.pool_id != -1) {
+    parent_spec = m_local_parent_spec;
+    parent_overlap = snap_info_it->second.parent.overlap;
+  }
   m_remote_image_ctx->snap_lock.put_read();
 
-  ldout(cct, 20) << ": "
-                 << "snap_name=" << m_snap_name << ", "
-                 << "snap_id=" << m_prev_snap_id << ", "
-                 << "size=" << size << dendl;
+
+  dout(20) << ": "
+           << "snap_name=" << m_snap_name << ", "
+           << "snap_id=" << m_prev_snap_id << ", "
+           << "size=" << size << ", "
+           << "parent_info=["
+           << "pool_id=" << parent_spec.pool_id << ", "
+           << "image_id=" << parent_spec.image_id << ", "
+           << "snap_id=" << parent_spec.snap_id << ", "
+           << "overlap=" << parent_overlap << "]" << dendl;
 
   Context *ctx = create_context_callback<
     SnapshotCopyRequest<I>, &SnapshotCopyRequest<I>::handle_snap_create>(
       this);
   SnapshotCreateRequest<I> *req = SnapshotCreateRequest<I>::create(
-    m_local_image_ctx, m_snap_name, size, ctx);
+    m_local_image_ctx, m_snap_name, size, parent_spec, parent_overlap, ctx);
   req->send();
 }
 
 template <typename I>
 void SnapshotCopyRequest<I>::handle_snap_create(int r) {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
   if (r < 0) {
-    lderr(cct) << ": failed to create snapshot '" << m_snap_name << "': "
-               << cpp_strerror(r) << dendl;
+    derr << ": failed to create snapshot '" << m_snap_name << "': "
+         << cpp_strerror(r) << dendl;
     finish(r);
     return;
   }
+  if (handle_cancellation())
+  {
+    return;
+  }
 
   assert(m_prev_snap_id != CEPH_NOSNAP);
 
@@ -305,8 +336,8 @@ void SnapshotCopyRequest<I>::handle_snap_create(int r) {
   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 " << m_prev_snap_id << " to "
-                 << local_snap_id << dendl;
+  dout(20) << ": mapping remote snap id " << m_prev_snap_id << " to "
+           << local_snap_id << dendl;
   m_snap_seqs[m_prev_snap_id] = local_snap_id;
 
   send_snap_create();
@@ -314,8 +345,6 @@ void SnapshotCopyRequest<I>::handle_snap_create(int r) {
 
 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);
@@ -329,8 +358,8 @@ void SnapshotCopyRequest<I>::send_snap_protect() {
     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;
+      derr << "failed to retrieve remote snap protect status: "
+           << cpp_strerror(r) << dendl;
       m_remote_image_ctx->snap_lock.put_read();
       finish(r);
       return;
@@ -351,8 +380,8 @@ void SnapshotCopyRequest<I>::send_snap_protect() {
     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;
+      derr << "failed to retrieve local snap protect status: "
+           << cpp_strerror(r) << dendl;
       m_local_image_ctx->snap_lock.put_read();
       finish(r);
       return;
@@ -374,9 +403,9 @@ void SnapshotCopyRequest<I>::send_snap_protect() {
   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;
+  dout(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>(
@@ -387,23 +416,25 @@ void SnapshotCopyRequest<I>::send_snap_protect() {
 
 template <typename I>
 void SnapshotCopyRequest<I>::handle_snap_protect(int r) {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
   if (r < 0) {
-    lderr(cct) << ": failed to protect snapshot '" << m_snap_name << "': "
-               << cpp_strerror(r) << dendl;
+    derr << ": failed to protect snapshot '" << m_snap_name << "': "
+         << cpp_strerror(r) << dendl;
     finish(r);
     return;
   }
+  if (handle_cancellation())
+  {
+    return;
+  }
 
   send_snap_protect();
 }
 
 template <typename I>
 void SnapshotCopyRequest<I>::send_update_client() {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << dendl;
+  dout(20) << dendl;
 
   compute_snap_map();
 
@@ -422,15 +453,18 @@ void SnapshotCopyRequest<I>::send_update_client() {
 
 template <typename I>
 void SnapshotCopyRequest<I>::handle_update_client(int r) {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
   if (r < 0) {
-    lderr(cct) << ": failed to update client data: " << cpp_strerror(r)
-               << dendl;
+    derr << ": failed to update client data: " << cpp_strerror(r)
+         << dendl;
     finish(r);
     return;
   }
+  if (handle_cancellation())
+  {
+    return;
+  }
 
   m_client_meta->snap_seqs = m_snap_seqs;
 
@@ -438,16 +472,23 @@ void SnapshotCopyRequest<I>::handle_update_client(int r) {
 }
 
 template <typename I>
-void SnapshotCopyRequest<I>::finish(int r) {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
-
-  if (r >= 0) {
-    m_client_meta->snap_seqs = m_snap_seqs;
+bool SnapshotCopyRequest<I>::handle_cancellation() {
+  {
+    Mutex::Locker locker(m_lock);
+    if (!m_canceled) {
+      return false;
+    }
   }
+  dout(10) << ": snapshot copy canceled" << dendl;
+  finish(-ECANCELED);
+  return true;
+}
+
+template <typename I>
+void SnapshotCopyRequest<I>::error(int r) {
+  dout(20) << ": r=" << r << dendl;
 
-  m_on_finish->complete(r);
-  delete this;
+  m_work_queue->queue(new FunctionContext([this, r](int r1) { finish(r); }));
 }
 
 template <typename I>
@@ -460,6 +501,30 @@ void SnapshotCopyRequest<I>::compute_snap_map() {
   }
 }
 
+template <typename I>
+int SnapshotCopyRequest<I>::validate_parent(I *image_ctx,
+                                            librbd::parent_spec *spec) {
+  RWLock::RLocker owner_locker(image_ctx->owner_lock);
+  RWLock::RLocker snap_locker(image_ctx->snap_lock);
+
+  // ensure remote image's parent specs are still consistent
+  *spec = image_ctx->parent_md.spec;
+  for (auto &snap_info_pair : image_ctx->snap_info) {
+    auto &parent_spec = snap_info_pair.second.parent.spec;
+    if (parent_spec.pool_id == -1) {
+      continue;
+    } else if (spec->pool_id == -1) {
+      *spec = parent_spec;
+      continue;
+    }
+
+    if (*spec != parent_spec) {
+      return -EINVAL;
+    }
+  }
+  return 0;
+}
+
 } // namespace image_sync
 } // namespace mirror
 } // namespace rbd
diff --git a/src/tools/rbd_mirror/image_sync/SnapshotCopyRequest.h b/src/tools/rbd_mirror/image_sync/SnapshotCopyRequest.h
index 87f766f..2be105e 100644
--- a/src/tools/rbd_mirror/image_sync/SnapshotCopyRequest.h
+++ b/src/tools/rbd_mirror/image_sync/SnapshotCopyRequest.h
@@ -8,13 +8,16 @@
 #include "include/rados/librados.hpp"
 #include "common/snap_types.h"
 #include "librbd/ImageCtx.h"
+#include "librbd/parent_types.h"
 #include "librbd/journal/TypeTraits.h"
+#include "tools/rbd_mirror/BaseRequest.h"
 #include <map>
 #include <set>
 #include <string>
 #include <tuple>
 
 class Context;
+class ContextWQ;
 namespace journal { class Journaler; }
 namespace librbd { namespace journal { struct MirrorPeerClientMeta; } }
 
@@ -23,7 +26,7 @@ namespace mirror {
 namespace image_sync {
 
 template <typename ImageCtxT = librbd::ImageCtx>
-class SnapshotCopyRequest {
+class SnapshotCopyRequest : public BaseRequest {
 public:
   typedef librbd::journal::TypeTraits<ImageCtxT> TypeTraits;
   typedef typename TypeTraits::Journaler Journaler;
@@ -35,17 +38,20 @@ public:
                                      ImageCtxT *remote_image_ctx,
                                      SnapMap *snap_map, Journaler *journaler,
                                      librbd::journal::MirrorPeerClientMeta *client_meta,
+                                     ContextWQ *work_queue,
                                      Context *on_finish) {
     return new SnapshotCopyRequest(local_image_ctx, remote_image_ctx,
-                                   snap_map, journaler, client_meta, on_finish);
+                                   snap_map, journaler, client_meta, work_queue,
+                                   on_finish);
   }
 
   SnapshotCopyRequest(ImageCtxT *local_image_ctx, ImageCtxT *remote_image_ctx,
                       SnapMap *snap_map, Journaler *journaler,
                       librbd::journal::MirrorPeerClientMeta *client_meta,
-                      Context *on_finish);
+                      ContextWQ *work_queue, Context *on_finish);
 
   void send();
+  void cancel();
 
 private:
   /**
@@ -90,7 +96,7 @@ private:
   SnapMap *m_snap_map;
   Journaler *m_journaler;
   librbd::journal::MirrorPeerClientMeta *m_client_meta;
-  Context *m_on_finish;
+  ContextWQ *m_work_queue;
 
   SnapIdSet m_local_snap_ids;
   SnapIdSet m_remote_snap_ids;
@@ -99,6 +105,11 @@ private:
 
   std::string m_snap_name;
 
+  librbd::parent_spec m_local_parent_spec;
+
+  Mutex m_lock;
+  bool m_canceled = false;
+
   void send_snap_unprotect();
   void handle_snap_unprotect(int r);
 
@@ -114,10 +125,14 @@ private:
   void send_update_client();
   void handle_update_client(int r);
 
-  void finish(int r);
+  bool handle_cancellation();
+
+  void error(int r);
 
   void compute_snap_map();
 
+  int validate_parent(ImageCtxT *image_ctx, librbd::parent_spec *spec);
+
 };
 
 } // namespace image_sync
diff --git a/src/tools/rbd_mirror/image_sync/SnapshotCreateRequest.cc b/src/tools/rbd_mirror/image_sync/SnapshotCreateRequest.cc
index 60d03ff..ee0c80f 100644
--- a/src/tools/rbd_mirror/image_sync/SnapshotCreateRequest.cc
+++ b/src/tools/rbd_mirror/image_sync/SnapshotCreateRequest.cc
@@ -25,8 +25,11 @@ template <typename I>
 SnapshotCreateRequest<I>::SnapshotCreateRequest(I *local_image_ctx,
                                                 const std::string &snap_name,
                                                 uint64_t size,
+                                                const librbd::parent_spec &spec,
+                                                uint64_t parent_overlap,
                                                 Context *on_finish)
   : m_local_image_ctx(local_image_ctx), m_snap_name(snap_name), m_size(size),
+    m_parent_spec(spec), m_parent_overlap(parent_overlap),
     m_on_finish(on_finish) {
 }
 
@@ -45,8 +48,7 @@ void SnapshotCreateRequest<I>::send_set_size() {
   }
   m_local_image_ctx->snap_lock.put_read();
 
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << dendl;
+  dout(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
@@ -66,12 +68,11 @@ void SnapshotCreateRequest<I>::send_set_size() {
 
 template <typename I>
 void SnapshotCreateRequest<I>::handle_set_size(int r) {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
   if (r < 0) {
-    lderr(cct) << ": failed to update image size '" << m_snap_name << "': "
-               << cpp_strerror(r) << dendl;
+    derr << ": failed to update image size '" << m_snap_name << "': "
+         << cpp_strerror(r) << dendl;
     finish(r);
     return;
   }
@@ -87,45 +88,99 @@ void SnapshotCreateRequest<I>::handle_set_size(int r) {
 
 template <typename I>
 void SnapshotCreateRequest<I>::send_remove_parent() {
-  // TODO: issue #14937 needs to add support for cloned images
-  if (true) {
-    send_snap_create();
+  m_local_image_ctx->parent_lock.get_read();
+  if (m_local_image_ctx->parent_md.spec.pool_id == -1 ||
+      m_local_image_ctx->parent_md.spec == m_parent_spec) {
+    m_local_image_ctx->parent_lock.put_read();
+    send_set_parent();
     return;
   }
+  m_local_image_ctx->parent_lock.put_read();
+
+  dout(20) << dendl;
 
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << dendl;
+  librados::ObjectWriteOperation op;
+  librbd::cls_client::remove_parent(&op);
 
+  librados::AioCompletion *comp = create_rados_safe_callback<
+    SnapshotCreateRequest<I>,
+    &SnapshotCreateRequest<I>::handle_remove_parent>(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_remove_parent(int r) {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
-  // TODO: issue #14937 needs to add support for cloned images
+  if (r < 0) {
+    derr << ": failed to remove parent '" << m_snap_name << "': "
+         << cpp_strerror(r) << dendl;
+    finish(r);
+    return;
+  }
+
+  {
+    // adjust in-memory parent now that it's updated on disk
+    RWLock::WLocker parent_locker(m_local_image_ctx->parent_lock);
+    m_local_image_ctx->parent_md.spec = {};
+    m_local_image_ctx->parent_md.overlap = 0;
+  }
+
+  send_set_parent();
 }
 
 template <typename I>
 void SnapshotCreateRequest<I>::send_set_parent() {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << dendl;
+  m_local_image_ctx->parent_lock.get_read();
+  if (m_local_image_ctx->parent_md.spec == m_parent_spec &&
+      m_local_image_ctx->parent_md.overlap == m_parent_overlap) {
+    m_local_image_ctx->parent_lock.put_read();
+    send_snap_create();
+    return;
+  }
+  m_local_image_ctx->parent_lock.put_read();
+
+  dout(20) << dendl;
 
-  // TODO: issue #14937 needs to add support for cloned images
+  librados::ObjectWriteOperation op;
+  librbd::cls_client::set_parent(&op, m_parent_spec, m_parent_overlap);
+
+  librados::AioCompletion *comp = create_rados_safe_callback<
+    SnapshotCreateRequest<I>,
+    &SnapshotCreateRequest<I>::handle_set_parent>(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_parent(int r) {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
+
+  if (r < 0) {
+    derr << ": failed to set parent '" << m_snap_name << "': "
+         << cpp_strerror(r) << dendl;
+    finish(r);
+    return;
+  }
+
+  {
+    // adjust in-memory parent now that it's updated on disk
+    RWLock::WLocker parent_locker(m_local_image_ctx->parent_lock);
+    m_local_image_ctx->parent_md.spec = m_parent_spec;
+    m_local_image_ctx->parent_md.overlap = m_parent_overlap;
+  }
 
-  // TODO: issue #14937 needs to add support for cloned images
+  send_snap_create();
 }
 
 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;
+  dout(20) << ": snap_name=" << m_snap_name << dendl;
 
   Context *ctx = create_context_callback<
     SnapshotCreateRequest<I>, &SnapshotCreateRequest<I>::handle_snap_create>(
@@ -137,12 +192,11 @@ void SnapshotCreateRequest<I>::send_snap_create() {
 
 template <typename I>
 void SnapshotCreateRequest<I>::handle_snap_create(int r) {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
   if (r < 0) {
-    lderr(cct) << ": failed to create snapshot '" << m_snap_name << "': "
-               << cpp_strerror(r) << dendl;
+    derr << ": failed to create snapshot '" << m_snap_name << "': "
+         << cpp_strerror(r) << dendl;
     finish(r);
     return;
   }
@@ -151,7 +205,6 @@ void SnapshotCreateRequest<I>::handle_snap_create(int r) {
 }
 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);
@@ -161,7 +214,7 @@ void SnapshotCreateRequest<I>::send_create_object_map() {
   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;
+    derr << "failed to locate snap: " << m_snap_name << dendl;
     m_local_image_ctx->snap_lock.put_read();
     finish(-ENOENT);
     return;
@@ -173,9 +226,9 @@ void SnapshotCreateRequest<I>::send_create_object_map() {
     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;
+  dout(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)
@@ -192,12 +245,11 @@ void SnapshotCreateRequest<I>::send_create_object_map() {
 
 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;
+  dout(20) << ": r=" << r << dendl;
 
   if (r < 0) {
-    lderr(cct) << ": failed to create object map: " << cpp_strerror(r)
-               << dendl;
+    derr << ": failed to create object map: " << cpp_strerror(r)
+         << dendl;
     finish(r);
     return;
   }
@@ -207,8 +259,7 @@ void SnapshotCreateRequest<I>::handle_create_object_map(int r) {
 
 template <typename I>
 void SnapshotCreateRequest<I>::finish(int r) {
-  CephContext *cct = m_local_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
   m_on_finish->complete(r);
   delete this;
diff --git a/src/tools/rbd_mirror/image_sync/SnapshotCreateRequest.h b/src/tools/rbd_mirror/image_sync/SnapshotCreateRequest.h
index 897c7b9..c8c772f 100644
--- a/src/tools/rbd_mirror/image_sync/SnapshotCreateRequest.h
+++ b/src/tools/rbd_mirror/image_sync/SnapshotCreateRequest.h
@@ -8,6 +8,7 @@
 #include "include/rados/librados.hpp"
 #include "common/snap_types.h"
 #include "librbd/ImageCtx.h"
+#include "librbd/parent_types.h"
 #include "librbd/journal/TypeTraits.h"
 #include <map>
 #include <set>
@@ -25,14 +26,18 @@ class SnapshotCreateRequest {
 public:
   static SnapshotCreateRequest* create(ImageCtxT *local_image_ctx,
                                        const std::string &snap_name,
-                                       uint64_t size, Context *on_finish) {
+                                       uint64_t size,
+                                       const librbd::parent_spec &parent_spec,
+                                       uint64_t parent_overlap,
+                                       Context *on_finish) {
     return new SnapshotCreateRequest(local_image_ctx, snap_name, size,
-                                     on_finish);
+                                     parent_spec, parent_overlap, on_finish);
   }
 
   SnapshotCreateRequest(ImageCtxT *local_image_ctx,
                         const std::string &snap_name, uint64_t size,
-                        Context *on_finish);
+                        const librbd::parent_spec &parent_spec,
+                        uint64_t parent_overlap, Context *on_finish);
 
   void send();
 
@@ -66,6 +71,8 @@ private:
   ImageCtxT *m_local_image_ctx;
   std::string m_snap_name;
   uint64_t m_size;
+  librbd::parent_spec m_parent_spec;
+  uint64_t m_parent_overlap;
   Context *m_on_finish;
 
   void send_set_size();
diff --git a/src/tools/rbd_mirror/image_sync/SyncPointCreateRequest.cc b/src/tools/rbd_mirror/image_sync/SyncPointCreateRequest.cc
index 589f532..2c344ba 100644
--- a/src/tools/rbd_mirror/image_sync/SyncPointCreateRequest.cc
+++ b/src/tools/rbd_mirror/image_sync/SyncPointCreateRequest.cc
@@ -60,8 +60,7 @@ void SyncPointCreateRequest<I>::send_update_client() {
   sync_point.snap_name = SNAP_NAME_PREFIX + "." + m_mirror_uuid + "." +
                          uuid_gen.to_string();
 
-  CephContext *cct = m_remote_image_ctx->cct;
-  ldout(cct, 20) << ": sync_point=" << sync_point << dendl;
+  dout(20) << ": sync_point=" << sync_point << dendl;
 
   bufferlist client_data_bl;
   librbd::journal::ClientData client_data(m_client_meta_copy);
@@ -75,12 +74,11 @@ void SyncPointCreateRequest<I>::send_update_client() {
 
 template <typename I>
 void SyncPointCreateRequest<I>::handle_update_client(int r) {
-  CephContext *cct = m_remote_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
   if (r < 0) {
-    lderr(cct) << ": failed to update client data: " << cpp_strerror(r)
-               << dendl;
+    derr << ": failed to update client data: " << cpp_strerror(r)
+         << dendl;
     finish(r);
     return;
   }
@@ -93,8 +91,7 @@ void SyncPointCreateRequest<I>::handle_update_client(int r) {
 
 template <typename I>
 void SyncPointCreateRequest<I>::send_refresh_image() {
-  CephContext *cct = m_remote_image_ctx->cct;
-  ldout(cct, 20) << dendl;
+  dout(20) << dendl;
 
   Context *ctx = create_context_callback<
     SyncPointCreateRequest<I>, &SyncPointCreateRequest<I>::handle_refresh_image>(
@@ -104,11 +101,10 @@ void SyncPointCreateRequest<I>::send_refresh_image() {
 
 template <typename I>
 void SyncPointCreateRequest<I>::handle_refresh_image(int r) {
-  CephContext *cct = m_remote_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
   if (r < 0) {
-    lderr(cct) << ": remote image refresh failed: " << cpp_strerror(r) << dendl;
+    derr << ": remote image refresh failed: " << cpp_strerror(r) << dendl;
     finish(r);
     return;
   }
@@ -118,8 +114,7 @@ void SyncPointCreateRequest<I>::handle_refresh_image(int r) {
 
 template <typename I>
 void SyncPointCreateRequest<I>::send_create_snap() {
-  CephContext *cct = m_remote_image_ctx->cct;
-  ldout(cct, 20) << dendl;
+  dout(20) << dendl;
 
   MirrorPeerSyncPoint &sync_point = m_client_meta_copy.sync_points.back();
 
@@ -132,14 +127,37 @@ void SyncPointCreateRequest<I>::send_create_snap() {
 
 template <typename I>
 void SyncPointCreateRequest<I>::handle_create_snap(int r) {
-  CephContext *cct = m_remote_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
   if (r == -EEXIST) {
     send_update_client();
     return;
   } else if (r < 0) {
-    lderr(cct) << ": failed to create snapshot: " << cpp_strerror(r) << dendl;
+    derr << ": failed to create snapshot: " << cpp_strerror(r) << dendl;
+    finish(r);
+    return;
+  }
+
+  send_final_refresh_image();
+}
+
+template <typename I>
+void SyncPointCreateRequest<I>::send_final_refresh_image() {
+  dout(20) << dendl;
+
+  Context *ctx = create_context_callback<
+    SyncPointCreateRequest<I>,
+    &SyncPointCreateRequest<I>::handle_final_refresh_image>(this);
+  m_remote_image_ctx->state->refresh(ctx);
+}
+
+template <typename I>
+void SyncPointCreateRequest<I>::handle_final_refresh_image(int r) {
+  dout(20) << ": r=" << r << dendl;
+
+  if (r < 0) {
+    derr << ": failed to refresh image for snapshot: " << cpp_strerror(r)
+         << dendl;
     finish(r);
     return;
   }
@@ -149,8 +167,7 @@ void SyncPointCreateRequest<I>::handle_create_snap(int r) {
 
 template <typename I>
 void SyncPointCreateRequest<I>::finish(int r) {
-  CephContext *cct = m_remote_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
   m_on_finish->complete(r);
   delete this;
diff --git a/src/tools/rbd_mirror/image_sync/SyncPointCreateRequest.h b/src/tools/rbd_mirror/image_sync/SyncPointCreateRequest.h
index 0aef0f8..45275ec 100644
--- a/src/tools/rbd_mirror/image_sync/SyncPointCreateRequest.h
+++ b/src/tools/rbd_mirror/image_sync/SyncPointCreateRequest.h
@@ -56,6 +56,9 @@ private:
    * CREATE_SNAP . . . .
    *    |
    *    v
+   * REFRESH_IMAGE
+   *    |
+   *    v
    * <finish>
    *
    * @endverbatim
@@ -78,6 +81,9 @@ private:
   void send_create_snap();
   void handle_create_snap(int r);
 
+  void send_final_refresh_image();
+  void handle_final_refresh_image(int r);
+
   void finish(int r);
 };
 
diff --git a/src/tools/rbd_mirror/image_sync/SyncPointPruneRequest.cc b/src/tools/rbd_mirror/image_sync/SyncPointPruneRequest.cc
index 7e6ab2e..d9b5e8e 100644
--- a/src/tools/rbd_mirror/image_sync/SyncPointPruneRequest.cc
+++ b/src/tools/rbd_mirror/image_sync/SyncPointPruneRequest.cc
@@ -91,8 +91,7 @@ void SyncPointPruneRequest<I>::send_remove_snap() {
 
   const std::string &snap_name = m_snap_names.front();
 
-  CephContext *cct = m_remote_image_ctx->cct;
-  ldout(cct, 20) << ": snap_name=" << snap_name << dendl;
+  dout(20) << ": snap_name=" << snap_name << dendl;
 
   Context *ctx = create_context_callback<
     SyncPointPruneRequest<I>, &SyncPointPruneRequest<I>::handle_remove_snap>(
@@ -102,8 +101,7 @@ void SyncPointPruneRequest<I>::send_remove_snap() {
 
 template <typename I>
 void SyncPointPruneRequest<I>::handle_remove_snap(int r) {
-  CephContext *cct = m_remote_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
   assert(!m_snap_names.empty());
   std::string snap_name = m_snap_names.front();
@@ -113,8 +111,8 @@ void SyncPointPruneRequest<I>::handle_remove_snap(int r) {
     r = 0;
   }
   if (r < 0) {
-    lderr(cct) << ": failed to remove snapshot '" << snap_name << "': "
-               << cpp_strerror(r) << dendl;
+    derr << ": failed to remove snapshot '" << snap_name << "': "
+         << cpp_strerror(r) << dendl;
     finish(r);
     return;
   }
@@ -124,8 +122,7 @@ void SyncPointPruneRequest<I>::handle_remove_snap(int r) {
 
 template <typename I>
 void SyncPointPruneRequest<I>::send_refresh_image() {
-  CephContext *cct = m_remote_image_ctx->cct;
-  ldout(cct, 20) << dendl;
+  dout(20) << dendl;
 
   Context *ctx = create_context_callback<
     SyncPointPruneRequest<I>, &SyncPointPruneRequest<I>::handle_refresh_image>(
@@ -135,11 +132,10 @@ void SyncPointPruneRequest<I>::send_refresh_image() {
 
 template <typename I>
 void SyncPointPruneRequest<I>::handle_refresh_image(int r) {
-  CephContext *cct = m_remote_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
   if (r < 0) {
-    lderr(cct) << ": remote image refresh failed: " << cpp_strerror(r) << dendl;
+    derr << ": remote image refresh failed: " << cpp_strerror(r) << dendl;
     finish(r);
     return;
   }
@@ -149,8 +145,7 @@ void SyncPointPruneRequest<I>::handle_refresh_image(int r) {
 
 template <typename I>
 void SyncPointPruneRequest<I>::send_update_client() {
-  CephContext *cct = m_remote_image_ctx->cct;
-  ldout(cct, 20) << dendl;
+  dout(20) << dendl;
 
   if (m_sync_complete) {
     m_client_meta_copy.sync_points.pop_front();
@@ -175,12 +170,11 @@ void SyncPointPruneRequest<I>::send_update_client() {
 
 template <typename I>
 void SyncPointPruneRequest<I>::handle_update_client(int r) {
-  CephContext *cct = m_remote_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
   if (r < 0) {
-    lderr(cct) << ": failed to update client data: " << cpp_strerror(r)
-               << dendl;
+    derr << ": failed to update client data: " << cpp_strerror(r)
+         << dendl;
     finish(r);
     return;
   }
@@ -192,8 +186,7 @@ void SyncPointPruneRequest<I>::handle_update_client(int r) {
 
 template <typename I>
 void SyncPointPruneRequest<I>::finish(int r) {
-  CephContext *cct = m_remote_image_ctx->cct;
-  ldout(cct, 20) << ": r=" << r << dendl;
+  dout(20) << ": r=" << r << dendl;
 
   m_on_finish->complete(r);
   delete this;
diff --git a/src/tools/rbd_mirror/main.cc b/src/tools/rbd_mirror/main.cc
index 017f5a4..0bdafe8 100644
--- a/src/tools/rbd_mirror/main.cc
+++ b/src/tools/rbd_mirror/main.cc
@@ -61,6 +61,9 @@ int main(int argc, const char **argv)
   std::vector<const char*> cmd_args;
   argv_to_vec(argc, argv, cmd_args);
 
+  // disable unnecessary librbd cache
+  g_ceph_context->_conf->set_val_or_die("rbd_cache", "false");
+
   mirror = new rbd::mirror::Mirror(g_ceph_context, cmd_args);
   int r = mirror->init();
   if (r < 0) {
diff --git a/systemd/ceph-mds at .service b/systemd/ceph-mds at .service
index ae1c29d..d34bd05 100644
--- a/systemd/ceph-mds at .service
+++ b/systemd/ceph-mds at .service
@@ -1,7 +1,7 @@
 [Unit]
 Description=Ceph metadata server daemon
-After=network-online.target local-fs.target
-Wants=network-online.target local-fs.target
+After=network-online.target local-fs.target time-sync.target
+Wants=network-online.target local-fs.target time-sync.target
 PartOf=ceph-mds.target
 
 [Service]
diff --git a/systemd/ceph-mon at .service b/systemd/ceph-mon at .service
index d62c4fd..15e2bf7 100644
--- a/systemd/ceph-mon at .service
+++ b/systemd/ceph-mon at .service
@@ -5,8 +5,8 @@ Description=Ceph cluster monitor daemon
 #   http://www.freedesktop.org/wiki/Software/systemd/NetworkTarget
 # these can be removed once ceph-mon will dynamically change network
 # configuration.
-After=network-online.target local-fs.target ceph-create-keys@%i.service
-Wants=network-online.target local-fs.target ceph-create-keys@%i.service
+After=network-online.target local-fs.target time-sync.target ceph-create-keys@%i.service
+Wants=network-online.target local-fs.target time-sync.target ceph-create-keys@%i.service
 
 PartOf=ceph-mon.target
 
diff --git a/systemd/ceph-osd at .service b/systemd/ceph-osd at .service
index ac178e3..592b324 100644
--- a/systemd/ceph-osd at .service
+++ b/systemd/ceph-osd at .service
@@ -1,7 +1,7 @@
 [Unit]
 Description=Ceph object storage daemon
-After=network-online.target local-fs.target
-Wants=network-online.target local-fs.target
+After=network-online.target local-fs.target time-sync.target
+Wants=network-online.target local-fs.target time-sync.target
 PartOf=ceph-osd.target
 
 [Service]
diff --git a/systemd/ceph-radosgw at .service b/systemd/ceph-radosgw at .service
index e19ba16..3f7fcac 100644
--- a/systemd/ceph-radosgw at .service
+++ b/systemd/ceph-radosgw at .service
@@ -1,7 +1,7 @@
 [Unit]
 Description=Ceph rados gateway
-After=network-online.target local-fs.target
-Wants=network-online.target local-fs.target
+After=network-online.target local-fs.target time-sync.target
+Wants=network-online.target local-fs.target time-sync.target
 PartOf=ceph-radosgw.target
 
 [Service]
diff --git a/udev/60-ceph-partuuid-workaround.rules b/udev/60-ceph-partuuid-workaround.rules
deleted file mode 100644
index 2905969..0000000
--- a/udev/60-ceph-partuuid-workaround.rules
+++ /dev/null
@@ -1,37 +0,0 @@
-#
-# this is a kludge installed by ceph to fix the /dev/disk/by-partuuid 
-# symlinks on systems with old udev (< 180).  it's a stripped down 
-# version of a newer 60-persistent-storage.rules file that hopefully 
-# captures the same set of conditions for setting up those symlinks.
-#
-
-# forward scsi device event to corresponding block device
-ACTION=="change", SUBSYSTEM=="scsi", ENV{DEVTYPE}=="scsi_device", TEST=="block", ATTR{block/*/uevent}="change"
-
-ACTION=="remove", GOTO="persistent_storage_end_two"
-
-SUBSYSTEM!="block", GOTO="persistent_storage_end_two"
-
-# skip rules for inappropriate block devices
-KERNEL=="fd*|mtd*|nbd*|gnbd*|btibm*|md*", GOTO="persistent_storage_end_two"
-
-# ignore partitions that span the entire disk
-TEST=="whole_disk", GOTO="persistent_storage_end_two"
-
-# for partitions import parent information
-ENV{DEVTYPE}=="partition", IMPORT{parent}="ID_*"
-
-# skip unpartitioned removable media devices from drivers which do not send "change" events
-ENV{DEVTYPE}=="disk", KERNEL!="sd*|sr*", ATTR{removable}=="1", GOTO="persistent_storage_end_two"
-
-# probe filesystem metadata of disks
-KERNEL!="sr*", IMPORT{program}="/sbin/blkid -o udev -p $tempnode"
-
-# by-partlabel/by-partuuid links (partition metadata)
-ENV{ID_PART_ENTRY_SCHEME}=="gpt", ENV{ID_PART_ENTRY_UUID}=="?*", SYMLINK+="disk/by-partuuid/$env{ID_PART_ENTRY_UUID}"
-ENV{ID_PART_ENTRY_SCHEME}=="gpt", ENV{ID_PART_ENTRY_NAME}=="?*", SYMLINK+="disk/by-partlabel/$env{ID_PART_ENTRY_NAME}"
-
-# NEW: by-parttypeuuid links (type.id)
-ENV{ID_PART_ENTRY_SCHEME}=="gpt", ENV{ID_PART_ENTRY_TYPE}=="?*", ENV{ID_PART_ENTRY_UUID}=="?*", SYMLINK+="disk/by-parttypeuuid/$env{ID_PART_ENTRY_TYPE}.$env{ID_PART_ENTRY_UUID}"
-
-LABEL="persistent_storage_end_two"
diff --git a/udev/95-ceph-osd-alt.rules b/udev/95-ceph-osd-alt.rules
deleted file mode 100644
index 3306e24..0000000
--- a/udev/95-ceph-osd-alt.rules
+++ /dev/null
@@ -1,5 +0,0 @@
-#  Check gpt partion for ceph tags and activate
-ACTION=="add", SUBSYSTEM=="block", \
-  ENV{DEVTYPE}=="partition", \
-  ENV{ID_PART_TABLE_TYPE}=="gpt", \
-  RUN+="/usr/sbin/ceph-disk-udev $number $name $parent"

-- 
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