// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab

#include "cls/journal/cls_journal_client.h"
#include "cls/rbd/cls_rbd_client.h"
#include "cls/rbd/cls_rbd_types.h"
#include "test/librados/test_cxx.h"
#include "test/librbd/test_fixture.h"
#include "test/librbd/test_support.h"
#include "include/rbd/librbd.h"
#include "librbd/ExclusiveLock.h"
#include "librbd/ImageState.h"
#include "librbd/ImageWatcher.h"
#include "librbd/internal.h"
#include "librbd/ObjectMap.h"
#include "librbd/Operations.h"
#include "librbd/api/DiffIterate.h"
#include "librbd/api/Image.h"
#include "librbd/api/Io.h"
#include "librbd/api/Migration.h"
#include "librbd/api/PoolMetadata.h"
#include "librbd/api/Snapshot.h"
#include "librbd/io/AioCompletion.h"
#include "librbd/io/ImageRequest.h"
#include "osdc/Striper.h"
#include "common/Cond.h"
#include <boost/scope_exit.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/assign/list_of.hpp>
#include <shared_mutex> // for std::shared_lock
#include <utility>
#include <vector>
#include "test/librados/crimson_utils.h"

using namespace std;

void register_test_internal() {
}

namespace librbd {

class TestInternal : public TestFixture {
public:

  TestInternal() {}

  typedef std::vector<std::pair<std::string, bool> > Snaps;

  void TearDown() override {
    unlock_image();
    for (Snaps::iterator iter = m_snaps.begin(); iter != m_snaps.end(); ++iter) {
      librbd::ImageCtx *ictx;
      EXPECT_EQ(0, open_image(m_image_name, &ictx));
      if (iter->second) {
	EXPECT_EQ(0,
		  ictx->operations->snap_unprotect(cls::rbd::UserSnapshotNamespace(),
						   iter->first.c_str()));
      }
      EXPECT_EQ(0,
		ictx->operations->snap_remove(cls::rbd::UserSnapshotNamespace(),
					      iter->first.c_str()));
    }

    TestFixture::TearDown();
  }

  int create_snapshot(const char *snap_name, bool snap_protect) {
    librbd::ImageCtx *ictx;
    int r = open_image(m_image_name, &ictx);
    if (r < 0) {
      return r;
    }

    r = snap_create(*ictx, snap_name);
    if (r < 0) {
      return r;
    }

    m_snaps.push_back(std::make_pair(snap_name, snap_protect));
    if (snap_protect) {
      r = ictx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(), snap_name);
      if (r < 0) {
	return r;
      }
    }
    close_image(ictx);
    return 0;
  }

  Snaps m_snaps;
};

class DummyContext : public Context {
public:
  void finish(int r) override {
  }
};

void generate_random_iomap(librbd::Image &image, int num_objects, int object_size,
                           int max_count, map<uint64_t, uint64_t> &iomap)
{
  uint64_t stripe_unit, stripe_count;

  stripe_unit = image.get_stripe_unit();
  stripe_count = image.get_stripe_count();

  while (max_count-- > 0) {
    // generate random image offset based on base random object
    // number and object offset and then map that back to an
    // object number based on stripe unit and count.
    uint64_t ono = rand() % num_objects;
    uint64_t offset = rand() % (object_size - TEST_IO_SIZE);
    uint64_t imageoff = (ono * object_size) + offset;

    file_layout_t layout;
    layout.object_size = object_size;
    layout.stripe_unit = stripe_unit;
    layout.stripe_count = stripe_count;

    vector<ObjectExtent> ex;
    Striper::file_to_extents(g_ceph_context, 1, &layout, imageoff, TEST_IO_SIZE, 0, ex);

    // lets not worry if IO spans multiple extents (>1 object). in such
    // as case we would perform the write multiple times to the same
    // offset, but we record all objects that would be generated with
    // this IO. TODO: fix this if such a need is required by your
    // test.
    vector<ObjectExtent>::iterator it;
    map<uint64_t, uint64_t> curr_iomap;
    for (it = ex.begin(); it != ex.end(); ++it) {
      if (iomap.find((*it).objectno) != iomap.end()) {
        break;
      }

      curr_iomap.insert(make_pair((*it).objectno, imageoff));
    }

    if (it == ex.end()) {
      iomap.insert(curr_iomap.begin(), curr_iomap.end());
    }
  }
}

static bool is_sparsify_supported(librados::IoCtx &ioctx,
                                  const std::string &oid) {
  EXPECT_EQ(0, ioctx.create(oid, true));
  int r = librbd::cls_client::sparsify(&ioctx, oid, 16, true);
  EXPECT_TRUE(r == 0 || r == -EOPNOTSUPP);
  ioctx.remove(oid);

  return (r == 0);
}

static bool is_sparse_read_supported(librados::IoCtx &ioctx,
                                     const std::string &oid) {
  EXPECT_EQ(0, ioctx.create(oid, true));
  bufferlist inbl;
  inbl.append(std::string(4096, 'X'));
  EXPECT_EQ(0, ioctx.write(oid, inbl, inbl.length(), 4096));
  EXPECT_EQ(0, ioctx.write(oid, inbl, inbl.length(), 4096 * 3));

  std::map<uint64_t, uint64_t> m;
  bufferlist outbl;
  int r = ioctx.sparse_read(oid, m, outbl, 4096 * 4, 0);
  ioctx.remove(oid);

  int expected_r = 2;
  std::map<uint64_t, uint64_t> expected_m = {{4096, 4096}, {4096 * 3, 4096}};
  bufferlist expected_outbl;
  expected_outbl.append(std::string(4096 * 2, 'X'));

  return (r == expected_r && m == expected_m &&
          outbl.contents_equal(expected_outbl));
}

TEST_F(TestInternal, OpenByID) {
   REQUIRE_FORMAT_V2();

   librbd::ImageCtx *ictx;
   ASSERT_EQ(0, open_image(m_image_name, &ictx));
   std::string id = ictx->id;
   close_image(ictx);

   ictx = new librbd::ImageCtx("", id, nullptr, m_ioctx, true);
   ASSERT_EQ(0, ictx->state->open(0));
   ASSERT_EQ(ictx->name, m_image_name);
   close_image(ictx);
}

TEST_F(TestInternal, OpenSnapDNE) {
   librbd::ImageCtx *ictx;
   ASSERT_EQ(0, open_image(m_image_name, &ictx));

   ictx = new librbd::ImageCtx(m_image_name, "", "unknown_snap", m_ioctx, true);
   ASSERT_EQ(-ENOENT, ictx->state->open(librbd::OPEN_FLAG_SKIP_OPEN_PARENT));
}

TEST_F(TestInternal, IsExclusiveLockOwner) {
  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));

  bool is_owner;
  ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner));
  ASSERT_FALSE(is_owner);

  C_SaferCond ctx;
  {
    std::unique_lock l{ictx->owner_lock};
    ictx->exclusive_lock->try_acquire_lock(&ctx);
  }
  ASSERT_EQ(0, ctx.wait());
  ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner));
  ASSERT_TRUE(is_owner);
}

TEST_F(TestInternal, ResizeLocksImage) {
  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));

  librbd::NoOpProgressContext no_op;
  ASSERT_EQ(0, ictx->operations->resize(m_image_size >> 1, true, no_op));

  bool is_owner;
  ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner));
  ASSERT_TRUE(is_owner);
}

TEST_F(TestInternal, ResizeFailsToLockImage) {
  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));
  ASSERT_EQ(0, lock_image(*ictx, ClsLockType::EXCLUSIVE, "manually locked"));

  librbd::NoOpProgressContext no_op;
  ASSERT_EQ(-EROFS, ictx->operations->resize(m_image_size >> 1, true, no_op));
}

TEST_F(TestInternal, SnapCreateLocksImage) {
  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));

  ASSERT_EQ(0, snap_create(*ictx, "snap1"));
  BOOST_SCOPE_EXIT( (ictx) ) {
    ASSERT_EQ(0,
	      ictx->operations->snap_remove(cls::rbd::UserSnapshotNamespace(),
					    "snap1"));
  } BOOST_SCOPE_EXIT_END;

  bool is_owner;
  ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner));
  ASSERT_TRUE(is_owner);
}

TEST_F(TestInternal, SnapCreateFailsToLockImage) {
  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));
  ASSERT_EQ(0, lock_image(*ictx, ClsLockType::EXCLUSIVE, "manually locked"));

  ASSERT_EQ(-EROFS, snap_create(*ictx, "snap1"));
}

TEST_F(TestInternal, SnapRollbackLocksImage) {
  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);

  ASSERT_EQ(0, create_snapshot("snap1", false));

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));

  librbd::NoOpProgressContext no_op;
  ASSERT_EQ(0, ictx->operations->snap_rollback(cls::rbd::UserSnapshotNamespace(),
					       "snap1",
					       no_op));

  bool is_owner;
  ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner));
  ASSERT_TRUE(is_owner);
}

TEST_F(TestInternal, SnapRollbackFailsToLockImage) {
  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);


  ASSERT_EQ(0, create_snapshot("snap1", false));

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));
  ASSERT_EQ(0, lock_image(*ictx, ClsLockType::EXCLUSIVE, "manually locked"));

  librbd::NoOpProgressContext no_op;
  ASSERT_EQ(-EROFS,
	    ictx->operations->snap_rollback(cls::rbd::UserSnapshotNamespace(),
					    "snap1",
					    no_op));
}

TEST_F(TestInternal, SnapSetReleasesLock) {
  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);

  ASSERT_EQ(0, create_snapshot("snap1", false));

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));
  ASSERT_EQ(0, librbd::api::Image<>::snap_set(
                 ictx, cls::rbd::UserSnapshotNamespace(), "snap1"));

  bool is_owner;
  ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner));
  ASSERT_FALSE(is_owner);
}

TEST_F(TestInternal, FlattenLocksImage) {
  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_LAYERING);

  ASSERT_EQ(0, create_snapshot("snap1", true));

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));

  uint64_t features;
  ASSERT_EQ(0, librbd::get_features(ictx, &features));

  std::string clone_name = get_temp_image_name();
  int order = ictx->order;
  ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx,
			     clone_name.c_str(), features, &order, 0, 0));

  librbd::ImageCtx *ictx2;
  ASSERT_EQ(0, open_image(clone_name, &ictx2));

  librbd::NoOpProgressContext no_op;
  ASSERT_EQ(0, ictx2->operations->flatten(no_op));

  bool is_owner;
  ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx2, &is_owner));
  ASSERT_TRUE(is_owner);
}

TEST_F(TestInternal, FlattenFailsToLockImage) {
  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_LAYERING);

  ASSERT_EQ(0, create_snapshot("snap1", true));

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));

  uint64_t features;
  ASSERT_EQ(0, librbd::get_features(ictx, &features));

  std::string clone_name = get_temp_image_name();
  int order = ictx->order;
  ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx,
                             clone_name.c_str(), features, &order, 0, 0));

  TestInternal *parent = this;
  librbd::ImageCtx *ictx2 = NULL;
  BOOST_SCOPE_EXIT( (&m_ioctx) (clone_name) (parent) (&ictx2) ) {
    if (ictx2 != NULL) {
      parent->close_image(ictx2);
      parent->unlock_image();
    }
    librbd::NoOpProgressContext no_op;
    ASSERT_EQ(0, librbd::api::Image<>::remove(m_ioctx, clone_name, no_op));
  } BOOST_SCOPE_EXIT_END;

  ASSERT_EQ(0, open_image(clone_name, &ictx2));
  ASSERT_EQ(0, lock_image(*ictx2, ClsLockType::EXCLUSIVE, "manually locked"));

  librbd::NoOpProgressContext no_op;
  ASSERT_EQ(-EROFS, ictx2->operations->flatten(no_op));
}

