From: Haomai Wang Date: Sat, 8 Feb 2014 07:41:52 +0000 (+0800) Subject: Rename test/filestore to test/objectstore X-Git-Tag: v0.78~198^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=1a588f18ba0e57df64f8a48c1393a4bc65019571;p=ceph.git Rename test/filestore to test/objectstore Now ObjectStore support three backend types, so we need to make each backend share unit test to avoid duplicate codes. This patch mainly make workload_generator workable for objectstore. Signed-off-by: Haomai Wang --- diff --git a/qa/workunits/filestore/filestore.sh b/qa/workunits/filestore/filestore.sh index f951a37ec958..391d1dc1a9b8 100755 --- a/qa/workunits/filestore/filestore.sh +++ b/qa/workunits/filestore/filestore.sh @@ -23,7 +23,7 @@ export PATH=/sbin:$PATH : ${EXT3:=$(which mkfs.ext3)} : ${XFS:=$(which mkfs.xfs)} : ${BTRFS:=$(which mkfs.btrfs)} -: ${CEPH_TEST_FILESTORE:=ceph_test_filestore} +: ${CEPH_TEST_FILESTORE:=ceph_test_objectstore} : ${FILE_SYSTEMS:=EXT4} # EXT3 XFS BTRFS : ${DEBUG:=} @@ -76,5 +76,5 @@ else main fi # Local Variables: -# compile-command: "CEPH_TEST_FILESTORE=../../../src/ceph_test_filestore filestore.sh TEST" +# compile-command: "CEPH_TEST_FILESTORE=../../../src/ceph_test_objectstore filestore.sh TEST" # End: diff --git a/src/test/Makefile.am b/src/test/Makefile.am index 83b83f4676ef..177a7a74f9dd 100644 --- a/src/test/Makefile.am +++ b/src/test/Makefile.am @@ -523,7 +523,7 @@ unittest_escape_LDADD = $(UNITTEST_LDADD) $(CEPH_GLOBAL) unittest_escape_CXXFLAGS = $(UNITTEST_CXXFLAGS) check_PROGRAMS += unittest_escape -unittest_chain_xattr_SOURCES = test/filestore/chain_xattr.cc +unittest_chain_xattr_SOURCES = test/objectstore/chain_xattr.cc unittest_chain_xattr_LDADD = $(LIBOS) $(UNITTEST_LDADD) $(CEPH_GLOBAL) unittest_chain_xattr_CXXFLAGS = $(UNITTEST_CXXFLAGS) check_PROGRAMS += unittest_chain_xattr @@ -853,30 +853,30 @@ ceph_test_libcephfs_CXXFLAGS = $(UNITTEST_CXXFLAGS) bin_DEBUGPROGRAMS += ceph_test_libcephfs if LINUX -ceph_test_filestore_SOURCES = test/filestore/store_test.cc -ceph_test_filestore_LDADD = $(LIBOS) $(UNITTEST_LDADD) $(CEPH_GLOBAL) -ceph_test_filestore_CXXFLAGS = $(UNITTEST_CXXFLAGS) -bin_DEBUGPROGRAMS += ceph_test_filestore +ceph_test_objectstore_SOURCES = test/objectstore/store_test.cc +ceph_test_objectstore_LDADD = $(LIBOS) $(UNITTEST_LDADD) $(CEPH_GLOBAL) +ceph_test_objectstore_CXXFLAGS = $(UNITTEST_CXXFLAGS) +bin_DEBUGPROGRAMS += ceph_test_objectstore endif -ceph_test_filestore_workloadgen_SOURCES = \ - test/filestore/workload_generator.cc \ - test/filestore/TestFileStoreState.cc -ceph_test_filestore_workloadgen_LDADD = $(LIBOS) $(CEPH_GLOBAL) -bin_DEBUGPROGRAMS += ceph_test_filestore_workloadgen +ceph_test_objectstore_workloadgen_SOURCES = \ + test/objectstore/workload_generator.cc \ + test/objectstore/TestObjectStoreState.cc +ceph_test_objectstore_workloadgen_LDADD = $(LIBOS) $(CEPH_GLOBAL) +bin_DEBUGPROGRAMS += ceph_test_objectstore_workloadgen ceph_test_filestore_idempotent_SOURCES = \ - test/filestore/test_idempotent.cc \ - test/filestore/FileStoreTracker.cc \ + test/objectstore/test_idempotent.cc \ + test/objectstore/FileStoreTracker.cc \ test/common/ObjectContents.cc ceph_test_filestore_idempotent_LDADD = $(LIBOS) $(CEPH_GLOBAL) bin_DEBUGPROGRAMS += ceph_test_filestore_idempotent ceph_test_filestore_idempotent_sequence_SOURCES = \ - test/filestore/test_idempotent_sequence.cc \ - test/filestore/DeterministicOpSequence.cc \ - test/filestore/TestFileStoreState.cc \ - test/filestore/FileStoreDiff.cc + test/objectstore/test_idempotent_sequence.cc \ + test/objectstore/DeterministicOpSequence.cc \ + test/objectstore/TestObjectStoreState.cc \ + test/objectstore/FileStoreDiff.cc ceph_test_filestore_idempotent_sequence_LDADD = $(LIBOS) $(CEPH_GLOBAL) bin_DEBUGPROGRAMS += ceph_test_filestore_idempotent_sequence @@ -950,11 +950,11 @@ noinst_HEADERS += \ test/bench/testfilestore_backend.h \ test/common/ObjectContents.h \ test/encoding/types.h \ - test/filestore/DeterministicOpSequence.h \ - test/filestore/FileStoreDiff.h \ - test/filestore/FileStoreTracker.h \ - test/filestore/TestFileStoreState.h \ - test/filestore/workload_generator.h \ + test/objectstore/DeterministicOpSequence.h \ + test/objectstore/FileStoreDiff.h \ + test/objectstore/FileStoreTracker.h \ + test/objectstore/TestObjectStoreState.h \ + test/objectstore/workload_generator.h \ test/kv_store_bench.h \ test/librados/test.h \ test/ObjectMap/KeyValueDBMemory.h \ diff --git a/src/test/filestore/DeterministicOpSequence.cc b/src/test/filestore/DeterministicOpSequence.cc deleted file mode 100644 index d9a0be43fc90..000000000000 --- a/src/test/filestore/DeterministicOpSequence.cc +++ /dev/null @@ -1,526 +0,0 @@ -// -*- 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 New Dream Network -* -* This is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License version 2.1, as published by the Free Software -* Foundation. See file COPYING. -*/ -#include -#include -#include -#include -#include -#include -#include -#include -#include "os/FileStore.h" -#include "common/ceph_argparse.h" -#include "global/global_init.h" -#include "common/debug.h" -#include -#include - -#include "DeterministicOpSequence.h" - -#include "common/config.h" -#include "include/assert.h" - -#define dout_subsys ceph_subsys_filestore -#undef dout_prefix -#define dout_prefix *_dout << "deterministic_seq " - -DeterministicOpSequence::DeterministicOpSequence(FileStore *store, - std::string status) - : TestFileStoreState(store), - txn(0), - m_osr("OSR") -{ - txn_coll = coll_t("meta"); - txn_object = hobject_t(sobject_t("txn", CEPH_NOSNAP)); - - if (!status.empty()) - m_status.open(status.c_str()); -} - -DeterministicOpSequence::~DeterministicOpSequence() -{ - // TODO Auto-generated destructor stub -} - -bool DeterministicOpSequence::run_one_op(int op, rngen_t& gen) -{ - bool ok = false; - switch (op) { - case DSOP_TOUCH: - ok = do_touch(gen); - break; - case DSOP_WRITE: - ok = do_write(gen); - break; - case DSOP_CLONE: - ok = do_clone(gen); - break; - case DSOP_CLONE_RANGE: - ok = do_clone_range(gen); - break; - case DSOP_OBJ_REMOVE: - ok = do_remove(gen); - break; - case DSOP_COLL_ADD: - ok = do_coll_add(gen); - break; - case DSOP_COLL_RENAME: - //do_coll_rename(gen); - break; - case DSOP_SET_ATTRS: - ok = do_set_attrs(gen); - break; - default: - assert(0 == "bad op"); - } - return ok; -} - -void DeterministicOpSequence::generate(int seed, int num_txs) -{ - std::ostringstream ss; - ss << "generate run " << num_txs << " --seed " << seed; - - if (m_status.is_open()) { - m_status << ss.str() << std::endl; - m_status.flush(); - } - - dout(0) << ss.str() << dendl; - - rngen_t gen(seed); - boost::uniform_int<> op_rng(DSOP_FIRST, DSOP_LAST); - - for (txn = 1; txn <= num_txs; ) { - int op = op_rng(gen); - _print_status(txn, op); - dout(0) << "generate seq " << txn << " op " << op << dendl; - if (run_one_op(op, gen)) - txn++; - } -} - -void DeterministicOpSequence::_print_status(int seq, int op) -{ - if (!m_status.is_open()) - return; - m_status << seq << " " << op << std::endl; - m_status.flush(); -} - -int DeterministicOpSequence::_gen_coll_id(rngen_t& gen) -{ - boost::uniform_int<> coll_rng(0, m_collections_ids.size()-1); - return coll_rng(gen); -} - -int DeterministicOpSequence::_gen_obj_id(rngen_t& gen) -{ - boost::uniform_int<> obj_rng(0, m_num_objects - 1); - return obj_rng(gen); -} - -void DeterministicOpSequence::note_txn(ObjectStore::Transaction *t) -{ - bufferlist bl; - ::encode(txn, bl); - t->truncate(txn_coll, txn_object, 0); - t->write(txn_coll, txn_object, 0, bl.length(), bl); - dout(10) << __func__ << " " << txn << dendl; -} - -bool DeterministicOpSequence::do_touch(rngen_t& gen) -{ - int coll_id = _gen_coll_id(gen); - int obj_id = _gen_obj_id(gen); - - coll_entry_t *entry = get_coll_at(coll_id); - ceph_assert(entry != NULL); - - // Don't care about other collections if already exists - if (!entry->check_for_obj(obj_id)) { - bool other_found = false; - map::iterator it = m_collections.begin(); - for (; it != m_collections.end(); ++it) { - if (it->second->check_for_obj(obj_id)) { - ceph_assert(it->first != coll_id); - other_found = true; - } - } - if (other_found) { - dout(0) << "do_touch new object in collection and exists in another" << dendl; - return false; - } - } - hobject_t *obj = entry->touch_obj(obj_id); - - dout(0) << "do_touch " << entry->m_coll.to_str() << "/" << obj->oid.name << dendl; - - _do_touch(entry->m_coll, *obj); - return true; -} - -bool DeterministicOpSequence::do_remove(rngen_t& gen) -{ - int coll_id = _gen_coll_id(gen); - - coll_entry_t *entry = get_coll_at(coll_id); - ceph_assert(entry != NULL); - - if (entry->m_objects.size() == 0) { - dout(0) << "do_remove no objects in collection" << dendl; - return false; - } - int obj_id = entry->get_random_obj_id(gen); - hobject_t *obj = entry->touch_obj(obj_id); - ceph_assert(obj); - - dout(0) << "do_remove " << entry->m_coll.to_str() << "/" << obj->oid.name << dendl; - - _do_remove(entry->m_coll, *obj); - hobject_t *rmobj = entry->remove_obj(obj_id); - ceph_assert(rmobj); - delete rmobj; - return true; -} - -static void _gen_random(rngen_t& gen, - size_t size, bufferlist& bl) { - - static const char alphanum[] = "0123456789" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz"; - - boost::uniform_int<> char_rng(0, sizeof(alphanum)); - bufferptr bp(size); - for (unsigned int i = 0; i < size - 1; i++) { - bp[i] = alphanum[char_rng(gen)]; - } - bp[size - 1] = '\0'; - bl.append(bp); -} - -static void gen_attrs(rngen_t &gen, - map *out) { - boost::uniform_int<> num_rng(10, 30); - boost::uniform_int<> key_size_rng(5, 10); - boost::uniform_int<> val_size_rng(100, 1000); - size_t num_attrs = static_cast(num_rng(gen)); - for (size_t i = 0; i < num_attrs; ++i) { - size_t key_size = static_cast(num_rng(gen)); - size_t val_size = static_cast(num_rng(gen)); - bufferlist keybl; - _gen_random(gen, key_size, keybl); - string key(keybl.c_str(), keybl.length()); - _gen_random(gen, val_size, (*out)[key]); - } -} - -bool DeterministicOpSequence::do_set_attrs(rngen_t& gen) -{ - int coll_id = _gen_coll_id(gen); - - coll_entry_t *entry = get_coll_at(coll_id); - ceph_assert(entry != NULL); - - if (entry->m_objects.size() == 0) { - dout(0) << "do_set_attrs no objects in collection" << dendl; - return false; - } - int obj_id = entry->get_random_obj_id(gen); - hobject_t *obj = entry->touch_obj(obj_id); - ceph_assert(obj); - - map out; - gen_attrs(gen, &out); - - dout(0) << "do_set_attrs " << out.size() << " entries" << dendl; - _do_set_attrs(entry->m_coll, *obj, out); - return true; -} - -bool DeterministicOpSequence::do_write(rngen_t& gen) -{ - int coll_id = _gen_coll_id(gen); - - coll_entry_t *entry = get_coll_at(coll_id); - ceph_assert(entry != NULL); - - if (entry->m_objects.size() == 0) { - dout(0) << "do_write no objects in collection" << dendl; - return false; - } - int obj_id = entry->get_random_obj_id(gen); - hobject_t *obj = entry->touch_obj(obj_id); - ceph_assert(obj); - - boost::uniform_int<> size_rng(100, (2 << 19)); - size_t size = (size_t) size_rng(gen); - bufferlist bl; - _gen_random(gen, size, bl); - - dout(0) << "do_write " << entry->m_coll.to_str() << "/" << obj->oid.name - << " 0~" << size << dendl; - - _do_write(entry->m_coll, *obj, 0, bl.length(), bl); - return true; -} - -bool DeterministicOpSequence::_prepare_clone(rngen_t& gen, - coll_t& coll_ret, hobject_t& orig_obj_ret, hobject_t& new_obj_ret) -{ - int coll_id = _gen_coll_id(gen); - - coll_entry_t *entry = get_coll_at(coll_id); - ceph_assert(entry != NULL); - - if (entry->m_objects.size() >= 2) { - dout(0) << "_prepare_clone coll " << entry->m_coll.to_str() - << " doesn't have 2 or more objects" << dendl; - return false; - } - - int orig_obj_id = entry->get_random_obj_id(gen); - hobject_t *orig_obj = entry->touch_obj(orig_obj_id); - ceph_assert(orig_obj); - - int id; - do { - id = entry->get_random_obj_id(gen); - } while (id == orig_obj_id); - hobject_t *new_obj = entry->touch_obj(id); - ceph_assert(new_obj); - - coll_ret = entry->m_coll; - orig_obj_ret = *orig_obj; - new_obj_ret = *new_obj; - - return true; -} - -bool DeterministicOpSequence::do_clone(rngen_t& gen) -{ - coll_t coll; - hobject_t orig_obj, new_obj; - if (!_prepare_clone(gen, coll, orig_obj, new_obj)) { - return false; - } - - dout(0) << "do_clone " << coll.to_str() << "/" << orig_obj.oid.name - << " => " << coll.to_str() << "/" << new_obj.oid.name << dendl; - - _do_clone(coll, orig_obj, new_obj); - return true; -} - -bool DeterministicOpSequence::do_clone_range(rngen_t& gen) -{ - coll_t coll; - hobject_t orig_obj, new_obj; - if (!_prepare_clone(gen, coll, orig_obj, new_obj)) { - return false; - } - - /* Whenever we have to make a clone_range() operation, just write to the - * object first, so we know we have something to clone in the said range. - * This may not be the best solution ever, but currently we're not keeping - * track of the written-to objects, and until we know for sure we really - * need to, let's just focus on the task at hand. - */ - - boost::uniform_int<> write_size_rng(100, (2 << 19)); - size_t size = (size_t) write_size_rng(gen); - bufferlist bl; - _gen_random(gen, size, bl); - - boost::uniform_int<> clone_len(1, bl.length()); - size = (size_t) clone_len(gen); - - dout(0) << "do_clone_range " << coll.to_str() << "/" << orig_obj.oid.name - << " (0~" << size << ")" - << " => " << coll.to_str() << "/" << new_obj.oid.name - << " (0)" << dendl; - _do_write_and_clone_range(coll, orig_obj, new_obj, 0, size, 0, bl); - return true; -} - -bool DeterministicOpSequence::_prepare_colls(rngen_t& gen, - coll_entry_t* &orig_coll, coll_entry_t* &new_coll) -{ - ceph_assert(m_collections_ids.size() > 1); - int orig_coll_id = _gen_coll_id(gen); - int new_coll_id; - do { - new_coll_id = _gen_coll_id(gen); - } while (new_coll_id == orig_coll_id); - - dout(0) << "_prepare_colls from coll id " << orig_coll_id - << " to coll id " << new_coll_id << dendl; - - orig_coll = get_coll_at(orig_coll_id); - ceph_assert(orig_coll != NULL); - new_coll = get_coll_at(new_coll_id); - ceph_assert(new_coll != NULL); - - if (!orig_coll->m_objects.size()) { - dout(0) << "_prepare_colls coll " << orig_coll->m_coll.to_str() - << " has no objects to use" << dendl; - return false; - } - - return true; -} - - -bool DeterministicOpSequence::do_coll_rename(rngen_t& gen) -{ - int coll_pos = _gen_coll_id(gen); - dout(0) << "do_coll_rename coll pos #" << coll_pos << dendl; - - coll_entry_t *coll_entry = get_coll_at(coll_pos); - if (!coll_entry) { - dout(0) << "do_coll_rename no collection at pos #" << coll_pos << dendl; - return false; - } - - coll_t orig_coll = coll_entry->m_coll; - char buf[100]; - memset(buf, 0, 100); - snprintf(buf, 100, "0.%d_head", m_next_coll_nr++); - coll_t new_coll(buf); - coll_entry->m_coll = new_coll; - - dout(0) << "do_coll_rename " << orig_coll.to_str() - << " => " << new_coll.to_str() << dendl; - _do_coll_rename(orig_coll, new_coll); - return true; -} - -bool DeterministicOpSequence::do_coll_add(rngen_t& gen) -{ - coll_entry_t *orig_coll = NULL, *new_coll = NULL; - if (!_prepare_colls(gen, orig_coll, new_coll)) - return false; - - ceph_assert(orig_coll && new_coll); - - boost::uniform_int<> obj_rng(0, orig_coll->m_objects.size()-1); - int obj_pos = obj_rng(gen); - int obj_key = -1; - hobject_t *obj = orig_coll->get_obj_at(obj_pos, &obj_key); - if (!obj) { - dout(0) << "do_coll_add coll " << orig_coll->m_coll.to_str() - << " has no object as pos #" << obj_pos << " (key " << obj_key << ")" - << dendl; - return false; - } - if (new_coll->check_for_obj(obj_key)) { - dout(0) << "do_coll_add coll " << orig_coll->m_coll.to_str() - << " already has object as pos #" << obj_pos << " (key " << obj_key << ")" - << dendl; - return false; - } - dout(0) << "do_coll_add " << orig_coll->m_coll.to_str() << "/" << obj->oid.name - << " => " << new_coll->m_coll.to_str() << "/" << obj->oid.name << dendl; - new_coll->touch_obj(obj_key); - - _do_coll_add(orig_coll->m_coll, new_coll->m_coll, *obj); - return true; -} - -void DeterministicOpSequence::_do_touch(coll_t coll, hobject_t& obj) -{ - ObjectStore::Transaction t; - note_txn(&t); - t.touch(coll, obj); - m_store->apply_transaction(t); -} - -void DeterministicOpSequence::_do_remove(coll_t coll, hobject_t& obj) -{ - ObjectStore::Transaction t; - note_txn(&t); - t.remove(coll, obj); - m_store->apply_transaction(t); -} - -void DeterministicOpSequence::_do_set_attrs(coll_t coll, - hobject_t &obj, - const map &attrs) -{ - ObjectStore::Transaction t; - note_txn(&t); - t.omap_setkeys(coll, obj, attrs); - m_store->apply_transaction(t); -} - -void DeterministicOpSequence::_do_write(coll_t coll, hobject_t& obj, - uint64_t off, uint64_t len, const bufferlist& data) -{ - ObjectStore::Transaction t; - note_txn(&t); - t.write(coll, obj, off, len, data); - m_store->apply_transaction(t); -} - -void DeterministicOpSequence::_do_clone(coll_t coll, hobject_t& orig_obj, - hobject_t& new_obj) -{ - ObjectStore::Transaction t; - note_txn(&t); - t.clone(coll, orig_obj, new_obj); - m_store->apply_transaction(t); -} - -void DeterministicOpSequence::_do_clone_range(coll_t coll, - hobject_t& orig_obj, hobject_t& new_obj, uint64_t srcoff, - uint64_t srclen, uint64_t dstoff) -{ - ObjectStore::Transaction t; - note_txn(&t); - t.clone_range(coll, orig_obj, new_obj, srcoff, srclen, dstoff); - m_store->apply_transaction(t); -} - -void DeterministicOpSequence::_do_write_and_clone_range(coll_t coll, - hobject_t& orig_obj, - hobject_t& new_obj, - uint64_t srcoff, - uint64_t srclen, - uint64_t dstoff, - bufferlist& bl) -{ - ObjectStore::Transaction t; - note_txn(&t); - t.write(coll, orig_obj, srcoff, bl.length(), bl); - t.clone_range(coll, orig_obj, new_obj, srcoff, srclen, dstoff); - m_store->apply_transaction(t); -} - -void DeterministicOpSequence::_do_coll_add(coll_t orig_coll, coll_t new_coll, - hobject_t& obj) -{ - ObjectStore::Transaction t; - note_txn(&t); - t.remove(new_coll, obj); - t.collection_add(new_coll, orig_coll, obj); - m_store->apply_transaction(t); -} - -void DeterministicOpSequence::_do_coll_rename(coll_t orig_coll, coll_t new_coll) -{ - ObjectStore::Transaction t; - note_txn(&t); - t.collection_rename(orig_coll, new_coll); - m_store->apply_transaction(t); -} diff --git a/src/test/filestore/DeterministicOpSequence.h b/src/test/filestore/DeterministicOpSequence.h deleted file mode 100644 index 818d0ed64489..000000000000 --- a/src/test/filestore/DeterministicOpSequence.h +++ /dev/null @@ -1,98 +0,0 @@ -// -*- 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 New Dream Network -* -* This is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License version 2.1, as published by the Free Software -* Foundation. See file COPYING. -*/ -#ifndef FILESTORE_DTRMNSTC_SEQ_OPS_H_ -#define FILESTORE_DTRMNSTC_SEQ_OPS_H_ - -#include -#include -#include -#include "os/FileStore.h" -#include -#include -#include - -#include "TestFileStoreState.h" - -typedef boost::mt11213b rngen_t; - -class DeterministicOpSequence : public TestFileStoreState { - public: - DeterministicOpSequence(FileStore *store, std::string status = std::string()); - virtual ~DeterministicOpSequence(); - - virtual void generate(int seed, int num_txs); - - protected: - enum { - DSOP_TOUCH = 0, - DSOP_WRITE = 1, - DSOP_CLONE = 2, - DSOP_CLONE_RANGE = 3, - DSOP_OBJ_REMOVE = 4, - DSOP_COLL_RENAME = 5, - DSOP_COLL_ADD = 6, - DSOP_SET_ATTRS = 7, - - DSOP_FIRST = DSOP_TOUCH, - DSOP_LAST = DSOP_SET_ATTRS, - }; - - int32_t txn; - - coll_t txn_coll; - hobject_t txn_object; - - ObjectStore::Sequencer m_osr; - std::ofstream m_status; - - bool run_one_op(int op, rngen_t& gen); - - void note_txn(ObjectStore::Transaction *t); - bool do_touch(rngen_t& gen); - bool do_remove(rngen_t& gen); - bool do_write(rngen_t& gen); - bool do_clone(rngen_t& gen); - bool do_clone_range(rngen_t& gen); - bool do_coll_rename(rngen_t& gen); - bool do_coll_add(rngen_t& gen); - bool do_set_attrs(rngen_t& gen); - - virtual void _do_touch(coll_t coll, hobject_t& obj); - virtual void _do_remove(coll_t coll, hobject_t& obj); - virtual void _do_write(coll_t coll, hobject_t& obj, uint64_t off, - uint64_t len, const bufferlist& data); - virtual void _do_set_attrs(coll_t coll, - hobject_t &obj, - const map &attrs); - virtual void _do_clone(coll_t coll, hobject_t& orig_obj, hobject_t& new_obj); - virtual void _do_clone_range(coll_t coll, hobject_t& orig_obj, - hobject_t& new_obj, uint64_t srcoff, uint64_t srclen, uint64_t dstoff); - virtual void _do_write_and_clone_range(coll_t coll, hobject_t& orig_obj, - hobject_t& new_obj, uint64_t srcoff, uint64_t srclen, - uint64_t dstoff, bufferlist& bl); - virtual void _do_coll_add(coll_t orig_coll, coll_t new_coll, hobject_t& obj); - virtual void _do_coll_rename(coll_t orig_coll, coll_t new_coll); - - int _gen_coll_id(rngen_t& gen); - int _gen_obj_id(rngen_t& gen); - void _print_status(int seq, int op); - - private: - bool _prepare_clone(rngen_t& gen, coll_t& coll_ret, - hobject_t& orig_obj_ret, hobject_t& new_obj_ret); - bool _prepare_colls(rngen_t& gen, - coll_entry_t* &orig_coll, coll_entry_t* &new_coll); -}; - - -#endif /* FILESTORE_DTRMNSTC_SEQ_OPS_H_ */ diff --git a/src/test/filestore/FileStoreDiff.cc b/src/test/filestore/FileStoreDiff.cc deleted file mode 100644 index 40c0b32d30c1..000000000000 --- a/src/test/filestore/FileStoreDiff.cc +++ /dev/null @@ -1,319 +0,0 @@ -// -*- 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 New Dream Network -* -* This is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License version 2.1, as published by the Free Software -* Foundation. See file COPYING. -*/ -#include -#include -#include -#include -#include "common/debug.h" -#include "os/FileStore.h" -#include "common/config.h" - -#include "FileStoreDiff.h" - -#define dout_subsys ceph_subsys_filestore -#undef dout_prefix -#define dout_prefix *_dout << "filestore_diff " - -FileStoreDiff::FileStoreDiff(FileStore *a, FileStore *b) - : a_store(a), b_store(b) -{ - int err; - err = a_store->mount(); - ceph_assert(err == 0); - - err = b_store->mount(); - ceph_assert(err == 0); -} - -FileStoreDiff::~FileStoreDiff() -{ - a_store->umount(); - b_store->umount(); -} - - -bool FileStoreDiff::diff_attrs(std::map& b, - std::map& a) -{ - bool ret = false; - std::map::iterator b_it = b.begin(); - std::map::iterator a_it = a.begin(); - for (; b_it != b.end(); ++b_it, ++a_it) { - if (b_it->first != a_it->first) { - dout(0) << "diff_attrs name mismatch (verify: " << b_it->first - << ", store: " << a_it->first << ")" << dendl; - ret = true; - continue; - } - - if (!b_it->second.cmp(a_it->second)) { - dout(0) << "diff_attrs contents mismatch on attr " << b_it->first << dendl; - ret = true; - continue; - } - } - return ret; -} - -static bool diff_omap(std::map& b, - std::map& a) -{ - bool ret = false; - std::map::iterator b_it = b.begin(); - std::map::iterator a_it = a.begin(); - for (; b_it != b.end(); ++b_it, ++a_it) { - if (b_it->first != a_it->first) { - dout(0) << "diff_attrs name mismatch (verify: " << b_it->first - << ", store: " << a_it->first << ")" << dendl; - ret = true; - continue; - } - - if (!(b_it->second == a_it->second)) { - dout(0) << "diff_attrs contents mismatch on attr " << b_it->first << dendl; - ret = true; - continue; - } - } - return ret; -} - -bool FileStoreDiff::diff_objects_stat(struct stat& a, struct stat& b) -{ - bool ret = false; - - if (a.st_uid != b.st_uid) { - dout(0) << "diff_objects_stat uid mismatch (A: " - << a.st_uid << " != B: " << b.st_uid << ")" << dendl; - ret = true; - } - - if (a.st_gid != b.st_gid) { - dout(0) << "diff_objects_stat gid mismatch (A: " - << a.st_gid << " != B: " << b.st_gid << ")" << dendl; - ret = true; - } - - if (a.st_mode != b.st_mode) { - dout(0) << "diff_objects_stat mode mismatch (A: " - << a.st_mode << " != B: " << b.st_mode << ")" << dendl; - ret = true; - } - - if (a.st_nlink != b.st_nlink) { - dout(0) << "diff_objects_stat nlink mismatch (A: " - << a.st_nlink << " != B: " << b.st_nlink << ")" << dendl; - ret = true; - } - - if (a.st_size != b.st_size) { - dout(0) << "diff_objects_stat size mismatch (A: " - << a.st_size << " != B: " << b.st_size << ")" << dendl; - ret = true; - } - return ret; -} - -bool FileStoreDiff::diff_objects(FileStore *a_store, FileStore *b_store, coll_t coll) -{ - dout(2) << __func__ << " coll " << coll << dendl; - - bool ret = false; - - int err; - std::vector b_objects, a_objects; - err = b_store->collection_list(coll, b_objects); - if (err < 0) { - dout(0) << "diff_objects list on verify coll " << coll.to_str() - << " returns " << err << dendl; - return true; - } - err = a_store->collection_list(coll, a_objects); - if (err < 0) { - dout(0) << "diff_objects list on store coll " << coll.to_str() - << " returns " << err << dendl; - return true; - } - - if (b_objects.size() != a_objects.size()) { - dout(0) << "diff_objects num objs mismatch (A: " << a_objects.size() - << ", B: " << b_objects.size() << ")" << dendl; - ret = true; - } - - std::vector::iterator b_it = b_objects.begin(); - std::vector::iterator a_it = b_objects.begin(); - for (; b_it != b_objects.end(); ++b_it, ++a_it) { - ghobject_t b_obj = *b_it, a_obj = *a_it; - if (b_obj.hobj.oid.name != a_obj.hobj.oid.name) { - dout(0) << "diff_objects name mismatch on A object " - << coll << "/" << a_obj << " and B object " - << coll << "/" << b_obj << dendl; - ret = true; - continue; - } - - struct stat b_stat, a_stat; - err = b_store->stat(coll, b_obj, &b_stat); - if (err < 0) { - dout(0) << "diff_objects error stating B object " - << coll.to_str() << "/" << b_obj.hobj.oid.name << dendl; - ret = true; - } - err = a_store->stat(coll, a_obj, &a_stat); - if (err < 0) { - dout(0) << "diff_objects error stating A object " - << coll << "/" << a_obj << dendl; - ret = true; - } - - if (diff_objects_stat(a_stat, b_stat)) { - dout(0) << "diff_objects stat mismatch on " - << coll << "/" << b_obj << dendl; - ret = true; - } - - bufferlist a_obj_bl, b_obj_bl; - b_store->read(coll, b_obj, 0, b_stat.st_size, b_obj_bl); - a_store->read(coll, a_obj, 0, a_stat.st_size, a_obj_bl); - - if (!a_obj_bl.contents_equal(b_obj_bl)) { - dout(0) << "diff_objects content mismatch on " - << coll << "/" << b_obj << dendl; - ret = true; - } - - std::map a_obj_attrs_map, b_obj_attrs_map; - err = a_store->getattrs(coll, a_obj, a_obj_attrs_map); - if (err < 0) { - dout(0) << "diff_objects getattrs on A object " << coll << "/" << a_obj - << " returns " << err << dendl; - ret = true; - } - err = b_store->getattrs(coll, b_obj, b_obj_attrs_map); - if (err < 0) { - dout(0) << "diff_objects getattrs on B object " << coll << "/" << b_obj - << "returns " << err << dendl; - ret = true; - } - - if (diff_attrs(b_obj_attrs_map, a_obj_attrs_map)) { - dout(0) << "diff_objects attrs mismatch on A object " - << coll << "/" << a_obj << " and B object " - << coll << "/" << b_obj << dendl; - ret = true; - } - - std::map a_obj_omap, b_obj_omap; - std::set a_omap_keys, b_omap_keys; - err = a_store->omap_get_keys(coll, a_obj, &a_omap_keys); - if (err < 0) { - dout(0) << "diff_objects getomap on A object " << coll << "/" << a_obj - << " returns " << err << dendl; - ret = true; - } - err = a_store->omap_get_values(coll, a_obj, a_omap_keys, &a_obj_omap); - if (err < 0) { - dout(0) << "diff_objects getomap on A object " << coll << "/" << a_obj - << " returns " << err << dendl; - ret = true; - } - err = b_store->omap_get_keys(coll, b_obj, &b_omap_keys); - if (err < 0) { - dout(0) << "diff_objects getomap on A object " << coll << "/" << b_obj - << " returns " << err << dendl; - ret = true; - } - err = b_store->omap_get_values(coll, b_obj, b_omap_keys, &b_obj_omap); - if (err < 0) { - dout(0) << "diff_objects getomap on A object " << coll << "/" << b_obj - << " returns " << err << dendl; - ret = true; - } - if (diff_omap(a_obj_omap, b_obj_omap)) { - dout(0) << "diff_objects omap mismatch on A object " - << coll << "/" << a_obj << " and B object " - << coll << "/" << b_obj << dendl; - ret = true; - } - } - - return ret; -} - -bool FileStoreDiff::diff_coll_attrs(FileStore *a_store, FileStore *b_store, coll_t coll) -{ - bool ret = false; - - int err; - std::map b_coll_attrs, a_coll_attrs; - err = b_store->collection_getattrs(coll, b_coll_attrs); - if (err < 0) { - dout(0) << "diff_attrs getattrs on verify coll " << coll.to_str() - << "returns " << err << dendl; - ret = true; - } - err = a_store->collection_getattrs(coll, a_coll_attrs); - if (err < 0) { - dout(0) << "diff_attrs getattrs on A coll " << coll.to_str() - << "returns " << err << dendl; - ret = true; - } - - if (b_coll_attrs.size() != a_coll_attrs.size()) { - dout(0) << "diff_attrs size mismatch (A: " << a_coll_attrs.size() - << ", B: " << a_coll_attrs.size() << ")" << dendl; - ret = true; - } - - return diff_attrs(b_coll_attrs, a_coll_attrs) || ret; -} - -bool FileStoreDiff::diff() -{ - bool ret = false; - - std::vector a_coll_list, b_coll_list; - a_store->list_collections(a_coll_list); - b_store->list_collections(b_coll_list); - - std::vector::iterator it = b_coll_list.begin(); - for (; it != b_coll_list.end(); ++it) { - coll_t b_coll = *it; - if (!a_store->collection_exists(b_coll)) { - dout(0) << "diff B coll " << b_coll.to_str() << " DNE on A" << dendl; - ret = true; - continue; - } - for (std::vector::iterator j = a_coll_list.begin(); - j != a_coll_list.end(); ++j) { - if (*j == *it) { - a_coll_list.erase(j); - break; - } - } - - if (diff_coll_attrs(a_store, b_store, b_coll)) - ret = true; - - if (diff_objects(a_store, b_store, b_coll)) - ret = true; - } - for (std::vector::iterator it = a_coll_list.begin(); - it != a_coll_list.end(); ++it) { - dout(0) << "diff A coll " << *it << " DNE on B" << dendl; - ret = true; - } - - return ret; -} diff --git a/src/test/filestore/FileStoreDiff.h b/src/test/filestore/FileStoreDiff.h deleted file mode 100644 index cacd3ce84747..000000000000 --- a/src/test/filestore/FileStoreDiff.h +++ /dev/null @@ -1,43 +0,0 @@ -// -*- 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 New Dream Network -* -* This is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License version 2.1, as published by the Free Software -* Foundation. See file COPYING. -*/ -#ifndef FILESTORE_DIFF_H_ -#define FILESTORE_DIFF_H_ - -#include -#include -#include -#include -#include "common/debug.h" -#include "os/FileStore.h" -#include "common/config.h" - -class FileStoreDiff { - - private: - FileStore *a_store; - FileStore *b_store; - - bool diff_coll_attrs(FileStore *a_store, FileStore *b_store, coll_t coll); - bool diff_objects(FileStore *a_store, FileStore *b_store, coll_t coll); - bool diff_objects_stat(struct stat& a, struct stat& b); - bool diff_attrs(std::map& b, - std::map& a); - -public: - FileStoreDiff(FileStore *a, FileStore *b); - virtual ~FileStoreDiff(); - - bool diff(); -}; - -#endif /* FILESTOREDIFF_H_ */ diff --git a/src/test/filestore/FileStoreTracker.cc b/src/test/filestore/FileStoreTracker.cc deleted file mode 100644 index afdc31bad23e..000000000000 --- a/src/test/filestore/FileStoreTracker.cc +++ /dev/null @@ -1,452 +0,0 @@ -// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- -#include "FileStoreTracker.h" -#include -#include -#include -#include "include/Context.h" -#include "common/Mutex.h" - -class OnApplied : public Context { - FileStoreTracker *tracker; - list, uint64_t> > in_flight; - ObjectStore::Transaction *t; -public: - OnApplied(FileStoreTracker *tracker, - list, uint64_t> > in_flight, - ObjectStore::Transaction *t) - : tracker(tracker), in_flight(in_flight), t(t) {} - - void finish(int r) { - for (list, uint64_t> >::iterator i = - in_flight.begin(); - i != in_flight.end(); - ++i) { - tracker->applied(i->first, i->second); - } - delete t; - } -}; - -class OnCommitted : public Context { - FileStoreTracker *tracker; - list, uint64_t> > in_flight; -public: - OnCommitted(FileStoreTracker *tracker, - list, uint64_t> > in_flight) - : tracker(tracker), in_flight(in_flight) {} - - void finish(int r) { - for (list, uint64_t> >::iterator i = - in_flight.begin(); - i != in_flight.end(); - ++i) { - tracker->committed(i->first, i->second); - } - } -}; - -int FileStoreTracker::init() -{ - set to_get; - to_get.insert("STATUS"); - map got; - db->get("STATUS", to_get, &got); - restart_seq = 0; - if (!got.empty()) { - bufferlist::iterator bp = got.begin()->second.begin(); - ::decode(restart_seq, bp); - } - ++restart_seq; - KeyValueDB::Transaction t = db->get_transaction(); - got.clear(); - ::encode(restart_seq, got["STATUS"]); - t->set("STATUS", got); - db->submit_transaction(t); - return 0; -} - -void FileStoreTracker::submit_transaction(Transaction &t) -{ - list, uint64_t> > in_flight; - OutTransaction out; - out.t = new ObjectStore::Transaction; - out.in_flight = &in_flight; - for (list::iterator i = t.ops.begin(); - i != t.ops.end(); - ++i) { - (**i)(this, &out); - } - store->queue_transaction( - 0, out.t, - new OnApplied(this, in_flight, out.t), - new OnCommitted(this, in_flight)); -} - -void FileStoreTracker::write(const pair &obj, - OutTransaction *out) -{ - Mutex::Locker l(lock); - std::cerr << "Writing " << obj << std::endl; - ObjectContents contents = get_current_content(obj); - - uint64_t offset = rand() % (SIZE/2); - uint64_t len = rand() % (SIZE/2); - if (!len) len = 10; - contents.write(rand(), offset, len); - - bufferlist to_write; - ObjectContents::Iterator iter = contents.get_iterator(); - iter.seek_to(offset); - for (uint64_t i = offset; - i < offset + len; - ++i, ++iter) { - assert(iter.valid()); - to_write.append(*iter); - } - out->t->write(coll_t(obj.first), - hobject_t(sobject_t(obj.second, CEPH_NOSNAP)), - offset, - len, - to_write); - out->in_flight->push_back(make_pair(obj, set_content(obj, contents))); -} - -void FileStoreTracker::remove(const pair &obj, - OutTransaction *out) -{ - std::cerr << "Deleting " << obj << std::endl; - Mutex::Locker l(lock); - ObjectContents old_contents = get_current_content(obj); - if (!old_contents.exists()) - return; - out->t->remove(coll_t(obj.first), - hobject_t(sobject_t(obj.second, CEPH_NOSNAP))); - ObjectContents contents; - out->in_flight->push_back(make_pair(obj, set_content(obj, contents))); -} - -void FileStoreTracker::clone_range(const pair &from, - const pair &to, - OutTransaction *out) { - Mutex::Locker l(lock); - std::cerr << "CloningRange " << from << " to " << to << std::endl; - assert(from.first == to.first); - ObjectContents from_contents = get_current_content(from); - ObjectContents to_contents = get_current_content(to); - if (!from_contents.exists()) { - return; - } - if (from.second == to.second) { - return; - } - - uint64_t new_size = from_contents.size(); - interval_set interval_to_clone; - uint64_t offset = rand() % (new_size/2); - uint64_t len = rand() % (new_size/2); - if (!len) len = 10; - interval_to_clone.insert(offset, len); - to_contents.clone_range(from_contents, interval_to_clone); - out->t->clone_range(coll_t(from.first), - hobject_t(sobject_t(from.second, CEPH_NOSNAP)), - hobject_t(sobject_t(to.second, CEPH_NOSNAP)), - offset, - len, - offset); - out->in_flight->push_back(make_pair(to, set_content(to, to_contents))); -} - -void FileStoreTracker::clone(const pair &from, - const pair &to, - OutTransaction *out) { - Mutex::Locker l(lock); - std::cerr << "Cloning " << from << " to " << to << std::endl; - assert(from.first == to.first); - if (from.second == to.second) { - return; - } - ObjectContents from_contents = get_current_content(from); - ObjectContents to_contents = get_current_content(to); - if (!from_contents.exists()) { - return; - } - - if (to_contents.exists()) - out->t->remove(coll_t(to.first), - hobject_t(sobject_t(to.second, CEPH_NOSNAP))); - out->t->clone(coll_t(from.first), - hobject_t(sobject_t(from.second, CEPH_NOSNAP)), - hobject_t(sobject_t(to.second, CEPH_NOSNAP))); - out->in_flight->push_back(make_pair(to, set_content(to, from_contents))); -} - - -string obj_to_prefix(const pair &obj) { - string sep; - sep.push_back('^'); - return obj.first + sep + obj.second + "_CONTENTS_"; -} - -string obj_to_meta_prefix(const pair &obj) { - string sep; - sep.push_back('^'); - return obj.first + sep + obj.second; -} - -string seq_to_key(uint64_t seq) { - char buf[50]; - snprintf(buf, sizeof(buf), "%*llu", 20, (unsigned long long int)seq); - return string(buf); -} - -struct ObjStatus { - uint64_t last_applied; - uint64_t last_committed; - uint64_t restart_seq; - ObjStatus() : last_applied(0), last_committed(0), restart_seq(0) {} - - uint64_t get_last_applied(uint64_t seq) const { - if (seq > restart_seq) - return last_committed; - else - return last_applied; - } - void set_last_applied(uint64_t _last_applied, uint64_t seq) { - last_applied = _last_applied; - restart_seq = seq; - } - uint64_t trim_to() const { - return last_applied < last_committed ? - last_applied : last_committed; - } -}; -void encode(const ObjStatus &obj, bufferlist &bl) { - ::encode(obj.last_applied, bl); - ::encode(obj.last_committed, bl); - ::encode(obj.restart_seq, bl); -} -void decode(ObjStatus &obj, bufferlist::iterator &bl) { - ::decode(obj.last_applied, bl); - ::decode(obj.last_committed, bl); - ::decode(obj.restart_seq, bl); -} - - -ObjStatus get_obj_status(const pair &obj, - KeyValueDB *db) -{ - set to_get; - to_get.insert("META"); - map got; - db->get(obj_to_meta_prefix(obj), to_get, &got); - ObjStatus retval; - if (!got.empty()) { - bufferlist::iterator bp = got.begin()->second.begin(); - ::decode(retval, bp); - } - return retval; -} - -void set_obj_status(const pair &obj, - const ObjStatus &status, - KeyValueDB::Transaction t) -{ - map to_set; - ::encode(status, to_set["META"]); - t->set(obj_to_meta_prefix(obj), to_set); -} - -void _clean_forward(const pair &obj, - uint64_t last_valid, - KeyValueDB *db) -{ - KeyValueDB::Transaction t = db->get_transaction(); - KeyValueDB::Iterator i = db->get_iterator(obj_to_prefix(obj)); - set to_remove; - i->upper_bound(seq_to_key(last_valid)); - for (; i->valid(); i->next()) { - to_remove.insert(i->key()); - } - t->rmkeys(obj_to_prefix(obj), to_remove); - db->submit_transaction(t); -} - - -void FileStoreTracker::verify(const string &coll, const string &obj, - bool on_start) { - Mutex::Locker l(lock); - std::cerr << "Verifying " << make_pair(coll, obj) << std::endl; - - pair valid_reads = get_valid_reads(make_pair(coll, obj)); - std::cerr << "valid_reads is " << valid_reads << std::endl; - bufferlist contents; - int r = store->read(coll_t(coll), - hobject_t(sobject_t(obj, CEPH_NOSNAP)), - 0, - 2*SIZE, - contents); - std::cerr << "exists: " << r << std::endl; - - - for (uint64_t i = valid_reads.first; - i < valid_reads.second; - ++i) { - ObjectContents old_contents = get_content(make_pair(coll, obj), i); - - std::cerr << "old_contents exists " << old_contents.exists() << std::endl; - if (!old_contents.exists() && (r == -ENOENT)) - return; - - if (old_contents.exists() && (r == -ENOENT)) - continue; - - if (!old_contents.exists() && (r != -ENOENT)) - continue; - - if (contents.length() != old_contents.size()) { - std::cerr << "old_contents.size() is " - << old_contents.size() << std::endl; - continue; - } - - bufferlist::iterator bp = contents.begin(); - ObjectContents::Iterator iter = old_contents.get_iterator(); - iter.seek_to_first(); - bool matches = true; - uint64_t pos = 0; - for (; !bp.end() && iter.valid(); - ++iter, ++bp, ++pos) { - if (*iter != *bp) { - std::cerr << "does not match at pos " << pos << std::endl; - matches = false; - break; - } - } - if (matches) { - if (on_start) - _clean_forward(make_pair(coll, obj), i, db); - return; - } - } - std::cerr << "Verifying " << make_pair(coll, obj) << " failed " << std::endl; - assert(0); -} - -ObjectContents FileStoreTracker::get_current_content( - const pair &obj) -{ - KeyValueDB::Iterator iter = db->get_iterator( - obj_to_prefix(obj)); - iter->seek_to_last(); - if (iter->valid()) { - bufferlist bl = iter->value(); - bufferlist::iterator bp = bl.begin(); - pair val; - ::decode(val, bp); - assert(seq_to_key(val.first) == iter->key()); - bp = val.second.begin(); - return ObjectContents(bp); - } - return ObjectContents(); -} - -ObjectContents FileStoreTracker::get_content( - const pair &obj, uint64_t version) -{ - set to_get; - map got; - to_get.insert(seq_to_key(version)); - db->get(obj_to_prefix(obj), to_get, &got); - if (got.empty()) - return ObjectContents(); - pair val; - bufferlist::iterator bp = got.begin()->second.begin(); - ::decode(val, bp); - bp = val.second.begin(); - assert(val.first == version); - return ObjectContents(bp); -} - -pair FileStoreTracker::get_valid_reads( - const pair &obj) -{ - pair bounds = make_pair(0,1); - KeyValueDB::Iterator iter = db->get_iterator( - obj_to_prefix(obj)); - iter->seek_to_last(); - if (iter->valid()) { - pair val; - bufferlist bl = iter->value(); - bufferlist::iterator bp = bl.begin(); - ::decode(val, bp); - bounds.second = val.first + 1; - } - - ObjStatus obj_status = get_obj_status(obj, db); - bounds.first = obj_status.get_last_applied(restart_seq); - return bounds; -} - -void clear_obsolete(const pair &obj, - const ObjStatus &status, - KeyValueDB *db, - KeyValueDB::Transaction t) -{ - KeyValueDB::Iterator iter = db->get_iterator(obj_to_prefix(obj)); - set to_remove; - iter->seek_to_first(); - for (; iter->valid() && iter->key() < seq_to_key(status.trim_to()); - iter->next()) - to_remove.insert(iter->key()); - t->rmkeys(obj_to_prefix(obj), to_remove); -} - -void FileStoreTracker::committed(const pair &obj, - uint64_t seq) { - Mutex::Locker l(lock); - ObjStatus status = get_obj_status(obj, db); - assert(status.last_committed < seq); - status.last_committed = seq; - KeyValueDB::Transaction t = db->get_transaction(); - clear_obsolete(obj, status, db, t); - set_obj_status(obj, status, t); - db->submit_transaction(t); -} - -void FileStoreTracker::applied(const pair &obj, - uint64_t seq) { - Mutex::Locker l(lock); - std::cerr << "Applied " << obj << " version " << seq << std::endl; - ObjStatus status = get_obj_status(obj, db); - assert(status.last_applied < seq); - status.set_last_applied(seq, restart_seq); - KeyValueDB::Transaction t = db->get_transaction(); - clear_obsolete(obj, status, db, t); - set_obj_status(obj, status, t); - db->submit_transaction(t); -} - - -uint64_t FileStoreTracker::set_content(const pair &obj, - ObjectContents &content) { - KeyValueDB::Transaction t = db->get_transaction(); - KeyValueDB::Iterator iter = db->get_iterator( - obj_to_prefix(obj)); - iter->seek_to_last(); - uint64_t most_recent = 0; - if (iter->valid()) { - pair val; - bufferlist bl = iter->value(); - bufferlist::iterator bp = bl.begin(); - ::decode(val, bp); - most_recent = val.first; - } - bufferlist buf_content; - content.encode(buf_content); - map to_set; - ::encode(make_pair(most_recent + 1, buf_content), - to_set[seq_to_key(most_recent + 1)]); - t->set(obj_to_prefix(obj), to_set); - db->submit_transaction(t); - return most_recent + 1; -} diff --git a/src/test/filestore/FileStoreTracker.h b/src/test/filestore/FileStoreTracker.h deleted file mode 100644 index d70e54a11231..000000000000 --- a/src/test/filestore/FileStoreTracker.h +++ /dev/null @@ -1,138 +0,0 @@ -// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- - -#ifndef FILESTORE_TRACKER_H -#define FILESTORE_TRACKER_H -#include "test/common/ObjectContents.h" -#include "os/FileStore.h" -#include "os/KeyValueDB.h" -#include -#include -#include -#include "common/Mutex.h" - -class FileStoreTracker { - const static uint64_t SIZE = 4 * 1024; - ObjectStore *store; - KeyValueDB *db; - Mutex lock; - uint64_t restart_seq; - - struct OutTransaction { - list, uint64_t> > *in_flight; - ObjectStore::Transaction *t; - }; -public: - FileStoreTracker(ObjectStore *store, KeyValueDB *db) - : store(store), db(db), - lock("Tracker Lock"), restart_seq(0) {} - - class Transaction { - class Op { - public: - virtual void operator()(FileStoreTracker *harness, - OutTransaction *out) = 0; - virtual ~Op() {}; - }; - list ops; - class Write : public Op { - public: - string coll; - string oid; - Write(const string &coll, - const string &oid) - : coll(coll), oid(oid) {} - void operator()(FileStoreTracker *harness, - OutTransaction *out) { - harness->write(make_pair(coll, oid), out); - } - }; - class CloneRange : public Op { - public: - string coll; - string from; - string to; - CloneRange(const string &coll, - const string &from, - const string &to) - : coll(coll), from(from), to(to) {} - void operator()(FileStoreTracker *harness, - OutTransaction *out) { - harness->clone_range(make_pair(coll, from), make_pair(coll, to), - out); - } - }; - class Clone : public Op { - public: - string coll; - string from; - string to; - Clone(const string &coll, - const string &from, - const string &to) - : coll(coll), from(from), to(to) {} - void operator()(FileStoreTracker *harness, - OutTransaction *out) { - harness->clone(make_pair(coll, from), make_pair(coll, to), - out); - } - }; - class Remove: public Op { - public: - string coll; - string obj; - Remove(const string &coll, - const string &obj) - : coll(coll), obj(obj) {} - void operator()(FileStoreTracker *harness, - OutTransaction *out) { - harness->remove(make_pair(coll, obj), - out); - } - }; - public: - void write(const string &coll, const string &oid) { - ops.push_back(new Write(coll, oid)); - } - void clone_range(const string &coll, const string &from, - const string &to) { - ops.push_back(new CloneRange(coll, from, to)); - } - void clone(const string &coll, const string &from, - const string &to) { - ops.push_back(new Clone(coll, from, to)); - } - void remove(const string &coll, const string &oid) { - ops.push_back(new Remove(coll, oid)); - } - friend class FileStoreTracker; - }; - - int init(); - void submit_transaction(Transaction &t); - void verify(const string &coll, - const string &from, - bool on_start = false); - -private: - ObjectContents get_current_content(const pair &obj); - pair get_valid_reads(const pair &obj); - ObjectContents get_content(const pair &obj, uint64_t version); - - void committed(const pair &obj, uint64_t seq); - void applied(const pair &obj, uint64_t seq); - uint64_t set_content(const pair &obj, ObjectContents &content); - - // ObjectContents Operations - void write(const pair &obj, OutTransaction *out); - void remove(const pair &obj, OutTransaction *out); - void clone_range(const pair &from, - const pair &to, - OutTransaction *out); - void clone(const pair &from, - const pair &to, - OutTransaction *out); - friend class OnApplied; - friend class OnCommitted; -}; - -#endif diff --git a/src/test/filestore/TestFileStoreState.cc b/src/test/filestore/TestFileStoreState.cc deleted file mode 100644 index a34e52636ceb..000000000000 --- a/src/test/filestore/TestFileStoreState.cc +++ /dev/null @@ -1,296 +0,0 @@ -// -*- 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 New Dream Network -* -* This is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License version 2.1, as published by the Free Software -* Foundation. See file COPYING. -*/ -#include -#include -#include -#include -#include -#include -#include "os/FileStore.h" -#include "common/ceph_argparse.h" -#include "global/global_init.h" -#include "common/debug.h" -#include -#include -#include "TestFileStoreState.h" -#include "include/assert.h" - -#define dout_subsys ceph_subsys_filestore -#undef dout_prefix -#define dout_prefix *_dout << "ceph_test_filestore_state " - -const coll_t TestFileStoreState::META_COLL("meta"); -const coll_t TestFileStoreState::TEMP_COLL("temp"); - -void TestFileStoreState::init(int colls, int objs) -{ - dout(5) << "init " << colls << " colls " << objs << " objs" << dendl; - - ObjectStore::Transaction *t; - t = new ObjectStore::Transaction; - - t->create_collection(META_COLL); - t->create_collection(TEMP_COLL); - m_store->apply_transaction(*t); - - wait_for_ready(); - - int baseid = 0; - for (int i = 0; i < colls; i++) { - int coll_id = i; - coll_entry_t *entry = coll_create(coll_id); - dout(5) << "init create collection " << entry->m_coll.to_str() - << " meta " << entry->m_meta_obj.oid.name << dendl; - - t = new ObjectStore::Transaction; - t->create_collection(entry->m_coll); - t->touch(META_COLL, entry->m_meta_obj); - - for (int i = 0; i < objs; i++) { - hobject_t *obj = entry->touch_obj(i + baseid); - t->touch(entry->m_coll, *obj); - ceph_assert(i + baseid == m_num_objects); - m_num_objects++; - } - baseid += objs; - - m_store->queue_transaction(&(entry->m_osr), t, - new C_OnFinished(this, t)); - inc_in_flight(); - - m_collections.insert(make_pair(coll_id, entry)); - m_collections_ids.push_back(coll_id); - m_next_coll_nr++; - } - dout(5) << "init has " << m_in_flight.read() << "in-flight transactions" << dendl; - wait_for_done(); - dout(5) << "init finished" << dendl; -} - -TestFileStoreState::coll_entry_t *TestFileStoreState::coll_create(int id) -{ - char buf[100]; - char meta_buf[100]; - memset(buf, 0, 100); - memset(meta_buf, 0, 100); - snprintf(buf, 100, "0.%d_head", id); - snprintf(meta_buf, 100, "pglog_0.%d_head", id); - return (new coll_entry_t(id, buf, meta_buf)); -} - -TestFileStoreState::coll_entry_t* -TestFileStoreState::get_coll(int key, bool erase) -{ - dout(5) << "get_coll id " << key << dendl; - - coll_entry_t *entry = NULL; - map::iterator it = m_collections.find(key); - if (it != m_collections.end()) { - entry = it->second; - if (erase) { - m_collections.erase(it); - vector::iterator cid_it = m_collections_ids.begin()+(entry->m_id); - dout(20) << __func__ << " removing key " << key << " coll_id " << entry->m_id - << " iterator's entry id " << (*cid_it) << dendl; - m_collections_ids.erase(cid_it); - } - } - - dout(5) << "get_coll id " << key; - if (!entry) - *_dout << " non-existent"; - else - *_dout << " name " << entry->m_coll.to_str(); - *_dout << dendl; - return entry; -} - -TestFileStoreState::coll_entry_t* -TestFileStoreState::get_coll_at(int pos, bool erase) -{ - dout(5) << "get_coll_at pos " << pos << dendl; - - if (m_collections.empty()) - return NULL; - - assert((size_t) pos < m_collections_ids.size()); - - int coll_id = m_collections_ids[pos]; - coll_entry_t *entry = m_collections[coll_id]; - - if (entry == NULL) { - dout(5) << "get_coll_at pos " << pos << " non-existent" << dendl; - return NULL; - } - - if (erase) { - m_collections.erase(coll_id); - vector::iterator it = m_collections_ids.begin()+(pos); - dout(20) << __func__ << " removing pos " << pos << " coll_id " << coll_id - << " iterator's entry id " << (*it) << dendl; - m_collections_ids.erase(it); - } - - dout(5) << "get_coll_at pos " << pos << ": " - << entry->m_coll << "(removed: " << erase << ")" << dendl; - - return entry; -} - -TestFileStoreState::coll_entry_t::~coll_entry_t() -{ - if (m_objects.size() > 0) { - map::iterator it = m_objects.begin(); - for (; it != m_objects.end(); ++it) { - hobject_t *obj = it->second; - if (obj) { - delete obj; - } - } - m_objects.clear(); - } -} - -bool TestFileStoreState::coll_entry_t::check_for_obj(int id) -{ - if (m_objects.count(id)) - return true; - return false; -} - -hobject_t *TestFileStoreState::coll_entry_t::touch_obj(int id) -{ - map::iterator it = m_objects.find(id); - if (it != m_objects.end()) { - dout(5) << "touch_obj coll id " << m_id - << " name " << it->second->oid.name << dendl; - return it->second; - } - - char buf[100]; - memset(buf, 0, 100); - snprintf(buf, 100, "obj%d", id); - - hobject_t *obj = new hobject_t(sobject_t(object_t(buf), CEPH_NOSNAP)); - m_objects.insert(make_pair(id, obj)); - - dout(5) << "touch_obj coll id " << m_id << " name " << buf << dendl; - return obj; -} - -hobject_t *TestFileStoreState::coll_entry_t::get_obj(int id) -{ - return get_obj(id, false); -} - -/** - * remove_obj - Removes object without freeing it. - * @param id Object's id in the map. - * @return The object or NULL in case of error. - */ -hobject_t *TestFileStoreState::coll_entry_t::remove_obj(int id) -{ - return get_obj(id, true); -} - -hobject_t *TestFileStoreState::coll_entry_t::get_obj(int id, bool remove) -{ - map::iterator it = m_objects.find(id); - if (it == m_objects.end()) { - dout(5) << "get_obj coll " << m_coll.to_str() - << " obj #" << id << " non-existent" << dendl; - return NULL; - } - - hobject_t *obj = it->second; - if (remove) - m_objects.erase(it); - - dout(5) << "get_obj coll " << m_coll.to_str() << " id " << id - << ": " << obj->oid.name << "(removed: " << remove << ")" << dendl; - - return obj; -} - -hobject_t *TestFileStoreState::coll_entry_t::get_obj_at(int pos, int *key) -{ - return get_obj_at(pos, false, key); -} - -/** - * remove_obj_at - Removes object without freeing it. - * @param pos The map's position in which the object lies. - * @return The object or NULL in case of error. - */ -hobject_t *TestFileStoreState::coll_entry_t::remove_obj_at(int pos, int *key) -{ - return get_obj_at(pos, true, key); -} - -hobject_t *TestFileStoreState::coll_entry_t::get_obj_at(int pos, - bool remove, int *key) -{ - if (m_objects.empty()) { - dout(5) << "get_obj_at coll " << m_coll.to_str() << " pos " << pos - << " in an empty collection" << dendl; - return NULL; - } - - hobject_t *ret = NULL; - map::iterator it = m_objects.begin(); - for (int i = 0; it != m_objects.end(); ++it, i++) { - if (i == pos) { - ret = it->second; - break; - } - } - - if (ret == NULL) { - dout(5) << "get_obj_at coll " << m_coll.to_str() << " pos " << pos - << " non-existent" << dendl; - return NULL; - } - - if (key != NULL) - *key = it->first; - - if (remove) - m_objects.erase(it); - - dout(5) << "get_obj_at coll id " << m_id << " pos " << pos - << ": " << ret->oid.name << "(removed: " << remove << ")" << dendl; - - return ret; -} - -hobject_t* -TestFileStoreState::coll_entry_t::replace_obj(int id, hobject_t *obj) { - hobject_t *old_obj = remove_obj(id); - m_objects.insert(make_pair(id, obj)); - return old_obj; -} - -int TestFileStoreState::coll_entry_t::get_random_obj_id(rngen_t& gen) -{ - ceph_assert(!m_objects.empty()); - - boost::uniform_int<> orig_obj_rng(0, m_objects.size()-1); - int pos = orig_obj_rng(gen); - map::iterator it = m_objects.begin(); - for (int i = 0; it != m_objects.end(); ++it, i++) { - if (i == pos) { - return it->first; - } - } - ceph_assert(0 == "INTERNAL ERROR"); -} diff --git a/src/test/filestore/TestFileStoreState.h b/src/test/filestore/TestFileStoreState.h deleted file mode 100644 index 7cd4527bccc5..000000000000 --- a/src/test/filestore/TestFileStoreState.h +++ /dev/null @@ -1,148 +0,0 @@ -// -*- 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 New Dream Network -* -* This is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License version 2.1, as published by the Free Software -* Foundation. See file COPYING. -*/ -#ifndef TEST_FILESTORE_STATE_H_ -#define TEST_FILESTORE_STATE_H_ - -#include "os/FileStore.h" -#include -#include -#include -#include -#include - -typedef boost::mt11213b rngen_t; - -class TestFileStoreState { -public: - struct coll_entry_t { - int m_id; - coll_t m_coll; - hobject_t m_meta_obj; - ObjectStore::Sequencer m_osr; - map m_objects; - int m_next_object_id; - - coll_entry_t(int i, char *coll_buf, char *meta_obj_buf) - : m_id(i), m_coll(coll_buf), - m_meta_obj(sobject_t(object_t(meta_obj_buf), CEPH_NOSNAP)), - m_osr(coll_buf), m_next_object_id(0) { - } - ~coll_entry_t(); - - hobject_t *touch_obj(int id); - bool check_for_obj(int id); - hobject_t *get_obj(int id); - hobject_t *remove_obj(int id); - hobject_t *get_obj_at(int pos, int *key = NULL); - hobject_t *remove_obj_at(int pos, int *key = NULL); - hobject_t *replace_obj(int id, hobject_t *obj); - int get_random_obj_id(rngen_t& gen); - - private: - hobject_t *get_obj(int id, bool remove); - hobject_t *get_obj_at(int pos, bool remove, int *key = NULL); - }; - - /* kept in upper case for consistency with coll_t's */ - static const coll_t META_COLL; - static const coll_t TEMP_COLL; - - protected: - boost::shared_ptr m_store; - map m_collections; - vector m_collections_ids; - int m_next_coll_nr; - int m_num_objs_per_coll; - int m_num_objects; - - int m_max_in_flight; - atomic_t m_in_flight; - Mutex m_finished_lock; - Cond m_finished_cond; - - void wait_for_ready() { - Mutex::Locker locker(m_finished_lock); - while ((m_max_in_flight > 0) && ((int)m_in_flight.read() >= m_max_in_flight)) - m_finished_cond.Wait(m_finished_lock); - } - - void wait_for_done() { - Mutex::Locker locker(m_finished_lock); - while (m_in_flight.read()) - m_finished_cond.Wait(m_finished_lock); - } - - void set_max_in_flight(int max) { - m_max_in_flight = max; - } - void set_num_objs_per_coll(int val) { - m_num_objs_per_coll = val; - } - - coll_entry_t *get_coll(int key, bool erase = false); - coll_entry_t *get_coll_at(int pos, bool erase = false); - - private: - static const int m_default_num_colls = 30; - - public: - TestFileStoreState(FileStore *store) : - m_next_coll_nr(0), m_num_objs_per_coll(10), m_num_objects(0), - m_max_in_flight(0), m_finished_lock("Finished Lock") { - m_in_flight.set(0); - m_store.reset(store); - } - ~TestFileStoreState() { - map::iterator it = m_collections.begin(); - while (it != m_collections.end()) { - if (it->second) - delete it->second; - m_collections.erase(it++); - } - } - - void init(int colls, int objs); - void init() { - init(m_default_num_colls, 0); - } - - int inc_in_flight() { - return ((int) m_in_flight.inc()); - } - - int dec_in_flight() { - return ((int) m_in_flight.dec()); - } - - coll_entry_t *coll_create(int id); - - class C_OnFinished: public Context { - protected: - TestFileStoreState *m_state; - ObjectStore::Transaction *m_tx; - - public: - C_OnFinished(TestFileStoreState *state, - ObjectStore::Transaction *t) : m_state(state), m_tx(t) { } - - void finish(int r) { - Mutex::Locker locker(m_state->m_finished_lock); - m_state->dec_in_flight(); - m_state->m_finished_cond.Signal(); - - delete m_tx; - } - }; -}; - -#endif /* TEST_FILESTORE_STATE_H_ */ diff --git a/src/test/filestore/chain_xattr.cc b/src/test/filestore/chain_xattr.cc deleted file mode 100644 index 8346c02b2b16..000000000000 --- a/src/test/filestore/chain_xattr.cc +++ /dev/null @@ -1,217 +0,0 @@ -// -*- 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) 2013 Cloudwatt - * - * Author: Loic Dachary - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Library Public License for more details. - * - */ - -#include -#include -#include "os/chain_xattr.h" -#include "include/Context.h" -#include "common/errno.h" -#include "common/ceph_argparse.h" -#include "global/global_init.h" -#include - -#define LARGE_BLOCK_LEN CHAIN_XATTR_MAX_BLOCK_LEN + 1024 - -TEST(chain_xattr, get_and_set) { - const char* file = "testfile"; - ::unlink(file); - int fd = ::open(file, O_CREAT|O_WRONLY|O_TRUNC, 0700); - const string user("user."); - - { - const string name = user + string(CHAIN_XATTR_MAX_NAME_LEN - user.size(), '@'); - const string x(LARGE_BLOCK_LEN, 'X'); - - { - char y[LARGE_BLOCK_LEN]; - ASSERT_EQ(LARGE_BLOCK_LEN, chain_setxattr(file, name.c_str(), x.c_str(), LARGE_BLOCK_LEN)); - ASSERT_EQ(LARGE_BLOCK_LEN, chain_getxattr(file, name.c_str(), 0, 0)); - ASSERT_EQ(LARGE_BLOCK_LEN, chain_getxattr(file, name.c_str(), y, LARGE_BLOCK_LEN)); - ASSERT_EQ(0, chain_removexattr(file, name.c_str())); - ASSERT_EQ(0, memcmp(x.c_str(), y, LARGE_BLOCK_LEN)); - } - - { - char y[LARGE_BLOCK_LEN]; - ASSERT_EQ(LARGE_BLOCK_LEN, chain_fsetxattr(fd, name.c_str(), x.c_str(), LARGE_BLOCK_LEN)); - ASSERT_EQ(LARGE_BLOCK_LEN, chain_fgetxattr(fd, name.c_str(), 0, 0)); - ASSERT_EQ(LARGE_BLOCK_LEN, chain_fgetxattr(fd, name.c_str(), y, LARGE_BLOCK_LEN)); - ASSERT_EQ(0, chain_fremovexattr(fd, name.c_str())); - ASSERT_EQ(0, memcmp(x.c_str(), y, LARGE_BLOCK_LEN)); - } - } - - // - // when chain_setxattr is used to store value that is - // CHAIN_XATTR_MAX_BLOCK_LEN * 2 + 10 bytes long it - // - // add user.foo => CHAIN_XATTR_MAX_BLOCK_LEN bytes - // add user.foo@1 => CHAIN_XATTR_MAX_BLOCK_LEN bytes - // add user.foo@2 => 10 bytes - // - // then ( no chain_removexattr in between ) when it is used to - // override with a value that is exactly CHAIN_XATTR_MAX_BLOCK_LEN - // bytes long it will - // - // replace user.foo => CHAIN_XATTR_MAX_BLOCK_LEN bytes - // remove user.foo@1 => CHAIN_XATTR_MAX_BLOCK_LEN bytes - // leak user.foo@2 => 10 bytes - // - // see http://marc.info/?l=ceph-devel&m=136027076615853&w=4 for the - // discussion - // - { - const string name = user + string(CHAIN_XATTR_MAX_NAME_LEN - user.size(), '@'); - const string x(LARGE_BLOCK_LEN, 'X'); - - { - char y[CHAIN_XATTR_MAX_NAME_LEN]; - ASSERT_EQ(LARGE_BLOCK_LEN, chain_setxattr(file, name.c_str(), x.c_str(), LARGE_BLOCK_LEN)); - ASSERT_EQ(CHAIN_XATTR_MAX_BLOCK_LEN, chain_setxattr(file, name.c_str(), x.c_str(), CHAIN_XATTR_MAX_BLOCK_LEN)); - ASSERT_EQ(CHAIN_XATTR_MAX_BLOCK_LEN, chain_getxattr(file, name.c_str(), 0, 0)); - ASSERT_EQ(CHAIN_XATTR_MAX_BLOCK_LEN, chain_getxattr(file, name.c_str(), y, CHAIN_XATTR_MAX_BLOCK_LEN)); - ASSERT_EQ(0, chain_removexattr(file, name.c_str())); - ASSERT_EQ(0, memcmp(x.c_str(), y, CHAIN_XATTR_MAX_BLOCK_LEN)); - } - - { - char y[CHAIN_XATTR_MAX_BLOCK_LEN]; - ASSERT_EQ(LARGE_BLOCK_LEN, chain_fsetxattr(fd, name.c_str(), x.c_str(), LARGE_BLOCK_LEN)); - ASSERT_EQ(CHAIN_XATTR_MAX_BLOCK_LEN, chain_fsetxattr(fd, name.c_str(), x.c_str(), CHAIN_XATTR_MAX_BLOCK_LEN)); - ASSERT_EQ(CHAIN_XATTR_MAX_BLOCK_LEN, chain_fgetxattr(fd, name.c_str(), 0, 0)); - ASSERT_EQ(CHAIN_XATTR_MAX_BLOCK_LEN, chain_fgetxattr(fd, name.c_str(), y, CHAIN_XATTR_MAX_BLOCK_LEN)); - ASSERT_EQ(0, chain_fremovexattr(fd, name.c_str())); - ASSERT_EQ(0, memcmp(x.c_str(), y, CHAIN_XATTR_MAX_BLOCK_LEN)); - } - } - - { - int x = 0; - ASSERT_EQ(-ENOENT, chain_setxattr("UNLIKELY_TO_EXIST", "NAME", &x, sizeof(x))); - ASSERT_EQ(-ENOENT, chain_getxattr("UNLIKELY_TO_EXIST", "NAME", 0, 0)); - ASSERT_EQ(-ENOENT, chain_getxattr("UNLIKELY_TO_EXIST", "NAME", &x, sizeof(x))); - ASSERT_EQ(-ENOENT, chain_removexattr("UNLIKELY_TO_EXIST", "NAME")); - int unlikely_to_be_a_valid_fd = 400; - ASSERT_EQ(-EBADF, chain_fsetxattr(unlikely_to_be_a_valid_fd, "NAME", &x, sizeof(x))); - ASSERT_EQ(-EBADF, chain_fgetxattr(unlikely_to_be_a_valid_fd, "NAME", 0, 0)); - ASSERT_EQ(-EBADF, chain_fgetxattr(unlikely_to_be_a_valid_fd, "NAME", &x, sizeof(x))); - ASSERT_EQ(-EBADF, chain_fremovexattr(unlikely_to_be_a_valid_fd, "NAME")); - } - - { - int x; - const string name = user + string(CHAIN_XATTR_MAX_NAME_LEN * 2, '@'); - ASSERT_THROW(chain_setxattr(file, name.c_str(), &x, sizeof(x)), FailedAssertion); - ASSERT_THROW(chain_fsetxattr(fd, name.c_str(), &x, sizeof(x)), FailedAssertion); - } - - { - const string name = user + string(CHAIN_XATTR_MAX_NAME_LEN - user.size(), '@'); - const string x(LARGE_BLOCK_LEN, 'X'); - { - char y[LARGE_BLOCK_LEN]; - ASSERT_EQ(LARGE_BLOCK_LEN, chain_setxattr(file, name.c_str(), x.c_str(), LARGE_BLOCK_LEN)); - ASSERT_EQ(-ERANGE, chain_getxattr(file, name.c_str(), y, LARGE_BLOCK_LEN - 1)); - ASSERT_EQ(-ERANGE, chain_getxattr(file, name.c_str(), y, CHAIN_XATTR_MAX_BLOCK_LEN)); - ASSERT_EQ(0, chain_removexattr(file, name.c_str())); - } - - { - char y[LARGE_BLOCK_LEN]; - ASSERT_EQ(LARGE_BLOCK_LEN, chain_fsetxattr(fd, name.c_str(), x.c_str(), LARGE_BLOCK_LEN)); - ASSERT_EQ(-ERANGE, chain_fgetxattr(fd, name.c_str(), y, LARGE_BLOCK_LEN - 1)); - ASSERT_EQ(-ERANGE, chain_fgetxattr(fd, name.c_str(), y, CHAIN_XATTR_MAX_BLOCK_LEN)); - ASSERT_EQ(0, chain_fremovexattr(fd, name.c_str())); - } - } - - ::close(fd); - ::unlink(file); -} - -TEST(chain_xattr, listxattr) { - const char* file = "testfile"; - ::unlink(file); - int fd = ::open(file, O_CREAT|O_WRONLY|O_TRUNC, 0700); - const string user("user."); - const string name1 = user + string(CHAIN_XATTR_MAX_NAME_LEN - user.size(), '1'); - const string name2 = user + string(CHAIN_XATTR_MAX_NAME_LEN - user.size(), '@'); - const string x(LARGE_BLOCK_LEN, 'X'); - const int y = 1234; - - ASSERT_EQ(LARGE_BLOCK_LEN, chain_setxattr(file, name1.c_str(), x.c_str(), LARGE_BLOCK_LEN)); - ASSERT_EQ((int)sizeof(y), chain_setxattr(file, name2.c_str(), &y, sizeof(y))); - - int buffer_size = name1.size() + sizeof('\0') + name2.size() + sizeof('\0'); - char* expected = (char*)malloc(buffer_size); - ::strcpy(expected, name1.c_str()); - ::strcpy(expected + name1.size() + 1, name2.c_str()); - char* actual = (char*)calloc(1, buffer_size); - ASSERT_LT(buffer_size, chain_listxattr(file, NULL, 0)); // size evaluation is conservative - chain_listxattr(file, actual, buffer_size); - ::memset(actual, '\0', buffer_size); - chain_flistxattr(fd, actual, buffer_size); - ASSERT_EQ(0, ::memcmp(expected, actual, buffer_size)); - - int unlikely_to_be_a_valid_fd = 400; - ASSERT_GT(0, chain_listxattr("UNLIKELY_TO_EXIST", actual, 0)); - ASSERT_GT(0, chain_listxattr("UNLIKELY_TO_EXIST", actual, buffer_size)); - ASSERT_GT(0, chain_flistxattr(unlikely_to_be_a_valid_fd, actual, 0)); - ASSERT_GT(0, chain_flistxattr(unlikely_to_be_a_valid_fd, actual, buffer_size)); - ASSERT_EQ(-ERANGE, chain_listxattr(file, actual, 1)); - ASSERT_EQ(-ERANGE, chain_flistxattr(fd, actual, 1)); - - ASSERT_EQ(0, chain_removexattr(file, name1.c_str())); - ASSERT_EQ(0, chain_removexattr(file, name2.c_str())); - - ::unlink(file); -} - -int main(int argc, char **argv) { - vector args; - argv_to_vec(argc, (const char **)argv, args); - - global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT, CODE_ENVIRONMENT_UTILITY, 0); - common_init_finish(g_ceph_context); - g_ceph_context->_conf->set_val("err_to_stderr", "false"); - g_ceph_context->_conf->set_val("log_to_stderr", "false"); - g_ceph_context->_conf->apply_changes(NULL); - - const char* file = "testfile"; - int x = 1234; - int y = 0; - int tmpfd = ::open(file, O_CREAT|O_WRONLY|O_TRUNC, 0700); - int ret = ::ceph_os_fsetxattr(tmpfd, "user.test", &x, sizeof(x)); - if (ret >= 0) - ret = ::ceph_os_fgetxattr(tmpfd, "user.test", &y, sizeof(y)); - ::close(tmpfd); - ::unlink(file); - if ((ret < 0) || (x != y)) { - cerr << "SKIP all tests because extended attributes don't appear to work in the file system in which the tests are run: " << cpp_strerror(ret) << std::endl; - } else { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); - } -} - -// Local Variables: -// compile-command: "cd ../.. ; make unittest_chain_xattr ; valgrind --tool=memcheck ./unittest_chain_xattr # --gtest_filter=chain_xattr.get_and_set" -// End: diff --git a/src/test/filestore/run_seed_to.sh b/src/test/filestore/run_seed_to.sh deleted file mode 100755 index d5bb671138c5..000000000000 --- a/src/test/filestore/run_seed_to.sh +++ /dev/null @@ -1,290 +0,0 @@ -#!/bin/bash -# vim: ts=8 sw=2 smarttab -# -# run_seed_to.sh - Run ceph_test_filestore_idempotent_sequence up until an -# injection point, generating a sequence of operations based on a -# provided seed. -# -# We also perform three additional tests, focused on assessing if -# replaying a larger chunck of the journal affects the expected store -# behavior. These tests will be performed by increasing the store's -# journal sync interval to a very large value, allowing the store to -# finish execution before the first sync (unless the store runs for -# over 10 hours, case on which the interval variables must be changed -# to an appropriate value). Unless the '--no-journal-test' option is -# specified, we will run the 3 following scenarios: -# -# 1) journal sync'ing for both stores is good as disabled -# (we call it '00', for store naming purposes) -# 2) journal sync'ing for store A is as good as disabled -# (we call it '01', for store naming purposes) -# 3) journal sync'ing for store B is as good as disabled -# (we call it '10', for store naming purposes) -# -# All log files are also appropriately named accordingly (i.e., a.00.fail, -# a.10.recover, or b.01.clean). -# -# By default, the test will not exit on error, although it will show the -# fail message. This behavior is so defined so we run the whole battery of -# tests, and obtain as many mismatches as possible in one go. We may force -# the test to exit on error by specifying the '--exit-on-error' option. -# -# -set -e - -test_opts="" - -usage() { - echo "usage: $1 [options..] " - echo - echo "options:" - echo " -c, --colls # of collections" - echo " -o, --objs # of objects" - echo " -b, --btrfs seq number for btrfs stores" - echo " --no-journal-test don't perform journal replay tests" - echo " -e, --exit-on-error exit with 1 on error" - echo " -v, --valgrind run commands through valgrind" - echo - echo "env vars:" - echo " OPTS_STORE additional opts for both stores" - echo " OPTS_STORE_A additional opts for store A" - echo " OPTS_STORE_B additional opts for store B" - echo -} - -die_on_missing_arg() { - if [[ "$2" == "" ]]; then - echo "$1: missing required parameter" - exit 1 - fi -} - - -required_args=2 -obtained_args=0 - -seed="" -killat="" -on_btrfs=0 -on_btrfs_seq=0 -journal_test=1 -min_sync_interval="36000" # ten hours, yes. -max_sync_interval="36001" -exit_on_error=0 -v="" - -do_rm() { - if [[ $on_btrfs -eq 0 ]]; then - rm -fr $* - fi -} - -set_arg() { - if [[ $1 -eq 1 ]]; then - seed=$2 - elif [[ $1 -eq 2 ]]; then - killat=$2 - else - echo "error: unknown purpose for '$2'" - usage $0 - exit 1 - fi -} - -while [[ $# -gt 0 ]]; -do - case "$1" in - -c | --colls) - die_on_missing_arg "$1" "$2" - test_opts="$test_opts --test-num-colls $2" - shift 2 - ;; - -o | --objs) - die_on_missing_arg "$1" "$2" - test_opts="$test_opts --test-num-objs $2" - shift 2 - ;; - -h | --help) - usage $0 ; - exit 0 - ;; - -b | --btrfs) - die_on_missing_arg "$1" "$2" - on_btrfs=1 - on_btrfs_seq=$2 - shift 2 - ;; - --no-journal-test) - journal_test=0 - shift - ;; - -e | --exit-on-error) - exit_on_error=1 - shift - ;; - -v | --valgrind) - v="valgrind --leak-check=full" - shift - ;; - --) - shift - break - ;; - -*) - echo "$1: unknown option" >&2 - usage $0 - exit 1 - ;; - *) - obtained_args=$(($obtained_args+1)) - set_arg $obtained_args $1 - shift - ;; - esac -done - -if [[ $obtained_args -ne $required_args ]]; then - echo "error: missing argument" - usage $0 ; - exit 1 -fi - -if [[ "$OPTS_STORE" != "" ]]; then - test_opts="$test_opts $OPTS_STORE" -fi - -test_opts_a="$test_opts" -test_opts_b="$test_opts" - -if [[ "$OPTS_STORE_A" != "" ]]; then - test_opts_a="$test_opts_a $OPTS_STORE_A" -fi -if [[ "$OPTS_STORE_B" != "" ]]; then - test_opts_b="$test_opts_b $OPTS_STORE_B" -fi - -echo seed $seed -echo kill at $killat - -# run forever, until $killat... -to=1000000000 - -# -# store names -# -# We need these for two reasons: -# 1) if we are running the tests on a btrfs volume, then we need to use -# a seq number for each run. Being on btrfs means we will fail when -# removing the store's directories and it's far more simple to just -# specify differente store names such as 'a.$seq' or 'b.$seq'. -# -# 2) unless the '--no-journal-test' option is specified, we will run -# three additional tests for each store, and we will reuse the same -# command for each one of the runs, but varying the store's name and -# arguments. -# -store_a="a" -store_b="b" - -if [[ $on_btrfs -eq 1 ]]; then - store_a="$store_a.$on_btrfs_seq" - store_b="$store_b.$on_btrfs_seq" -fi - -total_runs=1 - -if [[ $journal_test -eq 1 ]]; then - total_runs=$(($total_runs + 3)) -fi - -num_runs=0 - -opt_min_sync="--filestore-min-sync-interval $min_sync_interval" -opt_max_sync="--filestore-max-sync-interval $max_sync_interval" - -ret=0 - -while [[ $num_runs -lt $total_runs ]]; -do - tmp_name_a=$store_a - tmp_name_b=$store_b - tmp_opts_a=$test_opts_a - tmp_opts_b=$test_opts_b - - # - # We have already tested whether there are diffs when both journals - # are properly working. Now let's try on three other scenarios: - # 1) journal sync'ing for both stores is good as disabled - # (we call it '00') - # 2) journal sync'ing for store A is as good as disabled - # (we call it '01') - # 3) journal sync'ing for store B is as good as disabled - # (we call it '10') - # - if [[ $num_runs -gt 0 && $journal_test -eq 1 ]]; then - echo "run #$num_runs" - case $num_runs in - 1) - tmp_name_a="$tmp_name_a.00" - tmp_name_b="$tmp_name_b.00" - tmp_opts_a="$tmp_opts_a $opt_min_sync $opt_max_sync" - tmp_opts_b="$tmp_opts_b $opt_min_sync $opt_max_sync" - ;; - 2) - tmp_name_a="$tmp_name_a.01" - tmp_name_b="$tmp_name_b.01" - tmp_opts_a="$tmp_opts_a $opt_min_sync $opt_max_sync" - ;; - 3) - tmp_name_a="$tmp_name_a.10" - tmp_name_b="$tmp_name_b.10" - tmp_opts_b="$tmp_opts_b $opt_min_sync $opt_max_sync" - ;; - esac - fi - - do_rm $tmp_name_a $tmp_name_a.fail $tmp_name_a.recover - $v ceph_test_filestore_idempotent_sequence run-sequence-to $to \ - $tmp_name_a $tmp_name_a/journal \ - --test-seed $seed --osd-journal-size 100 \ - --filestore-kill-at $killat $tmp_opts_a \ - --log-file $tmp_name_a.fail --debug-filestore 20 || true - - stop_at=`ceph_test_filestore_idempotent_sequence get-last-op \ - $tmp_name_a $tmp_name_a/journal \ - --log-file $tmp_name_a.recover \ - --debug-filestore 20 --debug-journal 20` - - if [[ "`expr $stop_at - $stop_at 2>/dev/null`" != "0" ]]; then - echo "error: get-last-op returned '$stop_at'" - exit 1 - fi - - echo stopped at $stop_at - - do_rm $tmp_name_b $tmp_name_b.clean - $v ceph_test_filestore_idempotent_sequence run-sequence-to \ - $stop_at $tmp_name_b $tmp_name_b/journal \ - --test-seed $seed --osd-journal-size 100 \ - --log-file $tmp_name_b.clean --debug-filestore 20 $tmp_opts_b - - if $v ceph_test_filestore_idempotent_sequence diff \ - $tmp_name_a $tmp_name_a/journal $tmp_name_b $tmp_name_b/journal ; then - echo OK - else - echo "FAIL" - echo " see:" - echo " $tmp_name_a.fail -- leading up to failure" - echo " $tmp_name_a.recover -- journal replay" - echo " $tmp_name_b.clean -- the clean reference" - - ret=1 - if [[ $exit_on_error -eq 1 ]]; then - exit 1 - fi - fi - - num_runs=$(($num_runs+1)) -done - -exit $ret diff --git a/src/test/filestore/run_seed_to_range.sh b/src/test/filestore/run_seed_to_range.sh deleted file mode 100755 index 365b34918d27..000000000000 --- a/src/test/filestore/run_seed_to_range.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh - -set -e - -seed=$1 -from=$2 -to=$3 -dir=$4 - -mydir=`dirname $0` - -for f in `seq $from $to` -do - if ! $mydir/run_seed_to.sh $seed $f; then - if [ -d $dir ]; then - echo copying evidence to $dir - cp -a . $dir - else - echo no dir provided for evidence disposal - fi - exit 1 - fi -done \ No newline at end of file diff --git a/src/test/filestore/store_test.cc b/src/test/filestore/store_test.cc deleted file mode 100644 index 8e88ab325e12..000000000000 --- a/src/test/filestore/store_test.cc +++ /dev/null @@ -1,1065 +0,0 @@ -// -*- 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) 2004-2006 Sage Weil - * - * This is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License version 2.1, as published by the Free Software - * Foundation. See file COPYING. - * - */ - -#include -#include -#include -#include -#include -#include "os/FileStore.h" -#include "os/KeyValueStore.h" -#include "include/Context.h" -#include "common/ceph_argparse.h" -#include "global/global_init.h" -#include "common/Mutex.h" -#include "common/Cond.h" -#include "common/errno.h" -#include -#include -#include -#include -#include - -#include "include/unordered_map.h" -typedef boost::mt11213b gen_type; - -#if GTEST_HAS_PARAM_TEST - -class StoreTest : public ::testing::TestWithParam { -public: - boost::scoped_ptr store; - - StoreTest() : store(0) {} - virtual void SetUp() { - int r = ::mkdir("store_test_temp_dir", 0777); - if (r < 0 && errno != EEXIST) { - r = -errno; - cerr << __func__ << ": unable to create store_test_temp_dir" << ": " << cpp_strerror(r) << std::endl; - return; - } - - ObjectStore *store_ = ObjectStore::create(g_ceph_context, - string(GetParam()), - string("store_test_temp_dir"), - string("store_test_temp_journal")); - store.reset(store_); - EXPECT_EQ(store->mkfs(), 0); - EXPECT_EQ(store->mount(), 0); - } - - virtual void TearDown() { - store->umount(); - } -}; - -bool sorted(const vector &in) { - ghobject_t start; - for (vector::const_iterator i = in.begin(); - i != in.end(); - ++i) { - if (start > *i) return false; - start = *i; - } - return true; -} - -TEST_P(StoreTest, SimpleColTest) { - coll_t cid = coll_t("initial"); - int r = 0; - { - ObjectStore::Transaction t; - t.create_collection(cid); - cerr << "create collection" << std::endl; - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } - { - ObjectStore::Transaction t; - t.remove_collection(cid); - cerr << "remove collection" << std::endl; - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } - { - ObjectStore::Transaction t; - t.create_collection(cid); - cerr << "add collection" << std::endl; - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } - { - ObjectStore::Transaction t; - t.remove_collection(cid); - cerr << "remove collection" << std::endl; - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } -} - -TEST_P(StoreTest, SimpleObjectTest) { - int r; - coll_t cid = coll_t("coll"); - { - ObjectStore::Transaction t; - t.create_collection(cid); - cerr << "Creating collection " << cid << std::endl; - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } - ghobject_t hoid(hobject_t(sobject_t("Object 1", CEPH_NOSNAP))); - { - ObjectStore::Transaction t; - t.touch(cid, hoid); - cerr << "Creating object " << hoid << std::endl; - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } - { - ObjectStore::Transaction t; - t.remove(cid, hoid); - t.remove_collection(cid); - cerr << "Cleaning" << std::endl; - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } -} - -TEST_P(StoreTest, SimpleObjectLongnameTest) { - int r; - coll_t cid = coll_t("coll"); - { - ObjectStore::Transaction t; - t.create_collection(cid); - cerr << "Creating collection " << cid << std::endl; - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } - ghobject_t hoid(hobject_t(sobject_t("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaObjectaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 1", CEPH_NOSNAP))); - { - ObjectStore::Transaction t; - t.touch(cid, hoid); - cerr << "Creating object " << hoid << std::endl; - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } - { - ObjectStore::Transaction t; - t.remove(cid, hoid); - t.remove_collection(cid); - cerr << "Cleaning" << std::endl; - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } -} - -TEST_P(StoreTest, ManyObjectTest) { - int NUM_OBJS = 2000; - int r = 0; - coll_t cid("blah"); - string base = ""; - for (int i = 0; i < 100; ++i) base.append("aaaaa"); - set created; - { - ObjectStore::Transaction t; - t.create_collection(cid); - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } - for (int i = 0; i < NUM_OBJS; ++i) { - if (!(i % 5)) { - cerr << "Object " << i << std::endl; - } - ObjectStore::Transaction t; - char buf[100]; - snprintf(buf, sizeof(buf), "%d", i); - ghobject_t hoid(hobject_t(sobject_t(string(buf) + base, CEPH_NOSNAP))); - t.touch(cid, hoid); - created.insert(hoid); - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } - - for (set::iterator i = created.begin(); - i != created.end(); - ++i) { - struct stat buf; - ASSERT_TRUE(!store->stat(cid, *i, &buf)); - } - - set listed; - vector objects; - r = store->collection_list(cid, objects); - ASSERT_EQ(r, 0); - - cerr << "objects.size() is " << objects.size() << std::endl; - for (vector ::iterator i = objects.begin(); - i != objects.end(); - ++i) { - listed.insert(*i); - ASSERT_TRUE(created.count(*i)); - } - ASSERT_TRUE(listed.size() == created.size()); - - ghobject_t start, next; - objects.clear(); - r = store->collection_list_partial( - cid, - ghobject_t::get_max(), - 50, - 60, - 0, - &objects, - &next - ); - ASSERT_EQ(r, 0); - ASSERT_TRUE(objects.empty()); - - objects.clear(); - listed.clear(); - while (1) { - r = store->collection_list_partial(cid, start, - 50, - 60, - 0, - &objects, - &next); - ASSERT_TRUE(sorted(objects)); - ASSERT_EQ(r, 0); - listed.insert(objects.begin(), objects.end()); - if (objects.size() < 50) { - ASSERT_TRUE(next.is_max()); - break; - } - objects.clear(); - start = next; - } - cerr << "listed.size() is " << listed.size() << std::endl; - ASSERT_TRUE(listed.size() == created.size()); - for (set::iterator i = listed.begin(); - i != listed.end(); - ++i) { - ASSERT_TRUE(created.count(*i)); - } - - for (set::iterator i = created.begin(); - i != created.end(); - ++i) { - ObjectStore::Transaction t; - t.remove(cid, *i); - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } - cerr << "cleaning up" << std::endl; - { - ObjectStore::Transaction t; - t.remove_collection(cid); - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } -} - -class ObjectGenerator { -public: - virtual ghobject_t create_object(gen_type *gen) = 0; - virtual ~ObjectGenerator() {} -}; - -class MixedGenerator : public ObjectGenerator { -public: - unsigned seq; - MixedGenerator() : seq(0) {} - ghobject_t create_object(gen_type *gen) { - char buf[100]; - snprintf(buf, sizeof(buf), "%u", seq); - - boost::uniform_int<> true_false(0, 1); - string name(buf); - if (true_false(*gen)) { - // long - for (int i = 0; i < 100; ++i) name.append("aaaaa"); - } else if (true_false(*gen)) { - name = "DIR_" + name; - } - - // hash - //boost::binomial_distribution bin(0xFFFFFF, 0.5); - ++seq; - return ghobject_t(hobject_t(name, string(), rand() & 2 ? CEPH_NOSNAP : rand(), rand() & 0xFF, 0, "")); - } -}; - -class SyntheticWorkloadState { -public: - static const unsigned max_in_flight = 16; - static const unsigned max_objects = 3000; - coll_t cid; - unsigned in_flight; - set available_objects; - set in_use_objects; - ObjectGenerator *object_gen; - gen_type *rng; - ObjectStore *store; - ObjectStore::Sequencer *osr; - - Mutex lock; - Cond cond; - - class C_SyntheticOnReadable : public Context { - public: - SyntheticWorkloadState *state; - ObjectStore::Transaction *t; - ghobject_t hoid; - C_SyntheticOnReadable(SyntheticWorkloadState *state, - ObjectStore::Transaction *t, ghobject_t hoid) - : state(state), t(t), hoid(hoid) {} - - void finish(int r) { - ASSERT_TRUE(r >= 0); - Mutex::Locker locker(state->lock); - if (state->in_use_objects.count(hoid)) { - state->available_objects.insert(hoid); - state->in_use_objects.erase(hoid); - } - --(state->in_flight); - state->cond.Signal(); - } - }; - - - SyntheticWorkloadState(ObjectStore *store, - ObjectGenerator *gen, - gen_type *rng, - ObjectStore::Sequencer *osr, - coll_t cid) - : cid(cid), in_flight(0), object_gen(gen), rng(rng), store(store), osr(osr), - lock("State lock") {} - - int init() { - ObjectStore::Transaction t; - t.create_collection(cid); - return store->apply_transaction(t); - } - - ghobject_t get_uniform_random_object() { - while (in_flight >= max_in_flight || available_objects.empty()) - cond.Wait(lock); - boost::uniform_int<> choose(0, available_objects.size() - 1); - int index = choose(*rng); - set::iterator i = available_objects.begin(); - for ( ; index > 0; --index, ++i) ; - ghobject_t ret = *i; - available_objects.erase(i); - return ret; - } - - void wait_for_ready() { - while (in_flight >= max_in_flight) - cond.Wait(lock); - } - - void wait_for_done() { - Mutex::Locker locker(lock); - while (in_flight) - cond.Wait(lock); - } - - bool can_create() { - return (available_objects.size() + in_use_objects.size()) < max_objects; - } - - bool can_unlink() { - return (available_objects.size() + in_use_objects.size()) > 0; - } - - int touch() { - Mutex::Locker locker(lock); - if (!can_create()) - return -ENOSPC; - wait_for_ready(); - ghobject_t new_obj = object_gen->create_object(rng); - in_use_objects.insert(new_obj); - available_objects.erase(new_obj); - ObjectStore::Transaction *t = new ObjectStore::Transaction; - t->touch(cid, new_obj); - ++in_flight; - return store->queue_transaction(osr, t, new C_SyntheticOnReadable(this, t, new_obj)); - } - - void scan() { - Mutex::Locker locker(lock); - while (in_flight) - cond.Wait(lock); - vector objects; - set objects_set, objects_set2; - ghobject_t next, current; - while (1) { - cerr << "scanning..." << std::endl; - int r = store->collection_list_partial(cid, current, 50, 100, - 0, &objects, &next); - ASSERT_EQ(r, 0); - ASSERT_TRUE(sorted(objects)); - objects_set.insert(objects.begin(), objects.end()); - objects.clear(); - if (next.is_max()) break; - current = next; - } - ASSERT_EQ(objects_set.size(), available_objects.size()); - for (set::iterator i = objects_set.begin(); - i != objects_set.end(); - ++i) { - ASSERT_GT(available_objects.count(*i), (unsigned)0); - } - - int r = store->collection_list(cid, objects); - ASSERT_EQ(r, 0); - objects_set2.insert(objects.begin(), objects.end()); - ASSERT_EQ(objects_set2.size(), available_objects.size()); - for (set::iterator i = objects_set2.begin(); - i != objects_set2.end(); - ++i) { - ASSERT_GT(available_objects.count(*i), (unsigned)0); - } - } - - int stat() { - ghobject_t hoid; - { - Mutex::Locker locker(lock); - if (!can_unlink()) - return -ENOENT; - hoid = get_uniform_random_object(); - in_use_objects.insert(hoid); - ++in_flight; - } - struct stat buf; - int r = store->stat(cid, hoid, &buf); - { - Mutex::Locker locker(lock); - --in_flight; - cond.Signal(); - in_use_objects.erase(hoid); - available_objects.insert(hoid); - } - return r; - } - - int unlink() { - Mutex::Locker locker(lock); - if (!can_unlink()) - return -ENOENT; - ghobject_t to_remove = get_uniform_random_object(); - ObjectStore::Transaction *t = new ObjectStore::Transaction; - t->remove(cid, to_remove); - ++in_flight; - return store->queue_transaction(osr, t, new C_SyntheticOnReadable(this, t, to_remove)); - } - - void print_internal_state() { - Mutex::Locker locker(lock); - cerr << "available_objects: " << available_objects.size() - << " in_use_objects: " << in_use_objects.size() - << " total objects: " << in_use_objects.size() + available_objects.size() - << " in_flight " << in_flight << std::endl; - } -}; - -TEST_P(StoreTest, Synthetic) { - ObjectStore::Sequencer osr("test"); - MixedGenerator gen; - gen_type rng(time(NULL)); - coll_t cid("synthetic_1"); - - SyntheticWorkloadState test_obj(store.get(), &gen, &rng, &osr, cid); - test_obj.init(); - for (int i = 0; i < 1000; ++i) { - if (!(i % 10)) cerr << "seeding object " << i << std::endl; - test_obj.touch(); - } - for (int i = 0; i < 10000; ++i) { - if (!(i % 10)) { - cerr << "Op " << i << std::endl; - test_obj.print_internal_state(); - } - boost::uniform_int<> true_false(0, 99); - int val = true_false(rng); - if (val > 97) { - test_obj.scan(); - } else if (val > 50) { - test_obj.stat(); - } else if (val > 30) { - test_obj.unlink(); - } else { - test_obj.touch(); - } - } - test_obj.wait_for_done(); -} - -TEST_P(StoreTest, HashCollisionTest) { - coll_t cid("blah"); - int r; - { - ObjectStore::Transaction t; - t.create_collection(cid); - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } - string base = ""; - for (int i = 0; i < 100; ++i) base.append("aaaaa"); - set created; - for (int n = 0; n < 10; ++n) { - char nbuf[100]; - sprintf(nbuf, "n%d", n); - for (int i = 0; i < 1000; ++i) { - char buf[100]; - sprintf(buf, "%d", i); - if (!(i % 5)) { - cerr << "Object n" << n << " "<< i << std::endl; - } - ghobject_t hoid(hobject_t(string(buf) + base, string(), CEPH_NOSNAP, 0, 0, string(nbuf))); - { - ObjectStore::Transaction t; - t.touch(cid, hoid); - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } - created.insert(hoid); - } - } - vector objects; - r = store->collection_list(cid, objects); - ASSERT_EQ(r, 0); - set listed(objects.begin(), objects.end()); - cerr << "listed.size() is " << listed.size() << " and created.size() is " << created.size() << std::endl; - ASSERT_TRUE(listed.size() == created.size()); - objects.clear(); - listed.clear(); - ghobject_t current, next; - while (1) { - r = store->collection_list_partial(cid, current, 50, 60, - 0, &objects, &next); - ASSERT_EQ(r, 0); - ASSERT_TRUE(sorted(objects)); - for (vector::iterator i = objects.begin(); - i != objects.end(); - ++i) { - if (listed.count(*i)) - cerr << *i << " repeated" << std::endl; - listed.insert(*i); - } - if (objects.size() < 50) { - ASSERT_TRUE(next.is_max()); - break; - } - objects.clear(); - current = next; - } - cerr << "listed.size() is " << listed.size() << std::endl; - ASSERT_TRUE(listed.size() == created.size()); - for (set::iterator i = listed.begin(); - i != listed.end(); - ++i) { - ASSERT_TRUE(created.count(*i)); - } - - for (set::iterator i = created.begin(); - i != created.end(); - ++i) { - ObjectStore::Transaction t; - t.collection_remove(cid, *i); - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } - ObjectStore::Transaction t; - t.remove_collection(cid); - store->apply_transaction(t); -} - -TEST_P(StoreTest, OMapTest) { - coll_t cid("blah"); - ghobject_t hoid(hobject_t("tesomap", "", CEPH_NOSNAP, 0, 0, "")); - int r; - { - ObjectStore::Transaction t; - t.create_collection(cid); - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } - - map attrs; - { - ObjectStore::Transaction t; - t.touch(cid, hoid); - t.omap_clear(cid, hoid); - map start_set; - t.omap_setkeys(cid, hoid, start_set); - store->apply_transaction(t); - } - - for (int i = 0; i < 100; i++) { - if (!(i%5)) { - std::cout << "On iteration " << i << std::endl; - } - ObjectStore::Transaction t; - bufferlist bl; - map cur_attrs; - r = store->omap_get(cid, hoid, &bl, &cur_attrs); - ASSERT_EQ(r, 0); - for (map::iterator j = attrs.begin(); - j != attrs.end(); - ++j) { - bool correct = cur_attrs.count(j->first) && string(cur_attrs[j->first].c_str()) == string(j->second.c_str()); - if (!correct) { - std::cout << j->first << " is present in cur_attrs " << cur_attrs.count(j->first) << " times " << std::endl; - if (cur_attrs.count(j->first) > 0) { - std::cout << j->second.c_str() << " : " << cur_attrs[j->first].c_str() << std::endl; - } - } - ASSERT_EQ(correct, true); - } - ASSERT_EQ(attrs.size(), cur_attrs.size()); - - char buf[100]; - snprintf(buf, sizeof(buf), "%d", i); - bl.clear(); - bufferptr bp(buf, strlen(buf) + 1); - bl.append(bp); - map to_add; - to_add.insert(pair("key-" + string(buf), bl)); - attrs.insert(pair("key-" + string(buf), bl)); - t.omap_setkeys(cid, hoid, to_add); - store->apply_transaction(t); - } - - int i = 0; - while (attrs.size()) { - if (!(i%5)) { - std::cout << "removal: On iteration " << i << std::endl; - } - ObjectStore::Transaction t; - bufferlist bl; - map cur_attrs; - r = store->omap_get(cid, hoid, &bl, &cur_attrs); - ASSERT_EQ(r, 0); - for (map::iterator j = attrs.begin(); - j != attrs.end(); - ++j) { - bool correct = cur_attrs.count(j->first) && string(cur_attrs[j->first].c_str()) == string(j->second.c_str()); - if (!correct) { - std::cout << j->first << " is present in cur_attrs " << cur_attrs.count(j->first) << " times " << std::endl; - if (cur_attrs.count(j->first) > 0) { - std::cout << j->second.c_str() << " : " << cur_attrs[j->first].c_str() << std::endl; - } - } - ASSERT_EQ(correct, true); - } - - string to_remove = attrs.begin()->first; - set keys_to_remove; - keys_to_remove.insert(to_remove); - t.omap_rmkeys(cid, hoid, keys_to_remove); - store->apply_transaction(t); - - attrs.erase(to_remove); - - ++i; - } - - ObjectStore::Transaction t; - t.remove(cid, hoid); - t.remove_collection(cid); - store->apply_transaction(t); -} - -TEST_P(StoreTest, XattrTest) { - coll_t cid("blah"); - ghobject_t hoid(hobject_t("tesomap", "", CEPH_NOSNAP, 0, 0, "")); - bufferlist big; - for (unsigned i = 0; i < 10000; ++i) { - big.append('\0'); - } - bufferlist small; - for (unsigned i = 0; i < 10; ++i) { - small.append('\0'); - } - int r; - { - ObjectStore::Transaction t; - t.create_collection(cid); - t.touch(cid, hoid); - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } - - map attrs; - { - ObjectStore::Transaction t; - t.setattr(cid, hoid, "attr1", small); - attrs["attr1"] = small; - t.setattr(cid, hoid, "attr2", big); - attrs["attr2"] = big; - t.setattr(cid, hoid, "attr3", small); - attrs["attr3"] = small; - t.setattr(cid, hoid, "attr1", small); - attrs["attr1"] = small; - t.setattr(cid, hoid, "attr4", big); - attrs["attr4"] = big; - t.setattr(cid, hoid, "attr3", big); - attrs["attr3"] = big; - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } - - map aset; - store->getattrs(cid, hoid, aset); - ASSERT_EQ(aset.size(), attrs.size()); - for (map::iterator i = aset.begin(); - i != aset.end(); - ++i) { - bufferlist bl; - bl.push_back(i->second); - ASSERT_TRUE(attrs[i->first] == bl); - } - - { - ObjectStore::Transaction t; - t.rmattr(cid, hoid, "attr2"); - attrs.erase("attr2"); - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } - - aset.clear(); - store->getattrs(cid, hoid, aset); - ASSERT_EQ(aset.size(), attrs.size()); - for (map::iterator i = aset.begin(); - i != aset.end(); - ++i) { - bufferlist bl; - bl.push_back(i->second); - ASSERT_TRUE(attrs[i->first] == bl); - } - - bufferptr bp; - r = store->getattr(cid, hoid, "attr2", bp); - ASSERT_EQ(r, -ENODATA); - - r = store->getattr(cid, hoid, "attr3", bp); - ASSERT_GE(r, 0); - bufferlist bl2; - bl2.push_back(bp); - ASSERT_TRUE(bl2 == attrs["attr3"]); -} - -void colsplittest( - ObjectStore *store, - unsigned num_objects, - unsigned common_suffix_size - ) { - coll_t cid("from"); - coll_t tid("to"); - int r = 0; - { - ObjectStore::Transaction t; - t.create_collection(cid); - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } - { - ObjectStore::Transaction t; - for (uint32_t i = 0; i < 2*num_objects; ++i) { - stringstream objname; - objname << "obj" << i; - t.touch(cid, ghobject_t(hobject_t( - objname.str(), - "", - CEPH_NOSNAP, - i<apply_transaction(t); - ASSERT_EQ(r, 0); - } - { - ObjectStore::Transaction t; - t.create_collection(tid); - t.split_collection(cid, common_suffix_size+1, 0, tid); - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } - - ObjectStore::Transaction t; - vector objects; - r = store->collection_list(cid, objects); - ASSERT_EQ(r, 0); - ASSERT_EQ(objects.size(), num_objects); - for (vector::iterator i = objects.begin(); - i != objects.end(); - ++i) { - ASSERT_EQ(!(i->hobj.hash & (1<collection_list(tid, objects); - ASSERT_EQ(r, 0); - ASSERT_EQ(objects.size(), num_objects); - for (vector::iterator i = objects.begin(); - i != objects.end(); - ++i) { - ASSERT_EQ(i->hobj.hash & (1<apply_transaction(t); - ASSERT_EQ(r, 0); -} - -TEST_P(StoreTest, ColSplitTest1) { - colsplittest(store.get(), 10000, 11); -} -TEST_P(StoreTest, ColSplitTest2) { - colsplittest(store.get(), 100, 7); -} - -#if 0 -TEST_P(StoreTest, ColSplitTest3) { - colsplittest(store.get(), 100000, 25); -} -#endif - -/** - * This test tests adding two different groups - * of objects, each with 1 common prefix and 1 - * different prefix. We then remove half - * in order to verify that the merging correctly - * stops at the common prefix subdir. See bug - * #5273 */ -TEST_P(StoreTest, TwoHash) { - coll_t cid("asdf"); - int r; - { - ObjectStore::Transaction t; - t.create_collection(cid); - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } - std::cout << "Making objects" << std::endl; - for (int i = 0; i < 360; ++i) { - ObjectStore::Transaction t; - ghobject_t o; - if (i < 8) { - o.hobj.hash = (i << 16) | 0xA1; - t.touch(cid, o); - } - o.hobj.hash = (i << 16) | 0xB1; - t.touch(cid, o); - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } - std::cout << "Removing half" << std::endl; - for (int i = 1; i < 8; ++i) { - ObjectStore::Transaction t; - ghobject_t o; - o.hobj.hash = (i << 16) | 0xA1; - t.remove(cid, o); - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } - std::cout << "Checking" << std::endl; - for (int i = 1; i < 8; ++i) { - ObjectStore::Transaction t; - ghobject_t o; - o.hobj.hash = (i << 16) | 0xA1; - bool exists = store->exists(cid, o); - ASSERT_EQ(exists, false); - } - { - ghobject_t o; - o.hobj.hash = 0xA1; - bool exists = store->exists(cid, o); - ASSERT_EQ(exists, true); - } - std::cout << "Cleanup" << std::endl; - for (int i = 0; i < 360; ++i) { - ObjectStore::Transaction t; - ghobject_t o; - o.hobj.hash = (i << 16) | 0xA1; - t.remove(cid, o); - o.hobj.hash = (i << 16) | 0xB1; - t.remove(cid, o); - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } - ObjectStore::Transaction t; - t.remove_collection(cid); - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); -} - -TEST_P(StoreTest, MoveRename) { - coll_t temp_cid("mytemp"); - hobject_t temp_oid("tmp_oid", "", CEPH_NOSNAP, 0, 0, ""); - coll_t cid("dest"); - hobject_t oid("dest_oid", "", CEPH_NOSNAP, 0, 0, ""); - int r; - { - ObjectStore::Transaction t; - t.create_collection(cid); - t.touch(cid, oid); - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } - ASSERT_TRUE(store->exists(cid, oid)); - bufferlist data, attr; - map omap; - data.append("data payload"); - attr.append("attr value"); - omap["omap_key"].append("omap value"); - { - ObjectStore::Transaction t; - t.create_collection(temp_cid); - t.touch(temp_cid, temp_oid); - t.write(temp_cid, temp_oid, 0, data.length(), data); - t.setattr(temp_cid, temp_oid, "attr", attr); - t.omap_setkeys(temp_cid, temp_oid, omap); - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } - ASSERT_TRUE(store->exists(temp_cid, temp_oid)); - { - ObjectStore::Transaction t; - t.remove(cid, oid); - t.collection_move_rename(temp_cid, temp_oid, cid, oid); - r = store->apply_transaction(t); - ASSERT_EQ(r, 0); - } - ASSERT_TRUE(store->exists(cid, oid)); - ASSERT_FALSE(store->exists(temp_cid, temp_oid)); - { - bufferlist newdata; - r = store->read(cid, oid, 0, 1000, newdata); - ASSERT_GE(r, 0); - ASSERT_TRUE(newdata.contents_equal(data)); - bufferlist newattr; - r = store->getattr(cid, oid, "attr", newattr); - ASSERT_GE(r, 0); - ASSERT_TRUE(newattr.contents_equal(attr)); - set keys; - keys.insert("omap_key"); - map newomap; - r = store->omap_get_values(cid, oid, keys, &newomap); - ASSERT_GE(r, 0); - ASSERT_EQ(1u, newomap.size()); - ASSERT_TRUE(newomap.count("omap_key")); - ASSERT_TRUE(newomap["omap_key"].contents_equal(omap["omap_key"])); - } -} - -INSTANTIATE_TEST_CASE_P( - ObjectStore, - StoreTest, - ::testing::Values("filestore", "keyvaluestore-dev")); - -#else - -// Google Test may not support value-parameterized tests with some -// compilers. If we use conditional compilation to compile out all -// code referring to the gtest_main library, MSVC linker will not link -// that library at all and consequently complain about missing entry -// point defined in that library (fatal error LNK1561: entry point -// must be defined). This dummy test keeps gtest_main linked in. -TEST(DummyTest, ValueParameterizedTestsAreNotSupportedOnThisPlatform) {} - -#endif - - -// -// support tests for qa/workunits/filestore/filestore.sh -// -TEST(EXT4StoreTest, _detect_fs) { - if (::getenv("DISK") == NULL || ::getenv("MOUNTPOINT") == NULL) { - cerr << "SKIP because DISK and MOUNTPOINT environment variables are not set. It is meant to run from qa/workunits/filestore/filestore.sh " << std::endl; - return; - } - const string disk(::getenv("DISK")); - EXPECT_LT((unsigned)0, disk.size()); - const string mnt(::getenv("MOUNTPOINT")); - EXPECT_LT((unsigned)0, mnt.size()); - ::umount(mnt.c_str()); - - const string dir("store_test_temp_dir"); - const string journal("store_test_temp_journal"); - - // - // without user_xattr, ext4 fails - // - { - g_ceph_context->_conf->set_val("filestore_xattr_use_omap", "true"); - EXPECT_EQ(::system((string("mount -o loop,nouser_xattr ") + disk + " " + mnt).c_str()), 0); - EXPECT_EQ(::chdir(mnt.c_str()), 0); - EXPECT_EQ(::mkdir(dir.c_str(), 0755), 0); - FileStore store(dir, journal); - EXPECT_EQ(store._detect_fs(), -ENOTSUP); - EXPECT_EQ(::chdir(".."), 0); - EXPECT_EQ(::umount(mnt.c_str()), 0); - } - // - // mounted with user_xattr, ext4 fails if filestore_xattr_use_omap is false - // - { - g_ceph_context->_conf->set_val("filestore_xattr_use_omap", "false"); - EXPECT_EQ(::system((string("mount -o loop,user_xattr ") + disk + " " + mnt).c_str()), 0); - EXPECT_EQ(::chdir(mnt.c_str()), 0); - FileStore store(dir, journal); - EXPECT_EQ(store._detect_fs(), -ENOTSUP); - EXPECT_EQ(::chdir(".."), 0); - EXPECT_EQ(::umount(mnt.c_str()), 0); - } - // - // mounted with user_xattr, ext4 succeeds if filestore_xattr_use_omap is true - // - { - g_ceph_context->_conf->set_val("filestore_xattr_use_omap", "true"); - EXPECT_EQ(::system((string("mount -o loop,user_xattr ") + disk + " " + mnt).c_str()), 0); - EXPECT_EQ(::chdir(mnt.c_str()), 0); - FileStore store(dir, journal); - EXPECT_EQ(store._detect_fs(), 0); - EXPECT_EQ(::chdir(".."), 0); - EXPECT_EQ(::umount(mnt.c_str()), 0); - } -} - - -int main(int argc, char **argv) { - vector args; - argv_to_vec(argc, (const char **)argv, args); - - global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT, CODE_ENVIRONMENT_UTILITY, 0); - common_init_finish(g_ceph_context); - g_ceph_context->_conf->set_val("osd_journal_size", "400"); - g_ceph_context->_conf->set_val("filestore_index_retry_probability", "0.5"); - g_ceph_context->_conf->set_val("filestore_op_thread_timeout", "1000"); - g_ceph_context->_conf->set_val("filestore_op_thread_suicide_timeout", "10000"); - g_ceph_context->_conf->apply_changes(NULL); - - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} - -// Local Variables: -// compile-command: "cd ../.. ; make ceph_test_filestore ; ./ceph_test_filestore --gtest_filter=StoreTest.* --log-to-stderr=true --debug-filestore=20" -// End: diff --git a/src/test/filestore/test_idempotent.cc b/src/test/filestore/test_idempotent.cc deleted file mode 100644 index 4cfb8d11e2a6..000000000000 --- a/src/test/filestore/test_idempotent.cc +++ /dev/null @@ -1,113 +0,0 @@ -// -*- 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) 2004-2006 Sage Weil - * - * This is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License version 2.1, as published by the Free Software - * Foundation. See file COPYING. - * - */ - -#include -#include -#include -#include "os/FileStore.h" -#include "global/global_init.h" -#include "common/ceph_argparse.h" -#include "common/debug.h" -#include "test/common/ObjectContents.h" -#include "FileStoreTracker.h" -#include "os/LevelDBStore.h" -#include "os/KeyValueDB.h" -#include "os/ObjectStore.h" - -void usage(const string &name) { - std::cerr << "Usage: " << name << " [new|continue] store_path store_journal db_path" - << std::endl; -} - -template -typename T::iterator rand_choose(T &cont) { - if (cont.size() == 0) { - return cont.end(); - } - int index = rand() % cont.size(); - typename T::iterator retval = cont.begin(); - - for (; index > 0; --index) ++retval; - return retval; -} - -int main(int argc, char **argv) { - vector args; - argv_to_vec(argc, (const char **)argv, args); - - global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT, CODE_ENVIRONMENT_UTILITY, 0); - common_init_finish(g_ceph_context); - g_ceph_context->_conf->apply_changes(NULL); - - std::cerr << "args: " << args << std::endl; - if (args.size() < 4) { - usage(argv[0]); - return 1; - } - - string store_path(args[1]); - string store_dev(args[2]); - string db_path(args[3]); - - bool start_new = false; - if (string(args[0]) == string("new")) start_new = true; - - LevelDBStore *_db = new LevelDBStore(g_ceph_context, db_path); - assert(!_db->create_and_open(std::cerr)); - boost::scoped_ptr db(_db); - boost::scoped_ptr store(new FileStore(store_path, store_dev)); - - - if (start_new) { - std::cerr << "mkfs" << std::endl; - assert(!store->mkfs()); - ObjectStore::Transaction t; - assert(!store->mount()); - t.create_collection(coll_t("coll")); - store->apply_transaction(t); - } else { - assert(!store->mount()); - } - - FileStoreTracker tracker(store.get(), db.get()); - - set objects; - for (unsigned i = 0; i < 10; ++i) { - stringstream stream; - stream << "Object_" << i; - tracker.verify("coll", stream.str(), true); - objects.insert(stream.str()); - } - - while (1) { - FileStoreTracker::Transaction t; - for (unsigned j = 0; j < 100; ++j) { - int val = rand() % 100; - if (val < 30) { - t.write("coll", *rand_choose(objects)); - } else if (val < 60) { - t.clone("coll", *rand_choose(objects), - *rand_choose(objects)); - } else if (val < 70) { - t.remove("coll", *rand_choose(objects)); - } else { - t.clone_range("coll", *rand_choose(objects), - *rand_choose(objects)); - } - } - tracker.submit_transaction(t); - tracker.verify("coll", *rand_choose(objects)); - } - return 0; -} diff --git a/src/test/filestore/test_idempotent_sequence.cc b/src/test/filestore/test_idempotent_sequence.cc deleted file mode 100644 index 3ef2c79987d8..000000000000 --- a/src/test/filestore/test_idempotent_sequence.cc +++ /dev/null @@ -1,247 +0,0 @@ -// -*- 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 New Dream Network -* -* This is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License version 2.1, as published by the Free Software -* Foundation. See file COPYING. -*/ -#include -#include -#include -#include -#include -#include -#include "common/ceph_argparse.h" -#include "global/global_init.h" -#include "common/debug.h" -#include "os/FileStore.h" - -#include "DeterministicOpSequence.h" -#include "FileStoreDiff.h" - -#include "common/config.h" -#include "include/assert.h" - -#define dout_subsys ceph_subsys_ -#undef dout_prefix -#define dout_prefix *_dout << "test_idempotent_sequence " - -void usage(const char *name, std::string command = "") { - assert(name != NULL); - - std::string more = "cmd "; - std::string diff = "diff "; - std::string get_last_op = "get-last-op "; - std::string run_seq_to = "run-sequence-to "; - - if (!command.empty()) { - if (command == "diff") - more = diff; - else if (command == "get-last-op") - more = get_last_op; - else if (command == "run-sequence-to") - more = run_seq_to; - } - std::cout << "usage: " << name << " " << more << " [options]" << std::endl; - - std::cout << "\n\ -Commands:\n\ - " << diff << "\n\ - " << get_last_op << "\n\ - " << run_seq_to << "\n\ -\n\ -Global Options:\n\ - -c FILE Read configuration from FILE\n\ - --osd-data PATH Set OSD Data path\n\ - --osd-journal PATH Set OSD Journal path\n\ - --osd-journal-size VAL Set Journal size\n\ - --help This message\n\ -\n\ -Test-specific Options:\n\ - --test-seed VAL Seed to run the test\n\ - --test-status-file PATH Path to keep the status file\n\ - --test-num-colls VAL Number of collections to create on init\n\ - --test-num-objs VAL Number of objects to create on init\n\ -" << std::endl; -} - -const char *our_name = NULL; -int seed = 0, num_txs = 100, num_colls = 30, num_objs = 0; -bool is_seed_set = false; -int verify_at = 0; -std::string status_file; - -int run_diff(std::string& a_path, std::string& a_journal, - std::string& b_path, std::string& b_journal) -{ - FileStore *a = new FileStore(a_path, a_journal, "a"); - FileStore *b = new FileStore(b_path, b_journal, "b"); - - int ret = 0; - { - FileStoreDiff fsd(a, b); - if (fsd.diff()) { - dout(0) << "diff found an difference" << dendl; - ret = -1; - } else { - dout(0) << "no diff" << dendl; - } - } - - delete a; - delete b; - return ret; -} - -int run_get_last_op(std::string& filestore_path, std::string& journal_path) -{ - FileStore *store = new FileStore(filestore_path, journal_path); - - int err = store->mount(); - if (err) { - store->umount(); - delete store; - return err; - } - - coll_t txn_coll("meta"); - hobject_t txn_object(sobject_t("txn", CEPH_NOSNAP)); - bufferlist bl; - store->read(txn_coll, txn_object, 0, 100, bl); - int32_t txn = 0; - if (bl.length()) { - bufferlist::iterator p = bl.begin(); - ::decode(txn, p); - } - - store->umount(); - delete store; - - cout << txn << std::endl; - return 0; -} - -int run_sequence_to(int val, std::string& filestore_path, - std::string& journal_path) -{ - num_txs = val; - - if (!is_seed_set) - seed = (int) time(NULL); - - FileStore *store = new FileStore(filestore_path, journal_path); - - int err; - - // mkfs iff directory dne - err = ::mkdir(filestore_path.c_str(), 0755); - if (err) { - cerr << filestore_path << " already exists" << std::endl; - store->umount(); - delete store; - return err; - } - - err = store->mkfs(); - ceph_assert(err == 0); - - err = store->mount(); - ceph_assert(err == 0); - - DeterministicOpSequence op_sequence(store, status_file); - op_sequence.init(num_colls, num_objs); - op_sequence.generate(seed, num_txs); - store->umount(); - return 0; -} - -int run_command(std::string& command, std::vector& args) -{ - if (command.empty()) { - usage(our_name); - exit(0); - } - - /* We'll have a class that will handle the options, the command - * and its arguments. For the time being, and so we can move on, let's - * tolerate this big, ugly code. - */ - if (command == "diff") { - /* expect 4 arguments: (filestore path + journal path)*2 */ - if (args.size() == 4) { - return run_diff(args[0], args[1], args[2], args[3]); - } - } else if (command == "get-last-op") { - /* expect 2 arguments: a filestore path + journal */ - if (args.size() == 2) { - return run_get_last_op(args[0], args[1]); - } - } else if (command == "run-sequence-to") { - /* expect 3 arguments: # of operations and a filestore path + journal. */ - if (args.size() == 3) { - return run_sequence_to(strtoll(args[0].c_str(), NULL, 10), args[1], args[2]); - } - } else { - std::cout << "unknown command " << command << std::endl; - usage(our_name); - exit(1); - } - - usage(our_name, command); - exit(1); -} - -int main(int argc, const char *argv[]) -{ - vector def_args; - vector args; - our_name = argv[0]; - argv_to_vec(argc, argv, args); - - global_init(&def_args, args, - CEPH_ENTITY_TYPE_CLIENT, CODE_ENVIRONMENT_UTILITY, - CINIT_FLAG_NO_DEFAULT_CONFIG_FILE); - common_init_finish(g_ceph_context); - g_ceph_context->_conf->apply_changes(NULL); - - std::string command; - std::vector command_args; - - for (std::vector::iterator i = args.begin(); i != args.end();) { - string val; - - if (ceph_argparse_double_dash(args, i)) { - break; - } else if (ceph_argparse_witharg(args, i, &val, - "--test-seed", (char*) NULL)) { - seed = strtoll(val.c_str(), NULL, 10); - is_seed_set = true; - } else if (ceph_argparse_witharg(args, i, &val, - "--test-num-colls", (char*) NULL)) { - num_colls = strtoll(val.c_str(), NULL, 10); - } else if (ceph_argparse_witharg(args, i, &val, - "--test-num-objs", (char*) NULL)) { - num_objs = strtoll(val.c_str(), NULL, 10); - } else if (ceph_argparse_witharg(args, i, &val, - "--test-status-file", (char*) NULL)) { - status_file = val; - } else if (ceph_argparse_flag(args, i, "--help", (char*) NULL)) { - usage(our_name); - exit(0); - } else { - if (command.empty()) - command = *i++; - else - command_args.push_back(string(*i++)); - } - } - - int ret = run_command(command, command_args); - - return ret; -} diff --git a/src/test/filestore/workload_generator.cc b/src/test/filestore/workload_generator.cc deleted file mode 100644 index aadb4758d96d..000000000000 --- a/src/test/filestore/workload_generator.cc +++ /dev/null @@ -1,571 +0,0 @@ -// -*- 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 New Dream Network - * - * This is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License version 2.1, as published by the Free Software - * Foundation. See file COPYING. - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "os/FileStore.h" -#include "common/ceph_argparse.h" -#include "global/global_init.h" -#include "common/debug.h" -#include -#include -#include "workload_generator.h" -#include "include/assert.h" - -#include "TestFileStoreState.h" - -static const char *our_name = NULL; -void usage(); - -boost::scoped_ptr wrkldgen; - -#define dout_subsys ceph_subsys_ - - -WorkloadGenerator::WorkloadGenerator(vector args) - : TestFileStoreState(NULL), - m_max_in_flight(def_max_in_flight), - m_num_ops(-1), - m_destroy_coll_every_nr_runs(def_destroy_coll_every_nr_runs), - m_num_colls(def_num_colls), - m_write_data_bytes(0), m_write_xattr_obj_bytes(0), - m_write_xattr_coll_bytes(0), m_write_pglog_bytes(0), - m_suppress_write_data(false), m_suppress_write_xattr_obj(false), - m_suppress_write_xattr_coll(false), m_suppress_write_log(false), - m_do_stats(false), - m_stats_finished_txs(0), - m_stats_lock("WorldloadGenerator::m_stats_lock"), - m_stats_show_secs(5), - m_stats_total_written(0), - m_stats_begin() -{ - int err = 0; - - m_nr_runs.set(0); - - init_args(args); - dout(0) << "data = " << g_conf->osd_data << dendl; - dout(0) << "journal = " << g_conf->osd_journal << dendl; - dout(0) << "journal size = " << g_conf->osd_journal_size << dendl; - - err = ::mkdir(g_conf->osd_data.c_str(), 0755); - ceph_assert(err == 0 || (err < 0 && errno == EEXIST)); - ObjectStore *store_ptr = new FileStore(g_conf->osd_data, g_conf->osd_journal); - m_store.reset(store_ptr); - err = m_store->mkfs(); - ceph_assert(err == 0); - err = m_store->mount(); - ceph_assert(err == 0); - - set_max_in_flight(m_max_in_flight); - set_num_objs_per_coll(def_num_obj_per_coll); - - init(m_num_colls, 0); - - dout(0) << "#colls = " << m_num_colls << dendl; - dout(0) << "#objs per coll = " << m_num_objs_per_coll << dendl; - dout(0) << "#txs per destr = " << m_destroy_coll_every_nr_runs << dendl; - -} - -size_t WorkloadGenerator::_parse_size_or_die(std::string& val) -{ - size_t s = 0; - int multiplier = 0; - size_t i = 0; - - if (val.empty()) // this should never happen, but catch it anyway. - goto die; - - - for (i = 0; i < val.length(); i++) { - if (!isdigit(val[i])) { - if (isalpha(val[i])) { - val[i] = tolower(val[i]); - switch (val[i]) { - case 'b': break; - case 'k': multiplier = 10; break; - case 'm': multiplier = 20; break; - case 'g': multiplier = 30; break; - default: - goto die; - } - val[i] = '\0'; - break; - } else { - goto die; - } - } - } - - s = strtoll(val.c_str(), NULL, 10) * (1 << multiplier); - return s; - -die: - usage(); - exit(1); -} - -void WorkloadGenerator::_suppress_ops_or_die(std::string& val) -{ - for (size_t i = 0; i < val.length(); i++) { - switch (val[i]) { - case 'c': m_suppress_write_xattr_coll = true; break; - case 'o': m_suppress_write_xattr_obj = true; break; - case 'l': m_suppress_write_log = true; break; - case 'd': m_suppress_write_data = true; break; - default: - usage(); - exit(1); - } - } -} - -void WorkloadGenerator::init_args(vector args) -{ - for (std::vector::iterator i = args.begin(); i != args.end();) { - string val; - - if (ceph_argparse_double_dash(args, i)) { - break; - } else if (ceph_argparse_witharg(args, i, &val, - "--test-num-colls", (char*) NULL)) { - m_num_colls = strtoll(val.c_str(), NULL, 10); - } else if (ceph_argparse_witharg(args, i, &val, - "--test-objs-per-coll", (char*) NULL)) { - m_num_objs_per_coll = strtoll(val.c_str(), NULL, 10); - } else if (ceph_argparse_witharg(args, i, &val, - "--test-destroy-coll-per-N-trans", (char*) NULL)) { - m_destroy_coll_every_nr_runs = strtoll(val.c_str(), NULL, 10); - } else if (ceph_argparse_witharg(args, i, &val, - "--test-num-ops", (char*) NULL)) { - m_num_ops = strtoll(val.c_str(), NULL, 10); - } else if (ceph_argparse_witharg(args, i, &val, - "--test-max-in-flight", (char*) NULL)) { - m_max_in_flight = strtoll(val.c_str(), NULL, 10); - } else if (ceph_argparse_witharg(args, i, &val, - "--test-write-data-size", (char*) NULL)) { - m_write_data_bytes = _parse_size_or_die(val); - } else if (ceph_argparse_witharg(args, i, &val, - "--test-write-xattr-obj-size", (char*) NULL)) { - m_write_xattr_obj_bytes = _parse_size_or_die(val); - } else if (ceph_argparse_witharg(args, i, &val, - "--test-write-xattr-coll-size", (char*) NULL)) { - m_write_xattr_coll_bytes = _parse_size_or_die(val); - } else if (ceph_argparse_witharg(args, i, &val, - "--test-write-pglog-size", (char*) NULL)) { - m_write_pglog_bytes = _parse_size_or_die(val); - } else if (ceph_argparse_witharg(args, i, &val, - "--test-suppress-ops", (char*) NULL)) { - _suppress_ops_or_die(val); - } else if (ceph_argparse_witharg(args, i, &val, - "--test-show-stats-period", (char*) NULL)) { - m_stats_show_secs = strtoll(val.c_str(), NULL, 10); - } else if (ceph_argparse_flag(args, i, "--test-show-stats", (char*) NULL)) { - m_do_stats = true; - } else if (ceph_argparse_flag(args, i, "--help", (char*) NULL)) { - usage(); - exit(0); - } - } -} - -int WorkloadGenerator::get_uniform_random_value(int min, int max) -{ - boost::uniform_int<> value(min, max); - return value(m_rng); -} - -TestFileStoreState::coll_entry_t *WorkloadGenerator::get_rnd_coll_entry(bool erase = false) -{ - int index = get_uniform_random_value(0, m_collections_ids.size()-1); - coll_entry_t *entry = get_coll_at(index, erase); - return entry; -} - -hobject_t *WorkloadGenerator::get_rnd_obj(coll_entry_t *entry) -{ - assert(entry != NULL); - - bool create = - (get_uniform_random_value(0,100) < 50 || !entry->m_objects.size()); - - if (create && ((int) entry->m_objects.size() < m_num_objs_per_coll)) { - return (entry->touch_obj(entry->m_next_object_id++)); - } - - int idx = get_uniform_random_value(0, entry->m_objects.size()-1); - return entry->get_obj_at(idx); -} - -/** - * We'll generate a random amount of bytes, ranging from a single byte up to - * a couple of MB. - */ -size_t WorkloadGenerator::get_random_byte_amount(size_t min, size_t max) -{ - size_t diff = max - min; - return (size_t) (min + (rand() % diff)); -} - -void WorkloadGenerator::get_filled_byte_array(bufferlist& bl, size_t size) -{ - static const char alphanum[] = "0123456789" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz"; - - bufferptr bp(size); - if (false) { - for (unsigned int i = 0; i < size - 1; i++) { - bp[i] = alphanum[rand() % sizeof(alphanum)]; - } - bp[size - 1] = '\0'; - } else { - bp.zero(); - } - bl.append(bp); -} - -void WorkloadGenerator::do_write_object(ObjectStore::Transaction *t, - coll_t coll, hobject_t obj, - C_StatState *stat) -{ - if (m_suppress_write_data) { - dout(5) << __func__ << " suppressed" << dendl; - return; - } - - size_t size = m_write_data_bytes; - if (!size) - size = get_random_byte_amount(min_write_bytes, max_write_bytes); - - bufferlist bl; - get_filled_byte_array(bl, size); - - dout(2) << __func__ << " " << coll << "/" << obj - << " size " << bl.length() << dendl; - - if (m_do_stats && (stat != NULL)) - stat->written_data += bl.length(); - - t->write(coll, obj, 0, bl.length(), bl); -} - -void WorkloadGenerator::do_setattr_object(ObjectStore::Transaction *t, - coll_t coll, hobject_t obj, - C_StatState *stat) -{ - if (m_suppress_write_xattr_obj) { - dout(5) << __func__ << " suppressed" << dendl; - return; - } - - size_t size = m_write_xattr_obj_bytes; - if (!size) - size = get_random_byte_amount(min_xattr_obj_bytes, max_xattr_obj_bytes); - - bufferlist bl; - get_filled_byte_array(bl, size); - - dout(2) << __func__ << " " << coll << "/" << obj << " size " << size << dendl; - - if (m_do_stats && (stat != NULL)) - stat->written_data += bl.length(); - - t->setattr(coll, obj, "objxattr", bl); -} - -void WorkloadGenerator::do_setattr_collection(ObjectStore::Transaction *t, - coll_t coll, C_StatState *stat) -{ - if (m_suppress_write_xattr_coll) { - dout(5) << __func__ << " suppressed" << dendl; - return; - } - - size_t size = m_write_xattr_coll_bytes; - if (!size) - size = get_random_byte_amount(min_xattr_coll_bytes, max_xattr_coll_bytes); - - bufferlist bl; - get_filled_byte_array(bl, size); - dout(2) << __func__ << " coll " << coll << " size " << size << dendl; - - if (m_do_stats && (stat != NULL)) - stat->written_data += bl.length(); - - t->collection_setattr(coll, "collxattr", bl); -} - -void WorkloadGenerator::do_append_log(ObjectStore::Transaction *t, - coll_entry_t *entry, C_StatState *stat) -{ - if (m_suppress_write_log) { - dout(5) << __func__ << " suppressed" << dendl; - return; - } - - size_t size = (m_write_pglog_bytes ? m_write_pglog_bytes : log_append_bytes); - - bufferlist bl; - get_filled_byte_array(bl, size); - hobject_t log_obj = entry->m_meta_obj; - - dout(2) << __func__ << " coll " << entry->m_coll << " " - << META_COLL << " /" << log_obj << " (" << bl.length() << ")" << dendl; - - if (m_do_stats && (stat != NULL)) - stat->written_data += bl.length(); - - uint64_t s = pg_log_size[entry->m_coll]; - t->write(META_COLL, log_obj, s, bl.length(), bl); - pg_log_size[entry->m_coll] += bl.length(); -} - -void WorkloadGenerator::do_destroy_collection(ObjectStore::Transaction *t, - coll_entry_t *entry, - C_StatState *stat) -{ - m_nr_runs.set(0); - entry->m_osr.flush(); - vector ls; - m_store->collection_list(entry->m_coll, ls); - dout(2) << __func__ << " coll " << entry->m_coll - << " (" << ls.size() << " objects)" << dendl; - - for (vector::iterator it = ls.begin(); it < ls.end(); ++it) { - t->remove(entry->m_coll, *it); - } - - t->remove_collection(entry->m_coll); - t->remove(META_COLL, entry->m_meta_obj); -} - -TestFileStoreState::coll_entry_t -*WorkloadGenerator::do_create_collection(ObjectStore::Transaction *t, - C_StatState *stat) -{ - coll_entry_t *entry = coll_create(m_next_coll_nr++); - if (!entry) { - dout(0) << __func__ << " failed to create coll id " - << m_next_coll_nr << dendl; - return NULL; - } - m_collections.insert(make_pair(entry->m_id, entry)); - - dout(2) << __func__ << " id " << entry->m_id << " coll " << entry->m_coll << dendl; - t->create_collection(entry->m_coll); - dout(2) << __func__ << " meta " << META_COLL << "/" << entry->m_meta_obj << dendl; - t->touch(META_COLL, entry->m_meta_obj); - return entry; -} - -void WorkloadGenerator::do_stats() -{ - utime_t now = ceph_clock_now(NULL); - m_stats_lock.Lock(); - - utime_t duration = (now - m_stats_begin); - - // when cast to double, a utime_t behaves properly - double throughput = (m_stats_total_written / ((double) duration)); - double tx_throughput (m_stats_finished_txs / ((double) duration)); - - dout(0) << __func__ - << " written: " << m_stats_total_written - << " duration: " << duration << " sec" - << " bandwidth: " << prettybyte_t(throughput) << "/s" - << " iops: " << tx_throughput << "/s" - << dendl; - - m_stats_lock.Unlock(); -} - -void WorkloadGenerator::run() -{ - bool create_coll = false; - int ops_run = 0; - - utime_t stats_interval(m_stats_show_secs, 0); - utime_t now = ceph_clock_now(NULL); - utime_t stats_time = now; - m_stats_begin = now; - - do { - C_StatState *stat_state = NULL; - - if (m_num_ops && (ops_run == m_num_ops)) - break; - - if (!create_coll && !m_collections.size()) { - dout(0) << "We ran out of collections!" << dendl; - break; - } - - dout(5) << __func__ - << " m_finished_lock is-locked: " << m_finished_lock.is_locked() - << " in-flight: " << m_in_flight.read() - << dendl; - - wait_for_ready(); - - ObjectStore::Transaction *t = new ObjectStore::Transaction; - Context *c; - bool destroy_collection = false; - TestFileStoreState::coll_entry_t *entry = NULL; - - - if (m_do_stats) { - utime_t now = ceph_clock_now(NULL); - utime_t elapsed = now - stats_time; - if (elapsed >= stats_interval) { - do_stats(); - stats_time = now; - } - stat_state = new C_StatState(this, now); - } - - if (create_coll) { - create_coll = false; - - entry = do_create_collection(t, stat_state); - if (!entry) { - dout(0) << __func__ << " something went terribly wrong creating coll" << dendl; - break; - } - - c = new C_OnReadable(this, t); - goto queue_tx; - } - - destroy_collection = should_destroy_collection(); - entry = get_rnd_coll_entry(destroy_collection); - assert(entry != NULL); - - if (destroy_collection) { - do_destroy_collection(t, entry, stat_state); - c = new C_OnDestroyed(this, t, entry); - if (!m_num_ops) - create_coll = true; - } else { - hobject_t *obj = get_rnd_obj(entry); - - do_write_object(t, entry->m_coll, *obj, stat_state); - do_setattr_object(t, entry->m_coll, *obj, stat_state); - do_setattr_collection(t, entry->m_coll, stat_state); - do_append_log(t, entry, stat_state); - - c = new C_OnReadable(this, t); - } - -queue_tx: - - if (m_do_stats) { - Context *tmp = c; - c = new C_StatWrapper(stat_state, tmp); - } - - m_store->queue_transaction(&(entry->m_osr), t, c); - - inc_in_flight(); - - ops_run ++; - - } while (true); - - dout(2) << __func__ << " waiting for " - << m_in_flight.read() << " in-flight transactions" << dendl; - - wait_for_done(); - - do_stats(); - - dout(0) << __func__ << " finishing" << dendl; -} - -void usage() -{ - cout << "usage: " << our_name << "[options]" << std::endl; - - cout << "\ -\n\ -Global Options:\n\ - -c FILE Read configuration from FILE\n\ - --osd-data PATH Set OSD Data path\n\ - --osd-journal PATH Set OSD Journal path\n\ - --osd-journal-size VAL Set Journal size\n\ - --help This message\n\ -\n\ -Test-specific Options:\n\ - --test-num-colls VAL Set the number of collections\n\ - --test-num-objs-per-coll VAL Set the number of objects per collection\n\ - --test-destroy-coll-per-N-trans VAL Set how many transactions to run before\n\ - destroying a collection.\n\ - --test-num-ops VAL Run a certain number of operations\n\ - (a VAL of 0 runs the test forever)\n\ - --test-max-in-flight VAL Maximum number of in-flight transactions\n\ - (default: 50)\n\ - --test-suppress-ops OPS Suppress ops specified in OPS\n\ - --test-write-data-size SIZE Specify SIZE for all data writes\n\ - --test-write-xattr-obj-size SIZE Specify SIZE for all xattrs on objects\n\ - --test-write-xattr-coll-size SIZE Specify SIZE for all xattrs on colls\n\ - --test-write-pglog-size SIZE Specify SIZE for all pglog writes\n\ - --test-show-stats Show stats as we go\n\ - --test-show-stats-period SECS Show stats every SECS (default: 5)\n\ -\n\ - SIZE is a numeric value that can be assumed as being bytes, or may be any\n\ - other unit if specified: B or b, K or k, M or m, G or g.\n\ - e.g., 1G = 1024M = 1048576k = 1073741824\n\ -\n\ - OPS can be one or more of the following options:\n\ - c writes on collection's xattrs\n\ - o writes on object's xattr\n\ - l writes on pglog\n\ - d data writes on objects\n\ -\n\ -" << std::endl; -} - -int main(int argc, const char *argv[]) -{ - vector def_args; - vector args; - - our_name = argv[0]; - - def_args.push_back("--osd-journal-size"); - def_args.push_back("400"); -// def_args.push_back("--osd-data"); -// def_args.push_back("workload_gen_dir"); -// def_args.push_back("--osd-journal"); -// def_args.push_back("workload_gen_dir/journal"); - argv_to_vec(argc, argv, args); - - global_init(&def_args, args, - CEPH_ENTITY_TYPE_CLIENT, CODE_ENVIRONMENT_UTILITY, - CINIT_FLAG_NO_DEFAULT_CONFIG_FILE); - common_init_finish(g_ceph_context); - g_ceph_context->_conf->apply_changes(NULL); - - WorkloadGenerator *wrkldgen_ptr = new WorkloadGenerator(args); - wrkldgen.reset(wrkldgen_ptr); - wrkldgen->run(); - return 0; -} diff --git a/src/test/filestore/workload_generator.h b/src/test/filestore/workload_generator.h deleted file mode 100644 index 80e95dae6ec8..000000000000 --- a/src/test/filestore/workload_generator.h +++ /dev/null @@ -1,184 +0,0 @@ -// -*- 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 New Dream Network - * - * This is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License version 2.1, as published by the Free Software - * Foundation. See file COPYING. - */ -#ifndef WORKLOAD_GENERATOR_H_ -#define WORKLOAD_GENERATOR_H_ - -#include "os/FileStore.h" -#include -#include -#include -#include -#include - -#include "TestFileStoreState.h" - -typedef boost::mt11213b rngen_t; - -class WorkloadGenerator : public TestFileStoreState { - public: - static const int def_max_in_flight = 50; - - static const int def_destroy_coll_every_nr_runs = 100; - static const int def_num_obj_per_coll = 6000; - static const int def_num_colls = 30; - - static const size_t min_write_bytes = 1; - static const size_t max_write_mb = 5; - static const size_t max_write_bytes = (max_write_mb * 1024 * 1024); - - static const size_t min_xattr_obj_bytes = 2; - static const size_t max_xattr_obj_bytes = 300; - static const size_t min_xattr_coll_bytes = 4; - static const size_t max_xattr_coll_bytes = 600; - - static const size_t log_append_bytes = 1024; - - struct C_StatState { - utime_t start; - unsigned int written_data; - WorkloadGenerator *wrkldgen; - - C_StatState(WorkloadGenerator *state, utime_t s) - : start(s), written_data(0), wrkldgen(state) { } - }; - - - protected: - int m_max_in_flight; - int m_num_ops; - int m_destroy_coll_every_nr_runs; - atomic_t m_nr_runs; - - int m_num_colls; - - rngen_t m_rng; - - map pg_log_size; - - size_t m_write_data_bytes; - size_t m_write_xattr_obj_bytes; - size_t m_write_xattr_coll_bytes; - size_t m_write_pglog_bytes; - - bool m_suppress_write_data; - bool m_suppress_write_xattr_obj; - bool m_suppress_write_xattr_coll; - bool m_suppress_write_log; - - bool m_do_stats; - - int m_stats_finished_txs; - Mutex m_stats_lock; - int m_stats_show_secs; - - size_t m_stats_total_written; - utime_t m_stats_begin; - - private: - - void _suppress_ops_or_die(std::string& val); - size_t _parse_size_or_die(std::string& val); - void init_args(vector args); - - int get_uniform_random_value(int min, int max); - coll_entry_t *get_rnd_coll_entry(bool erase); - hobject_t *get_rnd_obj(coll_entry_t *entry); - int get_random_collection_nr(); - int get_random_object_nr(int coll_nr); - - size_t get_random_byte_amount(size_t min, size_t max); - void get_filled_byte_array(bufferlist& bl, size_t size); - - void do_write_object(ObjectStore::Transaction *t, - coll_t coll, hobject_t obj, C_StatState *stat); - void do_setattr_object(ObjectStore::Transaction *t, - coll_t coll, hobject_t obj, C_StatState *stat); - void do_setattr_collection(ObjectStore::Transaction *t, coll_t coll, - C_StatState *stat); - void do_append_log(ObjectStore::Transaction *t, coll_entry_t *entry, - C_StatState *stat); - - bool should_destroy_collection() { - return ((m_destroy_coll_every_nr_runs > 0) && - ((int)m_nr_runs.read() >= m_destroy_coll_every_nr_runs)); - } - void do_destroy_collection(ObjectStore::Transaction *t, coll_entry_t *entry, - C_StatState *stat); - coll_entry_t *do_create_collection(ObjectStore::Transaction *t, - C_StatState *stat); - - void do_stats(); - -public: - WorkloadGenerator(vector args); - ~WorkloadGenerator() { - m_store->umount(); - } - - class C_OnReadable: public TestFileStoreState::C_OnFinished { - WorkloadGenerator *wrkldgen_state; - - public: - C_OnReadable(WorkloadGenerator *state, - ObjectStore::Transaction *t) - :TestFileStoreState::C_OnFinished(state, t), wrkldgen_state(state) { } - - void finish(int r) - { - TestFileStoreState::C_OnFinished::finish(r); - wrkldgen_state->m_nr_runs.inc(); - } - }; - - class C_OnDestroyed: public C_OnReadable { - coll_entry_t *m_entry; - - public: - C_OnDestroyed(WorkloadGenerator *state, - ObjectStore::Transaction *t, coll_entry_t *entry) : - C_OnReadable(state, t), m_entry(entry) {} - - void finish(int r) { - C_OnReadable::finish(r); - delete m_entry; - } - }; - - class C_StatWrapper : public Context { - C_StatState *stat_state; - Context *ctx; - - public: - C_StatWrapper(C_StatState *state, Context *context) - : stat_state(state), ctx(context) { } - - void finish(int r) { - ctx->complete(r); - - stat_state->wrkldgen->m_stats_lock.Lock(); - - stat_state->wrkldgen->m_stats_total_written += stat_state->written_data; - stat_state->wrkldgen->m_stats_finished_txs ++; - stat_state->wrkldgen->m_stats_lock.Unlock(); - } - }; - - void run(void); -}; - -bool operator<(const WorkloadGenerator::coll_entry_t& l, - const WorkloadGenerator::coll_entry_t& r) { - return (l.m_id < r.m_id); -} - -#endif /* WORKLOAD_GENERATOR_H_ */ diff --git a/src/test/objectstore/DeterministicOpSequence.cc b/src/test/objectstore/DeterministicOpSequence.cc new file mode 100644 index 000000000000..09c9f5e7ff11 --- /dev/null +++ b/src/test/objectstore/DeterministicOpSequence.cc @@ -0,0 +1,526 @@ +// -*- 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 New Dream Network +* +* This is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License version 2.1, as published by the Free Software +* Foundation. See file COPYING. +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include "os/ObjectStore.h" +#include "common/ceph_argparse.h" +#include "global/global_init.h" +#include "common/debug.h" +#include +#include + +#include "DeterministicOpSequence.h" + +#include "common/config.h" +#include "include/assert.h" + +#define dout_subsys ceph_subsys_filestore +#undef dout_prefix +#define dout_prefix *_dout << "deterministic_seq " + +DeterministicOpSequence::DeterministicOpSequence(ObjectStore *store, + std::string status) + : TestObjectStoreState(store), + txn(0), + m_osr("OSR") +{ + txn_coll = coll_t("meta"); + txn_object = hobject_t(sobject_t("txn", CEPH_NOSNAP)); + + if (!status.empty()) + m_status.open(status.c_str()); +} + +DeterministicOpSequence::~DeterministicOpSequence() +{ + // TODO Auto-generated destructor stub +} + +bool DeterministicOpSequence::run_one_op(int op, rngen_t& gen) +{ + bool ok = false; + switch (op) { + case DSOP_TOUCH: + ok = do_touch(gen); + break; + case DSOP_WRITE: + ok = do_write(gen); + break; + case DSOP_CLONE: + ok = do_clone(gen); + break; + case DSOP_CLONE_RANGE: + ok = do_clone_range(gen); + break; + case DSOP_OBJ_REMOVE: + ok = do_remove(gen); + break; + case DSOP_COLL_ADD: + ok = do_coll_add(gen); + break; + case DSOP_COLL_RENAME: + //do_coll_rename(gen); + break; + case DSOP_SET_ATTRS: + ok = do_set_attrs(gen); + break; + default: + assert(0 == "bad op"); + } + return ok; +} + +void DeterministicOpSequence::generate(int seed, int num_txs) +{ + std::ostringstream ss; + ss << "generate run " << num_txs << " --seed " << seed; + + if (m_status.is_open()) { + m_status << ss.str() << std::endl; + m_status.flush(); + } + + dout(0) << ss.str() << dendl; + + rngen_t gen(seed); + boost::uniform_int<> op_rng(DSOP_FIRST, DSOP_LAST); + + for (txn = 1; txn <= num_txs; ) { + int op = op_rng(gen); + _print_status(txn, op); + dout(0) << "generate seq " << txn << " op " << op << dendl; + if (run_one_op(op, gen)) + txn++; + } +} + +void DeterministicOpSequence::_print_status(int seq, int op) +{ + if (!m_status.is_open()) + return; + m_status << seq << " " << op << std::endl; + m_status.flush(); +} + +int DeterministicOpSequence::_gen_coll_id(rngen_t& gen) +{ + boost::uniform_int<> coll_rng(0, m_collections_ids.size()-1); + return coll_rng(gen); +} + +int DeterministicOpSequence::_gen_obj_id(rngen_t& gen) +{ + boost::uniform_int<> obj_rng(0, m_num_objects - 1); + return obj_rng(gen); +} + +void DeterministicOpSequence::note_txn(ObjectStore::Transaction *t) +{ + bufferlist bl; + ::encode(txn, bl); + t->truncate(txn_coll, txn_object, 0); + t->write(txn_coll, txn_object, 0, bl.length(), bl); + dout(10) << __func__ << " " << txn << dendl; +} + +bool DeterministicOpSequence::do_touch(rngen_t& gen) +{ + int coll_id = _gen_coll_id(gen); + int obj_id = _gen_obj_id(gen); + + coll_entry_t *entry = get_coll_at(coll_id); + ceph_assert(entry != NULL); + + // Don't care about other collections if already exists + if (!entry->check_for_obj(obj_id)) { + bool other_found = false; + map::iterator it = m_collections.begin(); + for (; it != m_collections.end(); ++it) { + if (it->second->check_for_obj(obj_id)) { + ceph_assert(it->first != coll_id); + other_found = true; + } + } + if (other_found) { + dout(0) << "do_touch new object in collection and exists in another" << dendl; + return false; + } + } + hobject_t *obj = entry->touch_obj(obj_id); + + dout(0) << "do_touch " << entry->m_coll.to_str() << "/" << obj->oid.name << dendl; + + _do_touch(entry->m_coll, *obj); + return true; +} + +bool DeterministicOpSequence::do_remove(rngen_t& gen) +{ + int coll_id = _gen_coll_id(gen); + + coll_entry_t *entry = get_coll_at(coll_id); + ceph_assert(entry != NULL); + + if (entry->m_objects.size() == 0) { + dout(0) << "do_remove no objects in collection" << dendl; + return false; + } + int obj_id = entry->get_random_obj_id(gen); + hobject_t *obj = entry->touch_obj(obj_id); + ceph_assert(obj); + + dout(0) << "do_remove " << entry->m_coll.to_str() << "/" << obj->oid.name << dendl; + + _do_remove(entry->m_coll, *obj); + hobject_t *rmobj = entry->remove_obj(obj_id); + ceph_assert(rmobj); + delete rmobj; + return true; +} + +static void _gen_random(rngen_t& gen, + size_t size, bufferlist& bl) { + + static const char alphanum[] = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + + boost::uniform_int<> char_rng(0, sizeof(alphanum)); + bufferptr bp(size); + for (unsigned int i = 0; i < size - 1; i++) { + bp[i] = alphanum[char_rng(gen)]; + } + bp[size - 1] = '\0'; + bl.append(bp); +} + +static void gen_attrs(rngen_t &gen, + map *out) { + boost::uniform_int<> num_rng(10, 30); + boost::uniform_int<> key_size_rng(5, 10); + boost::uniform_int<> val_size_rng(100, 1000); + size_t num_attrs = static_cast(num_rng(gen)); + for (size_t i = 0; i < num_attrs; ++i) { + size_t key_size = static_cast(num_rng(gen)); + size_t val_size = static_cast(num_rng(gen)); + bufferlist keybl; + _gen_random(gen, key_size, keybl); + string key(keybl.c_str(), keybl.length()); + _gen_random(gen, val_size, (*out)[key]); + } +} + +bool DeterministicOpSequence::do_set_attrs(rngen_t& gen) +{ + int coll_id = _gen_coll_id(gen); + + coll_entry_t *entry = get_coll_at(coll_id); + ceph_assert(entry != NULL); + + if (entry->m_objects.size() == 0) { + dout(0) << "do_set_attrs no objects in collection" << dendl; + return false; + } + int obj_id = entry->get_random_obj_id(gen); + hobject_t *obj = entry->touch_obj(obj_id); + ceph_assert(obj); + + map out; + gen_attrs(gen, &out); + + dout(0) << "do_set_attrs " << out.size() << " entries" << dendl; + _do_set_attrs(entry->m_coll, *obj, out); + return true; +} + +bool DeterministicOpSequence::do_write(rngen_t& gen) +{ + int coll_id = _gen_coll_id(gen); + + coll_entry_t *entry = get_coll_at(coll_id); + ceph_assert(entry != NULL); + + if (entry->m_objects.size() == 0) { + dout(0) << "do_write no objects in collection" << dendl; + return false; + } + int obj_id = entry->get_random_obj_id(gen); + hobject_t *obj = entry->touch_obj(obj_id); + ceph_assert(obj); + + boost::uniform_int<> size_rng(100, (2 << 19)); + size_t size = (size_t) size_rng(gen); + bufferlist bl; + _gen_random(gen, size, bl); + + dout(0) << "do_write " << entry->m_coll.to_str() << "/" << obj->oid.name + << " 0~" << size << dendl; + + _do_write(entry->m_coll, *obj, 0, bl.length(), bl); + return true; +} + +bool DeterministicOpSequence::_prepare_clone(rngen_t& gen, + coll_t& coll_ret, hobject_t& orig_obj_ret, hobject_t& new_obj_ret) +{ + int coll_id = _gen_coll_id(gen); + + coll_entry_t *entry = get_coll_at(coll_id); + ceph_assert(entry != NULL); + + if (entry->m_objects.size() >= 2) { + dout(0) << "_prepare_clone coll " << entry->m_coll.to_str() + << " doesn't have 2 or more objects" << dendl; + return false; + } + + int orig_obj_id = entry->get_random_obj_id(gen); + hobject_t *orig_obj = entry->touch_obj(orig_obj_id); + ceph_assert(orig_obj); + + int id; + do { + id = entry->get_random_obj_id(gen); + } while (id == orig_obj_id); + hobject_t *new_obj = entry->touch_obj(id); + ceph_assert(new_obj); + + coll_ret = entry->m_coll; + orig_obj_ret = *orig_obj; + new_obj_ret = *new_obj; + + return true; +} + +bool DeterministicOpSequence::do_clone(rngen_t& gen) +{ + coll_t coll; + hobject_t orig_obj, new_obj; + if (!_prepare_clone(gen, coll, orig_obj, new_obj)) { + return false; + } + + dout(0) << "do_clone " << coll.to_str() << "/" << orig_obj.oid.name + << " => " << coll.to_str() << "/" << new_obj.oid.name << dendl; + + _do_clone(coll, orig_obj, new_obj); + return true; +} + +bool DeterministicOpSequence::do_clone_range(rngen_t& gen) +{ + coll_t coll; + hobject_t orig_obj, new_obj; + if (!_prepare_clone(gen, coll, orig_obj, new_obj)) { + return false; + } + + /* Whenever we have to make a clone_range() operation, just write to the + * object first, so we know we have something to clone in the said range. + * This may not be the best solution ever, but currently we're not keeping + * track of the written-to objects, and until we know for sure we really + * need to, let's just focus on the task at hand. + */ + + boost::uniform_int<> write_size_rng(100, (2 << 19)); + size_t size = (size_t) write_size_rng(gen); + bufferlist bl; + _gen_random(gen, size, bl); + + boost::uniform_int<> clone_len(1, bl.length()); + size = (size_t) clone_len(gen); + + dout(0) << "do_clone_range " << coll.to_str() << "/" << orig_obj.oid.name + << " (0~" << size << ")" + << " => " << coll.to_str() << "/" << new_obj.oid.name + << " (0)" << dendl; + _do_write_and_clone_range(coll, orig_obj, new_obj, 0, size, 0, bl); + return true; +} + +bool DeterministicOpSequence::_prepare_colls(rngen_t& gen, + coll_entry_t* &orig_coll, coll_entry_t* &new_coll) +{ + ceph_assert(m_collections_ids.size() > 1); + int orig_coll_id = _gen_coll_id(gen); + int new_coll_id; + do { + new_coll_id = _gen_coll_id(gen); + } while (new_coll_id == orig_coll_id); + + dout(0) << "_prepare_colls from coll id " << orig_coll_id + << " to coll id " << new_coll_id << dendl; + + orig_coll = get_coll_at(orig_coll_id); + ceph_assert(orig_coll != NULL); + new_coll = get_coll_at(new_coll_id); + ceph_assert(new_coll != NULL); + + if (!orig_coll->m_objects.size()) { + dout(0) << "_prepare_colls coll " << orig_coll->m_coll.to_str() + << " has no objects to use" << dendl; + return false; + } + + return true; +} + + +bool DeterministicOpSequence::do_coll_rename(rngen_t& gen) +{ + int coll_pos = _gen_coll_id(gen); + dout(0) << "do_coll_rename coll pos #" << coll_pos << dendl; + + coll_entry_t *coll_entry = get_coll_at(coll_pos); + if (!coll_entry) { + dout(0) << "do_coll_rename no collection at pos #" << coll_pos << dendl; + return false; + } + + coll_t orig_coll = coll_entry->m_coll; + char buf[100]; + memset(buf, 0, 100); + snprintf(buf, 100, "0.%d_head", m_next_coll_nr++); + coll_t new_coll(buf); + coll_entry->m_coll = new_coll; + + dout(0) << "do_coll_rename " << orig_coll.to_str() + << " => " << new_coll.to_str() << dendl; + _do_coll_rename(orig_coll, new_coll); + return true; +} + +bool DeterministicOpSequence::do_coll_add(rngen_t& gen) +{ + coll_entry_t *orig_coll = NULL, *new_coll = NULL; + if (!_prepare_colls(gen, orig_coll, new_coll)) + return false; + + ceph_assert(orig_coll && new_coll); + + boost::uniform_int<> obj_rng(0, orig_coll->m_objects.size()-1); + int obj_pos = obj_rng(gen); + int obj_key = -1; + hobject_t *obj = orig_coll->get_obj_at(obj_pos, &obj_key); + if (!obj) { + dout(0) << "do_coll_add coll " << orig_coll->m_coll.to_str() + << " has no object as pos #" << obj_pos << " (key " << obj_key << ")" + << dendl; + return false; + } + if (new_coll->check_for_obj(obj_key)) { + dout(0) << "do_coll_add coll " << orig_coll->m_coll.to_str() + << " already has object as pos #" << obj_pos << " (key " << obj_key << ")" + << dendl; + return false; + } + dout(0) << "do_coll_add " << orig_coll->m_coll.to_str() << "/" << obj->oid.name + << " => " << new_coll->m_coll.to_str() << "/" << obj->oid.name << dendl; + new_coll->touch_obj(obj_key); + + _do_coll_add(orig_coll->m_coll, new_coll->m_coll, *obj); + return true; +} + +void DeterministicOpSequence::_do_touch(coll_t coll, hobject_t& obj) +{ + ObjectStore::Transaction t; + note_txn(&t); + t.touch(coll, obj); + m_store->apply_transaction(t); +} + +void DeterministicOpSequence::_do_remove(coll_t coll, hobject_t& obj) +{ + ObjectStore::Transaction t; + note_txn(&t); + t.remove(coll, obj); + m_store->apply_transaction(t); +} + +void DeterministicOpSequence::_do_set_attrs(coll_t coll, + hobject_t &obj, + const map &attrs) +{ + ObjectStore::Transaction t; + note_txn(&t); + t.omap_setkeys(coll, obj, attrs); + m_store->apply_transaction(t); +} + +void DeterministicOpSequence::_do_write(coll_t coll, hobject_t& obj, + uint64_t off, uint64_t len, const bufferlist& data) +{ + ObjectStore::Transaction t; + note_txn(&t); + t.write(coll, obj, off, len, data); + m_store->apply_transaction(t); +} + +void DeterministicOpSequence::_do_clone(coll_t coll, hobject_t& orig_obj, + hobject_t& new_obj) +{ + ObjectStore::Transaction t; + note_txn(&t); + t.clone(coll, orig_obj, new_obj); + m_store->apply_transaction(t); +} + +void DeterministicOpSequence::_do_clone_range(coll_t coll, + hobject_t& orig_obj, hobject_t& new_obj, uint64_t srcoff, + uint64_t srclen, uint64_t dstoff) +{ + ObjectStore::Transaction t; + note_txn(&t); + t.clone_range(coll, orig_obj, new_obj, srcoff, srclen, dstoff); + m_store->apply_transaction(t); +} + +void DeterministicOpSequence::_do_write_and_clone_range(coll_t coll, + hobject_t& orig_obj, + hobject_t& new_obj, + uint64_t srcoff, + uint64_t srclen, + uint64_t dstoff, + bufferlist& bl) +{ + ObjectStore::Transaction t; + note_txn(&t); + t.write(coll, orig_obj, srcoff, bl.length(), bl); + t.clone_range(coll, orig_obj, new_obj, srcoff, srclen, dstoff); + m_store->apply_transaction(t); +} + +void DeterministicOpSequence::_do_coll_add(coll_t orig_coll, coll_t new_coll, + hobject_t& obj) +{ + ObjectStore::Transaction t; + note_txn(&t); + t.remove(new_coll, obj); + t.collection_add(new_coll, orig_coll, obj); + m_store->apply_transaction(t); +} + +void DeterministicOpSequence::_do_coll_rename(coll_t orig_coll, coll_t new_coll) +{ + ObjectStore::Transaction t; + note_txn(&t); + t.collection_rename(orig_coll, new_coll); + m_store->apply_transaction(t); +} diff --git a/src/test/objectstore/DeterministicOpSequence.h b/src/test/objectstore/DeterministicOpSequence.h new file mode 100644 index 000000000000..1980c98c3c59 --- /dev/null +++ b/src/test/objectstore/DeterministicOpSequence.h @@ -0,0 +1,98 @@ +// -*- 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 New Dream Network +* +* This is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License version 2.1, as published by the Free Software +* Foundation. See file COPYING. +*/ +#ifndef FILESTORE_DTRMNSTC_SEQ_OPS_H_ +#define FILESTORE_DTRMNSTC_SEQ_OPS_H_ + +#include +#include +#include +#include "os/ObjectStore.h" +#include +#include +#include + +#include "TestObjectStoreState.h" + +typedef boost::mt11213b rngen_t; + +class DeterministicOpSequence : public TestObjectStoreState { + public: + DeterministicOpSequence(ObjectStore *store, std::string status = std::string()); + virtual ~DeterministicOpSequence(); + + virtual void generate(int seed, int num_txs); + + protected: + enum { + DSOP_TOUCH = 0, + DSOP_WRITE = 1, + DSOP_CLONE = 2, + DSOP_CLONE_RANGE = 3, + DSOP_OBJ_REMOVE = 4, + DSOP_COLL_RENAME = 5, + DSOP_COLL_ADD = 6, + DSOP_SET_ATTRS = 7, + + DSOP_FIRST = DSOP_TOUCH, + DSOP_LAST = DSOP_SET_ATTRS, + }; + + int32_t txn; + + coll_t txn_coll; + hobject_t txn_object; + + ObjectStore::Sequencer m_osr; + std::ofstream m_status; + + bool run_one_op(int op, rngen_t& gen); + + void note_txn(ObjectStore::Transaction *t); + bool do_touch(rngen_t& gen); + bool do_remove(rngen_t& gen); + bool do_write(rngen_t& gen); + bool do_clone(rngen_t& gen); + bool do_clone_range(rngen_t& gen); + bool do_coll_rename(rngen_t& gen); + bool do_coll_add(rngen_t& gen); + bool do_set_attrs(rngen_t& gen); + + virtual void _do_touch(coll_t coll, hobject_t& obj); + virtual void _do_remove(coll_t coll, hobject_t& obj); + virtual void _do_write(coll_t coll, hobject_t& obj, uint64_t off, + uint64_t len, const bufferlist& data); + virtual void _do_set_attrs(coll_t coll, + hobject_t &obj, + const map &attrs); + virtual void _do_clone(coll_t coll, hobject_t& orig_obj, hobject_t& new_obj); + virtual void _do_clone_range(coll_t coll, hobject_t& orig_obj, + hobject_t& new_obj, uint64_t srcoff, uint64_t srclen, uint64_t dstoff); + virtual void _do_write_and_clone_range(coll_t coll, hobject_t& orig_obj, + hobject_t& new_obj, uint64_t srcoff, uint64_t srclen, + uint64_t dstoff, bufferlist& bl); + virtual void _do_coll_add(coll_t orig_coll, coll_t new_coll, hobject_t& obj); + virtual void _do_coll_rename(coll_t orig_coll, coll_t new_coll); + + int _gen_coll_id(rngen_t& gen); + int _gen_obj_id(rngen_t& gen); + void _print_status(int seq, int op); + + private: + bool _prepare_clone(rngen_t& gen, coll_t& coll_ret, + hobject_t& orig_obj_ret, hobject_t& new_obj_ret); + bool _prepare_colls(rngen_t& gen, + coll_entry_t* &orig_coll, coll_entry_t* &new_coll); +}; + + +#endif /* FILESTORE_DTRMNSTC_SEQ_OPS_H_ */ diff --git a/src/test/objectstore/FileStoreDiff.cc b/src/test/objectstore/FileStoreDiff.cc new file mode 100644 index 000000000000..40c0b32d30c1 --- /dev/null +++ b/src/test/objectstore/FileStoreDiff.cc @@ -0,0 +1,319 @@ +// -*- 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 New Dream Network +* +* This is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License version 2.1, as published by the Free Software +* Foundation. See file COPYING. +*/ +#include +#include +#include +#include +#include "common/debug.h" +#include "os/FileStore.h" +#include "common/config.h" + +#include "FileStoreDiff.h" + +#define dout_subsys ceph_subsys_filestore +#undef dout_prefix +#define dout_prefix *_dout << "filestore_diff " + +FileStoreDiff::FileStoreDiff(FileStore *a, FileStore *b) + : a_store(a), b_store(b) +{ + int err; + err = a_store->mount(); + ceph_assert(err == 0); + + err = b_store->mount(); + ceph_assert(err == 0); +} + +FileStoreDiff::~FileStoreDiff() +{ + a_store->umount(); + b_store->umount(); +} + + +bool FileStoreDiff::diff_attrs(std::map& b, + std::map& a) +{ + bool ret = false; + std::map::iterator b_it = b.begin(); + std::map::iterator a_it = a.begin(); + for (; b_it != b.end(); ++b_it, ++a_it) { + if (b_it->first != a_it->first) { + dout(0) << "diff_attrs name mismatch (verify: " << b_it->first + << ", store: " << a_it->first << ")" << dendl; + ret = true; + continue; + } + + if (!b_it->second.cmp(a_it->second)) { + dout(0) << "diff_attrs contents mismatch on attr " << b_it->first << dendl; + ret = true; + continue; + } + } + return ret; +} + +static bool diff_omap(std::map& b, + std::map& a) +{ + bool ret = false; + std::map::iterator b_it = b.begin(); + std::map::iterator a_it = a.begin(); + for (; b_it != b.end(); ++b_it, ++a_it) { + if (b_it->first != a_it->first) { + dout(0) << "diff_attrs name mismatch (verify: " << b_it->first + << ", store: " << a_it->first << ")" << dendl; + ret = true; + continue; + } + + if (!(b_it->second == a_it->second)) { + dout(0) << "diff_attrs contents mismatch on attr " << b_it->first << dendl; + ret = true; + continue; + } + } + return ret; +} + +bool FileStoreDiff::diff_objects_stat(struct stat& a, struct stat& b) +{ + bool ret = false; + + if (a.st_uid != b.st_uid) { + dout(0) << "diff_objects_stat uid mismatch (A: " + << a.st_uid << " != B: " << b.st_uid << ")" << dendl; + ret = true; + } + + if (a.st_gid != b.st_gid) { + dout(0) << "diff_objects_stat gid mismatch (A: " + << a.st_gid << " != B: " << b.st_gid << ")" << dendl; + ret = true; + } + + if (a.st_mode != b.st_mode) { + dout(0) << "diff_objects_stat mode mismatch (A: " + << a.st_mode << " != B: " << b.st_mode << ")" << dendl; + ret = true; + } + + if (a.st_nlink != b.st_nlink) { + dout(0) << "diff_objects_stat nlink mismatch (A: " + << a.st_nlink << " != B: " << b.st_nlink << ")" << dendl; + ret = true; + } + + if (a.st_size != b.st_size) { + dout(0) << "diff_objects_stat size mismatch (A: " + << a.st_size << " != B: " << b.st_size << ")" << dendl; + ret = true; + } + return ret; +} + +bool FileStoreDiff::diff_objects(FileStore *a_store, FileStore *b_store, coll_t coll) +{ + dout(2) << __func__ << " coll " << coll << dendl; + + bool ret = false; + + int err; + std::vector b_objects, a_objects; + err = b_store->collection_list(coll, b_objects); + if (err < 0) { + dout(0) << "diff_objects list on verify coll " << coll.to_str() + << " returns " << err << dendl; + return true; + } + err = a_store->collection_list(coll, a_objects); + if (err < 0) { + dout(0) << "diff_objects list on store coll " << coll.to_str() + << " returns " << err << dendl; + return true; + } + + if (b_objects.size() != a_objects.size()) { + dout(0) << "diff_objects num objs mismatch (A: " << a_objects.size() + << ", B: " << b_objects.size() << ")" << dendl; + ret = true; + } + + std::vector::iterator b_it = b_objects.begin(); + std::vector::iterator a_it = b_objects.begin(); + for (; b_it != b_objects.end(); ++b_it, ++a_it) { + ghobject_t b_obj = *b_it, a_obj = *a_it; + if (b_obj.hobj.oid.name != a_obj.hobj.oid.name) { + dout(0) << "diff_objects name mismatch on A object " + << coll << "/" << a_obj << " and B object " + << coll << "/" << b_obj << dendl; + ret = true; + continue; + } + + struct stat b_stat, a_stat; + err = b_store->stat(coll, b_obj, &b_stat); + if (err < 0) { + dout(0) << "diff_objects error stating B object " + << coll.to_str() << "/" << b_obj.hobj.oid.name << dendl; + ret = true; + } + err = a_store->stat(coll, a_obj, &a_stat); + if (err < 0) { + dout(0) << "diff_objects error stating A object " + << coll << "/" << a_obj << dendl; + ret = true; + } + + if (diff_objects_stat(a_stat, b_stat)) { + dout(0) << "diff_objects stat mismatch on " + << coll << "/" << b_obj << dendl; + ret = true; + } + + bufferlist a_obj_bl, b_obj_bl; + b_store->read(coll, b_obj, 0, b_stat.st_size, b_obj_bl); + a_store->read(coll, a_obj, 0, a_stat.st_size, a_obj_bl); + + if (!a_obj_bl.contents_equal(b_obj_bl)) { + dout(0) << "diff_objects content mismatch on " + << coll << "/" << b_obj << dendl; + ret = true; + } + + std::map a_obj_attrs_map, b_obj_attrs_map; + err = a_store->getattrs(coll, a_obj, a_obj_attrs_map); + if (err < 0) { + dout(0) << "diff_objects getattrs on A object " << coll << "/" << a_obj + << " returns " << err << dendl; + ret = true; + } + err = b_store->getattrs(coll, b_obj, b_obj_attrs_map); + if (err < 0) { + dout(0) << "diff_objects getattrs on B object " << coll << "/" << b_obj + << "returns " << err << dendl; + ret = true; + } + + if (diff_attrs(b_obj_attrs_map, a_obj_attrs_map)) { + dout(0) << "diff_objects attrs mismatch on A object " + << coll << "/" << a_obj << " and B object " + << coll << "/" << b_obj << dendl; + ret = true; + } + + std::map a_obj_omap, b_obj_omap; + std::set a_omap_keys, b_omap_keys; + err = a_store->omap_get_keys(coll, a_obj, &a_omap_keys); + if (err < 0) { + dout(0) << "diff_objects getomap on A object " << coll << "/" << a_obj + << " returns " << err << dendl; + ret = true; + } + err = a_store->omap_get_values(coll, a_obj, a_omap_keys, &a_obj_omap); + if (err < 0) { + dout(0) << "diff_objects getomap on A object " << coll << "/" << a_obj + << " returns " << err << dendl; + ret = true; + } + err = b_store->omap_get_keys(coll, b_obj, &b_omap_keys); + if (err < 0) { + dout(0) << "diff_objects getomap on A object " << coll << "/" << b_obj + << " returns " << err << dendl; + ret = true; + } + err = b_store->omap_get_values(coll, b_obj, b_omap_keys, &b_obj_omap); + if (err < 0) { + dout(0) << "diff_objects getomap on A object " << coll << "/" << b_obj + << " returns " << err << dendl; + ret = true; + } + if (diff_omap(a_obj_omap, b_obj_omap)) { + dout(0) << "diff_objects omap mismatch on A object " + << coll << "/" << a_obj << " and B object " + << coll << "/" << b_obj << dendl; + ret = true; + } + } + + return ret; +} + +bool FileStoreDiff::diff_coll_attrs(FileStore *a_store, FileStore *b_store, coll_t coll) +{ + bool ret = false; + + int err; + std::map b_coll_attrs, a_coll_attrs; + err = b_store->collection_getattrs(coll, b_coll_attrs); + if (err < 0) { + dout(0) << "diff_attrs getattrs on verify coll " << coll.to_str() + << "returns " << err << dendl; + ret = true; + } + err = a_store->collection_getattrs(coll, a_coll_attrs); + if (err < 0) { + dout(0) << "diff_attrs getattrs on A coll " << coll.to_str() + << "returns " << err << dendl; + ret = true; + } + + if (b_coll_attrs.size() != a_coll_attrs.size()) { + dout(0) << "diff_attrs size mismatch (A: " << a_coll_attrs.size() + << ", B: " << a_coll_attrs.size() << ")" << dendl; + ret = true; + } + + return diff_attrs(b_coll_attrs, a_coll_attrs) || ret; +} + +bool FileStoreDiff::diff() +{ + bool ret = false; + + std::vector a_coll_list, b_coll_list; + a_store->list_collections(a_coll_list); + b_store->list_collections(b_coll_list); + + std::vector::iterator it = b_coll_list.begin(); + for (; it != b_coll_list.end(); ++it) { + coll_t b_coll = *it; + if (!a_store->collection_exists(b_coll)) { + dout(0) << "diff B coll " << b_coll.to_str() << " DNE on A" << dendl; + ret = true; + continue; + } + for (std::vector::iterator j = a_coll_list.begin(); + j != a_coll_list.end(); ++j) { + if (*j == *it) { + a_coll_list.erase(j); + break; + } + } + + if (diff_coll_attrs(a_store, b_store, b_coll)) + ret = true; + + if (diff_objects(a_store, b_store, b_coll)) + ret = true; + } + for (std::vector::iterator it = a_coll_list.begin(); + it != a_coll_list.end(); ++it) { + dout(0) << "diff A coll " << *it << " DNE on B" << dendl; + ret = true; + } + + return ret; +} diff --git a/src/test/objectstore/FileStoreDiff.h b/src/test/objectstore/FileStoreDiff.h new file mode 100644 index 000000000000..cacd3ce84747 --- /dev/null +++ b/src/test/objectstore/FileStoreDiff.h @@ -0,0 +1,43 @@ +// -*- 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 New Dream Network +* +* This is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License version 2.1, as published by the Free Software +* Foundation. See file COPYING. +*/ +#ifndef FILESTORE_DIFF_H_ +#define FILESTORE_DIFF_H_ + +#include +#include +#include +#include +#include "common/debug.h" +#include "os/FileStore.h" +#include "common/config.h" + +class FileStoreDiff { + + private: + FileStore *a_store; + FileStore *b_store; + + bool diff_coll_attrs(FileStore *a_store, FileStore *b_store, coll_t coll); + bool diff_objects(FileStore *a_store, FileStore *b_store, coll_t coll); + bool diff_objects_stat(struct stat& a, struct stat& b); + bool diff_attrs(std::map& b, + std::map& a); + +public: + FileStoreDiff(FileStore *a, FileStore *b); + virtual ~FileStoreDiff(); + + bool diff(); +}; + +#endif /* FILESTOREDIFF_H_ */ diff --git a/src/test/objectstore/FileStoreTracker.cc b/src/test/objectstore/FileStoreTracker.cc new file mode 100644 index 000000000000..afdc31bad23e --- /dev/null +++ b/src/test/objectstore/FileStoreTracker.cc @@ -0,0 +1,452 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +#include "FileStoreTracker.h" +#include +#include +#include +#include "include/Context.h" +#include "common/Mutex.h" + +class OnApplied : public Context { + FileStoreTracker *tracker; + list, uint64_t> > in_flight; + ObjectStore::Transaction *t; +public: + OnApplied(FileStoreTracker *tracker, + list, uint64_t> > in_flight, + ObjectStore::Transaction *t) + : tracker(tracker), in_flight(in_flight), t(t) {} + + void finish(int r) { + for (list, uint64_t> >::iterator i = + in_flight.begin(); + i != in_flight.end(); + ++i) { + tracker->applied(i->first, i->second); + } + delete t; + } +}; + +class OnCommitted : public Context { + FileStoreTracker *tracker; + list, uint64_t> > in_flight; +public: + OnCommitted(FileStoreTracker *tracker, + list, uint64_t> > in_flight) + : tracker(tracker), in_flight(in_flight) {} + + void finish(int r) { + for (list, uint64_t> >::iterator i = + in_flight.begin(); + i != in_flight.end(); + ++i) { + tracker->committed(i->first, i->second); + } + } +}; + +int FileStoreTracker::init() +{ + set to_get; + to_get.insert("STATUS"); + map got; + db->get("STATUS", to_get, &got); + restart_seq = 0; + if (!got.empty()) { + bufferlist::iterator bp = got.begin()->second.begin(); + ::decode(restart_seq, bp); + } + ++restart_seq; + KeyValueDB::Transaction t = db->get_transaction(); + got.clear(); + ::encode(restart_seq, got["STATUS"]); + t->set("STATUS", got); + db->submit_transaction(t); + return 0; +} + +void FileStoreTracker::submit_transaction(Transaction &t) +{ + list, uint64_t> > in_flight; + OutTransaction out; + out.t = new ObjectStore::Transaction; + out.in_flight = &in_flight; + for (list::iterator i = t.ops.begin(); + i != t.ops.end(); + ++i) { + (**i)(this, &out); + } + store->queue_transaction( + 0, out.t, + new OnApplied(this, in_flight, out.t), + new OnCommitted(this, in_flight)); +} + +void FileStoreTracker::write(const pair &obj, + OutTransaction *out) +{ + Mutex::Locker l(lock); + std::cerr << "Writing " << obj << std::endl; + ObjectContents contents = get_current_content(obj); + + uint64_t offset = rand() % (SIZE/2); + uint64_t len = rand() % (SIZE/2); + if (!len) len = 10; + contents.write(rand(), offset, len); + + bufferlist to_write; + ObjectContents::Iterator iter = contents.get_iterator(); + iter.seek_to(offset); + for (uint64_t i = offset; + i < offset + len; + ++i, ++iter) { + assert(iter.valid()); + to_write.append(*iter); + } + out->t->write(coll_t(obj.first), + hobject_t(sobject_t(obj.second, CEPH_NOSNAP)), + offset, + len, + to_write); + out->in_flight->push_back(make_pair(obj, set_content(obj, contents))); +} + +void FileStoreTracker::remove(const pair &obj, + OutTransaction *out) +{ + std::cerr << "Deleting " << obj << std::endl; + Mutex::Locker l(lock); + ObjectContents old_contents = get_current_content(obj); + if (!old_contents.exists()) + return; + out->t->remove(coll_t(obj.first), + hobject_t(sobject_t(obj.second, CEPH_NOSNAP))); + ObjectContents contents; + out->in_flight->push_back(make_pair(obj, set_content(obj, contents))); +} + +void FileStoreTracker::clone_range(const pair &from, + const pair &to, + OutTransaction *out) { + Mutex::Locker l(lock); + std::cerr << "CloningRange " << from << " to " << to << std::endl; + assert(from.first == to.first); + ObjectContents from_contents = get_current_content(from); + ObjectContents to_contents = get_current_content(to); + if (!from_contents.exists()) { + return; + } + if (from.second == to.second) { + return; + } + + uint64_t new_size = from_contents.size(); + interval_set interval_to_clone; + uint64_t offset = rand() % (new_size/2); + uint64_t len = rand() % (new_size/2); + if (!len) len = 10; + interval_to_clone.insert(offset, len); + to_contents.clone_range(from_contents, interval_to_clone); + out->t->clone_range(coll_t(from.first), + hobject_t(sobject_t(from.second, CEPH_NOSNAP)), + hobject_t(sobject_t(to.second, CEPH_NOSNAP)), + offset, + len, + offset); + out->in_flight->push_back(make_pair(to, set_content(to, to_contents))); +} + +void FileStoreTracker::clone(const pair &from, + const pair &to, + OutTransaction *out) { + Mutex::Locker l(lock); + std::cerr << "Cloning " << from << " to " << to << std::endl; + assert(from.first == to.first); + if (from.second == to.second) { + return; + } + ObjectContents from_contents = get_current_content(from); + ObjectContents to_contents = get_current_content(to); + if (!from_contents.exists()) { + return; + } + + if (to_contents.exists()) + out->t->remove(coll_t(to.first), + hobject_t(sobject_t(to.second, CEPH_NOSNAP))); + out->t->clone(coll_t(from.first), + hobject_t(sobject_t(from.second, CEPH_NOSNAP)), + hobject_t(sobject_t(to.second, CEPH_NOSNAP))); + out->in_flight->push_back(make_pair(to, set_content(to, from_contents))); +} + + +string obj_to_prefix(const pair &obj) { + string sep; + sep.push_back('^'); + return obj.first + sep + obj.second + "_CONTENTS_"; +} + +string obj_to_meta_prefix(const pair &obj) { + string sep; + sep.push_back('^'); + return obj.first + sep + obj.second; +} + +string seq_to_key(uint64_t seq) { + char buf[50]; + snprintf(buf, sizeof(buf), "%*llu", 20, (unsigned long long int)seq); + return string(buf); +} + +struct ObjStatus { + uint64_t last_applied; + uint64_t last_committed; + uint64_t restart_seq; + ObjStatus() : last_applied(0), last_committed(0), restart_seq(0) {} + + uint64_t get_last_applied(uint64_t seq) const { + if (seq > restart_seq) + return last_committed; + else + return last_applied; + } + void set_last_applied(uint64_t _last_applied, uint64_t seq) { + last_applied = _last_applied; + restart_seq = seq; + } + uint64_t trim_to() const { + return last_applied < last_committed ? + last_applied : last_committed; + } +}; +void encode(const ObjStatus &obj, bufferlist &bl) { + ::encode(obj.last_applied, bl); + ::encode(obj.last_committed, bl); + ::encode(obj.restart_seq, bl); +} +void decode(ObjStatus &obj, bufferlist::iterator &bl) { + ::decode(obj.last_applied, bl); + ::decode(obj.last_committed, bl); + ::decode(obj.restart_seq, bl); +} + + +ObjStatus get_obj_status(const pair &obj, + KeyValueDB *db) +{ + set to_get; + to_get.insert("META"); + map got; + db->get(obj_to_meta_prefix(obj), to_get, &got); + ObjStatus retval; + if (!got.empty()) { + bufferlist::iterator bp = got.begin()->second.begin(); + ::decode(retval, bp); + } + return retval; +} + +void set_obj_status(const pair &obj, + const ObjStatus &status, + KeyValueDB::Transaction t) +{ + map to_set; + ::encode(status, to_set["META"]); + t->set(obj_to_meta_prefix(obj), to_set); +} + +void _clean_forward(const pair &obj, + uint64_t last_valid, + KeyValueDB *db) +{ + KeyValueDB::Transaction t = db->get_transaction(); + KeyValueDB::Iterator i = db->get_iterator(obj_to_prefix(obj)); + set to_remove; + i->upper_bound(seq_to_key(last_valid)); + for (; i->valid(); i->next()) { + to_remove.insert(i->key()); + } + t->rmkeys(obj_to_prefix(obj), to_remove); + db->submit_transaction(t); +} + + +void FileStoreTracker::verify(const string &coll, const string &obj, + bool on_start) { + Mutex::Locker l(lock); + std::cerr << "Verifying " << make_pair(coll, obj) << std::endl; + + pair valid_reads = get_valid_reads(make_pair(coll, obj)); + std::cerr << "valid_reads is " << valid_reads << std::endl; + bufferlist contents; + int r = store->read(coll_t(coll), + hobject_t(sobject_t(obj, CEPH_NOSNAP)), + 0, + 2*SIZE, + contents); + std::cerr << "exists: " << r << std::endl; + + + for (uint64_t i = valid_reads.first; + i < valid_reads.second; + ++i) { + ObjectContents old_contents = get_content(make_pair(coll, obj), i); + + std::cerr << "old_contents exists " << old_contents.exists() << std::endl; + if (!old_contents.exists() && (r == -ENOENT)) + return; + + if (old_contents.exists() && (r == -ENOENT)) + continue; + + if (!old_contents.exists() && (r != -ENOENT)) + continue; + + if (contents.length() != old_contents.size()) { + std::cerr << "old_contents.size() is " + << old_contents.size() << std::endl; + continue; + } + + bufferlist::iterator bp = contents.begin(); + ObjectContents::Iterator iter = old_contents.get_iterator(); + iter.seek_to_first(); + bool matches = true; + uint64_t pos = 0; + for (; !bp.end() && iter.valid(); + ++iter, ++bp, ++pos) { + if (*iter != *bp) { + std::cerr << "does not match at pos " << pos << std::endl; + matches = false; + break; + } + } + if (matches) { + if (on_start) + _clean_forward(make_pair(coll, obj), i, db); + return; + } + } + std::cerr << "Verifying " << make_pair(coll, obj) << " failed " << std::endl; + assert(0); +} + +ObjectContents FileStoreTracker::get_current_content( + const pair &obj) +{ + KeyValueDB::Iterator iter = db->get_iterator( + obj_to_prefix(obj)); + iter->seek_to_last(); + if (iter->valid()) { + bufferlist bl = iter->value(); + bufferlist::iterator bp = bl.begin(); + pair val; + ::decode(val, bp); + assert(seq_to_key(val.first) == iter->key()); + bp = val.second.begin(); + return ObjectContents(bp); + } + return ObjectContents(); +} + +ObjectContents FileStoreTracker::get_content( + const pair &obj, uint64_t version) +{ + set to_get; + map got; + to_get.insert(seq_to_key(version)); + db->get(obj_to_prefix(obj), to_get, &got); + if (got.empty()) + return ObjectContents(); + pair val; + bufferlist::iterator bp = got.begin()->second.begin(); + ::decode(val, bp); + bp = val.second.begin(); + assert(val.first == version); + return ObjectContents(bp); +} + +pair FileStoreTracker::get_valid_reads( + const pair &obj) +{ + pair bounds = make_pair(0,1); + KeyValueDB::Iterator iter = db->get_iterator( + obj_to_prefix(obj)); + iter->seek_to_last(); + if (iter->valid()) { + pair val; + bufferlist bl = iter->value(); + bufferlist::iterator bp = bl.begin(); + ::decode(val, bp); + bounds.second = val.first + 1; + } + + ObjStatus obj_status = get_obj_status(obj, db); + bounds.first = obj_status.get_last_applied(restart_seq); + return bounds; +} + +void clear_obsolete(const pair &obj, + const ObjStatus &status, + KeyValueDB *db, + KeyValueDB::Transaction t) +{ + KeyValueDB::Iterator iter = db->get_iterator(obj_to_prefix(obj)); + set to_remove; + iter->seek_to_first(); + for (; iter->valid() && iter->key() < seq_to_key(status.trim_to()); + iter->next()) + to_remove.insert(iter->key()); + t->rmkeys(obj_to_prefix(obj), to_remove); +} + +void FileStoreTracker::committed(const pair &obj, + uint64_t seq) { + Mutex::Locker l(lock); + ObjStatus status = get_obj_status(obj, db); + assert(status.last_committed < seq); + status.last_committed = seq; + KeyValueDB::Transaction t = db->get_transaction(); + clear_obsolete(obj, status, db, t); + set_obj_status(obj, status, t); + db->submit_transaction(t); +} + +void FileStoreTracker::applied(const pair &obj, + uint64_t seq) { + Mutex::Locker l(lock); + std::cerr << "Applied " << obj << " version " << seq << std::endl; + ObjStatus status = get_obj_status(obj, db); + assert(status.last_applied < seq); + status.set_last_applied(seq, restart_seq); + KeyValueDB::Transaction t = db->get_transaction(); + clear_obsolete(obj, status, db, t); + set_obj_status(obj, status, t); + db->submit_transaction(t); +} + + +uint64_t FileStoreTracker::set_content(const pair &obj, + ObjectContents &content) { + KeyValueDB::Transaction t = db->get_transaction(); + KeyValueDB::Iterator iter = db->get_iterator( + obj_to_prefix(obj)); + iter->seek_to_last(); + uint64_t most_recent = 0; + if (iter->valid()) { + pair val; + bufferlist bl = iter->value(); + bufferlist::iterator bp = bl.begin(); + ::decode(val, bp); + most_recent = val.first; + } + bufferlist buf_content; + content.encode(buf_content); + map to_set; + ::encode(make_pair(most_recent + 1, buf_content), + to_set[seq_to_key(most_recent + 1)]); + t->set(obj_to_prefix(obj), to_set); + db->submit_transaction(t); + return most_recent + 1; +} diff --git a/src/test/objectstore/FileStoreTracker.h b/src/test/objectstore/FileStoreTracker.h new file mode 100644 index 000000000000..d70e54a11231 --- /dev/null +++ b/src/test/objectstore/FileStoreTracker.h @@ -0,0 +1,138 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- + +#ifndef FILESTORE_TRACKER_H +#define FILESTORE_TRACKER_H +#include "test/common/ObjectContents.h" +#include "os/FileStore.h" +#include "os/KeyValueDB.h" +#include +#include +#include +#include "common/Mutex.h" + +class FileStoreTracker { + const static uint64_t SIZE = 4 * 1024; + ObjectStore *store; + KeyValueDB *db; + Mutex lock; + uint64_t restart_seq; + + struct OutTransaction { + list, uint64_t> > *in_flight; + ObjectStore::Transaction *t; + }; +public: + FileStoreTracker(ObjectStore *store, KeyValueDB *db) + : store(store), db(db), + lock("Tracker Lock"), restart_seq(0) {} + + class Transaction { + class Op { + public: + virtual void operator()(FileStoreTracker *harness, + OutTransaction *out) = 0; + virtual ~Op() {}; + }; + list ops; + class Write : public Op { + public: + string coll; + string oid; + Write(const string &coll, + const string &oid) + : coll(coll), oid(oid) {} + void operator()(FileStoreTracker *harness, + OutTransaction *out) { + harness->write(make_pair(coll, oid), out); + } + }; + class CloneRange : public Op { + public: + string coll; + string from; + string to; + CloneRange(const string &coll, + const string &from, + const string &to) + : coll(coll), from(from), to(to) {} + void operator()(FileStoreTracker *harness, + OutTransaction *out) { + harness->clone_range(make_pair(coll, from), make_pair(coll, to), + out); + } + }; + class Clone : public Op { + public: + string coll; + string from; + string to; + Clone(const string &coll, + const string &from, + const string &to) + : coll(coll), from(from), to(to) {} + void operator()(FileStoreTracker *harness, + OutTransaction *out) { + harness->clone(make_pair(coll, from), make_pair(coll, to), + out); + } + }; + class Remove: public Op { + public: + string coll; + string obj; + Remove(const string &coll, + const string &obj) + : coll(coll), obj(obj) {} + void operator()(FileStoreTracker *harness, + OutTransaction *out) { + harness->remove(make_pair(coll, obj), + out); + } + }; + public: + void write(const string &coll, const string &oid) { + ops.push_back(new Write(coll, oid)); + } + void clone_range(const string &coll, const string &from, + const string &to) { + ops.push_back(new CloneRange(coll, from, to)); + } + void clone(const string &coll, const string &from, + const string &to) { + ops.push_back(new Clone(coll, from, to)); + } + void remove(const string &coll, const string &oid) { + ops.push_back(new Remove(coll, oid)); + } + friend class FileStoreTracker; + }; + + int init(); + void submit_transaction(Transaction &t); + void verify(const string &coll, + const string &from, + bool on_start = false); + +private: + ObjectContents get_current_content(const pair &obj); + pair get_valid_reads(const pair &obj); + ObjectContents get_content(const pair &obj, uint64_t version); + + void committed(const pair &obj, uint64_t seq); + void applied(const pair &obj, uint64_t seq); + uint64_t set_content(const pair &obj, ObjectContents &content); + + // ObjectContents Operations + void write(const pair &obj, OutTransaction *out); + void remove(const pair &obj, OutTransaction *out); + void clone_range(const pair &from, + const pair &to, + OutTransaction *out); + void clone(const pair &from, + const pair &to, + OutTransaction *out); + friend class OnApplied; + friend class OnCommitted; +}; + +#endif diff --git a/src/test/objectstore/TestFileStoreState.cc b/src/test/objectstore/TestFileStoreState.cc new file mode 100644 index 000000000000..34e25fb2e2c5 --- /dev/null +++ b/src/test/objectstore/TestFileStoreState.cc @@ -0,0 +1,296 @@ +// -*- 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 New Dream Network +* +* This is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License version 2.1, as published by the Free Software +* Foundation. See file COPYING. +*/ +#include +#include +#include +#include +#include +#include +#include "os/ObjectStore.h" +#include "common/ceph_argparse.h" +#include "global/global_init.h" +#include "common/debug.h" +#include +#include +#include "TestObjectStoreState.h" +#include "include/assert.h" + +#define dout_subsys ceph_subsys_filestore +#undef dout_prefix +#define dout_prefix *_dout << "ceph_test_filestore_state " + +const coll_t TestObjectStoreState::META_COLL("meta"); +const coll_t TestObjectStoreState::TEMP_COLL("temp"); + +void TestObjectStoreState::init(int colls, int objs) +{ + dout(5) << "init " << colls << " colls " << objs << " objs" << dendl; + + ObjectStore::Transaction *t; + t = new ObjectStore::Transaction; + + t->create_collection(META_COLL); + t->create_collection(TEMP_COLL); + m_store->apply_transaction(*t); + + wait_for_ready(); + + int baseid = 0; + for (int i = 0; i < colls; i++) { + int coll_id = i; + coll_entry_t *entry = coll_create(coll_id); + dout(5) << "init create collection " << entry->m_coll.to_str() + << " meta " << entry->m_meta_obj.oid.name << dendl; + + t = new ObjectStore::Transaction; + t->create_collection(entry->m_coll); + t->touch(META_COLL, entry->m_meta_obj); + + for (int i = 0; i < objs; i++) { + hobject_t *obj = entry->touch_obj(i + baseid); + t->touch(entry->m_coll, *obj); + ceph_assert(i + baseid == m_num_objects); + m_num_objects++; + } + baseid += objs; + + m_store->queue_transaction(&(entry->m_osr), t, + new C_OnFinished(this, t)); + inc_in_flight(); + + m_collections.insert(make_pair(coll_id, entry)); + m_collections_ids.push_back(coll_id); + m_next_coll_nr++; + } + dout(5) << "init has " << m_in_flight.read() << "in-flight transactions" << dendl; + wait_for_done(); + dout(5) << "init finished" << dendl; +} + +TestObjectStoreState::coll_entry_t *TestObjectStoreState::coll_create(int id) +{ + char buf[100]; + char meta_buf[100]; + memset(buf, 0, 100); + memset(meta_buf, 0, 100); + snprintf(buf, 100, "0.%d_head", id); + snprintf(meta_buf, 100, "pglog_0.%d_head", id); + return (new coll_entry_t(id, buf, meta_buf)); +} + +TestObjectStoreState::coll_entry_t* +TestObjectStoreState::get_coll(int key, bool erase) +{ + dout(5) << "get_coll id " << key << dendl; + + coll_entry_t *entry = NULL; + map::iterator it = m_collections.find(key); + if (it != m_collections.end()) { + entry = it->second; + if (erase) { + m_collections.erase(it); + vector::iterator cid_it = m_collections_ids.begin()+(entry->m_id); + dout(20) << __func__ << " removing key " << key << " coll_id " << entry->m_id + << " iterator's entry id " << (*cid_it) << dendl; + m_collections_ids.erase(cid_it); + } + } + + dout(5) << "get_coll id " << key; + if (!entry) + *_dout << " non-existent"; + else + *_dout << " name " << entry->m_coll.to_str(); + *_dout << dendl; + return entry; +} + +TestObjectStoreState::coll_entry_t* +TestObjectStoreState::get_coll_at(int pos, bool erase) +{ + dout(5) << "get_coll_at pos " << pos << dendl; + + if (m_collections.empty()) + return NULL; + + assert((size_t) pos < m_collections_ids.size()); + + int coll_id = m_collections_ids[pos]; + coll_entry_t *entry = m_collections[coll_id]; + + if (entry == NULL) { + dout(5) << "get_coll_at pos " << pos << " non-existent" << dendl; + return NULL; + } + + if (erase) { + m_collections.erase(coll_id); + vector::iterator it = m_collections_ids.begin()+(pos); + dout(20) << __func__ << " removing pos " << pos << " coll_id " << coll_id + << " iterator's entry id " << (*it) << dendl; + m_collections_ids.erase(it); + } + + dout(5) << "get_coll_at pos " << pos << ": " + << entry->m_coll << "(removed: " << erase << ")" << dendl; + + return entry; +} + +TestObjectStoreState::coll_entry_t::~coll_entry_t() +{ + if (m_objects.size() > 0) { + map::iterator it = m_objects.begin(); + for (; it != m_objects.end(); ++it) { + hobject_t *obj = it->second; + if (obj) { + delete obj; + } + } + m_objects.clear(); + } +} + +bool TestObjectStoreState::coll_entry_t::check_for_obj(int id) +{ + if (m_objects.count(id)) + return true; + return false; +} + +hobject_t *TestObjectStoreState::coll_entry_t::touch_obj(int id) +{ + map::iterator it = m_objects.find(id); + if (it != m_objects.end()) { + dout(5) << "touch_obj coll id " << m_id + << " name " << it->second->oid.name << dendl; + return it->second; + } + + char buf[100]; + memset(buf, 0, 100); + snprintf(buf, 100, "obj%d", id); + + hobject_t *obj = new hobject_t(sobject_t(object_t(buf), CEPH_NOSNAP)); + m_objects.insert(make_pair(id, obj)); + + dout(5) << "touch_obj coll id " << m_id << " name " << buf << dendl; + return obj; +} + +hobject_t *TestObjectStoreState::coll_entry_t::get_obj(int id) +{ + return get_obj(id, false); +} + +/** + * remove_obj - Removes object without freeing it. + * @param id Object's id in the map. + * @return The object or NULL in case of error. + */ +hobject_t *TestObjectStoreState::coll_entry_t::remove_obj(int id) +{ + return get_obj(id, true); +} + +hobject_t *TestObjectStoreState::coll_entry_t::get_obj(int id, bool remove) +{ + map::iterator it = m_objects.find(id); + if (it == m_objects.end()) { + dout(5) << "get_obj coll " << m_coll.to_str() + << " obj #" << id << " non-existent" << dendl; + return NULL; + } + + hobject_t *obj = it->second; + if (remove) + m_objects.erase(it); + + dout(5) << "get_obj coll " << m_coll.to_str() << " id " << id + << ": " << obj->oid.name << "(removed: " << remove << ")" << dendl; + + return obj; +} + +hobject_t *TestObjectStoreState::coll_entry_t::get_obj_at(int pos, int *key) +{ + return get_obj_at(pos, false, key); +} + +/** + * remove_obj_at - Removes object without freeing it. + * @param pos The map's position in which the object lies. + * @return The object or NULL in case of error. + */ +hobject_t *TestObjectStoreState::coll_entry_t::remove_obj_at(int pos, int *key) +{ + return get_obj_at(pos, true, key); +} + +hobject_t *TestObjectStoreState::coll_entry_t::get_obj_at(int pos, + bool remove, int *key) +{ + if (m_objects.empty()) { + dout(5) << "get_obj_at coll " << m_coll.to_str() << " pos " << pos + << " in an empty collection" << dendl; + return NULL; + } + + hobject_t *ret = NULL; + map::iterator it = m_objects.begin(); + for (int i = 0; it != m_objects.end(); ++it, i++) { + if (i == pos) { + ret = it->second; + break; + } + } + + if (ret == NULL) { + dout(5) << "get_obj_at coll " << m_coll.to_str() << " pos " << pos + << " non-existent" << dendl; + return NULL; + } + + if (key != NULL) + *key = it->first; + + if (remove) + m_objects.erase(it); + + dout(5) << "get_obj_at coll id " << m_id << " pos " << pos + << ": " << ret->oid.name << "(removed: " << remove << ")" << dendl; + + return ret; +} + +hobject_t* +TestObjectStoreState::coll_entry_t::replace_obj(int id, hobject_t *obj) { + hobject_t *old_obj = remove_obj(id); + m_objects.insert(make_pair(id, obj)); + return old_obj; +} + +int TestObjectStoreState::coll_entry_t::get_random_obj_id(rngen_t& gen) +{ + ceph_assert(!m_objects.empty()); + + boost::uniform_int<> orig_obj_rng(0, m_objects.size()-1); + int pos = orig_obj_rng(gen); + map::iterator it = m_objects.begin(); + for (int i = 0; it != m_objects.end(); ++it, i++) { + if (i == pos) { + return it->first; + } + } + ceph_assert(0 == "INTERNAL ERROR"); +} diff --git a/src/test/objectstore/TestFileStoreState.h b/src/test/objectstore/TestFileStoreState.h new file mode 100644 index 000000000000..dad2aab73791 --- /dev/null +++ b/src/test/objectstore/TestFileStoreState.h @@ -0,0 +1,148 @@ +// -*- 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 New Dream Network +* +* This is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License version 2.1, as published by the Free Software +* Foundation. See file COPYING. +*/ +#ifndef TEST_OBJECTSTORE_STATE_H_ +#define TEST_OBJECTSTORE_STATE_H_ + +#include "os/ObjectStore.h" +#include +#include +#include +#include +#include + +typedef boost::mt11213b rngen_t; + +class TestObjectStoreState { +public: + struct coll_entry_t { + int m_id; + coll_t m_coll; + hobject_t m_meta_obj; + ObjectStore::Sequencer m_osr; + map m_objects; + int m_next_object_id; + + coll_entry_t(int i, char *coll_buf, char *meta_obj_buf) + : m_id(i), m_coll(coll_buf), + m_meta_obj(sobject_t(object_t(meta_obj_buf), CEPH_NOSNAP)), + m_osr(coll_buf), m_next_object_id(0) { + } + ~coll_entry_t(); + + hobject_t *touch_obj(int id); + bool check_for_obj(int id); + hobject_t *get_obj(int id); + hobject_t *remove_obj(int id); + hobject_t *get_obj_at(int pos, int *key = NULL); + hobject_t *remove_obj_at(int pos, int *key = NULL); + hobject_t *replace_obj(int id, hobject_t *obj); + int get_random_obj_id(rngen_t& gen); + + private: + hobject_t *get_obj(int id, bool remove); + hobject_t *get_obj_at(int pos, bool remove, int *key = NULL); + }; + + /* kept in upper case for consistency with coll_t's */ + static const coll_t META_COLL; + static const coll_t TEMP_COLL; + + protected: + boost::shared_ptr m_store; + map m_collections; + vector m_collections_ids; + int m_next_coll_nr; + int m_num_objs_per_coll; + int m_num_objects; + + int m_max_in_flight; + atomic_t m_in_flight; + Mutex m_finished_lock; + Cond m_finished_cond; + + void wait_for_ready() { + Mutex::Locker locker(m_finished_lock); + while ((m_max_in_flight > 0) && ((int)m_in_flight.read() >= m_max_in_flight)) + m_finished_cond.Wait(m_finished_lock); + } + + void wait_for_done() { + Mutex::Locker locker(m_finished_lock); + while (m_in_flight.read()) + m_finished_cond.Wait(m_finished_lock); + } + + void set_max_in_flight(int max) { + m_max_in_flight = max; + } + void set_num_objs_per_coll(int val) { + m_num_objs_per_coll = val; + } + + coll_entry_t *get_coll(int key, bool erase = false); + coll_entry_t *get_coll_at(int pos, bool erase = false); + + private: + static const int m_default_num_colls = 30; + + public: + TestObjectStoreState(ObjectStore *store) : + m_next_coll_nr(0), m_num_objs_per_coll(10), m_num_objects(0), + m_max_in_flight(0), m_finished_lock("Finished Lock") { + m_in_flight.set(0); + m_store.reset(store); + } + ~TestObjectStoreState() { + map::iterator it = m_collections.begin(); + while (it != m_collections.end()) { + if (it->second) + delete it->second; + m_collections.erase(it++); + } + } + + void init(int colls, int objs); + void init() { + init(m_default_num_colls, 0); + } + + int inc_in_flight() { + return ((int) m_in_flight.inc()); + } + + int dec_in_flight() { + return ((int) m_in_flight.dec()); + } + + coll_entry_t *coll_create(int id); + + class C_OnFinished: public Context { + protected: + TestObjectStoreState *m_state; + ObjectStore::Transaction *m_tx; + + public: + C_OnFinished(TestObjectStoreState *state, + ObjectStore::Transaction *t) : m_state(state), m_tx(t) { } + + void finish(int r) { + Mutex::Locker locker(m_state->m_finished_lock); + m_state->dec_in_flight(); + m_state->m_finished_cond.Signal(); + + delete m_tx; + } + }; +}; + +#endif /* TEST_OBJECTSTORE_STATE_H_ */ diff --git a/src/test/objectstore/chain_xattr.cc b/src/test/objectstore/chain_xattr.cc new file mode 100644 index 000000000000..8346c02b2b16 --- /dev/null +++ b/src/test/objectstore/chain_xattr.cc @@ -0,0 +1,217 @@ +// -*- 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) 2013 Cloudwatt + * + * Author: Loic Dachary + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library Public License for more details. + * + */ + +#include +#include +#include "os/chain_xattr.h" +#include "include/Context.h" +#include "common/errno.h" +#include "common/ceph_argparse.h" +#include "global/global_init.h" +#include + +#define LARGE_BLOCK_LEN CHAIN_XATTR_MAX_BLOCK_LEN + 1024 + +TEST(chain_xattr, get_and_set) { + const char* file = "testfile"; + ::unlink(file); + int fd = ::open(file, O_CREAT|O_WRONLY|O_TRUNC, 0700); + const string user("user."); + + { + const string name = user + string(CHAIN_XATTR_MAX_NAME_LEN - user.size(), '@'); + const string x(LARGE_BLOCK_LEN, 'X'); + + { + char y[LARGE_BLOCK_LEN]; + ASSERT_EQ(LARGE_BLOCK_LEN, chain_setxattr(file, name.c_str(), x.c_str(), LARGE_BLOCK_LEN)); + ASSERT_EQ(LARGE_BLOCK_LEN, chain_getxattr(file, name.c_str(), 0, 0)); + ASSERT_EQ(LARGE_BLOCK_LEN, chain_getxattr(file, name.c_str(), y, LARGE_BLOCK_LEN)); + ASSERT_EQ(0, chain_removexattr(file, name.c_str())); + ASSERT_EQ(0, memcmp(x.c_str(), y, LARGE_BLOCK_LEN)); + } + + { + char y[LARGE_BLOCK_LEN]; + ASSERT_EQ(LARGE_BLOCK_LEN, chain_fsetxattr(fd, name.c_str(), x.c_str(), LARGE_BLOCK_LEN)); + ASSERT_EQ(LARGE_BLOCK_LEN, chain_fgetxattr(fd, name.c_str(), 0, 0)); + ASSERT_EQ(LARGE_BLOCK_LEN, chain_fgetxattr(fd, name.c_str(), y, LARGE_BLOCK_LEN)); + ASSERT_EQ(0, chain_fremovexattr(fd, name.c_str())); + ASSERT_EQ(0, memcmp(x.c_str(), y, LARGE_BLOCK_LEN)); + } + } + + // + // when chain_setxattr is used to store value that is + // CHAIN_XATTR_MAX_BLOCK_LEN * 2 + 10 bytes long it + // + // add user.foo => CHAIN_XATTR_MAX_BLOCK_LEN bytes + // add user.foo@1 => CHAIN_XATTR_MAX_BLOCK_LEN bytes + // add user.foo@2 => 10 bytes + // + // then ( no chain_removexattr in between ) when it is used to + // override with a value that is exactly CHAIN_XATTR_MAX_BLOCK_LEN + // bytes long it will + // + // replace user.foo => CHAIN_XATTR_MAX_BLOCK_LEN bytes + // remove user.foo@1 => CHAIN_XATTR_MAX_BLOCK_LEN bytes + // leak user.foo@2 => 10 bytes + // + // see http://marc.info/?l=ceph-devel&m=136027076615853&w=4 for the + // discussion + // + { + const string name = user + string(CHAIN_XATTR_MAX_NAME_LEN - user.size(), '@'); + const string x(LARGE_BLOCK_LEN, 'X'); + + { + char y[CHAIN_XATTR_MAX_NAME_LEN]; + ASSERT_EQ(LARGE_BLOCK_LEN, chain_setxattr(file, name.c_str(), x.c_str(), LARGE_BLOCK_LEN)); + ASSERT_EQ(CHAIN_XATTR_MAX_BLOCK_LEN, chain_setxattr(file, name.c_str(), x.c_str(), CHAIN_XATTR_MAX_BLOCK_LEN)); + ASSERT_EQ(CHAIN_XATTR_MAX_BLOCK_LEN, chain_getxattr(file, name.c_str(), 0, 0)); + ASSERT_EQ(CHAIN_XATTR_MAX_BLOCK_LEN, chain_getxattr(file, name.c_str(), y, CHAIN_XATTR_MAX_BLOCK_LEN)); + ASSERT_EQ(0, chain_removexattr(file, name.c_str())); + ASSERT_EQ(0, memcmp(x.c_str(), y, CHAIN_XATTR_MAX_BLOCK_LEN)); + } + + { + char y[CHAIN_XATTR_MAX_BLOCK_LEN]; + ASSERT_EQ(LARGE_BLOCK_LEN, chain_fsetxattr(fd, name.c_str(), x.c_str(), LARGE_BLOCK_LEN)); + ASSERT_EQ(CHAIN_XATTR_MAX_BLOCK_LEN, chain_fsetxattr(fd, name.c_str(), x.c_str(), CHAIN_XATTR_MAX_BLOCK_LEN)); + ASSERT_EQ(CHAIN_XATTR_MAX_BLOCK_LEN, chain_fgetxattr(fd, name.c_str(), 0, 0)); + ASSERT_EQ(CHAIN_XATTR_MAX_BLOCK_LEN, chain_fgetxattr(fd, name.c_str(), y, CHAIN_XATTR_MAX_BLOCK_LEN)); + ASSERT_EQ(0, chain_fremovexattr(fd, name.c_str())); + ASSERT_EQ(0, memcmp(x.c_str(), y, CHAIN_XATTR_MAX_BLOCK_LEN)); + } + } + + { + int x = 0; + ASSERT_EQ(-ENOENT, chain_setxattr("UNLIKELY_TO_EXIST", "NAME", &x, sizeof(x))); + ASSERT_EQ(-ENOENT, chain_getxattr("UNLIKELY_TO_EXIST", "NAME", 0, 0)); + ASSERT_EQ(-ENOENT, chain_getxattr("UNLIKELY_TO_EXIST", "NAME", &x, sizeof(x))); + ASSERT_EQ(-ENOENT, chain_removexattr("UNLIKELY_TO_EXIST", "NAME")); + int unlikely_to_be_a_valid_fd = 400; + ASSERT_EQ(-EBADF, chain_fsetxattr(unlikely_to_be_a_valid_fd, "NAME", &x, sizeof(x))); + ASSERT_EQ(-EBADF, chain_fgetxattr(unlikely_to_be_a_valid_fd, "NAME", 0, 0)); + ASSERT_EQ(-EBADF, chain_fgetxattr(unlikely_to_be_a_valid_fd, "NAME", &x, sizeof(x))); + ASSERT_EQ(-EBADF, chain_fremovexattr(unlikely_to_be_a_valid_fd, "NAME")); + } + + { + int x; + const string name = user + string(CHAIN_XATTR_MAX_NAME_LEN * 2, '@'); + ASSERT_THROW(chain_setxattr(file, name.c_str(), &x, sizeof(x)), FailedAssertion); + ASSERT_THROW(chain_fsetxattr(fd, name.c_str(), &x, sizeof(x)), FailedAssertion); + } + + { + const string name = user + string(CHAIN_XATTR_MAX_NAME_LEN - user.size(), '@'); + const string x(LARGE_BLOCK_LEN, 'X'); + { + char y[LARGE_BLOCK_LEN]; + ASSERT_EQ(LARGE_BLOCK_LEN, chain_setxattr(file, name.c_str(), x.c_str(), LARGE_BLOCK_LEN)); + ASSERT_EQ(-ERANGE, chain_getxattr(file, name.c_str(), y, LARGE_BLOCK_LEN - 1)); + ASSERT_EQ(-ERANGE, chain_getxattr(file, name.c_str(), y, CHAIN_XATTR_MAX_BLOCK_LEN)); + ASSERT_EQ(0, chain_removexattr(file, name.c_str())); + } + + { + char y[LARGE_BLOCK_LEN]; + ASSERT_EQ(LARGE_BLOCK_LEN, chain_fsetxattr(fd, name.c_str(), x.c_str(), LARGE_BLOCK_LEN)); + ASSERT_EQ(-ERANGE, chain_fgetxattr(fd, name.c_str(), y, LARGE_BLOCK_LEN - 1)); + ASSERT_EQ(-ERANGE, chain_fgetxattr(fd, name.c_str(), y, CHAIN_XATTR_MAX_BLOCK_LEN)); + ASSERT_EQ(0, chain_fremovexattr(fd, name.c_str())); + } + } + + ::close(fd); + ::unlink(file); +} + +TEST(chain_xattr, listxattr) { + const char* file = "testfile"; + ::unlink(file); + int fd = ::open(file, O_CREAT|O_WRONLY|O_TRUNC, 0700); + const string user("user."); + const string name1 = user + string(CHAIN_XATTR_MAX_NAME_LEN - user.size(), '1'); + const string name2 = user + string(CHAIN_XATTR_MAX_NAME_LEN - user.size(), '@'); + const string x(LARGE_BLOCK_LEN, 'X'); + const int y = 1234; + + ASSERT_EQ(LARGE_BLOCK_LEN, chain_setxattr(file, name1.c_str(), x.c_str(), LARGE_BLOCK_LEN)); + ASSERT_EQ((int)sizeof(y), chain_setxattr(file, name2.c_str(), &y, sizeof(y))); + + int buffer_size = name1.size() + sizeof('\0') + name2.size() + sizeof('\0'); + char* expected = (char*)malloc(buffer_size); + ::strcpy(expected, name1.c_str()); + ::strcpy(expected + name1.size() + 1, name2.c_str()); + char* actual = (char*)calloc(1, buffer_size); + ASSERT_LT(buffer_size, chain_listxattr(file, NULL, 0)); // size evaluation is conservative + chain_listxattr(file, actual, buffer_size); + ::memset(actual, '\0', buffer_size); + chain_flistxattr(fd, actual, buffer_size); + ASSERT_EQ(0, ::memcmp(expected, actual, buffer_size)); + + int unlikely_to_be_a_valid_fd = 400; + ASSERT_GT(0, chain_listxattr("UNLIKELY_TO_EXIST", actual, 0)); + ASSERT_GT(0, chain_listxattr("UNLIKELY_TO_EXIST", actual, buffer_size)); + ASSERT_GT(0, chain_flistxattr(unlikely_to_be_a_valid_fd, actual, 0)); + ASSERT_GT(0, chain_flistxattr(unlikely_to_be_a_valid_fd, actual, buffer_size)); + ASSERT_EQ(-ERANGE, chain_listxattr(file, actual, 1)); + ASSERT_EQ(-ERANGE, chain_flistxattr(fd, actual, 1)); + + ASSERT_EQ(0, chain_removexattr(file, name1.c_str())); + ASSERT_EQ(0, chain_removexattr(file, name2.c_str())); + + ::unlink(file); +} + +int main(int argc, char **argv) { + vector args; + argv_to_vec(argc, (const char **)argv, args); + + global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT, CODE_ENVIRONMENT_UTILITY, 0); + common_init_finish(g_ceph_context); + g_ceph_context->_conf->set_val("err_to_stderr", "false"); + g_ceph_context->_conf->set_val("log_to_stderr", "false"); + g_ceph_context->_conf->apply_changes(NULL); + + const char* file = "testfile"; + int x = 1234; + int y = 0; + int tmpfd = ::open(file, O_CREAT|O_WRONLY|O_TRUNC, 0700); + int ret = ::ceph_os_fsetxattr(tmpfd, "user.test", &x, sizeof(x)); + if (ret >= 0) + ret = ::ceph_os_fgetxattr(tmpfd, "user.test", &y, sizeof(y)); + ::close(tmpfd); + ::unlink(file); + if ((ret < 0) || (x != y)) { + cerr << "SKIP all tests because extended attributes don't appear to work in the file system in which the tests are run: " << cpp_strerror(ret) << std::endl; + } else { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); + } +} + +// Local Variables: +// compile-command: "cd ../.. ; make unittest_chain_xattr ; valgrind --tool=memcheck ./unittest_chain_xattr # --gtest_filter=chain_xattr.get_and_set" +// End: diff --git a/src/test/objectstore/run_seed_to.sh b/src/test/objectstore/run_seed_to.sh new file mode 100755 index 000000000000..d5bb671138c5 --- /dev/null +++ b/src/test/objectstore/run_seed_to.sh @@ -0,0 +1,290 @@ +#!/bin/bash +# vim: ts=8 sw=2 smarttab +# +# run_seed_to.sh - Run ceph_test_filestore_idempotent_sequence up until an +# injection point, generating a sequence of operations based on a +# provided seed. +# +# We also perform three additional tests, focused on assessing if +# replaying a larger chunck of the journal affects the expected store +# behavior. These tests will be performed by increasing the store's +# journal sync interval to a very large value, allowing the store to +# finish execution before the first sync (unless the store runs for +# over 10 hours, case on which the interval variables must be changed +# to an appropriate value). Unless the '--no-journal-test' option is +# specified, we will run the 3 following scenarios: +# +# 1) journal sync'ing for both stores is good as disabled +# (we call it '00', for store naming purposes) +# 2) journal sync'ing for store A is as good as disabled +# (we call it '01', for store naming purposes) +# 3) journal sync'ing for store B is as good as disabled +# (we call it '10', for store naming purposes) +# +# All log files are also appropriately named accordingly (i.e., a.00.fail, +# a.10.recover, or b.01.clean). +# +# By default, the test will not exit on error, although it will show the +# fail message. This behavior is so defined so we run the whole battery of +# tests, and obtain as many mismatches as possible in one go. We may force +# the test to exit on error by specifying the '--exit-on-error' option. +# +# +set -e + +test_opts="" + +usage() { + echo "usage: $1 [options..] " + echo + echo "options:" + echo " -c, --colls # of collections" + echo " -o, --objs # of objects" + echo " -b, --btrfs seq number for btrfs stores" + echo " --no-journal-test don't perform journal replay tests" + echo " -e, --exit-on-error exit with 1 on error" + echo " -v, --valgrind run commands through valgrind" + echo + echo "env vars:" + echo " OPTS_STORE additional opts for both stores" + echo " OPTS_STORE_A additional opts for store A" + echo " OPTS_STORE_B additional opts for store B" + echo +} + +die_on_missing_arg() { + if [[ "$2" == "" ]]; then + echo "$1: missing required parameter" + exit 1 + fi +} + + +required_args=2 +obtained_args=0 + +seed="" +killat="" +on_btrfs=0 +on_btrfs_seq=0 +journal_test=1 +min_sync_interval="36000" # ten hours, yes. +max_sync_interval="36001" +exit_on_error=0 +v="" + +do_rm() { + if [[ $on_btrfs -eq 0 ]]; then + rm -fr $* + fi +} + +set_arg() { + if [[ $1 -eq 1 ]]; then + seed=$2 + elif [[ $1 -eq 2 ]]; then + killat=$2 + else + echo "error: unknown purpose for '$2'" + usage $0 + exit 1 + fi +} + +while [[ $# -gt 0 ]]; +do + case "$1" in + -c | --colls) + die_on_missing_arg "$1" "$2" + test_opts="$test_opts --test-num-colls $2" + shift 2 + ;; + -o | --objs) + die_on_missing_arg "$1" "$2" + test_opts="$test_opts --test-num-objs $2" + shift 2 + ;; + -h | --help) + usage $0 ; + exit 0 + ;; + -b | --btrfs) + die_on_missing_arg "$1" "$2" + on_btrfs=1 + on_btrfs_seq=$2 + shift 2 + ;; + --no-journal-test) + journal_test=0 + shift + ;; + -e | --exit-on-error) + exit_on_error=1 + shift + ;; + -v | --valgrind) + v="valgrind --leak-check=full" + shift + ;; + --) + shift + break + ;; + -*) + echo "$1: unknown option" >&2 + usage $0 + exit 1 + ;; + *) + obtained_args=$(($obtained_args+1)) + set_arg $obtained_args $1 + shift + ;; + esac +done + +if [[ $obtained_args -ne $required_args ]]; then + echo "error: missing argument" + usage $0 ; + exit 1 +fi + +if [[ "$OPTS_STORE" != "" ]]; then + test_opts="$test_opts $OPTS_STORE" +fi + +test_opts_a="$test_opts" +test_opts_b="$test_opts" + +if [[ "$OPTS_STORE_A" != "" ]]; then + test_opts_a="$test_opts_a $OPTS_STORE_A" +fi +if [[ "$OPTS_STORE_B" != "" ]]; then + test_opts_b="$test_opts_b $OPTS_STORE_B" +fi + +echo seed $seed +echo kill at $killat + +# run forever, until $killat... +to=1000000000 + +# +# store names +# +# We need these for two reasons: +# 1) if we are running the tests on a btrfs volume, then we need to use +# a seq number for each run. Being on btrfs means we will fail when +# removing the store's directories and it's far more simple to just +# specify differente store names such as 'a.$seq' or 'b.$seq'. +# +# 2) unless the '--no-journal-test' option is specified, we will run +# three additional tests for each store, and we will reuse the same +# command for each one of the runs, but varying the store's name and +# arguments. +# +store_a="a" +store_b="b" + +if [[ $on_btrfs -eq 1 ]]; then + store_a="$store_a.$on_btrfs_seq" + store_b="$store_b.$on_btrfs_seq" +fi + +total_runs=1 + +if [[ $journal_test -eq 1 ]]; then + total_runs=$(($total_runs + 3)) +fi + +num_runs=0 + +opt_min_sync="--filestore-min-sync-interval $min_sync_interval" +opt_max_sync="--filestore-max-sync-interval $max_sync_interval" + +ret=0 + +while [[ $num_runs -lt $total_runs ]]; +do + tmp_name_a=$store_a + tmp_name_b=$store_b + tmp_opts_a=$test_opts_a + tmp_opts_b=$test_opts_b + + # + # We have already tested whether there are diffs when both journals + # are properly working. Now let's try on three other scenarios: + # 1) journal sync'ing for both stores is good as disabled + # (we call it '00') + # 2) journal sync'ing for store A is as good as disabled + # (we call it '01') + # 3) journal sync'ing for store B is as good as disabled + # (we call it '10') + # + if [[ $num_runs -gt 0 && $journal_test -eq 1 ]]; then + echo "run #$num_runs" + case $num_runs in + 1) + tmp_name_a="$tmp_name_a.00" + tmp_name_b="$tmp_name_b.00" + tmp_opts_a="$tmp_opts_a $opt_min_sync $opt_max_sync" + tmp_opts_b="$tmp_opts_b $opt_min_sync $opt_max_sync" + ;; + 2) + tmp_name_a="$tmp_name_a.01" + tmp_name_b="$tmp_name_b.01" + tmp_opts_a="$tmp_opts_a $opt_min_sync $opt_max_sync" + ;; + 3) + tmp_name_a="$tmp_name_a.10" + tmp_name_b="$tmp_name_b.10" + tmp_opts_b="$tmp_opts_b $opt_min_sync $opt_max_sync" + ;; + esac + fi + + do_rm $tmp_name_a $tmp_name_a.fail $tmp_name_a.recover + $v ceph_test_filestore_idempotent_sequence run-sequence-to $to \ + $tmp_name_a $tmp_name_a/journal \ + --test-seed $seed --osd-journal-size 100 \ + --filestore-kill-at $killat $tmp_opts_a \ + --log-file $tmp_name_a.fail --debug-filestore 20 || true + + stop_at=`ceph_test_filestore_idempotent_sequence get-last-op \ + $tmp_name_a $tmp_name_a/journal \ + --log-file $tmp_name_a.recover \ + --debug-filestore 20 --debug-journal 20` + + if [[ "`expr $stop_at - $stop_at 2>/dev/null`" != "0" ]]; then + echo "error: get-last-op returned '$stop_at'" + exit 1 + fi + + echo stopped at $stop_at + + do_rm $tmp_name_b $tmp_name_b.clean + $v ceph_test_filestore_idempotent_sequence run-sequence-to \ + $stop_at $tmp_name_b $tmp_name_b/journal \ + --test-seed $seed --osd-journal-size 100 \ + --log-file $tmp_name_b.clean --debug-filestore 20 $tmp_opts_b + + if $v ceph_test_filestore_idempotent_sequence diff \ + $tmp_name_a $tmp_name_a/journal $tmp_name_b $tmp_name_b/journal ; then + echo OK + else + echo "FAIL" + echo " see:" + echo " $tmp_name_a.fail -- leading up to failure" + echo " $tmp_name_a.recover -- journal replay" + echo " $tmp_name_b.clean -- the clean reference" + + ret=1 + if [[ $exit_on_error -eq 1 ]]; then + exit 1 + fi + fi + + num_runs=$(($num_runs+1)) +done + +exit $ret diff --git a/src/test/objectstore/run_seed_to_range.sh b/src/test/objectstore/run_seed_to_range.sh new file mode 100755 index 000000000000..365b34918d27 --- /dev/null +++ b/src/test/objectstore/run_seed_to_range.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +set -e + +seed=$1 +from=$2 +to=$3 +dir=$4 + +mydir=`dirname $0` + +for f in `seq $from $to` +do + if ! $mydir/run_seed_to.sh $seed $f; then + if [ -d $dir ]; then + echo copying evidence to $dir + cp -a . $dir + else + echo no dir provided for evidence disposal + fi + exit 1 + fi +done \ No newline at end of file diff --git a/src/test/objectstore/store_test.cc b/src/test/objectstore/store_test.cc new file mode 100644 index 000000000000..4f66bd6d8929 --- /dev/null +++ b/src/test/objectstore/store_test.cc @@ -0,0 +1,1065 @@ +// -*- 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) 2004-2006 Sage Weil + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include +#include +#include +#include +#include +#include "os/ObjectStore.h" +#include "os/KeyValueStore.h" +#include "include/Context.h" +#include "common/ceph_argparse.h" +#include "global/global_init.h" +#include "common/Mutex.h" +#include "common/Cond.h" +#include "common/errno.h" +#include +#include +#include +#include +#include + +#include "include/unordered_map.h" +typedef boost::mt11213b gen_type; + +#if GTEST_HAS_PARAM_TEST + +class StoreTest : public ::testing::TestWithParam { +public: + boost::scoped_ptr store; + + StoreTest() : store(0) {} + virtual void SetUp() { + int r = ::mkdir("store_test_temp_dir", 0777); + if (r < 0 && errno != EEXIST) { + r = -errno; + cerr << __func__ << ": unable to create store_test_temp_dir" << ": " << cpp_strerror(r) << std::endl; + return; + } + + ObjectStore *store_ = ObjectStore::create(g_ceph_context, + string(GetParam()), + string("store_test_temp_dir"), + string("store_test_temp_journal")); + store.reset(store_); + EXPECT_EQ(store->mkfs(), 0); + EXPECT_EQ(store->mount(), 0); + } + + virtual void TearDown() { + store->umount(); + } +}; + +bool sorted(const vector &in) { + ghobject_t start; + for (vector::const_iterator i = in.begin(); + i != in.end(); + ++i) { + if (start > *i) return false; + start = *i; + } + return true; +} + +TEST_P(StoreTest, SimpleColTest) { + coll_t cid = coll_t("initial"); + int r = 0; + { + ObjectStore::Transaction t; + t.create_collection(cid); + cerr << "create collection" << std::endl; + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } + { + ObjectStore::Transaction t; + t.remove_collection(cid); + cerr << "remove collection" << std::endl; + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } + { + ObjectStore::Transaction t; + t.create_collection(cid); + cerr << "add collection" << std::endl; + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } + { + ObjectStore::Transaction t; + t.remove_collection(cid); + cerr << "remove collection" << std::endl; + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } +} + +TEST_P(StoreTest, SimpleObjectTest) { + int r; + coll_t cid = coll_t("coll"); + { + ObjectStore::Transaction t; + t.create_collection(cid); + cerr << "Creating collection " << cid << std::endl; + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } + ghobject_t hoid(hobject_t(sobject_t("Object 1", CEPH_NOSNAP))); + { + ObjectStore::Transaction t; + t.touch(cid, hoid); + cerr << "Creating object " << hoid << std::endl; + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } + { + ObjectStore::Transaction t; + t.remove(cid, hoid); + t.remove_collection(cid); + cerr << "Cleaning" << std::endl; + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } +} + +TEST_P(StoreTest, SimpleObjectLongnameTest) { + int r; + coll_t cid = coll_t("coll"); + { + ObjectStore::Transaction t; + t.create_collection(cid); + cerr << "Creating collection " << cid << std::endl; + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } + ghobject_t hoid(hobject_t(sobject_t("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaObjectaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 1", CEPH_NOSNAP))); + { + ObjectStore::Transaction t; + t.touch(cid, hoid); + cerr << "Creating object " << hoid << std::endl; + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } + { + ObjectStore::Transaction t; + t.remove(cid, hoid); + t.remove_collection(cid); + cerr << "Cleaning" << std::endl; + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } +} + +TEST_P(StoreTest, ManyObjectTest) { + int NUM_OBJS = 2000; + int r = 0; + coll_t cid("blah"); + string base = ""; + for (int i = 0; i < 100; ++i) base.append("aaaaa"); + set created; + { + ObjectStore::Transaction t; + t.create_collection(cid); + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } + for (int i = 0; i < NUM_OBJS; ++i) { + if (!(i % 5)) { + cerr << "Object " << i << std::endl; + } + ObjectStore::Transaction t; + char buf[100]; + snprintf(buf, sizeof(buf), "%d", i); + ghobject_t hoid(hobject_t(sobject_t(string(buf) + base, CEPH_NOSNAP))); + t.touch(cid, hoid); + created.insert(hoid); + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } + + for (set::iterator i = created.begin(); + i != created.end(); + ++i) { + struct stat buf; + ASSERT_TRUE(!store->stat(cid, *i, &buf)); + } + + set listed; + vector objects; + r = store->collection_list(cid, objects); + ASSERT_EQ(r, 0); + + cerr << "objects.size() is " << objects.size() << std::endl; + for (vector ::iterator i = objects.begin(); + i != objects.end(); + ++i) { + listed.insert(*i); + ASSERT_TRUE(created.count(*i)); + } + ASSERT_TRUE(listed.size() == created.size()); + + ghobject_t start, next; + objects.clear(); + r = store->collection_list_partial( + cid, + ghobject_t::get_max(), + 50, + 60, + 0, + &objects, + &next + ); + ASSERT_EQ(r, 0); + ASSERT_TRUE(objects.empty()); + + objects.clear(); + listed.clear(); + while (1) { + r = store->collection_list_partial(cid, start, + 50, + 60, + 0, + &objects, + &next); + ASSERT_TRUE(sorted(objects)); + ASSERT_EQ(r, 0); + listed.insert(objects.begin(), objects.end()); + if (objects.size() < 50) { + ASSERT_TRUE(next.is_max()); + break; + } + objects.clear(); + start = next; + } + cerr << "listed.size() is " << listed.size() << std::endl; + ASSERT_TRUE(listed.size() == created.size()); + for (set::iterator i = listed.begin(); + i != listed.end(); + ++i) { + ASSERT_TRUE(created.count(*i)); + } + + for (set::iterator i = created.begin(); + i != created.end(); + ++i) { + ObjectStore::Transaction t; + t.remove(cid, *i); + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } + cerr << "cleaning up" << std::endl; + { + ObjectStore::Transaction t; + t.remove_collection(cid); + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } +} + +class ObjectGenerator { +public: + virtual ghobject_t create_object(gen_type *gen) = 0; + virtual ~ObjectGenerator() {} +}; + +class MixedGenerator : public ObjectGenerator { +public: + unsigned seq; + MixedGenerator() : seq(0) {} + ghobject_t create_object(gen_type *gen) { + char buf[100]; + snprintf(buf, sizeof(buf), "%u", seq); + + boost::uniform_int<> true_false(0, 1); + string name(buf); + if (true_false(*gen)) { + // long + for (int i = 0; i < 100; ++i) name.append("aaaaa"); + } else if (true_false(*gen)) { + name = "DIR_" + name; + } + + // hash + //boost::binomial_distribution bin(0xFFFFFF, 0.5); + ++seq; + return ghobject_t(hobject_t(name, string(), rand() & 2 ? CEPH_NOSNAP : rand(), rand() & 0xFF, 0, "")); + } +}; + +class SyntheticWorkloadState { +public: + static const unsigned max_in_flight = 16; + static const unsigned max_objects = 3000; + coll_t cid; + unsigned in_flight; + set available_objects; + set in_use_objects; + ObjectGenerator *object_gen; + gen_type *rng; + ObjectStore *store; + ObjectStore::Sequencer *osr; + + Mutex lock; + Cond cond; + + class C_SyntheticOnReadable : public Context { + public: + SyntheticWorkloadState *state; + ObjectStore::Transaction *t; + ghobject_t hoid; + C_SyntheticOnReadable(SyntheticWorkloadState *state, + ObjectStore::Transaction *t, ghobject_t hoid) + : state(state), t(t), hoid(hoid) {} + + void finish(int r) { + ASSERT_TRUE(r >= 0); + Mutex::Locker locker(state->lock); + if (state->in_use_objects.count(hoid)) { + state->available_objects.insert(hoid); + state->in_use_objects.erase(hoid); + } + --(state->in_flight); + state->cond.Signal(); + } + }; + + + SyntheticWorkloadState(ObjectStore *store, + ObjectGenerator *gen, + gen_type *rng, + ObjectStore::Sequencer *osr, + coll_t cid) + : cid(cid), in_flight(0), object_gen(gen), rng(rng), store(store), osr(osr), + lock("State lock") {} + + int init() { + ObjectStore::Transaction t; + t.create_collection(cid); + return store->apply_transaction(t); + } + + ghobject_t get_uniform_random_object() { + while (in_flight >= max_in_flight || available_objects.empty()) + cond.Wait(lock); + boost::uniform_int<> choose(0, available_objects.size() - 1); + int index = choose(*rng); + set::iterator i = available_objects.begin(); + for ( ; index > 0; --index, ++i) ; + ghobject_t ret = *i; + available_objects.erase(i); + return ret; + } + + void wait_for_ready() { + while (in_flight >= max_in_flight) + cond.Wait(lock); + } + + void wait_for_done() { + Mutex::Locker locker(lock); + while (in_flight) + cond.Wait(lock); + } + + bool can_create() { + return (available_objects.size() + in_use_objects.size()) < max_objects; + } + + bool can_unlink() { + return (available_objects.size() + in_use_objects.size()) > 0; + } + + int touch() { + Mutex::Locker locker(lock); + if (!can_create()) + return -ENOSPC; + wait_for_ready(); + ghobject_t new_obj = object_gen->create_object(rng); + in_use_objects.insert(new_obj); + available_objects.erase(new_obj); + ObjectStore::Transaction *t = new ObjectStore::Transaction; + t->touch(cid, new_obj); + ++in_flight; + return store->queue_transaction(osr, t, new C_SyntheticOnReadable(this, t, new_obj)); + } + + void scan() { + Mutex::Locker locker(lock); + while (in_flight) + cond.Wait(lock); + vector objects; + set objects_set, objects_set2; + ghobject_t next, current; + while (1) { + cerr << "scanning..." << std::endl; + int r = store->collection_list_partial(cid, current, 50, 100, + 0, &objects, &next); + ASSERT_EQ(r, 0); + ASSERT_TRUE(sorted(objects)); + objects_set.insert(objects.begin(), objects.end()); + objects.clear(); + if (next.is_max()) break; + current = next; + } + ASSERT_EQ(objects_set.size(), available_objects.size()); + for (set::iterator i = objects_set.begin(); + i != objects_set.end(); + ++i) { + ASSERT_GT(available_objects.count(*i), (unsigned)0); + } + + int r = store->collection_list(cid, objects); + ASSERT_EQ(r, 0); + objects_set2.insert(objects.begin(), objects.end()); + ASSERT_EQ(objects_set2.size(), available_objects.size()); + for (set::iterator i = objects_set2.begin(); + i != objects_set2.end(); + ++i) { + ASSERT_GT(available_objects.count(*i), (unsigned)0); + } + } + + int stat() { + ghobject_t hoid; + { + Mutex::Locker locker(lock); + if (!can_unlink()) + return -ENOENT; + hoid = get_uniform_random_object(); + in_use_objects.insert(hoid); + ++in_flight; + } + struct stat buf; + int r = store->stat(cid, hoid, &buf); + { + Mutex::Locker locker(lock); + --in_flight; + cond.Signal(); + in_use_objects.erase(hoid); + available_objects.insert(hoid); + } + return r; + } + + int unlink() { + Mutex::Locker locker(lock); + if (!can_unlink()) + return -ENOENT; + ghobject_t to_remove = get_uniform_random_object(); + ObjectStore::Transaction *t = new ObjectStore::Transaction; + t->remove(cid, to_remove); + ++in_flight; + return store->queue_transaction(osr, t, new C_SyntheticOnReadable(this, t, to_remove)); + } + + void print_internal_state() { + Mutex::Locker locker(lock); + cerr << "available_objects: " << available_objects.size() + << " in_use_objects: " << in_use_objects.size() + << " total objects: " << in_use_objects.size() + available_objects.size() + << " in_flight " << in_flight << std::endl; + } +}; + +TEST_P(StoreTest, Synthetic) { + ObjectStore::Sequencer osr("test"); + MixedGenerator gen; + gen_type rng(time(NULL)); + coll_t cid("synthetic_1"); + + SyntheticWorkloadState test_obj(store.get(), &gen, &rng, &osr, cid); + test_obj.init(); + for (int i = 0; i < 1000; ++i) { + if (!(i % 10)) cerr << "seeding object " << i << std::endl; + test_obj.touch(); + } + for (int i = 0; i < 10000; ++i) { + if (!(i % 10)) { + cerr << "Op " << i << std::endl; + test_obj.print_internal_state(); + } + boost::uniform_int<> true_false(0, 99); + int val = true_false(rng); + if (val > 97) { + test_obj.scan(); + } else if (val > 50) { + test_obj.stat(); + } else if (val > 30) { + test_obj.unlink(); + } else { + test_obj.touch(); + } + } + test_obj.wait_for_done(); +} + +TEST_P(StoreTest, HashCollisionTest) { + coll_t cid("blah"); + int r; + { + ObjectStore::Transaction t; + t.create_collection(cid); + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } + string base = ""; + for (int i = 0; i < 100; ++i) base.append("aaaaa"); + set created; + for (int n = 0; n < 10; ++n) { + char nbuf[100]; + sprintf(nbuf, "n%d", n); + for (int i = 0; i < 1000; ++i) { + char buf[100]; + sprintf(buf, "%d", i); + if (!(i % 5)) { + cerr << "Object n" << n << " "<< i << std::endl; + } + ghobject_t hoid(hobject_t(string(buf) + base, string(), CEPH_NOSNAP, 0, 0, string(nbuf))); + { + ObjectStore::Transaction t; + t.touch(cid, hoid); + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } + created.insert(hoid); + } + } + vector objects; + r = store->collection_list(cid, objects); + ASSERT_EQ(r, 0); + set listed(objects.begin(), objects.end()); + cerr << "listed.size() is " << listed.size() << " and created.size() is " << created.size() << std::endl; + ASSERT_TRUE(listed.size() == created.size()); + objects.clear(); + listed.clear(); + ghobject_t current, next; + while (1) { + r = store->collection_list_partial(cid, current, 50, 60, + 0, &objects, &next); + ASSERT_EQ(r, 0); + ASSERT_TRUE(sorted(objects)); + for (vector::iterator i = objects.begin(); + i != objects.end(); + ++i) { + if (listed.count(*i)) + cerr << *i << " repeated" << std::endl; + listed.insert(*i); + } + if (objects.size() < 50) { + ASSERT_TRUE(next.is_max()); + break; + } + objects.clear(); + current = next; + } + cerr << "listed.size() is " << listed.size() << std::endl; + ASSERT_TRUE(listed.size() == created.size()); + for (set::iterator i = listed.begin(); + i != listed.end(); + ++i) { + ASSERT_TRUE(created.count(*i)); + } + + for (set::iterator i = created.begin(); + i != created.end(); + ++i) { + ObjectStore::Transaction t; + t.collection_remove(cid, *i); + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } + ObjectStore::Transaction t; + t.remove_collection(cid); + store->apply_transaction(t); +} + +TEST_P(StoreTest, OMapTest) { + coll_t cid("blah"); + ghobject_t hoid(hobject_t("tesomap", "", CEPH_NOSNAP, 0, 0, "")); + int r; + { + ObjectStore::Transaction t; + t.create_collection(cid); + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } + + map attrs; + { + ObjectStore::Transaction t; + t.touch(cid, hoid); + t.omap_clear(cid, hoid); + map start_set; + t.omap_setkeys(cid, hoid, start_set); + store->apply_transaction(t); + } + + for (int i = 0; i < 100; i++) { + if (!(i%5)) { + std::cout << "On iteration " << i << std::endl; + } + ObjectStore::Transaction t; + bufferlist bl; + map cur_attrs; + r = store->omap_get(cid, hoid, &bl, &cur_attrs); + ASSERT_EQ(r, 0); + for (map::iterator j = attrs.begin(); + j != attrs.end(); + ++j) { + bool correct = cur_attrs.count(j->first) && string(cur_attrs[j->first].c_str()) == string(j->second.c_str()); + if (!correct) { + std::cout << j->first << " is present in cur_attrs " << cur_attrs.count(j->first) << " times " << std::endl; + if (cur_attrs.count(j->first) > 0) { + std::cout << j->second.c_str() << " : " << cur_attrs[j->first].c_str() << std::endl; + } + } + ASSERT_EQ(correct, true); + } + ASSERT_EQ(attrs.size(), cur_attrs.size()); + + char buf[100]; + snprintf(buf, sizeof(buf), "%d", i); + bl.clear(); + bufferptr bp(buf, strlen(buf) + 1); + bl.append(bp); + map to_add; + to_add.insert(pair("key-" + string(buf), bl)); + attrs.insert(pair("key-" + string(buf), bl)); + t.omap_setkeys(cid, hoid, to_add); + store->apply_transaction(t); + } + + int i = 0; + while (attrs.size()) { + if (!(i%5)) { + std::cout << "removal: On iteration " << i << std::endl; + } + ObjectStore::Transaction t; + bufferlist bl; + map cur_attrs; + r = store->omap_get(cid, hoid, &bl, &cur_attrs); + ASSERT_EQ(r, 0); + for (map::iterator j = attrs.begin(); + j != attrs.end(); + ++j) { + bool correct = cur_attrs.count(j->first) && string(cur_attrs[j->first].c_str()) == string(j->second.c_str()); + if (!correct) { + std::cout << j->first << " is present in cur_attrs " << cur_attrs.count(j->first) << " times " << std::endl; + if (cur_attrs.count(j->first) > 0) { + std::cout << j->second.c_str() << " : " << cur_attrs[j->first].c_str() << std::endl; + } + } + ASSERT_EQ(correct, true); + } + + string to_remove = attrs.begin()->first; + set keys_to_remove; + keys_to_remove.insert(to_remove); + t.omap_rmkeys(cid, hoid, keys_to_remove); + store->apply_transaction(t); + + attrs.erase(to_remove); + + ++i; + } + + ObjectStore::Transaction t; + t.remove(cid, hoid); + t.remove_collection(cid); + store->apply_transaction(t); +} + +TEST_P(StoreTest, XattrTest) { + coll_t cid("blah"); + ghobject_t hoid(hobject_t("tesomap", "", CEPH_NOSNAP, 0, 0, "")); + bufferlist big; + for (unsigned i = 0; i < 10000; ++i) { + big.append('\0'); + } + bufferlist small; + for (unsigned i = 0; i < 10; ++i) { + small.append('\0'); + } + int r; + { + ObjectStore::Transaction t; + t.create_collection(cid); + t.touch(cid, hoid); + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } + + map attrs; + { + ObjectStore::Transaction t; + t.setattr(cid, hoid, "attr1", small); + attrs["attr1"] = small; + t.setattr(cid, hoid, "attr2", big); + attrs["attr2"] = big; + t.setattr(cid, hoid, "attr3", small); + attrs["attr3"] = small; + t.setattr(cid, hoid, "attr1", small); + attrs["attr1"] = small; + t.setattr(cid, hoid, "attr4", big); + attrs["attr4"] = big; + t.setattr(cid, hoid, "attr3", big); + attrs["attr3"] = big; + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } + + map aset; + store->getattrs(cid, hoid, aset); + ASSERT_EQ(aset.size(), attrs.size()); + for (map::iterator i = aset.begin(); + i != aset.end(); + ++i) { + bufferlist bl; + bl.push_back(i->second); + ASSERT_TRUE(attrs[i->first] == bl); + } + + { + ObjectStore::Transaction t; + t.rmattr(cid, hoid, "attr2"); + attrs.erase("attr2"); + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } + + aset.clear(); + store->getattrs(cid, hoid, aset); + ASSERT_EQ(aset.size(), attrs.size()); + for (map::iterator i = aset.begin(); + i != aset.end(); + ++i) { + bufferlist bl; + bl.push_back(i->second); + ASSERT_TRUE(attrs[i->first] == bl); + } + + bufferptr bp; + r = store->getattr(cid, hoid, "attr2", bp); + ASSERT_EQ(r, -ENODATA); + + r = store->getattr(cid, hoid, "attr3", bp); + ASSERT_GE(r, 0); + bufferlist bl2; + bl2.push_back(bp); + ASSERT_TRUE(bl2 == attrs["attr3"]); +} + +void colsplittest( + ObjectStore *store, + unsigned num_objects, + unsigned common_suffix_size + ) { + coll_t cid("from"); + coll_t tid("to"); + int r = 0; + { + ObjectStore::Transaction t; + t.create_collection(cid); + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } + { + ObjectStore::Transaction t; + for (uint32_t i = 0; i < 2*num_objects; ++i) { + stringstream objname; + objname << "obj" << i; + t.touch(cid, ghobject_t(hobject_t( + objname.str(), + "", + CEPH_NOSNAP, + i<apply_transaction(t); + ASSERT_EQ(r, 0); + } + { + ObjectStore::Transaction t; + t.create_collection(tid); + t.split_collection(cid, common_suffix_size+1, 0, tid); + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } + + ObjectStore::Transaction t; + vector objects; + r = store->collection_list(cid, objects); + ASSERT_EQ(r, 0); + ASSERT_EQ(objects.size(), num_objects); + for (vector::iterator i = objects.begin(); + i != objects.end(); + ++i) { + ASSERT_EQ(!(i->hobj.hash & (1<collection_list(tid, objects); + ASSERT_EQ(r, 0); + ASSERT_EQ(objects.size(), num_objects); + for (vector::iterator i = objects.begin(); + i != objects.end(); + ++i) { + ASSERT_EQ(i->hobj.hash & (1<apply_transaction(t); + ASSERT_EQ(r, 0); +} + +TEST_P(StoreTest, ColSplitTest1) { + colsplittest(store.get(), 10000, 11); +} +TEST_P(StoreTest, ColSplitTest2) { + colsplittest(store.get(), 100, 7); +} + +#if 0 +TEST_P(StoreTest, ColSplitTest3) { + colsplittest(store.get(), 100000, 25); +} +#endif + +/** + * This test tests adding two different groups + * of objects, each with 1 common prefix and 1 + * different prefix. We then remove half + * in order to verify that the merging correctly + * stops at the common prefix subdir. See bug + * #5273 */ +TEST_P(StoreTest, TwoHash) { + coll_t cid("asdf"); + int r; + { + ObjectStore::Transaction t; + t.create_collection(cid); + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } + std::cout << "Making objects" << std::endl; + for (int i = 0; i < 360; ++i) { + ObjectStore::Transaction t; + ghobject_t o; + if (i < 8) { + o.hobj.hash = (i << 16) | 0xA1; + t.touch(cid, o); + } + o.hobj.hash = (i << 16) | 0xB1; + t.touch(cid, o); + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } + std::cout << "Removing half" << std::endl; + for (int i = 1; i < 8; ++i) { + ObjectStore::Transaction t; + ghobject_t o; + o.hobj.hash = (i << 16) | 0xA1; + t.remove(cid, o); + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } + std::cout << "Checking" << std::endl; + for (int i = 1; i < 8; ++i) { + ObjectStore::Transaction t; + ghobject_t o; + o.hobj.hash = (i << 16) | 0xA1; + bool exists = store->exists(cid, o); + ASSERT_EQ(exists, false); + } + { + ghobject_t o; + o.hobj.hash = 0xA1; + bool exists = store->exists(cid, o); + ASSERT_EQ(exists, true); + } + std::cout << "Cleanup" << std::endl; + for (int i = 0; i < 360; ++i) { + ObjectStore::Transaction t; + ghobject_t o; + o.hobj.hash = (i << 16) | 0xA1; + t.remove(cid, o); + o.hobj.hash = (i << 16) | 0xB1; + t.remove(cid, o); + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } + ObjectStore::Transaction t; + t.remove_collection(cid); + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); +} + +TEST_P(StoreTest, MoveRename) { + coll_t temp_cid("mytemp"); + hobject_t temp_oid("tmp_oid", "", CEPH_NOSNAP, 0, 0, ""); + coll_t cid("dest"); + hobject_t oid("dest_oid", "", CEPH_NOSNAP, 0, 0, ""); + int r; + { + ObjectStore::Transaction t; + t.create_collection(cid); + t.touch(cid, oid); + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } + ASSERT_TRUE(store->exists(cid, oid)); + bufferlist data, attr; + map omap; + data.append("data payload"); + attr.append("attr value"); + omap["omap_key"].append("omap value"); + { + ObjectStore::Transaction t; + t.create_collection(temp_cid); + t.touch(temp_cid, temp_oid); + t.write(temp_cid, temp_oid, 0, data.length(), data); + t.setattr(temp_cid, temp_oid, "attr", attr); + t.omap_setkeys(temp_cid, temp_oid, omap); + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } + ASSERT_TRUE(store->exists(temp_cid, temp_oid)); + { + ObjectStore::Transaction t; + t.remove(cid, oid); + t.collection_move_rename(temp_cid, temp_oid, cid, oid); + r = store->apply_transaction(t); + ASSERT_EQ(r, 0); + } + ASSERT_TRUE(store->exists(cid, oid)); + ASSERT_FALSE(store->exists(temp_cid, temp_oid)); + { + bufferlist newdata; + r = store->read(cid, oid, 0, 1000, newdata); + ASSERT_GE(r, 0); + ASSERT_TRUE(newdata.contents_equal(data)); + bufferlist newattr; + r = store->getattr(cid, oid, "attr", newattr); + ASSERT_GE(r, 0); + ASSERT_TRUE(newattr.contents_equal(attr)); + set keys; + keys.insert("omap_key"); + map newomap; + r = store->omap_get_values(cid, oid, keys, &newomap); + ASSERT_GE(r, 0); + ASSERT_EQ(1u, newomap.size()); + ASSERT_TRUE(newomap.count("omap_key")); + ASSERT_TRUE(newomap["omap_key"].contents_equal(omap["omap_key"])); + } +} + +INSTANTIATE_TEST_CASE_P( + ObjectStore, + StoreTest, + ::testing::Values("filestore", "keyvaluestore-dev")); + +#else + +// Google Test may not support value-parameterized tests with some +// compilers. If we use conditional compilation to compile out all +// code referring to the gtest_main library, MSVC linker will not link +// that library at all and consequently complain about missing entry +// point defined in that library (fatal error LNK1561: entry point +// must be defined). This dummy test keeps gtest_main linked in. +TEST(DummyTest, ValueParameterizedTestsAreNotSupportedOnThisPlatform) {} + +#endif + + +// +// support tests for qa/workunits/filestore/filestore.sh +// +TEST(EXT4StoreTest, _detect_fs) { + if (::getenv("DISK") == NULL || ::getenv("MOUNTPOINT") == NULL) { + cerr << "SKIP because DISK and MOUNTPOINT environment variables are not set. It is meant to run from qa/workunits/filestore/filestore.sh " << std::endl; + return; + } + const string disk(::getenv("DISK")); + EXPECT_LT((unsigned)0, disk.size()); + const string mnt(::getenv("MOUNTPOINT")); + EXPECT_LT((unsigned)0, mnt.size()); + ::umount(mnt.c_str()); + + const string dir("store_test_temp_dir"); + const string journal("store_test_temp_journal"); + + // + // without user_xattr, ext4 fails + // + { + g_ceph_context->_conf->set_val("filestore_xattr_use_omap", "true"); + EXPECT_EQ(::system((string("mount -o loop,nouser_xattr ") + disk + " " + mnt).c_str()), 0); + EXPECT_EQ(::chdir(mnt.c_str()), 0); + EXPECT_EQ(::mkdir(dir.c_str(), 0755), 0); + FileStore store(dir, journal); + EXPECT_EQ(store._detect_fs(), -ENOTSUP); + EXPECT_EQ(::chdir(".."), 0); + EXPECT_EQ(::umount(mnt.c_str()), 0); + } + // + // mounted with user_xattr, ext4 fails if filestore_xattr_use_omap is false + // + { + g_ceph_context->_conf->set_val("filestore_xattr_use_omap", "false"); + EXPECT_EQ(::system((string("mount -o loop,user_xattr ") + disk + " " + mnt).c_str()), 0); + EXPECT_EQ(::chdir(mnt.c_str()), 0); + FileStore store(dir, journal); + EXPECT_EQ(store._detect_fs(), -ENOTSUP); + EXPECT_EQ(::chdir(".."), 0); + EXPECT_EQ(::umount(mnt.c_str()), 0); + } + // + // mounted with user_xattr, ext4 succeeds if filestore_xattr_use_omap is true + // + { + g_ceph_context->_conf->set_val("filestore_xattr_use_omap", "true"); + EXPECT_EQ(::system((string("mount -o loop,user_xattr ") + disk + " " + mnt).c_str()), 0); + EXPECT_EQ(::chdir(mnt.c_str()), 0); + FileStore store(dir, journal); + EXPECT_EQ(store._detect_fs(), 0); + EXPECT_EQ(::chdir(".."), 0); + EXPECT_EQ(::umount(mnt.c_str()), 0); + } +} + + +int main(int argc, char **argv) { + vector args; + argv_to_vec(argc, (const char **)argv, args); + + global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT, CODE_ENVIRONMENT_UTILITY, 0); + common_init_finish(g_ceph_context); + g_ceph_context->_conf->set_val("osd_journal_size", "400"); + g_ceph_context->_conf->set_val("filestore_index_retry_probability", "0.5"); + g_ceph_context->_conf->set_val("filestore_op_thread_timeout", "1000"); + g_ceph_context->_conf->set_val("filestore_op_thread_suicide_timeout", "10000"); + g_ceph_context->_conf->apply_changes(NULL); + + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} + +// Local Variables: +// compile-command: "cd ../.. ; make ceph_test_filestore ; ./ceph_test_filestore --gtest_filter=StoreTest.* --log-to-stderr=true --debug-filestore=20" +// End: diff --git a/src/test/objectstore/test_idempotent.cc b/src/test/objectstore/test_idempotent.cc new file mode 100644 index 000000000000..4cfb8d11e2a6 --- /dev/null +++ b/src/test/objectstore/test_idempotent.cc @@ -0,0 +1,113 @@ +// -*- 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) 2004-2006 Sage Weil + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include +#include +#include +#include "os/FileStore.h" +#include "global/global_init.h" +#include "common/ceph_argparse.h" +#include "common/debug.h" +#include "test/common/ObjectContents.h" +#include "FileStoreTracker.h" +#include "os/LevelDBStore.h" +#include "os/KeyValueDB.h" +#include "os/ObjectStore.h" + +void usage(const string &name) { + std::cerr << "Usage: " << name << " [new|continue] store_path store_journal db_path" + << std::endl; +} + +template +typename T::iterator rand_choose(T &cont) { + if (cont.size() == 0) { + return cont.end(); + } + int index = rand() % cont.size(); + typename T::iterator retval = cont.begin(); + + for (; index > 0; --index) ++retval; + return retval; +} + +int main(int argc, char **argv) { + vector args; + argv_to_vec(argc, (const char **)argv, args); + + global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT, CODE_ENVIRONMENT_UTILITY, 0); + common_init_finish(g_ceph_context); + g_ceph_context->_conf->apply_changes(NULL); + + std::cerr << "args: " << args << std::endl; + if (args.size() < 4) { + usage(argv[0]); + return 1; + } + + string store_path(args[1]); + string store_dev(args[2]); + string db_path(args[3]); + + bool start_new = false; + if (string(args[0]) == string("new")) start_new = true; + + LevelDBStore *_db = new LevelDBStore(g_ceph_context, db_path); + assert(!_db->create_and_open(std::cerr)); + boost::scoped_ptr db(_db); + boost::scoped_ptr store(new FileStore(store_path, store_dev)); + + + if (start_new) { + std::cerr << "mkfs" << std::endl; + assert(!store->mkfs()); + ObjectStore::Transaction t; + assert(!store->mount()); + t.create_collection(coll_t("coll")); + store->apply_transaction(t); + } else { + assert(!store->mount()); + } + + FileStoreTracker tracker(store.get(), db.get()); + + set objects; + for (unsigned i = 0; i < 10; ++i) { + stringstream stream; + stream << "Object_" << i; + tracker.verify("coll", stream.str(), true); + objects.insert(stream.str()); + } + + while (1) { + FileStoreTracker::Transaction t; + for (unsigned j = 0; j < 100; ++j) { + int val = rand() % 100; + if (val < 30) { + t.write("coll", *rand_choose(objects)); + } else if (val < 60) { + t.clone("coll", *rand_choose(objects), + *rand_choose(objects)); + } else if (val < 70) { + t.remove("coll", *rand_choose(objects)); + } else { + t.clone_range("coll", *rand_choose(objects), + *rand_choose(objects)); + } + } + tracker.submit_transaction(t); + tracker.verify("coll", *rand_choose(objects)); + } + return 0; +} diff --git a/src/test/objectstore/test_idempotent_sequence.cc b/src/test/objectstore/test_idempotent_sequence.cc new file mode 100644 index 000000000000..3ef2c79987d8 --- /dev/null +++ b/src/test/objectstore/test_idempotent_sequence.cc @@ -0,0 +1,247 @@ +// -*- 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 New Dream Network +* +* This is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License version 2.1, as published by the Free Software +* Foundation. See file COPYING. +*/ +#include +#include +#include +#include +#include +#include +#include "common/ceph_argparse.h" +#include "global/global_init.h" +#include "common/debug.h" +#include "os/FileStore.h" + +#include "DeterministicOpSequence.h" +#include "FileStoreDiff.h" + +#include "common/config.h" +#include "include/assert.h" + +#define dout_subsys ceph_subsys_ +#undef dout_prefix +#define dout_prefix *_dout << "test_idempotent_sequence " + +void usage(const char *name, std::string command = "") { + assert(name != NULL); + + std::string more = "cmd "; + std::string diff = "diff "; + std::string get_last_op = "get-last-op "; + std::string run_seq_to = "run-sequence-to "; + + if (!command.empty()) { + if (command == "diff") + more = diff; + else if (command == "get-last-op") + more = get_last_op; + else if (command == "run-sequence-to") + more = run_seq_to; + } + std::cout << "usage: " << name << " " << more << " [options]" << std::endl; + + std::cout << "\n\ +Commands:\n\ + " << diff << "\n\ + " << get_last_op << "\n\ + " << run_seq_to << "\n\ +\n\ +Global Options:\n\ + -c FILE Read configuration from FILE\n\ + --osd-data PATH Set OSD Data path\n\ + --osd-journal PATH Set OSD Journal path\n\ + --osd-journal-size VAL Set Journal size\n\ + --help This message\n\ +\n\ +Test-specific Options:\n\ + --test-seed VAL Seed to run the test\n\ + --test-status-file PATH Path to keep the status file\n\ + --test-num-colls VAL Number of collections to create on init\n\ + --test-num-objs VAL Number of objects to create on init\n\ +" << std::endl; +} + +const char *our_name = NULL; +int seed = 0, num_txs = 100, num_colls = 30, num_objs = 0; +bool is_seed_set = false; +int verify_at = 0; +std::string status_file; + +int run_diff(std::string& a_path, std::string& a_journal, + std::string& b_path, std::string& b_journal) +{ + FileStore *a = new FileStore(a_path, a_journal, "a"); + FileStore *b = new FileStore(b_path, b_journal, "b"); + + int ret = 0; + { + FileStoreDiff fsd(a, b); + if (fsd.diff()) { + dout(0) << "diff found an difference" << dendl; + ret = -1; + } else { + dout(0) << "no diff" << dendl; + } + } + + delete a; + delete b; + return ret; +} + +int run_get_last_op(std::string& filestore_path, std::string& journal_path) +{ + FileStore *store = new FileStore(filestore_path, journal_path); + + int err = store->mount(); + if (err) { + store->umount(); + delete store; + return err; + } + + coll_t txn_coll("meta"); + hobject_t txn_object(sobject_t("txn", CEPH_NOSNAP)); + bufferlist bl; + store->read(txn_coll, txn_object, 0, 100, bl); + int32_t txn = 0; + if (bl.length()) { + bufferlist::iterator p = bl.begin(); + ::decode(txn, p); + } + + store->umount(); + delete store; + + cout << txn << std::endl; + return 0; +} + +int run_sequence_to(int val, std::string& filestore_path, + std::string& journal_path) +{ + num_txs = val; + + if (!is_seed_set) + seed = (int) time(NULL); + + FileStore *store = new FileStore(filestore_path, journal_path); + + int err; + + // mkfs iff directory dne + err = ::mkdir(filestore_path.c_str(), 0755); + if (err) { + cerr << filestore_path << " already exists" << std::endl; + store->umount(); + delete store; + return err; + } + + err = store->mkfs(); + ceph_assert(err == 0); + + err = store->mount(); + ceph_assert(err == 0); + + DeterministicOpSequence op_sequence(store, status_file); + op_sequence.init(num_colls, num_objs); + op_sequence.generate(seed, num_txs); + store->umount(); + return 0; +} + +int run_command(std::string& command, std::vector& args) +{ + if (command.empty()) { + usage(our_name); + exit(0); + } + + /* We'll have a class that will handle the options, the command + * and its arguments. For the time being, and so we can move on, let's + * tolerate this big, ugly code. + */ + if (command == "diff") { + /* expect 4 arguments: (filestore path + journal path)*2 */ + if (args.size() == 4) { + return run_diff(args[0], args[1], args[2], args[3]); + } + } else if (command == "get-last-op") { + /* expect 2 arguments: a filestore path + journal */ + if (args.size() == 2) { + return run_get_last_op(args[0], args[1]); + } + } else if (command == "run-sequence-to") { + /* expect 3 arguments: # of operations and a filestore path + journal. */ + if (args.size() == 3) { + return run_sequence_to(strtoll(args[0].c_str(), NULL, 10), args[1], args[2]); + } + } else { + std::cout << "unknown command " << command << std::endl; + usage(our_name); + exit(1); + } + + usage(our_name, command); + exit(1); +} + +int main(int argc, const char *argv[]) +{ + vector def_args; + vector args; + our_name = argv[0]; + argv_to_vec(argc, argv, args); + + global_init(&def_args, args, + CEPH_ENTITY_TYPE_CLIENT, CODE_ENVIRONMENT_UTILITY, + CINIT_FLAG_NO_DEFAULT_CONFIG_FILE); + common_init_finish(g_ceph_context); + g_ceph_context->_conf->apply_changes(NULL); + + std::string command; + std::vector command_args; + + for (std::vector::iterator i = args.begin(); i != args.end();) { + string val; + + if (ceph_argparse_double_dash(args, i)) { + break; + } else if (ceph_argparse_witharg(args, i, &val, + "--test-seed", (char*) NULL)) { + seed = strtoll(val.c_str(), NULL, 10); + is_seed_set = true; + } else if (ceph_argparse_witharg(args, i, &val, + "--test-num-colls", (char*) NULL)) { + num_colls = strtoll(val.c_str(), NULL, 10); + } else if (ceph_argparse_witharg(args, i, &val, + "--test-num-objs", (char*) NULL)) { + num_objs = strtoll(val.c_str(), NULL, 10); + } else if (ceph_argparse_witharg(args, i, &val, + "--test-status-file", (char*) NULL)) { + status_file = val; + } else if (ceph_argparse_flag(args, i, "--help", (char*) NULL)) { + usage(our_name); + exit(0); + } else { + if (command.empty()) + command = *i++; + else + command_args.push_back(string(*i++)); + } + } + + int ret = run_command(command, command_args); + + return ret; +} diff --git a/src/test/objectstore/workload_generator.cc b/src/test/objectstore/workload_generator.cc new file mode 100644 index 000000000000..acf0fc147960 --- /dev/null +++ b/src/test/objectstore/workload_generator.cc @@ -0,0 +1,575 @@ +// -*- 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 New Dream Network + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "os/ObjectStore.h" +#include "common/ceph_argparse.h" +#include "global/global_init.h" +#include "common/debug.h" +#include +#include +#include "workload_generator.h" +#include "include/assert.h" + +#include "TestObjectStoreState.h" + +static const char *our_name = NULL; +void usage(); + +boost::scoped_ptr wrkldgen; + +#define dout_subsys ceph_subsys_ + + +WorkloadGenerator::WorkloadGenerator(vector args) + : TestObjectStoreState(NULL), + m_max_in_flight(def_max_in_flight), + m_num_ops(-1), + m_destroy_coll_every_nr_runs(def_destroy_coll_every_nr_runs), + m_num_colls(def_num_colls), + m_write_data_bytes(0), m_write_xattr_obj_bytes(0), + m_write_xattr_coll_bytes(0), m_write_pglog_bytes(0), + m_suppress_write_data(false), m_suppress_write_xattr_obj(false), + m_suppress_write_xattr_coll(false), m_suppress_write_log(false), + m_do_stats(false), + m_stats_finished_txs(0), + m_stats_lock("WorldloadGenerator::m_stats_lock"), + m_stats_show_secs(5), + m_stats_total_written(0), + m_stats_begin() +{ + int err = 0; + + m_nr_runs.set(0); + + init_args(args); + dout(0) << "data = " << g_conf->osd_data << dendl; + dout(0) << "journal = " << g_conf->osd_journal << dendl; + dout(0) << "journal size = " << g_conf->osd_journal_size << dendl; + + err = ::mkdir(g_conf->osd_data.c_str(), 0755); + ceph_assert(err == 0 || (err < 0 && errno == EEXIST)); + ObjectStore *store_ptr = ObjectStore::create(g_ceph_context, + g_conf->osd_objectstore, + g_conf->osd_data, + g_conf->osd_journal); + m_store.reset(store_ptr); + err = m_store->mkfs(); + ceph_assert(err == 0); + err = m_store->mount(); + ceph_assert(err == 0); + + set_max_in_flight(m_max_in_flight); + set_num_objs_per_coll(def_num_obj_per_coll); + + init(m_num_colls, 0); + + dout(0) << "#colls = " << m_num_colls << dendl; + dout(0) << "#objs per coll = " << m_num_objs_per_coll << dendl; + dout(0) << "#txs per destr = " << m_destroy_coll_every_nr_runs << dendl; + +} + +size_t WorkloadGenerator::_parse_size_or_die(std::string& val) +{ + size_t s = 0; + int multiplier = 0; + size_t i = 0; + + if (val.empty()) // this should never happen, but catch it anyway. + goto die; + + + for (i = 0; i < val.length(); i++) { + if (!isdigit(val[i])) { + if (isalpha(val[i])) { + val[i] = tolower(val[i]); + switch (val[i]) { + case 'b': break; + case 'k': multiplier = 10; break; + case 'm': multiplier = 20; break; + case 'g': multiplier = 30; break; + default: + goto die; + } + val[i] = '\0'; + break; + } else { + goto die; + } + } + } + + s = strtoll(val.c_str(), NULL, 10) * (1 << multiplier); + return s; + +die: + usage(); + exit(1); +} + +void WorkloadGenerator::_suppress_ops_or_die(std::string& val) +{ + for (size_t i = 0; i < val.length(); i++) { + switch (val[i]) { + case 'c': m_suppress_write_xattr_coll = true; break; + case 'o': m_suppress_write_xattr_obj = true; break; + case 'l': m_suppress_write_log = true; break; + case 'd': m_suppress_write_data = true; break; + default: + usage(); + exit(1); + } + } +} + +void WorkloadGenerator::init_args(vector args) +{ + for (std::vector::iterator i = args.begin(); i != args.end();) { + string val; + + if (ceph_argparse_double_dash(args, i)) { + break; + } else if (ceph_argparse_witharg(args, i, &val, + "--test-num-colls", (char*) NULL)) { + m_num_colls = strtoll(val.c_str(), NULL, 10); + } else if (ceph_argparse_witharg(args, i, &val, + "--test-objs-per-coll", (char*) NULL)) { + m_num_objs_per_coll = strtoll(val.c_str(), NULL, 10); + } else if (ceph_argparse_witharg(args, i, &val, + "--test-destroy-coll-per-N-trans", (char*) NULL)) { + m_destroy_coll_every_nr_runs = strtoll(val.c_str(), NULL, 10); + } else if (ceph_argparse_witharg(args, i, &val, + "--test-num-ops", (char*) NULL)) { + m_num_ops = strtoll(val.c_str(), NULL, 10); + } else if (ceph_argparse_witharg(args, i, &val, + "--test-max-in-flight", (char*) NULL)) { + m_max_in_flight = strtoll(val.c_str(), NULL, 10); + } else if (ceph_argparse_witharg(args, i, &val, + "--test-write-data-size", (char*) NULL)) { + m_write_data_bytes = _parse_size_or_die(val); + } else if (ceph_argparse_witharg(args, i, &val, + "--test-write-xattr-obj-size", (char*) NULL)) { + m_write_xattr_obj_bytes = _parse_size_or_die(val); + } else if (ceph_argparse_witharg(args, i, &val, + "--test-write-xattr-coll-size", (char*) NULL)) { + m_write_xattr_coll_bytes = _parse_size_or_die(val); + } else if (ceph_argparse_witharg(args, i, &val, + "--test-write-pglog-size", (char*) NULL)) { + m_write_pglog_bytes = _parse_size_or_die(val); + } else if (ceph_argparse_witharg(args, i, &val, + "--test-suppress-ops", (char*) NULL)) { + _suppress_ops_or_die(val); + } else if (ceph_argparse_witharg(args, i, &val, + "--test-show-stats-period", (char*) NULL)) { + m_stats_show_secs = strtoll(val.c_str(), NULL, 10); + } else if (ceph_argparse_flag(args, i, "--test-show-stats", (char*) NULL)) { + m_do_stats = true; + } else if (ceph_argparse_flag(args, i, "--help", (char*) NULL)) { + usage(); + exit(0); + } + } +} + +int WorkloadGenerator::get_uniform_random_value(int min, int max) +{ + boost::uniform_int<> value(min, max); + return value(m_rng); +} + +TestObjectStoreState::coll_entry_t *WorkloadGenerator::get_rnd_coll_entry(bool erase = false) +{ + int index = get_uniform_random_value(0, m_collections_ids.size()-1); + coll_entry_t *entry = get_coll_at(index, erase); + return entry; +} + +hobject_t *WorkloadGenerator::get_rnd_obj(coll_entry_t *entry) +{ + assert(entry != NULL); + + bool create = + (get_uniform_random_value(0,100) < 50 || !entry->m_objects.size()); + + if (create && ((int) entry->m_objects.size() < m_num_objs_per_coll)) { + return (entry->touch_obj(entry->m_next_object_id++)); + } + + int idx = get_uniform_random_value(0, entry->m_objects.size()-1); + return entry->get_obj_at(idx); +} + +/** + * We'll generate a random amount of bytes, ranging from a single byte up to + * a couple of MB. + */ +size_t WorkloadGenerator::get_random_byte_amount(size_t min, size_t max) +{ + size_t diff = max - min; + return (size_t) (min + (rand() % diff)); +} + +void WorkloadGenerator::get_filled_byte_array(bufferlist& bl, size_t size) +{ + static const char alphanum[] = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + + bufferptr bp(size); + if (false) { + for (unsigned int i = 0; i < size - 1; i++) { + bp[i] = alphanum[rand() % sizeof(alphanum)]; + } + bp[size - 1] = '\0'; + } else { + bp.zero(); + } + bl.append(bp); +} + +void WorkloadGenerator::do_write_object(ObjectStore::Transaction *t, + coll_t coll, hobject_t obj, + C_StatState *stat) +{ + if (m_suppress_write_data) { + dout(5) << __func__ << " suppressed" << dendl; + return; + } + + size_t size = m_write_data_bytes; + if (!size) + size = get_random_byte_amount(min_write_bytes, max_write_bytes); + + bufferlist bl; + get_filled_byte_array(bl, size); + + dout(2) << __func__ << " " << coll << "/" << obj + << " size " << bl.length() << dendl; + + if (m_do_stats && (stat != NULL)) + stat->written_data += bl.length(); + + t->write(coll, obj, 0, bl.length(), bl); +} + +void WorkloadGenerator::do_setattr_object(ObjectStore::Transaction *t, + coll_t coll, hobject_t obj, + C_StatState *stat) +{ + if (m_suppress_write_xattr_obj) { + dout(5) << __func__ << " suppressed" << dendl; + return; + } + + size_t size = m_write_xattr_obj_bytes; + if (!size) + size = get_random_byte_amount(min_xattr_obj_bytes, max_xattr_obj_bytes); + + bufferlist bl; + get_filled_byte_array(bl, size); + + dout(2) << __func__ << " " << coll << "/" << obj << " size " << size << dendl; + + if (m_do_stats && (stat != NULL)) + stat->written_data += bl.length(); + + t->setattr(coll, obj, "objxattr", bl); +} + +void WorkloadGenerator::do_setattr_collection(ObjectStore::Transaction *t, + coll_t coll, C_StatState *stat) +{ + if (m_suppress_write_xattr_coll) { + dout(5) << __func__ << " suppressed" << dendl; + return; + } + + size_t size = m_write_xattr_coll_bytes; + if (!size) + size = get_random_byte_amount(min_xattr_coll_bytes, max_xattr_coll_bytes); + + bufferlist bl; + get_filled_byte_array(bl, size); + dout(2) << __func__ << " coll " << coll << " size " << size << dendl; + + if (m_do_stats && (stat != NULL)) + stat->written_data += bl.length(); + + t->collection_setattr(coll, "collxattr", bl); +} + +void WorkloadGenerator::do_append_log(ObjectStore::Transaction *t, + coll_entry_t *entry, C_StatState *stat) +{ + if (m_suppress_write_log) { + dout(5) << __func__ << " suppressed" << dendl; + return; + } + + size_t size = (m_write_pglog_bytes ? m_write_pglog_bytes : log_append_bytes); + + bufferlist bl; + get_filled_byte_array(bl, size); + hobject_t log_obj = entry->m_meta_obj; + + dout(2) << __func__ << " coll " << entry->m_coll << " " + << META_COLL << " /" << log_obj << " (" << bl.length() << ")" << dendl; + + if (m_do_stats && (stat != NULL)) + stat->written_data += bl.length(); + + uint64_t s = pg_log_size[entry->m_coll]; + t->write(META_COLL, log_obj, s, bl.length(), bl); + pg_log_size[entry->m_coll] += bl.length(); +} + +void WorkloadGenerator::do_destroy_collection(ObjectStore::Transaction *t, + coll_entry_t *entry, + C_StatState *stat) +{ + m_nr_runs.set(0); + entry->m_osr.flush(); + vector ls; + m_store->collection_list(entry->m_coll, ls); + dout(2) << __func__ << " coll " << entry->m_coll + << " (" << ls.size() << " objects)" << dendl; + + for (vector::iterator it = ls.begin(); it < ls.end(); ++it) { + t->remove(entry->m_coll, *it); + } + + t->remove_collection(entry->m_coll); + t->remove(META_COLL, entry->m_meta_obj); +} + +TestObjectStoreState::coll_entry_t +*WorkloadGenerator::do_create_collection(ObjectStore::Transaction *t, + C_StatState *stat) +{ + coll_entry_t *entry = coll_create(m_next_coll_nr++); + if (!entry) { + dout(0) << __func__ << " failed to create coll id " + << m_next_coll_nr << dendl; + return NULL; + } + m_collections.insert(make_pair(entry->m_id, entry)); + + dout(2) << __func__ << " id " << entry->m_id << " coll " << entry->m_coll << dendl; + t->create_collection(entry->m_coll); + dout(2) << __func__ << " meta " << META_COLL << "/" << entry->m_meta_obj << dendl; + t->touch(META_COLL, entry->m_meta_obj); + return entry; +} + +void WorkloadGenerator::do_stats() +{ + utime_t now = ceph_clock_now(NULL); + m_stats_lock.Lock(); + + utime_t duration = (now - m_stats_begin); + + // when cast to double, a utime_t behaves properly + double throughput = (m_stats_total_written / ((double) duration)); + double tx_throughput (m_stats_finished_txs / ((double) duration)); + + dout(0) << __func__ + << " written: " << m_stats_total_written + << " duration: " << duration << " sec" + << " bandwidth: " << prettybyte_t(throughput) << "/s" + << " iops: " << tx_throughput << "/s" + << dendl; + + m_stats_lock.Unlock(); +} + +void WorkloadGenerator::run() +{ + bool create_coll = false; + int ops_run = 0; + + utime_t stats_interval(m_stats_show_secs, 0); + utime_t now = ceph_clock_now(NULL); + utime_t stats_time = now; + m_stats_begin = now; + + do { + C_StatState *stat_state = NULL; + + if (m_num_ops && (ops_run == m_num_ops)) + break; + + if (!create_coll && !m_collections.size()) { + dout(0) << "We ran out of collections!" << dendl; + break; + } + + dout(5) << __func__ + << " m_finished_lock is-locked: " << m_finished_lock.is_locked() + << " in-flight: " << m_in_flight.read() + << dendl; + + wait_for_ready(); + + ObjectStore::Transaction *t = new ObjectStore::Transaction; + Context *c; + bool destroy_collection = false; + TestObjectStoreState::coll_entry_t *entry = NULL; + + + if (m_do_stats) { + utime_t now = ceph_clock_now(NULL); + utime_t elapsed = now - stats_time; + if (elapsed >= stats_interval) { + do_stats(); + stats_time = now; + } + stat_state = new C_StatState(this, now); + } + + if (create_coll) { + create_coll = false; + + entry = do_create_collection(t, stat_state); + if (!entry) { + dout(0) << __func__ << " something went terribly wrong creating coll" << dendl; + break; + } + + c = new C_OnReadable(this, t); + goto queue_tx; + } + + destroy_collection = should_destroy_collection(); + entry = get_rnd_coll_entry(destroy_collection); + assert(entry != NULL); + + if (destroy_collection) { + do_destroy_collection(t, entry, stat_state); + c = new C_OnDestroyed(this, t, entry); + if (!m_num_ops) + create_coll = true; + } else { + hobject_t *obj = get_rnd_obj(entry); + + do_write_object(t, entry->m_coll, *obj, stat_state); + do_setattr_object(t, entry->m_coll, *obj, stat_state); + do_setattr_collection(t, entry->m_coll, stat_state); + do_append_log(t, entry, stat_state); + + c = new C_OnReadable(this, t); + } + +queue_tx: + + if (m_do_stats) { + Context *tmp = c; + c = new C_StatWrapper(stat_state, tmp); + } + + m_store->queue_transaction(&(entry->m_osr), t, c); + + inc_in_flight(); + + ops_run ++; + + } while (true); + + dout(2) << __func__ << " waiting for " + << m_in_flight.read() << " in-flight transactions" << dendl; + + wait_for_done(); + + do_stats(); + + dout(0) << __func__ << " finishing" << dendl; +} + +void usage() +{ + cout << "usage: " << our_name << "[options]" << std::endl; + + cout << "\ +\n\ +Global Options:\n\ + -c FILE Read configuration from FILE\n\ + --osd-objectstore TYPE Set OSD ObjectStore type\n\ + --osd-data PATH Set OSD Data path\n\ + --osd-journal PATH Set OSD Journal path\n\ + --osd-journal-size VAL Set Journal size\n\ + --help This message\n\ +\n\ +Test-specific Options:\n\ + --test-num-colls VAL Set the number of collections\n\ + --test-num-objs-per-coll VAL Set the number of objects per collection\n\ + --test-destroy-coll-per-N-trans VAL Set how many transactions to run before\n\ + destroying a collection.\n\ + --test-num-ops VAL Run a certain number of operations\n\ + (a VAL of 0 runs the test forever)\n\ + --test-max-in-flight VAL Maximum number of in-flight transactions\n\ + (default: 50)\n\ + --test-suppress-ops OPS Suppress ops specified in OPS\n\ + --test-write-data-size SIZE Specify SIZE for all data writes\n\ + --test-write-xattr-obj-size SIZE Specify SIZE for all xattrs on objects\n\ + --test-write-xattr-coll-size SIZE Specify SIZE for all xattrs on colls\n\ + --test-write-pglog-size SIZE Specify SIZE for all pglog writes\n\ + --test-show-stats Show stats as we go\n\ + --test-show-stats-period SECS Show stats every SECS (default: 5)\n\ +\n\ + SIZE is a numeric value that can be assumed as being bytes, or may be any\n\ + other unit if specified: B or b, K or k, M or m, G or g.\n\ + e.g., 1G = 1024M = 1048576k = 1073741824\n\ +\n\ + OPS can be one or more of the following options:\n\ + c writes on collection's xattrs\n\ + o writes on object's xattr\n\ + l writes on pglog\n\ + d data writes on objects\n\ +\n\ +" << std::endl; +} + +int main(int argc, const char *argv[]) +{ + vector def_args; + vector args; + + our_name = argv[0]; + + def_args.push_back("--osd-journal-size"); + def_args.push_back("400"); +// def_args.push_back("--osd-data"); +// def_args.push_back("workload_gen_dir"); +// def_args.push_back("--osd-journal"); +// def_args.push_back("workload_gen_dir/journal"); + argv_to_vec(argc, argv, args); + + global_init(&def_args, args, + CEPH_ENTITY_TYPE_CLIENT, CODE_ENVIRONMENT_UTILITY, + CINIT_FLAG_NO_DEFAULT_CONFIG_FILE); + common_init_finish(g_ceph_context); + g_ceph_context->_conf->apply_changes(NULL); + + WorkloadGenerator *wrkldgen_ptr = new WorkloadGenerator(args); + wrkldgen.reset(wrkldgen_ptr); + wrkldgen->run(); + return 0; +} diff --git a/src/test/objectstore/workload_generator.h b/src/test/objectstore/workload_generator.h new file mode 100644 index 000000000000..3235fe86e996 --- /dev/null +++ b/src/test/objectstore/workload_generator.h @@ -0,0 +1,184 @@ +// -*- 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 New Dream Network + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + */ +#ifndef WORKLOAD_GENERATOR_H_ +#define WORKLOAD_GENERATOR_H_ + +#include "os/ObjectStore.h" +#include +#include +#include +#include +#include + +#include "TestObjectStoreState.h" + +typedef boost::mt11213b rngen_t; + +class WorkloadGenerator : public TestObjectStoreState { + public: + static const int def_max_in_flight = 50; + + static const int def_destroy_coll_every_nr_runs = 100; + static const int def_num_obj_per_coll = 6000; + static const int def_num_colls = 30; + + static const size_t min_write_bytes = 1; + static const size_t max_write_mb = 5; + static const size_t max_write_bytes = (max_write_mb * 1024 * 1024); + + static const size_t min_xattr_obj_bytes = 2; + static const size_t max_xattr_obj_bytes = 300; + static const size_t min_xattr_coll_bytes = 4; + static const size_t max_xattr_coll_bytes = 600; + + static const size_t log_append_bytes = 1024; + + struct C_StatState { + utime_t start; + unsigned int written_data; + WorkloadGenerator *wrkldgen; + + C_StatState(WorkloadGenerator *state, utime_t s) + : start(s), written_data(0), wrkldgen(state) { } + }; + + + protected: + int m_max_in_flight; + int m_num_ops; + int m_destroy_coll_every_nr_runs; + atomic_t m_nr_runs; + + int m_num_colls; + + rngen_t m_rng; + + map pg_log_size; + + size_t m_write_data_bytes; + size_t m_write_xattr_obj_bytes; + size_t m_write_xattr_coll_bytes; + size_t m_write_pglog_bytes; + + bool m_suppress_write_data; + bool m_suppress_write_xattr_obj; + bool m_suppress_write_xattr_coll; + bool m_suppress_write_log; + + bool m_do_stats; + + int m_stats_finished_txs; + Mutex m_stats_lock; + int m_stats_show_secs; + + size_t m_stats_total_written; + utime_t m_stats_begin; + + private: + + void _suppress_ops_or_die(std::string& val); + size_t _parse_size_or_die(std::string& val); + void init_args(vector args); + + int get_uniform_random_value(int min, int max); + coll_entry_t *get_rnd_coll_entry(bool erase); + hobject_t *get_rnd_obj(coll_entry_t *entry); + int get_random_collection_nr(); + int get_random_object_nr(int coll_nr); + + size_t get_random_byte_amount(size_t min, size_t max); + void get_filled_byte_array(bufferlist& bl, size_t size); + + void do_write_object(ObjectStore::Transaction *t, + coll_t coll, hobject_t obj, C_StatState *stat); + void do_setattr_object(ObjectStore::Transaction *t, + coll_t coll, hobject_t obj, C_StatState *stat); + void do_setattr_collection(ObjectStore::Transaction *t, coll_t coll, + C_StatState *stat); + void do_append_log(ObjectStore::Transaction *t, coll_entry_t *entry, + C_StatState *stat); + + bool should_destroy_collection() { + return ((m_destroy_coll_every_nr_runs > 0) && + ((int)m_nr_runs.read() >= m_destroy_coll_every_nr_runs)); + } + void do_destroy_collection(ObjectStore::Transaction *t, coll_entry_t *entry, + C_StatState *stat); + coll_entry_t *do_create_collection(ObjectStore::Transaction *t, + C_StatState *stat); + + void do_stats(); + +public: + WorkloadGenerator(vector args); + ~WorkloadGenerator() { + m_store->umount(); + } + + class C_OnReadable: public TestObjectStoreState::C_OnFinished { + WorkloadGenerator *wrkldgen_state; + + public: + C_OnReadable(WorkloadGenerator *state, + ObjectStore::Transaction *t) + :TestObjectStoreState::C_OnFinished(state, t), wrkldgen_state(state) { } + + void finish(int r) + { + TestObjectStoreState::C_OnFinished::finish(r); + wrkldgen_state->m_nr_runs.inc(); + } + }; + + class C_OnDestroyed: public C_OnReadable { + coll_entry_t *m_entry; + + public: + C_OnDestroyed(WorkloadGenerator *state, + ObjectStore::Transaction *t, coll_entry_t *entry) : + C_OnReadable(state, t), m_entry(entry) {} + + void finish(int r) { + C_OnReadable::finish(r); + delete m_entry; + } + }; + + class C_StatWrapper : public Context { + C_StatState *stat_state; + Context *ctx; + + public: + C_StatWrapper(C_StatState *state, Context *context) + : stat_state(state), ctx(context) { } + + void finish(int r) { + ctx->complete(r); + + stat_state->wrkldgen->m_stats_lock.Lock(); + + stat_state->wrkldgen->m_stats_total_written += stat_state->written_data; + stat_state->wrkldgen->m_stats_finished_txs ++; + stat_state->wrkldgen->m_stats_lock.Unlock(); + } + }; + + void run(void); +}; + +bool operator<(const WorkloadGenerator::coll_entry_t& l, + const WorkloadGenerator::coll_entry_t& r) { + return (l.m_id < r.m_id); +} + +#endif /* WORKLOAD_GENERATOR_H_ */