From: Casey Bodley Date: Fri, 11 Dec 2015 21:24:34 +0000 (-0500) Subject: rgw: RGWPeriodHistory to track period history X-Git-Tag: v10.1.0~354^2~77 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=470595b0339bde8a20cc09c749eb6c7c84a7fdfb;p=ceph.git rgw: RGWPeriodHistory to track period history Signed-off-by: Casey Bodley --- diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 40f6263b7b8d..8c63c1c8468c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1170,6 +1170,7 @@ if(${WITH_RADOSGW}) rgw/rgw_sync.cc rgw/rgw_data_sync.cc rgw/rgw_dencoder.cc + rgw/rgw_period_history.cc rgw/rgw_period_pusher.cc rgw/rgw_realm_reloader.cc rgw/rgw_realm_watcher.cc diff --git a/src/rgw/Makefile.am b/src/rgw/Makefile.am index 5d30ff6a347d..f091a79a362d 100644 --- a/src/rgw/Makefile.am +++ b/src/rgw/Makefile.am @@ -59,6 +59,7 @@ librgw_la_SOURCES = \ rgw/rgw_keystone.cc \ rgw/rgw_quota.cc \ rgw/rgw_dencoder.cc \ + rgw/rgw_period_history.cc \ rgw/rgw_period_pusher.cc \ rgw/rgw_realm_reloader.cc \ rgw/rgw_realm_watcher.cc \ @@ -203,6 +204,7 @@ noinst_HEADERS += \ rgw/rgw_user.h \ rgw/rgw_bucket.h \ rgw/rgw_keystone.h \ + rgw/rgw_period_history.h \ rgw/rgw_period_pusher.h \ rgw/rgw_realm_reloader.h \ rgw/rgw_realm_watcher.h \ diff --git a/src/rgw/rgw_period_history.cc b/src/rgw/rgw_period_history.cc new file mode 100644 index 000000000000..fe5c79dec514 --- /dev/null +++ b/src/rgw/rgw_period_history.cc @@ -0,0 +1,249 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "rgw_period_history.h" +#include "rgw_rados.h" + +#define dout_subsys ceph_subsys_rgw + +#undef dout_prefix +#define dout_prefix (*_dout << "rgw period history: ") + +/// value comparison for avl_set +bool operator<(const RGWPeriodHistory::History& lhs, + const RGWPeriodHistory::History& rhs) +{ + return lhs.get_newest_epoch() < rhs.get_newest_epoch(); +} + +/// key-value comparison for avl_set +struct NewestEpochLess { + bool operator()(const RGWPeriodHistory::History& value, epoch_t key) const { + return value.get_newest_epoch() < key; + } +}; + + +RGWPeriodHistory::RGWPeriodHistory(CephContext* cct, Puller* puller, + const RGWPeriod& current_period) + : cct(cct), + puller(puller), + current_epoch(current_period.get_realm_epoch()) +{ + // copy the current period into a new history + auto history = new History; + history->periods.push_back(current_period); + + // insert as our current history + current_history = histories.insert(*history).first; +} + +RGWPeriodHistory::~RGWPeriodHistory() +{ + // clear the histories and delete each entry + histories.clear_and_dispose(std::default_delete{}); +} + +using Cursor = RGWPeriodHistory::Cursor; + +Cursor RGWPeriodHistory::get_current() const +{ + return Cursor{current_history, &mutex, current_epoch}; +} + +Cursor RGWPeriodHistory::attach(RGWPeriod&& period) +{ + const auto epoch = period.get_realm_epoch(); + + std::string predecessor_id; + for (;;) { + { + // hold the lock over insert, and while accessing the unsafe cursor + std::lock_guard lock(mutex); + + auto cursor = insert_locked(std::move(period)); + if (!cursor) { + return cursor; + } + if (current_history->contains(epoch)) { + break; // the history is complete + } + + // take the predecessor id of the most recent history + if (cursor.get_epoch() > current_epoch) { + predecessor_id = cursor.history->get_predecessor_id(); + } else { + predecessor_id = current_history->get_predecessor_id(); + } + } + + if (predecessor_id.empty()) { + lderr(cct) << "reached a period with an empty predecessor id" << dendl; + return Cursor{-EINVAL}; + } + + // pull the period outside of the lock + int r = puller->pull(predecessor_id, period); + if (r < 0) { + return Cursor{r}; + } + } + + // return a cursor to the requested period + return Cursor{current_history, &mutex, epoch}; +} + +Cursor RGWPeriodHistory::insert(RGWPeriod&& period) +{ + std::lock_guard lock(mutex); + + auto cursor = insert_locked(std::move(period)); + + if (cursor.get_error()) { + return cursor; + } + // we can only provide cursors that are safe to use outside of the mutex if + // they're within the current_history, because other histories can disappear + // in a merge. see merge() for the special handling of current_history + if (cursor.history == current_history) { + return cursor; + } + return Cursor{}; +} + +Cursor RGWPeriodHistory::lookup(epoch_t realm_epoch) +{ + if (current_history->contains(realm_epoch)) { + return Cursor{current_history, &mutex, realm_epoch}; + } + return Cursor{}; +} + +Cursor RGWPeriodHistory::insert_locked(RGWPeriod&& period) +{ + auto epoch = period.get_realm_epoch(); + + // find the first history whose newest epoch comes at or after this period + auto i = histories.lower_bound(epoch, NewestEpochLess{}); + + if (i == histories.end()) { + // epoch is past the end of our newest history + auto last = --Set::iterator{i}; // last = i - 1 + + if (epoch == last->get_newest_epoch() + 1) { + // insert at the back of the last history + last->periods.emplace_back(std::move(period)); + return Cursor{last, &mutex, epoch}; + } + + // create a new history for this period + auto history = new History; + history->periods.emplace_back(std::move(period)); + histories.insert(last, *history); + + i = Set::s_iterator_to(*history); + return Cursor{i, &mutex, epoch}; + } + + if (i->contains(epoch)) { + // already resident in this history + auto& existing = i->get(epoch); + // verify that the period ids match; otherwise we've forked the history + if (period.get_id() != existing.get_id()) { + lderr(cct) << "Got two different periods, " << period.get_id() + << " and " << existing.get_id() << ", with the same realm epoch " + << epoch << "! This indicates a fork in the period history." << dendl; + return Cursor{-EEXIST}; + } + // update the existing period if we got a newer period epoch + if (period.get_epoch() > existing.get_epoch()) { + existing = std::move(period); + } + return Cursor{i, &mutex, epoch}; + } + + if (epoch + 1 == i->get_oldest_epoch()) { + // insert at the front of this history + i->periods.emplace_front(std::move(period)); + + // try to merge with the previous history + if (i != histories.begin()) { + auto prev = --Set::iterator{i}; + if (epoch == prev->get_newest_epoch() + 1) { + i = merge(prev, i); + } + } + return Cursor{i, &mutex, epoch}; + } + + if (i != histories.begin()) { + auto prev = --Set::iterator{i}; + if (epoch == prev->get_newest_epoch() + 1) { + // insert at the back of the previous history + prev->periods.emplace_back(std::move(period)); + return Cursor{prev, &mutex, epoch}; + } + } + + // create a new history for this period + auto history = new History; + history->periods.emplace_back(std::move(period)); + histories.insert(i, *history); + + i = Set::s_iterator_to(*history); + return Cursor{i, &mutex, epoch}; +} + +RGWPeriodHistory::Set::iterator RGWPeriodHistory::merge(Set::iterator dst, + Set::iterator src) +{ + assert(dst->get_newest_epoch() + 1 == src->get_oldest_epoch()); + + // always merge into current_history + if (src == current_history) { + // move the periods from dst onto the front of src + src->periods.insert(src->periods.begin(), + std::make_move_iterator(dst->periods.begin()), + std::make_move_iterator(dst->periods.end())); + histories.erase_and_dispose(dst, std::default_delete{}); + return src; + } + + // move the periods from src onto the end of dst + dst->periods.insert(dst->periods.end(), + std::make_move_iterator(src->periods.begin()), + std::make_move_iterator(src->periods.end())); + histories.erase_and_dispose(src, std::default_delete{}); + return dst; +} + + +epoch_t RGWPeriodHistory::History::get_oldest_epoch() const +{ + return periods.front().get_realm_epoch(); +} + +epoch_t RGWPeriodHistory::History::get_newest_epoch() const +{ + return periods.back().get_realm_epoch(); +} + +bool RGWPeriodHistory::History::contains(epoch_t epoch) const +{ + return get_oldest_epoch() <= epoch && epoch <= get_newest_epoch(); +} + +RGWPeriod& RGWPeriodHistory::History::get(epoch_t epoch) +{ + return periods[epoch - get_oldest_epoch()]; +} + +const RGWPeriod& RGWPeriodHistory::History::get(epoch_t epoch) const +{ + return periods[epoch - get_oldest_epoch()]; +} + +const std::string& RGWPeriodHistory::History::get_predecessor_id() const +{ + return periods.front().get_predecessor(); +} diff --git a/src/rgw/rgw_period_history.h b/src/rgw/rgw_period_history.h new file mode 100644 index 000000000000..78049d037822 --- /dev/null +++ b/src/rgw/rgw_period_history.h @@ -0,0 +1,160 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef RGW_PERIOD_HISTORY_H +#define RGW_PERIOD_HISTORY_H + +#include +#include +#include +#include +#include "include/assert.h" +#include "include/types.h" + +namespace bi = boost::intrusive; + +class RGWPeriod; + +/** + * RGWPeriodHistory tracks the relative history of all inserted periods, + * coordinates the pulling of missing intermediate periods, and provides a + * Cursor object for traversing through the connected history. + */ +class RGWPeriodHistory final { + /// an ordered history of consecutive periods + struct History : public bi::avl_set_base_hook<> { + std::deque periods; + + epoch_t get_oldest_epoch() const; + epoch_t get_newest_epoch() const; + bool contains(epoch_t epoch) const; + RGWPeriod& get(epoch_t epoch); + const RGWPeriod& get(epoch_t epoch) const; + const std::string& get_predecessor_id() const; + }; + + // comparisons for avl_set ordering + friend bool operator<(const History& lhs, const History& rhs); + friend struct NewestEpochLess; + + /// an intrusive set of histories, ordered by their newest epoch. although + /// the newest epoch of each history is mutable, the ordering cannot change + /// because we prevent the histories from overlapping + using Set = bi::avl_set; + + public: + /** + * Puller is a synchronous interface for pulling periods from the master + * zone. The abstraction exists mainly to support unit testing. + */ + class Puller { + public: + virtual ~Puller() = default; + + virtual int pull(const std::string& period_id, RGWPeriod& period) = 0; + }; + + RGWPeriodHistory(CephContext* cct, Puller* puller, + const RGWPeriod& current_period); + ~RGWPeriodHistory(); + + /** + * Cursor tracks a position in the period history and allows forward and + * backward traversal. Only periods that are fully connected to the + * current_period are reachable via a Cursor, because other histories are + * temporary and can be merged away. Cursors to periods in disjoint + * histories, as provided by insert() or lookup(), are therefore invalid and + * their operator bool() will return false. + */ + class Cursor final { + public: + Cursor() = default; + + int get_error() const { return error; } + + /// return false for a default-constructed Cursor + operator bool() const { return history != Set::const_iterator{}; } + + epoch_t get_epoch() const { return epoch; } + const RGWPeriod& get_period() const; + + bool has_prev() const; + bool has_next() const; + + void prev() { epoch--; } + void next() { epoch++; } + + private: + // private constructors for RGWPeriodHistory + friend class RGWPeriodHistory; + + explicit Cursor(int error) : error(error) {} + Cursor(Set::const_iterator history, std::mutex* mutex, epoch_t epoch) + : history(history), mutex(mutex), epoch(epoch) {} + + int error{0}; + Set::const_iterator history; + std::mutex* mutex{nullptr}; + epoch_t epoch{0}; //< realm epoch of cursor position + }; + + /// return a cursor to the current period + Cursor get_current() const; + + /// build up a connected period history that covers the span between + /// current_period and the given period, reading predecessor periods or + /// fetching them from the master as necessary. returns a cursor at the + /// given period that can be used to traverse the current_history + Cursor attach(RGWPeriod&& period); + + /// insert the given period into an existing history, or create a new + /// unconnected history. similar to attach(), but it doesn't try to fetch + /// missing periods. returns a cursor to the inserted period iff it's in + /// the current_history + Cursor insert(RGWPeriod&& period); + + /// search for a period by realm epoch, returning a valid Cursor iff it's in + /// the current_history + Cursor lookup(epoch_t realm_epoch); + + private: + /// insert the given period into the period history, creating new unconnected + /// histories or merging existing histories as necessary. expects the caller + /// to hold a lock on mutex. returns a valid cursor regardless of whether it + /// ends up in current_history, though cursors in other histories are only + /// valid within the context of the lock + Cursor insert_locked(RGWPeriod&& period); + + /// merge the periods from the src history onto the end of the dst history, + /// and return an iterator to the merged history + Set::iterator merge(Set::iterator dst, Set::iterator src); + + + CephContext *const cct; + Puller *const puller; //< interface for pulling missing periods + const epoch_t current_epoch; //< realm_epoch of realm's current period + + mutable std::mutex mutex; //< protects the histories + + /// set of disjoint histories that are missing intermediate periods needed to + /// connect them together + Set histories; + + /// iterator to the history that contains the realm's current period + Set::const_iterator current_history; +}; + +inline const RGWPeriod& RGWPeriodHistory::Cursor::get_period() const { + std::lock_guard lock(*mutex); + return history->get(epoch); +} +inline bool RGWPeriodHistory::Cursor::has_prev() const { + std::lock_guard lock(*mutex); + return epoch > history->get_oldest_epoch(); +} +inline bool RGWPeriodHistory::Cursor::has_next() const { + std::lock_guard lock(*mutex); + return epoch < history->get_newest_epoch(); +} + +#endif // RGW_PERIOD_HISTORY_H diff --git a/src/rgw/rgw_rados.h b/src/rgw/rgw_rados.h index c35e01a5e23d..2e737a02d26a 100644 --- a/src/rgw/rgw_rados.h +++ b/src/rgw/rgw_rados.h @@ -1413,6 +1413,7 @@ public: period_map.id = id; } void set_epoch(epoch_t epoch) { this->epoch = epoch; } + void set_realm_epoch(epoch_t epoch) { realm_epoch = epoch; } void set_predecessor(const string& predecessor) { diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 0b06e77ead2d..e6298108efab 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -1504,6 +1504,7 @@ add_subdirectory(erasure-code EXCLUDE_FROM_ALL) #make check ends here if(${WITH_RADOSGW}) + add_subdirectory(rgw) # test_cors set(test_cors_srcs test_cors.cc) add_executable(test_cors diff --git a/src/test/Makefile-client.am b/src/test/Makefile-client.am index dcb1f991961a..d1b8d8d369a4 100644 --- a/src/test/Makefile-client.am +++ b/src/test/Makefile-client.am @@ -644,6 +644,13 @@ ceph_test_rgw_manifest_LDADD = \ ceph_test_rgw_manifest_CXXFLAGS = $(UNITTEST_CXXFLAGS) bin_DEBUGPROGRAMS += ceph_test_rgw_manifest +ceph_test_rgw_period_history_SOURCES = test/rgw/test_rgw_period_history.cc +ceph_test_rgw_period_history_LDADD = \ + $(LIBRADOS) $(LIBRGW) $(LIBRGW_DEPS) $(CEPH_GLOBAL) \ + $(UNITTEST_LDADD) $(CRYPTO_LIBS) -lcurl -lexpat +ceph_test_rgw_period_history_CXXFLAGS = $(UNITTEST_CXXFLAGS) +bin_DEBUGPROGRAMS += ceph_test_rgw_period_history + ceph_test_rgw_obj_SOURCES = test/rgw/test_rgw_obj.cc ceph_test_rgw_obj_LDADD = \ $(LIBRADOS) $(LIBRGW) $(LIBRGW_DEPS) $(CEPH_GLOBAL) \ diff --git a/src/test/rgw/CMakeLists.txt b/src/test/rgw/CMakeLists.txt new file mode 100644 index 000000000000..a45c29ceebbf --- /dev/null +++ b/src/test/rgw/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(test_rgw_period_history EXCLUDE_FROM_ALL test_rgw_period_history.cc) +target_link_libraries(test_rgw_period_history rgw_a ${UNITTEST_LIBS}) +set_target_properties(test_rgw_period_history PROPERTIES COMPILE_FLAGS ${UNITTEST_CXX_FLAGS}) +add_test(RGWPeriodHistory test_rgw_period_history) +add_dependencies(check test_rgw_period_history) diff --git a/src/test/rgw/test_rgw_period_history.cc b/src/test/rgw/test_rgw_period_history.cc new file mode 100644 index 000000000000..320f550dfab0 --- /dev/null +++ b/src/test/rgw/test_rgw_period_history.cc @@ -0,0 +1,330 @@ +// -*- 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) 2015 Red Hat + * + * 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 "rgw/rgw_period_history.h" +#include "rgw/rgw_rados.h" +#include "global/global_init.h" +#include "common/ceph_argparse.h" +#include +#include + +namespace { + +// construct a period with the given fields +RGWPeriod make_period(const std::string& id, epoch_t realm_epoch, + const std::string& predecessor) +{ + RGWPeriod period(id); + period.set_realm_epoch(realm_epoch); + period.set_predecessor(predecessor); + return period; +} + +const auto current_period = make_period("5", 5, "4"); + +// mock puller that throws an exception if it's called +struct ErrorPuller : public RGWPeriodHistory::Puller { + int pull(const std::string& id, RGWPeriod& period) override { + throw std::runtime_error("unexpected call to pull"); + } +}; +ErrorPuller puller; // default puller + +// mock puller that records the period ids requested and returns an error +using Ids = std::vector; +class RecordingPuller : public RGWPeriodHistory::Puller { + const int error; + public: + RecordingPuller(int error) : error(error) {} + Ids ids; + int pull(const std::string& id, RGWPeriod& period) override { + ids.push_back(id); + return error; + } +}; + +// mock puller that returns a fake period by parsing the period id +struct NumericPuller : public RGWPeriodHistory::Puller { + int pull(const std::string& id, RGWPeriod& period) override { + // relies on numeric period ids to divine the realm_epoch + auto realm_epoch = boost::lexical_cast(id); + auto predecessor = boost::lexical_cast(realm_epoch-1); + period = make_period(id, realm_epoch, predecessor); + return 0; + } +}; + +} // anonymous namespace + +// for ASSERT_EQ() +bool operator==(const RGWPeriod& lhs, const RGWPeriod& rhs) +{ + return lhs.get_id() == rhs.get_id() + && lhs.get_realm_epoch() == rhs.get_realm_epoch(); +} + +TEST(PeriodHistory, InsertBefore) +{ + RGWPeriodHistory history(g_ceph_context, &puller, current_period); + + // inserting right before current_period 5 will attach to history + auto c = history.insert(make_period("4", 4, "3")); + ASSERT_TRUE(c); + ASSERT_FALSE(c.has_prev()); + ASSERT_TRUE(c.has_next()); + + // cursor can traverse forward to current_period + c.next(); + ASSERT_EQ(5u, c.get_epoch()); + ASSERT_EQ(current_period, c.get_period()); +} + +TEST(PeriodHistory, InsertAfter) +{ + RGWPeriodHistory history(g_ceph_context, &puller, current_period); + + // inserting right after current_period 5 will attach to history + auto c = history.insert(make_period("6", 6, "5")); + ASSERT_TRUE(c); + ASSERT_TRUE(c.has_prev()); + ASSERT_FALSE(c.has_next()); + + // cursor can traverse back to current_period + c.prev(); + ASSERT_EQ(5u, c.get_epoch()); + ASSERT_EQ(current_period, c.get_period()); +} + +TEST(PeriodHistory, InsertWayBefore) +{ + RGWPeriodHistory history(g_ceph_context, &puller, current_period); + + // inserting way before current_period 5 will not attach to history + auto c = history.insert(make_period("1", 1, "")); + ASSERT_FALSE(c); + ASSERT_EQ(0, c.get_error()); +} + +TEST(PeriodHistory, InsertWayAfter) +{ + RGWPeriodHistory history(g_ceph_context, &puller, current_period); + + // inserting way after current_period 5 will not attach to history + auto c = history.insert(make_period("9", 9, "8")); + ASSERT_FALSE(c); + ASSERT_EQ(0, c.get_error()); +} + +TEST(PeriodHistory, PullPredecessorsBeforeCurrent) +{ + RecordingPuller puller{-EFAULT}; + RGWPeriodHistory history(g_ceph_context, &puller, current_period); + + // create a disjoint history at 1 and verify that periods are requested + // backwards from current_period + auto c1 = history.attach(make_period("1", 1, "")); + ASSERT_FALSE(c1); + ASSERT_EQ(-EFAULT, c1.get_error()); + ASSERT_EQ(Ids{"4"}, puller.ids); + + auto c4 = history.insert(make_period("4", 4, "3")); + ASSERT_TRUE(c4); + + c1 = history.attach(make_period("1", 1, "")); + ASSERT_FALSE(c1); + ASSERT_EQ(-EFAULT, c1.get_error()); + ASSERT_EQ(Ids({"4", "3"}), puller.ids); + + auto c3 = history.insert(make_period("3", 3, "2")); + ASSERT_TRUE(c3); + + c1 = history.attach(make_period("1", 1, "")); + ASSERT_FALSE(c1); + ASSERT_EQ(-EFAULT, c1.get_error()); + ASSERT_EQ(Ids({"4", "3", "2"}), puller.ids); + + auto c2 = history.insert(make_period("2", 2, "1")); + ASSERT_TRUE(c2); + + c1 = history.attach(make_period("1", 1, "")); + ASSERT_TRUE(c1); + ASSERT_EQ(Ids({"4", "3", "2"}), puller.ids); +} + +TEST(PeriodHistory, PullPredecessorsAfterCurrent) +{ + RecordingPuller puller{-EFAULT}; + RGWPeriodHistory history(g_ceph_context, &puller, current_period); + + // create a disjoint history at 9 and verify that periods are requested + // backwards down to current_period + auto c9 = history.attach(make_period("9", 9, "8")); + ASSERT_FALSE(c9); + ASSERT_EQ(-EFAULT, c9.get_error()); + ASSERT_EQ(Ids{"8"}, puller.ids); + + auto c8 = history.attach(make_period("8", 8, "7")); + ASSERT_FALSE(c8); + ASSERT_EQ(-EFAULT, c8.get_error()); + ASSERT_EQ(Ids({"8", "7"}), puller.ids); + + auto c7 = history.attach(make_period("7", 7, "6")); + ASSERT_FALSE(c7); + ASSERT_EQ(-EFAULT, c7.get_error()); + ASSERT_EQ(Ids({"8", "7", "6"}), puller.ids); + + auto c6 = history.attach(make_period("6", 6, "5")); + ASSERT_TRUE(c6); + ASSERT_EQ(Ids({"8", "7", "6"}), puller.ids); +} + +TEST(PeriodHistory, MergeBeforeCurrent) +{ + RGWPeriodHistory history(g_ceph_context, &puller, current_period); + + auto c = history.get_current(); + ASSERT_FALSE(c.has_prev()); + + // create a disjoint history at 3 + auto c3 = history.insert(make_period("3", 3, "2")); + ASSERT_FALSE(c3); + + // insert the missing period to merge 3 and 5 + auto c4 = history.insert(make_period("4", 4, "3")); + ASSERT_TRUE(c4); + ASSERT_TRUE(c4.has_prev()); + ASSERT_TRUE(c4.has_next()); + + // verify that the merge didn't destroy the original cursor's history + ASSERT_EQ(current_period, c.get_period()); + ASSERT_TRUE(c.has_prev()); +} + +TEST(PeriodHistory, MergeAfterCurrent) +{ + RGWPeriodHistory history(g_ceph_context, &puller, current_period); + + auto c = history.get_current(); + ASSERT_FALSE(c.has_next()); + + // create a disjoint history at 7 + auto c7 = history.insert(make_period("7", 7, "6")); + ASSERT_FALSE(c7); + + // insert the missing period to merge 5 and 7 + auto c6 = history.insert(make_period("6", 6, "5")); + ASSERT_TRUE(c6); + ASSERT_TRUE(c6.has_prev()); + ASSERT_TRUE(c6.has_next()); + + // verify that the merge didn't destroy the original cursor's history + ASSERT_EQ(current_period, c.get_period()); + ASSERT_TRUE(c.has_next()); +} + +TEST(PeriodHistory, MergeWithoutCurrent) +{ + RGWPeriodHistory history(g_ceph_context, &puller, current_period); + + // create a disjoint history at 7 + auto c7 = history.insert(make_period("7", 7, "6")); + ASSERT_FALSE(c7); + + // create a disjoint history at 9 + auto c9 = history.insert(make_period("9", 9, "8")); + ASSERT_FALSE(c9); + + // insert the missing period to merge 7 and 9 + auto c8 = history.insert(make_period("8", 8, "7")); + ASSERT_FALSE(c8); // not connected to current_period yet + + // insert the missing period to merge 5 and 7-9 + auto c = history.insert(make_period("6", 6, "5")); + ASSERT_TRUE(c); + ASSERT_TRUE(c.has_next()); + + // verify that we merged all periods from 5-9 + c.next(); + ASSERT_EQ(7u, c.get_epoch()); + ASSERT_TRUE(c.has_next()); + c.next(); + ASSERT_EQ(8u, c.get_epoch()); + ASSERT_TRUE(c.has_next()); + c.next(); + ASSERT_EQ(9u, c.get_epoch()); + ASSERT_FALSE(c.has_next()); +} + +TEST(PeriodHistory, AttachBefore) +{ + NumericPuller puller; + RGWPeriodHistory history(g_ceph_context, &puller, current_period); + + auto c1 = history.attach(make_period("1", 1, "")); + ASSERT_TRUE(c1); + + // verify that we pulled and merged all periods from 1-5 + auto c = history.get_current(); + ASSERT_TRUE(c); + ASSERT_TRUE(c.has_prev()); + c.prev(); + ASSERT_EQ(4u, c.get_epoch()); + ASSERT_TRUE(c.has_prev()); + c.prev(); + ASSERT_EQ(3u, c.get_epoch()); + ASSERT_TRUE(c.has_prev()); + c.prev(); + ASSERT_EQ(2u, c.get_epoch()); + ASSERT_TRUE(c.has_prev()); + c.prev(); + ASSERT_EQ(1u, c.get_epoch()); + ASSERT_FALSE(c.has_prev()); +} + +TEST(PeriodHistory, AttachAfter) +{ + NumericPuller puller; + RGWPeriodHistory history(g_ceph_context, &puller, current_period); + + auto c9 = history.attach(make_period("9", 9, "8")); + ASSERT_TRUE(c9); + + // verify that we pulled and merged all periods from 5-9 + auto c = history.get_current(); + ASSERT_TRUE(c); + ASSERT_TRUE(c.has_next()); + c.next(); + ASSERT_EQ(6u, c.get_epoch()); + ASSERT_TRUE(c.has_next()); + c.next(); + ASSERT_EQ(7u, c.get_epoch()); + ASSERT_TRUE(c.has_next()); + c.next(); + ASSERT_EQ(8u, c.get_epoch()); + ASSERT_TRUE(c.has_next()); + c.next(); + ASSERT_EQ(9u, c.get_epoch()); + ASSERT_FALSE(c.has_next()); +} + +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); + + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}