TEST_F(TestInternal, WriteFailsToLockImageBlocklisted) {
  SKIP_IF_CRIMSON();
  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);

  librados::Rados blocklist_rados;
  ASSERT_EQ("", connect_cluster_pp(blocklist_rados));

  librados::IoCtx blocklist_ioctx;
  ASSERT_EQ(0, blocklist_rados.ioctx_create(_pool_name.c_str(),
                                            blocklist_ioctx));

  auto ictx = new librbd::ImageCtx(m_image_name, "", nullptr, blocklist_ioctx,
                                   false);
  ASSERT_EQ(0, ictx->state->open(0));

  std::list<librbd::image_watcher_t> watchers;
  ASSERT_EQ(0, librbd::list_watchers(ictx, watchers));
  ASSERT_EQ(1U, watchers.size());

  bool lock_owner;
  ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &lock_owner));
  ASSERT_FALSE(lock_owner);

  ASSERT_EQ(0, blocklist_rados.blocklist_add(watchers.front().addr, 0));

  ceph::bufferlist bl;
  bl.append(std::string(256, '1'));
  ASSERT_EQ(-EBLOCKLISTED, api::Io<>::write(*ictx, 0, bl.length(),
                                            std::move(bl), 0));
  ASSERT_EQ(-EBLOCKLISTED, librbd::is_exclusive_lock_owner(ictx, &lock_owner));

  close_image(ictx);
}

TEST_F(TestInternal, WriteFailsToLockImageBlocklistedWatch) {
  SKIP_IF_CRIMSON();
  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);

  librados::Rados blocklist_rados;
  ASSERT_EQ("", connect_cluster_pp(blocklist_rados));

  librados::IoCtx blocklist_ioctx;
  ASSERT_EQ(0, blocklist_rados.ioctx_create(_pool_name.c_str(),
                                            blocklist_ioctx));

  auto ictx = new librbd::ImageCtx(m_image_name, "", nullptr, blocklist_ioctx,
                                   false);
  ASSERT_EQ(0, ictx->state->open(0));

  std::list<librbd::image_watcher_t> watchers;
  ASSERT_EQ(0, librbd::list_watchers(ictx, watchers));
  ASSERT_EQ(1U, watchers.size());

  bool lock_owner;
  ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &lock_owner));
  ASSERT_FALSE(lock_owner);

  ASSERT_EQ(0, blocklist_rados.blocklist_add(watchers.front().addr, 0));
  // let ImageWatcher discover that the watch can't be re-registered to
  // eliminate the (intended) race in WriteFailsToLockImageBlocklisted
  while (!ictx->image_watcher->is_blocklisted()) {
    sleep(1);
  }

  ceph::bufferlist bl;
  bl.append(std::string(256, '1'));
  ASSERT_EQ(-EBLOCKLISTED, api::Io<>::write(*ictx, 0, bl.length(),
                                            std::move(bl), 0));
  ASSERT_EQ(-EBLOCKLISTED, librbd::is_exclusive_lock_owner(ictx, &lock_owner));

  close_image(ictx);
}

TEST_F(TestInternal, AioWriteRequestsLock) {
  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));
  ASSERT_EQ(0, lock_image(*ictx, ClsLockType::EXCLUSIVE, "manually locked"));

  std::string buffer(256, '1');
  Context *ctx = new DummyContext();
  auto c = librbd::io::AioCompletion::create(ctx);
  c->get();

  bufferlist bl;
  bl.append(buffer);
  api::Io<>::aio_write(*ictx, c, 0, buffer.size(), std::move(bl), 0, true);

  bool is_owner;
  ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner));
  ASSERT_FALSE(is_owner);
  ASSERT_FALSE(c->is_complete());

  unlock_image();
  ASSERT_EQ(0, c->wait_for_complete());
  c->put();
}

TEST_F(TestInternal, AioDiscardRequestsLock) {
  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));
  ASSERT_EQ(0, lock_image(*ictx, ClsLockType::EXCLUSIVE, "manually locked"));

  Context *ctx = new DummyContext();
  auto c = librbd::io::AioCompletion::create(ctx);
  c->get();
  api::Io<>::aio_discard(*ictx, c, 0, 256, false, true);

  bool is_owner;
  ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner));
  ASSERT_FALSE(is_owner);
  ASSERT_FALSE(c->is_complete());

  unlock_image();
  ASSERT_EQ(0, c->wait_for_complete());
  c->put();
}

TEST_F(TestInternal, CancelAsyncResize) {
  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));

  C_SaferCond ctx;
  {
    std::unique_lock l{ictx->owner_lock};
    ictx->exclusive_lock->try_acquire_lock(&ctx);
  }

  ASSERT_EQ(0, ctx.wait());
  {
    std::shared_lock owner_locker{ictx->owner_lock};
    ASSERT_TRUE(ictx->exclusive_lock->is_lock_owner());
  }

  uint64_t size;
  ASSERT_EQ(0, librbd::get_size(ictx, &size));

  uint32_t attempts = 0;
  while (attempts++ < 20 && size > 0) {
    C_SaferCond ctx;
    librbd::NoOpProgressContext prog_ctx;

    size -= std::min<uint64_t>(size, 1 << 18);
    {
      std::shared_lock l{ictx->owner_lock};
      ictx->operations->execute_resize(size, true, prog_ctx, &ctx, 0);
    }

    // try to interrupt the in-progress resize
    ictx->cancel_async_requests();

    int r = ctx.wait();
    if (r == -ERESTART) {
      std::cout << "detected canceled async request" << std::endl;
      break;
    }
    ASSERT_EQ(0, r);
  }
}

TEST_F(TestInternal, MultipleResize) {
  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));

  if (ictx->exclusive_lock != nullptr) {
    C_SaferCond ctx;
    {
      std::unique_lock l{ictx->owner_lock};
      ictx->exclusive_lock->try_acquire_lock(&ctx);
    }

    std::shared_lock owner_locker{ictx->owner_lock};
    ASSERT_EQ(0, ctx.wait());
    ASSERT_TRUE(ictx->exclusive_lock->is_lock_owner());
  }

  uint64_t size;
  ASSERT_EQ(0, librbd::get_size(ictx, &size));
  uint64_t original_size = size;

  std::vector<C_SaferCond*> contexts;

  uint32_t attempts = 0;
  librbd::NoOpProgressContext prog_ctx;
  while (size > 0) {
    uint64_t new_size = original_size;
    if (attempts++ % 2 == 0) {
      size -= std::min<uint64_t>(size, 1 << 18);
      new_size = size;
    }

    std::shared_lock l{ictx->owner_lock};
    contexts.push_back(new C_SaferCond());
    ictx->operations->execute_resize(new_size, true, prog_ctx, contexts.back(), 0);
  }

  for (uint32_t i = 0; i < contexts.size(); ++i) {
    ASSERT_EQ(0, contexts[i]->wait());
    delete contexts[i];
  }

  ASSERT_EQ(0, librbd::get_size(ictx, &size));
  ASSERT_EQ(0U, size);
}

TEST_F(TestInternal, Metadata) {
  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);

  map<string, bool> test_confs = boost::assign::map_list_of(
    "aaaaaaa", false)(
    "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", false)(
    "cccccccccccccc", false);
  map<string, bool>::iterator it = test_confs.begin();
  int r;
  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));

  r = ictx->operations->metadata_set(it->first, "value1");
  ASSERT_EQ(0, r);
  ++it;
  r = ictx->operations->metadata_set(it->first, "value2");
  ASSERT_EQ(0, r);
  ++it;
  r = ictx->operations->metadata_set(it->first, "value3");
  ASSERT_EQ(0, r);
  r = ictx->operations->metadata_set("abcd", "value4");
  ASSERT_EQ(0, r);
  r = ictx->operations->metadata_set("xyz", "value5");
  ASSERT_EQ(0, r);
  map<string, bufferlist> pairs;
  r = librbd::metadata_list(ictx, "", 0, &pairs);
  ASSERT_EQ(0, r);

  uint8_t original_pairs_num = 0;
  if (ictx->test_features(RBD_FEATURE_DIRTY_CACHE)) {
    original_pairs_num = 1;
  }
  ASSERT_EQ(original_pairs_num + 5, pairs.size());
  r = ictx->operations->metadata_remove("abcd");
  ASSERT_EQ(0, r);
  r = ictx->operations->metadata_remove("xyz");
  ASSERT_EQ(0, r);
  pairs.clear();
  r = librbd::metadata_list(ictx, "", 0, &pairs);
  ASSERT_EQ(0, r);
  ASSERT_EQ(original_pairs_num + 3, pairs.size());
  string val;
  r = librbd::metadata_get(ictx, it->first, &val);
  ASSERT_EQ(0, r);
  ASSERT_STREQ(val.c_str(), "value3");
}

TEST_F(TestInternal, MetadataConfApply) {
  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));

  ASSERT_EQ(-ENOENT, ictx->operations->metadata_remove("conf_rbd_cache"));

  bool cache = ictx->cache;
  std::string rbd_conf_cache = cache ? "true" : "false";
  std::string new_rbd_conf_cache = !cache ? "true" : "false";

  ASSERT_EQ(0, ictx->operations->metadata_set("conf_rbd_cache",
                                              new_rbd_conf_cache));
  ASSERT_EQ(!cache, ictx->cache);

  ASSERT_EQ(0, ictx->operations->metadata_remove("conf_rbd_cache"));
  ASSERT_EQ(cache, ictx->cache);
}

