/*
CTDB DB test tool
Copyright (C) Martin Schwenke 2019
Parts based on ctdb.c, event_tool.c:
Copyright (C) Amitay Isaacs 2015, 2018
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; either version 3 of the License, or
(at your option) any later version.
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, see .
*/
#include "replace.h"
#include "system/filesys.h"
#include "system/network.h"
#include "system/time.h"
#include
#include
#include
#include
#include "lib/util/debug.h"
#include "lib/util/sys_rw.h"
#include "lib/util/util.h"
#include "lib/util/smb_strtox.h"
#include "lib/tdb_wrap/tdb_wrap.h"
#include "common/cmdline.h"
#include "common/logging.h"
#include "common/path.h"
#include "common/event_script.h"
#include "common/system_socket.h"
#include "protocol/protocol.h"
#include "protocol/protocol_api.h"
#include "protocol/protocol_util.h"
#include "client/client.h"
#include "client/client_sync.h"
struct tdb_context *client_db_tdb(struct ctdb_db_context *db);
#define TIMEOUT() tevent_timeval_current_ofs(ctx->timelimit, 0)
struct db_test_tool_context {
struct cmdline_context *cmdline;
struct tevent_context *ev;
struct ctdb_client_context *client;
uint32_t destnode;
uint32_t timelimit;
};
/*
* If this is ever consolidated into a larger test tool then these
* forward declarations can be moved to an include file
*/
int db_test_tool_init(TALLOC_CTX *mem_ctx,
const char *prog,
struct poptOption *options,
int argc,
const char **argv,
bool parse_options,
struct db_test_tool_context **result);
int db_test_tool_run(struct db_test_tool_context *ctx, int *result);
static int db_test_get_lmaster(TALLOC_CTX *mem_ctx,
int argc,
const char **argv,
void *private_data)
{
struct db_test_tool_context *ctx = talloc_get_type_abort(
private_data, struct db_test_tool_context);
struct ctdb_vnn_map *vnnmap;
TDB_DATA key;
uint32_t idx, lmaster;
unsigned int hash;
int ret = 0;
if (argc != 1) {
cmdline_usage(ctx->cmdline, "get-lmaster");
return 1;
}
ret = ctdb_ctrl_getvnnmap(mem_ctx,
ctx->ev,
ctx->client,
CTDB_CURRENT_NODE,
TIMEOUT(),
&vnnmap);
if (ret != 0) {
D_ERR("Control GETVNN_MAP failed, ret=%d\n", ret);
return ret;
}
key.dsize = strlen(argv[0]);
key.dptr = (uint8_t *)discard_const(argv[0]);
hash = tdb_jenkins_hash(&key);
idx = hash % vnnmap->size;
lmaster = vnnmap->map[idx];
printf("%"PRId32"\n", lmaster);
return 0;
}
static struct ctdb_dbid *db_find(TALLOC_CTX *mem_ctx,
struct db_test_tool_context *ctx,
struct ctdb_dbid_map *dbmap,
const char *db_name)
{
struct ctdb_dbid *db = NULL;
const char *name;
unsigned int i;
int ret;
for (i=0; inum; i++) {
ret = ctdb_ctrl_get_dbname(mem_ctx,
ctx->ev,
ctx->client,
ctx->destnode,
TIMEOUT(),
dbmap->dbs[i].db_id,
&name);
if (ret != 0) {
return NULL;
}
if (strcmp(db_name, name) == 0) {
talloc_free(discard_const(name));
db = &dbmap->dbs[i];
break;
}
}
return db;
}
static bool db_exists(TALLOC_CTX *mem_ctx,
struct db_test_tool_context *ctx,
const char *db_arg,
uint32_t *db_id,
const char **db_name,
uint8_t *db_flags)
{
struct ctdb_dbid_map *dbmap;
struct ctdb_dbid *db = NULL;
uint32_t id = 0;
const char *name = NULL;
unsigned int i;
int ret = 0;
ret = ctdb_ctrl_get_dbmap(mem_ctx,
ctx->ev,
ctx->client,
ctx->destnode,
TIMEOUT(),
&dbmap);
if (ret != 0) {
return false;
}
if (strncmp(db_arg, "0x", 2) == 0) {
id = smb_strtoul(db_arg, NULL, 0, &ret, SMB_STR_STANDARD);
if (ret != 0) {
return false;
}
for (i=0; inum; i++) {
if (id == dbmap->dbs[i].db_id) {
db = &dbmap->dbs[i];
break;
}
}
} else {
name = db_arg;
db = db_find(mem_ctx, ctx, dbmap, name);
}
if (db == NULL) {
fprintf(stderr, "No database matching '%s' found\n", db_arg);
return false;
}
if (name == NULL) {
ret = ctdb_ctrl_get_dbname(mem_ctx,
ctx->ev,
ctx->client,
ctx->destnode,
TIMEOUT(),
id,
&name);
if (ret != 0) {
return false;
}
}
if (db_id != NULL) {
*db_id = db->db_id;
}
if (db_name != NULL) {
*db_name = talloc_strdup(mem_ctx, name);
}
if (db_flags != NULL) {
*db_flags = db->flags;
}
return true;
}
static int db_test_fetch_local_delete(TALLOC_CTX *mem_ctx,
int argc,
const char **argv,
void *private_data)
{
struct db_test_tool_context *ctx = talloc_get_type_abort(
private_data, struct db_test_tool_context);
struct ctdb_db_context *db = NULL;
struct ctdb_record_handle *h = NULL;
struct tdb_context *tdb;
struct ctdb_ltdb_header header;
const char *db_name;
TDB_DATA key, data;
uint32_t db_id;
uint8_t db_flags;
size_t len;
uint8_t *buf;
size_t np;
int ret;
if (argc != 2) {
cmdline_usage(ctx->cmdline, "fetch-local-delete");
return 1;
}
if (! db_exists(mem_ctx, ctx, argv[0], &db_id, &db_name, &db_flags)) {
return ENOENT;
}
if (db_flags & (CTDB_DB_FLAGS_PERSISTENT | CTDB_DB_FLAGS_REPLICATED)) {
D_ERR("DB %s is not a volatile database\n", db_name);
return EINVAL;
}
ret = ctdb_attach(ctx->ev,
ctx->client,
TIMEOUT(),
db_name,
db_flags,
&db);
if (ret != 0) {
D_ERR("Failed to attach to DB %s\n", db_name);
return ret;
}
key.dsize = strlen(argv[1]);
key.dptr = (uint8_t *)discard_const(argv[1]);
ret = ctdb_fetch_lock(mem_ctx,
ctx->ev,
ctx->client,
db,
key,
false,
&h,
&header,
NULL);
if (ret != 0) {
D_ERR("Failed to fetch record for key %s\n", argv[1]);
goto done;
}
len = ctdb_ltdb_header_len(&header);
buf = talloc_size(mem_ctx, len);
if (buf == NULL) {
D_ERR("Memory allocation error\n");
ret = ENOMEM;
goto done;
}
ctdb_ltdb_header_push(&header, buf, &np);
data.dsize = np;
data.dptr = buf;
tdb = client_db_tdb(db);
ret = tdb_store(tdb, key, data, TDB_REPLACE);
TALLOC_FREE(buf);
if (ret != 0) {
D_ERR("fetch_lock delete: %s tdb_store failed, %s\n",
db_name,
tdb_errorstr(tdb));
}
done:
TALLOC_FREE(h);
return ret;
}
#define ISASCII(x) (isprint(x) && ! strchr("\"\\", (x)))
static void dump(const char *name, uint8_t *dptr, size_t dsize)
{
size_t i;
fprintf(stdout, "%s(%zu) = \"", name, dsize);
for (i = 0; i < dsize; i++) {
if (ISASCII(dptr[i])) {
fprintf(stdout, "%c", dptr[i]);
} else {
fprintf(stdout, "\\%02X", dptr[i]);
}
}
fprintf(stdout, "\"\n");
}
static void dump_ltdb_header(struct ctdb_ltdb_header *header)
{
fprintf(stdout, "dmaster: %u\n", header->dmaster);
fprintf(stdout, "rsn: %" PRIu64 "\n", header->rsn);
fprintf(stdout, "flags: 0x%08x", header->flags);
if (header->flags & CTDB_REC_FLAG_MIGRATED_WITH_DATA) {
fprintf(stdout, " MIGRATED_WITH_DATA");
}
if (header->flags & CTDB_REC_FLAG_VACUUM_MIGRATED) {
fprintf(stdout, " VACUUM_MIGRATED");
}
if (header->flags & CTDB_REC_FLAG_AUTOMATIC) {
fprintf(stdout, " AUTOMATIC");
}
if (header->flags & CTDB_REC_RO_HAVE_DELEGATIONS) {
fprintf(stdout, " RO_HAVE_DELEGATIONS");
}
if (header->flags & CTDB_REC_RO_HAVE_READONLY) {
fprintf(stdout, " RO_HAVE_READONLY");
}
if (header->flags & CTDB_REC_RO_REVOKING_READONLY) {
fprintf(stdout, " RO_REVOKING_READONLY");
}
if (header->flags & CTDB_REC_RO_REVOKE_COMPLETE) {
fprintf(stdout, " RO_REVOKE_COMPLETE");
}
fprintf(stdout, "\n");
}
static int db_test_local_lock(TALLOC_CTX *mem_ctx,
int argc,
const char **argv,
void *private_data)
{
struct db_test_tool_context *ctx = talloc_get_type_abort(
private_data, struct db_test_tool_context);
struct ctdb_db_context *db;
const char *db_name;
int pipefd[2];
TDB_DATA key;
uint32_t db_id;
uint8_t db_flags;
pid_t pid;
int ret;
if (argc != 2) {
cmdline_usage(ctx->cmdline, "local-lock");
return 1;
}
if (! db_exists(mem_ctx, ctx, argv[0], &db_id, &db_name, &db_flags)) {
D_ERR("DB %s not attached\n", db_name);
return 1;
}
if (db_flags & (CTDB_DB_FLAGS_PERSISTENT | CTDB_DB_FLAGS_REPLICATED)) {
D_ERR("DB %s is not a volatile database\n", db_name);
return 1;
}
ret = ctdb_attach(ctx->ev,
ctx->client,
TIMEOUT(),
db_name,
db_flags,
&db);
if (ret != 0) {
D_ERR("Failed to attach to DB %s\n", db_name);
return 1;
}
ret = pipe(pipefd);
if (ret != 0) {
DBG_ERR("Failed to create pipe\n");
return 1;
}
pid = fork();
if (pid < 0) {
DBG_ERR("Failed to fork()\n");
return 1;
}
if (pid != 0) {
ssize_t nread;
int status;
close(pipefd[1]);
nread = sys_read(pipefd[0], &status, sizeof(status));
if (nread < 0 || (size_t)nread != sizeof(status)) {
status = EINVAL;
}
if (status == 0) {
printf("OK %d\n", pid);
} else {
printf("FAIL %d\n", status);
}
fflush(stdout);
return status;
}
close(pipefd[0]);
key.dsize = strlen(argv[1]);
key.dptr = (uint8_t *)discard_const(argv[1]);
ret = tdb_chainlock(client_db_tdb(db), key);
if (ret != 0) {
D_ERR("Failed to lock chain for key %s\n", argv[1]);
goto fail;
}
sys_write(pipefd[1], &ret, sizeof(ret));
fclose(stdin);
fclose(stdout);
fclose(stderr);
/* Hold the lock- the caller should SIGTERM to release the lock */
sleep(120);
exit(1);
fail:
sys_write(pipefd[1], &ret, sizeof(ret));
return ret;
}
static int db_test_local_read(TALLOC_CTX *mem_ctx,
int argc,
const char **argv,
void *private_data)
{
struct db_test_tool_context *ctx = talloc_get_type_abort(
private_data, struct db_test_tool_context);
struct ctdb_db_context *db;
struct ctdb_ltdb_header header;
const char *db_name;
TDB_DATA key, data;
uint32_t db_id;
uint8_t db_flags;
size_t np;
int ret;
if (argc != 2) {
cmdline_usage(ctx->cmdline, "local-read");
return 1;
}
if (! db_exists(mem_ctx, ctx, argv[0], &db_id, &db_name, &db_flags)) {
return ENOENT;
}
if (db_flags & (CTDB_DB_FLAGS_PERSISTENT | CTDB_DB_FLAGS_REPLICATED)) {
D_ERR("DB %s is not a volatile database\n", db_name);
return EINVAL;
}
ret = ctdb_attach(ctx->ev,
ctx->client,
TIMEOUT(),
db_name,
db_flags,
&db);
if (ret != 0) {
D_ERR("Failed to attach to DB %s\n", db_name);
return ret;
}
key.dsize = strlen(argv[1]);
key.dptr = (uint8_t *)discard_const(argv[1]);
data = tdb_fetch(client_db_tdb(db), key);
if (data.dptr == NULL) {
D_ERR("No record for key %s\n", argv[1]);
return 1;
}
if (data.dsize < sizeof(struct ctdb_ltdb_header)) {
D_ERR("Invalid record for key %s\n", argv[1]);
free(data.dptr);
return 1;
}
ret = ctdb_ltdb_header_pull(data.dptr, data.dsize, &header, &np);
if (ret != 0) {
D_ERR("Failed to parse header from data\n");
free(data.dptr);
return 1;
}
dump_ltdb_header(&header);
dump("data", data.dptr + np, data.dsize - np);
free(data.dptr);
return 0;
}
static int db_test_vacuum(TALLOC_CTX *mem_ctx,
int argc,
const char **argv,
void *private_data)
{
struct db_test_tool_context *ctx = talloc_get_type_abort(
private_data, struct db_test_tool_context);
struct ctdb_db_vacuum db_vacuum;
struct ctdb_req_control request;
struct ctdb_reply_control *reply;
const char *db_arg;
uint32_t db_id;
const char *db_name;
uint8_t db_flags;
int ret = 0;
if (argc != 1 && argc != 2) {
cmdline_usage(ctx->cmdline, "vacuum");
return 1;
}
db_arg = argv[0];
db_vacuum.full_vacuum_run = false;
if (argc == 2) {
if (strcmp(argv[1], "full") == 0) {
db_vacuum.full_vacuum_run = true;
} else {
cmdline_usage(ctx->cmdline, "vacuum");
return 1;
}
}
if (! db_exists(mem_ctx, ctx, db_arg, &db_id, &db_name, &db_flags)) {
return ENOENT;
}
if (db_flags & (CTDB_DB_FLAGS_PERSISTENT | CTDB_DB_FLAGS_REPLICATED)) {
D_ERR("DB %s is not a volatile database\n", db_name);
return EINVAL;
}
db_vacuum.db_id = db_id;
ctdb_req_control_db_vacuum(&request, &db_vacuum);
ret = ctdb_client_control(mem_ctx,
ctx->ev,
ctx->client,
ctx->destnode,
TIMEOUT(),
&request,
&reply);
if (ret != 0) {
D_ERR("Control DB_VACUUM failed to node %u, ret=%d\n",
ctx->destnode,
ret);
return ret;
}
ret = ctdb_reply_control_db_vacuum(reply);
if (ret != 0) {
D_ERR("Control DB_VACUUM failed, ret=%d\n", ret);
return ret;
}
return 0;
}
struct cmdline_command db_test_commands[] = {
{
.name = "get-lmaster",
.fn = db_test_get_lmaster,
.msg_help = "Print lmaster for key",
.msg_args = ""
},
{
.name = "fetch-local-delete",
.fn = db_test_fetch_local_delete,
.msg_help = "Fetch record and delete from local database",
.msg_args = " "
},
{
.name = "local-lock",
.fn = db_test_local_lock,
.msg_help = "Lock a record in a local database",
.msg_args = " "
},
{
.name = "local-read",
.fn = db_test_local_read,
.msg_help = "Read a record from local database",
.msg_args = " "
},
{
.name = "vacuum",
.fn = db_test_vacuum,
.msg_help = "Vacuum a database",
.msg_args = " [full]"
},
CMDLINE_TABLEEND
};
int db_test_tool_init(TALLOC_CTX *mem_ctx,
const char *prog,
struct poptOption *options,
int argc,
const char **argv,
bool parse_options,
struct db_test_tool_context **result)
{
struct db_test_tool_context *ctx;
int ret;
ctx = talloc_zero(mem_ctx, struct db_test_tool_context);
if (ctx == NULL) {
D_ERR("Memory allocation error\n");
return ENOMEM;
}
ret = cmdline_init(mem_ctx,
prog,
options,
NULL,
db_test_commands,
&ctx->cmdline);
if (ret != 0) {
D_ERR("Failed to initialize cmdline, ret=%d\n", ret);
talloc_free(ctx);
return ret;
}
ret = cmdline_parse(ctx->cmdline, argc, argv, parse_options);
if (ret != 0) {
cmdline_usage(ctx->cmdline, NULL);
talloc_free(ctx);
return ret;
}
*result = ctx;
return 0;
}
int db_test_tool_run(struct db_test_tool_context *ctx, int *result)
{
char *ctdb_socket;
int ret;
ctx->ev = tevent_context_init(ctx);
if (ctx->ev == NULL) {
D_ERR("Failed to initialize tevent\n");
return ENOMEM;
}
ctdb_socket = path_socket(ctx, "ctdbd");
if (ctdb_socket == NULL) {
fprintf(stderr, "Memory allocation error\n");
return ENOMEM;
}
ret = ctdb_client_init(ctx, ctx->ev, ctdb_socket, &ctx->client);
if (ret != 0) {
D_ERR("Failed to connect to CTDB daemon (%s)\n", ctdb_socket);
return ret;
}
ret = cmdline_run(ctx->cmdline, ctx, result);
return ret;
}
#ifdef CTDB_DB_TEST_TOOL
static struct {
const char *debug;
int destnode;
int timelimit;
} db_test_data = {
.debug = "ERROR",
.destnode = CTDB_CURRENT_NODE,
.timelimit = 60,
};
struct poptOption db_test_options[] = {
{
.longName = "debug",
.shortName = 'd',
.argInfo = POPT_ARG_STRING,
.arg = &db_test_data.debug,
.val = 0,
.descrip = "debug level",
.argDescrip = "ERROR|WARNING|NOTICE|INFO|DEBUG"
},
{
.longName = "node",
.shortName = 'n',
.argInfo = POPT_ARG_INT,
.arg = &db_test_data.destnode,
.val = 0,
.descrip = "node number",
.argDescrip = "NUM"
},
{
.longName = "timelimit",
.shortName = 't',
.argInfo = POPT_ARG_INT,
.arg = &db_test_data.timelimit,
.val = 0,
.descrip = "control time limit",
.argDescrip = "SECONDS"
},
POPT_TABLEEND
};
int main(int argc, const char **argv)
{
TALLOC_CTX *mem_ctx;
struct db_test_tool_context *ctx;
int ret, result = 0;
int level;
bool ok;
mem_ctx = talloc_new(NULL);
if (mem_ctx == NULL) {
fprintf(stderr, "Memory allocation error\n");
exit(1);
}
ret = db_test_tool_init(mem_ctx,
"ctdb-db-test",
db_test_options,
argc,
argv,
true,
&ctx);
if (ret != 0) {
talloc_free(mem_ctx);
exit(1);
}
setup_logging("ctdb-db-test", DEBUG_STDERR);
ok = debug_level_parse(db_test_data.debug, &level);
if (!ok) {
level = DEBUG_ERR;
}
debuglevel_set(level);
ctx->destnode = db_test_data.destnode;
ctx->timelimit = db_test_data.timelimit;
ret = db_test_tool_run(ctx, &result);
if (ret != 0) {
result = ret;
}
talloc_free(mem_ctx);
exit(result);
}
#endif /* CTDB_DB_TEST_TOOL */