// -*- 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); auto cct = 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(); }