TEST_F(TestInternal, SnapshotCopyup)
{
  //https://tracker.ceph.com/issues/58263
  // Clone overlap is WIP
  SKIP_IF_CRIMSON();
  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));

  bool sparse_read_supported = is_sparse_read_supported(
      ictx->data_ctx, ictx->get_object_name(10));

  bufferlist bl;
  bl.append(std::string(4096, '1'));
  ASSERT_EQ(4096, api::Io<>::write(*ictx, 0, bl.length(), bufferlist{bl}, 0));
  ASSERT_EQ(4096, api::Io<>::write(*ictx, 4096 * 4, bl.length(), bufferlist{bl},
                                  0));

  ASSERT_EQ(0, snap_create(*ictx, "snap1"));
  ASSERT_EQ(0,
	    ictx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(),
					   "snap1"));

  uint64_t features;
  ASSERT_EQ(0, librbd::get_features(ictx, &features));

  std::string clone_name = get_temp_image_name();
  int order = ictx->order;
  ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx,
			     clone_name.c_str(), features, &order, 0, 0));

  librbd::ImageCtx *ictx2;
  ASSERT_EQ(0, open_image(clone_name, &ictx2));

  ASSERT_EQ(0, snap_create(*ictx2, "snap1"));
  ASSERT_EQ(0, snap_create(*ictx2, "snap2"));

  ASSERT_EQ(4096, api::Io<>::write(*ictx2, 4096, bl.length(), bufferlist{bl},
                                   0));

  ASSERT_EQ(0, flush_writeback_cache(ictx2));
  librados::IoCtx snap_ctx;
  snap_ctx.dup(ictx2->data_ctx);
  snap_ctx.snap_set_read(CEPH_SNAPDIR);

  librados::snap_set_t snap_set;
  ASSERT_EQ(0, snap_ctx.list_snaps(ictx2->get_object_name(0), &snap_set));

  std::vector< std::pair<uint64_t,uint64_t> > expected_overlap =
    boost::assign::list_of(
      std::make_pair(0, 4096))(
      std::make_pair(4096 * 2, 4096 * 3));
  ASSERT_EQ(2U, snap_set.clones.size());
  ASSERT_NE(CEPH_NOSNAP, snap_set.clones[0].cloneid);
  ASSERT_EQ(2U, snap_set.clones[0].snaps.size());
  ASSERT_EQ(expected_overlap, snap_set.clones[0].overlap);
  ASSERT_EQ(CEPH_NOSNAP, snap_set.clones[1].cloneid);

  bufferptr read_ptr(4096);
  bufferlist read_bl;
  read_bl.push_back(read_ptr);

  std::list<std::string> snaps = {"snap1", "snap2", ""};
  librbd::io::ReadResult read_result{&read_bl};
  for (std::list<std::string>::iterator it = snaps.begin();
       it != snaps.end(); ++it) {
    const char *snap_name = it->empty() ? NULL : it->c_str();
    ASSERT_EQ(0, librbd::api::Image<>::snap_set(
                   ictx2, cls::rbd::UserSnapshotNamespace(), snap_name));

    ASSERT_EQ(4096,
              api::Io<>::read(*ictx2, 0, 4096,
                              librbd::io::ReadResult{read_result}, 0));
    ASSERT_TRUE(bl.contents_equal(read_bl));

    ASSERT_EQ(4096,
              api::Io<>::read(*ictx2, 4096 * 4, 4096,
                              librbd::io::ReadResult{read_result}, 0));
    ASSERT_TRUE(bl.contents_equal(read_bl));

    ASSERT_EQ(4096,
              api::Io<>::read(*ictx2, 4096, 4096,
                              librbd::io::ReadResult{read_result}, 0));
    if (snap_name == NULL) {
      ASSERT_TRUE(bl.contents_equal(read_bl));
    } else {
      ASSERT_TRUE(read_bl.is_zero());
    }

    // verify sparseness was preserved
    {
      librados::IoCtx io_ctx;
      io_ctx.dup(m_ioctx);
      librados::Rados rados(io_ctx);
      EXPECT_EQ(0, rados.conf_set("rbd_cache", "false"));
      EXPECT_EQ(0, rados.conf_set("rbd_sparse_read_threshold_bytes", "4096"));
      auto ictx3 = new librbd::ImageCtx(clone_name, "", snap_name, io_ctx,
                                        true);
      ASSERT_EQ(0, ictx3->state->open(0));
      BOOST_SCOPE_EXIT(ictx3) {
        ictx3->state->close();
      } BOOST_SCOPE_EXIT_END;
      std::vector<std::pair<uint64_t, uint64_t>> expected_m;
      bufferlist expected_bl;
      if (ictx3->enable_sparse_copyup && sparse_read_supported) {
        if (snap_name == NULL) {
          expected_m = {{0, 4096 * 2}, {4096 * 4, 4096}};
          expected_bl.append(std::string(4096 * 3, '1'));
        } else {
          expected_m = {{0, 4096}, {4096 * 4, 4096}};
          expected_bl.append(std::string(4096 * 2, '1'));
        }
      } else {
        expected_m = {{0, 4096 * 5}};
        if (snap_name == NULL) {
          expected_bl.append(std::string(4096 * 2, '1'));
          expected_bl.append(std::string(4096 * 2, '\0'));
          expected_bl.append(std::string(4096 * 1, '1'));
        } else {
          expected_bl.append(std::string(4096 * 1, '1'));
          expected_bl.append(std::string(4096 * 3, '\0'));
          expected_bl.append(std::string(4096 * 1, '1'));
        }
      }
      std::vector<std::pair<uint64_t, uint64_t>> read_m;
      librbd::io::ReadResult sparse_read_result{&read_m, &read_bl};
      EXPECT_EQ(4096 * 5,
                api::Io<>::read(*ictx3, 0, 4096 * 5,
                                librbd::io::ReadResult{sparse_read_result}, 0));
      EXPECT_EQ(expected_m, read_m);
      EXPECT_TRUE(expected_bl.contents_equal(read_bl));
    }

    // verify the object map was properly updated
    if ((ictx2->features & RBD_FEATURE_OBJECT_MAP) != 0) {
      uint8_t state = OBJECT_EXISTS;
      if ((ictx2->features & RBD_FEATURE_FAST_DIFF) != 0 &&
          it != snaps.begin() && snap_name != NULL) {
        state = OBJECT_EXISTS_CLEAN;
      }

      librbd::ObjectMap<> *object_map = new librbd::ObjectMap<>(*ictx2, ictx2->snap_id);
      C_SaferCond ctx;
      object_map->open(&ctx);
      ASSERT_EQ(0, ctx.wait());

      std::shared_lock image_locker{ictx2->image_lock};
      ASSERT_EQ(state, (*object_map)[0]);
      object_map->put();
    }
  }
}

TEST_F(TestInternal, SnapshotCopyupZeros)
{
  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));

  // create an empty clone
  ASSERT_EQ(0, snap_create(*ictx, "snap1"));
  ASSERT_EQ(0,
	    ictx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(),
					   "snap1"));

  uint64_t features;
  ASSERT_EQ(0, librbd::get_features(ictx, &features));

  std::string clone_name = get_temp_image_name();
  int order = ictx->order;
  ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx,
			     clone_name.c_str(), features, &order, 0, 0));

  librbd::ImageCtx *ictx2;
  ASSERT_EQ(0, open_image(clone_name, &ictx2));

  ASSERT_EQ(0, snap_create(*ictx2, "snap1"));

  bufferlist bl;
  bl.append(std::string(256, '1'));
  ASSERT_EQ(256, api::Io<>::write(*ictx2, 256, bl.length(), bufferlist{bl}, 0));

  ASSERT_EQ(0, flush_writeback_cache(ictx2));

  librados::IoCtx snap_ctx;
  snap_ctx.dup(ictx2->data_ctx);
  snap_ctx.snap_set_read(CEPH_SNAPDIR);

  librados::snap_set_t snap_set;
  ASSERT_EQ(0, snap_ctx.list_snaps(ictx2->get_object_name(0), &snap_set));

  // verify that snapshot wasn't affected
  ASSERT_EQ(1U, snap_set.clones.size());
  ASSERT_EQ(CEPH_NOSNAP, snap_set.clones[0].cloneid);

  bufferptr read_ptr(256);
  bufferlist read_bl;
  read_bl.push_back(read_ptr);

  std::list<std::string> snaps = {"snap1", ""};
  librbd::io::ReadResult read_result{&read_bl};
  for (std::list<std::string>::iterator it = snaps.begin();
       it != snaps.end(); ++it) {
    const char *snap_name = it->empty() ? NULL : it->c_str();
    ASSERT_EQ(0, librbd::api::Image<>::snap_set(
                   ictx2, cls::rbd::UserSnapshotNamespace(), snap_name));

    ASSERT_EQ(256,
              api::Io<>::read(*ictx2, 0, 256,
                              librbd::io::ReadResult{read_result}, 0));
    ASSERT_TRUE(read_bl.is_zero());

    ASSERT_EQ(256,
              api::Io<>::read(*ictx2, 256, 256,
                              librbd::io::ReadResult{read_result}, 0));
    if (snap_name == NULL) {
      ASSERT_TRUE(bl.contents_equal(read_bl));
    } else {
      ASSERT_TRUE(read_bl.is_zero());
    }

    // verify that only HEAD object map was updated
    if ((ictx2->features & RBD_FEATURE_OBJECT_MAP) != 0) {
      uint8_t state = OBJECT_EXISTS;
      if (snap_name != NULL) {
        state = OBJECT_NONEXISTENT;
      }

      librbd::ObjectMap<> *object_map = new librbd::ObjectMap<>(*ictx2, ictx2->snap_id);
      C_SaferCond ctx;
      object_map->open(&ctx);
      ASSERT_EQ(0, ctx.wait());

      std::shared_lock image_locker{ictx2->image_lock};
      ASSERT_EQ(state, (*object_map)[0]);
      object_map->put();
    }
  }
}

TEST_F(TestInternal, SnapshotCopyupZerosMigration)
{
  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));

  uint64_t features;
  ASSERT_EQ(0, librbd::get_features(ictx, &features));

  close_image(ictx);

  // migrate an empty image
  std::string dst_name = get_temp_image_name();
  librbd::ImageOptions dst_opts;
  dst_opts.set(RBD_IMAGE_OPTION_FEATURES, features);
  ASSERT_EQ(0, librbd::api::Migration<>::prepare(m_ioctx, m_image_name,
                                                 m_ioctx, dst_name,
                                                 dst_opts));

  librbd::ImageCtx *ictx2;
  ASSERT_EQ(0, open_image(dst_name, &ictx2));

  ASSERT_EQ(0, snap_create(*ictx2, "snap1"));

  bufferlist bl;
  bl.append(std::string(256, '1'));
  ASSERT_EQ(256, api::Io<>::write(*ictx2, 256, bl.length(), bufferlist{bl}, 0));

  librados::IoCtx snap_ctx;
  snap_ctx.dup(ictx2->data_ctx);
  snap_ctx.snap_set_read(CEPH_SNAPDIR);

  librados::snap_set_t snap_set;
  ASSERT_EQ(0, snap_ctx.list_snaps(ictx2->get_object_name(0), &snap_set));

  // verify that snapshot wasn't affected
  ASSERT_EQ(1U, snap_set.clones.size());
  ASSERT_EQ(CEPH_NOSNAP, snap_set.clones[0].cloneid);

  bufferptr read_ptr(256);
  bufferlist read_bl;
  read_bl.push_back(read_ptr);

  std::list<std::string> snaps = {"snap1", ""};
  librbd::io::ReadResult read_result{&read_bl};
  for (std::list<std::string>::iterator it = snaps.begin();
       it != snaps.end(); ++it) {
    const char *snap_name = it->empty() ? NULL : it->c_str();
    ASSERT_EQ(0, librbd::api::Image<>::snap_set(
                   ictx2, cls::rbd::UserSnapshotNamespace(), snap_name));

    ASSERT_EQ(256,
              api::Io<>::read(*ictx2, 0, 256,
                              librbd::io::ReadResult{read_result}, 0));
    ASSERT_TRUE(read_bl.is_zero());

    ASSERT_EQ(256,
              api::Io<>::read(*ictx2, 256, 256,
                              librbd::io::ReadResult{read_result}, 0));
    if (snap_name == NULL) {
      ASSERT_TRUE(bl.contents_equal(read_bl));
    } else {
      ASSERT_TRUE(read_bl.is_zero());
    }

    // verify that only HEAD object map was updated
    if ((ictx2->features & RBD_FEATURE_OBJECT_MAP) != 0) {
      uint8_t state = OBJECT_EXISTS;
      if (snap_name != NULL) {
        state = OBJECT_NONEXISTENT;
      }

      librbd::ObjectMap<> *object_map = new librbd::ObjectMap<>(*ictx2, ictx2->snap_id);
      C_SaferCond ctx;
      object_map->open(&ctx);
      ASSERT_EQ(0, ctx.wait());

      std::shared_lock image_locker{ictx2->image_lock};
      ASSERT_EQ(state, (*object_map)[0]);
      object_map->put();
    }
  }
}

TEST_F(TestInternal, ResizeCopyup)
{
  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);

  m_image_name = get_temp_image_name();
  m_image_size = 1 << 14;

  uint64_t features = 0;
  ::get_features(&features);
  int order = 12;
  ASSERT_EQ(0, m_rbd.create2(m_ioctx, m_image_name.c_str(), m_image_size,
                             features, &order));

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));

  bufferlist bl;
  bl.append(std::string(4096, '1'));
  for (size_t i = 0; i < m_image_size; i += bl.length()) {
    ASSERT_EQ((ssize_t)bl.length(),
	      api::Io<>::write(*ictx, i, bl.length(), bufferlist{bl}, 0));
  }

  ASSERT_EQ(0, snap_create(*ictx, "snap1"));
  ASSERT_EQ(0,
	    ictx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(),
					   "snap1"));

  std::string clone_name = get_temp_image_name();
  ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx,
			     clone_name.c_str(), features, &order, 0, 0));

  librbd::ImageCtx *ictx2;
  ASSERT_EQ(0, open_image(clone_name, &ictx2));
  ASSERT_EQ(0, snap_create(*ictx2, "snap1"));

  bufferptr read_ptr(bl.length());
  bufferlist read_bl;
  read_bl.push_back(read_ptr);

  // verify full / partial object removal properly copyup
  librbd::NoOpProgressContext no_op;
  ASSERT_EQ(0, ictx2->operations->resize(m_image_size - (1 << order) - 32,
                                         true, no_op));
  ASSERT_EQ(0, ictx2->operations->resize(m_image_size - (2 << order) - 32,
                                         true, no_op));
  ASSERT_EQ(0, librbd::api::Image<>::snap_set(ictx2,
				              cls::rbd::UserSnapshotNamespace(),
				              "snap1"));

  {
    // hide the parent from the snapshot
    std::unique_lock image_locker{ictx2->image_lock};
    ictx2->snap_info.begin()->second.parent = librbd::ParentImageInfo();
  }

  librbd::io::ReadResult read_result{&read_bl};
  for (size_t i = 2 << order; i < m_image_size; i += bl.length()) {
    ASSERT_EQ((ssize_t)bl.length(),
              api::Io<>::read(*ictx2, i, bl.length(),
                              librbd::io::ReadResult{read_result}, 0));
    ASSERT_TRUE(bl.contents_equal(read_bl));
  }
}

