diff options
Diffstat (limited to 'src/test/ObjectMap')
-rw-r--r-- | src/test/ObjectMap/CMakeLists.txt | 42 | ||||
-rw-r--r-- | src/test/ObjectMap/KeyValueDBMemory.cc | 264 | ||||
-rw-r--r-- | src/test/ObjectMap/KeyValueDBMemory.h | 188 | ||||
-rw-r--r-- | src/test/ObjectMap/test_keyvaluedb_atomicity.cc | 109 | ||||
-rw-r--r-- | src/test/ObjectMap/test_keyvaluedb_iterators.cc | 1756 | ||||
-rw-r--r-- | src/test/ObjectMap/test_object_map.cc | 1126 |
6 files changed, 3485 insertions, 0 deletions
diff --git a/src/test/ObjectMap/CMakeLists.txt b/src/test/ObjectMap/CMakeLists.txt new file mode 100644 index 000000000..837ec5434 --- /dev/null +++ b/src/test/ObjectMap/CMakeLists.txt @@ -0,0 +1,42 @@ +# ceph_test_object_map +add_executable(ceph_test_object_map + test_object_map.cc + KeyValueDBMemory.cc + ) +add_ceph_unittest(ceph_test_object_map) +target_link_libraries(ceph_test_object_map + os + ceph-common + ${UNITTEST_LIBS} + global + ${EXTRALIBS} + ${CMAKE_DL_LIBS} + ) + +# ceph_test_keyvaluedb_atomicity +add_executable(ceph_test_keyvaluedb_atomicity + test_keyvaluedb_atomicity.cc + ) +target_link_libraries(ceph_test_keyvaluedb_atomicity + os + ceph-common + ${UNITTEST_LIBS} + global + ${EXTRALIBS} + ${CMAKE_DL_LIBS} + ) + +# ceph_test_keyvaluedb_iterators +add_executable(ceph_test_keyvaluedb_iterators + test_keyvaluedb_iterators.cc + KeyValueDBMemory.cc + ) +target_link_libraries(ceph_test_keyvaluedb_iterators + os + ceph-common + ${UNITTEST_LIBS} + global + ${EXTRALIBS} + ${CMAKE_DL_LIBS} + ) + diff --git a/src/test/ObjectMap/KeyValueDBMemory.cc b/src/test/ObjectMap/KeyValueDBMemory.cc new file mode 100644 index 000000000..234e96339 --- /dev/null +++ b/src/test/ObjectMap/KeyValueDBMemory.cc @@ -0,0 +1,264 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +#include "include/encoding.h" +#include "KeyValueDBMemory.h" +#include <map> +#include <set> +#include <iostream> + +using namespace std; + +/** + * Iterate over the whole key space of the in-memory store + * + * @note Removing keys from the store while iterating over the store key-space + * may result in unspecified behavior. + * If one wants to safely iterate over the store while updating the + * store, one should instead use a snapshot iterator, which provides + * strong read-consistency. + */ +class WholeSpaceMemIterator : public KeyValueDB::WholeSpaceIteratorImpl { +protected: + KeyValueDBMemory *db; + bool ready; + + map<pair<string,string>, bufferlist>::iterator it; + +public: + explicit WholeSpaceMemIterator(KeyValueDBMemory *db) : db(db), ready(false) { } + ~WholeSpaceMemIterator() override { } + + int seek_to_first() override { + if (db->db.empty()) { + it = db->db.end(); + ready = false; + return 0; + } + it = db->db.begin(); + ready = true; + return 0; + } + + int seek_to_first(const string &prefix) override { + it = db->db.lower_bound(make_pair(prefix, "")); + if (db->db.empty() || (it == db->db.end())) { + it = db->db.end(); + ready = false; + return 0; + } + ready = true; + return 0; + } + + int seek_to_last() override { + it = db->db.end(); + if (db->db.empty()) { + ready = false; + return 0; + } + --it; + ceph_assert(it != db->db.end()); + ready = true; + return 0; + } + + int seek_to_last(const string &prefix) override { + string tmp(prefix); + tmp.append(1, (char) 0); + it = db->db.upper_bound(make_pair(tmp,"")); + + if (db->db.empty() || (it == db->db.end())) { + seek_to_last(); + } + else { + ready = true; + prev(); + } + return 0; + } + + int lower_bound(const string &prefix, const string &to) override { + it = db->db.lower_bound(make_pair(prefix,to)); + if ((db->db.empty()) || (it == db->db.end())) { + it = db->db.end(); + ready = false; + return 0; + } + + ceph_assert(it != db->db.end()); + + ready = true; + return 0; + } + + int upper_bound(const string &prefix, const string &after) override { + it = db->db.upper_bound(make_pair(prefix,after)); + if ((db->db.empty()) || (it == db->db.end())) { + it = db->db.end(); + ready = false; + return 0; + } + ceph_assert(it != db->db.end()); + ready = true; + return 0; + } + + bool valid() override { + return ready && (it != db->db.end()); + } + + bool begin() { + return ready && (it == db->db.begin()); + } + + int prev() override { + if (!begin() && ready) + --it; + else + it = db->db.end(); + return 0; + } + + int next() override { + if (valid()) + ++it; + return 0; + } + + string key() override { + if (valid()) + return (*it).first.second; + else + return ""; + } + + pair<string,string> raw_key() override { + if (valid()) + return (*it).first; + else + return make_pair("", ""); + } + + bool raw_key_is_prefixed(const string &prefix) override { + return prefix == (*it).first.first; + } + + bufferlist value() override { + if (valid()) + return (*it).second; + else + return bufferlist(); + } + + int status() override { + return 0; + } +}; + +int KeyValueDBMemory::get(const string &prefix, + const std::set<string> &key, + map<string, bufferlist> *out) { + if (!exists_prefix(prefix)) + return 0; + + for (std::set<string>::const_iterator i = key.begin(); + i != key.end(); + ++i) { + pair<string,string> k(prefix, *i); + if (db.count(k)) + (*out)[*i] = db[k]; + } + return 0; +} + +int KeyValueDBMemory::get_keys(const string &prefix, + const std::set<string> &key, + std::set<string> *out) { + if (!exists_prefix(prefix)) + return 0; + + for (std::set<string>::const_iterator i = key.begin(); + i != key.end(); + ++i) { + if (db.count(make_pair(prefix, *i))) + out->insert(*i); + } + return 0; +} + +int KeyValueDBMemory::set(const string &prefix, + const string &key, + const bufferlist &bl) { + db[make_pair(prefix,key)] = bl; + return 0; +} + +int KeyValueDBMemory::rmkey(const string &prefix, + const string &key) { + db.erase(make_pair(prefix,key)); + return 0; +} + +int KeyValueDBMemory::rmkeys_by_prefix(const string &prefix) { + map<std::pair<string,string>,bufferlist>::iterator i; + i = db.lower_bound(make_pair(prefix, "")); + if (i == db.end()) + return 0; + + while (i != db.end()) { + std::pair<string,string> key = (*i).first; + if (key.first != prefix) + break; + + ++i; + rmkey(key.first, key.second); + } + return 0; +} + +int KeyValueDBMemory::rm_range_keys(const string &prefix, const string &start, const string &end) { + map<std::pair<string,string>,bufferlist>::iterator i; + i = db.lower_bound(make_pair(prefix, start)); + if (i == db.end()) + return 0; + + while (i != db.end()) { + std::pair<string,string> key = (*i).first; + if (key.first != prefix) + break; + if (key.second >= end) + break; + ++i; + rmkey(key.first, key.second); + } + return 0; +} + +KeyValueDB::WholeSpaceIterator KeyValueDBMemory::get_wholespace_iterator(IteratorOpts opts) { + return std::shared_ptr<KeyValueDB::WholeSpaceIteratorImpl>( + new WholeSpaceMemIterator(this) + ); +} + +class WholeSpaceSnapshotMemIterator : public WholeSpaceMemIterator { +public: + + /** + * @note + * We perform a copy of the db map, which is populated by bufferlists. + * + * These are designed as shallow containers, thus there is a chance that + * changing the underlying memory pages will lead to the iterator seeing + * erroneous states. + * + * Although we haven't verified this yet, there is this chance, so we should + * keep it in mind. + */ + + explicit WholeSpaceSnapshotMemIterator(KeyValueDBMemory *db) : + WholeSpaceMemIterator(db) { } + ~WholeSpaceSnapshotMemIterator() override { + delete db; + } +}; + diff --git a/src/test/ObjectMap/KeyValueDBMemory.h b/src/test/ObjectMap/KeyValueDBMemory.h new file mode 100644 index 000000000..de84ede90 --- /dev/null +++ b/src/test/ObjectMap/KeyValueDBMemory.h @@ -0,0 +1,188 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +#include <map> +#include <set> +#include <string> + +#include "kv/KeyValueDB.h" +#include "include/buffer.h" +#include "include/Context.h" + +using std::string; + +class KeyValueDBMemory : public KeyValueDB { +public: + std::map<std::pair<string,string>,bufferlist> db; + + KeyValueDBMemory() { } + explicit KeyValueDBMemory(KeyValueDBMemory *db) : db(db->db) { } + ~KeyValueDBMemory() override { } + + int init(string _opt) override { + return 0; + } + int open(std::ostream &out, const std::string& cfs="") override { + return 0; + } + int create_and_open(std::ostream &out, const std::string& cfs="") override { + return 0; + } + + int get( + const std::string &prefix, + const std::set<std::string> &key, + std::map<std::string, bufferlist> *out + ) override; + using KeyValueDB::get; + + int get_keys( + const std::string &prefix, + const std::set<std::string> &key, + std::set<std::string> *out + ); + + int set( + const std::string &prefix, + const std::string &key, + const bufferlist &bl + ); + + int rmkey( + const std::string &prefix, + const std::string &key + ); + + int rmkeys_by_prefix( + const std::string &prefix + ); + + int rm_range_keys( + const std::string &prefix, + const std::string &start, + const std::string &end + ); + + class TransactionImpl_ : public TransactionImpl { + public: + std::list<Context *> on_commit; + KeyValueDBMemory *db; + + explicit TransactionImpl_(KeyValueDBMemory *db) : db(db) {} + + + struct SetOp : public Context { + KeyValueDBMemory *db; + std::pair<std::string,std::string> key; + bufferlist value; + SetOp(KeyValueDBMemory *db, + const std::pair<std::string,std::string> &key, + const bufferlist &value) + : db(db), key(key), value(value) {} + void finish(int r) override { + db->set(key.first, key.second, value); + } + }; + + void set(const std::string &prefix, const std::string &k, const bufferlist& bl) override { + on_commit.push_back(new SetOp(db, std::make_pair(prefix, k), bl)); + } + + struct RmKeysOp : public Context { + KeyValueDBMemory *db; + std::pair<std::string,std::string> key; + RmKeysOp(KeyValueDBMemory *db, + const std::pair<std::string,std::string> &key) + : db(db), key(key) {} + void finish(int r) override { + db->rmkey(key.first, key.second); + } + }; + + using KeyValueDB::TransactionImpl::rmkey; + using KeyValueDB::TransactionImpl::set; + void rmkey(const std::string &prefix, const std::string &key) override { + on_commit.push_back(new RmKeysOp(db, std::make_pair(prefix, key))); + } + + struct RmKeysByPrefixOp : public Context { + KeyValueDBMemory *db; + std::string prefix; + RmKeysByPrefixOp(KeyValueDBMemory *db, + const std::string &prefix) + : db(db), prefix(prefix) {} + void finish(int r) override { + db->rmkeys_by_prefix(prefix); + } + }; + void rmkeys_by_prefix(const std::string &prefix) override { + on_commit.push_back(new RmKeysByPrefixOp(db, prefix)); + } + + struct RmRangeKeys: public Context { + KeyValueDBMemory *db; + std::string prefix, start, end; + RmRangeKeys(KeyValueDBMemory *db, const std::string &prefix, const std::string &s, const std::string &e) + : db(db), prefix(prefix), start(s), end(e) {} + void finish(int r) { + db->rm_range_keys(prefix, start, end); + } + }; + + void rm_range_keys(const std::string &prefix, const std::string &start, const std::string &end) { + on_commit.push_back(new RmRangeKeys(db, prefix, start, end)); + } + + int complete() { + for (auto i = on_commit.begin(); + i != on_commit.end(); + on_commit.erase(i++)) { + (*i)->complete(0); + } + return 0; + } + + ~TransactionImpl_() override { + for (auto i = on_commit.begin(); + i != on_commit.end(); + on_commit.erase(i++)) { + delete *i; + } + } + }; + + Transaction get_transaction() override { + return Transaction(new TransactionImpl_(this)); + } + + int submit_transaction(Transaction trans) override { + return static_cast<TransactionImpl_*>(trans.get())->complete(); + } + + uint64_t get_estimated_size(std::map<std::string,uint64_t> &extras) override { + uint64_t total_size = 0; + + for (auto& [key, bl] : db) { + string prefix = key.first; + + uint64_t sz = bl.length(); + total_size += sz; + if (extras.count(prefix) == 0) + extras[prefix] = 0; + extras[prefix] += sz; + } + + return total_size; + } + +private: + bool exists_prefix(const std::string &prefix) { + std::map<std::pair<std::string,std::string>,bufferlist>::iterator it; + it = db.lower_bound(std::make_pair(prefix, "")); + return ((it != db.end()) && ((*it).first.first == prefix)); + } + + friend class WholeSpaceMemIterator; + +public: + WholeSpaceIterator get_wholespace_iterator(IteratorOpts opts = 0) override; +}; diff --git a/src/test/ObjectMap/test_keyvaluedb_atomicity.cc b/src/test/ObjectMap/test_keyvaluedb_atomicity.cc new file mode 100644 index 000000000..f93e68c4a --- /dev/null +++ b/src/test/ObjectMap/test_keyvaluedb_atomicity.cc @@ -0,0 +1,109 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +#include <pthread.h> +#include "include/buffer.h" +#include "kv/KeyValueDB.h" +#include <sys/types.h> +#include <dirent.h> +#include <string> +#include <vector> +#include <boost/scoped_ptr.hpp> +#include <iostream> +#include <sstream> +#include "stdlib.h" +#include "global/global_context.h" + +using namespace std; + +const string CONTROL_PREFIX = "CONTROL"; +const string PRIMARY_PREFIX = "PREFIX"; +const int NUM_COPIES = 100; +const int NUM_THREADS = 30; + +string prefix_gen(int i) { + stringstream ss; + ss << PRIMARY_PREFIX << "_" << i << std::endl; + return ss.str(); +} + +int verify(KeyValueDB *db) { + // Verify + { + map<int, KeyValueDB::Iterator> iterators; + for (int i = 0; i < NUM_COPIES; ++i) { + iterators[i] = db->get_iterator(prefix_gen(i)); + iterators[i]->seek_to_first(); + } + while (iterators.rbegin()->second->valid()) { + for (map<int, KeyValueDB::Iterator>::iterator i = iterators.begin(); + i != iterators.end(); + ++i) { + ceph_assert(i->second->valid()); + ceph_assert(i->second->key() == iterators.rbegin()->second->key()); + bufferlist r = i->second->value(); + bufferlist l = iterators.rbegin()->second->value(); + i->second->next(); + } + } + for (map<int, KeyValueDB::Iterator>::iterator i = iterators.begin(); + i != iterators.end(); + ++i) { + ceph_assert(!i->second->valid()); + } + } + return 0; +} + +void *write(void *_db) { + KeyValueDB *db = static_cast<KeyValueDB*>(_db); + std::cout << "Writing..." << std::endl; + for (int i = 0; i < 12000; ++i) { + if (!(i % 10)) { + std::cout << "Iteration: " << i << std::endl; + } + int key_num = rand(); + stringstream key; + key << key_num << std::endl; + map<string, bufferlist> to_set; + stringstream val; + val << i << std::endl; + bufferptr bp(val.str().c_str(), val.str().size() + 1); + to_set[key.str()].push_back(bp); + + KeyValueDB::Transaction t = db->get_transaction(); + for (int j = 0; j < NUM_COPIES; ++j) { + t->set(prefix_gen(j), to_set); + } + ceph_assert(!db->submit_transaction(t)); + } + return 0; +} + +int main() { + char *path = getenv("OBJECT_MAP_PATH"); + boost::scoped_ptr< KeyValueDB > db; + if (!path) { + std::cerr << "No path found, OBJECT_MAP_PATH undefined" << std::endl; + return 0; + } + string strpath(path); + std::cerr << "Using path: " << strpath << std::endl; + KeyValueDB *store = KeyValueDB::create(g_ceph_context, "leveldb", strpath); + ceph_assert(!store->create_and_open(std::cerr)); + db.reset(store); + + verify(db.get()); + + vector<pthread_t> threads(NUM_THREADS); + for (vector<pthread_t>::iterator i = threads.begin(); + i != threads.end(); + ++i) { + pthread_create(&*i, 0, &write, static_cast<void *>(db.get())); + } + for (vector<pthread_t>::iterator i = threads.begin(); + i != threads.end(); + ++i) { + void *tmp; + pthread_join(*i, &tmp); + } + verify(db.get()); +} diff --git a/src/test/ObjectMap/test_keyvaluedb_iterators.cc b/src/test/ObjectMap/test_keyvaluedb_iterators.cc new file mode 100644 index 000000000..061639ad9 --- /dev/null +++ b/src/test/ObjectMap/test_keyvaluedb_iterators.cc @@ -0,0 +1,1756 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* +* Ceph - scalable distributed file system +* +* Copyright (C) 2012 Inktank, Inc. +* +* This is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License version 2.1, as published by the Free Software +* Foundation. See file COPYING. +*/ +#include <map> +#include <set> +#include <deque> +#include <boost/scoped_ptr.hpp> + +#include "test/ObjectMap/KeyValueDBMemory.h" +#include "kv/KeyValueDB.h" +#include <sys/types.h> +#include "global/global_init.h" +#include "common/ceph_argparse.h" +#include "gtest/gtest.h" + +using namespace std; + +string store_path; + +class IteratorTest : public ::testing::Test +{ +public: + boost::scoped_ptr<KeyValueDB> db; + boost::scoped_ptr<KeyValueDBMemory> mock; + + void SetUp() override { + ceph_assert(!store_path.empty()); + + KeyValueDB *db_ptr = KeyValueDB::create(g_ceph_context, "leveldb", store_path); + ceph_assert(!db_ptr->create_and_open(std::cerr)); + db.reset(db_ptr); + mock.reset(new KeyValueDBMemory()); + } + + void TearDown() override { } + + ::testing::AssertionResult validate_db_clear(KeyValueDB *store) { + KeyValueDB::WholeSpaceIterator it = store->get_wholespace_iterator(); + it->seek_to_first(); + while (it->valid()) { + pair<string,string> k = it->raw_key(); + if (mock->db.count(k)) { + return ::testing::AssertionFailure() + << __func__ + << " mock store count " << mock->db.count(k) + << " key(" << k.first << "," << k.second << ")"; + } + it->next(); + } + return ::testing::AssertionSuccess(); + } + + ::testing::AssertionResult validate_db_match() { + KeyValueDB::WholeSpaceIterator it = db->get_wholespace_iterator(); + it->seek_to_first(); + while (it->valid()) { + pair<string, string> k = it->raw_key(); + if (!mock->db.count(k)) { + return ::testing::AssertionFailure() + << __func__ + << " mock db.count() " << mock->db.count(k) + << " key(" << k.first << "," << k.second << ")"; + } + + bufferlist it_bl = it->value(); + bufferlist mock_bl = mock->db[k]; + + string it_val = _bl_to_str(it_bl); + string mock_val = _bl_to_str(mock_bl); + + if (it_val != mock_val) { + return ::testing::AssertionFailure() + << __func__ + << " key(" << k.first << "," << k.second << ")" + << " mismatch db value(" << it_val << ")" + << " mock value(" << mock_val << ")"; + } + it->next(); + } + return ::testing::AssertionSuccess(); + } + + ::testing::AssertionResult validate_iterator( + KeyValueDB::WholeSpaceIterator it, + string expected_prefix, + const string &expected_key, + const string &expected_value) { + if (!it->valid()) { + return ::testing::AssertionFailure() + << __func__ + << " iterator not valid"; + } + + if (!it->raw_key_is_prefixed(expected_prefix)) { + return ::testing::AssertionFailure() + << __func__ + << " expected raw_key_is_prefixed() == TRUE" + << " got FALSE"; + } + + if (it->raw_key_is_prefixed("??__SomeUnexpectedValue__??")) { + return ::testing::AssertionFailure() + << __func__ + << " expected raw_key_is_prefixed() == FALSE" + << " got TRUE"; + } + + pair<string,string> key = it->raw_key(); + + if (expected_prefix != key.first) { + return ::testing::AssertionFailure() + << __func__ + << " expected prefix '" << expected_prefix << "'" + << " got prefix '" << key.first << "'"; + } + + if (expected_key != it->key()) { + return ::testing::AssertionFailure() + << __func__ + << " expected key '" << expected_key << "'" + << " got key '" << it->key() << "'"; + } + + if (it->key() != key.second) { + return ::testing::AssertionFailure() + << __func__ + << " key '" << it->key() << "'" + << " does not match" + << " pair key '" << key.second << "'"; + } + + if (_bl_to_str(it->value()) != expected_value) { + return ::testing::AssertionFailure() + << __func__ + << " key '(" << key.first << "," << key.second << ")''" + << " expected value '" << expected_value << "'" + << " got value '" << _bl_to_str(it->value()) << "'"; + } + + return ::testing::AssertionSuccess(); + } + + /** + * Checks if each key in the queue can be forward sequentially read from + * the iterator iter. All keys must be present and be prefixed with prefix, + * otherwise the validation will fail. + * + * Assumes that each key value must be based on the key name and generated + * by _gen_val(). + */ + void validate_prefix(KeyValueDB::WholeSpaceIterator iter, + string &prefix, deque<string> &keys) { + + while (!keys.empty()) { + ASSERT_TRUE(iter->valid()); + string expected_key = keys.front(); + keys.pop_front(); + string expected_value = _gen_val_str(expected_key); + + ASSERT_TRUE(validate_iterator(iter, prefix, + expected_key, expected_value)); + + iter->next(); + } + } + /** + * Checks if each key in the queue can be backward sequentially read from + * the iterator iter. All keys must be present and be prefixed with prefix, + * otherwise the validation will fail. + * + * Assumes that each key value must be based on the key name and generated + * by _gen_val(). + */ + void validate_prefix_backwards(KeyValueDB::WholeSpaceIterator iter, + string &prefix, deque<string> &keys) { + + while (!keys.empty()) { + ASSERT_TRUE(iter->valid()); + string expected_key = keys.front(); + keys.pop_front(); + string expected_value = _gen_val_str(expected_key); + + ASSERT_TRUE(validate_iterator(iter, prefix, + expected_key, expected_value)); + + iter->prev(); + } + } + + void clear(KeyValueDB *store) { + KeyValueDB::WholeSpaceIterator it = store->get_wholespace_iterator(); + it->seek_to_first(); + KeyValueDB::Transaction t = store->get_transaction(); + while (it->valid()) { + pair<string,string> k = it->raw_key(); + t->rmkey(k.first, k.second); + it->next(); + } + store->submit_transaction_sync(t); + } + + string _bl_to_str(bufferlist val) { + string str(val.c_str(), val.length()); + return str; + } + + string _gen_val_str(const string &key) { + ostringstream ss; + ss << "##value##" << key << "##"; + return ss.str(); + } + + bufferlist _gen_val(const string &key) { + bufferlist bl; + bl.append(_gen_val_str(key)); + return bl; + } + + void print_iterator(KeyValueDB::WholeSpaceIterator iter) { + if (!iter->valid()) { + std::cerr << __func__ << " iterator is not valid; stop." << std::endl; + return; + } + + int i = 0; + while (iter->valid()) { + pair<string,string> k = iter->raw_key(); + std::cerr << __func__ + << " pos " << (++i) + << " key (" << k.first << "," << k.second << ")" + << " value(" << _bl_to_str(iter->value()) << ")" << std::endl; + iter->next(); + } + } + + void print_db(KeyValueDB *store) { + KeyValueDB::WholeSpaceIterator it = store->get_wholespace_iterator(); + it->seek_to_first(); + print_iterator(it); + } +}; + +// ------- Remove Keys / Remove Keys By Prefix ------- +class RmKeysTest : public IteratorTest +{ +public: + string prefix1; + string prefix2; + string prefix3; + + void init(KeyValueDB *db) { + KeyValueDB::Transaction tx = db->get_transaction(); + + tx->set(prefix1, "11", _gen_val("11")); + tx->set(prefix1, "12", _gen_val("12")); + tx->set(prefix1, "13", _gen_val("13")); + tx->set(prefix2, "21", _gen_val("21")); + tx->set(prefix2, "22", _gen_val("22")); + tx->set(prefix2, "23", _gen_val("23")); + tx->set(prefix3, "31", _gen_val("31")); + tx->set(prefix3, "32", _gen_val("32")); + tx->set(prefix3, "33", _gen_val("33")); + + db->submit_transaction_sync(tx); + } + + void SetUp() override { + IteratorTest::SetUp(); + + prefix1 = "_PREFIX_1_"; + prefix2 = "_PREFIX_2_"; + prefix3 = "_PREFIX_3_"; + + clear(db.get()); + ASSERT_TRUE(validate_db_clear(db.get())); + clear(mock.get()); + ASSERT_TRUE(validate_db_match()); + + init(db.get()); + init(mock.get()); + + ASSERT_TRUE(validate_db_match()); + } + + void TearDown() override { + IteratorTest::TearDown(); + } + + + /** + * Test the transaction's rmkeys behavior when we remove a given prefix + * from the beginning of the key space, or from the end of the key space, + * or even simply in the middle. + */ + void RmKeysByPrefix(KeyValueDB *store) { + // remove prefix2 ; check if prefix1 remains, and then prefix3 + KeyValueDB::Transaction tx = store->get_transaction(); + // remove the prefix in the middle of the key space + tx->rmkeys_by_prefix(prefix2); + store->submit_transaction_sync(tx); + + deque<string> key_deque; + KeyValueDB::WholeSpaceIterator iter = store->get_wholespace_iterator(); + iter->seek_to_first(); + + // check for prefix1 + key_deque.push_back("11"); + key_deque.push_back("12"); + key_deque.push_back("13"); + validate_prefix(iter, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + + // check for prefix3 + ASSERT_TRUE(iter->valid()); + key_deque.clear(); + key_deque.push_back("31"); + key_deque.push_back("32"); + key_deque.push_back("33"); + validate_prefix(iter, prefix3, key_deque); + ASSERT_FALSE(HasFatalFailure()); + + ASSERT_FALSE(iter->valid()); + + clear(store); + ASSERT_TRUE(validate_db_clear(store)); + init(store); + + // remove prefix1 ; check if prefix2 and then prefix3 remain + tx = store->get_transaction(); + // remove the prefix at the beginning of the key space + tx->rmkeys_by_prefix(prefix1); + store->submit_transaction_sync(tx); + + iter = store->get_wholespace_iterator(); + iter->seek_to_first(); + + // check for prefix2 + key_deque.clear(); + key_deque.push_back("21"); + key_deque.push_back("22"); + key_deque.push_back("23"); + validate_prefix(iter, prefix2, key_deque); + ASSERT_FALSE(HasFatalFailure()); + + // check for prefix3 + ASSERT_TRUE(iter->valid()); + key_deque.clear(); + key_deque.push_back("31"); + key_deque.push_back("32"); + key_deque.push_back("33"); + validate_prefix(iter, prefix3, key_deque); + ASSERT_FALSE(HasFatalFailure()); + + ASSERT_FALSE(iter->valid()); + + clear(store); + ASSERT_TRUE(validate_db_clear(store)); + init(store); + + // remove prefix3 ; check if prefix1 and then prefix2 remain + tx = store->get_transaction(); + // remove the prefix at the end of the key space + tx->rmkeys_by_prefix(prefix3); + store->submit_transaction_sync(tx); + + iter = store->get_wholespace_iterator(); + iter->seek_to_first(); + + // check for prefix1 + key_deque.clear(); + key_deque.push_back("11"); + key_deque.push_back("12"); + key_deque.push_back("13"); + validate_prefix(iter, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + + // check for prefix2 + ASSERT_TRUE(iter->valid()); + key_deque.clear(); + key_deque.push_back("21"); + key_deque.push_back("22"); + key_deque.push_back("23"); + validate_prefix(iter, prefix2, key_deque); + ASSERT_FALSE(HasFatalFailure()); + + ASSERT_FALSE(iter->valid()); + } + + /** + * Test how the leveldb's whole-space iterator behaves when we remove + * keys from the store while iterating over them. + */ + void RmKeysWhileIteratingSnapshot(KeyValueDB *store, + KeyValueDB::WholeSpaceIterator iter) { + + SCOPED_TRACE("RmKeysWhileIteratingSnapshot"); + + iter->seek_to_first(); + ASSERT_TRUE(iter->valid()); + + KeyValueDB::Transaction t = store->get_transaction(); + t->rmkey(prefix1, "11"); + t->rmkey(prefix1, "12"); + t->rmkey(prefix2, "23"); + t->rmkey(prefix3, "33"); + store->submit_transaction_sync(t); + + deque<string> key_deque; + + // check for prefix1 + key_deque.push_back("11"); + key_deque.push_back("12"); + key_deque.push_back("13"); + validate_prefix(iter, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + + // check for prefix2 + key_deque.clear(); + key_deque.push_back("21"); + key_deque.push_back("22"); + key_deque.push_back("23"); + validate_prefix(iter, prefix2, key_deque); + ASSERT_FALSE(HasFatalFailure()); + + // check for prefix3 + key_deque.clear(); + key_deque.push_back("31"); + key_deque.push_back("32"); + key_deque.push_back("33"); + validate_prefix(iter, prefix3, key_deque); + ASSERT_FALSE(HasFatalFailure()); + + iter->next(); + ASSERT_FALSE(iter->valid()); + + // make sure those keys were removed from the store + KeyValueDB::WholeSpaceIterator tmp_it = store->get_wholespace_iterator(); + tmp_it->seek_to_first(); + ASSERT_TRUE(tmp_it->valid()); + + key_deque.clear(); + key_deque.push_back("13"); + validate_prefix(tmp_it, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + + ASSERT_TRUE(tmp_it->valid()); + key_deque.clear(); + key_deque.push_back("21"); + key_deque.push_back("22"); + validate_prefix(tmp_it, prefix2, key_deque); + ASSERT_FALSE(HasFatalFailure()); + + ASSERT_TRUE(tmp_it->valid()); + key_deque.clear(); + key_deque.push_back("31"); + key_deque.push_back("32"); + validate_prefix(tmp_it, prefix3, key_deque); + ASSERT_FALSE(HasFatalFailure()); + + ASSERT_FALSE(tmp_it->valid()); + } +}; + +TEST_F(RmKeysTest, RmKeysByPrefixLevelDB) +{ + SCOPED_TRACE("LevelDB"); + RmKeysByPrefix(db.get()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(RmKeysTest, RmKeysByPrefixMockDB) +{ + SCOPED_TRACE("Mock DB"); + RmKeysByPrefix(mock.get()); + ASSERT_FALSE(HasFatalFailure()); +} + +/** + * If you refer to function RmKeysTest::RmKeysWhileIteratingSnapshot(), + * you will notice that we seek the iterator to the first key, and then + * we go on to remove several keys from the underlying store, including + * the first couple keys. + * + * We would expect that during this test, as soon as we removed the keys + * from the store, the iterator would get invalid, or cause some sort of + * unexpected mess. + * + * Instead, the current version of leveldb handles it perfectly, by making + * the iterator to use a snapshot instead of the store's real state. This + * way, LevelDBStore's whole-space iterator will behave much like its own + * whole-space snapshot iterator. + * + * However, this particular behavior of the iterator hasn't been documented + * on leveldb, and we should assume that it can be changed at any point in + * time. + * + * Therefore, we keep this test, being exactly the same as the one for the + * whole-space snapshot iterator, as we currently assume they should behave + * identically. If this test fails, at some point, and the whole-space + * snapshot iterator passes, then it probably means that leveldb changed + * how its iterator behaves. + */ +TEST_F(RmKeysTest, RmKeysWhileIteratingLevelDB) +{ + SCOPED_TRACE("LevelDB -- WholeSpaceIterator"); + RmKeysWhileIteratingSnapshot(db.get(), db->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(RmKeysTest, RmKeysWhileIteratingMockDB) +{ + std::cout << "There is no safe way to test key removal while iterating\n" + << "over the mock store without using snapshots" << std::endl; +} + +// ------- Set Keys / Update Values ------- +class SetKeysTest : public IteratorTest +{ +public: + string prefix1; + string prefix2; + + void init(KeyValueDB *db) { + KeyValueDB::Transaction tx = db->get_transaction(); + + tx->set(prefix1, "aaa", _gen_val("aaa")); + tx->set(prefix1, "ccc", _gen_val("ccc")); + tx->set(prefix1, "eee", _gen_val("eee")); + tx->set(prefix2, "vvv", _gen_val("vvv")); + tx->set(prefix2, "xxx", _gen_val("xxx")); + tx->set(prefix2, "zzz", _gen_val("zzz")); + + db->submit_transaction_sync(tx); + } + + void SetUp() override { + IteratorTest::SetUp(); + + prefix1 = "_PREFIX_1_"; + prefix2 = "_PREFIX_2_"; + + clear(db.get()); + ASSERT_TRUE(validate_db_clear(db.get())); + clear(mock.get()); + ASSERT_TRUE(validate_db_match()); + + init(db.get()); + init(mock.get()); + + ASSERT_TRUE(validate_db_match()); + } + + void TearDown() override { + IteratorTest::TearDown(); + } + + /** + * Make sure that the iterator picks on new keys added if it hasn't yet + * iterated away from that position. + * + * This should only happen for the whole-space iterator when not using + * the snapshot version. + * + * We don't need to test the validity of all elements, but we do test + * inserting while moving from the first element to the last, using next() + * to move forward, and then we test the same behavior while iterating + * from the last element to the first, using prev() to move backwards. + */ + void SetKeysWhileIterating(KeyValueDB *store, + KeyValueDB::WholeSpaceIterator iter) { + iter->seek_to_first(); + ASSERT_TRUE(iter->valid()); + ASSERT_TRUE(validate_iterator(iter, prefix1, "aaa", + _gen_val_str("aaa"))); + iter->next(); + ASSERT_TRUE(iter->valid()); + ASSERT_TRUE(validate_iterator(iter, prefix1, "ccc", + _bl_to_str(_gen_val("ccc")))); + + // insert new key 'ddd' after 'ccc' and before 'eee' + KeyValueDB::Transaction tx = store->get_transaction(); + tx->set(prefix1, "ddd", _gen_val("ddd")); + store->submit_transaction_sync(tx); + + iter->next(); + ASSERT_TRUE(iter->valid()); + ASSERT_TRUE(validate_iterator(iter, prefix1, "ddd", + _gen_val_str("ddd"))); + + iter->seek_to_last(); + ASSERT_TRUE(iter->valid()); + tx = store->get_transaction(); + tx->set(prefix2, "yyy", _gen_val("yyy")); + store->submit_transaction_sync(tx); + + iter->prev(); + ASSERT_TRUE(iter->valid()); + ASSERT_TRUE(validate_iterator(iter, prefix2, + "yyy", _gen_val_str("yyy"))); + } + + /** + * Make sure that the whole-space snapshot iterator does not pick on new keys + * added to the store since we created the iterator, thus guaranteeing + * read-consistency. + * + * We don't need to test the validity of all elements, but we do test + * inserting while moving from the first element to the last, using next() + * to move forward, and then we test the same behavior while iterating + * from the last element to the first, using prev() to move backwards. + */ + void SetKeysWhileIteratingSnapshot(KeyValueDB *store, + KeyValueDB::WholeSpaceIterator iter) { + iter->seek_to_first(); + ASSERT_TRUE(iter->valid()); + ASSERT_TRUE(validate_iterator(iter, prefix1, "aaa", + _gen_val_str("aaa"))); + iter->next(); + ASSERT_TRUE(iter->valid()); + ASSERT_TRUE(validate_iterator(iter, prefix1, "ccc", + _bl_to_str(_gen_val("ccc")))); + + // insert new key 'ddd' after 'ccc' and before 'eee' + KeyValueDB::Transaction tx = store->get_transaction(); + tx->set(prefix1, "ddd", _gen_val("ddd")); + store->submit_transaction_sync(tx); + + iter->next(); + ASSERT_TRUE(iter->valid()); + ASSERT_TRUE(validate_iterator(iter, prefix1, "eee", + _gen_val_str("eee"))); + + iter->seek_to_last(); + ASSERT_TRUE(iter->valid()); + tx = store->get_transaction(); + tx->set(prefix2, "yyy", _gen_val("yyy")); + store->submit_transaction_sync(tx); + + iter->prev(); + ASSERT_TRUE(iter->valid()); + ASSERT_TRUE(validate_iterator(iter, prefix2, + "xxx", _gen_val_str("xxx"))); + } + + /** + * Make sure that the whole-space iterator is able to read values changed on + * the store, even after we moved to the updated position. + * + * This should only be possible when not using the whole-space snapshot + * version of the iterator. + */ + void UpdateValuesWhileIterating(KeyValueDB *store, + KeyValueDB::WholeSpaceIterator iter) { + iter->seek_to_first(); + ASSERT_TRUE(iter->valid()); + ASSERT_TRUE(validate_iterator(iter, prefix1, + "aaa", _gen_val_str("aaa"))); + + KeyValueDB::Transaction tx = store->get_transaction(); + tx->set(prefix1, "aaa", _gen_val("aaa_1")); + store->submit_transaction_sync(tx); + + ASSERT_TRUE(validate_iterator(iter, prefix1, + "aaa", _gen_val_str("aaa_1"))); + + iter->seek_to_last(); + ASSERT_TRUE(iter->valid()); + ASSERT_TRUE(validate_iterator(iter, prefix2, + "zzz", _gen_val_str("zzz"))); + + tx = store->get_transaction(); + tx->set(prefix2, "zzz", _gen_val("zzz_1")); + store->submit_transaction_sync(tx); + + ASSERT_TRUE(validate_iterator(iter, prefix2, + "zzz", _gen_val_str("zzz_1"))); + } + + /** + * Make sure that the whole-space iterator is able to read values changed on + * the store, even after we moved to the updated position. + * + * This should only be possible when not using the whole-space snapshot + * version of the iterator. + */ + void UpdateValuesWhileIteratingSnapshot( + KeyValueDB *store, + KeyValueDB::WholeSpaceIterator iter) { + iter->seek_to_first(); + ASSERT_TRUE(iter->valid()); + ASSERT_TRUE(validate_iterator(iter, prefix1, + "aaa", _gen_val_str("aaa"))); + + KeyValueDB::Transaction tx = store->get_transaction(); + tx->set(prefix1, "aaa", _gen_val("aaa_1")); + store->submit_transaction_sync(tx); + + ASSERT_TRUE(validate_iterator(iter, prefix1, + "aaa", _gen_val_str("aaa"))); + + iter->seek_to_last(); + ASSERT_TRUE(iter->valid()); + ASSERT_TRUE(validate_iterator(iter, prefix2, + "zzz", _gen_val_str("zzz"))); + + tx = store->get_transaction(); + tx->set(prefix2, "zzz", _gen_val("zzz_1")); + store->submit_transaction_sync(tx); + + ASSERT_TRUE(validate_iterator(iter, prefix2, + "zzz", _gen_val_str("zzz"))); + + // check those values were really changed in the store + KeyValueDB::WholeSpaceIterator tmp_iter = store->get_wholespace_iterator(); + tmp_iter->seek_to_first(); + ASSERT_TRUE(tmp_iter->valid()); + ASSERT_TRUE(validate_iterator(tmp_iter, prefix1, + "aaa", _gen_val_str("aaa_1"))); + tmp_iter->seek_to_last(); + ASSERT_TRUE(tmp_iter->valid()); + ASSERT_TRUE(validate_iterator(tmp_iter, prefix2, + "zzz", _gen_val_str("zzz_1"))); + } + + +}; + +TEST_F(SetKeysTest, DISABLED_SetKeysWhileIteratingLevelDB) +{ + SCOPED_TRACE("LevelDB: SetKeysWhileIteratingLevelDB"); + SetKeysWhileIterating(db.get(), db->get_wholespace_iterator()); + ASSERT_TRUE(HasFatalFailure()); +} + +TEST_F(SetKeysTest, SetKeysWhileIteratingMockDB) +{ + SCOPED_TRACE("Mock DB: SetKeysWhileIteratingMockDB"); + SetKeysWhileIterating(mock.get(), mock->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(SetKeysTest, DISABLED_UpdateValuesWhileIteratingLevelDB) +{ + SCOPED_TRACE("LevelDB: UpdateValuesWhileIteratingLevelDB"); + UpdateValuesWhileIterating(db.get(), db->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(SetKeysTest, UpdateValuesWhileIteratingMockDB) +{ + SCOPED_TRACE("MockDB: UpdateValuesWhileIteratingMockDB"); + UpdateValuesWhileIterating(mock.get(), mock->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +class BoundsTest : public IteratorTest +{ +public: + string prefix1; + string prefix2; + string prefix3; + + void init(KeyValueDB *store) { + KeyValueDB::Transaction tx = store->get_transaction(); + + tx->set(prefix1, "aaa", _gen_val("aaa")); + tx->set(prefix1, "ccc", _gen_val("ccc")); + tx->set(prefix1, "eee", _gen_val("eee")); + tx->set(prefix2, "vvv", _gen_val("vvv")); + tx->set(prefix2, "xxx", _gen_val("xxx")); + tx->set(prefix2, "zzz", _gen_val("zzz")); + tx->set(prefix3, "aaa", _gen_val("aaa")); + tx->set(prefix3, "mmm", _gen_val("mmm")); + tx->set(prefix3, "yyy", _gen_val("yyy")); + + store->submit_transaction_sync(tx); + } + + void SetUp() override { + IteratorTest::SetUp(); + + prefix1 = "_PREFIX_1_"; + prefix2 = "_PREFIX_2_"; + prefix3 = "_PREFIX_4_"; + + clear(db.get()); + ASSERT_TRUE(validate_db_clear(db.get())); + clear(mock.get()); + ASSERT_TRUE(validate_db_match()); + + init(db.get()); + init(mock.get()); + + ASSERT_TRUE(validate_db_match()); + } + + void TearDown() override { + IteratorTest::TearDown(); + } + + void LowerBoundWithEmptyKeyOnWholeSpaceIterator( + KeyValueDB::WholeSpaceIterator iter) { + deque<string> key_deque; + // see what happens when we have an empty key and try to get to the + // first available prefix + iter->lower_bound(prefix1, ""); + ASSERT_TRUE(iter->valid()); + + key_deque.push_back("aaa"); + key_deque.push_back("ccc"); + key_deque.push_back("eee"); + validate_prefix(iter, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + // if we got here without problems, then it is safe to assume the + // remaining prefixes are intact. + + // see what happens when we have an empty key and try to get to the + // middle of the key-space + iter->lower_bound(prefix2, ""); + ASSERT_TRUE(iter->valid()); + key_deque.clear(); + + key_deque.push_back("vvv"); + key_deque.push_back("xxx"); + key_deque.push_back("zzz"); + validate_prefix(iter, prefix2, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + // if we got here without problems, then it is safe to assume the + // remaining prefixes are intact. + + // see what happens when we have an empty key and try to get to the + // last prefix on the key-space + iter->lower_bound(prefix3, ""); + ASSERT_TRUE(iter->valid()); + key_deque.clear(); + + key_deque.push_back("aaa"); + key_deque.push_back("mmm"); + key_deque.push_back("yyy"); + validate_prefix(iter, prefix3, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_FALSE(iter->valid()); + // we reached the end of the key_space, so the iterator should no longer + // be valid + + // see what happens when we look for an inexistent prefix, that will + // compare higher than the existing prefixes, with an empty key + // expected: reach the store's end; iterator becomes invalid + iter->lower_bound("_PREFIX_9_", ""); + ASSERT_FALSE(iter->valid()); + + // see what happens when we look for an inexistent prefix, that will + // compare lower than the existing prefixes, with an empty key + // expected: find the first prefix; iterator is valid + iter->lower_bound("_PREFIX_0_", ""); + ASSERT_TRUE(iter->valid()); + key_deque.clear(); + key_deque.push_back("aaa"); + key_deque.push_back("ccc"); + key_deque.push_back("eee"); + validate_prefix(iter, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + + // see what happens when we look for an empty prefix (that should compare + // lower than any existing prefixes) + // expected: find the first prefix; iterator is valid + iter->lower_bound("", ""); + ASSERT_TRUE(iter->valid()); + key_deque.push_back("aaa"); + key_deque.push_back("ccc"); + key_deque.push_back("eee"); + validate_prefix(iter, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + } + + void LowerBoundWithEmptyPrefixOnWholeSpaceIterator( + KeyValueDB::WholeSpaceIterator iter) { + deque<string> key_deque; + // check for an empty prefix, with key 'aaa'. Since this key is shared + // among two different prefixes, it is relevant to check which will be + // found first. + // expected: find key (prefix1, aaa); iterator is valid + iter->lower_bound("", "aaa"); + ASSERT_TRUE(iter->valid()); + + key_deque.push_back("aaa"); + key_deque.push_back("ccc"); + key_deque.push_back("eee"); + validate_prefix(iter, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + // since we found prefix1, it is safe to assume that the remaining + // prefixes (prefix2 and prefix3) will follow + + // any lower_bound operation with an empty prefix should always put the + // iterator in the first key in the key-space, despite what key is + // specified. This means that looking for ("","AAAAAAAAAA") should + // also position the iterator on (prefix1, aaa). + // expected: find key (prefix1, aaa); iterator is valid + iter->lower_bound("", "AAAAAAAAAA"); + ASSERT_TRUE(iter->valid()); + key_deque.clear(); + key_deque.push_back("aaa"); + key_deque.push_back("ccc"); + key_deque.push_back("eee"); + validate_prefix(iter, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + + // note: this test is a duplicate of the one in the function above. Why? + // Well, because it also fits here (being its prefix empty), and one could + // very well run solely this test (instead of the whole battery) and would + // certainly expect this case to be tested. + + // see what happens when we look for an empty prefix (that should compare + // lower than any existing prefixes) + // expected: find the first prefix; iterator is valid + iter->lower_bound("", ""); + ASSERT_TRUE(iter->valid()); + key_deque.push_back("aaa"); + key_deque.push_back("ccc"); + key_deque.push_back("eee"); + validate_prefix(iter, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + } + + void LowerBoundOnWholeSpaceIterator( + KeyValueDB::WholeSpaceIterator iter) { + deque<string> key_deque; + // check that we find the first key in the store + // expected: find (prefix1, aaa); iterator is valid + iter->lower_bound(prefix1, "aaa"); + ASSERT_TRUE(iter->valid()); + key_deque.push_back("aaa"); + validate_prefix(iter, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + + // check that we find the last key in the store + // expected: find (prefix3, yyy); iterator is valid + iter->lower_bound(prefix3, "yyy"); + ASSERT_TRUE(iter->valid()); + key_deque.clear(); + key_deque.push_back("yyy"); + validate_prefix(iter, prefix3, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_FALSE(iter->valid()); + + // check that looking for non-existent prefix '_PREFIX_0_' will + // always result in the first value of prefix1 (prefix1,"aaa") + // expected: find (prefix1, aaa); iterator is valid + iter->lower_bound("_PREFIX_0_", "AAAAA"); + ASSERT_TRUE(iter->valid()); + key_deque.clear(); + key_deque.push_back("aaa"); + validate_prefix(iter, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + + // check that looking for non-existent prefix '_PREFIX_3_' will + // always result in the first value of prefix3 (prefix4,"aaa") + // expected: find (prefix3, aaa); iterator is valid + iter->lower_bound("_PREFIX_3_", "AAAAA"); + ASSERT_TRUE(iter->valid()); + key_deque.clear(); + key_deque.push_back("aaa"); + validate_prefix(iter, prefix3, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + + // check that looking for non-existent prefix '_PREFIX_9_' will + // always result in an invalid iterator. + // expected: iterator is invalid + iter->lower_bound("_PREFIX_9_", "AAAAA"); + ASSERT_FALSE(iter->valid()); + } + + void UpperBoundWithEmptyKeyOnWholeSpaceIterator( + KeyValueDB::WholeSpaceIterator iter) { + deque<string> key_deque; + // check that looking for (prefix1, "") will result in finding + // the first key in prefix1 (prefix1, "aaa") + // expected: find (prefix1, aaa); iterator is valid + iter->upper_bound(prefix1, ""); + key_deque.push_back("aaa"); + validate_prefix(iter, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + + // check that looking for (prefix2, "") will result in finding + // the first key in prefix2 (prefix2, vvv) + // expected: find (prefix2, aaa); iterator is valid + iter->upper_bound(prefix2, ""); + key_deque.push_back("vvv"); + validate_prefix(iter, prefix2, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + + + // check that looking for (prefix3, "") will result in finding + // the first key in prefix3 (prefix3, aaa) + // expected: find (prefix3, aaa); iterator is valid + iter->upper_bound(prefix3, ""); + key_deque.push_back("aaa"); + validate_prefix(iter, prefix3, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + + // see what happens when we look for an inexistent prefix, that will + // compare higher than the existing prefixes, with an empty key + // expected: reach the store's end; iterator becomes invalid + iter->upper_bound("_PREFIX_9_", ""); + ASSERT_FALSE(iter->valid()); + + // see what happens when we look for an inexistent prefix, that will + // compare lower than the existing prefixes, with an empty key + // expected: find the first prefix; iterator is valid + iter->upper_bound("_PREFIX_0_", ""); + ASSERT_TRUE(iter->valid()); + key_deque.clear(); + key_deque.push_back("aaa"); + validate_prefix(iter, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + + // see what happens when we look for an empty prefix (that should compare + // lower than any existing prefixes) + // expected: find the first prefix; iterator is valid + iter->upper_bound("", ""); + ASSERT_TRUE(iter->valid()); + key_deque.push_back("aaa"); + validate_prefix(iter, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + } + + void UpperBoundWithEmptyPrefixOnWholeSpaceIterator( + KeyValueDB::WholeSpaceIterator iter) { + deque<string> key_deque; + // check for an empty prefix, with key 'aaa'. Since this key is shared + // among two different prefixes, it is relevant to check which will be + // found first. + // expected: find key (prefix1, aaa); iterator is valid + iter->upper_bound("", "aaa"); + ASSERT_TRUE(iter->valid()); + key_deque.push_back("aaa"); + key_deque.push_back("ccc"); + key_deque.push_back("eee"); + validate_prefix(iter, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + + // any upper_bound operation with an empty prefix should always put the + // iterator in the first key whose prefix compares greater, despite the + // key that is specified. This means that looking for ("","AAAAAAAAAA") + // should position the iterator on (prefix1, aaa). + // expected: find key (prefix1, aaa); iterator is valid + iter->upper_bound("", "AAAAAAAAAA"); + ASSERT_TRUE(iter->valid()); + key_deque.clear(); + key_deque.push_back("aaa"); + validate_prefix(iter, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + + // note: this test is a duplicate of the one in the function above. Why? + // Well, because it also fits here (being its prefix empty), and one could + // very well run solely this test (instead of the whole battery) and would + // certainly expect this case to be tested. + + // see what happens when we look for an empty prefix (that should compare + // lower than any existing prefixes) + // expected: find the first prefix; iterator is valid + iter->upper_bound("", ""); + ASSERT_TRUE(iter->valid()); + key_deque.push_back("aaa"); + validate_prefix(iter, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + } + + void UpperBoundOnWholeSpaceIterator( + KeyValueDB::WholeSpaceIterator iter) { + deque<string> key_deque; + // check that we find the second key in the store + // expected: find (prefix1, ccc); iterator is valid + iter->upper_bound(prefix1, "bbb"); + ASSERT_TRUE(iter->valid()); + key_deque.push_back("ccc"); + validate_prefix(iter, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + + // check that we find the last key in the store + // expected: find (prefix3, yyy); iterator is valid + iter->upper_bound(prefix3, "xxx"); + ASSERT_TRUE(iter->valid()); + key_deque.clear(); + key_deque.push_back("yyy"); + validate_prefix(iter, prefix3, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_FALSE(iter->valid()); + + // check that looking for non-existent prefix '_PREFIX_0_' will + // always result in the first value of prefix1 (prefix1,"aaa") + // expected: find (prefix1, aaa); iterator is valid + iter->upper_bound("_PREFIX_0_", "AAAAA"); + ASSERT_TRUE(iter->valid()); + key_deque.clear(); + key_deque.push_back("aaa"); + validate_prefix(iter, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + + // check that looking for non-existent prefix '_PREFIX_3_' will + // always result in the first value of prefix3 (prefix3,"aaa") + // expected: find (prefix3, aaa); iterator is valid + iter->upper_bound("_PREFIX_3_", "AAAAA"); + ASSERT_TRUE(iter->valid()); + key_deque.clear(); + key_deque.push_back("aaa"); + validate_prefix(iter, prefix3, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + + // check that looking for non-existent prefix '_PREFIX_9_' will + // always result in an invalid iterator. + // expected: iterator is invalid + iter->upper_bound("_PREFIX_9_", "AAAAA"); + ASSERT_FALSE(iter->valid()); + } +}; + +TEST_F(BoundsTest, LowerBoundWithEmptyKeyOnWholeSpaceIteratorLevelDB) +{ + SCOPED_TRACE("LevelDB: Lower Bound, Empty Key, Whole-Space Iterator"); + LowerBoundWithEmptyKeyOnWholeSpaceIterator(db->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(BoundsTest, LowerBoundWithEmptyKeyOnWholeSpaceIteratorMockDB) +{ + SCOPED_TRACE("MockDB: Lower Bound, Empty Key, Whole-Space Iterator"); + LowerBoundWithEmptyKeyOnWholeSpaceIterator(mock->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(BoundsTest, LowerBoundWithEmptyPrefixOnWholeSpaceIteratorLevelDB) +{ + SCOPED_TRACE("LevelDB: Lower Bound, Empty Prefix, Whole-Space Iterator"); + LowerBoundWithEmptyPrefixOnWholeSpaceIterator(db->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(BoundsTest, LowerBoundWithEmptyPrefixOnWholeSpaceIteratorMockDB) +{ + SCOPED_TRACE("MockDB: Lower Bound, Empty Prefix, Whole-Space Iterator"); + LowerBoundWithEmptyPrefixOnWholeSpaceIterator(mock->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(BoundsTest, LowerBoundOnWholeSpaceIteratorLevelDB) +{ + SCOPED_TRACE("LevelDB: Lower Bound, Whole-Space Iterator"); + LowerBoundOnWholeSpaceIterator(db->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(BoundsTest, LowerBoundOnWholeSpaceIteratorMockDB) +{ + SCOPED_TRACE("MockDB: Lower Bound, Whole-Space Iterator"); + LowerBoundOnWholeSpaceIterator(mock->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(BoundsTest, UpperBoundWithEmptyKeyOnWholeSpaceIteratorLevelDB) +{ + SCOPED_TRACE("LevelDB: Upper Bound, Empty Key, Whole-Space Iterator"); + UpperBoundWithEmptyKeyOnWholeSpaceIterator(db->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(BoundsTest, UpperBoundWithEmptyKeyOnWholeSpaceIteratorMockDB) +{ + SCOPED_TRACE("MockDB: Upper Bound, Empty Key, Whole-Space Iterator"); + UpperBoundWithEmptyKeyOnWholeSpaceIterator(mock->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(BoundsTest, UpperBoundWithEmptyPrefixOnWholeSpaceIteratorLevelDB) +{ + SCOPED_TRACE("LevelDB: Upper Bound, Empty Prefix, Whole-Space Iterator"); + UpperBoundWithEmptyPrefixOnWholeSpaceIterator(db->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(BoundsTest, UpperBoundWithEmptyPrefixOnWholeSpaceIteratorMockDB) +{ + SCOPED_TRACE("MockDB: Upper Bound, Empty Prefix, Whole-Space Iterator"); + UpperBoundWithEmptyPrefixOnWholeSpaceIterator(mock->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(BoundsTest, UpperBoundOnWholeSpaceIteratorLevelDB) +{ + SCOPED_TRACE("LevelDB: Upper Bound, Whole-Space Iterator"); + UpperBoundOnWholeSpaceIterator(db->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(BoundsTest, UpperBoundOnWholeSpaceIteratorMockDB) +{ + SCOPED_TRACE("MockDB: Upper Bound, Whole-Space Iterator"); + UpperBoundOnWholeSpaceIterator(mock->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + + +class SeeksTest : public IteratorTest +{ +public: + string prefix0; + string prefix1; + string prefix2; + string prefix3; + string prefix4; + string prefix5; + + void init(KeyValueDB *store) { + KeyValueDB::Transaction tx = store->get_transaction(); + + tx->set(prefix1, "aaa", _gen_val("aaa")); + tx->set(prefix1, "ccc", _gen_val("ccc")); + tx->set(prefix1, "eee", _gen_val("eee")); + tx->set(prefix2, "vvv", _gen_val("vvv")); + tx->set(prefix2, "xxx", _gen_val("xxx")); + tx->set(prefix2, "zzz", _gen_val("zzz")); + tx->set(prefix4, "aaa", _gen_val("aaa")); + tx->set(prefix4, "mmm", _gen_val("mmm")); + tx->set(prefix4, "yyy", _gen_val("yyy")); + + store->submit_transaction_sync(tx); + } + + void SetUp() override { + IteratorTest::SetUp(); + + prefix0 = "_PREFIX_0_"; + prefix1 = "_PREFIX_1_"; + prefix2 = "_PREFIX_2_"; + prefix3 = "_PREFIX_3_"; + prefix4 = "_PREFIX_4_"; + prefix5 = "_PREFIX_5_"; + + clear(db.get()); + ASSERT_TRUE(validate_db_clear(db.get())); + clear(mock.get()); + ASSERT_TRUE(validate_db_match()); + + init(db.get()); + init(mock.get()); + + ASSERT_TRUE(validate_db_match()); + } + + void TearDown() override { + IteratorTest::TearDown(); + } + + + void SeekToFirstOnWholeSpaceIterator( + KeyValueDB::WholeSpaceIterator iter) { + iter->seek_to_first(); + ASSERT_TRUE(iter->valid()); + deque<string> key_deque; + key_deque.push_back("aaa"); + key_deque.push_back("ccc"); + key_deque.push_back("eee"); + validate_prefix(iter, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + } + + void SeekToFirstWithPrefixOnWholeSpaceIterator( + KeyValueDB::WholeSpaceIterator iter) { + deque<string> key_deque; + + // if the prefix is empty, we must end up seeking to the first key. + // expected: seek to (prefix1, aaa); iterator is valid + iter->seek_to_first(""); + ASSERT_TRUE(iter->valid()); + key_deque.push_back("aaa"); + validate_prefix(iter, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + + // try seeking to non-existent prefix that compares lower than the + // first available prefix + // expected: seek to (prefix1, aaa); iterator is valid + iter->seek_to_first(prefix0); + ASSERT_TRUE(iter->valid()); + key_deque.clear(); + key_deque.push_back("aaa"); + validate_prefix(iter, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + + // try seeking to non-existent prefix + // expected: seek to (prefix4, aaa); iterator is valid + iter->seek_to_first(prefix3); + ASSERT_TRUE(iter->valid()); + key_deque.clear(); + key_deque.push_back("aaa"); + validate_prefix(iter, prefix4, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + + // try seeking to non-existent prefix that compares greater than the + // last available prefix + // expected: iterator is invalid + iter->seek_to_first(prefix5); + ASSERT_FALSE(iter->valid()); + + // try seeking to the first prefix and make sure we end up in its first + // position + // expected: seek to (prefix1,aaa); iterator is valid + iter->seek_to_first(prefix1); + ASSERT_TRUE(iter->valid()); + key_deque.clear(); + key_deque.push_back("aaa"); + validate_prefix(iter, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + + // try seeking to the second prefix and make sure we end up in its + // first position + // expected: seek to (prefix2,vvv); iterator is valid + iter->seek_to_first(prefix2); + ASSERT_TRUE(iter->valid()); + key_deque.clear(); + key_deque.push_back("vvv"); + validate_prefix(iter, prefix2, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + + // try seeking to the last prefix and make sure we end up in its + // first position + // expected: seek to (prefix4,aaa); iterator is valid + iter->seek_to_first(prefix4); + ASSERT_TRUE(iter->valid()); + key_deque.clear(); + key_deque.push_back("aaa"); + validate_prefix(iter, prefix4, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + } + + void SeekToLastOnWholeSpaceIterator( + KeyValueDB::WholeSpaceIterator iter) { + deque<string> key_deque; + iter->seek_to_last(); + key_deque.push_back("yyy"); + validate_prefix(iter, prefix4, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_FALSE(iter->valid()); + } + + void SeekToLastWithPrefixOnWholeSpaceIterator( + KeyValueDB::WholeSpaceIterator iter) { + deque<string> key_deque; + + // if the prefix is empty, we must end up seeking to last position + // that has an empty prefix, or to the previous position to the first + // position whose prefix compares higher than empty. + // expected: iterator is invalid (because (prefix1,aaa) is the first + // position that compared higher than an empty prefix) + iter->seek_to_last(""); + ASSERT_FALSE(iter->valid()); + + // try seeking to non-existent prefix that compares lower than the + // first available prefix + // expected: iterator is invalid (because (prefix1,aaa) is the first + // position that compared higher than prefix0) + iter->seek_to_last(prefix0); + ASSERT_FALSE(iter->valid()); + + // try seeking to non-existent prefix + // expected: seek to (prefix2, zzz); iterator is valid + iter->seek_to_last(prefix3); + ASSERT_TRUE(iter->valid()); + key_deque.clear(); + key_deque.push_back("zzz"); + validate_prefix(iter, prefix2, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + + // try seeking to non-existent prefix that compares greater than the + // last available prefix + // expected: iterator is in the last position of the store; + // i.e., (prefix4,yyy) + iter->seek_to_last(prefix5); + ASSERT_TRUE(iter->valid()); + key_deque.clear(); + key_deque.push_back("yyy"); + validate_prefix(iter, prefix4, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_FALSE(iter->valid()); + + // try seeking to the first prefix and make sure we end up in its last + // position + // expected: seek to (prefix1,eee); iterator is valid + iter->seek_to_last(prefix1); + ASSERT_TRUE(iter->valid()); + key_deque.clear(); + key_deque.push_back("eee"); + validate_prefix(iter, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + + // try seeking to the second prefix and make sure we end up in its + // last position + // expected: seek to (prefix2,vvv); iterator is valid + iter->seek_to_last(prefix2); + ASSERT_TRUE(iter->valid()); + key_deque.clear(); + key_deque.push_back("zzz"); + validate_prefix(iter, prefix2, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_TRUE(iter->valid()); + + // try seeking to the last prefix and make sure we end up in its + // last position + // expected: seek to (prefix4,aaa); iterator is valid + iter->seek_to_last(prefix4); + ASSERT_TRUE(iter->valid()); + key_deque.clear(); + key_deque.push_back("yyy"); + validate_prefix(iter, prefix4, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_FALSE(iter->valid()); + } +}; + +TEST_F(SeeksTest, SeekToFirstOnWholeSpaceIteratorLevelDB) { + SCOPED_TRACE("LevelDB: Seek To First, Whole Space Iterator"); + SeekToFirstOnWholeSpaceIterator(db->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(SeeksTest, SeekToFirstOnWholeSpaceIteratorMockDB) { + SCOPED_TRACE("MockDB: Seek To First, Whole Space Iterator"); + SeekToFirstOnWholeSpaceIterator(mock->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(SeeksTest, SeekToFirstWithPrefixOnWholeSpaceIteratorLevelDB) { + SCOPED_TRACE("LevelDB: Seek To First, With Prefix, Whole Space Iterator"); + SeekToFirstWithPrefixOnWholeSpaceIterator(db->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(SeeksTest, SeekToFirstWithPrefixOnWholeSpaceIteratorMockDB) { + SCOPED_TRACE("MockDB: Seek To First, With Prefix, Whole Space Iterator"); + SeekToFirstWithPrefixOnWholeSpaceIterator(mock->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(SeeksTest, SeekToLastOnWholeSpaceIteratorLevelDB) { + SCOPED_TRACE("LevelDB: Seek To Last, Whole Space Iterator"); + SeekToLastOnWholeSpaceIterator(db->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(SeeksTest, SeekToLastOnWholeSpaceIteratorMockDB) { + SCOPED_TRACE("MockDB: Seek To Last, Whole Space Iterator"); + SeekToLastOnWholeSpaceIterator(mock->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(SeeksTest, SeekToLastWithPrefixOnWholeSpaceIteratorLevelDB) { + SCOPED_TRACE("LevelDB: Seek To Last, With Prefix, Whole Space Iterator"); + SeekToLastWithPrefixOnWholeSpaceIterator(db->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(SeeksTest, SeekToLastWithPrefixOnWholeSpaceIteratorMockDB) { + SCOPED_TRACE("MockDB: Seek To Last, With Prefix, Whole Space Iterator"); + SeekToLastWithPrefixOnWholeSpaceIterator(mock->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +class KeySpaceIteration : public IteratorTest +{ +public: + string prefix1; + + void init(KeyValueDB *store) { + KeyValueDB::Transaction tx = store->get_transaction(); + + tx->set(prefix1, "aaa", _gen_val("aaa")); + tx->set(prefix1, "vvv", _gen_val("vvv")); + tx->set(prefix1, "zzz", _gen_val("zzz")); + + store->submit_transaction_sync(tx); + } + + void SetUp() override { + IteratorTest::SetUp(); + + prefix1 = "_PREFIX_1_"; + + clear(db.get()); + ASSERT_TRUE(validate_db_clear(db.get())); + clear(mock.get()); + ASSERT_TRUE(validate_db_match()); + + init(db.get()); + init(mock.get()); + + ASSERT_TRUE(validate_db_match()); + } + + void TearDown() override { + IteratorTest::TearDown(); + } + + void ForwardIteration(KeyValueDB::WholeSpaceIterator iter) { + deque<string> key_deque; + iter->seek_to_first(); + key_deque.push_back("aaa"); + key_deque.push_back("vvv"); + key_deque.push_back("zzz"); + validate_prefix(iter, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_FALSE(iter->valid()); + } + + void BackwardIteration(KeyValueDB::WholeSpaceIterator iter) { + deque<string> key_deque; + iter->seek_to_last(); + key_deque.push_back("zzz"); + key_deque.push_back("vvv"); + key_deque.push_back("aaa"); + validate_prefix_backwards(iter, prefix1, key_deque); + ASSERT_FALSE(HasFatalFailure()); + ASSERT_FALSE(iter->valid()); + } +}; + +TEST_F(KeySpaceIteration, ForwardIterationLevelDB) +{ + SCOPED_TRACE("LevelDB: Forward Iteration, Whole Space Iterator"); + ForwardIteration(db->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(KeySpaceIteration, ForwardIterationMockDB) { + SCOPED_TRACE("MockDB: Forward Iteration, Whole Space Iterator"); + ForwardIteration(mock->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(KeySpaceIteration, BackwardIterationLevelDB) +{ + SCOPED_TRACE("LevelDB: Backward Iteration, Whole Space Iterator"); + BackwardIteration(db->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(KeySpaceIteration, BackwardIterationMockDB) { + SCOPED_TRACE("MockDB: Backward Iteration, Whole Space Iterator"); + BackwardIteration(mock->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +class EmptyStore : public IteratorTest +{ +public: + void SetUp() override { + IteratorTest::SetUp(); + + clear(db.get()); + ASSERT_TRUE(validate_db_clear(db.get())); + clear(mock.get()); + ASSERT_TRUE(validate_db_match()); + } + + void SeekToFirst(KeyValueDB::WholeSpaceIterator iter) { + // expected: iterator is invalid + iter->seek_to_first(); + ASSERT_FALSE(iter->valid()); + } + + void SeekToFirstWithPrefix(KeyValueDB::WholeSpaceIterator iter) { + // expected: iterator is invalid + iter->seek_to_first("prefix"); + ASSERT_FALSE(iter->valid()); + } + + void SeekToLast(KeyValueDB::WholeSpaceIterator iter) { + // expected: iterator is invalid + iter->seek_to_last(); + ASSERT_FALSE(iter->valid()); + } + + void SeekToLastWithPrefix(KeyValueDB::WholeSpaceIterator iter) { + // expected: iterator is invalid + iter->seek_to_last("prefix"); + ASSERT_FALSE(iter->valid()); + } + + void LowerBound(KeyValueDB::WholeSpaceIterator iter) { + // expected: iterator is invalid + iter->lower_bound("prefix", ""); + ASSERT_FALSE(iter->valid()); + + // expected: iterator is invalid + iter->lower_bound("", "key"); + ASSERT_FALSE(iter->valid()); + + // expected: iterator is invalid + iter->lower_bound("prefix", "key"); + ASSERT_FALSE(iter->valid()); + } + + void UpperBound(KeyValueDB::WholeSpaceIterator iter) { + // expected: iterator is invalid + iter->upper_bound("prefix", ""); + ASSERT_FALSE(iter->valid()); + + // expected: iterator is invalid + iter->upper_bound("", "key"); + ASSERT_FALSE(iter->valid()); + + // expected: iterator is invalid + iter->upper_bound("prefix", "key"); + ASSERT_FALSE(iter->valid()); + } +}; + +TEST_F(EmptyStore, SeekToFirstLevelDB) +{ + SCOPED_TRACE("LevelDB: Empty Store, Seek To First"); + SeekToFirst(db->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(EmptyStore, SeekToFirstMockDB) +{ + SCOPED_TRACE("MockDB: Empty Store, Seek To First"); + SeekToFirst(mock->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(EmptyStore, SeekToFirstWithPrefixLevelDB) +{ + SCOPED_TRACE("LevelDB: Empty Store, Seek To First With Prefix"); + SeekToFirstWithPrefix(db->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(EmptyStore, SeekToFirstWithPrefixMockDB) +{ + SCOPED_TRACE("MockDB: Empty Store, Seek To First With Prefix"); + SeekToFirstWithPrefix(mock->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(EmptyStore, SeekToLastLevelDB) +{ + SCOPED_TRACE("LevelDB: Empty Store, Seek To Last"); + SeekToLast(db->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(EmptyStore, SeekToLastMockDB) +{ + SCOPED_TRACE("MockDB: Empty Store, Seek To Last"); + SeekToLast(mock->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(EmptyStore, SeekToLastWithPrefixLevelDB) +{ + SCOPED_TRACE("LevelDB: Empty Store, Seek To Last With Prefix"); + SeekToLastWithPrefix(db->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(EmptyStore, SeekToLastWithPrefixMockDB) +{ + SCOPED_TRACE("MockDB: Empty Store, Seek To Last With Prefix"); + SeekToLastWithPrefix(mock->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(EmptyStore, LowerBoundLevelDB) +{ + SCOPED_TRACE("LevelDB: Empty Store, Lower Bound"); + LowerBound(db->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(EmptyStore, LowerBoundMockDB) +{ + SCOPED_TRACE("MockDB: Empty Store, Lower Bound"); + LowerBound(mock->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(EmptyStore, UpperBoundLevelDB) +{ + SCOPED_TRACE("LevelDB: Empty Store, Upper Bound"); + UpperBound(db->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + +TEST_F(EmptyStore, UpperBoundMockDB) +{ + SCOPED_TRACE("MockDB: Empty Store, Upper Bound"); + UpperBound(mock->get_wholespace_iterator()); + ASSERT_FALSE(HasFatalFailure()); +} + + +int main(int argc, char *argv[]) +{ + auto args = argv_to_vec(argc, argv); + + auto cct = global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT, CODE_ENVIRONMENT_UTILITY, CINIT_FLAG_NO_DEFAULT_CONFIG_FILE); + common_init_finish(g_ceph_context); + ::testing::InitGoogleTest(&argc, argv); + + if (argc < 2) { + std::cerr << "Usage: " << argv[0] + << "[ceph_options] [gtest_options] <store_path>" << std::endl; + return 1; + } + store_path = string(argv[1]); + + return RUN_ALL_TESTS(); +} diff --git a/src/test/ObjectMap/test_object_map.cc b/src/test/ObjectMap/test_object_map.cc new file mode 100644 index 000000000..37c1d7cd5 --- /dev/null +++ b/src/test/ObjectMap/test_object_map.cc @@ -0,0 +1,1126 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +#include <iterator> +#include <map> +#include <set> +#include <boost/scoped_ptr.hpp> + +#include "include/buffer.h" +#include "test/ObjectMap/KeyValueDBMemory.h" +#include "kv/KeyValueDB.h" +#include "os/DBObjectMap.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 (std::empty(cont)) { + return std::end(cont); + } + return std::next(std::begin(cont), rand() % cont.size()); +} + +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); + ceph_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) { + auto args = argv_to_vec(argc, argv); + + auto cct = global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT, + CODE_ENVIRONMENT_UTILITY, + CINIT_FLAG_NO_DEFAULT_CONFIG_FILE); + 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); + ceph_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); +} + |