/* Copyright (c) 2008, 2014, 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 as published by the Free Software Foundation; version 2 of the License. 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 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include #define CHECKNOTNULL(p) if ((p) == NULL) { \ ndbout << "Error at line " << __LINE__ << endl; \ NDB_ERR(trans->getNdbError()); \ trans->close(); \ return NDBT_FAILED; } #define CHECKEQUAL(v, e) if ((e) != (v)) { \ ndbout << "Error at line " << __LINE__ << \ " expected " << v << endl; \ NDB_ERR(trans->getNdbError()); \ trans->close(); \ return NDBT_FAILED; } /* Setup memory as a long Varchar with 2 bytes of * length information */ Uint32 setLongVarchar(char* where, const char* what, Uint32 sz) { where[0]=sz & 0xff; where[1]=(sz >> 8) & 0xff; memcpy(&where[2], what, sz); return (sz + 2); } /* Activate the given error insert in TC block * This is used for error insertion where a TCKEYREQ * is required to activate the error */ int activateErrorInsert(NdbTransaction* trans, const NdbRecord* record, const NdbDictionary::Table* tab, const char* buf, NdbRestarter* restarter, Uint32 val) { /* We insert the error twice to avoid what appear to be * races between the error insert and the subsequent * tests * Alternatively we could sleep here. */ if (restarter->insertErrorInAllNodes(val) != 0){ g_err << "error insert 1 (" << val << ") failed" << endl; return NDBT_FAILED; } if (restarter->insertErrorInAllNodes(val) != 0){ g_err << "error insert 2 (" << val << ") failed" << endl; return NDBT_FAILED; } NdbOperation* insert= trans->getNdbOperation(tab); CHECKNOTNULL(insert); CHECKEQUAL(0, insert->insertTuple()); CHECKEQUAL(0, insert->equal((Uint32) 0, NdbDictionary::getValuePtr (record, buf, 0))); CHECKEQUAL(0, insert->setValue(1, NdbDictionary::getValuePtr (record, buf, 1))); CHECKEQUAL(0, trans->execute(NdbTransaction::NoCommit)); CHECKEQUAL(0, trans->getNdbError().code); return NDBT_OK; } /* Test for correct behaviour using primary key operations * when an NDBD node's SegmentedSection pool is exhausted. */ int testSegmentedSectionPk(NDBT_Context* ctx, NDBT_Step* step){ /* * Signal type Exhausted @ How * ----------------------------------------------------- * Long TCKEYREQ Initial import Consume + send * Long TCKEYREQ Initial import, not first * TCKEYREQ in batch Consume + send * Long TCKEYREQ Initial import, not last * TCKEYREQ in batch Consume + send * No testing of short TCKEYREQ variants as they cannot be * generated in mysql-5.1-telco-6.4+ * TODO : Add short variant testing to testUpgrade. */ /* We just run on one table */ if (strcmp(ctx->getTab()->getName(), "WIDE_2COL") != 0) return NDBT_OK; const Uint32 maxRowBytes= NDB_MAX_TUPLE_SIZE_IN_WORDS * sizeof(Uint32); const Uint32 maxKeyBytes= NDBT_Tables::MaxVarTypeKeyBytes; const Uint32 maxAttrBytes= NDBT_Tables::MaxKeyMaxVarTypeAttrBytes; const Uint32 srcBuffBytes= MAX(maxKeyBytes,maxAttrBytes); char smallKey[50]; char srcBuff[srcBuffBytes]; char smallRowBuf[maxRowBytes]; char bigKeyRowBuf[maxRowBytes]; char bigAttrRowBuf[maxRowBytes]; /* Small key for hinting to same TC */ Uint32 smallKeySize= setLongVarchar(&smallKey[0], "ShortKey", 8); /* Large value source */ memset(srcBuff, 'B', srcBuffBytes); const NdbRecord* record= ctx->getTab()->getDefaultRecord(); /* Setup buffers * Small row buffer with small key and small data */ setLongVarchar(NdbDictionary::getValuePtr(record, smallRowBuf, 0), "ShortKey", 8); NdbDictionary::setNull(record, smallRowBuf, 0, false); setLongVarchar(NdbDictionary::getValuePtr(record, smallRowBuf, 1), "ShortData", 9); NdbDictionary::setNull(record, smallRowBuf, 1, false); /* Big key buffer with big key and small data*/ setLongVarchar(NdbDictionary::getValuePtr(record, bigKeyRowBuf, 0), &srcBuff[0], maxKeyBytes); NdbDictionary::setNull(record, bigKeyRowBuf, 0, false); setLongVarchar(NdbDictionary::getValuePtr(record, bigKeyRowBuf, 1), "ShortData", 9); NdbDictionary::setNull(record, bigKeyRowBuf, 1, false); /* Big AttrInfo buffer with small key and big data */ setLongVarchar(NdbDictionary::getValuePtr(record, bigAttrRowBuf, 0), "ShortKey", 8); NdbDictionary::setNull(record, bigAttrRowBuf, 0, false); setLongVarchar(NdbDictionary::getValuePtr(record, bigAttrRowBuf, 1), &srcBuff[0], maxAttrBytes); NdbDictionary::setNull(record, bigAttrRowBuf, 1, false); NdbRestarter restarter; Ndb* pNdb= GETNDB(step); /* Start a transaction on a specific node */ NdbTransaction* trans= pNdb->startTransaction(ctx->getTab(), &smallKey[0], smallKeySize); CHECKNOTNULL(trans); /* Activate error insert 8065 in this transaction, limits * any single import/append to 1 section */ CHECKEQUAL(NDBT_OK, activateErrorInsert(trans, record, ctx->getTab(), smallRowBuf, &restarter, 8065)); /* Ok, let's try an insert with a key bigger than 1 section. * Since it's part of the same transaction, it'll go via * the same TC. */ const NdbOperation* bigInsert = trans->insertTuple(record, bigKeyRowBuf); CHECKNOTNULL(bigInsert); CHECKEQUAL(-1, trans->execute(NdbTransaction::NoCommit)); /* ZGET_DATABUF_ERR expected */ CHECKEQUAL(218, trans->getNdbError().code) trans->close(); /* Ok, now a long TCKEYREQ to the same TC - this * has slightly different abort handling since no other * operations exist in this new transaction. * We also change it so that import overflow occurs * on the AttrInfo section */ /* Start transaction on the same node */ CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(), &smallKey[0], smallKeySize)); CHECKNOTNULL(bigInsert = trans->insertTuple(record, bigAttrRowBuf)); CHECKEQUAL(-1,trans->execute(NdbTransaction::NoCommit)); /* ZGET_DATABUF_ERR expected */ CHECKEQUAL(218, trans->getNdbError().code); trans->close(); /* Ok, now a long TCKEYREQ where we run out of SegmentedSections * on the first TCKEYREQ, but there are other TCKEYREQs following * in the same batch. Check that abort handling is correct */ /* Start transaction on the same node */ CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(), &smallKey[0], smallKeySize)); /* First op in batch, will cause overflow */ CHECKNOTNULL(bigInsert = trans->insertTuple(record, bigAttrRowBuf)); /* Second op in batch, what happens to it? */ const NdbOperation* secondOp; CHECKNOTNULL(secondOp = trans->insertTuple(record, bigAttrRowBuf)); CHECKEQUAL(-1,trans->execute(NdbTransaction::NoCommit)); /* ZGET_DATABUF_ERR expected */ CHECKEQUAL(218, trans->getNdbError().code); trans->close(); /* Now try with a 'short' TCKEYREQ, generated using the old Api * with a big key value */ /* Start transaction on the same node */ CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(), &smallKey[0], smallKeySize)); NdbOperation* bigInsertOldApi; CHECKNOTNULL(bigInsertOldApi= trans->getNdbOperation(ctx->getTab())); CHECKEQUAL(0, bigInsertOldApi->insertTuple()); CHECKEQUAL(0, bigInsertOldApi->equal((Uint32)0, NdbDictionary::getValuePtr (record, bigKeyRowBuf, 0))); CHECKEQUAL(0, bigInsertOldApi->setValue(1, NdbDictionary::getValuePtr (record, bigKeyRowBuf, 1))); CHECKEQUAL(-1, trans->execute(NdbTransaction::NoCommit)); /* ZGET_DATABUF_ERR expected */ CHECKEQUAL(218, trans->getNdbError().code) trans->close(); /* Now try with a 'short' TCKEYREQ, generated using the old Api * with a big data value */ CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(), &smallKey[0], smallKeySize)); CHECKNOTNULL(bigInsertOldApi= trans->getNdbOperation(ctx->getTab())); CHECKEQUAL(0, bigInsertOldApi->insertTuple()); CHECKEQUAL(0, bigInsertOldApi->equal((Uint32)0, NdbDictionary::getValuePtr (record, bigAttrRowBuf, 0))); CHECKEQUAL(0, bigInsertOldApi->setValue(1, NdbDictionary::getValuePtr (record, bigAttrRowBuf, 1))); CHECKEQUAL(-1, trans->execute(NdbTransaction::NoCommit)); /* ZGET_DATABUF_ERR expected */ CHECKEQUAL(218, trans->getNdbError().code) trans->close(); // TODO : Add code to testUpgrade #if 0 /* * Short TCKEYREQ KeyInfo accumulate Consume + send long * (TCKEYREQ + KEYINFO) * Short TCKEYREQ AttrInfo accumulate Consume + send short key * + long AI * (TCKEYREQ + ATTRINFO) */ /* Change error insert so that next TCKEYREQ will grab * all but one SegmentedSection so that we can then test SegmentedSection * exhaustion when importing the Key/AttrInfo words from the * TCKEYREQ signal itself. */ restarter.insertErrorInAllNodes(8066); /* Now a 'short' TCKEYREQ, there will be space to import the * short key, but not the AttrInfo */ /* Start transaction on same node */ CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(), &smallKey[0], smallKeySize)); CHECKNOTNULL(bigInsertOldApi= trans->getNdbOperation(ctx->getTab())); CHECKEQUAL(0, bigInsertOldApi->insertTuple()); CHECKEQUAL(0, bigInsertOldApi->equal((Uint32)0, NdbDictionary::getValuePtr (record, smallRowBuf, 0))); CHECKEQUAL(0, bigInsertOldApi->setValue(1, NdbDictionary::getValuePtr (record, smallRowBuf, 1))); CHECKEQUAL(-1, trans->execute(NdbTransaction::NoCommit)); /* ZGET_DATABUF_ERR expected */ CHECKEQUAL(218, trans->getNdbError().code) trans->close(); /* Change error insert so that there are no SectionSegments * This will cause failure when attempting to import the * KeyInfo from the TCKEYREQ */ restarter.insertErrorInAllNodes(8067); /* Now a 'short' TCKEYREQ - there will be no space to import the key */ CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(), &smallKey[0], smallKeySize)); CHECKNOTNULL(bigInsertOldApi= trans->getNdbOperation(ctx->getTab())); CHECKEQUAL(0, bigInsertOldApi->insertTuple()); CHECKEQUAL(0, bigInsertOldApi->equal((Uint32)0, NdbDictionary::getValuePtr (record, smallRowBuf, 0))); CHECKEQUAL(0, bigInsertOldApi->setValue(1, NdbDictionary::getValuePtr (record, smallRowBuf, 1))); CHECKEQUAL(-1, trans->execute(NdbTransaction::NoCommit)); /* ZGET_DATABUF_ERR expected */ CHECKEQUAL(218, trans->getNdbError().code) trans->close(); #endif /* Finished with error insert, cleanup the error insertion * Error insert 8068 will free the hoarded segments */ CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(), &smallKey[0], smallKeySize)); CHECKEQUAL(NDBT_OK, activateErrorInsert(trans, record, ctx->getTab(), smallRowBuf, &restarter, 8068)); trans->execute(NdbTransaction::Rollback); CHECKEQUAL(0, trans->getNdbError().code); trans->close(); return NDBT_OK; } /* Test for correct behaviour using unique key operations * when an NDBD node's SegmentedSection pool is exhausted. */ int testSegmentedSectionIx(NDBT_Context* ctx, NDBT_Step* step){ /* * Signal type Exhausted @ How * ----------------------------------------------------- * Long TCINDXREQ Initial import Consume + send * Long TCINDXREQ Build second TCKEYREQ Consume + send short * w. long base key */ /* We will generate : * 10 SS left : * Long IndexReq with too long Key/AttrInfo * 1 SS left : * Long IndexReq read with short Key + Attrinfo to long * base table Key */ /* We just run on one table */ if (strcmp(ctx->getTab()->getName(), "WIDE_2COL_IX") != 0) return NDBT_OK; const char* indexName= "WIDE_2COL_IX$NDBT_IDX0"; const Uint32 maxRowBytes= NDB_MAX_TUPLE_SIZE_IN_WORDS * sizeof(Uint32); const Uint32 srcBuffBytes= NDBT_Tables::MaxVarTypeKeyBytes; const Uint32 maxIndexKeyBytes= NDBT_Tables::MaxKeyMaxVarTypeAttrBytesIndex; /* We want to use 6 Segmented Sections, each of 60 32-bit words, including * a 2 byte length overhead * (We don't want to use 10 Segmented Sections as in some scenarios TUP * uses Segmented Sections when sending results, and if we use TUP on * the same node, the exhaustion will occur in TUP, which is not what * we're testing) */ const Uint32 mediumPrimaryKeyBytes= (6* 60 * 4) - 2; char smallKey[50]; char srcBuff[srcBuffBytes]; char smallRowBuf[maxRowBytes]; char bigKeyIxBuf[maxRowBytes]; char bigAttrIxBuf[maxRowBytes]; char bigKeyRowBuf[maxRowBytes]; char resultSpace[maxRowBytes]; /* Small key for hinting to same TC */ Uint32 smallKeySize= setLongVarchar(&smallKey[0], "ShortKey", 8); /* Large value source */ memset(srcBuff, 'B', srcBuffBytes); Ndb* pNdb= GETNDB(step); const NdbRecord* baseRecord= ctx->getTab()->getDefaultRecord(); const NdbRecord* ixRecord= pNdb-> getDictionary()->getIndex(indexName, ctx->getTab()->getName())->getDefaultRecord(); /* Setup buffers * Small row buffer with short key and data in base table record format */ setLongVarchar(NdbDictionary::getValuePtr(baseRecord, smallRowBuf, 0), "ShortKey", 8); NdbDictionary::setNull(baseRecord, smallRowBuf, 0, false); setLongVarchar(NdbDictionary::getValuePtr(baseRecord, smallRowBuf, 1), "ShortData", 9); NdbDictionary::setNull(baseRecord, smallRowBuf, 1, false); /* Big index key buffer * Big index key (normal row attribute) in index record format * Index's key is attrid 1 from the base table * This could get confusing ! */ setLongVarchar(NdbDictionary::getValuePtr(ixRecord, bigKeyIxBuf, 1), &srcBuff[0], maxIndexKeyBytes); NdbDictionary::setNull(ixRecord, bigKeyIxBuf, 1, false); /* Big AttrInfo buffer * Small key and large attrinfo in base table record format */ setLongVarchar(NdbDictionary::getValuePtr(baseRecord, bigAttrIxBuf, 0), "ShortIXKey", 10); NdbDictionary::setNull(baseRecord, bigAttrIxBuf, 0, false); setLongVarchar(NdbDictionary::getValuePtr(baseRecord, bigAttrIxBuf, 1), &srcBuff[0], maxIndexKeyBytes); NdbDictionary::setNull(baseRecord, bigAttrIxBuf, 1, false); /* Big key row buffer * Medium sized key and small attrinfo (index key) in * base table record format */ setLongVarchar(NdbDictionary::getValuePtr(baseRecord, bigKeyRowBuf, 0), &srcBuff[0], mediumPrimaryKeyBytes); NdbDictionary::setNull(baseRecord, bigKeyRowBuf, 0, false); setLongVarchar(NdbDictionary::getValuePtr(baseRecord, bigKeyRowBuf, 1), "ShortIXKey", 10); NdbDictionary::setNull(baseRecord, bigKeyRowBuf, 1, false); /* Start a transaction on a specific node */ NdbTransaction* trans= pNdb->startTransaction(ctx->getTab(), &smallKey[0], smallKeySize); /* Insert a row in the base table with a big PK, and * small data (Unique IX key). This is used later to lookup * a big PK and cause overflow when reading TRANSID_AI in TC. */ CHECKNOTNULL(trans->insertTuple(baseRecord, bigKeyRowBuf)); CHECKEQUAL(0, trans->execute(NdbTransaction::Commit)); NdbRestarter restarter; /* Start a transaction on a specific node */ trans= pNdb->startTransaction(ctx->getTab(), &smallKey[0], smallKeySize); CHECKNOTNULL(trans); /* Activate error insert 8065 in this transaction, limits any * single append/import to 10 sections. */ CHECKEQUAL(NDBT_OK, activateErrorInsert(trans, baseRecord, ctx->getTab(), smallRowBuf, &restarter, 8065)); /* Ok, let's try an index read with a big index key. * Since it's part of the same transaction, it'll go via * the same TC. */ const NdbOperation* bigRead= trans->readTuple(ixRecord, bigKeyIxBuf, baseRecord, resultSpace); CHECKNOTNULL(bigRead); CHECKEQUAL(-1, trans->execute(NdbTransaction::NoCommit)); /* ZGET_DATABUF_ERR expected */ CHECKEQUAL(218, trans->getNdbError().code) trans->close(); /* Ok, now a long TCINDXREQ to the same TC - this * has slightly different abort handling since no other * operations exist in this new transaction. */ /* Start a transaction on a specific node */ CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(), &smallKey[0], smallKeySize)); CHECKNOTNULL(trans->readTuple(ixRecord, bigKeyIxBuf, baseRecord, resultSpace)); CHECKEQUAL(-1, trans->execute(NdbTransaction::NoCommit)); /* ZGET_DATABUF_ERR expected */ CHECKEQUAL(218, trans->getNdbError().code); trans->close(); /* Now a TCINDXREQ that overflows, but is not the last in the * batch, what happens to the other TCINDXREQ in the batch? */ CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(), &smallKey[0], smallKeySize)); CHECKNOTNULL(trans->readTuple(ixRecord, bigKeyIxBuf, baseRecord, resultSpace)); /* Another read */ CHECKNOTNULL(trans->readTuple(ixRecord, bigKeyIxBuf, baseRecord, resultSpace)); CHECKEQUAL(-1, trans->execute(NdbTransaction::NoCommit)); /* ZGET_DATABUF_ERR expected */ CHECKEQUAL(218, trans->getNdbError().code); trans->close(); /* Next we read a tuple with a large primary key via the unique * index. The index read itself should be fine, but * pulling in the base table PK will cause abort due to overflow * handling TRANSID_AI */ /* Start a transaction on a specific node */ CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(), &smallKey[0], smallKeySize)); /* Activate error insert 8066 in this transaction, limits a * single import/append to 1 section. * Note that the TRANSID_AI is received by TC as a short-signal * train, so no single append is large, but when the first * segment is used and append starts on the second, it will * fail. */ CHECKEQUAL(NDBT_OK, activateErrorInsert(trans, baseRecord, ctx->getTab(), smallRowBuf, &restarter, 8066)); CHECKEQUAL(0, trans->execute(NdbTransaction::NoCommit)); CHECKNOTNULL(bigRead= trans->readTuple(ixRecord, bigAttrIxBuf, baseRecord, resultSpace)); CHECKEQUAL(-1, trans->execute(NdbTransaction::NoCommit)); /* ZGET_DATABUF_ERR expected */ CHECKEQUAL(218, trans->getNdbError().code) trans->close(); // TODO Move short signal testing to testUpgrade #if 0 /* * Short TCINDXREQ KeyInfo accumulate Consume + send long * (TCINDXREQ + KEYINFO) * Short TCINDXREQ AttrInfo accumulate Consume + send short key * + long AI * (TCINDXREQ + ATTRINFO) */ /* Now try with a 'short' TCINDXREQ, generated using the old Api * with a big index key value */ CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(), &smallKey[0], smallKeySize)); const NdbDictionary::Index* index; CHECKNOTNULL(index= pNdb->getDictionary()-> getIndex(indexName, ctx->getTab()->getName())); NdbIndexOperation* bigReadOldApi; CHECKNOTNULL(bigReadOldApi= trans->getNdbIndexOperation(index)); CHECKEQUAL(0, bigReadOldApi->readTuple()); /* We use the attribute id of the index, not the base table here */ CHECKEQUAL(0, bigReadOldApi->equal((Uint32)0, NdbDictionary::getValuePtr (ixRecord, bigKeyIxBuf, 1))); CHECKNOTNULL(bigReadOldApi->getValue((Uint32)1)); CHECKEQUAL(-1, trans->execute(NdbTransaction::NoCommit)); /* ZGET_DATABUF_ERR expected */ CHECKEQUAL(218, trans->getNdbError().code) trans->close(); /* Now try with a 'short' TCINDXREQ, generated using the old Api * with a big attrinfo value */ CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(), &smallKey[0], smallKeySize)); NdbIndexOperation* bigUpdateOldApi; CHECKNOTNULL(bigUpdateOldApi= trans->getNdbIndexOperation(index)); CHECKEQUAL(0, bigUpdateOldApi->updateTuple()); /* We use the attribute id of the index, not the base table here */ CHECKEQUAL(0, bigUpdateOldApi->equal((Uint32)0, NdbDictionary::getValuePtr (baseRecord, smallRowBuf, 1))); CHECKEQUAL(0, bigUpdateOldApi->setValue((Uint32)1, NdbDictionary::getValuePtr (baseRecord, bigAttrIxBuf, 1))); CHECKEQUAL(-1, trans->execute(NdbTransaction::NoCommit)); /* ZGET_DATABUF_ERR expected */ CHECKEQUAL(218, trans->getNdbError().code) trans->close(); /* Change error insert so that next TCINDXREQ will grab * all but one SegmentedSection */ restarter.insertErrorInAllNodes(8066); /* Now a short TCINDXREQ where the KeyInfo from the TCINDXREQ * can be imported, but the ATTRINFO can't */ CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(), &smallKey[0], smallKeySize)); CHECKNOTNULL(bigUpdateOldApi= trans->getNdbIndexOperation(index)); CHECKEQUAL(0, bigUpdateOldApi->updateTuple()); /* We use the attribute id of the index, not the base table here */ CHECKEQUAL(0, bigUpdateOldApi->equal((Uint32)0, NdbDictionary::getValuePtr (baseRecord, smallRowBuf, 1))); CHECKEQUAL(0, bigUpdateOldApi->setValue((Uint32)1, NdbDictionary::getValuePtr (baseRecord, bigAttrIxBuf, 1))); CHECKEQUAL(-1, trans->execute(NdbTransaction::NoCommit)); /* ZGET_DATABUF_ERR expected */ CHECKEQUAL(218, trans->getNdbError().code) trans->close(); /* Change error insert so that there are no SectionSegments */ restarter.insertErrorInAllNodes(8067); /* Now a short TCINDXREQ where the KeyInfo from the TCINDXREQ * can't be imported */ CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(), &smallKey[0], smallKeySize)); CHECKNOTNULL(bigUpdateOldApi= trans->getNdbIndexOperation(index)); CHECKEQUAL(0, bigUpdateOldApi->updateTuple()); /* We use the attribute id of the index, not the base table here */ CHECKEQUAL(0, bigUpdateOldApi->equal((Uint32)0, NdbDictionary::getValuePtr (baseRecord, smallRowBuf, 1))); CHECKEQUAL(0, bigUpdateOldApi->setValue((Uint32)1, NdbDictionary::getValuePtr (baseRecord, bigAttrIxBuf, 1))); CHECKEQUAL(-1, trans->execute(NdbTransaction::NoCommit)); /* ZGET_DATABUF_ERR expected */ CHECKEQUAL(218, trans->getNdbError().code) trans->close(); #endif /* Finished with error insert, cleanup the error insertion */ CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(), &smallKey[0], smallKeySize)); CHECKEQUAL(NDBT_OK, activateErrorInsert(trans, baseRecord, ctx->getTab(), smallRowBuf, &restarter, 8068)); trans->execute(NdbTransaction::Rollback); CHECKEQUAL(0, trans->getNdbError().code); trans->close(); return NDBT_OK; } int testSegmentedSectionScan(NDBT_Context* ctx, NDBT_Step* step){ /* Test that TC handling of segmented section exhaustion is * correct * Since NDBAPI always send long requests, that is all that * we test */ /* We just run on one table */ if (strcmp(ctx->getTab()->getName(), "WIDE_2COL") != 0) return NDBT_OK; const Uint32 maxRowBytes= NDB_MAX_TUPLE_SIZE_IN_WORDS * sizeof(Uint32); char smallKey[50]; char smallRowBuf[maxRowBytes]; Uint32 smallKeySize= setLongVarchar(&smallKey[0], "ShortKey", 8); const NdbRecord* record= ctx->getTab()->getDefaultRecord(); /* Setup buffers * Small row buffer with small key and small data */ setLongVarchar(NdbDictionary::getValuePtr(record, smallRowBuf, 0), "ShortKey", 8); NdbDictionary::setNull(record, smallRowBuf, 0, false); setLongVarchar(NdbDictionary::getValuePtr(record, smallRowBuf, 1), "ShortData", 9); NdbDictionary::setNull(record, smallRowBuf, 1, false); NdbRestarter restarter; Ndb* pNdb= GETNDB(step); /* Start a transaction on a specific node */ NdbTransaction* trans= pNdb->startTransaction(ctx->getTab(), &smallKey[0], smallKeySize); CHECKNOTNULL(trans); /* Activate error insert 8066 in this transaction, limits a * single import/append to 1 section. */ CHECKEQUAL(NDBT_OK, activateErrorInsert(trans, record, ctx->getTab(), smallRowBuf, &restarter, 8066)); /* A scan will always send 2 long sections (Receiver Ids, * AttrInfo) * Let's start a scan with > 2400 bytes of * ATTRINFO and see what happens */ NdbScanOperation* scan= trans->getNdbScanOperation(ctx->getTab()); CHECKNOTNULL(scan); CHECKEQUAL(0, scan->readTuples()); /* Create a particularly useless program */ NdbInterpretedCode prog; for (Uint32 w=0; w < 2500; w++) CHECKEQUAL(0, prog.load_const_null(1)); CHECKEQUAL(0, prog.interpret_exit_ok()); CHECKEQUAL(0, prog.finalise()); CHECKEQUAL(0, scan->setInterpretedCode(&prog)); /* Api doesn't seem to wait for result of scan request */ CHECKEQUAL(0, trans->execute(NdbTransaction::NoCommit)); CHECKEQUAL(0, trans->getNdbError().code); CHECKEQUAL(-1, scan->nextResult()); CHECKEQUAL(217, scan->getNdbError().code); trans->close(); /* Finished with error insert, cleanup the error insertion */ CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(), &smallKey[0], smallKeySize)); CHECKEQUAL(NDBT_OK, activateErrorInsert(trans, record, ctx->getTab(), smallRowBuf, &restarter, 8068)); CHECKEQUAL(0, trans->execute(NdbTransaction::Rollback)); CHECKEQUAL(0, trans->getNdbError().code); trans->close(); return NDBT_OK; } int testDropSignalFragments(NDBT_Context* ctx, NDBT_Step* step){ /* Segmented section exhaustion results in dropped signals * Fragmented signals split one logical signal over multiple * physical signals (to cope with the MAX_SIGNAL_LENGTH=32kB * limitation). * This testcase checks that when individual signals comprising * a fragmented signal (in this case SCANTABREQ) are dropped, the * system behaves correctly. * Correct behaviour is to behave in the same way as if the signal * was not fragmented, and for SCANTABREQ, to return a temporary * resource error. */ NdbRestarter restarter; Ndb* pNdb= GETNDB(step); /* SEND > ((2 * MAX_SEND_MESSAGE_BYTESIZE) + SOME EXTRA) * This way we get at least 3 fragments * However, as this is generally > 64kB, it's too much AttrInfo for * a ScanTabReq, so the 'success' case returns error 874 */ const Uint32 PROG_WORDS= 16500; struct SubCase { Uint32 errorInsertCode; int expectedRc; }; const Uint32 numSubCases= 5; const SubCase cases[numSubCases]= /* Error insert Scanrc */ {{ 0, 874}, // Normal, success which gives too much AI error { 8074, 217}, // Drop first fragment -> error 217 { 8075, 217}, // Drop middle fragment(s) -> error 217 { 8076, 217}, // Drop last fragment -> error 217 { 8077, 217}}; // Drop all fragments -> error 217 const Uint32 numIterations= 50; Uint32 buff[ PROG_WORDS + 10 ]; // 10 extra for final 'return' etc. for (Uint32 iteration=0; iteration < (numIterations * numSubCases); iteration++) { /* Start a transaction */ NdbTransaction* trans= pNdb->startTransaction(); CHECKNOTNULL(trans); SubCase subcase= cases[iteration % numSubCases]; Uint32 errorInsertVal= subcase.errorInsertCode; // printf("Inserting error : %u\n", errorInsertVal); /* We insert the error twice, to bias races between * error-insert propagation and the succeeding scan * in favour of error insert winning! * This problem needs a more general fix */ CHECKEQUAL(0, restarter.insertErrorInAllNodes(errorInsertVal)); CHECKEQUAL(0, restarter.insertErrorInAllNodes(errorInsertVal)); NdbScanOperation* scan= trans->getNdbScanOperation(ctx->getTab()); CHECKNOTNULL(scan); CHECKEQUAL(0, scan->readTuples()); /* Create a large program, to give a large SCANTABREQ */ NdbInterpretedCode prog(ctx->getTab(), buff, PROG_WORDS + 10); for (Uint32 w=0; w < PROG_WORDS; w++) CHECKEQUAL(0, prog.load_const_null(1)); CHECKEQUAL(0, prog.interpret_exit_ok()); CHECKEQUAL(0, prog.finalise()); CHECKEQUAL(0, scan->setInterpretedCode(&prog)); /* Api doesn't seem to wait for result of scan request */ CHECKEQUAL(0, trans->execute(NdbTransaction::NoCommit)); CHECKEQUAL(0, trans->getNdbError().code); CHECKEQUAL(-1, scan->nextResult()); int expectedResult= subcase.expectedRc; CHECKEQUAL(expectedResult, scan->getNdbError().code); scan->close(); trans->close(); } restarter.insertErrorInAllNodes(0); return NDBT_OK; } int create100Tables(NDBT_Context* ctx, NDBT_Step* step) { Ndb* pNdb = GETNDB(step); const NdbDictionary::Table* pTab= ctx->getTab(); /* Run as a 'T1' testcase - do nothing for other tables */ if (strcmp(pTab->getName(), "T1") != 0) return NDBT_OK; for (Uint32 t=0; t < 100; t++) { char tabnameBuff[10]; snprintf(tabnameBuff, sizeof(tabnameBuff), "TAB%u", t); NdbDictionary::Table tab; tab.setName(tabnameBuff); NdbDictionary::Column pk; pk.setName("PK"); pk.setType(NdbDictionary::Column::Varchar); pk.setLength(20); pk.setNullable(false); pk.setPrimaryKey(true); tab.addColumn(pk); pNdb->getDictionary()->dropTable(tab.getName()); if(pNdb->getDictionary()->createTable(tab) != 0) { ndbout << "Create table failed with error : " << pNdb->getDictionary()->getNdbError().code << " " << pNdb->getDictionary()->getNdbError().message << endl; return NDBT_FAILED; } ndbout << "Created table " << tabnameBuff << endl; } return NDBT_OK; } int drop100Tables(NDBT_Context* ctx, NDBT_Step* step) { Ndb* pNdb = GETNDB(step); const NdbDictionary::Table* pTab= ctx->getTab(); /* Run as a 'T1' testcase - do nothing for other tables */ if (strcmp(pTab->getName(), "T1") != 0) return NDBT_OK; for (Uint32 t=0; t < 100; t++) { char tabnameBuff[10]; snprintf(tabnameBuff, sizeof(tabnameBuff), "TAB%u", t); if (pNdb->getDictionary()->dropTable(tabnameBuff) != 0) { ndbout << "Drop table failed with error : " << pNdb->getDictionary()->getNdbError().code << " " << pNdb->getDictionary()->getNdbError().message << endl; } else { ndbout << "Dropped table " << tabnameBuff << endl; } } return NDBT_OK; } int dropTable(NDBT_Context* ctx, NDBT_Step* step, Uint32 num) { Ndb* pNdb = GETNDB(step); const NdbDictionary::Table* pTab= ctx->getTab(); /* Run as a 'T1' testcase - do nothing for other tables */ if (strcmp(pTab->getName(), "T1") != 0) return NDBT_OK; char tabnameBuff[10]; snprintf(tabnameBuff, sizeof(tabnameBuff), "TAB%u", num); if (pNdb->getDictionary()->dropTable(tabnameBuff) != 0) { ndbout << "Drop table failed with error : " << pNdb->getDictionary()->getNdbError().code << " " << pNdb->getDictionary()->getNdbError().message << endl; } else { ndbout << "Dropped table " << tabnameBuff << endl; } return NDBT_OK; } enum Scenarios { // NORMAL, // Commented to save some time. DROP_TABLE, RESTART_MASTER, RESTART_SLAVE, NUM_SCENARIOS }; enum Tasks { WAIT = 0, DROP_TABLE_REQ = 1, MASTER_RESTART_REQ = 2, SLAVE_RESTART_REQ = 3 }; int testWorker(NDBT_Context* ctx, NDBT_Step* step) { /* Run as a 'T1' testcase - do nothing for other tables */ if (strcmp(ctx->getTab()->getName(), "T1") != 0) return NDBT_OK; /* Worker step to run in a separate thread for * blocking activities * Generally the blocking of the DIH table definition flush * blocks the completion of the drop table/node restarts, * so this must be done in a separate thread to avoid * deadlocks. */ while (!ctx->isTestStopped()) { ndbout_c("Worker : waiting for request..."); ctx->getPropertyWait("DIHWritesRequest", 1); if (!ctx->isTestStopped()) { Uint32 req = ctx->getProperty("DIHWritesRequestType", (Uint32)0); switch ((Tasks) req) { case DROP_TABLE_REQ: { /* Drop table */ ndbout_c("Worker : dropping table"); if (dropTable(ctx, step, 2) != NDBT_OK) { return NDBT_FAILED; } ndbout_c("Worker : table dropped."); break; } case MASTER_RESTART_REQ: { ndbout_c("Worker : restarting Master"); NdbRestarter restarter; int master_nodeid = restarter.getMasterNodeId(); ndbout_c("Worker : Restarting Master (%d)...", master_nodeid); if (restarter.restartOneDbNode2(master_nodeid, NdbRestarter::NRRF_NOSTART | NdbRestarter::NRRF_FORCE | NdbRestarter::NRRF_ABORT) || restarter.waitNodesNoStart(&master_nodeid, 1) || restarter.startAll()) { ndbout_c("Worker : Error restarting Master."); return NDBT_FAILED; } ndbout_c("Worker : Waiting for master to recover..."); if (restarter.waitNodesStarted(&master_nodeid, 1)) { ndbout_c("Worker : Error waiting for Master restart"); return NDBT_FAILED; } ndbout_c("Worker : Master recovered."); break; } case SLAVE_RESTART_REQ: { NdbRestarter restarter; int slave_nodeid = restarter.getRandomNotMasterNodeId(rand()); ndbout_c("Worker : Restarting non-master (%d)...", slave_nodeid); if (restarter.restartOneDbNode2(slave_nodeid, NdbRestarter::NRRF_NOSTART | NdbRestarter::NRRF_FORCE | NdbRestarter::NRRF_ABORT) || restarter.waitNodesNoStart(&slave_nodeid, 1) || restarter.startAll()) { ndbout_c("Worker : Error restarting Slave."); return NDBT_FAILED; } ndbout_c("Worker : Waiting for slave to recover..."); if (restarter.waitNodesStarted(&slave_nodeid, 1)) { ndbout_c("Worker : Error waiting for Slave restart"); return NDBT_FAILED; } ndbout_c("Worker : Slave recovered."); break; } default: { break; } } } ctx->setProperty("DIHWritesRequestType", (Uint32) 0); ctx->setProperty("DIHWritesRequest", (Uint32) 2); } ndbout_c("Worker, done."); return NDBT_OK; } int testSlowDihFileWrites(NDBT_Context* ctx, NDBT_Step* step) { /* Testcase checks behaviour with slow flushing of DIH table definitions * This caused problems in the past by exhausting the DIH page pool * Now there's a concurrent operations limit. * Check that it behaves with many queued ops, parallel drop/node restarts */ /* Run as a 'T1' testcase - do nothing for other tables */ if (strcmp(ctx->getTab()->getName(), "T1") != 0) return NDBT_OK; /* 1. Activate slow write error insert * 2. Trigger LCP * 3. Wait some time, periodically producing info on * the internal state * 4. Perform some parallel action (drop table/node restarts) * 5. Wait some time, periodically producing info on * the internal state * 6. Clear the error insert * 7. Wait a little longer * 8. Done. */ NdbRestarter restarter; for (Uint32 scenario = 0; scenario < NUM_SCENARIOS; scenario++) { ndbout_c("Inserting error 7235"); restarter.insertErrorInAllNodes(7235); ndbout_c("Triggering LCP"); int dumpArg = 7099; restarter.dumpStateAllNodes(&dumpArg, 1); const Uint32 periodSeconds = 10; Uint32 waitPeriods = 6; dumpArg = 7032; for (Uint32 p=0; psetProperty("DIHWritesRequestType", (Uint32) DROP_TABLE_REQ); ctx->setProperty("DIHWritesRequest", (Uint32) 1); break; } case RESTART_MASTER: { ndbout_c("Requesting Master restart"); ctx->setProperty("DIHWritesRequestType", (Uint32) MASTER_RESTART_REQ); ctx->setProperty("DIHWritesRequest", (Uint32) 1); break; } case RESTART_SLAVE: { ndbout_c("Requesting Slave restart"); ctx->setProperty("DIHWritesRequestType", (Uint32) SLAVE_RESTART_REQ); ctx->setProperty("DIHWritesRequest", (Uint32) 1); break; } default: break; } } ndbout_c("Dumping DIH page info to ndbd stdout"); restarter.dumpStateAllNodes(&dumpArg, 1); NdbSleep_MilliSleep(periodSeconds * 1000); } ndbout_c("Clearing error insert..."); restarter.insertErrorInAllNodes(0); waitPeriods = 2; for (Uint32 p=0; pgetPropertyWait("DIHWritesRequest", 2); if (ctx->isTestStopped()) return NDBT_OK; ndbout_c("Done."); } /* Finish up */ ctx->stopTest(); return NDBT_OK; } NDBT_TESTSUITE(testLimits); TESTCASE("ExhaustSegmentedSectionPk", "Test behaviour at Segmented Section exhaustion for PK"){ INITIALIZER(testSegmentedSectionPk); } TESTCASE("ExhaustSegmentedSectionIX", "Test behaviour at Segmented Section exhaustion for Unique index"){ INITIALIZER(testSegmentedSectionIx); } TESTCASE("ExhaustSegmentedSectionScan", "Test behaviour at Segmented Section exhaustion for Scan"){ INITIALIZER(testSegmentedSectionScan); } TESTCASE("DropSignalFragments", "Test behaviour of Segmented Section exhaustion with fragmented signals"){ INITIALIZER(testDropSignalFragments); } TESTCASE("SlowDihFileWrites", "Test behaviour of slow Dih table file writes") { INITIALIZER(create100Tables); STEP(testWorker); STEP(testSlowDihFileWrites); FINALIZER(drop100Tables); } NDBT_TESTSUITE_END(testLimits); int main(int argc, const char** argv){ ndb_init(); NDBT_TESTSUITE_INSTANCE(testLimits); return testLimits.execute(argc, argv); }