TEST_F(TestInternal, DiscardCopyup)
{
  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);

  CephContext* cct = reinterpret_cast<CephContext*>(_rados.cct());
  REQUIRE(!cct->_conf.get_val<bool>("rbd_skip_partial_discard"));

  m_image_name = get_temp_image_name();
  m_image_size = 1 << 14;

  uint64_t features = 0;
  ::get_features(&features);
  int order = 12;
  ASSERT_EQ(0, m_rbd.create2(m_ioctx, m_image_name.c_str(), m_image_size,
                             features, &order));

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));

  bufferlist bl;
  bl.append(std::string(4096, '1'));
  for (size_t i = 0; i < m_image_size; i += bl.length()) {
    ASSERT_EQ((ssize_t)bl.length(),
	      api::Io<>::write(*ictx, i, bl.length(), bufferlist{bl}, 0));
  }

  ASSERT_EQ(0, snap_create(*ictx, "snap1"));
  ASSERT_EQ(0,
	    ictx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(),
					   "snap1"));

  std::string clone_name = get_temp_image_name();
  ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx,
			     clone_name.c_str(), features, &order, 0, 0));

  librbd::ImageCtx *ictx2;
  ASSERT_EQ(0, open_image(clone_name, &ictx2));

  ASSERT_EQ(0, snap_create(*ictx2, "snap1"));

  bufferptr read_ptr(bl.length());
  bufferlist read_bl;
  read_bl.push_back(read_ptr);

  ASSERT_EQ(static_cast<int>(m_image_size - 64),
            api::Io<>::discard(*ictx2, 32, m_image_size - 64, false));
  ASSERT_EQ(0, librbd::api::Image<>::snap_set(ictx2,
				              cls::rbd::UserSnapshotNamespace(),
				              "snap1"));

  {
    // hide the parent from the snapshot
    std::unique_lock image_locker{ictx2->image_lock};
    ictx2->snap_info.begin()->second.parent = librbd::ParentImageInfo();
  }

  librbd::io::ReadResult read_result{&read_bl};
  for (size_t i = 0; i < m_image_size; i += bl.length()) {
    ASSERT_EQ((ssize_t)bl.length(),
              api::Io<>::read(*ictx2, i, bl.length(),
                              librbd::io::ReadResult{read_result}, 0));
    ASSERT_TRUE(bl.contents_equal(read_bl));
  }
}

TEST_F(TestInternal, SparseReadParent)
{
  // https://tracker.ceph.com/issues/72727
  SKIP_IF_CRIMSON();
  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);

  // all reads should go to the OSDs
  auto cct = reinterpret_cast<CephContext*>(_rados.cct());
  REQUIRE(!cct->_conf.get_val<bool>("rbd_cache") ||
          cct->_conf.get_val<std::string>("rbd_cache_policy") == "writearound");
  REQUIRE(!cct->_conf.get_val<bool>("rbd_parent_cache_enabled"));
  REQUIRE(!is_rbd_pwl_enabled(cct));

  // clone should remain empty for the entire duration of the test
  REQUIRE(!cct->_conf.get_val<bool>("rbd_clone_copy_on_read"));

  uint64_t features;
  ASSERT_TRUE(::get_features(&features));

  std::string parent_name = get_temp_image_name();
  int order = 15;
  ASSERT_EQ(0, m_rbd.create2(m_ioctx, parent_name.c_str(), 4096 * 16, features,
                             &order));

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(parent_name, &ictx));
  ASSERT_EQ(0, ictx->operations->metadata_set(
      "conf_rbd_sparse_read_threshold_bytes", "4096"));

  bool sparse_read_supported = is_sparse_read_supported(
      ictx->data_ctx, ictx->get_object_name(10));

  bufferlist bl1;
  bl1.append(std::string(4096, '1'));
  ASSERT_EQ(4096, api::Io<>::write(*ictx, 4096 * 10, 4096, std::move(bl1), 0));
  bufferlist bl2;
  bl2.append(std::string(4096 * 2, '2'));
  ASSERT_EQ(4096 * 2, api::Io<>::write(*ictx, 4096 * 12, 4096 * 2,
                                       std::move(bl2), 0));

  ASSERT_EQ(0, snap_create(*ictx, "snap1"));
  ASSERT_EQ(0, snap_protect(*ictx, "snap1"));

  std::string clone_name = get_temp_image_name();
  ASSERT_EQ(0, librbd::clone(m_ioctx, parent_name.c_str(), "snap1", m_ioctx,
			     clone_name.c_str(), features, &order, 0, 0));

  close_image(ictx);
  ASSERT_EQ(0, open_image(clone_name, &ictx));

  std::vector<std::pair<uint64_t, uint64_t>> read_m;
  bufferlist read_bl;
  librbd::io::ReadResult sparse_read_result{&read_m, &read_bl};

  // 0. read entire clone at once
  {
    std::vector<std::pair<uint64_t, uint64_t>> expected_m;
    bufferlist expected_bl;
    if (sparse_read_supported) {
      expected_m = {{4096 * 10, 4096}, {4096 * 12, 4096 * 2}};
      expected_bl.append(std::string(4096, '1'));
      expected_bl.append(std::string(4096 * 2, '2'));
    } else {
      expected_m = {{4096 * 8, 4096 * 6}};
      expected_bl.append(std::string(4096 * 2, '\0'));
      expected_bl.append(std::string(4096, '1'));
      expected_bl.append(std::string(4096, '\0'));
      expected_bl.append(std::string(4096 * 2, '2'));
    }

    ASSERT_EQ(4096 * 16,
              api::Io<>::read(*ictx, 0, 4096 * 16,
                              librbd::io::ReadResult{sparse_read_result}, 0));
    ASSERT_EQ(expected_m, read_m);
    ASSERT_TRUE(expected_bl.contents_equal(read_bl));
  }

  // 1. parent object DNE
  {
    std::vector<std::pair<uint64_t, uint64_t>> expected_m;
    bufferlist expected_bl;

    ASSERT_EQ(4096 * 6,
              api::Io<>::read(*ictx, 4096, 4096 * 6,
                              librbd::io::ReadResult{sparse_read_result}, 0));
    ASSERT_EQ(expected_m, read_m);
    ASSERT_TRUE(expected_bl.contents_equal(read_bl));
  }

  // 2. parent object has two extents
  {
    std::vector<std::pair<uint64_t, uint64_t>> expected_m;
    bufferlist expected_bl;
    if (sparse_read_supported) {
      expected_m = {{4096 * 10, 4096}, {4096 * 12, 4096 * 2}};
      expected_bl.append(std::string(4096, '1'));
      expected_bl.append(std::string(4096 * 2, '2'));
    } else {
      expected_m = {{4096 * 9, 4096 * 5}};
      expected_bl.append(std::string(4096, '\0'));
      expected_bl.append(std::string(4096, '1'));
      expected_bl.append(std::string(4096, '\0'));
      expected_bl.append(std::string(4096 * 2, '2'));
    }

    ASSERT_EQ(4096 * 6,
              api::Io<>::read(*ictx, 4096 * 9, 4096 * 6,
                              librbd::io::ReadResult{sparse_read_result}, 0));
    ASSERT_EQ(expected_m, read_m);
    ASSERT_TRUE(expected_bl.contents_equal(read_bl));
  }

  // 3. parent object has two extents but the second extent is
  //    partially beyond parent overlap
  ASSERT_EQ(0, librbd::cls_client::set_size(&m_ioctx, ictx->header_oid,
                                            4096 * 13));
  ASSERT_EQ(0, librbd::cls_client::set_size(&m_ioctx, ictx->header_oid,
                                            4096 * 16));
  ASSERT_EQ(0, ictx->state->refresh());
  {
    std::vector<std::pair<uint64_t, uint64_t>> expected_m;
    bufferlist expected_bl;
    if (sparse_read_supported) {
      expected_m = {{4096 * 10, 4096}, {4096 * 12, 4096}};
      expected_bl.append(std::string(4096, '1'));
      expected_bl.append(std::string(4096, '2'));
    } else {
      expected_m = {{4096 * 9, 4096 * 4}};
      expected_bl.append(std::string(4096, '\0'));
      expected_bl.append(std::string(4096, '1'));
      expected_bl.append(std::string(4096, '\0'));
      expected_bl.append(std::string(4096, '2'));
    }

    ASSERT_EQ(4096 * 6,
              api::Io<>::read(*ictx, 4096 * 9, 4096 * 6,
                              librbd::io::ReadResult{sparse_read_result}, 0));
    ASSERT_EQ(expected_m, read_m);
    ASSERT_TRUE(expected_bl.contents_equal(read_bl));
  }

  // 4. parent object has two extents but the second extent is
  //    completely beyond parent overlap
  ASSERT_EQ(0, librbd::cls_client::set_size(&m_ioctx, ictx->header_oid,
                                            4096 * 12));
  ASSERT_EQ(0, librbd::cls_client::set_size(&m_ioctx, ictx->header_oid,
                                            4096 * 16));
  ASSERT_EQ(0, ictx->state->refresh());
  {
    std::vector<std::pair<uint64_t, uint64_t>> expected_m;
    bufferlist expected_bl;
    if (sparse_read_supported) {
      expected_m = {{4096 * 10, 4096}};
      expected_bl.append(std::string(4096, '1'));
    } else {
      expected_m = {{4096 * 9, 4096 * 3}};
      expected_bl.append(std::string(4096, '\0'));
      expected_bl.append(std::string(4096, '1'));
      expected_bl.append(std::string(4096, '\0'));
    }

    ASSERT_EQ(4096 * 6,
              api::Io<>::read(*ictx, 4096 * 9, 4096 * 6,
                              librbd::io::ReadResult{sparse_read_result}, 0));
    ASSERT_EQ(expected_m, read_m);
    ASSERT_TRUE(expected_bl.contents_equal(read_bl));
  }

  // 5. parent object has two extents but both extents are beyond
  //    parent overlap
  ASSERT_EQ(0, librbd::cls_client::set_size(&m_ioctx, ictx->header_oid,
                                            4096 * 10));
  ASSERT_EQ(0, librbd::cls_client::set_size(&m_ioctx, ictx->header_oid,
                                            4096 * 16));
  ASSERT_EQ(0, ictx->state->refresh());
  {
    std::vector<std::pair<uint64_t, uint64_t>> expected_m;
    bufferlist expected_bl;
    if (!sparse_read_supported) {
      expected_m = {{4096 * 9, 4096}};
      expected_bl.append(std::string(4096, '\0'));
    }

    ASSERT_EQ(4096 * 6,
              api::Io<>::read(*ictx, 4096 * 9, 4096 * 6,
                              librbd::io::ReadResult{sparse_read_result}, 0));
    ASSERT_EQ(expected_m, read_m);
    ASSERT_TRUE(expected_bl.contents_equal(read_bl));
  }

  // 6. parent object is beyond parent overlap
  ASSERT_EQ(0, librbd::cls_client::set_size(&m_ioctx, ictx->header_oid,
                                            4096 * 8));
  ASSERT_EQ(0, librbd::cls_client::set_size(&m_ioctx, ictx->header_oid,
                                            4096 * 16));
  ASSERT_EQ(0, ictx->state->refresh());
  {
    std::vector<std::pair<uint64_t, uint64_t>> expected_m;
    bufferlist expected_bl;

    ASSERT_EQ(4096 * 6,
              api::Io<>::read(*ictx, 4096 * 9, 4096 * 6,
                              librbd::io::ReadResult{sparse_read_result}, 0));
    ASSERT_EQ(expected_m, read_m);
    ASSERT_TRUE(expected_bl.contents_equal(read_bl));
  }
}

