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
scattered payloads.
Add a CLI tool for blobstore allowing basic operations through either command
line or shell interface.
### Event Framework
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/log.h"
#include "spdk/version.h"
#include "spdk/string.h"
/*
* This is not a public header file, but the CLI does expose
@ -47,12 +48,25 @@
* include it here.
*/
#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_conf = "blobcli.conf";
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 {
CLI_NONE,
CLI_IMPORT,
CLI_DUMP,
CLI_FILL,
@ -64,14 +78,22 @@ enum cli_action_type {
CLI_CREATE_BLOB,
CLI_LIST_BDEVS,
CLI_LIST_BLOBS,
CLI_INIT_BS
CLI_INIT_BS,
CLI_SHELL_EXIT,
CLI_HELP
};
#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 spdk_blob_store *bs;
struct spdk_blob *blob;
struct spdk_bs_dev *bs_dev;
spdk_blob_id blobid;
spdk_blob_id superid;
struct spdk_io_channel *channel;
@ -91,8 +113,39 @@ struct cli_context_t {
int rc;
int num_clusters;
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.
*/
@ -108,18 +161,7 @@ usage(char *msg)
program_name);
printf("on the underlying device specified in the conf file passed\n");
printf("in as a command line option.\n");
printf("\nCommands include:\n");
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");
print_cmds("");
}
/*
@ -131,9 +173,6 @@ cli_cleanup(struct cli_context_t *cli_context)
if (cli_context->buff) {
spdk_dma_free(cli_context->buff);
}
if (cli_context->channel) {
spdk_bs_free_io_channel(cli_context->channel);
}
free(cli_context);
}
@ -150,7 +189,18 @@ unload_complete(void *cb_arg, int bserrno)
cli_context->rc = bserrno;
}
spdk_app_stop(cli_context->rc);
/*
* 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);
} 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);
cli_context->rc = bserrno;
}
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);
} else {
spdk_app_stop(bserrno);
@ -427,6 +481,8 @@ show_blob(struct cli_context_t *cli_context)
}
printf("open ref count: %d\n",
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.
*/
static void
list_bdevs(void)
list_bdevs(struct cli_context_t *cli_context)
{
struct spdk_bdev *bdev = NULL;
@ -892,7 +948,12 @@ list_bdevs(void)
}
printf("\n");
spdk_app_stop(0);
if (cli_context->cli_mode == CLI_MODE_CMD) {
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)
{
struct spdk_bdev *bdev = NULL;
struct spdk_bs_dev *bs_dev = NULL;
bdev = spdk_bdev_get_by_name(cli_context->bdev_name);
if (bdev == NULL) {
@ -933,14 +993,14 @@ init_bs(struct cli_context_t *cli_context)
printf("Blobstore using bdev Product Name: %s\n",
spdk_bdev_get_product_name(bdev));
bs_dev = spdk_bdev_create_bs_dev(bdev, NULL, NULL);
if (bs_dev == NULL) {
cli_context->bs_dev = spdk_bdev_create_bs_dev(bdev, NULL, NULL);
if (cli_context->bs_dev == NULL) {
printf("Could not create blob bdev!!\n");
spdk_app_stop(-1);
return;
}
spdk_bs_init(bs_dev, NULL, bs_init_complete,
spdk_bs_init(cli_context->bs_dev, NULL, bs_init_complete,
cli_context);
}
@ -953,7 +1013,14 @@ cli_start(void *arg1, void *arg2)
{
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
@ -997,7 +1064,20 @@ cli_start(void *arg1, void *arg2)
init_bs(cli_context);
break;
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;
default:
/* 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;
bool cmd_chosen = false;
int cmd_chosen = 0;
char resp;
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;
while ((op = getopt(argc, argv, "c:d:f:il:m:n:p:r:s:x:")) != -1) {
while ((op = getopt(argc, argv, "c:d:f:hil:m:n:p:r:s:SXx:")) != -1) {
switch (op) {
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;
case 'd':
cmd_chosen++;
cli_context->action = CLI_DUMP;
cli_context->blobid = atoll(optarg);
break;
case 'f':
cmd_chosen++;
cli_context->action = CLI_FILL;
cli_context->blobid = atoll(optarg);
break;
case 'h':
cmd_chosen++;
cli_context->action = CLI_HELP;
break;
case 'i':
cli_context->action = CLI_INIT_BS;
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;
} else {
if (cli_context->cli_mode == CLI_MODE_CMD) {
exit(0);
}
}
}
break;
case 'r':
cmd_chosen++;
cli_context->action = CLI_REM_XATTR;
cli_context->blobid = atoll(optarg);
break;
case 'l':
if (strcmp("bdevs", optarg) == 0) {
cmd_chosen++;
cli_context->action = CLI_LIST_BDEVS;
} else if (strcmp("blobs", optarg) == 0) {
cmd_chosen++;
cli_context->action = CLI_LIST_BLOBS;
} else {
usage("ERROR: invalid option for list\n");
cli_cleanup(cli_context);
exit(-1);
if (cli_context->cli_mode == CLI_MODE_CMD) {
usage("ERROR: invalid option for list\n");
exit(-1);
} else {
print_cmds("ERROR: invalid option for list\n");
}
}
break;
case 'm':
cmd_chosen++;
cli_context->action = CLI_IMPORT;
cli_context->blobid = atoll(optarg);
break;
case 'n':
cmd_chosen++;
cli_context->num_clusters = atoi(optarg);
if (cli_context->num_clusters > 0) {
cli_context->action = CLI_CREATE_BLOB;
} else {
usage("ERROR: invalid option for new\n");
cli_cleanup(cli_context);
exit(-1);
if (cli_context->cli_mode == CLI_MODE_CMD) {
usage("ERROR: invalid option for new\n");
exit(-1);
} else {
print_cmds("ERROR: invalid option for new\n");
}
}
break;
case 'p':
cmd_chosen++;
cli_context->action = CLI_SET_SUPER;
cli_context->superid = atoll(optarg);
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':
cmd_chosen++;
if (strcmp("bs", optarg) == 0) {
cli_context->action = CLI_SHOW_BS;
} else {
@ -1085,46 +1194,42 @@ main(int argc, char **argv)
cli_context->blobid = atoll(optarg);
}
break;
case 'X':
cmd_chosen++;
cli_context->action = CLI_SHELL_EXIT;
break;
case 'x':
cmd_chosen++;
cli_context->action = CLI_SET_XATTR;
cli_context->blobid = atoll(optarg);
break;
default:
usage("ERROR: Invalid option\n");
cli_cleanup(cli_context);
exit(1);
if (cli_context->cli_mode == CLI_MODE_CMD) {
usage("ERROR: invalid option\n");
exit(-1);
} else {
print_cmds("ERROR: invalid option\n");
}
}
/* config file is the only option that can be combined */
if (op != 'c') {
if (cmd_chosen) {
usage("Error: Please choose only one command\n");
cli_cleanup(cli_context);
exit(1);
} else {
cmd_chosen = true;
if (cmd_chosen > 1) {
if (cli_context->cli_mode == CLI_MODE_CMD) {
usage("Error: Please choose only one command\n");
cli_cleanup(cli_context);
exit(1);
} else {
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");
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 */
if (cli_context->action == CLI_SET_XATTR ||
cli_context->action == CLI_REM_XATTR) {
@ -1141,16 +1246,116 @@ main(int argc, char **argv)
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. */
spdk_app_opts_init(&opts);
opts.name = "blobcli";
opts.config_file = config_file;
opts.config_file = cli_context->config_file;
/*
* spdk_app_start() will block running cli_start() until
* spdk_app_stop() is called by someone (not simply when
* cli_start() returns)
*/
cli_context->app_started = true;
rc = spdk_app_start(&opts, cli_start, cli_context, NULL);
if (rc) {
printf("ERROR!\n");