1512 lines
37 KiB
C++
1512 lines
37 KiB
C++
/*
|
|
Copyright (c) 2012, 2015, 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 St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include <NDBT.hpp>
|
|
#include <NDBT_Test.hpp>
|
|
#include <HugoOperations.hpp>
|
|
#include <NdbRestarter.hpp>
|
|
#include <mgmapi.h>
|
|
#include <ndb_logevent.h>
|
|
#include <NdbTick.h>
|
|
#include <NDBT_Stats.hpp>
|
|
#include <random.h>
|
|
#include <NdbMgmd.hpp>
|
|
|
|
static NdbMutex* g_msgmutex = 0;
|
|
|
|
#undef require
|
|
#define require(b) \
|
|
if (!(b)) { \
|
|
NdbMutex_Lock(g_msgmutex); \
|
|
g_err << "ABORT: " << #b << " failed at line " << __LINE__ << endl; \
|
|
NdbMutex_Unlock(g_msgmutex); \
|
|
abort(); \
|
|
}
|
|
|
|
#define CHK1(b) \
|
|
if (!(b)) { \
|
|
NdbMutex_Lock(g_msgmutex); \
|
|
g_err << "ERROR: " << #b << " failed at line " << __LINE__ << endl; \
|
|
NdbMutex_Unlock(g_msgmutex); \
|
|
result = NDBT_FAILED; \
|
|
break; \
|
|
}
|
|
|
|
#define CHK2(b, e) \
|
|
if (!(b)) { \
|
|
NdbMutex_Lock(g_msgmutex); \
|
|
g_err << "ERROR: " << #b << " failed at line " << __LINE__ \
|
|
<< ": " << e << endl; \
|
|
NdbMutex_Unlock(g_msgmutex); \
|
|
result = NDBT_FAILED; \
|
|
break; \
|
|
}
|
|
|
|
#define info(x) \
|
|
do { \
|
|
NdbMutex_Lock(g_msgmutex); \
|
|
g_info << x << endl; \
|
|
NdbMutex_Unlock(g_msgmutex); \
|
|
} while (0)
|
|
|
|
static const int g_tabmax = 3;
|
|
static const char* g_tabname[g_tabmax] = { "T1", "T2", "T4" };
|
|
static const NdbDictionary::Table* g_tabptr[g_tabmax] = { 0, 0, 0 };
|
|
|
|
static int
|
|
runCreate(NDBT_Context* ctx, NDBT_Step* step)
|
|
{
|
|
Ndb* pNdb = GETNDB(step);
|
|
NdbDictionary::Dictionary* pDic = pNdb->getDictionary();
|
|
int result = NDBT_OK;
|
|
int tabmask = ctx->getProperty("TABMASK", (Uint32)0);
|
|
|
|
for (int i = 0; i < g_tabmax; i++)
|
|
{
|
|
if (!(tabmask & (1 << i)))
|
|
continue;
|
|
const char* tabname = g_tabname[i];
|
|
(void)pDic->dropTable(tabname);
|
|
|
|
const NdbDictionary::Table* pTab = NDBT_Tables::getTable(tabname);
|
|
require(pTab != 0);
|
|
NdbDictionary::Table tab2(*pTab);
|
|
// make sure to hit all log parts
|
|
tab2.setFragmentType(NdbDictionary::Object::FragAllLarge);
|
|
//tab2.setMaxRows(100000000);
|
|
CHK2(pDic->createTable(tab2) == 0, pDic->getNdbError());
|
|
|
|
g_tabptr[i] = pDic->getTable(tabname);
|
|
require(g_tabptr[i] != 0);
|
|
info("created " << tabname);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static int
|
|
runDrop(NDBT_Context* ctx, NDBT_Step* step)
|
|
{
|
|
Ndb* pNdb = GETNDB(step);
|
|
NdbDictionary::Dictionary* pDic = pNdb->getDictionary();
|
|
int result = NDBT_OK;
|
|
int tabmask = ctx->getProperty("TABMASK", (Uint32)0);
|
|
|
|
for (int i = 0; i < g_tabmax; i++)
|
|
{
|
|
if (!(tabmask & (1 << i)))
|
|
continue;
|
|
const char* tabname = g_tabname[i];
|
|
if (g_tabptr[i] != 0)
|
|
{
|
|
CHK2(pDic->dropTable(tabname) == 0, pDic->getNdbError());
|
|
g_tabptr[i] = 0;
|
|
info("dropped " << tabname);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// 0-writer has not seen 410 error
|
|
// 1-writer sees 410 error
|
|
// 2-longtrans has rolled back
|
|
|
|
static int
|
|
get_err410(NDBT_Context* ctx)
|
|
{
|
|
int v = (int)ctx->getProperty("ERR410", (Uint32)0);
|
|
require(v == 0 || v == 1 || v == 2);
|
|
return v;
|
|
}
|
|
|
|
static void
|
|
set_err410(NDBT_Context* ctx, int v)
|
|
{
|
|
require(v == 0 || v == 1 || v == 2);
|
|
require(get_err410(ctx) != v);
|
|
ctx->setProperty("ERR410", (Uint32)v);
|
|
}
|
|
|
|
static int
|
|
runLongtrans(NDBT_Context* ctx, NDBT_Step* step)
|
|
{
|
|
Ndb* pNdb = GETNDB(step);
|
|
int result = NDBT_OK;
|
|
const int sleep410 = ctx->getProperty("SLEEP410", (Uint32)0);
|
|
|
|
const NdbDictionary::Table* pTab = g_tabptr[0];
|
|
require(pTab != 0);
|
|
|
|
info("longtrans: start");
|
|
int loop = 0;
|
|
while (!ctx->isTestStopped())
|
|
{
|
|
info("longtrans: loop " << loop);
|
|
HugoOperations ops(*pTab);
|
|
ops.setQuiet();
|
|
CHK2(ops.startTransaction(pNdb) == 0, ops.getNdbError());
|
|
CHK2(ops.pkInsertRecord(pNdb, 0, 1, 0) == 0, ops.getNdbError());
|
|
|
|
while (!ctx->isTestStopped())
|
|
{
|
|
int v = get_err410(ctx);
|
|
require(v == 0 || v == 1);
|
|
if (v != 0)
|
|
{
|
|
info("longtrans: 410 seen");
|
|
if (sleep410 > 0)
|
|
{
|
|
info("longtrans: sleep " << sleep410);
|
|
sleep(sleep410);
|
|
}
|
|
|
|
CHK2(ops.execute_Rollback(pNdb) == 0, ops.getNdbError());
|
|
ops.closeTransaction(pNdb);
|
|
info("longtrans: rollback done");
|
|
set_err410(ctx, 2);
|
|
|
|
while (!ctx->isTestStopped())
|
|
{
|
|
int v = get_err410(ctx);
|
|
if (v != 0)
|
|
{
|
|
}
|
|
else
|
|
{
|
|
info("longtrans: 410 cleared");
|
|
break;
|
|
}
|
|
sleep(1);
|
|
}
|
|
break;
|
|
}
|
|
sleep(1);
|
|
}
|
|
CHK1(result == NDBT_OK);
|
|
|
|
if (ops.getTransaction() != NULL)
|
|
{
|
|
info("longtrans: close leftover transaction");
|
|
ops.closeTransaction(pNdb);
|
|
}
|
|
|
|
loop++;
|
|
}
|
|
|
|
info("longtrans: stop");
|
|
return result;
|
|
}
|
|
|
|
static int
|
|
run_write_ops(NDBT_Context* ctx, NDBT_Step* step, int upval, NdbError& err, bool abort_on_error=false)
|
|
{
|
|
Ndb* pNdb = GETNDB(step);
|
|
const int records = ctx->getNumRecords();
|
|
int result = NDBT_OK;
|
|
|
|
const NdbDictionary::Table* pTab = g_tabptr[1];
|
|
require(pTab != 0);
|
|
|
|
while (!ctx->isTestStopped())
|
|
{
|
|
HugoOperations ops(*pTab);
|
|
ops.setQuiet();
|
|
CHK2(ops.startTransaction(pNdb) == 0, ops.getNdbError());
|
|
|
|
for (int record = 0; record < records; record++)
|
|
{
|
|
CHK2(ops.pkWriteRecord(pNdb, record, 1, upval) == 0, ops.getNdbError());
|
|
}
|
|
CHK1(result == NDBT_OK);
|
|
|
|
int ret = ops.execute_Commit(pNdb);
|
|
err = ops.getNdbError();
|
|
ops.closeTransaction(pNdb);
|
|
|
|
if (ret == 0)
|
|
break;
|
|
|
|
require(err.code != 0);
|
|
CHK2(err.status == NdbError::TemporaryError, err);
|
|
|
|
if (abort_on_error)
|
|
{
|
|
g_err << "Temporary error " << err.code << " during write" << endl;
|
|
result = NDBT_FAILED;
|
|
break;
|
|
}
|
|
|
|
if (err.code == 410)
|
|
break;
|
|
|
|
info("write: continue on " << err);
|
|
NdbSleep_MilliSleep(100);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static int
|
|
runWriteOK(NDBT_Context* ctx, NDBT_Step* step)
|
|
{
|
|
int result = NDBT_OK;
|
|
|
|
info("write: start");
|
|
int loop = 0;
|
|
int upval = 0;
|
|
|
|
while (!ctx->isTestStopped())
|
|
{
|
|
if (loop % 100 == 0)
|
|
info("write: loop " << loop);
|
|
|
|
NdbError err;
|
|
CHK2(run_write_ops(ctx, step, upval++, err) == 0, err);
|
|
require(err.code == 0 || err.code == 410);
|
|
CHK2(err.code == 0, err);
|
|
NdbSleep_MilliSleep(100);
|
|
|
|
loop++;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static int
|
|
runWrite410(NDBT_Context* ctx, NDBT_Step* step)
|
|
{
|
|
int result = NDBT_OK;
|
|
const int loops = ctx->getNumLoops();
|
|
|
|
info("write: start");
|
|
int loop = 0;
|
|
int upval = 0;
|
|
|
|
while (loop < loops && !ctx->isTestStopped())
|
|
{
|
|
info("write: loop " << loop);
|
|
|
|
while (!ctx->isTestStopped())
|
|
{
|
|
NdbError err;
|
|
CHK2(run_write_ops(ctx, step, upval++, err) == 0, err);
|
|
if (err.code != 0)
|
|
{
|
|
require(err.code == 410);
|
|
info("write: setting 410");
|
|
set_err410(ctx, 1);
|
|
break;
|
|
}
|
|
NdbSleep_MilliSleep(100);
|
|
}
|
|
|
|
while (1)
|
|
{
|
|
int v = get_err410(ctx);
|
|
if (v != 2)
|
|
{
|
|
require(v == 1);
|
|
}
|
|
else
|
|
{
|
|
info("write: longtrans rollback seen");
|
|
break;
|
|
}
|
|
sleep(1);
|
|
}
|
|
|
|
while (!ctx->isTestStopped())
|
|
{
|
|
NdbError err;
|
|
CHK2(run_write_ops(ctx, step, upval++, err) == 0, err);
|
|
if (err.code == 0)
|
|
{
|
|
info("write: clearing 410");
|
|
set_err410(ctx, 0);
|
|
break;
|
|
}
|
|
require(err.code == 410);
|
|
NdbSleep_MilliSleep(100);
|
|
}
|
|
|
|
loop++;
|
|
}
|
|
|
|
info("write: stop test");
|
|
ctx->stopTest();
|
|
return result;
|
|
}
|
|
|
|
struct OpLat {
|
|
const int m_op;
|
|
const int m_repeat;
|
|
// 2: 0-410 off 1-410 on
|
|
// 3: 0-op ok 1-op 410 2-op other temp error
|
|
NDBT_Stats m_lat[2][3];
|
|
OpLat(int op, int repeat) :
|
|
m_op(op),
|
|
m_repeat(repeat) {}
|
|
};
|
|
|
|
static void
|
|
run_latency_report(const OpLat* oplist, int opcnt)
|
|
{
|
|
for (int i = 0; i < opcnt; i++)
|
|
{
|
|
const OpLat& oplat = oplist[i];
|
|
printf("optype: %d\n", oplat.m_op);
|
|
for (int i0 = 0; i0 < 2; i0++)
|
|
{
|
|
printf("410 off/on: %d\n", i0);
|
|
printf("op status ok / 410 / other temp error:\n");
|
|
for (int i1 = 0; i1 < 3; i1++)
|
|
{
|
|
const NDBT_Stats& lat = oplat.m_lat[i0][i1];
|
|
printf("count: %d", lat.getCount());
|
|
if (lat.getCount() > 0)
|
|
{
|
|
printf(" mean: %.2f min: %.2f max: %.2f stddev: %.2f",
|
|
lat.getMean(), lat.getMin(), lat.getMax(), lat.getStddev());
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
run_latency_ops(NDBT_Context* ctx, NDBT_Step* step, OpLat& oplat, int upval, NdbError& err)
|
|
{
|
|
Ndb* pNdb = GETNDB(step);
|
|
const int records = ctx->getNumRecords();
|
|
int result = NDBT_OK;
|
|
|
|
const NdbDictionary::Table* pTab = g_tabptr[2];
|
|
require(pTab != 0);
|
|
|
|
int record = 0;
|
|
while (record < records && !ctx->isTestStopped())
|
|
{
|
|
HugoOperations ops(*pTab);
|
|
ops.setQuiet();
|
|
|
|
const NDB_TICKS timer_start = NdbTick_getCurrentTicks();
|
|
|
|
CHK2(ops.startTransaction(pNdb) == 0, ops.getNdbError());
|
|
|
|
switch (oplat.m_op)
|
|
{
|
|
case NdbOperation::InsertRequest:
|
|
CHK2(ops.pkInsertRecord(pNdb, record, 1, upval) == 0, ops.getNdbError());
|
|
break;
|
|
case NdbOperation::UpdateRequest:
|
|
CHK2(ops.pkUpdateRecord(pNdb, record, 1, upval) == 0, ops.getNdbError());
|
|
break;
|
|
case NdbOperation::ReadRequest:
|
|
CHK2(ops.pkReadRecord(pNdb, record, 1) == 0, ops.getNdbError());
|
|
break;
|
|
case NdbOperation::DeleteRequest:
|
|
CHK2(ops.pkDeleteRecord(pNdb, record, 1) == 0, ops.getNdbError());
|
|
break;
|
|
default:
|
|
require(false);
|
|
break;
|
|
}
|
|
CHK2(result == NDBT_OK, "latency: ndbapi error at op " << oplat.m_op << " record" << record);
|
|
|
|
int ret = ops.execute_Commit(pNdb);
|
|
err = ops.getNdbError();
|
|
ops.closeTransaction(pNdb);
|
|
|
|
if (ret != 0)
|
|
{
|
|
require(err.code != 0);
|
|
CHK2(err.status == NdbError::TemporaryError, err);
|
|
}
|
|
|
|
const NDB_TICKS timer_stop = NdbTick_getCurrentTicks();
|
|
Uint64 tt = NdbTick_Elapsed(timer_start, timer_stop).microSec();
|
|
require(tt > 0);
|
|
double td = (double)tt;
|
|
int i0 = get_err410(ctx);
|
|
int i1 = (ret == 0 ? 0 : (err.code == 410 ? 1 : 2));
|
|
NDBT_Stats& lat = oplat.m_lat[i0][i1];
|
|
lat.addObservation(td);
|
|
|
|
if (ret == 0)
|
|
record++;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static int
|
|
runLatency(NDBT_Context* ctx, NDBT_Step* step)
|
|
{
|
|
int result = NDBT_OK;
|
|
|
|
info("latency: start");
|
|
const int opcnt = 4;
|
|
OpLat oplist[opcnt] = {
|
|
OpLat(NdbOperation::InsertRequest, 1),
|
|
OpLat(NdbOperation::UpdateRequest, 10),
|
|
OpLat(NdbOperation::ReadRequest, 5),
|
|
OpLat(NdbOperation::DeleteRequest, 1)
|
|
};
|
|
|
|
int loop = 0;
|
|
int upval = 0;
|
|
|
|
while (!ctx->isTestStopped())
|
|
{
|
|
info("latency: loop " << loop);
|
|
for (int i = 0; i < opcnt && !ctx->isTestStopped(); i++)
|
|
{
|
|
OpLat& oplat = oplist[i];
|
|
NdbError err;
|
|
for (int j = 0; j < oplat.m_repeat && !ctx->isTestStopped(); j++)
|
|
{
|
|
CHK2(run_latency_ops(ctx, step, oplat, upval, err) == 0, err);
|
|
upval++;
|
|
}
|
|
CHK1(result == NDBT_OK);
|
|
}
|
|
loop++;
|
|
}
|
|
|
|
run_latency_report(oplist, opcnt);
|
|
return result;
|
|
}
|
|
|
|
// gnu bitmap madness
|
|
#undef reset
|
|
#undef isset
|
|
|
|
struct LogPos {
|
|
int m_fileno;
|
|
int m_mb;
|
|
int m_pos; // absolute mb
|
|
friend NdbOut& operator<<(NdbOut&, const LogPos&);
|
|
};
|
|
|
|
NdbOut&
|
|
operator<<(NdbOut& out, const LogPos& pos)
|
|
{
|
|
out << pos.m_fileno;
|
|
out << "." << pos.m_mb;
|
|
out << "-" << pos.m_pos;
|
|
return out;
|
|
}
|
|
|
|
struct LogPart {
|
|
int m_partno; // for print
|
|
bool m_set;
|
|
int m_files; // redo files
|
|
int m_filesize; // mb
|
|
int m_total; // m_files * m_filesize
|
|
int m_free; // mb
|
|
int m_used; // mb
|
|
LogPos m_head;
|
|
LogPos m_tail;
|
|
int m_fileused;
|
|
void reset() {
|
|
m_set = false;
|
|
}
|
|
bool isset() {
|
|
return m_set;
|
|
}
|
|
friend NdbOut& operator<<(NdbOut&, const LogPart&);
|
|
};
|
|
|
|
NdbOut&
|
|
operator<<(NdbOut& out, const LogPart& lp)
|
|
{
|
|
out << "part " << lp.m_partno << ":";
|
|
out << " files=" << lp.m_files;
|
|
out << " filesize=" << lp.m_filesize;
|
|
out << " total=" << lp.m_total;
|
|
out << " free=" << lp.m_free;
|
|
out << " head: " << lp.m_head;
|
|
out << " tail: " << lp.m_tail;
|
|
out << " fileused=" << lp.m_fileused;
|
|
return out;
|
|
}
|
|
|
|
struct LogNode {
|
|
int m_nodeid;
|
|
LogPart m_logpart[4];
|
|
int m_files; // from LogPart (must be same for all)
|
|
int m_filesize;
|
|
int m_minfds; // min and max FDs in page 0
|
|
int m_maxfds; // LQH uses max FDs by default
|
|
void reset() {
|
|
for (int i = 0; i < 4; i++) {
|
|
m_logpart[i].m_partno = i;
|
|
m_logpart[i].reset();
|
|
}
|
|
}
|
|
bool isset() {
|
|
for (int i = 0; i < 4; i++)
|
|
if (!m_logpart[i].isset())
|
|
return false;
|
|
return true;
|
|
}
|
|
};
|
|
|
|
struct LogInfo {
|
|
int m_nodes;
|
|
LogNode* m_lognode;
|
|
int m_files; // from LogNode (config is same for all in these tests)
|
|
int m_filesize;
|
|
int m_minfds;
|
|
int m_maxfds;
|
|
LogInfo(int nodes) {
|
|
m_nodes = nodes;
|
|
m_lognode = new LogNode [nodes];
|
|
reset();
|
|
}
|
|
~LogInfo() {
|
|
m_nodes = 0;
|
|
delete [] m_lognode;
|
|
}
|
|
void reset() {
|
|
for (int n = 0; n < m_nodes; n++)
|
|
m_lognode[n].reset();
|
|
}
|
|
bool isset() {
|
|
for (int n = 0; n < m_nodes; n++)
|
|
if (!m_lognode[n].isset())
|
|
return false;
|
|
return true;
|
|
}
|
|
LogNode* findnode(int nodeid) const {
|
|
for (int n = 0; n < m_nodes; n++) {
|
|
if (m_lognode[n].m_nodeid == nodeid)
|
|
return &m_lognode[n];
|
|
}
|
|
return 0;
|
|
}
|
|
void copyto(LogInfo& li2) const {
|
|
require(m_nodes == li2.m_nodes);
|
|
for (int n = 0; n < m_nodes; n++) {
|
|
const LogNode& ln1 = m_lognode[n];
|
|
LogNode& ln2 = li2.m_lognode[n];
|
|
ln2 = ln1;
|
|
}
|
|
}
|
|
};
|
|
|
|
static int
|
|
get_nodestatus(NdbMgmHandle h, LogInfo& li)
|
|
{
|
|
int result = 0;
|
|
ndb_mgm_cluster_state* cs = 0;
|
|
|
|
do
|
|
{
|
|
require(h != 0);
|
|
CHK2((cs = ndb_mgm_get_status(h)) != 0, ndb_mgm_get_latest_error_msg(h));
|
|
int n = 0;
|
|
for (int i = 0; i < cs->no_of_nodes; i++)
|
|
{
|
|
ndb_mgm_node_state& ns = cs->node_states[i];
|
|
if (ns.node_type == NDB_MGM_NODE_TYPE_NDB)
|
|
{
|
|
// called only when all started
|
|
CHK1(ns.node_status == NDB_MGM_NODE_STATUS_STARTED);
|
|
CHK1(n < li.m_nodes);
|
|
|
|
LogNode& ln = li.m_lognode[n];
|
|
ln.m_nodeid = ns.node_id;
|
|
info("node " << n << ": " << ln.m_nodeid);
|
|
n++;
|
|
}
|
|
CHK1(result == 0);
|
|
}
|
|
CHK1(n == li.m_nodes);
|
|
}
|
|
while (0);
|
|
|
|
free(cs);
|
|
info("get_nodestatus result=" << result);
|
|
return result;
|
|
}
|
|
|
|
static int
|
|
get_redostatus(NdbMgmHandle h, LogInfo& li)
|
|
{
|
|
int result = 0;
|
|
|
|
do
|
|
{
|
|
li.reset();
|
|
require(h != 0);
|
|
|
|
int filter[] = { 15, NDB_MGM_EVENT_CATEGORY_CHECKPOINT, 0 };
|
|
NdbLogEventHandle evh = 0;
|
|
CHK2((evh = ndb_mgm_create_logevent_handle(h, filter)) != 0, ndb_mgm_get_latest_error_msg(h));
|
|
|
|
for (int n = 0; n < li.m_nodes; n++)
|
|
{
|
|
int dump[] = { 2399 };
|
|
const LogNode& ln = li.m_lognode[n];
|
|
struct ndb_mgm_reply reply;
|
|
CHK2(ndb_mgm_dump_state(h, ln.m_nodeid, dump, 1, &reply) == 0, ndb_mgm_get_latest_error_msg(h));
|
|
}
|
|
CHK1(result == 0);
|
|
|
|
int maxcnt = 4 * li.m_nodes;
|
|
int rescnt = 0;
|
|
time_t start = time(0);
|
|
int maxwait = 5;
|
|
|
|
while (rescnt < maxcnt && time(0) < start + maxwait)
|
|
{
|
|
while (1)
|
|
{
|
|
int res;
|
|
ndb_logevent ev;
|
|
int msec = 100;
|
|
CHK2((res = ndb_logevent_get_next(evh, &ev, msec)) >= 0, ndb_mgm_get_latest_error_msg(h));
|
|
if (res == 0)
|
|
break;
|
|
if (ev.type != NDB_LE_RedoStatus)
|
|
continue;
|
|
|
|
LogNode* lnptr = 0;
|
|
CHK2((lnptr = li.findnode(ev.source_nodeid)) != 0, "unknown nodeid " << ev.source_nodeid);
|
|
LogNode& ln = *lnptr;
|
|
|
|
const ndb_logevent_RedoStatus& rs = ev.RedoStatus;
|
|
CHK1(rs.log_part < 4);
|
|
LogPart& lp = ln.m_logpart[rs.log_part];
|
|
|
|
CHK1(!lp.m_set);
|
|
LogPos& head = lp.m_head;
|
|
LogPos& tail = lp.m_tail;
|
|
lp.m_files = rs.no_logfiles;
|
|
lp.m_filesize = rs.logfilesize;
|
|
head.m_fileno = rs.head_file_no;
|
|
head.m_mb = rs.head_mbyte;
|
|
head.m_pos = head.m_fileno * lp.m_filesize + head.m_mb;
|
|
tail.m_fileno = rs.tail_file_no;
|
|
tail.m_mb = rs.tail_mbyte;
|
|
tail.m_pos = tail.m_fileno * lp.m_filesize + tail.m_mb;
|
|
CHK1(rs.total_hi == 0 && rs.total_lo < (1u << 31));
|
|
lp.m_total = rs.total_lo;
|
|
CHK1(rs.free_hi == 0 && rs.free_lo < (1u << 31));
|
|
lp.m_free = rs.free_lo;
|
|
lp.m_used = lp.m_total - lp.m_free;
|
|
|
|
// set number of files used
|
|
if (tail.m_fileno < head.m_fileno)
|
|
{
|
|
lp.m_fileused = head.m_fileno - tail.m_fileno + 1;
|
|
}
|
|
else if (tail.m_fileno > head.m_fileno)
|
|
{
|
|
lp.m_fileused = lp.m_files - (tail.m_fileno - head.m_fileno - 1);
|
|
}
|
|
else if (tail.m_pos < head.m_pos)
|
|
{
|
|
lp.m_fileused = 1;
|
|
}
|
|
else if (tail.m_pos > head.m_pos)
|
|
{
|
|
lp.m_fileused = lp.m_files;
|
|
}
|
|
else
|
|
{
|
|
lp.m_fileused = 0;
|
|
}
|
|
|
|
// sanity checks
|
|
{
|
|
CHK2(lp.m_total == lp.m_files * lp.m_filesize, lp);
|
|
CHK2(head.m_fileno < lp.m_files, lp);
|
|
CHK2(head.m_mb < lp.m_filesize, lp);
|
|
require(head.m_pos < lp.m_total);
|
|
CHK2(tail.m_fileno < lp.m_files, lp);
|
|
CHK2(tail.m_mb < lp.m_filesize, lp);
|
|
require(tail.m_pos < lp.m_total);
|
|
CHK2(lp.m_free <= lp.m_total, lp);
|
|
if (tail.m_pos <= head.m_pos)
|
|
{
|
|
CHK2(lp.m_free == lp.m_total - (head.m_pos - tail.m_pos), lp);
|
|
}
|
|
else
|
|
{
|
|
CHK2(lp.m_free == tail.m_pos - head.m_pos, lp);
|
|
}
|
|
}
|
|
lp.m_set = true;
|
|
//info("node " << ln.m_nodeid << ": " << lp);
|
|
|
|
rescnt++;
|
|
}
|
|
CHK1(result == 0);
|
|
}
|
|
CHK1(result == 0);
|
|
CHK2(rescnt == maxcnt, "got events " << rescnt << " != " << maxcnt);
|
|
require(li.isset()); // already implied by counts
|
|
|
|
for (int n = 0; n < li.m_nodes; n++)
|
|
{
|
|
LogNode& ln = li.m_lognode[n];
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
LogPart& lp = ln.m_logpart[i];
|
|
if (i == 0)
|
|
{
|
|
ln.m_files = lp.m_files;
|
|
ln.m_filesize = lp.m_filesize;
|
|
CHK1(ln.m_files >= 3 && ln.m_filesize >= 4);
|
|
|
|
// see Dblqh::execREAD_CONFIG_REQ()
|
|
ln.m_minfds = 2;
|
|
ln.m_maxfds = (8192 - 32 - 128) / (3 * ln.m_filesize);
|
|
if (ln.m_maxfds > 40)
|
|
ln.m_maxfds = 40;
|
|
CHK1(ln.m_minfds <= ln.m_maxfds);
|
|
}
|
|
else
|
|
{
|
|
CHK1(ln.m_files == lp.m_files && ln.m_filesize == lp.m_filesize);
|
|
}
|
|
}
|
|
|
|
if (n == 0)
|
|
{
|
|
li.m_files = ln.m_files;
|
|
li.m_filesize = ln.m_filesize;
|
|
li.m_minfds = ln.m_minfds;
|
|
li.m_maxfds = ln.m_maxfds;
|
|
require(li.m_files > 0 && li.m_filesize > 0);
|
|
require(li.m_minfds <= li.m_maxfds);
|
|
}
|
|
else
|
|
{
|
|
CHK1(li.m_files == ln.m_files && li.m_filesize == ln.m_filesize);
|
|
require(li.m_minfds == ln.m_minfds && li.m_maxfds == ln.m_maxfds);
|
|
}
|
|
|
|
CHK1(result == 0);
|
|
}
|
|
CHK1(result == 0);
|
|
|
|
ndb_mgm_destroy_logevent_handle(&evh);
|
|
}
|
|
while (0);
|
|
|
|
info("get_redostatus result=" << result);
|
|
return result;
|
|
}
|
|
|
|
// get node with max redo files used in some part
|
|
|
|
struct LogUsed {
|
|
int m_nodeidx;
|
|
int m_nodeid;
|
|
int m_partno;
|
|
int m_used; // mb
|
|
LogPos m_head;
|
|
LogPos m_tail;
|
|
int m_fileused;
|
|
int m_rand; // randomize node to restart if file usage is same
|
|
friend NdbOut& operator<<(NdbOut&, const LogUsed&);
|
|
};
|
|
|
|
NdbOut&
|
|
operator<<(NdbOut& out, const LogUsed& lu)
|
|
{
|
|
out << "n=" << lu.m_nodeid;
|
|
out << " p=" << lu.m_partno;
|
|
out << " u=" << lu.m_used;
|
|
out << " h=" << lu.m_head;
|
|
out << " t=" << lu.m_tail;
|
|
out << " f=" << lu.m_fileused;
|
|
return out;
|
|
}
|
|
|
|
static int
|
|
cmp_logused(const void* a1, const void* a2)
|
|
{
|
|
const LogUsed& lu1 = *(const LogUsed*)a1;
|
|
const LogUsed& lu2 = *(const LogUsed*)a2;
|
|
int k = lu1.m_fileused - lu2.m_fileused;
|
|
if (k != 0)
|
|
{
|
|
// sorting by larger file usage
|
|
return (-1) * k;
|
|
}
|
|
return lu1.m_rand - lu2.m_rand;
|
|
}
|
|
|
|
struct LogMax {
|
|
int m_nodes;
|
|
LogUsed* m_logused;
|
|
LogMax(int nodes) {
|
|
m_nodes = nodes;
|
|
m_logused = new LogUsed [nodes];
|
|
};
|
|
~LogMax() {
|
|
m_nodes = 0;
|
|
delete [] m_logused;
|
|
}
|
|
};
|
|
|
|
static void
|
|
get_redoused(const LogInfo& li, LogMax& lx)
|
|
{
|
|
require(li.m_nodes == lx.m_nodes);
|
|
for (int n = 0; n < li.m_nodes; n++)
|
|
{
|
|
const LogNode& ln = li.m_lognode[n];
|
|
LogUsed& lu = lx.m_logused[n];
|
|
lu.m_used = -1;
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
const LogPart& lp = ln.m_logpart[i];
|
|
if (lu.m_used < lp.m_used)
|
|
{
|
|
lu.m_nodeidx = n;
|
|
lu.m_nodeid = ln.m_nodeid;
|
|
lu.m_partno = i;
|
|
lu.m_used = lp.m_used;
|
|
lu.m_head = lp.m_head;
|
|
lu.m_tail = lp.m_tail;
|
|
lu.m_fileused = lp.m_fileused;
|
|
lu.m_rand = myRandom48(100);
|
|
}
|
|
}
|
|
}
|
|
qsort(lx.m_logused, lx.m_nodes, sizeof(LogUsed), cmp_logused);
|
|
for (int n = 0; n + 1 < li.m_nodes; n++)
|
|
{
|
|
const LogUsed& lu1 = lx.m_logused[n];
|
|
const LogUsed& lu2 = lx.m_logused[n + 1];
|
|
require(lu1.m_fileused >= lu2.m_fileused);
|
|
}
|
|
}
|
|
|
|
struct LogDiff {
|
|
bool m_tailmove; // all tails since all redo parts are used
|
|
};
|
|
|
|
static void
|
|
get_redodiff(const LogInfo& li1, const LogInfo& li2, LogDiff& ld)
|
|
{
|
|
require(li1.m_nodes == li2.m_nodes);
|
|
ld.m_tailmove = true;
|
|
for (int i = 0; i < li1.m_nodes; i++)
|
|
{
|
|
LogNode& ln1 = li1.m_lognode[i];
|
|
LogNode& ln2 = li2.m_lognode[i];
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
LogPart& lp1 = ln1.m_logpart[j];
|
|
LogPart& lp2 = ln2.m_logpart[j];
|
|
if (lp1.m_tail.m_pos == lp2.m_tail.m_pos)
|
|
{
|
|
ld.m_tailmove = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
runRestartOK(NDBT_Context* ctx, NDBT_Step* step)
|
|
{
|
|
int result = NDBT_OK;
|
|
const int loops = ctx->getNumLoops();
|
|
NdbRestarter restarter;
|
|
|
|
info("restart01: start");
|
|
int nodes = restarter.getNumDbNodes();
|
|
require(nodes >= 1);
|
|
info("restart: nodes " << nodes);
|
|
|
|
if (nodes == 1)
|
|
{
|
|
info("restart01: need at least 2 nodes");
|
|
return result;
|
|
}
|
|
|
|
int nodeidx = myRandom48(nodes);
|
|
int nodeid = restarter.getDbNodeId(nodeidx);
|
|
info("restart01: using nodeid " << nodeid);
|
|
|
|
LogInfo logInfo(nodes);
|
|
|
|
int loop = 0;
|
|
while (loop < loops && !ctx->isTestStopped())
|
|
{
|
|
info("restart01: loop " << loop);
|
|
CHK1(get_nodestatus(restarter.handle, logInfo) == 0);
|
|
|
|
bool fi = false;
|
|
bool fn = false;
|
|
bool fa = false;
|
|
info("restart01: restart nodeid " << nodeid);
|
|
CHK1(restarter.restartOneDbNode(nodeid, fi, fn, fa) == 0);
|
|
CHK1(restarter.waitClusterStarted() == 0);
|
|
info("restart01: cluster up again");
|
|
|
|
// let write run until redo wraps (no check yet)
|
|
sleep(300);
|
|
loop++;
|
|
}
|
|
|
|
info("restart01: stop test");
|
|
ctx->stopTest();
|
|
return result;
|
|
}
|
|
|
|
#define g_SETFDS "SETFDS"
|
|
|
|
static int
|
|
run_write_ops(NDBT_Context* ctx, NDBT_Step* step, int cnt, int& upval, NdbError& err)
|
|
{
|
|
int result = NDBT_OK;
|
|
|
|
for (int i = 0; i < cnt && !ctx->isTestStopped(); i++)
|
|
{
|
|
CHK2(run_write_ops(ctx, step, upval++, err) == 0, err);
|
|
if (err.code != 0)
|
|
{
|
|
require(err.code == 410);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static int
|
|
get_newfds(const LogInfo& li)
|
|
{
|
|
require(li.m_files >= 3);
|
|
int newfds = li.m_files - 1;
|
|
require(newfds >= li.m_minfds);
|
|
// twice to prefer smaller
|
|
newfds = li.m_minfds + myRandom48(newfds - li.m_minfds + 1);
|
|
newfds = li.m_minfds + myRandom48(newfds - li.m_minfds + 1);
|
|
return newfds;
|
|
}
|
|
|
|
static int
|
|
get_limfds(const LogInfo& li, int newfds)
|
|
{
|
|
int off = li.m_files - newfds;
|
|
require(off > 0);
|
|
off= myRandom48(off + 1);
|
|
off = myRandom48(off + 1);
|
|
return newfds + off;
|
|
}
|
|
|
|
static int
|
|
run_restart(NDBT_Context* ctx, NDBT_Step* step, int nodeid, bool fi)
|
|
{
|
|
int result = NDBT_OK;
|
|
int setfds = ctx->getProperty(g_SETFDS, (Uint32)0xff);
|
|
require(setfds != 0xff);
|
|
int dump[2] = { 2396, setfds };
|
|
NdbRestarter restarter;
|
|
info("run_restart: nodeid=" << nodeid << " initial=" << fi << " setfds=" << setfds);
|
|
|
|
/*
|
|
* When starting non-initial the node(s) have already some setfds
|
|
* but it is lost on restart. We must dump the same setfds again.
|
|
*/
|
|
do
|
|
{
|
|
bool fn = true;
|
|
bool fa = false;
|
|
if (nodeid == 0)
|
|
{
|
|
info("run_restart: restart all nostart");
|
|
CHK1(restarter.restartAll(fi, fn, fa) == 0);
|
|
info("run_restart: wait nostart");
|
|
CHK1(restarter.waitClusterNoStart() == 0);
|
|
info("run_restart: dump " << dump[0] << " " << dump[1]);
|
|
CHK1(restarter.dumpStateAllNodes(dump, 2) == 0);
|
|
info("run_restart: start all");
|
|
CHK1(restarter.startAll() == 0);
|
|
}
|
|
else
|
|
{
|
|
info("run_restart: restart node nostart");
|
|
CHK1(restarter.restartOneDbNode(nodeid, fi, fn, fa) == 0);
|
|
info("run_restart: wait nostart");
|
|
CHK1(restarter.waitNodesNoStart(&nodeid, 1) == 0);
|
|
info("run_restart: dump " << dump[0] << " " << dump[1]);
|
|
CHK1(restarter.dumpStateAllNodes(dump, 2) == 0);
|
|
info("run_restart: start all");
|
|
CHK1(restarter.startAll() == 0);
|
|
}
|
|
info("run_restart: wait started");
|
|
CHK1(restarter.waitClusterStarted() == 0);
|
|
info("run_restart: started");
|
|
}
|
|
while (0);
|
|
|
|
info("run_restart: result=" << result);
|
|
return result;
|
|
}
|
|
|
|
static int
|
|
run_start_lcp(NdbRestarter& restarter)
|
|
{
|
|
int result = NDBT_OK;
|
|
int dump[1] = { 7099 };
|
|
do
|
|
{
|
|
CHK1(restarter.dumpStateAllNodes(dump, 1) == 0);
|
|
}
|
|
while (0);
|
|
info("run_start_lcp: result=" << result);
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Start long trans to freeze log tail. Run writes until over
|
|
* FDs stored in zero-pages (may hit 410). Run restart (which
|
|
* aborts long trans) and verify log tail moves (must not hit 410).
|
|
* At start and every 5 loops do initial restart and DUMP to
|
|
* change number of FDs stored to a random number between 2
|
|
* (minimum) and number of redo log files minus 1.
|
|
*/
|
|
|
|
static int
|
|
runRestartFD(NDBT_Context* ctx, NDBT_Step* step)
|
|
{
|
|
Ndb* pNdb = GETNDB(step);
|
|
int result = NDBT_OK;
|
|
const int loops = ctx->getNumLoops();
|
|
const bool srflag = ctx->getProperty("SRFLAG", (Uint32)0);
|
|
NdbRestarter restarter;
|
|
|
|
info("restart: start srflag=" << srflag);
|
|
int nodes = restarter.getNumDbNodes();
|
|
require(nodes >= 1);
|
|
info("restart: nodes " << nodes);
|
|
|
|
if (nodes == 1 && !srflag)
|
|
{
|
|
info("restart: need at least 2 nodes");
|
|
return result;
|
|
}
|
|
|
|
LogInfo logInfo(nodes);
|
|
LogInfo logInfo2(nodes);
|
|
LogMax logMax(nodes);
|
|
LogDiff logDiff;
|
|
|
|
const NdbDictionary::Table* pTab = 0;
|
|
|
|
int upval = 0;
|
|
int loop = 0;
|
|
int newfds = 0;
|
|
int limfds = 0;
|
|
while (loop < loops && !ctx->isTestStopped())
|
|
{
|
|
info("restart: loop " << loop);
|
|
if (loop % 5 == 0)
|
|
{
|
|
CHK1(get_nodestatus(restarter.handle, logInfo) == 0);
|
|
CHK1(get_redostatus(restarter.handle, logInfo) == 0);
|
|
|
|
// set new cmaxLogFilesInPageZero in all LQH nodes
|
|
newfds = get_newfds(logInfo);
|
|
ctx->setProperty(g_SETFDS, (Uint32)newfds);
|
|
bool nodeid = 0; // all nodes
|
|
bool fi = true; // initial start
|
|
CHK1(run_restart(ctx, step, nodeid, fi) == 0);
|
|
|
|
CHK1(runCreate(ctx, step) == 0);
|
|
pTab = g_tabptr[0];
|
|
require(pTab != 0);
|
|
}
|
|
|
|
// start long trans
|
|
HugoOperations ops(*pTab);
|
|
ops.setQuiet();
|
|
CHK2(ops.startTransaction(pNdb) == 0, ops.getNdbError());
|
|
for (int i = 0; i < 100; i++)
|
|
{
|
|
CHK2(ops.pkInsertRecord(pNdb, i, 1, 0) == 0, ops.getNdbError());
|
|
}
|
|
CHK2(ops.execute_NoCommit(pNdb) == 0, ops.getNdbError());
|
|
|
|
// randomize load1 limit a bit upwards
|
|
limfds = get_limfds(logInfo, newfds);
|
|
// may be up to logInfo.m_files and then hit 410
|
|
require(newfds <= limfds && limfds <= logInfo.m_files);
|
|
info("restart: newfds=" << newfds << " limfds=" << limfds);
|
|
|
|
// start load1 loop
|
|
info("restart: load1");
|
|
while (!ctx->isTestStopped())
|
|
{
|
|
info("restart: load1 at " << upval);
|
|
NdbError err;
|
|
int cnt = 100 + myRandom48(100);
|
|
CHK1(run_write_ops(ctx, step, cnt, upval, err) == 0);
|
|
|
|
CHK1(get_redostatus(restarter.handle, logInfo) == 0);
|
|
get_redoused(logInfo, logMax);
|
|
info("restart: load1 max: " << logMax.m_logused[0]);
|
|
info("restart: load1 min: " << logMax.m_logused[nodes - 1]);
|
|
|
|
if (err.code != 0)
|
|
{
|
|
require(err.code == 410);
|
|
info("restart: break load1 on 410");
|
|
break;
|
|
}
|
|
|
|
int fileused = logMax.m_logused[0].m_fileused;
|
|
if (fileused > limfds)
|
|
{
|
|
info("restart: break load1 on file usage > FDs");
|
|
break;
|
|
}
|
|
}
|
|
CHK1(result == NDBT_OK);
|
|
|
|
// restart
|
|
if (srflag)
|
|
{
|
|
int nodeid = 0;
|
|
int fi = false;
|
|
CHK1(run_restart(ctx, step, nodeid, fi) == 0);
|
|
}
|
|
else
|
|
{
|
|
int nodeid = logMax.m_logused[0].m_nodeid;
|
|
int fi = false;
|
|
CHK1(run_restart(ctx, step, nodeid, fi) == 0);
|
|
}
|
|
|
|
// start load2 loop
|
|
info("restart: load2");
|
|
CHK1(get_redostatus(restarter.handle, logInfo) == 0);
|
|
logInfo.copyto(logInfo2);
|
|
|
|
// should be fast but allow for slow machines
|
|
int retry2 = 0;
|
|
while (!ctx->isTestStopped())
|
|
{
|
|
info("restart: load2 at " << upval);
|
|
NdbError err;
|
|
int cnt = 100 + myRandom48(100);
|
|
CHK1(run_write_ops(ctx, step, cnt, upval, err) == 0);
|
|
|
|
CHK1(get_redostatus(restarter.handle, logInfo2) == 0);
|
|
get_redoused(logInfo2, logMax);
|
|
info("restart: load2 max: " << logMax.m_logused[0]);
|
|
info("restart: load2 min: " << logMax.m_logused[nodes - 1]);
|
|
|
|
require(err.code == 0 || err.code == 410);
|
|
CHK2(retry2 < 60 || err.code == 0, err);
|
|
|
|
get_redodiff(logInfo, logInfo2, logDiff);
|
|
if (logDiff.m_tailmove)
|
|
{
|
|
info("restart: break load2");
|
|
break;
|
|
}
|
|
|
|
info("restart: retry2=" << retry2);
|
|
if (retry2 % 5 == 0)
|
|
{
|
|
CHK1(run_start_lcp(restarter) == 0);
|
|
NdbSleep_MilliSleep(1000);
|
|
}
|
|
retry2++;
|
|
}
|
|
CHK1(result == NDBT_OK);
|
|
|
|
sleep(1 + myRandom48(10));
|
|
loop++;
|
|
}
|
|
|
|
info("restart: stop test");
|
|
ctx->stopTest();
|
|
return result;
|
|
}
|
|
|
|
static int
|
|
runResetFD(NDBT_Context* ctx, NDBT_Step* step)
|
|
{
|
|
int result = NDBT_OK;
|
|
int oldfds = ctx->getProperty(g_SETFDS, (Uint32)-1);
|
|
do
|
|
{
|
|
if (oldfds == -1)
|
|
{
|
|
// never changed (some step failed)
|
|
break;
|
|
}
|
|
ctx->setProperty(g_SETFDS, (Uint32)0);
|
|
CHK1(run_restart(ctx, step, 0, true) == 0);
|
|
}
|
|
while (0);
|
|
return result;
|
|
}
|
|
|
|
static int
|
|
resizeRedoLog(NDBT_Context* ctx, NDBT_Step* step)
|
|
{
|
|
int result = NDBT_FAILED;
|
|
Config conf;
|
|
NdbRestarter restarter;
|
|
Uint32 noOfLogFiles = ctx->getProperty("REDOLOGCOUNT", (Uint32)16);
|
|
Uint32 logFileSize = ctx->getProperty("REDOLOGSIZE", (Uint32)16*1024*1024);
|
|
Uint32 defaultNoOfLogFiles = 0, defaultLogFileSize = 0;
|
|
do
|
|
{
|
|
NdbMgmd mgmd;
|
|
if(!mgmd.connect())
|
|
{
|
|
g_err << "Failed to connect to ndb_mgmd." << endl;
|
|
break;
|
|
}
|
|
if (!mgmd.get_config(conf))
|
|
{
|
|
g_err << "Failed to get config from ndb_mgmd." << endl;
|
|
break;
|
|
}
|
|
|
|
g_err << "Setting NoOfFragmentLogFiles = " << noOfLogFiles
|
|
<< " and FragmentLogFileSize = " << logFileSize << "..." << endl;
|
|
ConfigValues::Iterator iter(conf.m_configValues->m_config);
|
|
for (int nodeid = 1; nodeid < MAX_NODES; nodeid ++)
|
|
{
|
|
Uint32 oldValue;
|
|
if (!iter.openSection(CFG_SECTION_NODE, nodeid))
|
|
continue;
|
|
if (iter.get(CFG_DB_NO_REDOLOG_FILES, &oldValue))
|
|
{
|
|
iter.set(CFG_DB_NO_REDOLOG_FILES, noOfLogFiles);
|
|
if(defaultNoOfLogFiles == 0)
|
|
{
|
|
defaultNoOfLogFiles = oldValue;
|
|
}
|
|
else if(oldValue != defaultNoOfLogFiles)
|
|
{
|
|
g_err << "NoOfFragmentLogFiles is not consistent across nodes" << endl;
|
|
break;
|
|
}
|
|
}
|
|
if(iter.get(CFG_DB_REDOLOG_FILE_SIZE, &oldValue))
|
|
{
|
|
iter.set(CFG_DB_REDOLOG_FILE_SIZE, logFileSize);
|
|
if(defaultLogFileSize == 0)
|
|
{
|
|
defaultLogFileSize = oldValue;
|
|
}
|
|
else if(oldValue != defaultLogFileSize)
|
|
{
|
|
g_err << "FragmentLogFileSize is not consistent across nodes" << endl;
|
|
break;
|
|
}
|
|
}
|
|
iter.closeSection();
|
|
}
|
|
|
|
// Save old values of NoOfFragmentLogFiles and FragmentLogFileSize
|
|
ctx->setProperty("REDOLOGCOUNT", (Uint32)defaultNoOfLogFiles);
|
|
ctx->setProperty("REDOLOGSIZE", (Uint32)defaultLogFileSize);
|
|
|
|
if (!mgmd.set_config(conf))
|
|
{
|
|
g_err << "Failed to set config in ndb_mgmd." << endl;
|
|
break;
|
|
}
|
|
|
|
g_err << "Restarting nodes to apply config change..." << endl;
|
|
if (restarter.restartAll(true))
|
|
{
|
|
g_err << "Failed to restart node." << endl;
|
|
break;
|
|
}
|
|
if (restarter.waitClusterStarted(120) != 0)
|
|
{
|
|
g_err << "Failed waiting for node started." << endl;
|
|
break;
|
|
}
|
|
g_err << "Nodes restarted with new config" << endl;
|
|
result = NDBT_OK;
|
|
} while (0);
|
|
return result;
|
|
}
|
|
|
|
static int
|
|
runWriteWithRedoFull(NDBT_Context* ctx, NDBT_Step* step)
|
|
{
|
|
int upval = 0;
|
|
NdbRestarter restarter;
|
|
Ndb* pNdb = GETNDB(step);
|
|
|
|
/**
|
|
* Ensure we don't use the same record for the open transaction as for the ones
|
|
* filling up the REDO log. In that case we get into a deadlock, we solve this
|
|
* by using a different table for the pending transaction.
|
|
*/
|
|
const NdbDictionary::Table* pTab = g_tabptr[0];
|
|
|
|
g_err << "Starting a write and leaving it open so the pending " <<
|
|
"COMMIT indefinitely delays redo log trimming..." << endl;
|
|
HugoOperations ops(*pTab);
|
|
ops.setQuiet();
|
|
if(ops.startTransaction(pNdb) != 0)
|
|
{
|
|
g_err << "Failed to start transaction: error " << ops.getNdbError() << endl;
|
|
return NDBT_FAILED;
|
|
}
|
|
if(ops.pkWriteRecord(pNdb, 0, 1, upval++) != 0)
|
|
{
|
|
g_err << "Failed to write record: error " << ops.getNdbError() << endl;
|
|
return NDBT_FAILED;
|
|
}
|
|
if(ops.execute_NoCommit(pNdb) != 0)
|
|
{
|
|
g_err << "Error: failed to execute NoCommit" << ops.getNdbError() << endl;
|
|
return NDBT_FAILED;
|
|
}
|
|
|
|
g_err << "Starting PK insert load..." << endl;
|
|
int loop = 0;
|
|
int result = NDBT_FAILED;
|
|
while (!ctx->isTestStopped())
|
|
{
|
|
if (loop % 100 == 0)
|
|
{
|
|
info("write: loop " << loop);
|
|
}
|
|
|
|
NdbError err;
|
|
run_write_ops(ctx, step, upval++, err, true);
|
|
if(err.code == 410)
|
|
{
|
|
g_err << "Redo log full, new requests aborted as expected" << endl;
|
|
result = NDBT_OK;
|
|
break;
|
|
}
|
|
else if(err.code == 266)
|
|
{
|
|
g_err << "Error; redo log full, but new requests still allowed to queue" << endl;
|
|
break;
|
|
}
|
|
else if(err.code != 0)
|
|
{
|
|
g_err << "Error: write failed with unexpected error " << err.code << endl;
|
|
break;
|
|
}
|
|
loop++;
|
|
}
|
|
|
|
g_err << "Executing pending COMMIT so that redo log can be trimmed..." << endl;
|
|
int ret = ops.execute_Commit(pNdb);
|
|
if(ret != 0)
|
|
{
|
|
g_err << "Error: failed to execute commit" << ops.getNdbError() << endl;
|
|
result = NDBT_FAILED;
|
|
}
|
|
ops.closeTransaction(pNdb);
|
|
return result;
|
|
}
|
|
|
|
NDBT_TESTSUITE(testRedo);
|
|
TESTCASE("WriteOK",
|
|
"Run only write to verify REDO size is adequate"){
|
|
TC_PROPERTY("TABMASK", (Uint32)(2));
|
|
INITIALIZER(runCreate);
|
|
STEP(runWriteOK);
|
|
FINALIZER(runDrop);
|
|
}
|
|
TESTCASE("Bug36500",
|
|
"Long trans and recovery from 410"){
|
|
TC_PROPERTY("TABMASK", (Uint32)(1|2));
|
|
INITIALIZER(runCreate);
|
|
STEP(runLongtrans);
|
|
STEP(runWrite410);
|
|
FINALIZER(runDrop);
|
|
}
|
|
TESTCASE("Latency410",
|
|
"Transaction latency under 410"){
|
|
TC_PROPERTY("TABMASK", (Uint32)(1|2|4));
|
|
TC_PROPERTY("SLEEP410", (Uint32)60);
|
|
INITIALIZER(runCreate);
|
|
STEP(runLongtrans);
|
|
STEP(runWrite410);
|
|
STEP(runLatency);
|
|
FINALIZER(runDrop);
|
|
}
|
|
TESTCASE("RestartOK",
|
|
"Node restart"){
|
|
TC_PROPERTY("TABMASK", (Uint32)(2));
|
|
INITIALIZER(runCreate);
|
|
STEP(runWriteOK);
|
|
STEP(runRestartOK);
|
|
FINALIZER(runDrop);
|
|
}
|
|
TESTCASE("RestartFD",
|
|
"Long trans and node restart with few LQH FDs"){
|
|
TC_PROPERTY("TABMASK", (Uint32)(1|2));
|
|
STEP(runRestartFD);
|
|
FINALIZER(runDrop);
|
|
FINALIZER(runResetFD);
|
|
}
|
|
TESTCASE("RestartFDSR",
|
|
"RestartFD using system restart"){
|
|
TC_PROPERTY("TABMASK", (Uint32)(1|2));
|
|
TC_PROPERTY("SRFLAG", (Uint32)1);
|
|
STEP(runRestartFD);
|
|
FINALIZER(runDrop);
|
|
FINALIZER(runResetFD);
|
|
}
|
|
TESTCASE("RedoFull",
|
|
"Fill redo logs, apply load and check queuing aborted"){
|
|
TC_PROPERTY("TABMASK", (Uint32)(3));
|
|
TC_PROPERTY("REDOLOGCOUNT", (Uint32)(3));
|
|
TC_PROPERTY("REDOLOGSIZE", (Uint32)(4*1024*1024));
|
|
INITIALIZER(resizeRedoLog);
|
|
INITIALIZER(runCreate);
|
|
STEP(runWriteWithRedoFull);
|
|
FINALIZER(runDrop);
|
|
FINALIZER(resizeRedoLog);
|
|
}
|
|
NDBT_TESTSUITE_END(testRedo);
|
|
|
|
|
|
int
|
|
main(int argc, const char** argv)
|
|
{
|
|
ndb_init();
|
|
NDBT_TESTSUITE_INSTANCE(testRedo);
|
|
testRedo.setCreateTable(false);
|
|
myRandom48Init((long)NdbTick_CurrentMillisecond());
|
|
g_msgmutex = NdbMutex_Create();
|
|
require(g_msgmutex != 0);
|
|
int ret = testRedo.execute(argc, argv);
|
|
NdbMutex_Destroy(g_msgmutex);
|
|
return ret;
|
|
}
|