TEST_F(TestInternal, SparseReadParentMultipleObjectExtents)
{
  // https://tracker.ceph.com/issues/72727
  SKIP_IF_CRIMSON();
  REQUIRE_FEATURE(RBD_FEATURE_LAYERING | RBD_FEATURE_STRIPINGV2);

  // all reads should go to the OSDs
  auto cct = reinterpret_cast<CephContext*>(_rados.cct());
  REQUIRE(!cct->_conf.get_val<bool>("rbd_cache") ||
          cct->_conf.get_val<std::string>("rbd_cache_policy") == "writearound");
  REQUIRE(!cct->_conf.get_val<bool>("rbd_parent_cache_enabled"));
  REQUIRE(!is_rbd_pwl_enabled(cct));

  // clone should remain empty for the entire duration of the test
  REQUIRE(!cct->_conf.get_val<bool>("rbd_clone_copy_on_read"));

  uint64_t features;
  ASSERT_TRUE(::get_features(&features));

  std::string parent_name = get_temp_image_name();
  int order = 17;
  ASSERT_EQ(0, m_rbd.create2(m_ioctx, parent_name.c_str(), 4096 * 64, features,
                             &order));

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(parent_name, &ictx));
  ASSERT_EQ(0, ictx->operations->metadata_set(
      "conf_rbd_sparse_read_threshold_bytes", "4096"));

  bool sparse_read_supported = is_sparse_read_supported(
      ictx->data_ctx, ictx->get_object_name(10));

  // parent obj1: ... .1 1. .2 .. 33 3. .. 4
  // clone obj0:  ... 1. .. 3. 4
  // clone obj1:  ... .1 .2 33

  bufferlist bl1;
  bl1.append(std::string(4096 * 2, '1'));
  ASSERT_EQ(4096 * 2, api::Io<>::write(*ictx, 4096 * 39, 4096 * 2,
                                       std::move(bl1), 0));
  bufferlist bl2;
  bl2.append(std::string(4096, '2'));
  ASSERT_EQ(4096, api::Io<>::write(*ictx, 4096 * 43, 4096, std::move(bl2), 0));
  bufferlist bl3;
  bl3.append(std::string(4096 * 3, '3'));
  ASSERT_EQ(4096 * 3, api::Io<>::write(*ictx, 4096 * 46, 4096 * 3,
                                       std::move(bl3), 0));
  bufferlist bl4;
  bl4.append(std::string(4096, '4'));
  ASSERT_EQ(4096, api::Io<>::write(*ictx, 4096 * 52, 4096, std::move(bl4), 0));

  ASSERT_EQ(0, snap_create(*ictx, "snap1"));
  ASSERT_EQ(0, snap_protect(*ictx, "snap1"));

  std::string mid_clone_name = get_temp_image_name();
  ASSERT_EQ(0, librbd::clone(m_ioctx, parent_name.c_str(), "snap1", m_ioctx,
			     mid_clone_name.c_str(), features, &order, 0, 0));

  close_image(ictx);
  ASSERT_EQ(0, open_image(mid_clone_name, &ictx));

  ASSERT_EQ(0, snap_create(*ictx, "snap1"));
  ASSERT_EQ(0, snap_protect(*ictx, "snap1"));

  std::string top_clone_name = get_temp_image_name();
  ASSERT_EQ(0, librbd::clone(m_ioctx, mid_clone_name.c_str(), "snap1", m_ioctx,
			     top_clone_name.c_str(), features, &order,
                             4096 * 2, 2));

  close_image(ictx);
  ASSERT_EQ(0, open_image(top_clone_name, &ictx));

  std::vector<std::pair<uint64_t, uint64_t>> expected_m;
  bufferlist expected_bl;
  if (sparse_read_supported) {
    expected_m = {{4096 * 39, 4096}, {4096 * 40, 4096},
                  {4096 * 43, 4096}, {4096 * 46, 4096 * 2},
                  {4096 * 48, 4096}, {4096 * 52, 4096}};
    expected_bl.append(std::string(4096 * 2, '1'));
    expected_bl.append(std::string(4096, '2'));
    expected_bl.append(std::string(4096 * 3, '3'));
    expected_bl.append(std::string(4096, '4'));
  } else {
    expected_m = {{4096 * 32, 4096 * 2}, {4096 * 34, 4096 * 2},
                  {4096 * 36, 4096 * 2}, {4096 * 38, 4096 * 2},
                  {4096 * 40, 4096 * 2}, {4096 * 42, 4096 * 2},
                  {4096 * 44, 4096 * 2}, {4096 * 46, 4096 * 2},
                  {4096 * 48, 4096 * 2}, {4096 * 50, 4096 * 2},
                  {4096 * 52, 4096}};
    expected_bl.append(std::string(4096 * 7, '\0'));
    expected_bl.append(std::string(4096 * 2, '1'));
    expected_bl.append(std::string(4096 * 2, '\0'));
    expected_bl.append(std::string(4096, '2'));
    expected_bl.append(std::string(4096 * 2, '\0'));
    expected_bl.append(std::string(4096 * 3, '3'));
    expected_bl.append(std::string(4096 * 3, '\0'));
    expected_bl.append(std::string(4096, '4'));
  }

  std::vector<std::pair<uint64_t, uint64_t>> read_m;
  bufferlist read_bl;
  ASSERT_EQ(4096 * 64,
            api::Io<>::read(*ictx, 0, 4096 * 64,
                            librbd::io::ReadResult{&read_m, &read_bl}, 0));
  ASSERT_EQ(expected_m, read_m);
  ASSERT_TRUE(expected_bl.contents_equal(read_bl));
}

TEST_F(TestInternal, SparseReadParentImportOnlyMigration)
{
  // https://tracker.ceph.com/issues/72727
  SKIP_IF_CRIMSON();
  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);

  // all reads should go to the OSDs
  auto cct = reinterpret_cast<CephContext*>(_rados.cct());
  REQUIRE(!cct->_conf.get_val<bool>("rbd_cache") ||
          cct->_conf.get_val<std::string>("rbd_cache_policy") == "writearound");
  REQUIRE(!cct->_conf.get_val<bool>("rbd_parent_cache_enabled"));
  REQUIRE(!is_rbd_pwl_enabled(cct));

  // clone should remain empty for the entire duration of the test
  REQUIRE(!cct->_conf.get_val<bool>("rbd_clone_copy_on_read"));

  uint64_t features;
  ASSERT_TRUE(::get_features(&features));

  std::string parent_name = get_temp_image_name();
  int order = 22;
  ASSERT_EQ(0, m_rbd.create2(m_ioctx, parent_name.c_str(), 32 << 20, features,
                             &order));

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(parent_name, &ictx));
  ASSERT_EQ(0, ictx->operations->metadata_set(
      "conf_rbd_sparse_read_threshold_bytes", "4096"));

  bool sparse_read_supported = is_sparse_read_supported(
      ictx->data_ctx, ictx->get_object_name(10));

  bufferlist bl1;
  bl1.append(std::string((4 << 20) + (4 << 10), '1'));
  ASSERT_EQ((4 << 20) + (4 << 10),
            api::Io<>::write(*ictx, 4 << 20, (4 << 20) + (4 << 10),
                             std::move(bl1), 0));
  bufferlist bl2;
  bl2.append(std::string(16 << 10, '2'));
  ASSERT_EQ(16 << 10, api::Io<>::write(*ictx, 10 << 20, 16 << 10,
                                       std::move(bl2), 0));
  bufferlist bl3;
  bl3.append(std::string(24 << 10, '3'));
  ASSERT_EQ(24 << 10, api::Io<>::write(*ictx, (12 << 20) - (4 << 10), 24 << 10,
                                       std::move(bl3), 0));
  bufferlist bl4;
  bl4.append(std::string(8 << 10, '4'));
  ASSERT_EQ(8 << 10, api::Io<>::write(*ictx, (24 << 20) - (8 << 10), 8 << 10,
                                      std::move(bl4), 0));
  bufferlist bl5;
  bl5.append(std::string(4 << 10, '5'));
  ASSERT_EQ(4 << 10, api::Io<>::write(*ictx, 25 << 20, 4 << 10,
                                      std::move(bl5), 0));
  bufferlist bl6;
  bl6.append(std::string(12 << 10, '6'));
  ASSERT_EQ(12 << 10, api::Io<>::write(*ictx, 27 << 20, 12 << 10,
                                       std::move(bl6), 0));

  ASSERT_EQ(0, snap_create(*ictx, "snap1"));
  ASSERT_EQ(0, snap_protect(*ictx, "snap1"));

  std::string clone_name = get_temp_image_name();
  ASSERT_EQ(0, librbd::clone(m_ioctx, parent_name.c_str(), "snap1", m_ioctx,
			     clone_name.c_str(), features, &order, 0, 0));

  close_image(ictx);
  ASSERT_EQ(0, open_image(clone_name, &ictx));

  std::vector<std::pair<uint64_t, uint64_t>> read_m;
  bufferlist read_bl;
  librbd::io::ReadResult sparse_read_result{&read_m, &read_bl};

  std::vector<std::pair<uint64_t, uint64_t>> expected_m;
  bufferlist expected_bl;
  if (sparse_read_supported) {
    expected_m = {{4 << 20, 4 << 20}, {8 << 20, 4 << 10},
                  {10 << 20, 16 << 10}, {(12 << 20) - (4 << 10), 4 << 10},
                  {12 << 20, 20 << 10}, {(24 << 20) - (8 << 10), 8 << 10},
                  {25 << 20, 4 << 10}, {27 << 20, 12 << 10}};
    expected_bl.append(std::string((4 << 20) + (4 << 10), '1'));
    expected_bl.append(std::string(16 << 10, '2'));
    expected_bl.append(std::string(24 << 10, '3'));
    expected_bl.append(std::string(8 << 10, '4'));
    expected_bl.append(std::string(4 << 10, '5'));
    expected_bl.append(std::string(12 << 10, '6'));
  } else {
    expected_m = {{4 << 20, 4 << 20}, {8 << 20, 4 << 20},
                  {12 << 20, 20 << 10}, {20 << 20, 4 << 20},
                  {24 << 20, (3 << 20) + (12 << 10)}};
    expected_bl.append(std::string((4 << 20) + (4 << 10), '1'));
    expected_bl.append(std::string((2 << 20) - (4 << 10), '\0'));
    expected_bl.append(std::string(16 << 10, '2'));
    expected_bl.append(std::string((2 << 20) - (20 << 10), '\0'));
    expected_bl.append(std::string(24 << 10, '3'));
    expected_bl.append(std::string((4 << 20) - (8 << 10), '\0'));
    expected_bl.append(std::string(8 << 10, '4'));
    expected_bl.append(std::string(1 << 20, '\0'));
    expected_bl.append(std::string(4 << 10, '5'));
    expected_bl.append(std::string((2 << 20) - (4 << 10), '\0'));
    expected_bl.append(std::string(12 << 10, '6'));
  }

  ASSERT_EQ(32 << 20,
            api::Io<>::read(*ictx, 0, 32 << 20,
                            librbd::io::ReadResult{sparse_read_result}, 0));
  ASSERT_EQ(expected_m, read_m);
  ASSERT_TRUE(expected_bl.contents_equal(read_bl));

  ASSERT_EQ(0, snap_create(*ictx, "snap1"));
  ASSERT_EQ(0, snap_protect(*ictx, "snap1"));

  std::string dst_name = get_temp_image_name();
  std::string source_spec = R"({)"
      R"("type": "native", )"
      R"("pool_name": ")" + m_ioctx.get_pool_name() + R"(", )"
      R"("image_name": ")" + clone_name + R"(", )"
      R"("snap_name": "snap1"})";
  librbd::ImageOptions dst_opts;
  dst_opts.set(RBD_IMAGE_OPTION_FEATURES, features);
  ASSERT_EQ(0, api::Migration<>::prepare_import(source_spec.c_str(), m_ioctx,
                                                dst_name.c_str(), dst_opts));

  close_image(ictx);
  ASSERT_EQ(0, open_image(dst_name, &ictx));

  ASSERT_EQ(32 << 20,
            api::Io<>::read(*ictx, 0, 32 << 20,
                            librbd::io::ReadResult{sparse_read_result}, 0));
  ASSERT_EQ(expected_m, read_m);
  ASSERT_TRUE(expected_bl.contents_equal(read_bl));

  librbd::NoOpProgressContext no_op;
  ASSERT_EQ(0, api::Migration<>::execute(m_ioctx, dst_name.c_str(), no_op));
  ASSERT_EQ(0, api::Migration<>::commit(m_ioctx, dst_name.c_str(), no_op));

  ASSERT_EQ(32 << 20,
            api::Io<>::read(*ictx, 0, 32 << 20,
                            librbd::io::ReadResult{sparse_read_result}, 0));
  ASSERT_EQ(expected_m, read_m);
  ASSERT_TRUE(expected_bl.contents_equal(read_bl));
}

