/* Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by the Free Software Foundation. This program is also distributed with certain software (including but not limited to OpenSSL) that is licensed under separate terms, as designated in a particular file or component or in included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the separately licensed software that they have included with MySQL. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "NDBT_Test.hpp" #include "NDBT_ReturnCodes.h" #include "HugoTransactions.hpp" #include "UtilTransactions.hpp" #include "NdbRestarter.hpp" /** * Global vector to keep track of * records stored in db */ struct SavedRecord { Uint64 m_gci; Uint32 m_author; BaseString m_str; SavedRecord(Uint64 _gci, Uint32 _author, BaseString _str){ m_gci = _gci; m_author = _author; m_str.assign(_str); } SavedRecord(){ m_gci = 0; m_str = ""; } }; Vector savedRecords; Uint64 highestExpectedGci; #define CHECK(b) if (!(b)) { \ ndbout << "ERR: "<< step->getName() \ << " failed on line " << __LINE__ << endl; \ result = NDBT_FAILED; \ break; } static int maybeExtraBits(Ndb* ndb, NdbDictionary::Table& tab, int when, void* arg) { switch(when){ case 0: // Before break; case 1: // After return 0; default: return 0; } bool useExtendedBits = ((ndb_rand() % 5) != 0); Uint32 numGciBits= ndb_rand() % 32; /* 0 -> 31 */ Uint32 numAuthorBits = ndb_rand() % 32; /* 0 -> 31 */ if (useExtendedBits && (numGciBits || numAuthorBits)) { ndbout_c("Creating table %s with %u extra Gci and %u extra Author bits", tab.getName(), numGciBits, numAuthorBits); tab.setExtraRowGciBits(numGciBits); tab.setExtraRowAuthorBits(numAuthorBits); } else { ndbout_c("Table has no extra bits"); } return 0; } int runDropTable(NDBT_Context* ctx, NDBT_Step* step) { GETNDB(step)->getDictionary()->dropTable(ctx->getTab()->getName()); return NDBT_OK; } int runCreateTable(NDBT_Context* ctx, NDBT_Step* step) { runDropTable(ctx, step); /* Use extra proc to control whether we have extra bits */ if (NDBT_Tables::createTable(GETNDB(step), ctx->getTab()->getName(), false, false, maybeExtraBits) == NDBT_OK) { ctx->setTab(GETNDB(step)-> getDictionary()-> getTable(ctx->getTab()->getName())); return NDBT_OK; } return NDBT_FAILED; } int runInsertRememberGci(NDBT_Context* ctx, NDBT_Step* step){ int result = NDBT_OK; int records = ctx->getNumRecords(); HugoOperations hugoOps(*ctx->getTab()); HugoCalculator hugoCalc(*ctx->getTab()); Ndb* pNdb = GETNDB(step); int i = 0; ndbout_c("Inserting %u records", records); Uint64 minGci = ~Uint64(0); Uint64 maxGci = 0; Uint32 numAuthorBits = ctx->getTab()->getExtraRowAuthorBits(); Uint32 authorMask = (1 << numAuthorBits) -1; ndbout_c("numAuthor bits is %u, mask is %x", numAuthorBits, authorMask); while(i < records){ // Insert record and read it in same transaction CHECK(hugoOps.startTransaction(pNdb) == 0); CHECK(hugoOps.pkInsertRecord(pNdb, i) == 0); if (hugoOps.execute_NoCommit(pNdb) != 0){ ndbout << "Could not insert record " << i << endl; result = NDBT_FAILED; break; } /* Set the author column (if present) */ Uint32 authorVal = 0; if (ctx->getTab()->getExtraRowAuthorBits() > 0) { authorVal = (ndb_rand() & authorMask); /* Pain here due to need to use NdbRecord */ char rowBuff[NDB_MAX_TUPLE_SIZE]; const NdbDictionary::Table* tab = ctx->getTab(); CHECK(hugoCalc.setValues((Uint8*) rowBuff, tab->getDefaultRecord(), i, 0) == 0); NdbOperation::SetValueSpec setValueSpec; setValueSpec.column = NdbDictionary::Column::ROW_AUTHOR; setValueSpec.value = &authorVal; NdbOperation::OperationOptions opts; opts.optionsPresent= NdbOperation::OperationOptions::OO_SETVALUE; opts.extraSetValues= &setValueSpec; opts.numExtraSetValues = 1; const NdbOperation* update = hugoOps.getTransaction()-> updateTuple(tab->getDefaultRecord(), rowBuff, tab->getDefaultRecord(), rowBuff, NULL, /* mask */ &opts, sizeof(opts)); CHECK(update != NULL); } /* Read row back */ CHECK(hugoOps.pkReadRecord(pNdb, i) == 0); if (hugoOps.execute_Commit(pNdb) != 0){ ndbout << "Did not find record in DB " << i << endl; result = NDBT_FAILED; break; } Uint64 gci; CHECK(hugoOps.getTransaction()->getGCI(&gci) == 0); if (gci < minGci) minGci = gci; if (gci > maxGci) maxGci = gci; savedRecords.push_back(SavedRecord(gci, authorVal, hugoOps.getRecordStr(0))); CHECK(hugoOps.closeTransaction(pNdb) == 0); i++; /* Sleep so that records will have > 1 GCI between them */ NdbSleep_MilliSleep(10); }; ndbout_c(" Inserted records from gci %x/%x to gci %x/%x", (Uint32) (minGci >> 32), (Uint32) (minGci & 0xffffffff), (Uint32) (maxGci >> 32), (Uint32) (maxGci & 0xffffffff)); highestExpectedGci = maxGci; return result; } int runRestartAll(NDBT_Context* ctx, NDBT_Step* step){ Ndb* pNdb = GETNDB(step); NdbRestarter restarter; ndbout_c("Restart of all nodes"); // Restart cluster with abort if (restarter.restartAll(false, false, true) != 0){ ctx->stopTest(); return NDBT_FAILED; } if (restarter.waitClusterStarted(300) != 0){ return NDBT_FAILED; } if (pNdb->waitUntilReady() != 0){ return NDBT_FAILED; } return NDBT_OK; } int runRestartOneInitial(NDBT_Context* ctx, NDBT_Step* step){ Ndb* pNdb = GETNDB(step); NdbRestarter restarter; if (restarter.getNumDbNodes() < 2) return NDBT_OK; /* We don't restart the Master as we need to know a * non-restarted node to reliably get the restartGci * afterwards! * Should be no real reason not to restart the master. */ int node = restarter.getRandomNotMasterNodeId(rand()); ndbout_c("Restarting node %u initial", node); if (restarter.restartOneDbNode(node, true, /* Initial */ false, /* Nostart */ true) /* Abort */ != 0) { ctx->stopTest(); return NDBT_FAILED; } if (restarter.waitClusterStarted(300) != 0){ return NDBT_FAILED; } if (pNdb->waitUntilReady() != 0){ return NDBT_FAILED; } return NDBT_OK; } int runRestartGciControl(NDBT_Context* ctx, NDBT_Step* step){ int records = ctx->getNumRecords(); Ndb* pNdb = GETNDB(step); UtilTransactions utilTrans(*ctx->getTab()); // Wait until we have enough records in db int count = 0; while (count < records){ if (utilTrans.selectCount(pNdb, 64, &count) != 0){ ctx->stopTest(); return NDBT_FAILED; } NdbSleep_MilliSleep(10); } return runRestartAll(ctx,step); } int runDetermineRestartGci(NDBT_Context* ctx, NDBT_Step* step) { Ndb* pNdb = GETNDB(step); Uint32 restartGci; int res = pNdb->getDictionary()->getRestartGCI(&restartGci); if (res != 0) { ndbout << "Failed to retrieve restart gci" << endl; ndbout << pNdb->getDictionary()->getNdbError() << endl; return NDBT_FAILED; } ndbout_c("Restart GCI is %u (0x%x)", restartGci, restartGci); ndbout_c("Highest expected GCI was %x/%x", (Uint32) (highestExpectedGci >> 32), (Uint32) (highestExpectedGci & 0xffffffff)); highestExpectedGci = ((Uint64) restartGci) << 32 | 0xffffffff; ndbout_c("Resetting Highest expected GCI to align with restart Gci (%x/%x)", (Uint32) (highestExpectedGci >> 32), (Uint32) (highestExpectedGci & 0xffffffff)); return NDBT_OK; } int runRequireExact(NDBT_Context* ctx, NDBT_Step* step){ ctx->incProperty("ExactGCI"); return NDBT_OK; } int runVerifyInserts(NDBT_Context* ctx, NDBT_Step* step){ int result = NDBT_OK; Ndb* pNdb = GETNDB(step); UtilTransactions utilTrans(*ctx->getTab()); HugoOperations hugoOps(*ctx->getTab()); NdbRestarter restarter; Uint32 extraGciBits = ctx->getTab()->getExtraRowGciBits(); Uint32 firstSaturatedValue = (1 << extraGciBits) -1; int count = 0; if (utilTrans.selectCount(pNdb, 64, &count) != 0){ return NDBT_FAILED; } // RULE1: The vector with saved records should have exactly as many // records with lower or same gci as there are in DB int recordsWithLowerOrSameGci = 0; unsigned i; for (i = 0; i < savedRecords.size(); i++){ if (savedRecords[i].m_gci <= highestExpectedGci) recordsWithLowerOrSameGci++; } if (recordsWithLowerOrSameGci != count){ ndbout << "ERR: Wrong number of expected records" << endl; result = NDBT_FAILED; } bool exactGCIonly = ctx->getProperty("ExactGCI", (unsigned) 0); // RULE2: The records found in db should have same or lower // gci as in the vector int recordsWithIncorrectGci = 0; int recordsWithRoundedGci = 0; int recordsWithIncorrectAuthor = 0; for (i = 0; i < savedRecords.size(); i++){ CHECK(hugoOps.startTransaction(pNdb) == 0); /* First read of row to check contents */ CHECK(hugoOps.pkReadRecord(pNdb, i) == 0); /* Second read of row to get GCI */ NdbTransaction* trans = hugoOps.getTransaction(); NdbOperation* readOp = trans->getNdbOperation(ctx->getTab()); CHECK(readOp != NULL); CHECK(readOp->readTuple() == 0); CHECK(hugoOps.equalForRow(readOp, i) == 0); NdbRecAttr* rowGci = readOp->getValue(NdbDictionary::Column::ROW_GCI64); NdbRecAttr* rowAuthor = readOp->getValue(NdbDictionary::Column::ROW_AUTHOR); CHECK(rowGci != NULL); CHECK(rowAuthor != NULL); if (hugoOps.execute_Commit(pNdb) != 0){ // Record was not found in db' // Check record gci if (savedRecords[i].m_gci <= highestExpectedGci) { ndbout << "ERR: Record "< highestExpectedGci){ ndbout << "ERR: Record "<= firstSaturatedValue; Uint64 expectedRoundedGci = (expectedRecordGci | 0xffffffff); Uint64 readGci = rowGci->u_64_value(); Uint64 expectedRead = (expectRounding)?expectedRoundedGci : expectedRecordGci; // Check record gci is exactly correct if (expectedRead != readGci){ if ((!exactGCIonly) && (expectedRoundedGci == readGci)) { /* Record rounded, though bits can be represented * presumably due to Redo gci truncation */ recordsWithRoundedGci++; } else { ndbout_c("ERR: Record %u should have GCI %x/%x, but has " "%x/%x.", i, (Uint32) (expectedRead >> 32), (Uint32) (expectedRead & 0xffffffff), (Uint32) (readGci >> 32), (Uint32) (readGci & 0xffffffff)); recordsWithIncorrectGci++; result = NDBT_FAILED; } } // Check author value is correct. Uint32 expectedAuthor = savedRecords[i].m_author; if (rowAuthor->u_32_value() != expectedAuthor) { ndbout_c("ERR: Record %u should have Author %d, but has %d.", i, expectedAuthor, rowAuthor->u_32_value()); recordsWithIncorrectAuthor++; result = NDBT_FAILED; } } CHECK(hugoOps.closeTransaction(pNdb) == 0); } ndbout << "There are " << count << " records in db" << endl; ndbout << "There are " << savedRecords.size() << " records in vector" << endl; ndbout_c("There are %u records with lower or same gci than %x/%x", recordsWithLowerOrSameGci, (Uint32)(highestExpectedGci >> 32), (Uint32)(highestExpectedGci & 0xffffffff)); ndbout_c("There are %u records with rounded Gcis. Exact GCI flag is %u", recordsWithRoundedGci, exactGCIonly); ndbout << "There are " << recordsWithIncorrectGci << " records with incorrect Gci on recovery." << endl; ndbout << "There are " << recordsWithIncorrectAuthor << " records with incorrect Author on recovery." << endl; return result; } int runClearGlobals(NDBT_Context* ctx, NDBT_Step* step){ savedRecords.clear(); highestExpectedGci = 0; return NDBT_OK; } int runClearTable(NDBT_Context* ctx, NDBT_Step* step){ int records = ctx->getNumRecords(); UtilTransactions utilTrans(*ctx->getTab()); if (utilTrans.clearTable2(GETNDB(step), records, 240) != 0){ return NDBT_FAILED; } return NDBT_OK; } int runLoadTable(NDBT_Context* ctx, NDBT_Step* step) { int records = ctx->getNumRecords(); HugoTransactions hugoTrans(*ctx->getTab()); if (hugoTrans.loadTable(GETNDB(step), records, 512, false, 0, true) != 0){ return NDBT_FAILED; } return NDBT_OK; } int runNodeInitialRestarts(NDBT_Context* ctx, NDBT_Step* step) { NdbRestarter restarter; const Uint32 numRestarts = 4; for (Uint32 nr = 0; nr < numRestarts; nr++) { if (ctx->isTestStopped()) { return NDBT_OK; } int nodeId = restarter.getNode(NdbRestarter::NS_RANDOM); ndbout_c("Restarting node %u", nodeId); if (restarter.restartOneDbNode2(nodeId, NdbRestarter::NRRF_INITIAL) != 0) { ndbout_c("Error restarting node"); ctx->stopTest(); return NDBT_FAILED; } if (restarter.waitClusterStarted(300) != 0) { ctx->stopTest(); return NDBT_FAILED; } if (GETNDB(step)->waitUntilReady() != 0) { ctx->stopTest(); return NDBT_FAILED; } } ctx->stopTest(); return NDBT_OK; } int runUpdateVerifyGCI(NDBT_Context* ctx, NDBT_Step* step) { HugoOperations hugoOps(*ctx->getTab()); HugoCalculator hugoCalc(*ctx->getTab()); Ndb* pNdb = GETNDB(step); /* Loop, updating the first record in the table, and checking * that it has the GCI it should */ Uint64 loopCount = 0; Uint64 distinctCount = 0; Uint64 expectedGCI = 0; Uint64 lastGoodReadGCI = 0; Uint32 extraGciBits = ctx->getTab()->getExtraRowGciBits(); Uint32 firstSaturatedValue = (1 << extraGciBits) -1; ndbout_c("Extra GCI bits : %u, firstSaturatedValue : %u", extraGciBits, firstSaturatedValue); int result = NDBT_OK; while (!ctx->isTestStopped()) { CHECK(hugoOps.startTransaction(pNdb) == 0); /* Define a read op to get the 'existing' GCI */ NdbTransaction* trans = hugoOps.getTransaction(); CHECK(hugoOps.pkReadRecord(pNdb, 0, 1) == 0); NdbOperation* readOp = trans->getNdbOperation(ctx->getTab()); CHECK(readOp != NULL); CHECK(readOp->readTuple() == 0); CHECK(hugoOps.equalForRow(readOp, 0) == 0); NdbRecAttr* rowGci = readOp->getValue(NdbDictionary::Column::ROW_GCI64); CHECK(rowGci != NULL); /* Define an update op to set the next GCI */ CHECK(hugoOps.pkUpdateRecord(pNdb, 0, 1, (int)(loopCount+1)) == 0); if (hugoOps.execute_Commit(pNdb) != 0) { if (hugoOps.getNdbError().classification == NdbError::NodeRecoveryError) { hugoOps.closeTransaction(pNdb); ndbout_c("Temporary error at loopCount %llu", loopCount); continue; } ndbout << "Error executing : " << hugoOps.getNdbError() << endl; return NDBT_FAILED; } /* First check the data is as expected */ CHECK(hugoCalc.verifyRowValues(&hugoOps.get_row(0)) == 0); CHECK((Uint64)hugoCalc.getUpdatesValue(&hugoOps.get_row(0)) == loopCount); //ndbout_c("Updates value is %u", hugoCalc.getUpdatesValue(&hugoOps.get_row(0))); Uint64 committedGCI; CHECK(trans->getGCI(&committedGCI) == 0); Uint32 gci_lo = Uint32(committedGCI & 0xffffffff); Uint64 saturatedCommittedGCI = (gci_lo >= firstSaturatedValue) ? committedGCI | 0xffffffff : committedGCI; Uint64 rowGCI64 = rowGci->u_64_value(); // ndbout_c("Read row GCI64 %x/%x. Committed GCI64 : %x/%x. Saturated GCI64 :%x/%x Last good read : %x/%x", // Uint32(rowGCI64 >> 32), // Uint32(rowGCI64 & 0xffffffff), // Uint32(committedGCI >> 32), // Uint32(committedGCI & 0xffffffff), // Uint32(saturatedCommittedGCI >> 32), // Uint32(saturatedCommittedGCI & 0xffffffff), // Uint32(lastGoodReadGCI >> 32), // Uint32(lastGoodReadGCI & 0xffffffff)); if (rowGCI64 < lastGoodReadGCI) { ndbout_c("ERROR : Read row GCI value (%x/%x) lower than previous value (%x/%x)", (Uint32) (rowGCI64 >> 32), (Uint32) (rowGCI64 & 0xffffffff), Uint32(lastGoodReadGCI >> 32), Uint32(lastGoodReadGCI & 0xffffffff)); } /* We certainly should not read a committed GCI value that's * bigger than the read's commit-point GCI */ if (saturatedCommittedGCI < rowGCI64) { ndbout_c("ERROR : Saturated committed GCI (%x/%x) lower than actual read GCI (%x/%x)", Uint32(saturatedCommittedGCI >>32), Uint32(saturatedCommittedGCI & 0xffffffff), (Uint32) (rowGCI64 >> 32), (Uint32) (rowGCI64 & 0xffffffff)); } /* If we've read a committed GCI then we should certainly not * be committing at lower values */ if (saturatedCommittedGCI < lastGoodReadGCI) { ndbout_c("ERROR : Saturated committed GCI (%x/%x) lower than a previously" "read GCI (%x/%x)", Uint32(saturatedCommittedGCI >>32), Uint32(saturatedCommittedGCI & 0xffffffff), Uint32(lastGoodReadGCI >> 32), Uint32(lastGoodReadGCI & 0xffffffff)); }; /* If we've previously had a particular committed GCI then we * should certainly not now have a lower committed GCI */ if (saturatedCommittedGCI < expectedGCI) { ndbout_c("ERROR : Saturated committed GCI (%x/%x) lower than expected GCI" " (%x/%x)", Uint32(saturatedCommittedGCI >>32), Uint32(saturatedCommittedGCI & 0xffffffff), Uint32(expectedGCI >> 32), Uint32(expectedGCI & 0xffffffff)); } if (loopCount > 0) { if (rowGCI64 != expectedGCI) { ndbout_c("MISMATCH : Expected GCI of %x/%x, but found %x/%x", (Uint32) (expectedGCI >> 32), (Uint32) (expectedGCI & 0xffffffff), (Uint32) (rowGCI64 >> 32), (Uint32) (rowGCI64 & 0xffffffff)); ndbout_c("At loopcount %llu", loopCount); ndbout_c("Last good read GCI %x/%x", Uint32(lastGoodReadGCI >> 32), Uint32(lastGoodReadGCI & 0xffffffff)); ndbout_c("Read committed GCI : %x/%x", Uint32(saturatedCommittedGCI >>32), Uint32(saturatedCommittedGCI & 0xffffffff)); ndbout_c("Transaction coordinator node : %u", trans->getConnectedNodeId()); return NDBT_FAILED; } if (saturatedCommittedGCI != expectedGCI) { distinctCount++; } } expectedGCI = saturatedCommittedGCI; lastGoodReadGCI = rowGCI64; hugoOps.closeTransaction(pNdb); loopCount++; /* Sleep to avoid excessive updating */ NdbSleep_MilliSleep(10); } ndbout_c("%llu updates with %llu distinct GCI values", loopCount, distinctCount); return result; } NDBT_TESTSUITE(testRestartGci); TESTCASE("InsertRestartGci", "Verify that only expected records are still in NDB\n" "after a restart" ){ INITIALIZER(runCreateTable); INITIALIZER(runClearGlobals); INITIALIZER(runInsertRememberGci); INITIALIZER(runRestartGciControl); INITIALIZER(runDetermineRestartGci); TC_PROPERTY("ExactGCI", Uint32(0)); /* Recovery from Redo == inexact low word */ VERIFIER(runVerifyInserts); /* Restart again - LCP after first restart will mean that this * time we recover from LCP, not Redo */ VERIFIER(runRestartAll); VERIFIER(runDetermineRestartGci); VERIFIER(runVerifyInserts); // Check GCIs again /* Restart again - one node, initial. This will check * COPYFRAG behaviour */ VERIFIER(runRestartOneInitial); VERIFIER(runVerifyInserts); // Check GCIs again VERIFIER(runClearTable); /* Re-fill table with records, will just be in Redo * Then restart, testing COPYFRAG behaviour with * non #ffff... low word */ VERIFIER(runClearGlobals); VERIFIER(runInsertRememberGci); VERIFIER(runRestartOneInitial); /* Require exact GCI match from here - no Redo messing it up */ VERIFIER(runRequireExact); VERIFIER(runVerifyInserts); /* Now-restart all nodes - all inserts should be * in LCP, and should be restored correctly */ VERIFIER(runRestartAll); VERIFIER(runDetermineRestartGci); VERIFIER(runVerifyInserts); FINALIZER(runClearTable); FINALIZER(runDropTable); } TESTCASE("InitialNodeRestartUpdate", "Check that initial node restart (copyfrag) does " "not affect GCI recording") { INITIALIZER(runCreateTable); INITIALIZER(runLoadTable); STEP(runNodeInitialRestarts); STEP(runUpdateVerifyGCI); FINALIZER(runClearTable); FINALIZER(runDropTable); } NDBT_TESTSUITE_END(testRestartGci) int main(int argc, const char** argv){ ndb_init(); NDBT_TESTSUITE_INSTANCE(testRestartGci); testRestartGci.setCreateTable(false); return testRestartGci.execute(argc, argv); } template class Vector;