summaryrefslogtreecommitdiffstats
path: root/src/test/test_snap_mapper.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:45:59 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:45:59 +0000
commit19fcec84d8d7d21e796c7624e521b60d28ee21ed (patch)
tree42d26aa27d1e3f7c0b8bd3fd14e7d7082f5008dc /src/test/test_snap_mapper.cc
parentInitial commit. (diff)
downloadceph-upstream/16.2.11+ds.tar.xz
ceph-upstream/16.2.11+ds.zip
Adding upstream version 16.2.11+ds.upstream/16.2.11+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/test/test_snap_mapper.cc')
-rw-r--r--src/test/test_snap_mapper.cc685
1 files changed, 685 insertions, 0 deletions
diff --git a/src/test/test_snap_mapper.cc b/src/test/test_snap_mapper.cc
new file mode 100644
index 000000000..77e31ec0e
--- /dev/null
+++ b/src/test/test_snap_mapper.cc
@@ -0,0 +1,685 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+#include <iterator>
+#include <map>
+#include <set>
+#include <boost/scoped_ptr.hpp>
+#include <sys/types.h>
+#include <cstdlib>
+
+#include "include/buffer.h"
+#include "common/map_cacher.hpp"
+#include "osd/SnapMapper.h"
+#include "common/Cond.h"
+
+#include "gtest/gtest.h"
+
+using namespace std;
+
+template <typename T>
+typename T::iterator rand_choose(T &cont) {
+ if (std::empty(cont)) {
+ return std::end(cont);
+ }
+ return std::next(std::begin(cont), rand() % cont.size());
+}
+
+string random_string(size_t size)
+{
+ string name;
+ for (size_t j = 0; j < size; ++j) {
+ name.push_back('a' + (rand() % 26));
+ }
+ return name;
+}
+
+class PausyAsyncMap : public MapCacher::StoreDriver<string, bufferlist> {
+ struct _Op {
+ virtual void operate(map<string, bufferlist> *store) = 0;
+ virtual ~_Op() {}
+ };
+ typedef std::shared_ptr<_Op> Op;
+ struct Remove : public _Op {
+ set<string> to_remove;
+ explicit Remove(const set<string> &to_remove) : to_remove(to_remove) {}
+ void operate(map<string, bufferlist> *store) override {
+ for (set<string>::iterator i = to_remove.begin();
+ i != to_remove.end();
+ ++i) {
+ store->erase(*i);
+ }
+ }
+ };
+ struct Insert : public _Op {
+ map<string, bufferlist> to_insert;
+ explicit Insert(const map<string, bufferlist> &to_insert) : to_insert(to_insert) {}
+ void operate(map<string, bufferlist> *store) override {
+ for (map<string, bufferlist>::iterator i = to_insert.begin();
+ i != to_insert.end();
+ ++i) {
+ store->erase(i->first);
+ store->insert(*i);
+ }
+ }
+ };
+ struct Callback : public _Op {
+ Context *context;
+ explicit Callback(Context *c) : context(c) {}
+ void operate(map<string, bufferlist> *store) override {
+ context->complete(0);
+ }
+ };
+public:
+ class Transaction : public MapCacher::Transaction<string, bufferlist> {
+ friend class PausyAsyncMap;
+ list<Op> ops;
+ list<Op> callbacks;
+ public:
+ void set_keys(const map<string, bufferlist> &i) override {
+ ops.push_back(Op(new Insert(i)));
+ }
+ void remove_keys(const set<string> &r) override {
+ ops.push_back(Op(new Remove(r)));
+ }
+ void add_callback(Context *c) override {
+ callbacks.push_back(Op(new Callback(c)));
+ }
+ };
+private:
+
+ ceph::mutex lock = ceph::make_mutex("PausyAsyncMap");
+ map<string, bufferlist> store;
+
+ class Doer : public Thread {
+ static const size_t MAX_SIZE = 100;
+ PausyAsyncMap *parent;
+ ceph::mutex lock = ceph::make_mutex("Doer lock");
+ ceph::condition_variable cond;
+ int stopping;
+ bool paused;
+ list<Op> queue;
+ public:
+ explicit Doer(PausyAsyncMap *parent) :
+ parent(parent), stopping(0), paused(false) {}
+ void *entry() override {
+ while (1) {
+ list<Op> ops;
+ {
+ std::unique_lock l{lock};
+ cond.wait(l, [this] {
+ return stopping || (!queue.empty() && !paused);
+ });
+ if (stopping && queue.empty()) {
+ stopping = 2;
+ cond.notify_all();
+ return 0;
+ }
+ ceph_assert(!queue.empty());
+ ceph_assert(!paused);
+ ops.swap(queue);
+ cond.notify_all();
+ }
+ ceph_assert(!ops.empty());
+
+ for (list<Op>::iterator i = ops.begin();
+ i != ops.end();
+ ops.erase(i++)) {
+ if (!(rand()%3))
+ usleep(1+(rand() % 5000));
+ std::lock_guard l{parent->lock};
+ (*i)->operate(&(parent->store));
+ }
+ }
+ }
+
+ void pause() {
+ std::lock_guard l{lock};
+ paused = true;
+ cond.notify_all();
+ }
+
+ void resume() {
+ std::lock_guard l{lock};
+ paused = false;
+ cond.notify_all();
+ }
+
+ void submit(list<Op> &in) {
+ std::unique_lock l{lock};
+ cond.wait(l, [this] { return queue.size() < MAX_SIZE;});
+ queue.splice(queue.end(), in, in.begin(), in.end());
+ cond.notify_all();
+ }
+
+ void stop() {
+ std::unique_lock l{lock};
+ stopping = 1;
+ cond.notify_all();
+ cond.wait(l, [this] { return stopping == 2; });
+ cond.notify_all();
+ }
+ } doer;
+
+public:
+ PausyAsyncMap() : doer(this) {
+ doer.create("doer");
+ }
+ ~PausyAsyncMap() override {
+ doer.join();
+ }
+ int get_keys(
+ const set<string> &keys,
+ map<string, bufferlist> *out) override {
+ std::lock_guard l{lock};
+ for (set<string>::const_iterator i = keys.begin();
+ i != keys.end();
+ ++i) {
+ map<string, bufferlist>::iterator j = store.find(*i);
+ if (j != store.end())
+ out->insert(*j);
+ }
+ return 0;
+ }
+ int get_next(
+ const string &key,
+ pair<string, bufferlist> *next) override {
+ std::lock_guard l{lock};
+ map<string, bufferlist>::iterator j = store.upper_bound(key);
+ if (j != store.end()) {
+ if (next)
+ *next = *j;
+ return 0;
+ } else {
+ return -ENOENT;
+ }
+ }
+ void submit(Transaction *t) {
+ doer.submit(t->ops);
+ doer.submit(t->callbacks);
+ }
+
+ void flush() {
+ ceph::mutex lock = ceph::make_mutex("flush lock");
+ ceph::condition_variable cond;
+ bool done = false;
+
+ class OnFinish : public Context {
+ ceph::mutex *lock;
+ ceph::condition_variable *cond;
+ bool *done;
+ public:
+ OnFinish(ceph::mutex *lock, ceph::condition_variable *cond, bool *done)
+ : lock(lock), cond(cond), done(done) {}
+ void finish(int) override {
+ std::lock_guard l{*lock};
+ *done = true;
+ cond->notify_all();
+ }
+ };
+ Transaction t;
+ t.add_callback(new OnFinish(&lock, &cond, &done));
+ submit(&t);
+ {
+ std::unique_lock l{lock};
+ cond.wait(l, [&] { return done; });
+ }
+ }
+
+ void pause() {
+ doer.pause();
+ }
+ void resume() {
+ doer.resume();
+ }
+ void stop() {
+ doer.stop();
+ }
+
+};
+
+class MapCacherTest : public ::testing::Test {
+protected:
+ boost::scoped_ptr< PausyAsyncMap > driver;
+ boost::scoped_ptr<MapCacher::MapCacher<string, bufferlist> > cache;
+ map<string, bufferlist> truth;
+ set<string> names;
+public:
+ void assert_bl_eq(bufferlist &bl1, bufferlist &bl2) {
+ ASSERT_EQ(bl1.length(), bl2.length());
+ bufferlist::iterator j = bl2.begin();
+ for (bufferlist::iterator i = bl1.begin();
+ !i.end();
+ ++i, ++j) {
+ ASSERT_TRUE(!j.end());
+ ASSERT_EQ(*i, *j);
+ }
+ }
+ void assert_bl_map_eq(map<string, bufferlist> &m1,
+ map<string, bufferlist> &m2) {
+ ASSERT_EQ(m1.size(), m2.size());
+ map<string, bufferlist>::iterator j = m2.begin();
+ for (map<string, bufferlist>::iterator i = m1.begin();
+ i != m1.end();
+ ++i, ++j) {
+ ASSERT_TRUE(j != m2.end());
+ ASSERT_EQ(i->first, j->first);
+ assert_bl_eq(i->second, j->second);
+ }
+
+ }
+ size_t random_num() {
+ return random() % 10;
+ }
+ size_t random_size() {
+ return random() % 1000;
+ }
+ void random_bl(size_t size, bufferlist *bl) {
+ for (size_t i = 0; i < size; ++i) {
+ bl->append(rand());
+ }
+ }
+ void do_set() {
+ size_t set_size = random_num();
+ map<string, bufferlist> to_set;
+ for (size_t i = 0; i < set_size; ++i) {
+ bufferlist bl;
+ random_bl(random_size(), &bl);
+ string key = *rand_choose(names);
+ to_set.insert(
+ make_pair(key, bl));
+ }
+ for (map<string, bufferlist>::iterator i = to_set.begin();
+ i != to_set.end();
+ ++i) {
+ truth.erase(i->first);
+ truth.insert(*i);
+ }
+ {
+ PausyAsyncMap::Transaction t;
+ cache->set_keys(to_set, &t);
+ driver->submit(&t);
+ }
+ }
+ void remove() {
+ size_t remove_size = random_num();
+ set<string> to_remove;
+ for (size_t i = 0; i < remove_size ; ++i) {
+ to_remove.insert(*rand_choose(names));
+ }
+ for (set<string>::iterator i = to_remove.begin();
+ i != to_remove.end();
+ ++i) {
+ truth.erase(*i);
+ }
+ {
+ PausyAsyncMap::Transaction t;
+ cache->remove_keys(to_remove, &t);
+ driver->submit(&t);
+ }
+ }
+ void get() {
+ set<string> to_get;
+ size_t get_size = random_num();
+ for (size_t i = 0; i < get_size; ++i) {
+ to_get.insert(*rand_choose(names));
+ }
+
+ map<string, bufferlist> got_truth;
+ for (set<string>::iterator i = to_get.begin();
+ i != to_get.end();
+ ++i) {
+ map<string, bufferlist>::iterator j = truth.find(*i);
+ if (j != truth.end())
+ got_truth.insert(*j);
+ }
+
+ map<string, bufferlist> got;
+ cache->get_keys(to_get, &got);
+
+ assert_bl_map_eq(got, got_truth);
+ }
+
+ void get_next() {
+ string cur;
+ while (true) {
+ pair<string, bufferlist> next;
+ int r = cache->get_next(cur, &next);
+
+ pair<string, bufferlist> next_truth;
+ map<string, bufferlist>::iterator i = truth.upper_bound(cur);
+ int r_truth = (i == truth.end()) ? -ENOENT : 0;
+ if (i != truth.end())
+ next_truth = *i;
+
+ ASSERT_EQ(r, r_truth);
+ if (r == -ENOENT)
+ break;
+
+ ASSERT_EQ(next.first, next_truth.first);
+ assert_bl_eq(next.second, next_truth.second);
+ cur = next.first;
+ }
+ }
+ void SetUp() override {
+ driver.reset(new PausyAsyncMap());
+ cache.reset(new MapCacher::MapCacher<string, bufferlist>(driver.get()));
+ names.clear();
+ truth.clear();
+ size_t names_size(random_num() + 10);
+ for (size_t i = 0; i < names_size; ++i) {
+ names.insert(random_string(1 + (random_size() % 10)));
+ }
+ }
+ void TearDown() override {
+ driver->stop();
+ cache.reset();
+ driver.reset();
+ }
+
+};
+
+TEST_F(MapCacherTest, Simple)
+{
+ driver->pause();
+ map<string, bufferlist> truth;
+ set<string> truth_keys;
+ string blah("asdf");
+ bufferlist bl;
+ encode(blah, bl);
+ truth[string("asdf")] = bl;
+ truth_keys.insert(truth.begin()->first);
+ {
+ PausyAsyncMap::Transaction t;
+ cache->set_keys(truth, &t);
+ driver->submit(&t);
+ cache->set_keys(truth, &t);
+ driver->submit(&t);
+ }
+
+ map<string, bufferlist> got;
+ cache->get_keys(truth_keys, &got);
+ assert_bl_map_eq(got, truth);
+
+ driver->resume();
+ sleep(1);
+
+ got.clear();
+ cache->get_keys(truth_keys, &got);
+ assert_bl_map_eq(got, truth);
+}
+
+TEST_F(MapCacherTest, Random)
+{
+ for (size_t i = 0; i < 5000; ++i) {
+ if (!(i % 50)) {
+ std::cout << "On iteration " << i << std::endl;
+ }
+ switch (rand() % 4) {
+ case 0:
+ get();
+ break;
+ case 1:
+ do_set();
+ break;
+ case 2:
+ get_next();
+ break;
+ case 3:
+ remove();
+ break;
+ }
+ }
+}
+
+class MapperVerifier {
+ PausyAsyncMap *driver;
+ boost::scoped_ptr< SnapMapper > mapper;
+ map<snapid_t, set<hobject_t> > snap_to_hobject;
+ map<hobject_t, set<snapid_t>> hobject_to_snap;
+ snapid_t next;
+ uint32_t mask;
+ uint32_t bits;
+ ceph::mutex lock = ceph::make_mutex("lock");
+public:
+
+ MapperVerifier(
+ PausyAsyncMap *driver,
+ uint32_t mask,
+ uint32_t bits)
+ : driver(driver),
+ mapper(new SnapMapper(g_ceph_context, driver, mask, bits, 0, shard_id_t(1))),
+ mask(mask), bits(bits) {}
+
+ hobject_t random_hobject() {
+ return hobject_t(
+ random_string(1+(rand() % 16)),
+ random_string(1+(rand() % 16)),
+ snapid_t(rand() % 1000),
+ (rand() & ((~0)<<bits)) | (mask & ~((~0)<<bits)),
+ 0, random_string(rand() % 16));
+ }
+
+ void choose_random_snaps(int num, set<snapid_t> *snaps) {
+ ceph_assert(snaps);
+ ceph_assert(!snap_to_hobject.empty());
+ for (int i = 0; i < num || snaps->empty(); ++i) {
+ snaps->insert(rand_choose(snap_to_hobject)->first);
+ }
+ }
+
+ void create_snap() {
+ snap_to_hobject[next];
+ ++next;
+ }
+
+ void create_object() {
+ std::lock_guard l{lock};
+ if (snap_to_hobject.empty())
+ return;
+ hobject_t obj;
+ do {
+ obj = random_hobject();
+ } while (hobject_to_snap.count(obj));
+
+ set<snapid_t> &snaps = hobject_to_snap[obj];
+ choose_random_snaps(1 + (rand() % 20), &snaps);
+ for (set<snapid_t>::iterator i = snaps.begin();
+ i != snaps.end();
+ ++i) {
+ map<snapid_t, set<hobject_t> >::iterator j = snap_to_hobject.find(*i);
+ ceph_assert(j != snap_to_hobject.end());
+ j->second.insert(obj);
+ }
+ {
+ PausyAsyncMap::Transaction t;
+ mapper->add_oid(obj, snaps, &t);
+ driver->submit(&t);
+ }
+ }
+
+ std::pair<std::string, ceph::buffer::list> to_raw(
+ const std::pair<snapid_t, hobject_t> &to_map) {
+ return mapper->to_raw(to_map);
+ }
+
+ std::string to_legacy_raw_key(
+ const std::pair<snapid_t, hobject_t> &to_map) {
+ return mapper->to_legacy_raw_key(to_map);
+ }
+
+ std::string to_raw_key(
+ const std::pair<snapid_t, hobject_t> &to_map) {
+ return mapper->to_raw_key(to_map);
+ }
+
+ void trim_snap() {
+ std::lock_guard l{lock};
+ if (snap_to_hobject.empty())
+ return;
+ map<snapid_t, set<hobject_t> >::iterator snap =
+ rand_choose(snap_to_hobject);
+ set<hobject_t> hobjects = snap->second;
+
+ vector<hobject_t> hoids;
+ while (mapper->get_next_objects_to_trim(
+ snap->first, rand() % 5 + 1, &hoids) == 0) {
+ for (auto &&hoid: hoids) {
+ ceph_assert(!hoid.is_max());
+ ceph_assert(hobjects.count(hoid));
+ hobjects.erase(hoid);
+
+ map<hobject_t, set<snapid_t>>::iterator j =
+ hobject_to_snap.find(hoid);
+ ceph_assert(j->second.count(snap->first));
+ set<snapid_t> old_snaps(j->second);
+ j->second.erase(snap->first);
+
+ {
+ PausyAsyncMap::Transaction t;
+ mapper->update_snaps(
+ hoid,
+ j->second,
+ &old_snaps,
+ &t);
+ driver->submit(&t);
+ }
+ if (j->second.empty()) {
+ hobject_to_snap.erase(j);
+ }
+ hoid = hobject_t::get_max();
+ }
+ hoids.clear();
+ }
+ ceph_assert(hobjects.empty());
+ snap_to_hobject.erase(snap);
+ }
+
+ void remove_oid() {
+ std::lock_guard l{lock};
+ if (hobject_to_snap.empty())
+ return;
+ map<hobject_t, set<snapid_t>>::iterator obj =
+ rand_choose(hobject_to_snap);
+ for (set<snapid_t>::iterator i = obj->second.begin();
+ i != obj->second.end();
+ ++i) {
+ map<snapid_t, set<hobject_t> >::iterator j =
+ snap_to_hobject.find(*i);
+ ceph_assert(j->second.count(obj->first));
+ j->second.erase(obj->first);
+ }
+ {
+ PausyAsyncMap::Transaction t;
+ mapper->remove_oid(
+ obj->first,
+ &t);
+ driver->submit(&t);
+ }
+ hobject_to_snap.erase(obj);
+ }
+
+ void check_oid() {
+ std::lock_guard l{lock};
+ if (hobject_to_snap.empty())
+ return;
+ map<hobject_t, set<snapid_t>>::iterator obj =
+ rand_choose(hobject_to_snap);
+ set<snapid_t> snaps;
+ int r = mapper->get_snaps(obj->first, &snaps);
+ ceph_assert(r == 0);
+ ASSERT_EQ(snaps, obj->second);
+ }
+};
+
+class SnapMapperTest : public ::testing::Test {
+protected:
+ boost::scoped_ptr< PausyAsyncMap > driver;
+ map<pg_t, std::shared_ptr<MapperVerifier> > mappers;
+ uint32_t pgnum;
+
+ void SetUp() override {
+ driver.reset(new PausyAsyncMap());
+ pgnum = 0;
+ }
+
+ void TearDown() override {
+ driver->stop();
+ mappers.clear();
+ driver.reset();
+ }
+
+ MapperVerifier &get_tester() {
+ //return *(mappers.begin()->second);
+ return *(rand_choose(mappers)->second);
+ }
+
+ void init(uint32_t to_set) {
+ pgnum = to_set;
+ for (uint32_t i = 0; i < pgnum; ++i) {
+ pg_t pgid(i, 0);
+ mappers[pgid].reset(
+ new MapperVerifier(
+ driver.get(),
+ i,
+ pgid.get_split_bits(pgnum)
+ )
+ );
+ }
+ }
+
+ void run() {
+ for (int i = 0; i < 5000; ++i) {
+ if (!(i % 50))
+ std::cout << i << std::endl;
+ switch (rand() % 5) {
+ case 0:
+ get_tester().create_snap();
+ break;
+ case 1:
+ get_tester().create_object();
+ break;
+ case 2:
+ get_tester().trim_snap();
+ break;
+ case 3:
+ get_tester().check_oid();
+ break;
+ case 4:
+ get_tester().remove_oid();
+ break;
+ }
+ }
+ }
+};
+
+TEST_F(SnapMapperTest, Simple) {
+ init(1);
+ get_tester().create_snap();
+ get_tester().create_object();
+ get_tester().trim_snap();
+}
+
+TEST_F(SnapMapperTest, More) {
+ init(1);
+ run();
+}
+
+TEST_F(SnapMapperTest, MultiPG) {
+ init(50);
+ run();
+}
+
+TEST_F(SnapMapperTest, LegacyKeyConvertion) {
+ init(1);
+ auto obj = get_tester().random_hobject();
+ snapid_t snapid = random() % 10;
+ auto snap_obj = make_pair(snapid, obj);
+ auto raw = get_tester().to_raw(snap_obj);
+ std::string old_key = get_tester().to_legacy_raw_key(snap_obj);
+ std::string converted_key =
+ SnapMapper::convert_legacy_key(old_key, raw.second);
+ std::string new_key = get_tester().to_raw_key(snap_obj);
+ std::cout << "Converted: " << old_key << "\nTo: " << converted_key
+ << "\nNew key: " << new_key << std::endl;
+ ASSERT_EQ(converted_key, new_key);
+}
+