TEST_F(TestInternal, ImageOptions) {
  rbd_image_options_t opts1 = NULL, opts2 = NULL;
  uint64_t uint64_val1 = 10, uint64_val2 = 0;
  std::string string_val1;

  librbd::image_options_create(&opts1);
  ASSERT_NE((rbd_image_options_t)NULL, opts1);
  ASSERT_TRUE(librbd::image_options_is_empty(opts1));

  ASSERT_EQ(-EINVAL, librbd::image_options_get(opts1, RBD_IMAGE_OPTION_FEATURES,
	  &string_val1));
  ASSERT_EQ(-ENOENT, librbd::image_options_get(opts1, RBD_IMAGE_OPTION_FEATURES,
	  &uint64_val1));

  ASSERT_EQ(-EINVAL, librbd::image_options_set(opts1, RBD_IMAGE_OPTION_FEATURES,
	  string_val1));

  ASSERT_EQ(0, librbd::image_options_set(opts1, RBD_IMAGE_OPTION_FEATURES,
	  uint64_val1));
  ASSERT_FALSE(librbd::image_options_is_empty(opts1));
  ASSERT_EQ(0, librbd::image_options_get(opts1, RBD_IMAGE_OPTION_FEATURES,
	  &uint64_val2));
  ASSERT_EQ(uint64_val1, uint64_val2);

  librbd::image_options_create_ref(&opts2, opts1);
  ASSERT_NE((rbd_image_options_t)NULL, opts2);
  ASSERT_FALSE(librbd::image_options_is_empty(opts2));

  uint64_val2 = 0;
  ASSERT_NE(uint64_val1, uint64_val2);
  ASSERT_EQ(0, librbd::image_options_get(opts2, RBD_IMAGE_OPTION_FEATURES,
	  &uint64_val2));
  ASSERT_EQ(uint64_val1, uint64_val2);

  uint64_val2++;
  ASSERT_NE(uint64_val1, uint64_val2);
  ASSERT_EQ(-ENOENT, librbd::image_options_get(opts1, RBD_IMAGE_OPTION_ORDER,
	  &uint64_val1));
  ASSERT_EQ(-ENOENT, librbd::image_options_get(opts2, RBD_IMAGE_OPTION_ORDER,
	  &uint64_val2));
  ASSERT_EQ(0, librbd::image_options_set(opts2, RBD_IMAGE_OPTION_ORDER,
	  uint64_val2));
  ASSERT_EQ(0, librbd::image_options_get(opts1, RBD_IMAGE_OPTION_ORDER,
	  &uint64_val1));
  ASSERT_EQ(0, librbd::image_options_get(opts2, RBD_IMAGE_OPTION_ORDER,
	  &uint64_val2));
  ASSERT_EQ(uint64_val1, uint64_val2);

  librbd::image_options_destroy(opts1);

  uint64_val2++;
  ASSERT_NE(uint64_val1, uint64_val2);
  ASSERT_EQ(0, librbd::image_options_get(opts2, RBD_IMAGE_OPTION_ORDER,
	  &uint64_val2));
  ASSERT_EQ(uint64_val1, uint64_val2);

  ASSERT_EQ(0, librbd::image_options_unset(opts2, RBD_IMAGE_OPTION_ORDER));
  ASSERT_EQ(-ENOENT, librbd::image_options_unset(opts2, RBD_IMAGE_OPTION_ORDER));

  librbd::image_options_clear(opts2);
  ASSERT_EQ(-ENOENT, librbd::image_options_get(opts2, RBD_IMAGE_OPTION_FEATURES,
	  &uint64_val2));
  ASSERT_TRUE(librbd::image_options_is_empty(opts2));

  librbd::image_options_destroy(opts2);
}

TEST_F(TestInternal, WriteFullCopyup) {
  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));

  librbd::NoOpProgressContext no_op;
  ASSERT_EQ(0, ictx->operations->resize(1 << ictx->order, true, no_op));

  bufferlist bl;
  bl.append(std::string(1 << ictx->order, '1'));
  ASSERT_EQ((ssize_t)bl.length(),
            api::Io<>::write(*ictx, 0, bl.length(), bufferlist{bl}, 0));
  ASSERT_EQ(0, flush_writeback_cache(ictx));

  ASSERT_EQ(0, create_snapshot("snap1", true));

  std::string clone_name = get_temp_image_name();
  int order = ictx->order;
  ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx,
                             clone_name.c_str(), ictx->features, &order, 0, 0));

  TestInternal *parent = this;
  librbd::ImageCtx *ictx2 = NULL;
  BOOST_SCOPE_EXIT( (&m_ioctx) (clone_name) (parent) (&ictx2) ) {
    if (ictx2 != NULL) {
      ictx2->operations->snap_remove(cls::rbd::UserSnapshotNamespace(),
				     "snap1");
      parent->close_image(ictx2);
    }

    librbd::NoOpProgressContext remove_no_op;
    ASSERT_EQ(0, librbd::api::Image<>::remove(m_ioctx, clone_name,
                                              remove_no_op));
  } BOOST_SCOPE_EXIT_END;

  ASSERT_EQ(0, open_image(clone_name, &ictx2));
  ASSERT_EQ(0, ictx2->operations->snap_create(cls::rbd::UserSnapshotNamespace(),
					      "snap1", 0, no_op));

  bufferlist write_full_bl;
  write_full_bl.append(std::string(1 << ictx2->order, '2'));
  ASSERT_EQ((ssize_t)write_full_bl.length(),
            api::Io<>::write(*ictx2, 0, write_full_bl.length(),
                             bufferlist{write_full_bl}, 0));

  ASSERT_EQ(0, ictx2->operations->flatten(no_op));

  bufferptr read_ptr(bl.length());
  bufferlist read_bl;
  read_bl.push_back(read_ptr);

  librbd::io::ReadResult read_result{&read_bl};
  ASSERT_EQ((ssize_t)read_bl.length(),
            api::Io<>::read(*ictx2, 0, read_bl.length(),
                            librbd::io::ReadResult{read_result}, 0));
  ASSERT_TRUE(write_full_bl.contents_equal(read_bl));

  ASSERT_EQ(0, librbd::api::Image<>::snap_set(ictx2,
				              cls::rbd::UserSnapshotNamespace(),
				              "snap1"));
  ASSERT_EQ((ssize_t)read_bl.length(),
            api::Io<>::read(*ictx2, 0, read_bl.length(),
                            librbd::io::ReadResult{read_result}, 0));
  ASSERT_TRUE(bl.contents_equal(read_bl));
}

static int iterate_cb(uint64_t off, size_t len, int exists, void *arg)
{
  interval_set<uint64_t> *diff = static_cast<interval_set<uint64_t> *>(arg);
  diff->insert(off, len);
  return 0;
}

TEST_F(TestInternal, DiffIterateCloneOverwrite) {
  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);

  librbd::RBD rbd;
  librbd::Image image;
  uint64_t size = 20 << 20;
  int order = 0;

  ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL));

  bufferlist bl;
  bl.append(std::string(4096, '1'));
  ASSERT_EQ(4096, image.write(0, 4096, bl));

  interval_set<uint64_t> one;
  ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, false, false, iterate_cb,
                                   (void *)&one));
  ASSERT_EQ(0, image.snap_create("one"));
  ASSERT_EQ(0, image.snap_protect("one"));

  std::string clone_name = this->get_temp_image_name();
  ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx,
                         clone_name.c_str(), RBD_FEATURE_LAYERING, &order));

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(clone_name, &ictx));
  ASSERT_EQ(0, snap_create(*ictx, "one"));
  ASSERT_EQ(0,
	    ictx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(),
					   "one"));

  // Simulate a client that doesn't support deep flatten (old librbd / krbd)
  // which will copy up the full object from the parent
  std::string oid = ictx->object_prefix + ".0000000000000000";
  librados::IoCtx io_ctx;
  io_ctx.dup(m_ioctx);
  io_ctx.selfmanaged_snap_set_write_ctx(ictx->snapc.seq, ictx->snaps);
  ASSERT_EQ(0, io_ctx.write(oid, bl, 4096, 4096));

  interval_set<uint64_t> diff;
  ASSERT_EQ(0, librbd::api::Image<>::snap_set(ictx,
                                              cls::rbd::UserSnapshotNamespace(),
                                              "one"));
  ASSERT_EQ(0, librbd::api::DiffIterate<>::diff_iterate(
    ictx, 0, 0, size, true, false, iterate_cb, (void *)&diff));
  ASSERT_EQ(one, diff);
}

TEST_F(TestInternal, TestCoR)
{
  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);

  std::string config_value;
  ASSERT_EQ(0, _rados.conf_get("rbd_clone_copy_on_read", config_value));
  if (config_value == "false") {
    GTEST_SKIP() << "Skipping due to disabled copy-on-read";
  }

  m_image_name = get_temp_image_name();
  m_image_size = 4 << 20;

  int order = 12; // smallest object size is 4K
  uint64_t features;
  ASSERT_TRUE(::get_features(&features));

  ASSERT_EQ(0, create_image_full_pp(m_rbd, m_ioctx, m_image_name, m_image_size,
                                    features, false, &order));

  librbd::Image image;
  ASSERT_EQ(0, m_rbd.open(m_ioctx, image, m_image_name.c_str(), NULL));

  librbd::image_info_t info;
  ASSERT_EQ(0, image.stat(info, sizeof(info)));

  const int object_num = info.size / info.obj_size;
  printf("made parent image \"%s\": %ldK (%d * %" PRIu64 "K)\n", m_image_name.c_str(),
         (unsigned long)m_image_size, object_num, info.obj_size/1024);

  // write something into parent
  char test_data[TEST_IO_SIZE + 1];
  for (int i = 0; i < TEST_IO_SIZE; ++i) {
    test_data[i] = (char) (rand() % (126 - 33) + 33);
  }
  test_data[TEST_IO_SIZE] = '\0';

  // generate a random map which covers every objects with random
  // offset
  map<uint64_t, uint64_t> write_tracker;
  generate_random_iomap(image, object_num, info.obj_size, 100, write_tracker);

  printf("generated random write map:\n");
  for (map<uint64_t, uint64_t>::iterator itr = write_tracker.begin();
       itr != write_tracker.end(); ++itr)
    printf("\t [%-8lu, %-8lu]\n",
           (unsigned long)itr->first, (unsigned long)itr->second);

  bufferlist bl;
  bl.append(test_data, TEST_IO_SIZE);

  printf("write data based on random map\n");
  for (map<uint64_t, uint64_t>::iterator itr = write_tracker.begin();
       itr != write_tracker.end(); ++itr) {
    printf("\twrite object-%-4lu\t\n", (unsigned long)itr->first);
    ASSERT_EQ(TEST_IO_SIZE, image.write(itr->second, TEST_IO_SIZE, bl));
  }

  ASSERT_EQ(0, image.flush());

  bufferlist readbl;
  printf("verify written data by reading\n");
  {
    map<uint64_t, uint64_t>::iterator itr = write_tracker.begin();
    printf("\tread object-%-4lu\n", (unsigned long)itr->first);
    ASSERT_EQ(TEST_IO_SIZE, image.read(itr->second, TEST_IO_SIZE, readbl));
    ASSERT_TRUE(readbl.contents_equal(bl));
  }

  int64_t data_pool_id = image.get_data_pool_id();
  rados_ioctx_t d_ioctx;
  ASSERT_EQ(0, rados_wait_for_latest_osdmap(_cluster));
  ASSERT_EQ(0, rados_ioctx_create2(_cluster, data_pool_id, &d_ioctx));

  std::string block_name_prefix = image.get_block_name_prefix() + ".";

  const char *entry;
  rados_list_ctx_t list_ctx;
  set<string> obj_checker;
  ASSERT_EQ(0, rados_nobjects_list_open(d_ioctx, &list_ctx));
  while (rados_nobjects_list_next(list_ctx, &entry, NULL, NULL) != -ENOENT) {
    if (boost::starts_with(entry, block_name_prefix)) {
      const char *block_name_suffix = entry + block_name_prefix.length();
      obj_checker.insert(block_name_suffix);
    }
  }
  rados_nobjects_list_close(list_ctx);

  std::string snapname = "snap";
  std::string clonename = get_temp_image_name();
  ASSERT_EQ(0, image.snap_create(snapname.c_str()));
  ASSERT_EQ(0, image.close());
  ASSERT_EQ(0, m_rbd.open(m_ioctx, image, m_image_name.c_str(), snapname.c_str()));
  ASSERT_EQ(0, image.snap_protect(snapname.c_str()));
  printf("made snapshot \"%s@parent_snap\" and protect it\n", m_image_name.c_str());

  ASSERT_EQ(0, clone_image_pp(m_rbd, image, m_ioctx, m_image_name.c_str(), snapname.c_str(),
                              m_ioctx, clonename.c_str(), features));
  ASSERT_EQ(0, image.close());
  ASSERT_EQ(0, m_rbd.open(m_ioctx, image, clonename.c_str(), NULL));
  printf("made and opened clone \"%s\"\n", clonename.c_str());

  printf("read from \"child\"\n");
  {
    map<uint64_t, uint64_t>::iterator itr = write_tracker.begin();
    printf("\tread object-%-4lu\n", (unsigned long)itr->first);
    ASSERT_EQ(TEST_IO_SIZE, image.read(itr->second, TEST_IO_SIZE, readbl));
    ASSERT_TRUE(readbl.contents_equal(bl));
  }

  for (map<uint64_t, uint64_t>::iterator itr = write_tracker.begin();
       itr != write_tracker.end(); ++itr) {
    printf("\tread object-%-4lu\n", (unsigned long)itr->first);
    ASSERT_EQ(TEST_IO_SIZE, image.read(itr->second, TEST_IO_SIZE, readbl));
    ASSERT_TRUE(readbl.contents_equal(bl));
  }

  printf("read again reversely\n");
  for (map<uint64_t, uint64_t>::iterator itr = --write_tracker.end();
       itr != write_tracker.begin(); --itr) {
    printf("\tread object-%-4lu\n", (unsigned long)itr->first);
    ASSERT_EQ(TEST_IO_SIZE, image.read(itr->second, TEST_IO_SIZE, readbl));
    ASSERT_TRUE(readbl.contents_equal(bl));
  }

  // close child to flush all copy-on-read
  ASSERT_EQ(0, image.close());

  printf("check whether child image has the same set of objects as parent\n");
  ASSERT_EQ(0, m_rbd.open(m_ioctx, image, clonename.c_str(), NULL));
  block_name_prefix = image.get_block_name_prefix() + ".";

  ASSERT_EQ(0, rados_nobjects_list_open(d_ioctx, &list_ctx));
  while (rados_nobjects_list_next(list_ctx, &entry, NULL, NULL) != -ENOENT) {
    if (boost::starts_with(entry, block_name_prefix)) {
      const char *block_name_suffix = entry + block_name_prefix.length();
      set<string>::iterator it = obj_checker.find(block_name_suffix);
      ASSERT_TRUE(it != obj_checker.end());
      obj_checker.erase(it);
    }
  }
  rados_nobjects_list_close(list_ctx);
  ASSERT_TRUE(obj_checker.empty());
  ASSERT_EQ(0, image.close());

  rados_ioctx_destroy(d_ioctx);
}

