initial code repo
[stor4nfv.git] / src / ceph / src / test / ObjectMap / test_object_map.cc
diff --git a/src/ceph/src/test/ObjectMap/test_object_map.cc b/src/ceph/src/test/ObjectMap/test_object_map.cc
new file mode 100644 (file)
index 0000000..8d3f688
--- /dev/null
@@ -0,0 +1,1131 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+#include "include/memory.h"
+#include <map>
+#include <set>
+#include <boost/scoped_ptr.hpp>
+
+#include "include/buffer.h"
+#include "test/ObjectMap/KeyValueDBMemory.h"
+#include "kv/KeyValueDB.h"
+#include "os/filestore/DBObjectMap.h"
+#include "os/filestore/HashIndex.h"
+#include <sys/types.h>
+#include "global/global_init.h"
+#include "common/ceph_argparse.h"
+#include <dirent.h>
+
+#include "gtest/gtest.h"
+#include "stdlib.h"
+
+using namespace std;
+
+template <typename T>
+typename T::iterator rand_choose(T &cont) {
+  if (cont.size() == 0) {
+    return cont.end();
+  }
+  int index = rand() % cont.size();
+  typename T::iterator retval = cont.begin();
+
+  for (; index > 0; --index) ++retval;
+  return retval;
+}
+
+string num_str(unsigned i) {
+  char buf[100];
+  snprintf(buf, sizeof(buf), "%.10u", i);
+  return string(buf);
+}
+
+class ObjectMapTester {
+public:
+  ObjectMap *db;
+  set<string> key_space;
+  set<string> object_name_space;
+  map<string, map<string, string> > omap;
+  map<string, string > hmap;
+  map<string, map<string, string> > xattrs;
+  unsigned seq;
+
+  ObjectMapTester() : db(0), seq(0) {}
+
+  string val_from_key(const string &object, const string &key) {
+    return object + "_" + key + "_" + num_str(seq++);
+  }
+
+  void set_key(const string &objname, const string &key, const string &value) {
+    set_key(ghobject_t(hobject_t(sobject_t(objname, CEPH_NOSNAP))),
+           key, value);
+  }
+
+  void set_xattr(const string &objname, const string &key, const string &value) {
+    set_xattr(ghobject_t(hobject_t(sobject_t(objname, CEPH_NOSNAP))),
+             key, value);
+  }
+
+  void set_key(ghobject_t hoid,
+              string key, string value) {
+    map<string, bufferlist> to_write;
+    bufferptr bp(value.c_str(), value.size());
+    bufferlist bl;
+    bl.append(bp);
+    to_write.insert(make_pair(key, bl));
+    db->set_keys(hoid, to_write);
+  }
+
+  void set_keys(ghobject_t hoid, const map<string, string> &to_set) {
+    map<string, bufferlist> to_write;
+    for (auto &&i: to_set) {
+      bufferptr bp(i.second.data(), i.second.size());
+      bufferlist bl;
+      bl.append(bp);
+      to_write.insert(make_pair(i.first, bl));
+    }
+    db->set_keys(hoid, to_write);
+  }
+
+  void set_xattr(ghobject_t hoid,
+                string key, string value) {
+    map<string, bufferlist> to_write;
+    bufferptr bp(value.c_str(), value.size());
+    bufferlist bl;
+    bl.append(bp);
+    to_write.insert(make_pair(key, bl));
+    db->set_xattrs(hoid, to_write);
+  }
+
+  void set_header(const string &objname, const string &value) {
+    set_header(ghobject_t(hobject_t(sobject_t(objname, CEPH_NOSNAP))),
+              value);
+  }
+
+  void set_header(ghobject_t hoid,
+                 const string &value) {
+    bufferlist header;
+    header.append(bufferptr(value.c_str(), value.size() + 1));
+    db->set_header(hoid, header);
+  }
+
+  int get_header(const string &objname, string *value) {
+    return get_header(ghobject_t(hobject_t(sobject_t(objname, CEPH_NOSNAP))),
+                     value);
+  }
+
+  int get_header(ghobject_t hoid,
+                string *value) {
+    bufferlist header;
+    int r = db->get_header(hoid, &header);
+    if (r < 0)
+      return r;
+    if (header.length())
+      *value = string(header.c_str());
+    else
+      *value = string("");
+    return 0;
+  }
+
+  int get_xattr(const string &objname, const string &key, string *value) {
+    return get_xattr(ghobject_t(hobject_t(sobject_t(objname, CEPH_NOSNAP))),
+                    key, value);
+  }
+
+  int get_xattr(ghobject_t hoid,
+               string key, string *value) {
+    set<string> to_get;
+    to_get.insert(key);
+    map<string, bufferlist> got;
+    db->get_xattrs(hoid, to_get, &got);
+    if (!got.empty()) {
+      *value = string(got.begin()->second.c_str(),
+                     got.begin()->second.length());
+      return 1;
+    } else {
+      return 0;
+    }
+  }
+
+  int get_key(const string &objname, const string &key, string *value) {
+    return get_key(ghobject_t(hobject_t(sobject_t(objname, CEPH_NOSNAP))),
+                  key, value);
+  }
+
+  int get_key(ghobject_t hoid,
+             string key, string *value) {
+    set<string> to_get;
+    to_get.insert(key);
+    map<string, bufferlist> got;
+    db->get_values(hoid, to_get, &got);
+    if (!got.empty()) {
+      if (value) {
+       *value = string(got.begin()->second.c_str(),
+                       got.begin()->second.length());
+      }
+      return 1;
+    } else {
+      return 0;
+    }
+  }
+
+  void remove_key(const string &objname, const string &key) {
+    remove_key(ghobject_t(hobject_t(sobject_t(objname, CEPH_NOSNAP))),
+              key);
+  }
+
+  void remove_keys(const string &objname, const set<string> &to_remove) {
+    remove_keys(ghobject_t(hobject_t(sobject_t(objname, CEPH_NOSNAP))),
+                to_remove);
+  }
+
+  void remove_key(ghobject_t hoid,
+                 string key) {
+    set<string> to_remove;
+    to_remove.insert(key);
+    db->rm_keys(hoid, to_remove);
+  }
+
+  void remove_keys(ghobject_t hoid,
+                   const set<string> &to_remove) {
+    db->rm_keys(hoid, to_remove);
+  }
+
+  void remove_xattr(const string &objname, const string &key) {
+    remove_xattr(ghobject_t(hobject_t(sobject_t(objname, CEPH_NOSNAP))),
+                key);
+  }
+
+  void remove_xattr(ghobject_t hoid,
+                   string key) {
+    set<string> to_remove;
+    to_remove.insert(key);
+    db->remove_xattrs(hoid, to_remove);
+  }
+
+  void clone(const string &objname, const string &target) {
+    clone(ghobject_t(hobject_t(sobject_t(objname, CEPH_NOSNAP))),
+         ghobject_t(hobject_t(sobject_t(target, CEPH_NOSNAP))));
+  }
+
+  void clone(ghobject_t hoid,
+            ghobject_t hoid2) {
+    db->clone(hoid, hoid2);
+  }
+
+  void rename(const string &objname, const string &target) {
+    rename(ghobject_t(hobject_t(sobject_t(objname, CEPH_NOSNAP))),
+         ghobject_t(hobject_t(sobject_t(target, CEPH_NOSNAP))));
+  }
+
+  void rename(ghobject_t hoid,
+            ghobject_t hoid2) {
+    db->rename(hoid, hoid2);
+  }
+
+  void clear(const string &objname) {
+    clear(ghobject_t(hobject_t(sobject_t(objname, CEPH_NOSNAP))));
+  }
+
+  void legacy_clone(const string &objname, const string &target) {
+    legacy_clone(ghobject_t(hobject_t(sobject_t(objname, CEPH_NOSNAP))),
+         ghobject_t(hobject_t(sobject_t(target, CEPH_NOSNAP))));
+  }
+
+  void legacy_clone(ghobject_t hoid,
+            ghobject_t hoid2) {
+    db->legacy_clone(hoid, hoid2);
+  }
+
+  void clear(ghobject_t hoid) {
+    db->clear(hoid);
+  }
+
+  void clear_omap(const string &objname) {
+    clear_omap(ghobject_t(hobject_t(sobject_t(objname, CEPH_NOSNAP))));
+  }
+
+  void clear_omap(const ghobject_t &objname) {
+    db->clear_keys_header(objname);
+  }
+
+  void def_init() {
+    for (unsigned i = 0; i < 10000; ++i) {
+      key_space.insert("key_" + num_str(i));
+    }
+    for (unsigned i = 0; i < 100; ++i) {
+      object_name_space.insert("name_" + num_str(i));
+    }
+  }
+
+  void init_key_set(const set<string> &keys) {
+    key_space = keys;
+  }
+
+  void init_object_name_space(const set<string> &onamespace) {
+    object_name_space = onamespace;
+  }
+
+  void auto_set_xattr(ostream &out) {
+    set<string>::iterator key = rand_choose(key_space);
+    set<string>::iterator object = rand_choose(object_name_space);
+
+    string value = val_from_key(*object, *key);
+
+    xattrs[*object][*key] = value;
+    set_xattr(*object, *key, value);
+
+    out << "auto_set_xattr " << *object << ": " << *key << " -> "
+       << value << std::endl;
+  }
+
+  void test_set_key(const string &obj, const string &key, const string &val) {
+    omap[obj][key] = val;
+    set_key(obj, key, val);
+  }
+
+  void test_set_keys(const string &obj, const map<string, string> &to_set) {
+    for (auto &&i: to_set) {
+      omap[obj][i.first] = i.second;
+    }
+    set_keys(
+      ghobject_t(hobject_t(sobject_t(obj, CEPH_NOSNAP))),
+      to_set);
+  }
+
+  void auto_set_keys(ostream &out) {
+    set<string>::iterator object = rand_choose(object_name_space);
+
+    map<string, string> to_set;
+    unsigned amount = (rand() % 10) + 1;
+    for (unsigned i = 0; i < amount; ++i) {
+      set<string>::iterator key = rand_choose(key_space);
+      string value = val_from_key(*object, *key);
+      out << "auto_set_key " << *object << ": " << *key << " -> "
+         << value << std::endl;
+      to_set.insert(make_pair(*key, value));
+    }
+
+
+    test_set_keys(*object, to_set);
+  }
+
+  void xattrs_on_object(const string &object, set<string> *out) {
+    if (!xattrs.count(object))
+      return;
+    const map<string, string> &xmap = xattrs.find(object)->second;
+    for (map<string, string>::const_iterator i = xmap.begin();
+        i != xmap.end();
+        ++i) {
+      out->insert(i->first);
+    }
+  }
+
+  void keys_on_object(const string &object, set<string> *out) {
+    if (!omap.count(object))
+      return;
+    const map<string, string> &kmap = omap.find(object)->second;
+    for (map<string, string>::const_iterator i = kmap.begin();
+        i != kmap.end();
+        ++i) {
+      out->insert(i->first);
+    }
+  }
+
+  void xattrs_off_object(const string &object, set<string> *out) {
+    *out = key_space;
+    set<string> xspace;
+    xattrs_on_object(object, &xspace);
+    for (set<string>::iterator i = xspace.begin();
+        i != xspace.end();
+        ++i) {
+      out->erase(*i);
+    }
+  }
+
+  void keys_off_object(const string &object, set<string> *out) {
+    *out = key_space;
+    set<string> kspace;
+    keys_on_object(object, &kspace);
+    for (set<string>::iterator i = kspace.begin();
+        i != kspace.end();
+        ++i) {
+      out->erase(*i);
+    }
+  }
+
+  int auto_check_present_xattr(ostream &out) {
+    set<string>::iterator object = rand_choose(object_name_space);
+    set<string> xspace;
+    xattrs_on_object(*object, &xspace);
+    set<string>::iterator key = rand_choose(xspace);
+    if (key == xspace.end()) {
+      return 1;
+    }
+
+    string result;
+    int r = get_xattr(*object, *key, &result);
+    if (!r) {
+      out << "auto_check_present_key: failed to find key "
+         << *key << " on object " << *object << std::endl;
+      return 0;
+    }
+
+    if (result != xattrs[*object][*key]) {
+      out << "auto_check_present_key: for key "
+         << *key << " on object " << *object
+         << " found value " << result << " where we should have found "
+         << xattrs[*object][*key] << std::endl;
+      return 0;
+    }
+
+    out << "auto_check_present_key: for key "
+       << *key << " on object " << *object
+       << " found value " << result << " where we should have found "
+       << xattrs[*object][*key] << std::endl;
+    return 1;
+  }
+
+
+  int auto_check_present_key(ostream &out) {
+    set<string>::iterator object = rand_choose(object_name_space);
+    set<string> kspace;
+    keys_on_object(*object, &kspace);
+    set<string>::iterator key = rand_choose(kspace);
+    if (key == kspace.end()) {
+      return 1;
+    }
+
+    string result;
+    int r = get_key(*object, *key, &result);
+    if (!r) {
+      out << "auto_check_present_key: failed to find key "
+         << *key << " on object " << *object << std::endl;
+      return 0;
+    }
+
+    if (result != omap[*object][*key]) {
+      out << "auto_check_present_key: for key "
+         << *key << " on object " << *object
+         << " found value " << result << " where we should have found "
+         << omap[*object][*key] << std::endl;
+      return 0;
+    }
+
+    out << "auto_check_present_key: for key "
+       << *key << " on object " << *object
+       << " found value " << result << " where we should have found "
+       << omap[*object][*key] << std::endl;
+    return 1;
+  }
+
+  int auto_check_absent_xattr(ostream &out) {
+    set<string>::iterator object = rand_choose(object_name_space);
+    set<string> xspace;
+    xattrs_off_object(*object, &xspace);
+    set<string>::iterator key = rand_choose(xspace);
+    if (key == xspace.end()) {
+      return 1;
+    }
+
+    string result;
+    int r = get_xattr(*object, *key, &result);
+    if (!r) {
+      out << "auto_check_absent_key: did not find key "
+         << *key << " on object " << *object << std::endl;
+      return 1;
+    }
+
+    out << "auto_check_basent_key: for key "
+       << *key << " on object " << *object
+       << " found value " << result << " where we should have found nothing"
+       << std::endl;
+    return 0;
+  }
+
+  int auto_check_absent_key(ostream &out) {
+    set<string>::iterator object = rand_choose(object_name_space);
+    set<string> kspace;
+    keys_off_object(*object, &kspace);
+    set<string>::iterator key = rand_choose(kspace);
+    if (key == kspace.end()) {
+      return 1;
+    }
+
+    string result;
+    int r = get_key(*object, *key, &result);
+    if (!r) {
+      out << "auto_check_absent_key: did not find key "
+         << *key << " on object " << *object << std::endl;
+      return 1;
+    }
+
+    out << "auto_check_basent_key: for key "
+       << *key << " on object " << *object
+       << " found value " << result << " where we should have found nothing"
+       << std::endl;
+    return 0;
+  }
+
+  void test_clone(const string &object, const string &target, ostream &out) {
+    clone(object, target);
+    if (!omap.count(object)) {
+      out << " source missing.";
+      omap.erase(target);
+    } else {
+      out << " source present.";
+      omap[target] = omap[object];
+    }
+    if (!hmap.count(object)) {
+      out << " hmap source missing." << std::endl;
+      hmap.erase(target);
+    } else {
+      out << " hmap source present." << std::endl;
+      hmap[target] = hmap[object];
+    }
+    if (!xattrs.count(object)) {
+      out << " hmap source missing." << std::endl;
+      xattrs.erase(target);
+    } else {
+      out << " hmap source present." << std::endl;
+      xattrs[target] = xattrs[object];
+    }
+  }
+
+  void auto_clone_key(ostream &out) {
+    set<string>::iterator object = rand_choose(object_name_space);
+    set<string>::iterator target = rand_choose(object_name_space);
+    while (target == object) {
+      target = rand_choose(object_name_space);
+    }
+    out << "clone " << *object << " to " << *target;
+    test_clone(*object, *target, out);
+  }
+
+  void test_remove_keys(const string &obj, const set<string> &to_remove) {
+    for (auto &&k: to_remove)
+      omap[obj].erase(k);
+    remove_keys(obj, to_remove);
+  }
+
+  void test_remove_key(const string &obj, const string &key) {
+    omap[obj].erase(key);
+    remove_key(obj, key);
+  }
+
+  void auto_remove_keys(ostream &out) {
+    set<string>::iterator object = rand_choose(object_name_space);
+    set<string> kspace;
+    keys_on_object(*object, &kspace);
+    set<string> to_remove;
+    for (unsigned i = 0; i < 3; ++i) {
+      set<string>::iterator key = rand_choose(kspace);
+      if (key == kspace.end())
+       continue;
+      out << "removing " << *key << " from " << *object << std::endl;
+      to_remove.insert(*key);
+    }
+    test_remove_keys(*object, to_remove);
+  }
+
+  void auto_remove_xattr(ostream &out) {
+    set<string>::iterator object = rand_choose(object_name_space);
+    set<string> kspace;
+    xattrs_on_object(*object, &kspace);
+    set<string>::iterator key = rand_choose(kspace);
+    if (key == kspace.end()) {
+      return;
+    }
+    out << "removing xattr " << *key << " from " << *object << std::endl;
+    xattrs[*object].erase(*key);
+    remove_xattr(*object, *key);
+  }
+
+  void auto_delete_object(ostream &out) {
+    set<string>::iterator object = rand_choose(object_name_space);
+    out << "auto_delete_object " << *object << std::endl;
+    clear(*object);
+    omap.erase(*object);
+    hmap.erase(*object);
+    xattrs.erase(*object);
+  }
+
+  void test_clear(const string &obj) {
+    clear_omap(obj);
+    omap.erase(obj);
+    hmap.erase(obj);
+  }
+
+  void auto_clear_omap(ostream &out) {
+    set<string>::iterator object = rand_choose(object_name_space);
+    out << "auto_clear_object " << *object << std::endl;
+    test_clear(*object);
+  }
+
+  void auto_write_header(ostream &out) {
+    set<string>::iterator object = rand_choose(object_name_space);
+    string header = val_from_key(*object, "HEADER");
+    out << "auto_write_header: " << *object << " -> " << header << std::endl;
+    set_header(*object, header);
+    hmap[*object] = header;
+  }
+
+  int auto_verify_header(ostream &out) {
+    set<string>::iterator object = rand_choose(object_name_space);
+    out << "verify_header: " << *object << " ";
+    string header;
+    int r = get_header(*object, &header);
+    if (r < 0) {
+      ceph_abort();
+    }
+    if (header.size() == 0) {
+      if (hmap.count(*object)) {
+       out << " failed to find header " << hmap[*object] << std::endl;
+       return 0;
+      } else {
+       out << " found no header" << std::endl;
+       return 1;
+      }
+    }
+
+    if (!hmap.count(*object)) {
+      out << " found header " << header << " should have been empty"
+             << std::endl;
+      return 0;
+    } else if (header == hmap[*object]) {
+      out << " found correct header " << header << std::endl;
+      return 1;
+    } else {
+      out << " found incorrect header " << header
+         << " where we should have found " << hmap[*object] << std::endl;
+      return 0;
+    }
+  }
+
+  void verify_keys(const std::string &obj, ostream &out) {
+    set<string> in_db;
+    ObjectMap::ObjectMapIterator iter = db->get_iterator(
+      ghobject_t(hobject_t(sobject_t(obj, CEPH_NOSNAP))));
+    for (iter->seek_to_first(); iter->valid(); iter->next()) {
+      in_db.insert(iter->key());
+    }
+    bool err = false;
+    for (auto &&i: omap[obj]) {
+      if (!in_db.count(i.first)) {
+       out << __func__ << ": obj " << obj << " missing key "
+           << i.first << std::endl;
+       err = true;
+      } else {
+       in_db.erase(i.first);
+      }
+    }
+    if (!in_db.empty()) {
+      out << __func__ << ": obj " << obj << " found extra keys "
+         << in_db << std::endl;
+      err = true;
+    }
+    ASSERT_FALSE(err);
+  }
+
+  void auto_verify_objects(ostream &out) {
+    for (auto &&i: omap) {
+      verify_keys(i.first, out);
+    }
+  }
+};
+
+class ObjectMapTest : public ::testing::Test {
+public:
+  boost::scoped_ptr< ObjectMap > db;
+  ObjectMapTester tester;
+  void SetUp() override {
+    char *path = getenv("OBJECT_MAP_PATH");
+    if (!path) {
+      db.reset(new DBObjectMap(g_ceph_context, new KeyValueDBMemory()));
+      tester.db = db.get();
+      return;
+    }
+
+    string strpath(path);
+
+    cerr << "using path " << strpath << std::endl;
+    KeyValueDB *store = KeyValueDB::create(g_ceph_context, "leveldb", strpath);
+    assert(!store->create_and_open(cerr));
+
+    db.reset(new DBObjectMap(g_ceph_context, store));
+    tester.db = db.get();
+  }
+
+  void TearDown() override {
+    std::cerr << "Checking..." << std::endl;
+    ASSERT_EQ(0, db->check(std::cerr));
+  }
+};
+
+
+int main(int argc, char **argv) {
+  vector<const char*> args;
+  argv_to_vec(argc, (const char **)argv, args);
+
+  auto cct = global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT,
+                        CODE_ENVIRONMENT_UTILITY, 0);
+  common_init_finish(g_ceph_context);
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
+
+TEST_F(ObjectMapTest, CreateOneObject) {
+  ghobject_t hoid(hobject_t(sobject_t("foo", CEPH_NOSNAP)), 100, shard_id_t(0));
+  map<string, bufferlist> to_set;
+  string key("test");
+  string val("test_val");
+  bufferptr bp(val.c_str(), val.size());
+  bufferlist bl;
+  bl.append(bp);
+  to_set.insert(make_pair(key, bl));
+  ASSERT_EQ(db->set_keys(hoid, to_set), 0);
+
+  map<string, bufferlist> got;
+  set<string> to_get;
+  to_get.insert(key);
+  to_get.insert("not there");
+  db->get_values(hoid, to_get, &got);
+  ASSERT_EQ(got.size(), (unsigned)1);
+  ASSERT_EQ(string(got[key].c_str(), got[key].length()), val);
+
+  bufferlist header;
+  got.clear();
+  db->get(hoid, &header, &got);
+  ASSERT_EQ(got.size(), (unsigned)1);
+  ASSERT_EQ(string(got[key].c_str(), got[key].length()), val);
+  ASSERT_EQ(header.length(), (unsigned)0);
+
+  db->rm_keys(hoid, to_get);
+  got.clear();
+  db->get(hoid, &header, &got);
+  ASSERT_EQ(got.size(), (unsigned)0);
+
+  map<string, bufferlist> attrs;
+  attrs["attr1"] = bl;
+  db->set_xattrs(hoid, attrs);
+
+  db->set_header(hoid, bl);
+
+  db->clear_keys_header(hoid);
+  set<string> attrs_got;
+  db->get_all_xattrs(hoid, &attrs_got);
+  ASSERT_EQ(attrs_got.size(), 1U);
+  ASSERT_EQ(*(attrs_got.begin()), "attr1");
+  db->get(hoid, &header, &got);
+  ASSERT_EQ(got.size(), (unsigned)0);
+  ASSERT_EQ(header.length(), 0U);
+  got.clear();
+
+  db->clear(hoid);
+  db->get(hoid, &header, &got);
+  ASSERT_EQ(got.size(), (unsigned)0);
+  attrs_got.clear();
+  db->get_all_xattrs(hoid, &attrs_got);
+  ASSERT_EQ(attrs_got.size(), 0U);
+}
+
+TEST_F(ObjectMapTest, CloneOneObject) {
+  ghobject_t hoid(hobject_t(sobject_t("foo", CEPH_NOSNAP)), 200, shard_id_t(0));
+  ghobject_t hoid2(hobject_t(sobject_t("foo2", CEPH_NOSNAP)), 201, shard_id_t(1));
+
+  tester.set_key(hoid, "foo", "bar");
+  tester.set_key(hoid, "foo2", "bar2");
+  string result;
+  int r = tester.get_key(hoid, "foo", &result);
+  ASSERT_EQ(r, 1);
+  ASSERT_EQ(result, "bar");
+
+  db->clone(hoid, hoid2);
+  r = tester.get_key(hoid, "foo", &result);
+  ASSERT_EQ(r, 1);
+  ASSERT_EQ(result, "bar");
+  r = tester.get_key(hoid2, "foo", &result);
+  ASSERT_EQ(r, 1);
+  ASSERT_EQ(result, "bar");
+
+  tester.remove_key(hoid, "foo");
+  r = tester.get_key(hoid2, "foo", &result);
+  ASSERT_EQ(r, 1);
+  ASSERT_EQ(result, "bar");
+  r = tester.get_key(hoid, "foo", &result);
+  ASSERT_EQ(r, 0);
+  r = tester.get_key(hoid, "foo2", &result);
+  ASSERT_EQ(r, 1);
+  ASSERT_EQ(result, "bar2");
+
+  tester.set_key(hoid, "foo", "baz");
+  tester.remove_key(hoid, "foo");
+  r = tester.get_key(hoid, "foo", &result);
+  ASSERT_EQ(r, 0);
+
+  tester.set_key(hoid, "foo2", "baz");
+  tester.remove_key(hoid, "foo2");
+  r = tester.get_key(hoid, "foo2", &result);
+  ASSERT_EQ(r, 0);
+
+  map<string, bufferlist> got;
+  bufferlist header;
+
+  got.clear();
+  db->clear(hoid);
+  db->get(hoid, &header, &got);
+  ASSERT_EQ(got.size(), (unsigned)0);
+
+  got.clear();
+  r = db->clear(hoid2);
+  ASSERT_EQ(0, r);
+  db->get(hoid2, &header, &got);
+  ASSERT_EQ(got.size(), (unsigned)0);
+
+  tester.set_key(hoid, "baz", "bar");
+  got.clear();
+  db->get(hoid, &header, &got);
+  ASSERT_EQ(got.size(), (unsigned)1);
+  db->clear(hoid);
+  db->clear(hoid2);
+}
+
+TEST_F(ObjectMapTest, OddEvenClone) {
+  ghobject_t hoid(hobject_t(sobject_t("foo", CEPH_NOSNAP)));
+  ghobject_t hoid2(hobject_t(sobject_t("foo2", CEPH_NOSNAP)));
+
+  for (unsigned i = 0; i < 1000; ++i) {
+    tester.set_key(hoid, "foo" + num_str(i), "bar" + num_str(i));
+  }
+
+  db->clone(hoid, hoid2);
+
+  int r = 0;
+  for (unsigned i = 0; i < 1000; ++i) {
+    string result;
+    r = tester.get_key(hoid, "foo" + num_str(i), &result);
+    ASSERT_EQ(1, r);
+    ASSERT_EQ("bar" + num_str(i), result);
+    r = tester.get_key(hoid2, "foo" + num_str(i), &result);
+    ASSERT_EQ(1, r);
+    ASSERT_EQ("bar" + num_str(i), result);
+
+    if (i % 2) {
+      tester.remove_key(hoid, "foo" + num_str(i));
+    } else {
+      tester.remove_key(hoid2, "foo" + num_str(i));
+    }
+  }
+
+  for (unsigned i = 0; i < 1000; ++i) {
+    string result;
+    string result2;
+    r = tester.get_key(hoid, "foo" + num_str(i), &result);
+    int r2 = tester.get_key(hoid2, "foo" + num_str(i), &result2);
+    if (i % 2) {
+      ASSERT_EQ(0, r);
+      ASSERT_EQ(1, r2);
+      ASSERT_EQ("bar" + num_str(i), result2);
+    } else {
+      ASSERT_EQ(0, r2);
+      ASSERT_EQ(1, r);
+      ASSERT_EQ("bar" + num_str(i), result);
+    }
+  }
+
+  {
+    ObjectMap::ObjectMapIterator iter = db->get_iterator(hoid);
+    iter->seek_to_first();
+    for (unsigned i = 0; i < 1000; ++i) {
+      if (!(i % 2)) {
+       ASSERT_TRUE(iter->valid());
+       ASSERT_EQ("foo" + num_str(i), iter->key());
+       iter->next();
+      }
+    }
+  }
+
+  {
+    ObjectMap::ObjectMapIterator iter2 = db->get_iterator(hoid2);
+    iter2->seek_to_first();
+    for (unsigned i = 0; i < 1000; ++i) {
+      if (i % 2) {
+       ASSERT_TRUE(iter2->valid());
+       ASSERT_EQ("foo" + num_str(i), iter2->key());
+       iter2->next();
+      }
+    }
+  }
+
+  db->clear(hoid);
+  db->clear(hoid2);
+}
+
+TEST_F(ObjectMapTest, Rename) {
+  ghobject_t hoid(hobject_t(sobject_t("foo", CEPH_NOSNAP)));
+  ghobject_t hoid2(hobject_t(sobject_t("foo2", CEPH_NOSNAP)));
+
+  for (unsigned i = 0; i < 1000; ++i) {
+    tester.set_key(hoid, "foo" + num_str(i), "bar" + num_str(i));
+  }
+
+  db->rename(hoid, hoid2);
+  // Verify rename where target exists
+  db->clone(hoid2, hoid);
+  db->rename(hoid, hoid2);
+
+  int r = 0;
+  for (unsigned i = 0; i < 1000; ++i) {
+    string result;
+    r = tester.get_key(hoid2, "foo" + num_str(i), &result);
+    ASSERT_EQ(1, r);
+    ASSERT_EQ("bar" + num_str(i), result);
+
+    if (i % 2) {
+      tester.remove_key(hoid2, "foo" + num_str(i));
+    }
+  }
+
+  for (unsigned i = 0; i < 1000; ++i) {
+    string result;
+    r = tester.get_key(hoid2, "foo" + num_str(i), &result);
+    if (i % 2) {
+      ASSERT_EQ(0, r);
+    } else {
+      ASSERT_EQ(1, r);
+      ASSERT_EQ("bar" + num_str(i), result);
+    }
+  }
+
+  {
+    ObjectMap::ObjectMapIterator iter = db->get_iterator(hoid2);
+    iter->seek_to_first();
+    for (unsigned i = 0; i < 1000; ++i) {
+      if (!(i % 2)) {
+       ASSERT_TRUE(iter->valid());
+       ASSERT_EQ("foo" + num_str(i), iter->key());
+       iter->next();
+      }
+    }
+  }
+
+  db->clear(hoid2);
+}
+
+TEST_F(ObjectMapTest, OddEvenOldClone) {
+  ghobject_t hoid(hobject_t(sobject_t("foo", CEPH_NOSNAP)));
+  ghobject_t hoid2(hobject_t(sobject_t("foo2", CEPH_NOSNAP)));
+
+  for (unsigned i = 0; i < 1000; ++i) {
+    tester.set_key(hoid, "foo" + num_str(i), "bar" + num_str(i));
+  }
+
+  db->legacy_clone(hoid, hoid2);
+
+  int r = 0;
+  for (unsigned i = 0; i < 1000; ++i) {
+    string result;
+    r = tester.get_key(hoid, "foo" + num_str(i), &result);
+    ASSERT_EQ(1, r);
+    ASSERT_EQ("bar" + num_str(i), result);
+    r = tester.get_key(hoid2, "foo" + num_str(i), &result);
+    ASSERT_EQ(1, r);
+    ASSERT_EQ("bar" + num_str(i), result);
+
+    if (i % 2) {
+      tester.remove_key(hoid, "foo" + num_str(i));
+    } else {
+      tester.remove_key(hoid2, "foo" + num_str(i));
+    }
+  }
+
+  for (unsigned i = 0; i < 1000; ++i) {
+    string result;
+    string result2;
+    r = tester.get_key(hoid, "foo" + num_str(i), &result);
+    int r2 = tester.get_key(hoid2, "foo" + num_str(i), &result2);
+    if (i % 2) {
+      ASSERT_EQ(0, r);
+      ASSERT_EQ(1, r2);
+      ASSERT_EQ("bar" + num_str(i), result2);
+    } else {
+      ASSERT_EQ(0, r2);
+      ASSERT_EQ(1, r);
+      ASSERT_EQ("bar" + num_str(i), result);
+    }
+  }
+
+  {
+    ObjectMap::ObjectMapIterator iter = db->get_iterator(hoid);
+    iter->seek_to_first();
+    for (unsigned i = 0; i < 1000; ++i) {
+      if (!(i % 2)) {
+       ASSERT_TRUE(iter->valid());
+       ASSERT_EQ("foo" + num_str(i), iter->key());
+       iter->next();
+      }
+    }
+  }
+
+  {
+    ObjectMap::ObjectMapIterator iter2 = db->get_iterator(hoid2);
+    iter2->seek_to_first();
+    for (unsigned i = 0; i < 1000; ++i) {
+      if (i % 2) {
+       ASSERT_TRUE(iter2->valid());
+       ASSERT_EQ("foo" + num_str(i), iter2->key());
+       iter2->next();
+      }
+    }
+  }
+
+  db->clear(hoid);
+  db->clear(hoid2);
+}
+
+TEST_F(ObjectMapTest, RandomTest) {
+  tester.def_init();
+  for (unsigned i = 0; i < 5000; ++i) {
+    unsigned val = rand();
+    val <<= 8;
+    val %= 100;
+    if (!(i%100))
+      std::cout << "on op " << i
+               << " val is " << val << std::endl;
+
+    if (val < 7) {
+      tester.auto_write_header(std::cerr);
+    } else if (val < 14) {
+      ASSERT_TRUE(tester.auto_verify_header(std::cerr));
+    } else if (val < 30) {
+      tester.auto_set_keys(std::cerr);
+    } else if (val < 42) {
+      tester.auto_set_xattr(std::cerr);
+    } else if (val < 55) {
+      ASSERT_TRUE(tester.auto_check_present_key(std::cerr));
+    } else if (val < 62) {
+      ASSERT_TRUE(tester.auto_check_present_xattr(std::cerr));
+    } else if (val < 70) {
+      ASSERT_TRUE(tester.auto_check_absent_key(std::cerr));
+    } else if (val < 72) {
+      ASSERT_TRUE(tester.auto_check_absent_xattr(std::cerr));
+    } else if (val < 73) {
+      tester.auto_clear_omap(std::cerr);
+    } else if (val < 76) {
+      tester.auto_delete_object(std::cerr);
+    } else if (val < 85) {
+      tester.auto_clone_key(std::cerr);
+    } else if (val < 92) {
+      tester.auto_remove_xattr(std::cerr);
+    } else {
+      tester.auto_remove_keys(std::cerr);
+    }
+
+    if (i % 500) {
+      tester.auto_verify_objects(std::cerr);
+    }
+  }
+}
+
+TEST_F(ObjectMapTest, RandomTestNoDeletesXattrs) {
+  tester.def_init();
+  for (unsigned i = 0; i < 5000; ++i) {
+    unsigned val = rand();
+    val <<= 8;
+    val %= 100;
+    if (!(i%100))
+      std::cout << "on op " << i
+               << " val is " << val << std::endl;
+
+    if (val < 45) {
+      tester.auto_set_keys(std::cerr);
+    } else if (val < 90) {
+      tester.auto_remove_keys(std::cerr);
+    } else {
+      tester.auto_clone_key(std::cerr);
+    }
+
+    if (i % 500) {
+      tester.auto_verify_objects(std::cerr);
+    }
+  }
+}
+
+string num_to_key(unsigned i) {
+  char buf[100];
+  int ret = snprintf(buf, sizeof(buf), "%010u", i);
+  assert(ret > 0);
+  return string(buf, ret);
+}
+
+TEST_F(ObjectMapTest, TestMergeNewCompleteContainBug) {
+  /* This test exploits a bug in kraken and earlier where merge_new_complete
+   * could miss complete entries fully contained by a new entry.  To get this
+   * to actually result in an incorrect return value, you need to remove at
+   * least two values, one before a complete region, and one which occurs in
+   * the parent after the complete region (but within 20 not yet completed
+   * parent points of the first value).
+   */
+  for (unsigned i = 10; i < 160; i+=2) {
+    tester.test_set_key("foo", num_to_key(i), "asdf");
+  }
+  tester.test_clone("foo", "foo2", std::cout);
+  tester.test_clear("foo");
+
+  tester.test_set_key("foo2", num_to_key(15), "asdf");
+  tester.test_set_key("foo2", num_to_key(13), "asdf");
+  tester.test_set_key("foo2", num_to_key(57), "asdf");
+
+  tester.test_remove_key("foo2", num_to_key(15));
+
+  set<string> to_remove;
+  to_remove.insert(num_to_key(13));
+  to_remove.insert(num_to_key(58));
+  to_remove.insert(num_to_key(60));
+  to_remove.insert(num_to_key(62));
+  tester.test_remove_keys("foo2", to_remove);
+
+  tester.verify_keys("foo2", std::cout);
+  ASSERT_EQ(tester.get_key("foo2", num_to_key(10), nullptr), 1);
+  ASSERT_EQ(tester.get_key("foo2", num_to_key(1), nullptr), 0);
+  ASSERT_EQ(tester.get_key("foo2", num_to_key(56), nullptr), 1);
+  // this one triggers the bug
+  ASSERT_EQ(tester.get_key("foo2", num_to_key(58), nullptr), 0);
+}
+
+TEST_F(ObjectMapTest, TestIterateBug18533) {
+  /* This test starts with the one immediately above to create a pair of
+   * complete regions where one contains the other.  Then, it deletes the
+   * key at the start of the contained region.  The logic in next_parent()
+   * skips ahead to the end of the contained region, and we start copying
+   * values down again from the parent into the child -- including some
+   * that had actually been deleted.  I think this works for any removal
+   * within the outer complete region after the start of the contained
+   * region.
+   */
+  for (unsigned i = 10; i < 160; i+=2) {
+    tester.test_set_key("foo", num_to_key(i), "asdf");
+  }
+  tester.test_clone("foo", "foo2", std::cout);
+  tester.test_clear("foo");
+
+  tester.test_set_key("foo2", num_to_key(15), "asdf");
+  tester.test_set_key("foo2", num_to_key(13), "asdf");
+  tester.test_set_key("foo2", num_to_key(57), "asdf");
+  tester.test_set_key("foo2", num_to_key(91), "asdf");
+
+  tester.test_remove_key("foo2", num_to_key(15));
+
+  set<string> to_remove;
+  to_remove.insert(num_to_key(13));
+  to_remove.insert(num_to_key(58));
+  to_remove.insert(num_to_key(60));
+  to_remove.insert(num_to_key(62));
+  to_remove.insert(num_to_key(82));
+  to_remove.insert(num_to_key(84));
+  tester.test_remove_keys("foo2", to_remove);
+
+  //tester.test_remove_key("foo2", num_to_key(15)); also does the trick
+  tester.test_remove_key("foo2", num_to_key(80));
+
+  // the iterator in verify_keys will return an extra value
+  tester.verify_keys("foo2", std::cout);
+}
+