diff options
Diffstat (limited to '')
-rw-r--r-- | ctdb/tests/src/db_test_tool.c | 792 |
1 files changed, 792 insertions, 0 deletions
diff --git a/ctdb/tests/src/db_test_tool.c b/ctdb/tests/src/db_test_tool.c new file mode 100644 index 0000000..e99da3c --- /dev/null +++ b/ctdb/tests/src/db_test_tool.c @@ -0,0 +1,792 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "system/network.h" +#include "system/time.h" + +#include <ctype.h> +#include <popt.h> +#include <talloc.h> +#include <tevent.h> + +#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; i<dbmap->num; 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; i<dbmap->num; 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 = "<key>" + }, + { + .name = "fetch-local-delete", + .fn = db_test_fetch_local_delete, + .msg_help = "Fetch record and delete from local database", + .msg_args = "<dbname|dbid> <key>" + }, + { + .name = "local-lock", + .fn = db_test_local_lock, + .msg_help = "Lock a record in a local database", + .msg_args = "<dbname|dbid> <key>" + }, + { + .name = "local-read", + .fn = db_test_local_read, + .msg_help = "Read a record from local database", + .msg_args = "<dbname|dbid> <key>" + }, + { + .name = "vacuum", + .fn = db_test_vacuum, + .msg_help = "Vacuum a database", + .msg_args = "<dbname|dbid> [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 */ |