TEST_F(TestInternal, FlattenNoEmptyObjects)
{
  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);

  m_image_name = get_temp_image_name();
  m_image_size = 4 << 20;

  int order = 12; // smallest object size is 4K
  uint64_t features;
  ASSERT_TRUE(::get_features(&features));

  ASSERT_EQ(0, create_image_full_pp(m_rbd, m_ioctx, m_image_name, m_image_size,
                                    features, false, &order));

  librbd::Image image;
  ASSERT_EQ(0, m_rbd.open(m_ioctx, image, m_image_name.c_str(), NULL));

  librbd::image_info_t info;
  ASSERT_EQ(0, image.stat(info, sizeof(info)));

  const int object_num = info.size / info.obj_size;
  printf("made parent image \"%s\": %" PRIu64 "K (%d * %" PRIu64 "K)\n",
	 m_image_name.c_str(), m_image_size, object_num, info.obj_size/1024);

  // write something into parent
  char test_data[TEST_IO_SIZE + 1];
  for (int i = 0; i < TEST_IO_SIZE; ++i) {
    test_data[i] = (char) (rand() % (126 - 33) + 33);
  }
  test_data[TEST_IO_SIZE] = '\0';

  // generate a random map which covers every objects with random
  // offset
  map<uint64_t, uint64_t> write_tracker;
  generate_random_iomap(image, object_num, info.obj_size, 100, write_tracker);

  printf("generated random write map:\n");
  for (map<uint64_t, uint64_t>::iterator itr = write_tracker.begin();
       itr != write_tracker.end(); ++itr)
    printf("\t [%-8lu, %-8lu]\n",
           (unsigned long)itr->first, (unsigned long)itr->second);

  bufferlist bl;
  bl.append(test_data, TEST_IO_SIZE);

  printf("write data based on random map\n");
  for (map<uint64_t, uint64_t>::iterator itr = write_tracker.begin();
       itr != write_tracker.end(); ++itr) {
    printf("\twrite object-%-4lu\t\n", (unsigned long)itr->first);
    ASSERT_EQ(TEST_IO_SIZE, image.write(itr->second, TEST_IO_SIZE, bl));
  }

  ASSERT_EQ(0, image.close());
  ASSERT_EQ(0, m_rbd.open(m_ioctx, image, m_image_name.c_str(), NULL));

  bufferlist readbl;
  printf("verify written data by reading\n");
  {
    map<uint64_t, uint64_t>::iterator itr = write_tracker.begin();
    printf("\tread object-%-4lu\n", (unsigned long)itr->first);
    ASSERT_EQ(TEST_IO_SIZE, image.read(itr->second, TEST_IO_SIZE, readbl));
    ASSERT_TRUE(readbl.contents_equal(bl));
  }

  int64_t data_pool_id = image.get_data_pool_id();
  rados_ioctx_t d_ioctx;
  ASSERT_EQ(0, rados_wait_for_latest_osdmap(_cluster));
  ASSERT_EQ(0, rados_ioctx_create2(_cluster, data_pool_id, &d_ioctx));

  std::string block_name_prefix = image.get_block_name_prefix() + ".";

  const char *entry;
  rados_list_ctx_t list_ctx;
  set<string> obj_checker;
  ASSERT_EQ(0, rados_nobjects_list_open(d_ioctx, &list_ctx));
  while (rados_nobjects_list_next(list_ctx, &entry, NULL, NULL) != -ENOENT) {
    if (boost::starts_with(entry, block_name_prefix)) {
      const char *block_name_suffix = entry + block_name_prefix.length();
      obj_checker.insert(block_name_suffix);
    }
  }
  rados_nobjects_list_close(list_ctx);

  std::string snapname = "snap";
  std::string clonename = get_temp_image_name();
  ASSERT_EQ(0, image.snap_create(snapname.c_str()));
  ASSERT_EQ(0, image.close());
  ASSERT_EQ(0, m_rbd.open(m_ioctx, image, m_image_name.c_str(), snapname.c_str()));
  ASSERT_EQ(0, image.snap_protect(snapname.c_str()));
  printf("made snapshot \"%s@parent_snap\" and protect it\n", m_image_name.c_str());

  ASSERT_EQ(0, clone_image_pp(m_rbd, image, m_ioctx, m_image_name.c_str(), snapname.c_str(),
                              m_ioctx, clonename.c_str(), features));
  ASSERT_EQ(0, image.close());

  ASSERT_EQ(0, m_rbd.open(m_ioctx, image, clonename.c_str(), NULL));
  printf("made and opened clone \"%s\"\n", clonename.c_str());

  printf("flattening clone: \"%s\"\n", clonename.c_str());
  ASSERT_EQ(0, image.flatten());

  printf("check whether child image has the same set of objects as parent\n");
  block_name_prefix = image.get_block_name_prefix() + ".";

  ASSERT_EQ(0, rados_nobjects_list_open(d_ioctx, &list_ctx));
  while (rados_nobjects_list_next(list_ctx, &entry, NULL, NULL) != -ENOENT) {
    if (boost::starts_with(entry, block_name_prefix)) {
      const char *block_name_suffix = entry + block_name_prefix.length();
      set<string>::iterator it = obj_checker.find(block_name_suffix);
      ASSERT_TRUE(it != obj_checker.end());
      obj_checker.erase(it);
    }
  }
  rados_nobjects_list_close(list_ctx);
  ASSERT_TRUE(obj_checker.empty());
  ASSERT_EQ(0, image.close());

  rados_ioctx_destroy(d_ioctx);
}

TEST_F(TestInternal, FlattenInconsistentObjectMap)
{
  REQUIRE_FEATURE(RBD_FEATURE_LAYERING | RBD_FEATURE_OBJECT_MAP);
  REQUIRE(!is_feature_enabled(RBD_FEATURE_STRIPINGV2));

  librbd::ImageCtx* ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));

  librbd::NoOpProgressContext no_op;
  ASSERT_EQ(0, ictx->operations->resize((1 << ictx->order) * 5, true, no_op));

  bufferlist bl;
  bl.append(std::string(256, '1'));
  for (int i = 1; i < 5; i++) {
    ASSERT_EQ(256, api::Io<>::write(*ictx, (1 << ictx->order) * i, 256,
                                    bufferlist{bl}, 0));
  }

  ASSERT_EQ(0, snap_create(*ictx, "snap"));
  ASSERT_EQ(0, snap_protect(*ictx, "snap"));

  uint64_t features;
  ASSERT_EQ(0, librbd::get_features(ictx, &features));

  std::string clone_name = get_temp_image_name();
  int order = ictx->order;
  ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap", m_ioctx,
                             clone_name.c_str(), features, &order, 0, 0));

  close_image(ictx);
  ASSERT_EQ(0, open_image(clone_name, &ictx));

  C_SaferCond lock_ctx;
  {
    std::shared_lock owner_locker{ictx->owner_lock};
    ictx->exclusive_lock->try_acquire_lock(&lock_ctx);
  }
  ASSERT_EQ(0, lock_ctx.wait());
  ASSERT_TRUE(ictx->exclusive_lock->is_lock_owner());

  ceph::BitVector<2> inconsistent_object_map;
  inconsistent_object_map.resize(5);
  inconsistent_object_map[0] = OBJECT_NONEXISTENT;
  inconsistent_object_map[1] = OBJECT_NONEXISTENT;
  inconsistent_object_map[2] = OBJECT_EXISTS;
  inconsistent_object_map[3] = OBJECT_EXISTS_CLEAN;
  // OBJECT_PENDING shouldn't happen within parent overlap, but test
  // anyway
  inconsistent_object_map[4] = OBJECT_PENDING;

  auto object_map = new librbd::ObjectMap<>(*ictx, CEPH_NOSNAP);
  C_SaferCond save_ctx;
  {
    std::shared_lock owner_locker{ictx->owner_lock};
    std::unique_lock image_locker{ictx->image_lock};
    object_map->set_object_map(inconsistent_object_map);
    object_map->aio_save(&save_ctx);
  }
  ASSERT_EQ(0, save_ctx.wait());
  object_map->put();

  close_image(ictx);
  ASSERT_EQ(0, open_image(clone_name, &ictx));
  ASSERT_EQ(0, ictx->operations->flatten(no_op));

  bufferptr read_ptr(256);
  bufferlist read_bl;
  read_bl.push_back(read_ptr);

  librbd::io::ReadResult read_result{&read_bl};
  for (int i = 1; i < 5; i++) {
    ASSERT_EQ(256, api::Io<>::read(*ictx, (1 << ictx->order) * i, 256,
                                   librbd::io::ReadResult{read_result}, 0));
    EXPECT_TRUE(bl.contents_equal(read_bl));
  }
}

