blobcli: add interactive shell mode

All of the same commands and options are available but by
starting with the -S option the user enters an interactive
shell mode to interact with blobstore significantly
improving response time by only having to init DPDK and NVMe
subsystems one time.

Change-Id: Ib927ba0848166dba1090484cecbbcf011122b714
Signed-off-by: Paul Luse <paul.e.luse@intel.com>
Reviewed-on: https://review.gerrithub.io/380833
Tested-by: SPDK Automated Test System <sys_sgsw@intel.com>
Reviewed-by: Jim Harris <james.r.harris@intel.com>
Reviewed-by: Daniel Verkamp <daniel.verkamp@intel.com>
This commit is contained in:
Paul Luse 2017-09-29 13:11:15 -07:00 committed by Jim Harris
parent 9aa4bc7010
commit c5e63c71ff
2 changed files with 292 additions and 84 deletions

View File

@ -79,6 +79,9 @@ makes it explicit that the default is being used.
spdk_bs_io_readv_blob() and spdk_bs_io_writev_blob() were added to enable spdk_bs_io_readv_blob() and spdk_bs_io_writev_blob() were added to enable
scattered payloads. scattered payloads.
Add a CLI tool for blobstore allowing basic operations through either command
line or shell interface.
### Event Framework ### Event Framework
The ability to set a thread name, previously only used by the reactor code, is The ability to set a thread name, previously only used by the reactor code, is

View File

