From: Samuel Just Date: Tue, 15 Feb 2011 21:48:57 +0000 (-0800) Subject: testsnaps: add snapshot test X-Git-Tag: v0.25~121 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=28bb6fb5271802beaed14309ff663719944c9aa4;p=ceph.git testsnaps: add snapshot test Uses RadosModel.h to check the results of a randomized sequence of writes, reads, snapshots, snapshot removals, and rollbacks for errors. Signed-off-by: Samuel Just --- diff --git a/src/Makefile.am b/src/Makefile.am index 8bffa32663cf..aeb92d677ee5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -156,6 +156,10 @@ test_trans_SOURCES = test_trans.cc test_trans_LDADD = libos.a libcommon.a -lpthread -lm $(CRYPTOPP_LIBS) bin_PROGRAMS += test_trans +testsnaps_SOURCES = test/osd/TestSnaps.cc +testsnaps_LDADD = librados.la -lpthread -lm $(CRYPTOPP_LIBS) +bin_PROGRAMS += testsnaps + endif diff --git a/src/test/osd/RadosModel.h b/src/test/osd/RadosModel.h new file mode 100644 index 000000000000..a3018c7245e7 --- /dev/null +++ b/src/test/osd/RadosModel.h @@ -0,0 +1,371 @@ +#include "common/Mutex.h" +#include "common/Cond.h" +#include "include/rados/librados.hpp" + +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +/* Snap creation/removal tester + */ + +struct RadosTestContext; + +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; +} + +struct TestOp +{ + librados::Rados::AioCompletion *completion; + bool done; + virtual void begin() = 0; + + virtual void finalize() + { + return; + } + + virtual bool finished() + { + return true; + } +}; + + +struct TestOpGenerator +{ + virtual TestOp *next(RadosTestContext &context) = 0; +}; + +struct RadosTestContext +{ + Mutex state_lock; + Cond wait_cond; + map > pool_obj_cont; + set snaps; + set oid_in_use; + set oid_not_in_use; + int current_snap; + string pool_name; + librados::pool_t pool; + librados::Rados rados; + int next_oid; + string prefix; + int errors; + int max_in_flight; + + RadosTestContext(const string &pool_name, int max_in_flight) : + state_lock("Context Lock"), + pool_obj_cont(), + current_snap(0), + pool_name(pool_name), + errors(0), + max_in_flight(max_in_flight) + { + rados.initialize(0, 0); + rados.open_pool(pool_name.c_str(), &pool); + char hostname_cstr[100]; + gethostname(hostname_cstr, 100); + stringstream hostpid; + hostpid << hostname_cstr << getpid() << "-"; + prefix = hostpid.str(); + } + + void shutdown() + { + rados.shutdown(); + } + + void loop(TestOpGenerator &gen) + { + list inflight; + state_lock.Lock(); + + TestOp *next = gen.next(*this); + while (next || inflight.size()) { + if (next) { + inflight.push_back(next); + } + state_lock.Unlock(); + if (next) { + (*inflight.rbegin())->begin(); + } + state_lock.Lock(); + while (1) { + for (list::iterator i = inflight.begin(); + i != inflight.end();) { + if ((*i)->finished()) { + delete *i; + inflight.erase(i++); + } else { + ++i; + } + } + + if (inflight.size() >= (unsigned) max_in_flight || (!next && inflight.size())) { + cout << "Waiting on " << inflight.size() << std::endl; + wait(); + } else { + break; + } + } + next = gen.next(*this); + } + state_lock.Unlock(); + } + + void wait() + { + wait_cond.Wait(state_lock); + } + + void kick() + { + wait_cond.Signal(); + } + + bool find_object(string oid, string &contents, int snap = -1) const + { + for (map >::const_reverse_iterator i = + pool_obj_cont.rbegin(); + i != pool_obj_cont.rend(); + ++i) { + if (snap != -1 && snap < i->first) continue; + if (i->second.count(oid) != 0) { + contents = i->second.find(oid)->second; + return true; + } + } + return false; + } + + void remove_snap(int snap) + { + map >::iterator next_iter = pool_obj_cont.find(snap); + map >::iterator current_iter = next_iter++; + if (next_iter != pool_obj_cont.end()) { + map ¤t = current_iter->second; + map &next = next_iter->second; + for (map::iterator i = current.begin(); + i != current.end(); + ++i) { + if (next.count(i->first) == 0) { + next[i->first] = i->second; + } + } + } + snaps.erase(snap); + pool_obj_cont.erase(current_iter); + } + + void add_snap() + { + current_snap++; + pool_obj_cont[current_snap]; + snaps.insert(current_snap - 1); + } + + void roll_back(string oid, int snap) + { + string contents; + find_object(oid, contents, snap); + pool_obj_cont.rbegin()->second[oid] = contents; + } +}; + +void callback(librados::completion_t cb, void *arg) { + TestOp *op = (TestOp *) arg; + op->finalize(); +} + +struct WriteOp : public TestOp +{ + RadosTestContext &context; + string oid; + string written; + WriteOp(RadosTestContext &cont, const string &oid) : + context(cont), + oid(oid) + {} + + void begin() + { + context.state_lock.Lock(); + done = 0; + completion = context.rados.aio_create_completion((void *) this, &callback, 0); + stringstream to_write; + to_write << context.prefix << "OID: " << oid << " snap " << context.current_snap << std::endl; + written = to_write.str(); + context.pool_obj_cont[context.current_snap][oid] = written; + + context.oid_in_use.insert(oid); + if (context.oid_not_in_use.count(oid) != 0) { + context.oid_not_in_use.erase(oid); + } + + bufferlist write_buffer; + write_buffer.append(to_write.str()); + context.state_lock.Unlock(); + + context.rados.aio_write(context.pool, + context.prefix+oid, + 0, + write_buffer, + to_write.str().length(), + completion); + } + + void finalize() + { + context.state_lock.Lock(); + context.oid_in_use.erase(oid); + context.oid_not_in_use.insert(oid); + context.kick(); + done = true; + context.state_lock.Unlock(); + } + + bool finished() + { + return done && completion->is_complete(); + } +}; + +struct ReadOp : public TestOp +{ + RadosTestContext &context; + string oid; + bufferlist result; + string old_value; + ReadOp(RadosTestContext &cont, const string &oid) : + context(cont), + oid(oid) + {} + + void begin() + { + context.state_lock.Lock(); + done = 0; + completion = context.rados.aio_create_completion((void *) this, &callback, 0); + + context.oid_in_use.insert(oid); + context.oid_not_in_use.erase(oid); + context.find_object(oid, old_value); + + context.state_lock.Unlock(); + context.rados.aio_read(context.pool, + context.prefix+oid, + 0, + &result, + old_value.length(), + completion); + } + + void finalize() + { + context.state_lock.Lock(); + context.oid_in_use.erase(oid); + context.oid_not_in_use.insert(oid); + string to_check; + result.copy(0, old_value.length(), to_check); + if (to_check != old_value) { + context.errors++; + cerr << "Error: oid " << oid << " read returned \n" + << to_check << "\nShould have returned\n" + << old_value << "\nCurrent snap is " << context.current_snap << std::endl; + } + context.kick(); + done = true; + context.state_lock.Unlock(); + } + + bool finished() + { + return done && completion->is_complete(); + } +}; + +struct SnapCreateOp : public TestOp +{ + RadosTestContext &context; + SnapCreateOp(RadosTestContext &cont) : + context(cont) + {} + + void begin() + { + context.state_lock.Lock(); + context.add_snap(); + context.state_lock.Unlock(); + + stringstream snap_name; + snap_name << context.prefix << context.current_snap - 1; + context.rados.snap_create(context.pool, + snap_name.str().c_str()); + } +}; + +struct SnapRemoveOp : public TestOp +{ + RadosTestContext &context; + int to_remove; + SnapRemoveOp(RadosTestContext &cont, int snap) : + context(cont), + to_remove(snap) + {} + + void begin() + { + context.state_lock.Lock(); + context.remove_snap(to_remove); + context.state_lock.Unlock(); + + stringstream snap_name; + snap_name << context.prefix << to_remove; + context.rados.snap_remove(context.pool, + snap_name.str().c_str()); + } +}; + +struct RollbackOp : public TestOp +{ + RadosTestContext &context; + string oid; + int roll_back_to; + + RollbackOp(RadosTestContext &cont, const string &_oid, int snap) : + context(cont), + oid(_oid), + roll_back_to(snap) + {} + + void begin() + { + context.state_lock.Lock(); + context.oid_in_use.insert(oid); + context.roll_back(oid, roll_back_to); + context.state_lock.Unlock(); + + stringstream snap_name; + snap_name << context.prefix << roll_back_to; + context.rados.snap_rollback_object(context.pool, + context.prefix+oid, + snap_name.str().c_str()); + } +}; diff --git a/src/test/osd/TestSnaps.cc b/src/test/osd/TestSnaps.cc new file mode 100644 index 000000000000..9ddee0ff091a --- /dev/null +++ b/src/test/osd/TestSnaps.cc @@ -0,0 +1,100 @@ +#include "common/Mutex.h" +#include "common/Cond.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test/osd/RadosModel.h" + +using namespace std; + +struct SnapTestGenerator : public TestOpGenerator +{ + TestOp *nextop; + int op; + int ops; + int objects; + SnapTestGenerator(int ops, int objects) : nextop(0), op(0), ops(ops), objects(objects) {} + + + TestOp *next(RadosTestContext &context) + { + op++; + if (op <= objects) { + stringstream oid; + oid << op; + cout << "Writing initial " << oid.str() << std::endl; + return new WriteOp(context, oid.str()); + } else if (op >= ops) { + return 0; + } + + if (nextop) { + TestOp *retval = nextop; + nextop = 0; + return retval; + } + + int switchval = rand() % 50; + if (switchval < 20) { + string oid = *(rand_choose(context.oid_not_in_use)); + cout << "Reading " << oid << std::endl; + return new ReadOp(context, oid); + } else if (switchval < 40) { + string oid = *(rand_choose(context.oid_not_in_use)); + cout << "Writing " << oid << " current snap is " << context.current_snap << std::endl; + return new WriteOp(context, oid); + } else if ((switchval < 45) && !context.snaps.empty()) { + int snap = *(rand_choose(context.snaps)); + string oid = *(rand_choose(context.oid_not_in_use)); + cout << "RollingBack " << oid << " to " << snap << std::endl; + nextop = new ReadOp(context, oid); + return new RollbackOp(context, oid, snap); + } else if ((switchval < 47) && !context.snaps.empty()) { + int snap = *(rand_choose(context.snaps)); + cout << "RemovingSnap " << snap << std::endl; + return new SnapRemoveOp(context, snap); + } else { + cout << "Snapping" << std::endl; + return new SnapCreateOp(context); + } + } +}; + +int main(int argc, char **argv) +{ + int ops = 1000; + int objects = 50; + int max_in_flight = 16; + if (argc > 1) { + ops = atoi(argv[1]); + } + + if (argc > 2) { + objects = atoi(argv[2]); + } + + if (argc > 3) { + max_in_flight = atoi(argv[3]); + } + + if (max_in_flight > objects) { + cerr << "Error: max_in_flight must be greater than the number of objects" + << std::endl; + return 0; + } + + string pool_name = "casdata"; + RadosTestContext context(pool_name, max_in_flight); + SnapTestGenerator gen = SnapTestGenerator(ops, objects); + context.loop(gen); + context.shutdown(); + cerr << context.errors << " errors." << std::endl; + return 0; +}