From: Loic Dachary Date: Fri, 7 Jun 2013 13:51:28 +0000 (+0200) Subject: unit tests for PGLog::merge_log X-Git-Tag: v0.65~120^2~1 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=04e89a4012f81a12cce0324462281ff6c0778ed8;p=ceph.git unit tests for PGLog::merge_log The tests covers 100% of the LOC of merge_log. It is broken down in 7 cases to enumerate all the situations it must address. Each case is isolated in a independant code block where the conditions are reproduced. Where possible and sensible to read, a code block covers as much lines as possible. For instance: The log entry (1,3) deletes the object x9 but the olog entry (2,3) modifies it and is authoritative : the log entry (1,3) is divergent. is the only test case covering a dozen "if" statements and half a dozen "while/for" loops. It covers all the lines but it would be useful to create others scenarii in the future. Each test is made of a comment describing the test case, the definition of the data structures to create the desired conditons, a sequence of EXPECT_* checking that they are met, a single call to merge_log and another sequence of EXPECT_* ( ordered to be easy to compare with the first sequence ) checking all the desired side effects. The TestPGLog.cc file was untabified to improve the display of ascii art when it is output as part of a diff. http://tracker.ceph.com/issues/5213 refs #5213 Signed-off-by: Loic Dachary --- diff --git a/src/test/osd/TestPGLog.cc b/src/test/osd/TestPGLog.cc index 10d32db22e2e9..3cb90e94a1d46 100644 --- a/src/test/osd/TestPGLog.cc +++ b/src/test/osd/TestPGLog.cc @@ -70,7 +70,7 @@ TEST_F(PGLogTest, merge_old_entry) { // a clone with no non-divergent log entry is deleted { clear(); - + ObjectStore::Transaction t; pg_log_entry_t oe; pg_info_t info; @@ -106,7 +106,7 @@ TEST_F(PGLogTest, merge_old_entry) { // given in argument) have the same version : do nothing and return true. { clear(); - + ObjectStore::Transaction t; pg_log_entry_t oe; pg_info_t info; @@ -134,11 +134,11 @@ TEST_F(PGLogTest, merge_old_entry) { } // the new entry (from the logs) has a version that is higher than - // the old entry (from the log entry given in argument) : do + // the old entry (from the log entry given in argument) : do // nothing and return false { clear(); - + ObjectStore::Transaction t; pg_info_t info; list remove_snap; @@ -203,7 +203,7 @@ TEST_F(PGLogTest, merge_old_entry) { // old and new are delete : do nothing and return false { clear(); - + ObjectStore::Transaction t; pg_log_entry_t oe; pg_info_t info; @@ -237,7 +237,7 @@ TEST_F(PGLogTest, merge_old_entry) { // the new entry (from the logs) has a version that is lower than // the old entry (from the log entry given in argument) and - // new is update : + // new is update : // if the object is not already in missing, add it // if the object is already in missing, revise the version it needs // return false @@ -247,7 +247,7 @@ TEST_F(PGLogTest, merge_old_entry) { __s32 oe_op = ops[i]; clear(); - + ObjectStore::Transaction t; pg_log_entry_t oe; pg_info_t info; @@ -272,20 +272,20 @@ TEST_F(PGLogTest, merge_old_entry) { eversion_t old_version(0, 0); // if the object is not already in missing, add it { - EXPECT_FALSE(merge_old_entry(t, oe, info, remove_snap, dirty_log)); + EXPECT_FALSE(merge_old_entry(t, oe, info, remove_snap, dirty_log)); - EXPECT_TRUE(missing.is_missing(ne.soid, ne.version)); - EXPECT_FALSE(missing.is_missing(ne.soid, old_version)); + EXPECT_TRUE(missing.is_missing(ne.soid, ne.version)); + EXPECT_FALSE(missing.is_missing(ne.soid, old_version)); } // if the object is already in missing, revise the version it needs { - missing.revise_need(ne.soid, old_version); - EXPECT_TRUE(missing.is_missing(ne.soid, old_version)); + missing.revise_need(ne.soid, old_version); + EXPECT_TRUE(missing.is_missing(ne.soid, old_version)); - EXPECT_FALSE(merge_old_entry(t, oe, info, remove_snap, dirty_log)); + EXPECT_FALSE(merge_old_entry(t, oe, info, remove_snap, dirty_log)); - EXPECT_TRUE(missing.is_missing(ne.soid, ne.version)); - EXPECT_FALSE(missing.is_missing(ne.soid, old_version)); + EXPECT_TRUE(missing.is_missing(ne.soid, ne.version)); + EXPECT_FALSE(missing.is_missing(ne.soid, old_version)); } EXPECT_FALSE(dirty_log); @@ -299,11 +299,11 @@ TEST_F(PGLogTest, merge_old_entry) { // the new entry (from the logs) has a version that is lower than // the old entry (from the log entry given in argument) and - // old is update and new is DELETE : + // old is update and new is DELETE : // if the object is in missing, it is removed { clear(); - + ObjectStore::Transaction t; pg_log_entry_t oe; pg_info_t info; @@ -342,7 +342,7 @@ TEST_F(PGLogTest, merge_old_entry) { // do nothing and return false { clear(); - + ObjectStore::Transaction t; pg_log_entry_t oe; pg_info_t info; @@ -374,14 +374,14 @@ TEST_F(PGLogTest, merge_old_entry) { // the old entry (from the log entry given in argument) is not a CLONE and // the old entry (from the log entry given in argument) is not a DELETE and // the old entry prior_version is lower than the tail of the log : - // add the old object to the remove_snap list and + // add the old object to the remove_snap list and // add the old object to ondisklog divergent priors and // set dirty_log to true and // add or update the prior_version of the object to missing and // return false { clear(); - + ObjectStore::Transaction t; pg_log_entry_t oe; pg_info_t info; @@ -420,7 +420,7 @@ TEST_F(PGLogTest, merge_old_entry) { // return false { clear(); - + ObjectStore::Transaction t; pg_log_entry_t oe; pg_info_t info; @@ -457,7 +457,7 @@ TEST_F(PGLogTest, merge_old_entry) { // return false { clear(); - + ObjectStore::Transaction t; pg_log_entry_t oe; pg_info_t info; @@ -468,7 +468,7 @@ TEST_F(PGLogTest, merge_old_entry) { oe.soid.hash = 1; oe.op = pg_log_entry_t::DELETE; oe.prior_version = eversion_t(); - + missing.add(oe.soid, eversion_t(1,1), eversion_t()); EXPECT_FALSE(dirty_log); @@ -492,12 +492,12 @@ TEST_F(PGLogTest, merge_old_entry) { // the old entry (from the log entry given in argument) is not a CLONE and // the old entry (from the log entry given in argument) is not a DELETE and // the old entry prior_version is eversion_t() : - // add the old object to the remove_snap list and + // add the old object to the remove_snap list and // remove the prior_version of the object from missing, if any and // return false { clear(); - + ObjectStore::Transaction t; pg_log_entry_t oe; pg_info_t info; @@ -508,7 +508,7 @@ TEST_F(PGLogTest, merge_old_entry) { oe.soid.hash = 1; oe.op = pg_log_entry_t::MODIFY; oe.prior_version = eversion_t(); - + missing.add(oe.soid, eversion_t(1,1), eversion_t()); EXPECT_FALSE(dirty_log); @@ -530,6 +530,514 @@ TEST_F(PGLogTest, merge_old_entry) { } +TEST_F(PGLogTest, merge_log) { + // head and tail match, last_backfill is set: + // noop + { + clear(); + + ObjectStore::Transaction t; + pg_log_t olog; + pg_info_t oinfo; + int fromosd = -1; + pg_info_t info; + list remove_snap; + bool dirty_log = false; + bool dirty_info = false; + bool dirty_big_info = false; + + hobject_t last_backfill(object_t("oname"), string("key"), 1, 234, 1); + info.last_backfill = last_backfill; + eversion_t stat_version(10, 1); + info.stats.version = stat_version; + log.tail = olog.tail = eversion_t(1, 1); + log.head = olog.head = eversion_t(2, 1); + + EXPECT_FALSE(missing.have_missing()); + EXPECT_EQ(0U, log.log.size()); + EXPECT_EQ(0U, ondisklog.length()); + EXPECT_EQ(stat_version, info.stats.version); + EXPECT_TRUE(remove_snap.empty()); + EXPECT_TRUE(t.empty()); + EXPECT_EQ(last_backfill, info.last_backfill); + EXPECT_TRUE(info.purged_snaps.empty()); + EXPECT_FALSE(dirty_log); + EXPECT_FALSE(dirty_info); + EXPECT_FALSE(dirty_big_info); + + merge_log(t, oinfo, olog, fromosd, info, remove_snap, + dirty_log, dirty_info, dirty_big_info); + + EXPECT_FALSE(missing.have_missing()); + EXPECT_EQ(0U, log.log.size()); + EXPECT_EQ(0U, ondisklog.length()); + EXPECT_EQ(stat_version, info.stats.version); + EXPECT_TRUE(remove_snap.empty()); + EXPECT_TRUE(t.empty()); + EXPECT_TRUE(info.purged_snaps.empty()); + EXPECT_FALSE(dirty_log); + EXPECT_FALSE(dirty_info); + EXPECT_FALSE(dirty_big_info); + } + + // head and tail match, last_backfill is not set: info.stats is + // copied from oinfo.stats but info.stats.reported is guaranteed to + // never be replaced by a lower version + { + clear(); + + ObjectStore::Transaction t; + pg_log_t olog; + pg_info_t oinfo; + int fromosd = -1; + pg_info_t info; + list remove_snap; + bool dirty_log = false; + bool dirty_info = false; + bool dirty_big_info = false; + + eversion_t stat_version(10, 1); + oinfo.stats.version = stat_version; + eversion_t stat_reported(10, 1); + info.stats.reported = stat_reported; + oinfo.stats.reported = eversion_t(1, 1); + log.tail = olog.tail = eversion_t(1, 1); + log.head = olog.head = eversion_t(2, 1); + + EXPECT_FALSE(missing.have_missing()); + EXPECT_EQ(0U, log.log.size()); + EXPECT_EQ(0U, ondisklog.length()); + EXPECT_EQ(eversion_t(), info.stats.version); + EXPECT_EQ(stat_reported, info.stats.reported); + EXPECT_TRUE(remove_snap.empty()); + EXPECT_TRUE(t.empty()); + EXPECT_TRUE(info.last_backfill.is_max()); + EXPECT_TRUE(info.purged_snaps.empty()); + EXPECT_FALSE(dirty_log); + EXPECT_FALSE(dirty_info); + EXPECT_FALSE(dirty_big_info); + + merge_log(t, oinfo, olog, fromosd, info, remove_snap, + dirty_log, dirty_info, dirty_big_info); + + EXPECT_FALSE(missing.have_missing()); + EXPECT_EQ(0U, log.log.size()); + EXPECT_EQ(0U, ondisklog.length()); + EXPECT_EQ(stat_version, info.stats.version); + EXPECT_EQ(stat_reported, info.stats.reported); + EXPECT_TRUE(remove_snap.empty()); + EXPECT_TRUE(t.empty()); + EXPECT_TRUE(info.purged_snaps.empty()); + EXPECT_FALSE(dirty_log); + EXPECT_FALSE(dirty_info); + EXPECT_FALSE(dirty_big_info); + } + + /* Before + +--------------------------+ + | log olog | + +--------+-------+---------+ + | |object | | + |version | hash | version | + | | | | + | | x5 | (1,1) < tail + | | | | + | | | | + tail > (1,4) | x7 | | + | | | | + | | | | + head > (1,5) | x9 | (1,5) < head + | | | | + | | | | + +--------+-------+---------+ + + After + +----------------- + | log | + +--------+-------+ + | |object | + |version | hash | + | | | + tail > (1,1) | x5 | + | | | + | | | + | (1,4) | x7 | + | | | + | | | + head > (1,5) | x9 | + | | | + | | | + +--------+-------+ + */ + { + clear(); + + ObjectStore::Transaction t; + pg_log_t olog; + pg_info_t oinfo; + int fromosd = -1; + pg_info_t info; + list remove_snap; + bool dirty_log = false; + bool dirty_info = false; + bool dirty_big_info = false; + + { + pg_log_entry_t e; + + e.version = eversion_t(1, 4); + e.soid.hash = 0x5; + log.tail = e.version; + log.log.push_back(e); + e.version = eversion_t(1, 5); + e.soid.hash = 0x9; + log.log.push_back(e); + log.head = e.version; + log.index(); + + info.last_update = log.head; + + e.version = eversion_t(1, 1); + e.soid.hash = 0x5; + olog.tail = e.version; + olog.log.push_back(e); + e.version = eversion_t(1, 5); + e.soid.hash = 0x9; + olog.log.push_back(e); + olog.head = e.version; + } + + hobject_t last_backfill(object_t("oname"), string("key"), 1, 234, 1); + info.last_backfill = last_backfill; + eversion_t stat_version(10, 1); + info.stats.version = stat_version; + + EXPECT_FALSE(missing.have_missing()); + EXPECT_EQ(2U, log.log.size()); + EXPECT_EQ(0U, ondisklog.length()); + EXPECT_EQ(stat_version, info.stats.version); + EXPECT_TRUE(remove_snap.empty()); + EXPECT_TRUE(t.empty()); + EXPECT_EQ(last_backfill, info.last_backfill); + EXPECT_TRUE(info.purged_snaps.empty()); + EXPECT_FALSE(dirty_log); + EXPECT_FALSE(dirty_info); + EXPECT_FALSE(dirty_big_info); + + merge_log(t, oinfo, olog, fromosd, info, remove_snap, + dirty_log, dirty_info, dirty_big_info); + + EXPECT_FALSE(missing.have_missing()); + EXPECT_EQ(3U, log.log.size()); + EXPECT_EQ(0U, ondisklog.length()); + EXPECT_EQ(stat_version, info.stats.version); + EXPECT_TRUE(remove_snap.empty()); + EXPECT_TRUE(t.empty()); + EXPECT_TRUE(info.purged_snaps.empty()); + EXPECT_TRUE(dirty_log); + EXPECT_TRUE(dirty_info); + EXPECT_TRUE(dirty_big_info); + } + + /* +--------------------------+ + | log olog | + +--------+-------+---------+ + | |object | | + |version | hash | version | + | | | | + tail > (1,1) | x5 | (1,1) < tail + | | | | + | | | | + | (1,2) | x3 | (1,2) < lower_bound + | | | | + | | | | + head > (1,3) | x9 | | + | DELETE | | | + | | | | + | | x9 | (2,3) | + | | | MODIFY | + | | | | + | | x7 | (2,4) < head + | | | DELETE | + +--------+-------+---------+ + + The log entry (1,3) deletes the object x9 but the olog entry (2,3) modifies + it and is authoritative : the log entry (1,3) is divergent. + + */ + { + clear(); + + ObjectStore::Transaction t; + pg_log_t olog; + pg_info_t oinfo; + int fromosd = -1; + pg_info_t info; + list remove_snap; + bool dirty_log = false; + bool dirty_info = false; + bool dirty_big_info = false; + + hobject_t divergent_object; + + { + pg_log_entry_t e; + + e.version = eversion_t(1, 1); + e.soid.hash = 0x5; + log.tail = e.version; + log.log.push_back(e); + e.version = eversion_t(1, 2); + e.soid.hash = 0x3; + log.log.push_back(e); + e.version = eversion_t(1,3); + e.soid.hash = 0x9; + divergent_object = e.soid; + e.op = pg_log_entry_t::DELETE; + log.log.push_back(e); + log.head = e.version; + log.index(); + + info.last_update = log.head; + + e.version = eversion_t(1, 1); + e.soid.hash = 0x5; + olog.tail = e.version; + olog.log.push_back(e); + e.version = eversion_t(1, 2); + e.soid.hash = 0x3; + olog.log.push_back(e); + e.version = eversion_t(2, 3); + e.soid.hash = 0x9; + e.op = pg_log_entry_t::MODIFY; + olog.log.push_back(e); + e.version = eversion_t(2, 4); + e.soid.hash = 0x7; + e.op = pg_log_entry_t::DELETE; + olog.log.push_back(e); + olog.head = e.version; + } + + snapid_t purged_snap(1); + { + oinfo.last_update = olog.head; + oinfo.purged_snaps.insert(purged_snap); + } + + EXPECT_FALSE(missing.have_missing()); + EXPECT_EQ(1U, log.objects.count(divergent_object)); + EXPECT_EQ(3U, log.log.size()); + EXPECT_EQ(0U, ondisklog.length()); + EXPECT_TRUE(remove_snap.empty()); + EXPECT_TRUE(t.empty()); + EXPECT_EQ(log.head, info.last_update); + EXPECT_TRUE(info.purged_snaps.empty()); + EXPECT_FALSE(dirty_log); + EXPECT_FALSE(dirty_info); + EXPECT_FALSE(dirty_big_info); + + merge_log(t, oinfo, olog, fromosd, info, remove_snap, + dirty_log, dirty_info, dirty_big_info); + + /* When the divergent entry is a DELETE and the authoritative + entry is a MODIFY, the object will be added to missing : it is + a verifiable side effect proving the entry was identified + to be divergent. + */ + EXPECT_TRUE(missing.is_missing(divergent_object)); + EXPECT_EQ(1U, log.objects.count(divergent_object)); + EXPECT_EQ(4U, log.log.size()); + EXPECT_EQ(0U, ondisklog.length()); + /* DELETE entries from olog that are appended to the hed of the + log are also added to remove_snap. + */ + EXPECT_EQ(0x7U, remove_snap.front().hash); + EXPECT_TRUE(t.empty()); + EXPECT_EQ(log.head, info.last_update); + EXPECT_TRUE(info.purged_snaps.contains(purged_snap)); + EXPECT_TRUE(dirty_log); + EXPECT_TRUE(dirty_info); + EXPECT_TRUE(dirty_big_info); + } + + /* +--------------------------+ + | log olog | + +--------+-------+---------+ + | |object | | + |version | hash | version | + | | | | + tail > (1,1) | x5 | (1,1) < tail + | | | | + | | | | + | (1,4) | x7 | (1,4) < head + | | | | + | | | | + head > (1,5) | x9 | | + | | | | + | | | | + +--------+-------+---------+ + + The head of the log entry (1,5) is divergent because it is greater than the + head of olog. + + */ + { + clear(); + + ObjectStore::Transaction t; + pg_log_t olog; + pg_info_t oinfo; + int fromosd = -1; + pg_info_t info; + list remove_snap; + bool dirty_log = false; + bool dirty_info = false; + bool dirty_big_info = false; + + { + pg_log_entry_t e; + + e.version = eversion_t(1, 1); + e.soid.hash = 0x5; + log.tail = e.version; + log.log.push_back(e); + e.version = eversion_t(1, 4); + e.soid.hash = 0x7; + log.log.push_back(e); + e.version = eversion_t(1, 5); + e.soid.hash = 0x9; + log.log.push_back(e); + log.head = e.version; + log.index(); + + info.last_update = log.head; + + e.version = eversion_t(1, 1); + e.soid.hash = 0x5; + olog.tail = e.version; + olog.log.push_back(e); + e.version = eversion_t(1, 4); + e.soid.hash = 0x7; + olog.log.push_back(e); + olog.head = e.version; + } + + hobject_t last_backfill(object_t("oname"), string("key"), 1, 234, 1); + info.last_backfill = last_backfill; + eversion_t stat_version(10, 1); + info.stats.version = stat_version; + + EXPECT_FALSE(missing.have_missing()); + EXPECT_EQ(3U, log.log.size()); + EXPECT_EQ(0U, ondisklog.length()); + EXPECT_EQ(stat_version, info.stats.version); + EXPECT_TRUE(remove_snap.empty()); + EXPECT_TRUE(t.empty()); + EXPECT_EQ(last_backfill, info.last_backfill); + EXPECT_TRUE(info.purged_snaps.empty()); + EXPECT_FALSE(dirty_log); + EXPECT_FALSE(dirty_info); + EXPECT_FALSE(dirty_big_info); + + merge_log(t, oinfo, olog, fromosd, info, remove_snap, + dirty_log, dirty_info, dirty_big_info); + + EXPECT_FALSE(missing.have_missing()); + EXPECT_EQ(2U, log.log.size()); + EXPECT_EQ(0U, ondisklog.length()); + EXPECT_EQ(stat_version, info.stats.version); + EXPECT_EQ(0x9U, remove_snap.front().hash); + EXPECT_TRUE(t.empty()); + EXPECT_TRUE(info.purged_snaps.empty()); + EXPECT_TRUE(dirty_log); + EXPECT_TRUE(dirty_info); + EXPECT_TRUE(dirty_big_info); + } + + // If our log is empty, the incoming log needs to have not been trimmed. + { + clear(); + + ObjectStore::Transaction t; + pg_log_t olog; + pg_info_t oinfo; + int fromosd = -1; + pg_info_t info; + list remove_snap; + bool dirty_log = false; + bool dirty_info = false; + bool dirty_big_info = false; + + // olog has been trimmed + olog.tail = eversion_t(1, 1); + + ASSERT_THROW(merge_log(t, oinfo, olog, fromosd, info, remove_snap, + dirty_log, dirty_info, dirty_big_info), FailedAssertion); + } + + /* +--------------------------+ + | log olog | + +--------+-------+---------+ + | |object | | + |version | hash | version | + | | | | + tail > (1,1) | x5 | | + | | | | + | | | | + head > (1,2) | x3 | | + | | | | + | | x9 | (2,3) < tail + | | | | + | | | | + | | x5 | (2,4) < head + | | | | + +--------+-------+---------+ + + If the logs do not overlap, throw an exception. + + */ + { + clear(); + + ObjectStore::Transaction t; + pg_log_t olog; + pg_info_t oinfo; + int fromosd = -1; + pg_info_t info; + list remove_snap; + bool dirty_log = false; + bool dirty_info = false; + bool dirty_big_info = false; + + { + pg_log_entry_t e; + + e.version = eversion_t(1, 1); + e.soid.hash = 0x5; + log.tail = e.version; + log.log.push_back(e); + e.version = eversion_t(1, 2); + e.soid.hash = 0x3; + log.log.push_back(e); + log.head = e.version; + log.index(); + + info.last_update = log.head; + + e.version = eversion_t(2, 3); + e.soid.hash = 0x9; + olog.log.push_back(e); + e.version = eversion_t(2, 4); + e.soid.hash = 0x5; + olog.log.push_back(e); + olog.head = e.version; + } + + EXPECT_THROW(merge_log(t, oinfo, olog, fromosd, info, remove_snap, + dirty_log, dirty_info, dirty_big_info), FailedAssertion); + } +} + int main(int argc, char **argv) { vector args; argv_to_vec(argc, (const char **)argv, args);