@ -40,6 +40,7 @@
#include "spdk/blob.h" #include "spdk/blob.h"
#include "spdk/log.h" #include "spdk/log.h"
#include "spdk/version.h" #include "spdk/version.h"
#include "spdk/string.h"
/* /*
* This is not a public header file, but the CLI does expose * This is not a public header file, but the CLI does expose
@ -47,12 +48,25 @@
* include it here. * include it here.
*/ */
#include "../lib/blob/blobstore.h" #include "../lib/blob/blobstore.h"
static void cli_start(void *arg1, void *arg2);
static bool cli_shell(void *arg1, void *arg2);
static const char *program_name = "blobcli"; static const char *program_name = "blobcli";
static const char *program_conf = "blobcli.conf"; static const char *program_conf = "blobcli.conf";
static const char *bdev_name = "Nvme0n1"; static const char *bdev_name = "Nvme0n1";
/*
* CMD mode runs one command at a time which can be annoying as the init takes
* a few seconds, so the shell mode, invoked with -S, does the init once and gives
* the user an interactive shell instead.
*/
enum cli_mode_type {
CLI_MODE_CMD,
CLI_MODE_SHELL
};
enum cli_action_type { enum cli_action_type {
CLI_NONE,
CLI_IMPORT, CLI_IMPORT,
CLI_DUMP, CLI_DUMP,
CLI_FILL, CLI_FILL,
@ -64,14 +78,22 @@ enum cli_action_type {
CLI_CREATE_BLOB, CLI_CREATE_BLOB,
CLI_LIST_BDEVS, CLI_LIST_BDEVS,
CLI_LIST_BLOBS, CLI_LIST_BLOBS,
CLI_INIT_BS CLI_INIT_BS,
CLI_SHELL_EXIT,
CLI_HELP
}; };
#define BUFSIZE 255 #define BUFSIZE 255
/* todo, scrub this as there may be some extra junk in here picked up along the way... */ #define MAX_ARGS 6
/*
* The CLI uses the SPDK app framework so is async and callback driven. A
* pointer to this structure is passed to SPDK calls and returned in the
* callbacks for easy access to all the info we may need.
*/
struct cli_context_t { struct cli_context_t {
struct spdk_blob_store *bs; struct spdk_blob_store *bs;
struct spdk_blob *blob; struct spdk_blob *blob;
struct spdk_bs_dev *bs_dev;
spdk_blob_id blobid; spdk_blob_id blobid;
spdk_blob_id superid; spdk_blob_id superid;
struct spdk_io_channel *channel; struct spdk_io_channel *channel;
@ -91,8 +113,39 @@ struct cli_context_t {
int rc; int rc;
int num_clusters; int num_clusters;
void (*next_func)(void *arg1, struct spdk_blob_store *bs, int bserrno); void (*next_func)(void *arg1, struct spdk_blob_store *bs, int bserrno);
enum cli_mode_type cli_mode;
const char *config_file;
int argc;
char *argv[MAX_ARGS];
bool app_started;
}; };
/*
* Common printing of commands for CLI and shell modes.
*/
static void
print_cmds(char *msg)
{
if (msg) {
printf("%s", msg);
}
printf("\nCommands include:\n");
printf("\t-d <blobid> filename - dump contents of a blob to a file\n");
printf("\t-f <blobid> value - fill a blob with a decimal value\n");
printf("\t-h - this help screen\n");
printf("\t-i - initialize a blobstore\n");
printf("\t-l bdevs | blobs - list either available bdevs or existing blobs\n");
printf("\t-m <blobid> filename - import contents of a file to a blob\n");
printf("\t-n <# clusters> - create new blob\n");
printf("\t-p <blobid> - set the superblob to the ID provided\n");
printf("\t-r <blobid> name - remove xattr name/value pair\n");
printf("\t-s <blobid> | bs - show blob info or blobstore info\n");
printf("\t-x <blobid> name value - set xattr name/value pair\n");
printf("\t-X - exit when in interactive shell mode\n");
printf("\t-S - enter interactive shell mode\n");
printf("\n");
}
/* /*
* Prints usage and relevant error message. * Prints usage and relevant error message.
*/ */
@ -108,18 +161,7 @@ usage(char *msg)
program_name); program_name);
printf("on the underlying device specified in the conf file passed\n"); printf("on the underlying device specified in the conf file passed\n");
printf("in as a command line option.\n"); printf("in as a command line option.\n");
printf("\nCommands include:\n"); print_cmds("");
printf("\t-i - initialize a blobstore\n");
printf("\t-l bdevs | blobs - list either available bdevs or existing blobs\n");
printf("\t-n <# clusters> - create new blob\n");
printf("\t-p <blobid> - set the superblob to the ID provided\n");
printf("\t-s <blobid> | bs - show blob info or blobstore info\n");
printf("\t-x <blobid> name value - set xattr name/value pair\n");
printf("\t-r <blobid> name - remove xattr name/value pair\n");
printf("\t-f <blobid> value - fill a blob with a decimal value\n");
printf("\t-d <blobid> filename - dump contents of a blob to a file\n");
printf("\t-m <blobid> filename - import contents of a file to a blob\n");
printf("\n");
} }
/* /*
@ -131,9 +173,6 @@ cli_cleanup(struct cli_context_t *cli_context)
if (cli_context->buff) { if (cli_context->buff) {
spdk_dma_free(cli_context->buff); spdk_dma_free(cli_context->buff);
} }
if (cli_context->channel) {
spdk_bs_free_io_channel(cli_context->channel);
}
free(cli_context); free(cli_context);
} }
@ -150,7 +189,18 @@ unload_complete(void *cb_arg, int bserrno)
cli_context->rc = bserrno; cli_context->rc = bserrno;
} }
/*
* Quit if we're in cmd mode or exiting shell mode, otherwise
* clear the action field and start the main function again.
*/
if (cli_context->cli_mode == CLI_MODE_CMD ||
cli_context->action == CLI_SHELL_EXIT) {
spdk_app_stop(cli_context->rc); spdk_app_stop(cli_context->rc);
} else {
/* when action is NONE, we know we need to remain in the shell */
cli_context->action = CLI_NONE;
cli_start(cli_context, NULL);
}
} }
/* /*
@ -163,7 +213,11 @@ unload_bs(struct cli_context_t *cli_context, char *msg, int bserrno)
printf("%s (err %d)\n", msg, bserrno); printf("%s (err %d)\n", msg, bserrno);
cli_context->rc = bserrno; cli_context->rc = bserrno;
} }
if (cli_context->bs) { if (cli_context->bs) {
if (cli_context->channel) {
spdk_bs_free_io_channel(cli_context->channel);
}
spdk_bs_unload(cli_context->bs, unload_complete, cli_context); spdk_bs_unload(cli_context->bs, unload_complete, cli_context);
} else { } else {
spdk_app_stop(bserrno); spdk_app_stop(bserrno);
@ -427,6 +481,8 @@ show_blob(struct cli_context_t *cli_context)
} }
printf("open ref count: %d\n", printf("open ref count: %d\n",
cli_context->blob->open_ref); cli_context->blob->open_ref);
spdk_xattr_names_free(names);
} }
/* /*
@ -874,7 +930,7 @@ load_bs(struct cli_context_t *cli_context)
* Lists all the blobs on this blobstore. * Lists all the blobs on this blobstore.
*/ */
static void static void
list_bdevs(void) list_bdevs(struct cli_context_t *cli_context)
{ {
struct spdk_bdev *bdev = NULL; struct spdk_bdev *bdev = NULL;
@ -892,7 +948,12 @@ list_bdevs(void)
} }
printf("\n"); printf("\n");
if (cli_context->cli_mode == CLI_MODE_CMD) {
spdk_app_stop(0); spdk_app_stop(0);
} else {
cli_context->action = CLI_NONE;
cli_start(cli_context, NULL);
}
} }
/* /*
@ -922,7 +983,6 @@ static void
init_bs(struct cli_context_t *cli_context) init_bs(struct cli_context_t *cli_context)
{ {
struct spdk_bdev *bdev = NULL; struct spdk_bdev *bdev = NULL;
struct spdk_bs_dev *bs_dev = NULL;
bdev = spdk_bdev_get_by_name(cli_context->bdev_name); bdev = spdk_bdev_get_by_name(cli_context->bdev_name);
if (bdev == NULL) { if (bdev == NULL) {
@ -933,14 +993,14 @@ init_bs(struct cli_context_t *cli_context)
printf("Blobstore using bdev Product Name: %s\n", printf("Blobstore using bdev Product Name: %s\n",
spdk_bdev_get_product_name(bdev)); spdk_bdev_get_product_name(bdev));
bs_dev = spdk_bdev_create_bs_dev(bdev, NULL, NULL); cli_context->bs_dev = spdk_bdev_create_bs_dev(bdev, NULL, NULL);
if (bs_dev == NULL) { if (cli_context->bs_dev == NULL) {
printf("Could not create blob bdev!!\n"); printf("Could not create blob bdev!!\n");
spdk_app_stop(-1); spdk_app_stop(-1);
return; return;
} }
spdk_bs_init(bs_dev, NULL, bs_init_complete, spdk_bs_init(cli_context->bs_dev, NULL, bs_init_complete,
cli_context); cli_context);
} }
@ -953,7 +1013,14 @@ cli_start(void *arg1, void *arg2)
{ {
struct cli_context_t *cli_context = arg1; struct cli_context_t *cli_context = arg1;
printf("\n"); /*
* The initial cmd line options are parsed once before this function is
* called so if there is no action, we're in shell mode and will loop
* here until a a valid option is parsed and returned.
*/
if (cli_context->action == CLI_NONE) {
while (cli_shell(cli_context, NULL) == false);
}
/* /*
* Decide what to do next based on cmd line parsing that * Decide what to do next based on cmd line parsing that
@ -997,7 +1064,20 @@ cli_start(void *arg1, void *arg2)
init_bs(cli_context); init_bs(cli_context);
break; break;
case CLI_LIST_BDEVS: case CLI_LIST_BDEVS:
list_bdevs(); list_bdevs(cli_context);
break;
case CLI_SHELL_EXIT:
/*
* Because shell mode reuses cmd mode functions, the blobstore
* is loaded/unloaded with every action so we just need to
* stop the framework. For this app there's no need to optimize
* and keep the blobstore open while the app is in shell mode.
*/
spdk_app_stop(0);
break;
case CLI_HELP:
print_cmds("");
unload_complete(cli_context, 0);
break; break;
default: default:
/* should never get here */ /* should never get here */
@ -1006,78 +1086,107 @@ cli_start(void *arg1, void *arg2)
} }
} }
int /*
main(int argc, char **argv) * Common cmd/option parser for command and shell modes.
*/
static bool
cmd_parser(int argc, char **argv, struct cli_context_t *cli_context)
{ {
struct spdk_app_opts opts = {};
struct cli_context_t *cli_context = NULL;
const char *config_file = NULL;
int rc = 0;
int op; int op;
bool cmd_chosen = false; int cmd_chosen = 0;
char resp;
if (argc < 2) { while ((op = getopt(argc, argv, "c:d:f:hil:m:n:p:r:s:SXx:")) != -1) {
usage("ERROR: Invalid option\n");
exit(1);
}
cli_context = calloc(1, sizeof(struct cli_context_t));
if (cli_context == NULL) {
printf("ERROR: could not allocate context structure\n");
exit(-1);
}
cli_context->bdev_name = bdev_name;
while ((op = getopt(argc, argv, "c:d:f:il:m:n:p:r:s:x:")) != -1) {
switch (op) { switch (op) {
case 'c': case 'c':
config_file = optarg; if (cli_context->app_started == false) {
cmd_chosen++;
cli_context->config_file = optarg;
} else {
print_cmds("ERROR: -c option not valid during shell mode.\n");
}
break; break;
case 'd': case 'd':
cmd_chosen++;
cli_context->action = CLI_DUMP; cli_context->action = CLI_DUMP;
cli_context->blobid = atoll(optarg); cli_context->blobid = atoll(optarg);
break; break;
case 'f': case 'f':
cmd_chosen++;
cli_context->action = CLI_FILL; cli_context->action = CLI_FILL;
cli_context->blobid = atoll(optarg); cli_context->blobid = atoll(optarg);
break; break;
case 'h':
cmd_chosen++;
cli_context->action = CLI_HELP;
break;
case 'i': case 'i':
printf("You entire blobstore will be destroyed. Are you sure? (y/n) ");
if (scanf("%c%*c", &resp)) {
if (resp == 'y' || resp == 'Y') {
cmd_chosen++;
cli_context->action = CLI_INIT_BS; cli_context->action = CLI_INIT_BS;
} else {
if (cli_context->cli_mode == CLI_MODE_CMD) {
exit(0);
}
}
}
break; break;
case 'r': case 'r':
cmd_chosen++;
cli_context->action = CLI_REM_XATTR; cli_context->action = CLI_REM_XATTR;
cli_context->blobid = atoll(optarg); cli_context->blobid = atoll(optarg);
break; break;
case 'l': case 'l':
if (strcmp("bdevs", optarg) == 0) { if (strcmp("bdevs", optarg) == 0) {
cmd_chosen++;
cli_context->action = CLI_LIST_BDEVS; cli_context->action = CLI_LIST_BDEVS;
} else if (strcmp("blobs", optarg) == 0) { } else if (strcmp("blobs", optarg) == 0) {
cmd_chosen++;
cli_context->action = CLI_LIST_BLOBS; cli_context->action = CLI_LIST_BLOBS;
} else { } else {
if (cli_context->cli_mode == CLI_MODE_CMD) {
usage("ERROR: invalid option for list\n"); usage("ERROR: invalid option for list\n");
cli_cleanup(cli_context);
exit(-1); exit(-1);
} else {
print_cmds("ERROR: invalid option for list\n");
}
} }
break; break;
case 'm': case 'm':
cmd_chosen++;
cli_context->action = CLI_IMPORT; cli_context->action = CLI_IMPORT;
cli_context->blobid = atoll(optarg); cli_context->blobid = atoll(optarg);
break; break;
case 'n': case 'n':
cmd_chosen++;
cli_context->num_clusters = atoi(optarg); cli_context->num_clusters = atoi(optarg);
if (cli_context->num_clusters > 0) { if (cli_context->num_clusters > 0) {
cli_context->action = CLI_CREATE_BLOB; cli_context->action = CLI_CREATE_BLOB;
} else { } else {
if (cli_context->cli_mode == CLI_MODE_CMD) {
usage("ERROR: invalid option for new\n"); usage("ERROR: invalid option for new\n");
cli_cleanup(cli_context);
exit(-1); exit(-1);
} else {
print_cmds("ERROR: invalid option for new\n");
}
} }
break; break;
case 'p': case 'p':
cmd_chosen++;
cli_context->action = CLI_SET_SUPER; cli_context->action = CLI_SET_SUPER;
cli_context->superid = atoll(optarg); cli_context->superid = atoll(optarg);
break; break;
case 'S':
if (cli_context->cli_mode == CLI_MODE_CMD) {
cli_context->action = CLI_NONE;
cli_context->cli_mode = CLI_MODE_SHELL;
}
cli_context->action = CLI_NONE;
break;
case 's': case 's':
cmd_chosen++;
if (strcmp("bs", optarg) == 0) { if (strcmp("bs", optarg) == 0) {
cli_context->action = CLI_SHOW_BS; cli_context->action = CLI_SHOW_BS;
} else { } else {
@ -1085,46 +1194,42 @@ main(int argc, char **argv)
cli_context->blobid = atoll(optarg); cli_context->blobid = atoll(optarg);
} }
break; break;
case 'X':
cmd_chosen++;
cli_context->action = CLI_SHELL_EXIT;
break;
case 'x': case 'x':
cmd_chosen++;
cli_context->action = CLI_SET_XATTR; cli_context->action = CLI_SET_XATTR;
cli_context->blobid = atoll(optarg); cli_context->blobid = atoll(optarg);
break; break;
default: default:
usage("ERROR: Invalid option\n"); if (cli_context->cli_mode == CLI_MODE_CMD) {
cli_cleanup(cli_context); usage("ERROR: invalid option\n");
exit(1); exit(-1);
} else {
print_cmds("ERROR: invalid option\n");
}
} }
/* config file is the only option that can be combined */ /* config file is the only option that can be combined */
if (op != 'c') { if (op != 'c') {
if (cmd_chosen) { if (cmd_chosen > 1) {
if (cli_context->cli_mode == CLI_MODE_CMD) {
usage("Error: Please choose only one command\n"); usage("Error: Please choose only one command\n");
cli_cleanup(cli_context); cli_cleanup(cli_context);
exit(1); exit(1);
} else { } else {
cmd_chosen = true; print_cmds("Error: Please choose only one command\n");
}
} }
} }
} }
if (cmd_chosen == false) { if (cli_context->cli_mode == CLI_MODE_CMD && cmd_chosen == 0) {
usage("Error: Please choose a command.\n"); usage("Error: Please choose a command.\n");
exit(1); exit(1);
} }
/* if they don't supply a conf name, use the default */
if (!config_file) {
config_file = program_conf;
}
/* if the config file doesn't exist, tell them how to make one */
if (access(config_file, F_OK) == -1) {
printf("Error: No config file found.\n");
printf("To create a config file named 'blobcli.conf' for your NVMe device:\n");
printf(" <path to spdk>/scripts/gen_nvme.sh > blobcli.conf\n");
printf("and then re-run the cli tool.\n");
exit(1);
}
/* a few options require some extra paramters */ /* a few options require some extra paramters */
if (cli_context->action == CLI_SET_XATTR || if (cli_context->action == CLI_SET_XATTR ||
cli_context->action == CLI_REM_XATTR) { cli_context->action == CLI_REM_XATTR) {
@ -1141,16 +1246,116 @@ main(int argc, char **argv)
cli_context->fill_value = atoi(argv[3]); cli_context->fill_value = atoi(argv[3]);
} }
/* in shell mode we'll call getopt multiple times so need to reset its index */
optind = 0;
return (cmd_chosen > 0);
}
/*
* Provides for a shell interface as opposed to one shot command line.
*/
static bool
cli_shell(void *arg1, void *arg2)
{
struct cli_context_t *cli_context = arg1;
char *line = NULL;
ssize_t buf_size = 0;
ssize_t bytes_in = 0;
ssize_t tok_len = 0;
char *tok = NULL;
bool cmd_chosen = false;
int start_idx = cli_context->argc;
int i;
printf("blob> ");
bytes_in = getline(&line, &buf_size, stdin);
/* parse input and update cli_context so we can use common option parser */
if (bytes_in > 0) {
tok = strtok(line, " ");
}
while ((tok != NULL) && (cli_context->argc < MAX_ARGS)) {
cli_context->argv[cli_context->argc] = strdup(tok);
tok_len = strlen(tok);
cli_context->argc++;
tok = strtok(NULL, " ,.-");
}
/* replace newline on last arg with null */
if (tok_len) {
spdk_str_chomp(cli_context->argv[cli_context->argc - 1]);
}
/* call parse cmd line with user input as args */
cmd_chosen = cmd_parser(cli_context->argc, &cli_context->argv[0], cli_context);
/* free strdup mem & reset arg count for next shell interaction */
for (i = start_idx; i < cli_context->argc; i++) {
free(cli_context->argv[i]);
}
cli_context->argc = 1;
free(line);
return cmd_chosen;
}
int
main(int argc, char **argv)
{
struct spdk_app_opts opts = {};
struct cli_context_t *cli_context = NULL;
int rc = 0;
if (argc < 2) {
usage("ERROR: Invalid option\n");
exit(1);
}
cli_context = calloc(1, sizeof(struct cli_context_t));
if (cli_context == NULL) {
printf("ERROR: could not allocate context structure\n");
exit(-1);
}
cli_context->bdev_name = bdev_name;
/* default to CMD mode until we've parsed the first parms */
cli_context->cli_mode = CLI_MODE_CMD;
cli_context->argv[0] = strdup(argv[0]);
cli_context->argc = 1;
/* parse command line */
cmd_parser(argc, argv, cli_context);
free(cli_context->argv[0]);
/* after displaying help, just exit */
if (cli_context->action == CLI_HELP) {
usage("");
cli_cleanup(cli_context);
exit(-1);
}
/* if they don't supply a conf name, use the default */
if (!cli_context->config_file) {
cli_context->config_file = program_conf;
}
/* if the config file doesn't exist, tell them how to make one */
if (access(cli_context->config_file, F_OK) == -1) {
printf("Error: No config file found.\n");
printf("To create a config file named 'blobcli.conf' for your NVMe device:\n");
printf(" <path to spdk>/scripts/gen_nvme.sh > blobcli.conf\n");
printf("and then re-run the cli tool.\n");
exit(1);
}
/* Set default values in opts struct along with name and conf file. */ /* Set default values in opts struct along with name and conf file. */
spdk_app_opts_init(&opts); spdk_app_opts_init(&opts);
opts.name = "blobcli"; opts.name = "blobcli";
opts.config_file = config_file; opts.config_file = cli_context->config_file;
/* cli_context->app_started = true;
* spdk_app_start() will block running cli_start() until
* spdk_app_stop() is called by someone (not simply when
* cli_start() returns)
*/
rc = spdk_app_start(&opts, cli_start, cli_context, NULL); rc = spdk_app_start(&opts, cli_start, cli_context, NULL);
if (rc) { if (rc) {
printf("ERROR!\n"); printf("ERROR!\n");