TEST_F(TestInternal, PoolMetadataConfApply) {
  REQUIRE_FORMAT_V2();

  librbd::api::PoolMetadata<>::remove(m_ioctx, "conf_rbd_cache");

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));

  bool cache = ictx->cache;
  std::string rbd_conf_cache = cache ? "true" : "false";
  std::string new_rbd_conf_cache = !cache ? "true" : "false";

  ASSERT_EQ(0, librbd::api::PoolMetadata<>::set(m_ioctx, "conf_rbd_cache",
                                                new_rbd_conf_cache));
  ASSERT_EQ(0, ictx->state->refresh());
  ASSERT_EQ(!cache, ictx->cache);

  ASSERT_EQ(0, ictx->operations->metadata_set("conf_rbd_cache",
                                              rbd_conf_cache));
  ASSERT_EQ(cache, ictx->cache);

  ASSERT_EQ(0, ictx->operations->metadata_remove("conf_rbd_cache"));
  ASSERT_EQ(!cache, ictx->cache);

  ASSERT_EQ(0, librbd::api::PoolMetadata<>::remove(m_ioctx, "conf_rbd_cache"));
  ASSERT_EQ(0, ictx->state->refresh());
  ASSERT_EQ(cache, ictx->cache);
  close_image(ictx);

  ASSERT_EQ(0, librbd::api::PoolMetadata<>::set(m_ioctx,
                                                "conf_rbd_default_order",
                                                "17"));
  ASSERT_EQ(0, librbd::api::PoolMetadata<>::set(m_ioctx,
                                                "conf_rbd_journal_order",
                                                "13"));
  std::string image_name = get_temp_image_name();
  int order = 0;
  uint64_t features;
  ASSERT_TRUE(::get_features(&features));
  ASSERT_EQ(0, create_image_full_pp(m_rbd, m_ioctx, image_name, m_image_size,
                                    features, false, &order));

  ASSERT_EQ(0, open_image(image_name, &ictx));
  ASSERT_EQ(ictx->order, 17);
  ASSERT_EQ(ictx->config.get_val<uint64_t>("rbd_journal_order"), 13U);

  if (is_feature_enabled(RBD_FEATURE_JOURNALING)) {
    uint8_t order;
    uint8_t splay_width;
    int64_t pool_id;
    C_SaferCond cond;
    cls::journal::client::get_immutable_metadata(m_ioctx, "journal." + ictx->id,
                                                 &order, &splay_width, &pool_id,
                                                 &cond);
    ASSERT_EQ(0, cond.wait());
    ASSERT_EQ(order, 13);
    ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_JOURNALING,
                                                   false));
    ASSERT_EQ(0, librbd::api::PoolMetadata<>::set(m_ioctx,
                                                  "conf_rbd_journal_order",
                                                  "14"));
    ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_JOURNALING,
                                                   true));
    ASSERT_EQ(ictx->config.get_val<uint64_t>("rbd_journal_order"), 14U);

    C_SaferCond cond1;
    cls::journal::client::get_immutable_metadata(m_ioctx, "journal." + ictx->id,
                                                 &order, &splay_width, &pool_id,
                                                 &cond1);
    ASSERT_EQ(0, cond1.wait());
    ASSERT_EQ(order, 14);
  }

  ASSERT_EQ(0, librbd::api::PoolMetadata<>::remove(m_ioctx,
                                                   "conf_rbd_default_order"));
  ASSERT_EQ(0, librbd::api::PoolMetadata<>::remove(m_ioctx,
                                                   "conf_rbd_journal_order"));
}

TEST_F(TestInternal, Sparsify) {
  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));

  bool sparsify_supported = is_sparsify_supported(ictx->data_ctx,
                                                  ictx->get_object_name(10));
  bool sparse_read_supported = is_sparse_read_supported(
      ictx->data_ctx, ictx->get_object_name(10));

  std::cout << "sparsify_supported=" << sparsify_supported << std::endl;
  std::cout << "sparse_read_supported=" << sparse_read_supported << std::endl;

  librbd::NoOpProgressContext no_op;
  ASSERT_EQ(0, ictx->operations->resize((1 << ictx->order) * 20, true, no_op));

  bufferlist bl;
  bl.append(std::string(4096, '\0'));

  ASSERT_EQ((ssize_t)bl.length(),
            api::Io<>::write(*ictx, 0, bl.length(), bufferlist{bl}, 0));

  ASSERT_EQ((ssize_t)bl.length(),
            api::Io<>::write(*ictx, (1 << ictx->order) * 1 + 512,
                             bl.length(), bufferlist{bl}, 0));

  bl.append(std::string(4096, '1'));
  bl.append(std::string(4096, '\0'));
  bl.append(std::string(4096, '2'));
  bl.append(std::string(4096 - 1, '\0'));
  ASSERT_EQ((ssize_t)bl.length(),
            api::Io<>::write(*ictx, (1 << ictx->order) * 10, bl.length(),
                             bufferlist{bl}, 0));

  bufferlist bl2;
  bl2.append(std::string(4096 - 1, '\0'));
  ASSERT_EQ((ssize_t)bl2.length(),
            api::Io<>::write(*ictx, (1 << ictx->order) * 10 + 4096 * 10,
                             bl2.length(), bufferlist{bl2}, 0));

  ASSERT_EQ(0, flush_writeback_cache(ictx));

  ASSERT_EQ(0, ictx->operations->sparsify(4096, no_op));

  bufferptr read_ptr(bl.length());
  bufferlist read_bl;
  read_bl.push_back(read_ptr);

  librbd::io::ReadResult read_result{&read_bl};
  ASSERT_EQ((ssize_t)read_bl.length(),
            api::Io<>::read(*ictx, (1 << ictx->order) * 10, read_bl.length(),
                            librbd::io::ReadResult{read_result}, 0));
  ASSERT_TRUE(bl.contents_equal(read_bl));

  std::string oid = ictx->get_object_name(0);
  uint64_t size;
  ASSERT_EQ(-ENOENT, ictx->data_ctx.stat(oid, &size, NULL));

  oid = ictx->get_object_name(1);
  ASSERT_EQ(-ENOENT, ictx->data_ctx.stat(oid, &size, NULL));

  oid = ictx->get_object_name(10);
  std::map<uint64_t, uint64_t> m;
  std::map<uint64_t, uint64_t> expected_m;
  auto read_len = bl.length();
  bl.clear();
  if (sparsify_supported && sparse_read_supported) {
    expected_m = {{4096 * 1, 4096}, {4096 * 3, 4096}};
    bl.append(std::string(4096, '1'));
    bl.append(std::string(4096, '2'));
  } else {
    expected_m = {{0, 4096 * 4}};
    bl.append(std::string(4096, '\0'));
    bl.append(std::string(4096, '1'));
    bl.append(std::string(4096, '\0'));
    bl.append(std::string(4096, '2'));
  }
  read_bl.clear();
  EXPECT_EQ(static_cast<int>(expected_m.size()),
            ictx->data_ctx.sparse_read(oid, m, read_bl, read_len, 0));
  EXPECT_EQ(m, expected_m);
  EXPECT_TRUE(bl.contents_equal(read_bl));
}


TEST_F(TestInternal, SparsifyClone) {
  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));

  bool sparsify_supported = is_sparsify_supported(ictx->data_ctx,
                                                  ictx->get_object_name(10));
  std::cout << "sparsify_supported=" << sparsify_supported << std::endl;

  librbd::NoOpProgressContext no_op;
  ASSERT_EQ(0, ictx->operations->resize((1 << ictx->order) * 10, true, no_op));

  ASSERT_EQ(0, create_snapshot("snap", true));
  std::string clone_name = get_temp_image_name();
  int order = ictx->order;
  ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap", m_ioctx,
                             clone_name.c_str(), ictx->features, &order, 0, 0));
  close_image(ictx);

  ASSERT_EQ(0, open_image(clone_name, &ictx));

  BOOST_SCOPE_EXIT_ALL(this, &ictx, clone_name) {
    close_image(ictx);
    librbd::NoOpProgressContext no_op;
    EXPECT_EQ(0, librbd::api::Image<>::remove(m_ioctx, clone_name, no_op));
  };

  ASSERT_EQ(0, ictx->operations->resize((1 << ictx->order) * 20, true, no_op));

  bufferlist bl;
  bl.append(std::string(4096, '\0'));

  ASSERT_EQ((ssize_t)bl.length(),
            api::Io<>::write(*ictx, 0, bl.length(), bufferlist{bl}, 0));

  bl.append(std::string(4096, '1'));
  bl.append(std::string(4096, '\0'));
  bl.append(std::string(4096, '2'));
  bl.append(std::string(4096, '\0'));
  ASSERT_EQ((ssize_t)bl.length(),
            api::Io<>::write(*ictx, (1 << ictx->order) * 10, bl.length(),
                             bufferlist{bl}, 0));
  ASSERT_EQ(0, flush_writeback_cache(ictx));

  ASSERT_EQ(0, ictx->operations->sparsify(4096, no_op));

  bufferptr read_ptr(bl.length());
  bufferlist read_bl;
  read_bl.push_back(read_ptr);

  librbd::io::ReadResult read_result{&read_bl};
  ASSERT_EQ((ssize_t)read_bl.length(),
            api::Io<>::read(*ictx, (1 << ictx->order) * 10, read_bl.length(),
                            librbd::io::ReadResult{read_result}, 0));
  ASSERT_TRUE(bl.contents_equal(read_bl));

  std::string oid = ictx->get_object_name(0);
  uint64_t size;
  ASSERT_EQ(0, ictx->data_ctx.stat(oid, &size, NULL));
  ASSERT_EQ(0, ictx->data_ctx.read(oid, read_bl, 4096, 0));
}

TEST_F(TestInternal, MissingDataPool) {
  REQUIRE_FORMAT_V2();

  librbd::ImageCtx *ictx;
  ASSERT_EQ(0, open_image(m_image_name, &ictx));
  ASSERT_EQ(0, snap_create(*ictx, "snap1"));
  std::string header_oid = ictx->header_oid;
  close_image(ictx);

  // emulate non-existent data pool
  int64_t pool_id = 1234;
  std::string pool_name;
  int r;
  while ((r = _rados.pool_reverse_lookup(pool_id, &pool_name)) == 0) {
    pool_id++;
  }
  ASSERT_EQ(r, -ENOENT);
  bufferlist bl;
  using ceph::encode;
  encode(pool_id, bl);
  ASSERT_EQ(0, m_ioctx.omap_set(header_oid, {{"data_pool_id", bl}}));

  ASSERT_EQ(0, open_image(m_image_name, &ictx));

  ASSERT_FALSE(ictx->data_ctx.is_valid());
  ASSERT_EQ(pool_id, librbd::api::Image<>::get_data_pool_id(ictx));

  librbd::image_info_t info;
  ASSERT_EQ(0, librbd::info(ictx, info, sizeof(info)));

  vector<librbd::snap_info_t> snaps;
  EXPECT_EQ(0, librbd::api::Snapshot<>::list(ictx, snaps));
  EXPECT_EQ(1U, snaps.size());
  EXPECT_EQ("snap1", snaps[0].name);

  bufferptr read_ptr(256);
  bufferlist read_bl;
  read_bl.push_back(read_ptr);
  librbd::io::ReadResult read_result{&read_bl};
  ASSERT_EQ(-ENODEV,
            api::Io<>::read(*ictx, 0, 256,
                            librbd::io::ReadResult{read_result}, 0));
  ASSERT_EQ(-ENODEV,
            api::Io<>::write(*ictx, 0, bl.length(), bufferlist{bl}, 0));
  ASSERT_EQ(-ENODEV, api::Io<>::discard(*ictx, 0, 1, 256));
  ASSERT_EQ(-ENODEV,
            api::Io<>::write_same(*ictx, 0, bl.length(), bufferlist{bl}, 0));
  uint64_t mismatch_off;
  ASSERT_EQ(-ENODEV,
            api::Io<>::compare_and_write(*ictx, 0, bl.length(),
                                         bufferlist{bl}, bufferlist{bl},
                                         &mismatch_off, 0));
  ASSERT_EQ(-ENODEV, api::Io<>::flush(*ictx));

  ASSERT_EQ(-ENODEV, snap_create(*ictx, "snap2"));
  ASSERT_EQ(0, ictx->operations->snap_remove(cls::rbd::UserSnapshotNamespace(),
                                             "snap1"));

  librbd::NoOpProgressContext no_op;
  ASSERT_EQ(-ENODEV, ictx->operations->resize(0, true, no_op));

  close_image(ictx);

  ASSERT_EQ(0, librbd::api::Image<>::remove(m_ioctx, m_image_name, no_op));

  ASSERT_EQ(0, create_image_pp(m_rbd, m_ioctx, m_image_name, m_image_size));
}

} // namespace librbd
