diff --git a/lib/common/cmdline.c b/lib/common/cmdline.c index a21dfc0286..7c95d02953 100644 --- a/lib/common/cmdline.c +++ b/lib/common/cmdline.c @@ -1,269 +1,262 @@ /* * Copyright 2019-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ -#ifndef _GNU_SOURCE -# define _GNU_SOURCE -#endif - -#ifndef PCMK__CONFIG_H -# define PCMK__CONFIG_H -# include -#endif +#include #include #include #include #include #include static gboolean bump_verbosity(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { pcmk__common_args_t *common_args = (pcmk__common_args_t *) data; common_args->verbosity++; return TRUE; } pcmk__common_args_t * pcmk__new_common_args(const char *summary) { pcmk__common_args_t *args = NULL; args = calloc(1, sizeof(pcmk__common_args_t)); if (args == NULL) { crm_exit(crm_errno2exit(-ENOMEM)); } args->summary = strdup(summary); if (args->summary == NULL) { crm_exit(crm_errno2exit(-ENOMEM)); } return args; } static void free_common_args(gpointer data) { pcmk__common_args_t *common_args = (pcmk__common_args_t *) data; free(common_args->summary); free(common_args->output_ty); free(common_args->output_dest); if (common_args->output_as_descr != NULL) { free(common_args->output_as_descr); } free(common_args); } GOptionContext * pcmk__build_arg_context(pcmk__common_args_t *common_args, const char *fmts, GOptionGroup **output_group, const char *param_string) { char *desc = crm_strdup_printf("Report bugs to %s\n", PACKAGE_BUGREPORT); GOptionContext *context; GOptionGroup *main_group; GOptionEntry main_entries[3] = { { "version", '$', 0, G_OPTION_ARG_NONE, &(common_args->version), "Display software version and exit", NULL }, { "verbose", 'V', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, bump_verbosity, "Increase debug output (may be specified multiple times)", NULL }, { NULL } }; main_group = g_option_group_new(NULL, "Application Options:", NULL, common_args, free_common_args); g_option_group_add_entries(main_group, main_entries); context = g_option_context_new(param_string); g_option_context_set_summary(context, common_args->summary); g_option_context_set_description(context, desc); g_option_context_set_main_group(context, main_group); if (fmts != NULL) { GOptionEntry output_entries[3] = { { "output-as", 0, 0, G_OPTION_ARG_STRING, &(common_args->output_ty), NULL, "FORMAT" }, { "output-to", 0, 0, G_OPTION_ARG_STRING, &(common_args->output_dest), "Specify file name for output (or \"-\" for stdout)", "DEST" }, { NULL } }; if (*output_group == NULL) { *output_group = g_option_group_new("output", "Output Options:", "Show output help", NULL, NULL); } common_args->output_as_descr = crm_strdup_printf("Specify output format as one of: %s", fmts); output_entries[0].description = common_args->output_as_descr; g_option_group_add_entries(*output_group, output_entries); g_option_context_add_group(context, *output_group); } free(desc); // main_group is now owned by context, we don't free it here // cppcheck-suppress memleak return context; } void pcmk__free_arg_context(GOptionContext *context) { if (context == NULL) { return; } g_option_context_free(context); } void pcmk__add_main_args(GOptionContext *context, GOptionEntry entries[]) { GOptionGroup *main_group = g_option_context_get_main_group(context); g_option_group_add_entries(main_group, entries); } void pcmk__add_arg_group(GOptionContext *context, const char *name, const char *header, const char *desc, GOptionEntry entries[]) { GOptionGroup *group = NULL; group = g_option_group_new(name, header, desc, NULL, NULL); g_option_group_add_entries(group, entries); g_option_context_add_group(context, group); // group is now owned by context, we don't free it here // cppcheck-suppress memleak } gchar ** pcmk__cmdline_preproc(char **argv, const char *special) { GPtrArray *arr = NULL; bool saw_dash_dash = false; if (argv == NULL) { return NULL; } if (g_get_prgname() == NULL && argv && *argv) { gchar *basename = g_path_get_basename(*argv); g_set_prgname(basename); g_free(basename); } arr = g_ptr_array_new(); for (int i = 0; argv[i] != NULL; i++) { /* If this is the first time we saw "--" in the command line, set * a flag so we know to just copy everything after it over. We also * want to copy the "--" over so whatever actually parses the command * line when we're done knows where arguments end. */ if (saw_dash_dash == false && strcmp(argv[i], "--") == 0) { saw_dash_dash = true; } if (saw_dash_dash == true) { g_ptr_array_add(arr, g_strdup(argv[i])); continue; } /* This is just a dash by itself. That could indicate stdin/stdout, or * it could be user error. Copy it over and let glib figure it out. */ if (pcmk__str_eq(argv[i], "-", pcmk__str_casei)) { g_ptr_array_add(arr, g_strdup(argv[i])); continue; } /* This is a short argument, or perhaps several. Iterate over it * and explode them out into individual arguments. */ if (g_str_has_prefix(argv[i], "-") && !g_str_has_prefix(argv[i], "--")) { /* Skip over leading dash */ char *ch = argv[i]+1; while (*ch != '\0') { /* This is a special short argument that takes an option. getopt * allows values to be interspersed with a list of arguments, but * glib does not. Grab both the argument and its value and * separate them into a new argument. */ if (special != NULL && strchr(special, *ch) != NULL) { /* The argument does not occur at the end of this string of * arguments. Take everything through the end as its value. */ if (*(ch+1) != '\0') { g_ptr_array_add(arr, g_strdup_printf("-%c", *ch)); g_ptr_array_add(arr, g_strdup(ch+1)); break; /* The argument occurs at the end of this string. Hopefully * whatever comes next in argv is its value. It may not be, * but that is not for us to decide. */ } else { g_ptr_array_add(arr, g_strdup_printf("-%c", *ch)); ch++; } /* This is a regular short argument. Just copy it over. */ } else { g_ptr_array_add(arr, g_strdup_printf("-%c", *ch)); ch++; } } /* This is a long argument, or an option, or something else. * Copy it over - everything else is copied, so this keeps it easy for * the caller to know what to do with the memory when it's done. */ } else { g_ptr_array_add(arr, g_strdup(argv[i])); } } g_ptr_array_add(arr, NULL); return (char **) g_ptr_array_free(arr, FALSE); } G_GNUC_PRINTF(3, 4) gboolean pcmk__force_args(GOptionContext *context, GError **error, const char *format, ...) { int len = 0; char *buf = NULL; gchar **extra_args = NULL; va_list ap; gboolean retval = TRUE; va_start(ap, format); len = vasprintf(&buf, format, ap); CRM_ASSERT(len > 0); va_end(ap); if (!g_shell_parse_argv(buf, NULL, &extra_args, error)) { g_strfreev(extra_args); free(buf); return FALSE; } retval = g_option_context_parse_strv(context, &extra_args, error); g_strfreev(extra_args); free(buf); return retval; } diff --git a/lib/common/output.c b/lib/common/output.c index a02ba2bc2f..6cb49b587a 100644 --- a/lib/common/output.c +++ b/lib/common/output.c @@ -1,170 +1,167 @@ /* * Copyright 2019-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ -#include +#include #include #include -#include -#include -#include #include static GHashTable *formatters = NULL; void pcmk__output_free(pcmk__output_t *out) { out->free_priv(out); if (out->messages != NULL) { g_hash_table_destroy(out->messages); } g_free(out->request); free(out); } int pcmk__output_new(pcmk__output_t **out, const char *fmt_name, const char *filename, char **argv) { pcmk__output_factory_t create = NULL;; if (formatters == NULL) { return EINVAL; } /* If no name was given, just try "text". It's up to each tool to register * what it supports so this also may not be valid. */ if (fmt_name == NULL) { create = g_hash_table_lookup(formatters, "text"); } else { create = g_hash_table_lookup(formatters, fmt_name); } if (create == NULL) { return pcmk_rc_unknown_format; } *out = create(argv); if (*out == NULL) { return ENOMEM; } if (pcmk__str_eq(filename, "-", pcmk__str_null_matches)) { (*out)->dest = stdout; } else { (*out)->dest = fopen(filename, "w"); if ((*out)->dest == NULL) { return errno; } } (*out)->quiet = false; (*out)->messages = pcmk__strkey_table(free, NULL); if ((*out)->init(*out) == false) { pcmk__output_free(*out); return ENOMEM; } return pcmk_rc_ok; } int pcmk__register_format(GOptionGroup *group, const char *name, pcmk__output_factory_t create, GOptionEntry *options) { if (create == NULL) { return -EINVAL; } if (formatters == NULL) { formatters = pcmk__strkey_table(free, NULL); } if (options != NULL && group != NULL) { g_option_group_add_entries(group, options); } g_hash_table_insert(formatters, strdup(name), create); return 0; } void pcmk__register_formats(GOptionGroup *group, pcmk__supported_format_t *formats) { pcmk__supported_format_t *entry = NULL; if (formats == NULL) { return; } for (entry = formats; entry->name != NULL; entry++) { pcmk__register_format(group, entry->name, entry->create, entry->options); } } void pcmk__unregister_formats() { if (formatters != NULL) { g_hash_table_destroy(formatters); } } int pcmk__call_message(pcmk__output_t *out, const char *message_id, ...) { va_list args; int rc = pcmk_rc_ok; pcmk__message_fn_t fn; fn = g_hash_table_lookup(out->messages, message_id); if (fn == NULL) { crm_debug("Called unknown output message '%s' for format '%s'", message_id, out->fmt_name); return EINVAL; } va_start(args, message_id); rc = fn(out, args); va_end(args); return rc; } void pcmk__register_message(pcmk__output_t *out, const char *message_id, pcmk__message_fn_t fn) { g_hash_table_replace(out->messages, strdup(message_id), fn); } void pcmk__register_messages(pcmk__output_t *out, pcmk__message_entry_t *table) { pcmk__message_entry_t *entry; for (entry = table; entry->message_id != NULL; entry++) { if (pcmk__strcase_any_of(entry->fmt_name, "default", out->fmt_name, NULL)) { pcmk__register_message(out, entry->message_id, entry->fn); } } } void pcmk__output_and_clear_error(GError *error, pcmk__output_t *out) { if (error == NULL) { return; } if (out != NULL) { out->err(out, "%s: %s", g_get_prgname(), error->message); } else { fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message); } g_clear_error(&error); } diff --git a/lib/common/output_text.c b/lib/common/output_text.c index 0098ae0729..4408425620 100644 --- a/lib/common/output_text.c +++ b/lib/common/output_text.c @@ -1,433 +1,433 @@ /* * Copyright 2019-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ +#include + #include #include #include #include -#include - static gboolean fancy = FALSE; GOptionEntry pcmk__text_output_entries[] = { { "text-fancy", 0, 0, G_OPTION_ARG_NONE, &fancy, "Use more highly formatted output (requires --output-as=text)", NULL }, { NULL } }; typedef struct text_list_data_s { unsigned int len; char *singular_noun; char *plural_noun; } text_list_data_t; typedef struct private_data_s { GQueue *parent_q; } private_data_t; static void text_free_priv(pcmk__output_t *out) { private_data_t *priv = out->priv; if (priv == NULL) { return; } g_queue_free(priv->parent_q); free(priv); out->priv = NULL; } static bool text_init(pcmk__output_t *out) { private_data_t *priv = NULL; /* If text_init was previously called on this output struct, just return. */ if (out->priv != NULL) { return true; } else { out->priv = calloc(1, sizeof(private_data_t)); if (out->priv == NULL) { return false; } priv = out->priv; } priv->parent_q = g_queue_new(); return true; } static void text_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) { fflush(out->dest); } static void text_reset(pcmk__output_t *out) { CRM_ASSERT(out != NULL); if (out->dest != stdout) { out->dest = freopen(NULL, "w", out->dest); } CRM_ASSERT(out->dest != NULL); text_free_priv(out); text_init(out); } static void text_subprocess_output(pcmk__output_t *out, int exit_status, const char *proc_stdout, const char *proc_stderr) { CRM_ASSERT(out != NULL); if (proc_stdout != NULL) { fprintf(out->dest, "%s\n", proc_stdout); } if (proc_stderr != NULL) { fprintf(out->dest, "%s\n", proc_stderr); } } static void text_version(pcmk__output_t *out, bool extended) { CRM_ASSERT(out != NULL); if (extended) { fprintf(out->dest, "Pacemaker %s (Build: %s): %s\n", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES); } else { fprintf(out->dest, "Pacemaker %s\n", PACEMAKER_VERSION); fprintf(out->dest, "Written by Andrew Beekhof\n"); } } G_GNUC_PRINTF(2, 3) static void text_err(pcmk__output_t *out, const char *format, ...) { va_list ap; int len = 0; CRM_ASSERT(out != NULL); va_start(ap, format); /* Informational output does not get indented, to separate it from other * potentially indented list output. */ len = vfprintf(stderr, format, ap); CRM_ASSERT(len >= 0); va_end(ap); /* Add a newline. */ fprintf(stderr, "\n"); } G_GNUC_PRINTF(2, 3) static int text_info(pcmk__output_t *out, const char *format, ...) { va_list ap; int len = 0; CRM_ASSERT(out != NULL); if (out->is_quiet(out)) { return pcmk_rc_no_output; } va_start(ap, format); /* Informational output does not get indented, to separate it from other * potentially indented list output. */ len = vfprintf(out->dest, format, ap); CRM_ASSERT(len >= 0); va_end(ap); /* Add a newline. */ fprintf(out->dest, "\n"); return pcmk_rc_ok; } static void text_output_xml(pcmk__output_t *out, const char *name, const char *buf) { CRM_ASSERT(out != NULL); pcmk__indented_printf(out, "%s", buf); } G_GNUC_PRINTF(4, 5) static void text_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun, const char *format, ...) { private_data_t *priv = NULL; text_list_data_t *new_list = NULL; va_list ap; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; va_start(ap, format); if (fancy && format) { pcmk__indented_vprintf(out, format, ap); fprintf(out->dest, ":\n"); } va_end(ap); new_list = calloc(1, sizeof(text_list_data_t)); new_list->len = 0; new_list->singular_noun = singular_noun == NULL ? NULL : strdup(singular_noun); new_list->plural_noun = plural_noun == NULL ? NULL : strdup(plural_noun); g_queue_push_tail(priv->parent_q, new_list); } G_GNUC_PRINTF(3, 4) static void text_list_item(pcmk__output_t *out, const char *id, const char *format, ...) { va_list ap; CRM_ASSERT(out != NULL); va_start(ap, format); if (fancy) { if (id != NULL) { /* Not really a good way to do this all in one call, so make it two. * The first handles the indentation and list styling. The second * just prints right after that one. */ pcmk__indented_printf(out, "%s: ", id); vfprintf(out->dest, format, ap); } else { pcmk__indented_vprintf(out, format, ap); } } else { pcmk__indented_vprintf(out, format, ap); } fputc('\n', out->dest); fflush(out->dest); va_end(ap); out->increment_list(out); } static void text_increment_list(pcmk__output_t *out) { private_data_t *priv = NULL; gpointer tail; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; tail = g_queue_peek_tail(priv->parent_q); CRM_ASSERT(tail != NULL); ((text_list_data_t *) tail)->len++; } static void text_end_list(pcmk__output_t *out) { private_data_t *priv = NULL; text_list_data_t *node = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; node = g_queue_pop_tail(priv->parent_q); if (node->singular_noun != NULL && node->plural_noun != NULL) { if (node->len == 1) { pcmk__indented_printf(out, "%d %s found\n", node->len, node->singular_noun); } else { pcmk__indented_printf(out, "%d %s found\n", node->len, node->plural_noun); } } free(node); } static bool text_is_quiet(pcmk__output_t *out) { CRM_ASSERT(out != NULL); return out->quiet; } static void text_spacer(pcmk__output_t *out) { CRM_ASSERT(out != NULL); fprintf(out->dest, "\n"); } static void text_progress(pcmk__output_t *out, bool end) { CRM_ASSERT(out != NULL); if (out->dest == stdout) { fprintf(out->dest, "."); if (end) { fprintf(out->dest, "\n"); } } } pcmk__output_t * pcmk__mk_text_output(char **argv) { pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t)); if (retval == NULL) { return NULL; } retval->fmt_name = "text"; retval->request = argv == NULL ? NULL : g_strjoinv(" ", argv); retval->init = text_init; retval->free_priv = text_free_priv; retval->finish = text_finish; retval->reset = text_reset; retval->register_message = pcmk__register_message; retval->message = pcmk__call_message; retval->subprocess_output = text_subprocess_output; retval->version = text_version; retval->info = text_info; retval->err = text_err; retval->output_xml = text_output_xml; retval->begin_list = text_begin_list; retval->list_item = text_list_item; retval->increment_list = text_increment_list; retval->end_list = text_end_list; retval->is_quiet = text_is_quiet; retval->spacer = text_spacer; retval->progress = text_progress; retval->prompt = pcmk__text_prompt; return retval; } G_GNUC_PRINTF(2, 0) void pcmk__formatted_vprintf(pcmk__output_t *out, const char *format, va_list args) { int len = 0; CRM_ASSERT(out != NULL); len = vfprintf(out->dest, format, args); CRM_ASSERT(len >= 0); } G_GNUC_PRINTF(2, 3) void pcmk__formatted_printf(pcmk__output_t *out, const char *format, ...) { va_list ap; CRM_ASSERT(out != NULL); va_start(ap, format); pcmk__formatted_vprintf(out, format, ap); va_end(ap); } G_GNUC_PRINTF(2, 0) void pcmk__indented_vprintf(pcmk__output_t *out, const char *format, va_list args) { CRM_ASSERT(out != NULL); if (!pcmk__str_eq(out->fmt_name, "text", pcmk__str_none)) { return; } if (fancy) { int level = 0; private_data_t *priv = out->priv; CRM_ASSERT(priv != NULL); level = g_queue_get_length(priv->parent_q); for (int i = 0; i < level; i++) { fprintf(out->dest, " "); } if (level > 0) { fprintf(out->dest, "* "); } } pcmk__formatted_vprintf(out, format, args); } G_GNUC_PRINTF(2, 3) void pcmk__indented_printf(pcmk__output_t *out, const char *format, ...) { va_list ap; CRM_ASSERT(out != NULL); va_start(ap, format); pcmk__indented_vprintf(out, format, ap); va_end(ap); } void pcmk__text_prompt(const char *prompt, bool echo, char **dest) { int rc = 0; struct termios settings; tcflag_t orig_c_lflag = 0; CRM_ASSERT(prompt != NULL); CRM_ASSERT(dest != NULL); if (!echo) { rc = tcgetattr(0, &settings); if (rc == 0) { orig_c_lflag = settings.c_lflag; settings.c_lflag &= ~ECHO; rc = tcsetattr(0, TCSANOW, &settings); } } if (rc == 0) { fprintf(stderr, "%s: ", prompt); if (*dest != NULL) { free(*dest); *dest = NULL; } #if SSCANF_HAS_M rc = scanf("%ms", dest); #else *dest = calloc(1, 1024); rc = scanf("%1023s", *dest); #endif fprintf(stderr, "\n"); } if (rc < 1) { free(*dest); *dest = NULL; } if (orig_c_lflag != 0) { settings.c_lflag = orig_c_lflag; rc = tcsetattr(0, TCSANOW, &settings); } } diff --git a/lib/common/tests/agents/pcmk_stonith_param_test.c b/lib/common/tests/agents/pcmk_stonith_param_test.c index 1e67057a82..ee82b855ab 100644 --- a/lib/common/tests/agents/pcmk_stonith_param_test.c +++ b/lib/common/tests/agents/pcmk_stonith_param_test.c @@ -1,58 +1,59 @@ /* - * Copyright 2020 the Pacemaker project contributors + * Copyright 2020-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ +#include + #include -#include #include static void is_stonith_param(void) { g_assert_false(pcmk_stonith_param(NULL)); g_assert_false(pcmk_stonith_param("")); g_assert_false(pcmk_stonith_param("unrecognized")); g_assert_false(pcmk_stonith_param("pcmk_unrecognized")); g_assert_false(pcmk_stonith_param("x" PCMK_STONITH_ACTION_LIMIT)); g_assert_false(pcmk_stonith_param(PCMK_STONITH_ACTION_LIMIT "x")); g_assert_true(pcmk_stonith_param(PCMK_STONITH_ACTION_LIMIT)); g_assert_true(pcmk_stonith_param(PCMK_STONITH_DELAY_BASE)); g_assert_true(pcmk_stonith_param(PCMK_STONITH_DELAY_MAX)); g_assert_true(pcmk_stonith_param(PCMK_STONITH_HOST_ARGUMENT)); g_assert_true(pcmk_stonith_param(PCMK_STONITH_HOST_CHECK)); g_assert_true(pcmk_stonith_param(PCMK_STONITH_HOST_LIST)); g_assert_true(pcmk_stonith_param(PCMK_STONITH_HOST_MAP)); g_assert_true(pcmk_stonith_param(PCMK_STONITH_PROVIDES)); g_assert_true(pcmk_stonith_param(PCMK_STONITH_STONITH_TIMEOUT)); } static void is_stonith_action_param(void) { /* Currently, the function accepts any string not containing underbars as * the action name, so we do not need to verify particular action names. */ g_assert_false(pcmk_stonith_param("pcmk_on_unrecognized")); g_assert_true(pcmk_stonith_param("pcmk_on_action")); g_assert_true(pcmk_stonith_param("pcmk_on_timeout")); g_assert_true(pcmk_stonith_param("pcmk_on_retries")); } int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); g_test_add_func("/common/utils/parse_op_key/is_stonith_param", is_stonith_param); g_test_add_func("/common/utils/parse_op_key/is_stonith_action_param", is_stonith_action_param); return g_test_run(); } diff --git a/lib/common/tests/operations/parse_op_key_test.c b/lib/common/tests/operations/parse_op_key_test.c index 37b5d95724..857431af1b 100644 --- a/lib/common/tests/operations/parse_op_key_test.c +++ b/lib/common/tests/operations/parse_op_key_test.c @@ -1,219 +1,228 @@ -#include -#include +/* + * Copyright 2020-2021 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ #include +#include +#include + static void basic(void) { char *rsc = NULL; char *ty = NULL; guint ms = 0; g_assert_true(parse_op_key("Fencing_monitor_60000", &rsc, &ty, &ms)); g_assert_cmpstr(rsc, ==, "Fencing"); g_assert_cmpstr(ty, ==, "monitor"); g_assert_cmpint(ms, ==, 60000); free(rsc); free(ty); } static void colon_in_rsc(void) { char *rsc = NULL; char *ty = NULL; guint ms = 0; g_assert_true(parse_op_key("ClusterIP:0_start_0", &rsc, &ty, &ms)); g_assert_cmpstr(rsc, ==, "ClusterIP:0"); g_assert_cmpstr(ty, ==, "start"); g_assert_cmpint(ms, ==, 0); free(rsc); free(ty); g_assert_true(parse_op_key("imagestoreclone:1_post_notify_stop_0", &rsc, &ty, &ms)); g_assert_cmpstr(rsc, ==, "imagestoreclone:1"); g_assert_cmpstr(ty, ==, "post_notify_stop"); g_assert_cmpint(ms, ==, 0); free(rsc); free(ty); } static void dashes_in_rsc(void) { char *rsc = NULL; char *ty = NULL; guint ms = 0; g_assert_true(parse_op_key("httpd-bundle-0_monitor_30000", &rsc, &ty, &ms)); g_assert_cmpstr(rsc, ==, "httpd-bundle-0"); g_assert_cmpstr(ty, ==, "monitor"); g_assert_cmpint(ms, ==, 30000); free(rsc); free(ty); g_assert_true(parse_op_key("httpd-bundle-ip-192.168.122.132_start_0", &rsc, &ty, &ms)); g_assert_cmpstr(rsc, ==, "httpd-bundle-ip-192.168.122.132"); g_assert_cmpstr(ty, ==, "start"); g_assert_cmpint(ms, ==, 0); free(rsc); free(ty); } static void migrate_to_from(void) { char *rsc = NULL; char *ty = NULL; guint ms = 0; g_assert_true(parse_op_key("vm_migrate_from_0", &rsc, &ty, &ms)); g_assert_cmpstr(rsc, ==, "vm"); g_assert_cmpstr(ty, ==, "migrate_from"); g_assert_cmpint(ms, ==, 0); free(rsc); free(ty); g_assert_true(parse_op_key("vm_migrate_to_0", &rsc, &ty, &ms)); g_assert_cmpstr(rsc, ==, "vm"); g_assert_cmpstr(ty, ==, "migrate_to"); g_assert_cmpint(ms, ==, 0); free(rsc); free(ty); g_assert_true(parse_op_key("vm_idcc_devel_migrate_to_0", &rsc, &ty, &ms)); g_assert_cmpstr(rsc, ==, "vm_idcc_devel"); g_assert_cmpstr(ty, ==, "migrate_to"); g_assert_cmpint(ms, ==, 0); free(rsc); free(ty); } static void pre_post(void) { char *rsc = NULL; char *ty = NULL; guint ms = 0; g_assert_true(parse_op_key("rsc_drbd_7788:1_post_notify_start_0", &rsc, &ty, &ms)); g_assert_cmpstr(rsc, ==, "rsc_drbd_7788:1"); g_assert_cmpstr(ty, ==, "post_notify_start"); g_assert_cmpint(ms, ==, 0); free(rsc); free(ty); g_assert_true(parse_op_key("rabbitmq-bundle-clone_pre_notify_stop_0", &rsc, &ty, &ms)); g_assert_cmpstr(rsc, ==, "rabbitmq-bundle-clone"); g_assert_cmpstr(ty, ==, "pre_notify_stop"); g_assert_cmpint(ms, ==, 0); free(rsc); free(ty); g_assert_true(parse_op_key("post_notify_start_0", &rsc, &ty, &ms)); g_assert_cmpstr(rsc, ==, "post_notify"); g_assert_cmpstr(ty, ==, "start"); g_assert_cmpint(ms, ==, 0); free(rsc); free(ty); } static void skip_rsc(void) { char *ty = NULL; guint ms = 0; g_assert_true(parse_op_key("Fencing_monitor_60000", NULL, &ty, &ms)); g_assert_cmpstr(ty, ==, "monitor"); g_assert_cmpint(ms, ==, 60000); free(ty); } static void skip_ty(void) { char *rsc = NULL; guint ms = 0; g_assert_true(parse_op_key("Fencing_monitor_60000", &rsc, NULL, &ms)); g_assert_cmpstr(rsc, ==, "Fencing"); g_assert_cmpint(ms, ==, 60000); free(rsc); } static void skip_ms(void) { char *rsc = NULL; char *ty = NULL; g_assert_true(parse_op_key("Fencing_monitor_60000", &rsc, &ty, NULL)); g_assert_cmpstr(rsc, ==, "Fencing"); g_assert_cmpstr(ty, ==, "monitor"); free(rsc); free(ty); } static void empty_input(void) { char *rsc = NULL; char *ty = NULL; guint ms = 0; g_assert_false(parse_op_key("", &rsc, &ty, &ms)); g_assert_null(rsc); g_assert_null(ty); g_assert_cmpint(ms, ==, 0); g_assert_false(parse_op_key(NULL, &rsc, &ty, &ms)); g_assert_null(rsc); g_assert_null(ty); g_assert_cmpint(ms, ==, 0); } static void malformed_input(void) { char *rsc = NULL; char *ty = NULL; guint ms = 0; g_assert_false(parse_op_key("httpd-bundle-0", &rsc, &ty, &ms)); g_assert_null(rsc); g_assert_null(ty); g_assert_cmpint(ms, ==, 0); g_assert_false(parse_op_key("httpd-bundle-0_monitor", &rsc, &ty, &ms)); g_assert_null(rsc); g_assert_null(ty); g_assert_cmpint(ms, ==, 0); g_assert_false(parse_op_key("httpd-bundle-0_30000", &rsc, &ty, &ms)); g_assert_null(rsc); g_assert_null(ty); g_assert_cmpint(ms, ==, 0); } int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); g_test_add_func("/common/utils/parse_op_key/basic", basic); g_test_add_func("/common/utils/parse_op_key/colon_in_rsc", colon_in_rsc); g_test_add_func("/common/utils/parse_op_key/dashes_in_rsc", dashes_in_rsc); g_test_add_func("/common/utils/parse_op_key/migrate_to_from", migrate_to_from); g_test_add_func("/common/utils/parse_op_key/pre_post", pre_post); g_test_add_func("/common/utils/parse_op_key/skip_rsc", skip_rsc); g_test_add_func("/common/utils/parse_op_key/skip_ty", skip_ty); g_test_add_func("/common/utils/parse_op_key/skip_ms", skip_ms); g_test_add_func("/common/utils/parse_op_key/empty_input", empty_input); g_test_add_func("/common/utils/parse_op_key/malformed_input", malformed_input); return g_test_run(); } diff --git a/lib/common/tests/strings/pcmk__char_in_any_str_test.c b/lib/common/tests/strings/pcmk__char_in_any_str_test.c index 4fb315d3ea..11159ccad9 100644 --- a/lib/common/tests/strings/pcmk__char_in_any_str_test.c +++ b/lib/common/tests/strings/pcmk__char_in_any_str_test.c @@ -1,46 +1,55 @@ +/* + * Copyright 2020-2021 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include + #include #include #include -#include - static void empty_list(void) { g_assert_false(pcmk__char_in_any_str('x', NULL)); g_assert_false(pcmk__char_in_any_str('\0', NULL)); } static void null_char(void) { g_assert_true(pcmk__char_in_any_str('\0', "xxx", "yyy", NULL)); g_assert_true(pcmk__char_in_any_str('\0', "", NULL)); } static void in_list(void) { g_assert_true(pcmk__char_in_any_str('x', "aaa", "bbb", "xxx", NULL)); } static void not_in_list(void) { g_assert_false(pcmk__char_in_any_str('x', "aaa", "bbb", NULL)); g_assert_false(pcmk__char_in_any_str('A', "aaa", "bbb", NULL)); g_assert_false(pcmk__char_in_any_str('x', "", NULL)); } int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); g_test_add_func("/common/strings/char_in_any_str/empty_list", empty_list); g_test_add_func("/common/strings/char_in_any_str/null_char", null_char); g_test_add_func("/common/strings/char_in_any_str/in", in_list); g_test_add_func("/common/strings/char_in_any_str/not_in", not_in_list); return g_test_run(); } diff --git a/lib/common/tests/strings/pcmk__parse_ll_range_test.c b/lib/common/tests/strings/pcmk__parse_ll_range_test.c index d94a9e36bb..19049fea18 100644 --- a/lib/common/tests/strings/pcmk__parse_ll_range_test.c +++ b/lib/common/tests/strings/pcmk__parse_ll_range_test.c @@ -1,100 +1,109 @@ -#include +/* + * Copyright 2020-2021 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ #include +#include + static void empty_input_string(void) { long long start, end; g_assert_cmpint(pcmk__parse_ll_range(NULL, &start, &end), ==, pcmk_rc_unknown_format); g_assert_cmpint(pcmk__parse_ll_range("", &start, &end), ==, pcmk_rc_unknown_format); } static void missing_separator(void) { long long start, end; g_assert_cmpint(pcmk__parse_ll_range("1234", &start, &end), ==, pcmk_rc_ok); g_assert_cmpint(start, ==, 1234); g_assert_cmpint(end, ==, 1234); } static void only_separator(void) { long long start, end; g_assert_cmpint(pcmk__parse_ll_range("-", &start, &end), ==, pcmk_rc_unknown_format); g_assert_cmpint(start, ==, PCMK__PARSE_INT_DEFAULT); g_assert_cmpint(end, ==, PCMK__PARSE_INT_DEFAULT); } static void no_range_end(void) { long long start, end; g_assert_cmpint(pcmk__parse_ll_range("2000-", &start, &end), ==, pcmk_rc_ok); g_assert_cmpint(start, ==, 2000); g_assert_cmpint(end, ==, PCMK__PARSE_INT_DEFAULT); } static void no_range_start(void) { long long start, end; g_assert_cmpint(pcmk__parse_ll_range("-2020", &start, &end), ==, pcmk_rc_ok); g_assert_cmpint(start, ==, PCMK__PARSE_INT_DEFAULT); g_assert_cmpint(end, ==, 2020); } static void range_start_and_end(void) { long long start, end; g_assert_cmpint(pcmk__parse_ll_range("2000-2020", &start, &end), ==, pcmk_rc_ok); g_assert_cmpint(start, ==, 2000); g_assert_cmpint(end, ==, 2020); } static void garbage(void) { long long start, end; g_assert_cmpint(pcmk__parse_ll_range("2000x-", &start, &end), ==, pcmk_rc_unknown_format); g_assert_cmpint(start, ==, PCMK__PARSE_INT_DEFAULT); g_assert_cmpint(end, ==, PCMK__PARSE_INT_DEFAULT); g_assert_cmpint(pcmk__parse_ll_range("-x2000", &start, &end), ==, pcmk_rc_unknown_format); g_assert_cmpint(start, ==, PCMK__PARSE_INT_DEFAULT); g_assert_cmpint(end, ==, PCMK__PARSE_INT_DEFAULT); } int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); g_test_add_func("/common/strings/range/empty", empty_input_string); g_test_add_func("/common/strings/range/no_sep", missing_separator); g_test_add_func("/common/strings/range/only_sep", only_separator); g_test_add_func("/common/strings/range/no_end", no_range_end); g_test_add_func("/common/strings/range/no_start", no_range_start); g_test_add_func("/common/strings/range/start_and_end", range_start_and_end); g_test_add_func("/common/strings/range/garbage", garbage); return g_test_run(); } diff --git a/lib/common/tests/strings/pcmk__scan_double_test.c b/lib/common/tests/strings/pcmk__scan_double_test.c index 1e6138a2e6..3d0d0f160d 100644 --- a/lib/common/tests/strings/pcmk__scan_double_test.c +++ b/lib/common/tests/strings/pcmk__scan_double_test.c @@ -1,161 +1,169 @@ +/* + * Copyright 2004-2021 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include + #include // DBL_MAX, etc. #include // fabs() - #include -#include - // Ensure plenty of characters for %f display #define LOCAL_BUF_SIZE 2 * DBL_MAX_10_EXP /* * Avoids compiler warnings for floating-point equality checks. * Use for comparing numbers (e.g., 1.0 == 1.0), not expression values. */ #define ASSERT_DBL_EQ(d1, d2) g_assert_cmpfloat(fabs(d1 - d2), \ <, DBL_EPSILON); static void empty_input_string(void) { double result; // Without default_text g_assert_cmpint(pcmk__scan_double(NULL, &result, NULL, NULL), ==, EINVAL); ASSERT_DBL_EQ(result, PCMK__PARSE_DBL_DEFAULT); g_assert_cmpint(pcmk__scan_double("", &result, NULL, NULL), ==, EINVAL); ASSERT_DBL_EQ(result, PCMK__PARSE_DBL_DEFAULT); // With default_text g_assert_cmpint(pcmk__scan_double(NULL, &result, "2.0", NULL), ==, pcmk_rc_ok); ASSERT_DBL_EQ(result, 2.0); g_assert_cmpint(pcmk__scan_double("", &result, "2.0", NULL), ==, EINVAL); ASSERT_DBL_EQ(result, PCMK__PARSE_DBL_DEFAULT); } static void bad_input_string(void) { double result; // Without default text g_assert_cmpint(pcmk__scan_double("asdf", &result, NULL, NULL), ==, EINVAL); ASSERT_DBL_EQ(result, PCMK__PARSE_DBL_DEFAULT); g_assert_cmpint(pcmk__scan_double("as2.0", &result, NULL, NULL), ==, EINVAL); ASSERT_DBL_EQ(result, PCMK__PARSE_DBL_DEFAULT); // With default text (not used) g_assert_cmpint(pcmk__scan_double("asdf", &result, "2.0", NULL), ==, EINVAL); ASSERT_DBL_EQ(result, PCMK__PARSE_DBL_DEFAULT); g_assert_cmpint(pcmk__scan_double("as2.0", &result, "2.0", NULL), ==, EINVAL); ASSERT_DBL_EQ(result, PCMK__PARSE_DBL_DEFAULT); } static void trailing_chars(void) { double result; g_assert_cmpint(pcmk__scan_double("2.0asdf", &result, NULL, NULL), ==, pcmk_rc_ok); ASSERT_DBL_EQ(result, 2.0); } static void typical_case(void) { char str[LOCAL_BUF_SIZE]; double result; g_assert_cmpint(pcmk__scan_double("0.0", &result, NULL, NULL), ==, pcmk_rc_ok); ASSERT_DBL_EQ(result, 0.0); g_assert_cmpint(pcmk__scan_double("1.0", &result, NULL, NULL), ==, pcmk_rc_ok); ASSERT_DBL_EQ(result, 1.0); g_assert_cmpint(pcmk__scan_double("-1.0", &result, NULL, NULL), ==, pcmk_rc_ok); ASSERT_DBL_EQ(result, -1.0); snprintf(str, LOCAL_BUF_SIZE, "%f", DBL_MAX); g_assert_cmpint(pcmk__scan_double(str, &result, NULL, NULL), ==, pcmk_rc_ok); ASSERT_DBL_EQ(result, DBL_MAX); snprintf(str, LOCAL_BUF_SIZE, "%f", -DBL_MAX); g_assert_cmpint(pcmk__scan_double(str, &result, NULL, NULL), ==, pcmk_rc_ok); ASSERT_DBL_EQ(result, -DBL_MAX); } static void double_overflow(void) { char str[LOCAL_BUF_SIZE]; double result; /* * 1e(DBL_MAX_10_EXP + 1) produces an inf value * Can't use ASSERT_DBL_EQ() because (inf - inf) == NaN */ snprintf(str, LOCAL_BUF_SIZE, "1e%d", DBL_MAX_10_EXP + 1); g_assert_cmpint(pcmk__scan_double(str, &result, NULL, NULL), ==, EOVERFLOW); g_assert_cmpfloat(result, >, DBL_MAX); snprintf(str, LOCAL_BUF_SIZE, "-1e%d", DBL_MAX_10_EXP + 1); g_assert_cmpint(pcmk__scan_double(str, &result, NULL, NULL), ==, EOVERFLOW); g_assert_cmpfloat(result, <, -DBL_MAX); } static void double_underflow(void) { char str[LOCAL_BUF_SIZE]; double result; /* * 1e(DBL_MIN_10_EXP - 1) produces a denormalized value (between 0 * and DBL_MIN) * * C99/C11: result will be **no greater than** DBL_MIN */ snprintf(str, LOCAL_BUF_SIZE, "1e%d", DBL_MIN_10_EXP - 1); g_assert_cmpint(pcmk__scan_double(str, &result, NULL, NULL), ==, pcmk_rc_underflow); g_assert_cmpfloat(result, >=, 0.0); g_assert_cmpfloat(result, <=, DBL_MIN); snprintf(str, LOCAL_BUF_SIZE, "-1e%d", DBL_MIN_10_EXP - 1); g_assert_cmpint(pcmk__scan_double(str, &result, NULL, NULL), ==, pcmk_rc_underflow); g_assert_cmpfloat(result, <=, 0.0); g_assert_cmpfloat(result, >=, -DBL_MIN); } int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); // Test for input string issues g_test_add_func("/common/strings/double/empty_input", empty_input_string); g_test_add_func("/common/strings/double/bad_input", bad_input_string); g_test_add_func("/common/strings/double/trailing_chars", trailing_chars); // Test for numeric issues g_test_add_func("/common/strings/double/typical", typical_case); g_test_add_func("/common/strings/double/overflow", double_overflow); g_test_add_func("/common/strings/double/underflow", double_underflow); return g_test_run(); } diff --git a/lib/common/tests/strings/pcmk__str_any_of_test.c b/lib/common/tests/strings/pcmk__str_any_of_test.c index 2e2147e2d5..61340d7f8c 100644 --- a/lib/common/tests/strings/pcmk__str_any_of_test.c +++ b/lib/common/tests/strings/pcmk__str_any_of_test.c @@ -1,47 +1,56 @@ +/* + * Copyright 2020-2021 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include + #include #include #include -#include - static void empty_input_list(void) { g_assert_false(pcmk__strcase_any_of("xxx", NULL)); g_assert_false(pcmk__str_any_of("xxx", NULL)); g_assert_false(pcmk__strcase_any_of("", NULL)); g_assert_false(pcmk__str_any_of("", NULL)); } static void empty_string(void) { g_assert_false(pcmk__strcase_any_of("", "xxx", "yyy", NULL)); g_assert_false(pcmk__str_any_of("", "xxx", "yyy", NULL)); g_assert_false(pcmk__strcase_any_of(NULL, "xxx", "yyy", NULL)); g_assert_false(pcmk__str_any_of(NULL, "xxx", "yyy", NULL)); } static void in_list(void) { g_assert_true(pcmk__strcase_any_of("xxx", "aaa", "bbb", "xxx", NULL)); g_assert_true(pcmk__str_any_of("xxx", "aaa", "bbb", "xxx", NULL)); g_assert_true(pcmk__strcase_any_of("XXX", "aaa", "bbb", "xxx", NULL)); } static void not_in_list(void) { g_assert_false(pcmk__strcase_any_of("xxx", "aaa", "bbb", NULL)); g_assert_false(pcmk__str_any_of("xxx", "aaa", "bbb", NULL)); g_assert_false(pcmk__str_any_of("AAA", "aaa", "bbb", NULL)); } int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); g_test_add_func("/common/strings/any_of/empty_list", empty_input_list); g_test_add_func("/common/strings/any_of/empty_string", empty_string); g_test_add_func("/common/strings/any_of/in", in_list); g_test_add_func("/common/strings/any_of/not_in", not_in_list); return g_test_run(); } diff --git a/lib/common/tests/strings/pcmk__strcmp_test.c b/lib/common/tests/strings/pcmk__strcmp_test.c index d23ed7ed6a..85790eced0 100644 --- a/lib/common/tests/strings/pcmk__strcmp_test.c +++ b/lib/common/tests/strings/pcmk__strcmp_test.c @@ -1,78 +1,87 @@ +/* + * Copyright 2020-2021 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include + #include #include #include -#include - static void same_pointer(void) { const char *s1 = "abcd"; const char *s2 = "wxyz"; g_assert_cmpint(pcmk__strcmp(s1, s1, pcmk__str_none), ==, 0); g_assert_true(pcmk__str_eq(s1, s1, pcmk__str_none)); g_assert_cmpint(pcmk__strcmp(s1, s2, pcmk__str_none), !=, 0); g_assert_false(pcmk__str_eq(s1, s2, pcmk__str_none)); g_assert_cmpint(pcmk__strcmp(NULL, NULL, pcmk__str_none), ==, 0); } static void one_is_null(void) { const char *s1 = "abcd"; g_assert_cmpint(pcmk__strcmp(s1, NULL, pcmk__str_null_matches), ==, 0); g_assert_true(pcmk__str_eq(s1, NULL, pcmk__str_null_matches)); g_assert_cmpint(pcmk__strcmp(NULL, s1, pcmk__str_null_matches), ==, 0); g_assert_cmpint(pcmk__strcmp(s1, NULL, pcmk__str_none), >, 0); g_assert_false(pcmk__str_eq(s1, NULL, pcmk__str_none)); g_assert_cmpint(pcmk__strcmp(NULL, s1, pcmk__str_none), <, 0); } static void case_matters(void) { const char *s1 = "abcd"; const char *s2 = "ABCD"; g_assert_cmpint(pcmk__strcmp(s1, s2, pcmk__str_none), >, 0); g_assert_false(pcmk__str_eq(s1, s2, pcmk__str_none)); g_assert_cmpint(pcmk__strcmp(s2, s1, pcmk__str_none), <, 0); } static void case_insensitive(void) { const char *s1 = "abcd"; const char *s2 = "ABCD"; g_assert_cmpint(pcmk__strcmp(s1, s2, pcmk__str_casei), ==, 0); g_assert_true(pcmk__str_eq(s1, s2, pcmk__str_casei)); } static void regex(void) { const char *s1 = "abcd"; const char *s2 = "ABCD"; g_assert_cmpint(pcmk__strcmp(NULL, "a..d", pcmk__str_regex), ==, 1); g_assert_cmpint(pcmk__strcmp(s1, NULL, pcmk__str_regex), ==, 1); g_assert_cmpint(pcmk__strcmp(s1, "a..d", pcmk__str_regex), ==, 0); g_assert_true(pcmk__str_eq(s1, "a..d", pcmk__str_regex)); g_assert_cmpint(pcmk__strcmp(s1, "xxyy", pcmk__str_regex), !=, 0); g_assert_false(pcmk__str_eq(s1, "xxyy", pcmk__str_regex)); g_assert_cmpint(pcmk__strcmp(s2, "a..d", pcmk__str_regex|pcmk__str_casei), ==, 0); g_assert_true(pcmk__str_eq(s2, "a..d", pcmk__str_regex|pcmk__str_casei)); g_assert_cmpint(pcmk__strcmp(s2, "a..d", pcmk__str_regex), !=, 0); g_assert_false(pcmk__str_eq(s2, "a..d", pcmk__str_regex)); g_assert_cmpint(pcmk__strcmp(s2, "*ab", pcmk__str_regex), ==, 1); } int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); g_test_add_func("/common/strings/strcmp/same_pointer", same_pointer); g_test_add_func("/common/strings/strcmp/one_is_null", one_is_null); g_test_add_func("/common/strings/strcmp/case_matters", case_matters); g_test_add_func("/common/strings/strcmp/case_insensitive", case_insensitive); g_test_add_func("/common/strings/strcmp/regex", regex); return g_test_run(); } diff --git a/lib/common/tests/utils/pcmk_str_is_infinity_test.c b/lib/common/tests/utils/pcmk_str_is_infinity_test.c index 18362a7208..1e012d9671 100644 --- a/lib/common/tests/utils/pcmk_str_is_infinity_test.c +++ b/lib/common/tests/utils/pcmk_str_is_infinity_test.c @@ -1,56 +1,65 @@ +/* + * Copyright 2020-2021 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include + #include #include #include -#include - static void uppercase_str_passes(void) { g_assert_true(pcmk_str_is_infinity("INFINITY")); g_assert_true(pcmk_str_is_infinity("+INFINITY")); } static void mixed_case_str_fails(void) { g_assert_false(pcmk_str_is_infinity("infinity")); g_assert_false(pcmk_str_is_infinity("+infinity")); g_assert_false(pcmk_str_is_infinity("Infinity")); g_assert_false(pcmk_str_is_infinity("+Infinity")); } static void added_whitespace_fails(void) { g_assert_false(pcmk_str_is_infinity(" INFINITY")); g_assert_false(pcmk_str_is_infinity("INFINITY ")); g_assert_false(pcmk_str_is_infinity(" INFINITY ")); g_assert_false(pcmk_str_is_infinity("+ INFINITY")); } static void empty_str_fails(void) { g_assert_false(pcmk_str_is_infinity(NULL)); g_assert_false(pcmk_str_is_infinity("")); } static void minus_infinity_fails(void) { g_assert_false(pcmk_str_is_infinity("-INFINITY")); } int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); g_test_add_func("/common/utils/infinity/uppercase", uppercase_str_passes); g_test_add_func("/common/utils/infinity/mixed_case", mixed_case_str_fails); g_test_add_func("/common/utils/infinity/whitespace", added_whitespace_fails); g_test_add_func("/common/utils/infinity/empty", empty_str_fails); g_test_add_func("/common/utils/infinity/minus_infinity", minus_infinity_fails); return g_test_run(); } diff --git a/lib/common/tests/utils/pcmk_str_is_minus_infinity_test.c b/lib/common/tests/utils/pcmk_str_is_minus_infinity_test.c index e58d5121ba..9daf1100e0 100644 --- a/lib/common/tests/utils/pcmk_str_is_minus_infinity_test.c +++ b/lib/common/tests/utils/pcmk_str_is_minus_infinity_test.c @@ -1,53 +1,62 @@ +/* + * Copyright 2020-2021 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include + #include #include #include -#include - static void uppercase_str_passes(void) { g_assert_true(pcmk_str_is_minus_infinity("-INFINITY")); } static void mixed_case_str_fails(void) { g_assert_false(pcmk_str_is_minus_infinity("-infinity")); g_assert_false(pcmk_str_is_minus_infinity("-Infinity")); } static void added_whitespace_fails(void) { g_assert_false(pcmk_str_is_minus_infinity(" -INFINITY")); g_assert_false(pcmk_str_is_minus_infinity("-INFINITY ")); g_assert_false(pcmk_str_is_minus_infinity(" -INFINITY ")); g_assert_false(pcmk_str_is_minus_infinity("- INFINITY")); } static void empty_str_fails(void) { g_assert_false(pcmk_str_is_minus_infinity(NULL)); g_assert_false(pcmk_str_is_minus_infinity("")); } static void infinity_fails(void) { g_assert_false(pcmk_str_is_minus_infinity("INFINITY")); } int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); g_test_add_func("/common/utils/minus_infinity/uppercase", uppercase_str_passes); g_test_add_func("/common/utils/minus_infinity/mixed_case", mixed_case_str_fails); g_test_add_func("/common/utils/minus_infinity/whitespace", added_whitespace_fails); g_test_add_func("/common/utils/minus_infinity/empty", empty_str_fails); g_test_add_func("/common/utils/minus_infinity/infinity", infinity_fails); return g_test_run(); } diff --git a/lib/pacemaker/pcmk_cluster_queries.c b/lib/pacemaker/pcmk_cluster_queries.c index 7dd9032987..c68cf9d548 100644 --- a/lib/pacemaker/pcmk_cluster_queries.c +++ b/lib/pacemaker/pcmk_cluster_queries.c @@ -1,508 +1,510 @@ /* * Copyright 2020-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ +#include + #include // gboolean, GMainLoop, etc. #include // xmlNode #include #include #include #include #include #include #include #include #include #include #include #include #define DEFAULT_MESSAGE_TIMEOUT_MS 30000 typedef struct { pcmk__output_t *out; GMainLoop *mainloop; int rc; guint message_timer_id; guint message_timeout_ms; } data_t; static void quit_main_loop(data_t *data) { if (data->mainloop != NULL) { GMainLoop *mloop = data->mainloop; data->mainloop = NULL; // Don't re-enter this block pcmk_quit_main_loop(mloop, 10); g_main_loop_unref(mloop); } } static gboolean admin_message_timeout(gpointer user_data) { data_t *data = user_data; pcmk__output_t *out = data->out; out->err(out, "error: No reply received from controller before timeout (%dms)", data->message_timeout_ms); data->message_timer_id = 0; data->rc = ETIMEDOUT; quit_main_loop(data); return FALSE; // Tells glib to remove source } static void start_main_loop(data_t *data) { if (data->message_timeout_ms < 1) { data->message_timeout_ms = DEFAULT_MESSAGE_TIMEOUT_MS; } data->rc = ECONNRESET; // For unexpected disconnects data->mainloop = g_main_loop_new(NULL, FALSE); data->message_timer_id = g_timeout_add(data->message_timeout_ms, admin_message_timeout, data); g_main_loop_run(data->mainloop); } static void event_done(data_t *data, pcmk_ipc_api_t *api) { pcmk_disconnect_ipc(api); quit_main_loop(data); } static pcmk_controld_api_reply_t * controld_event_reply(data_t *data, pcmk_ipc_api_t *controld_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data) { pcmk__output_t *out = data->out; pcmk_controld_api_reply_t *reply = event_data; switch (event_type) { case pcmk_ipc_event_disconnect: if (data->rc == ECONNRESET) { // Unexpected out->err(out, "error: Lost connection to controller"); } event_done(data, controld_api); return NULL; case pcmk_ipc_event_reply: break; default: return NULL; } if (data->message_timer_id != 0) { g_source_remove(data->message_timer_id); data->message_timer_id = 0; } if (status != CRM_EX_OK) { out->err(out, "error: Bad reply from controller: %s", crm_exit_str(status)); data->rc = EBADMSG; event_done(data, controld_api); return NULL; } if (reply->reply_type != pcmk_controld_reply_ping) { out->err(out, "error: Unknown reply type %d from controller", reply->reply_type); data->rc = EBADMSG; event_done(data, controld_api); return NULL; } return reply; } static void controller_status_event_cb(pcmk_ipc_api_t *controld_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { data_t *data = user_data; pcmk__output_t *out = data->out; pcmk_controld_api_reply_t *reply = controld_event_reply(data, controld_api, event_type, status, event_data); if (reply != NULL) { out->message(out, "health", reply->data.ping.sys_from, reply->host_from, reply->data.ping.fsa_state, reply->data.ping.result); data->rc = pcmk_rc_ok; } event_done(data, controld_api); } static void designated_controller_event_cb(pcmk_ipc_api_t *controld_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { data_t *data = user_data; pcmk__output_t *out = data->out; pcmk_controld_api_reply_t *reply = controld_event_reply(data, controld_api, event_type, status, event_data); if (reply != NULL) { out->message(out, "dc", reply->host_from); data->rc = pcmk_rc_ok; } event_done(data, controld_api); } static void pacemakerd_event_cb(pcmk_ipc_api_t *pacemakerd_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { data_t *data = user_data; pcmk__output_t *out = data->out; pcmk_pacemakerd_api_reply_t *reply = event_data; crm_time_t *crm_when; char *pinged_buf = NULL; switch (event_type) { case pcmk_ipc_event_disconnect: if (data->rc == ECONNRESET) { // Unexpected out->err(out, "error: Lost connection to pacemakerd"); } event_done(data, pacemakerd_api); return; case pcmk_ipc_event_reply: break; default: return; } if (data->message_timer_id != 0) { g_source_remove(data->message_timer_id); data->message_timer_id = 0; } if (status != CRM_EX_OK) { out->err(out, "error: Bad reply from pacemakerd: %s", crm_exit_str(status)); event_done(data, pacemakerd_api); return; } if (reply->reply_type != pcmk_pacemakerd_reply_ping) { out->err(out, "error: Unknown reply type %d from pacemakerd", reply->reply_type); event_done(data, pacemakerd_api); return; } // Parse desired information from reply crm_when = crm_time_new(NULL); crm_time_set_timet(crm_when, &reply->data.ping.last_good); pinged_buf = crm_time_as_string(crm_when, crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone); out->message(out, "pacemakerd-health", reply->data.ping.sys_from, (reply->data.ping.status == pcmk_rc_ok)? pcmk_pacemakerd_api_daemon_state_enum2text( reply->data.ping.state):"query failed", (reply->data.ping.status == pcmk_rc_ok)?pinged_buf:""); data->rc = pcmk_rc_ok; crm_time_free(crm_when); free(pinged_buf); event_done(data, pacemakerd_api); } static pcmk_ipc_api_t * ipc_connect(data_t *data, enum pcmk_ipc_server server, pcmk_ipc_callback_t cb) { int rc; pcmk__output_t *out = data->out; pcmk_ipc_api_t *api = NULL; rc = pcmk_new_ipc_api(&api, server); if (api == NULL) { out->err(out, "error: Could not connect to %s: %s", pcmk_ipc_name(api, true), pcmk_rc_str(rc)); data->rc = rc; return NULL; } if (cb != NULL) { pcmk_register_ipc_callback(api, cb, data); } rc = pcmk_connect_ipc(api, pcmk_ipc_dispatch_main); if (rc != pcmk_rc_ok) { out->err(out, "error: Could not connect to %s: %s", pcmk_ipc_name(api, true), pcmk_rc_str(rc)); data->rc = rc; return NULL; } return api; } int pcmk__controller_status(pcmk__output_t *out, char *dest_node, guint message_timeout_ms) { data_t data = { .out = out, .mainloop = NULL, .rc = pcmk_rc_ok, .message_timer_id = 0, .message_timeout_ms = message_timeout_ms }; pcmk_ipc_api_t *controld_api = ipc_connect(&data, pcmk_ipc_controld, controller_status_event_cb); if (controld_api != NULL) { int rc = pcmk_controld_api_ping(controld_api, dest_node); if (rc != pcmk_rc_ok) { out->err(out, "error: Command failed: %s", pcmk_rc_str(rc)); data.rc = rc; } start_main_loop(&data); pcmk_free_ipc_api(controld_api); } return data.rc; } int pcmk_controller_status(xmlNodePtr *xml, char *dest_node, unsigned int message_timeout_ms) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__out_prologue(&out, xml); if (rc != pcmk_rc_ok) { return rc; } pcmk__register_lib_messages(out); rc = pcmk__controller_status(out, dest_node, (guint) message_timeout_ms); pcmk__out_epilogue(out, xml, rc); return rc; } int pcmk__designated_controller(pcmk__output_t *out, guint message_timeout_ms) { data_t data = { .out = out, .mainloop = NULL, .rc = pcmk_rc_ok, .message_timer_id = 0, .message_timeout_ms = message_timeout_ms }; pcmk_ipc_api_t *controld_api = ipc_connect(&data, pcmk_ipc_controld, designated_controller_event_cb); if (controld_api != NULL) { int rc = pcmk_controld_api_ping(controld_api, NULL); if (rc != pcmk_rc_ok) { out->err(out, "error: Command failed: %s", pcmk_rc_str(rc)); data.rc = rc; } start_main_loop(&data); pcmk_free_ipc_api(controld_api); } return data.rc; } int pcmk_designated_controller(xmlNodePtr *xml, unsigned int message_timeout_ms) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__out_prologue(&out, xml); if (rc != pcmk_rc_ok) { return rc; } pcmk__register_lib_messages(out); rc = pcmk__designated_controller(out, (guint) message_timeout_ms); pcmk__out_epilogue(out, xml, rc); return rc; } int pcmk__pacemakerd_status(pcmk__output_t *out, char *ipc_name, guint message_timeout_ms) { data_t data = { .out = out, .mainloop = NULL, .rc = pcmk_rc_ok, .message_timer_id = 0, .message_timeout_ms = message_timeout_ms }; pcmk_ipc_api_t *pacemakerd_api = ipc_connect(&data, pcmk_ipc_pacemakerd, pacemakerd_event_cb); if (pacemakerd_api != NULL) { int rc = pcmk_pacemakerd_api_ping(pacemakerd_api, ipc_name); if (rc != pcmk_rc_ok) { out->err(out, "error: Command failed: %s", pcmk_rc_str(rc)); data.rc = rc; } start_main_loop(&data); pcmk_free_ipc_api(pacemakerd_api); } return data.rc; } int pcmk_pacemakerd_status(xmlNodePtr *xml, char *ipc_name, unsigned int message_timeout_ms) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__out_prologue(&out, xml); if (rc != pcmk_rc_ok) { return rc; } pcmk__register_lib_messages(out); rc = pcmk__pacemakerd_status(out, ipc_name, (guint) message_timeout_ms); pcmk__out_epilogue(out, xml, rc); return rc; } /* user data for looping through remote node xpath searches */ struct node_data { pcmk__output_t *out; int found; const char *field; /* XML attribute to check for node name */ const char *type; gboolean BASH_EXPORT; }; static void remote_node_print_helper(xmlNode *result, void *user_data) { struct node_data *data = user_data; pcmk__output_t *out = data->out; const char *name = crm_element_value(result, XML_ATTR_UNAME); const char *id = crm_element_value(result, data->field); // node name and node id are the same for remote/guest nodes out->message(out, "crmadmin-node", data->type, name ? name : id, id, data->BASH_EXPORT); data->found++; } // \return Standard Pacemaker return code int pcmk__list_nodes(pcmk__output_t *out, char *node_types, gboolean BASH_EXPORT) { cib_t *the_cib = cib_new(); xmlNode *xml_node = NULL; int rc; if (the_cib == NULL) { return ENOMEM; } rc = the_cib->cmds->signon(the_cib, crm_system_name, cib_command); if (rc != pcmk_ok) { return pcmk_legacy2rc(rc); } rc = the_cib->cmds->query(the_cib, NULL, &xml_node, cib_scope_local | cib_sync_call); if (rc == pcmk_ok) { struct node_data data = { .out = out, .found = 0, .BASH_EXPORT = BASH_EXPORT }; out->begin_list(out, NULL, NULL, "nodes"); if (!pcmk__str_empty(node_types) && strstr(node_types, "all")) { node_types = NULL; } if (pcmk__str_empty(node_types) || strstr(node_types, "cluster")) { data.field = "id"; data.type = "cluster"; crm_foreach_xpath_result(xml_node, PCMK__XP_MEMBER_NODE_CONFIG, remote_node_print_helper, &data); } if (pcmk__str_empty(node_types) || strstr(node_types, "guest")) { data.field = "value"; data.type = "guest"; crm_foreach_xpath_result(xml_node, PCMK__XP_GUEST_NODE_CONFIG, remote_node_print_helper, &data); } if (pcmk__str_empty(node_types) || !pcmk__strcmp(node_types, ",|^remote", pcmk__str_regex)) { data.field = "id"; data.type = "remote"; crm_foreach_xpath_result(xml_node, PCMK__XP_REMOTE_NODE_CONFIG, remote_node_print_helper, &data); } out->end_list(out); if (data.found == 0) { out->info(out, "No nodes configured"); } free_xml(xml_node); } the_cib->cmds->signoff(the_cib); return pcmk_legacy2rc(rc); } int pcmk_list_nodes(xmlNodePtr *xml, char *node_types) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__out_prologue(&out, xml); if (rc != pcmk_rc_ok) { return rc; } pcmk__register_lib_messages(out); rc = pcmk__list_nodes(out, node_types, FALSE); pcmk__out_epilogue(out, xml, rc); return rc; } diff --git a/lib/pengine/tags.c b/lib/pengine/tags.c index ad1d8184af..81c27e41ec 100644 --- a/lib/pengine/tags.c +++ b/lib/pengine/tags.c @@ -1,109 +1,111 @@ /* - * Copyright 2020 the Pacemaker project contributors + * Copyright 2020-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ +#include + #include #include #include #include #include GList * pe__rscs_with_tag(pe_working_set_t *data_set, const char *tag_name) { gpointer value; GList *retval = NULL; if (data_set->tags == NULL) { return retval; } value = g_hash_table_lookup(data_set->tags, tag_name); if (value == NULL) { return retval; } for (GList *refs = ((pe_tag_t *) value)->refs; refs; refs = refs->next) { const char *id = (const char *) refs->data; pe_resource_t *rsc = pe_find_resource_with_flags(data_set->resources, id, pe_find_renamed|pe_find_any); if (!rsc) { continue; } retval = g_list_append(retval, strdup(rsc_printable_id(rsc))); } return retval; } GList * pe__unames_with_tag(pe_working_set_t *data_set, const char *tag_name) { gpointer value; GList *retval = NULL; if (data_set->tags == NULL) { return retval; } value = g_hash_table_lookup(data_set->tags, tag_name); if (value == NULL) { return retval; } /* Iterate over the list of node IDs. */ for (GList *refs = ((pe_tag_t *) value)->refs; refs; refs = refs->next) { /* Find the node that has this ID. */ const char *id = (const char *) refs->data; pe_node_t *node = pe_find_node_id(data_set->nodes, id); if (!node) { continue; } /* Get the uname for the node and add it to the return list. */ retval = g_list_append(retval, strdup(node->details->uname)); } return retval; } bool pe__rsc_has_tag(pe_working_set_t *data_set, const char *rsc_name, const char *tag_name) { GList *rscs = pe__rscs_with_tag(data_set, tag_name); bool retval = false; if (rscs == NULL) { return retval; } retval = g_list_find_custom(rscs, rsc_name, (GCompareFunc) strcmp) != NULL; g_list_free_full(rscs, free); return retval; } bool pe__uname_has_tag(pe_working_set_t *data_set, const char *node_name, const char *tag_name) { GList *unames = pe__unames_with_tag(data_set, tag_name); bool retval = false; if (unames == NULL) { return retval; } retval = g_list_find_custom(unames, node_name, (GCompareFunc) strcmp) != NULL; g_list_free_full(unames, free); return retval; } diff --git a/tools/crm_mon_print.c b/tools/crm_mon_print.c index 7cbc9c9a91..09943a8caf 100644 --- a/tools/crm_mon_print.c +++ b/tools/crm_mon_print.c @@ -1,869 +1,871 @@ /* * Copyright 2019-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ +#include + #include #include #include #include #ifndef PCMK__CONFIG_H # define PCMK__CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "crm_mon.h" static int print_rsc_history(pe_working_set_t *data_set, pe_node_t *node, xmlNode *rsc_entry, unsigned int mon_ops, GList *op_list); static int print_node_history(pe_working_set_t *data_set, pe_node_t *node, xmlNode *node_state, gboolean operations, unsigned int mon_ops, GList *only_node, GList *only_rsc); static int print_node_summary(pe_working_set_t * data_set, gboolean operations, unsigned int mon_ops, GList *only_node, GList *only_rsc, gboolean print_spacer); static int print_cluster_tickets(pe_working_set_t * data_set, gboolean print_spacer); static int print_neg_locations(pe_working_set_t *data_set, unsigned int mon_ops, const char *prefix, GList *only_rsc, gboolean print_spacer); /*! * \internal * \brief Return resource display options corresponding to command-line choices * * \return Bitmask of pe_print_options suitable for resource print functions */ static unsigned int get_resource_display_options(unsigned int mon_ops) { int print_opts = 0; if (pcmk_is_set(mon_ops, mon_op_print_pending)) { print_opts |= pe_print_pending; } if (pcmk_is_set(mon_ops, mon_op_print_clone_detail)) { print_opts |= pe_print_clone_details|pe_print_implicit; } if (!pcmk_is_set(mon_ops, mon_op_inactive_resources)) { print_opts |= pe_print_clone_active; } if (pcmk_is_set(mon_ops, mon_op_print_brief)) { print_opts |= pe_print_brief; } return print_opts; } static GList * build_uname_list(pe_working_set_t *data_set, const char *s) { GList *unames = NULL; if (pcmk__str_eq(s, "*", pcmk__str_null_matches)) { /* Nothing was given so return a list of all node names. Or, '*' was * given. This would normally fall into the pe__unames_with_tag branch * where it will return an empty list. Catch it here instead. */ unames = g_list_prepend(unames, strdup("*")); } else { pe_node_t *node = pe_find_node(data_set->nodes, s); if (node) { /* The given string was a valid uname for a node. Return a * singleton list containing just that uname. */ unames = g_list_prepend(unames, strdup(s)); } else { /* The given string was not a valid uname. It's either a tag or * it's a typo or something. In the first case, we'll return a * list of all the unames of the nodes with the given tag. In the * second case, we'll return a NULL pointer and nothing will * get displayed. */ unames = pe__unames_with_tag(data_set, s); } } return unames; } static GList * build_rsc_list(pe_working_set_t *data_set, const char *s) { GList *resources = NULL; if (pcmk__str_eq(s, "*", pcmk__str_null_matches)) { resources = g_list_prepend(resources, strdup("*")); } else { pe_resource_t *rsc = pe_find_resource_with_flags(data_set->resources, s, pe_find_renamed|pe_find_any); if (rsc) { /* A colon in the name we were given means we're being asked to filter * on a specific instance of a cloned resource. Put that exact string * into the filter list. Otherwise, use the printable ID of whatever * resource was found that matches what was asked for. */ if (strstr(s, ":") != NULL) { resources = g_list_prepend(resources, strdup(rsc->id)); } else { resources = g_list_prepend(resources, strdup(rsc_printable_id(rsc))); } } else { /* The given string was not a valid resource name. It's either * a tag or it's a typo or something. See build_uname_list for * more detail. */ resources = pe__rscs_with_tag(data_set, s); } } return resources; } static int failure_count(pe_working_set_t *data_set, pe_node_t *node, pe_resource_t *rsc, time_t *last_failure) { return rsc ? pe_get_failcount(node, rsc, last_failure, pe_fc_default, NULL, data_set) : 0; } static GList * get_operation_list(xmlNode *rsc_entry) { GList *op_list = NULL; xmlNode *rsc_op = NULL; for (rsc_op = pcmk__xe_first_child(rsc_entry); rsc_op != NULL; rsc_op = pcmk__xe_next(rsc_op)) { const char *task = crm_element_value(rsc_op, XML_LRM_ATTR_TASK); const char *interval_ms_s = crm_element_value(rsc_op, XML_LRM_ATTR_INTERVAL_MS); const char *op_rc = crm_element_value(rsc_op, XML_LRM_ATTR_RC); int op_rc_i; pcmk__scan_min_int(op_rc, &op_rc_i, 0); /* Display 0-interval monitors as "probe" */ if (pcmk__str_eq(task, CRMD_ACTION_STATUS, pcmk__str_casei) && pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches | pcmk__str_casei)) { task = "probe"; } /* Ignore notifies and some probes */ if (pcmk__str_eq(task, CRMD_ACTION_NOTIFY, pcmk__str_casei) || (pcmk__str_eq(task, "probe", pcmk__str_casei) && (op_rc_i == 7))) { continue; } if (pcmk__str_eq((const char *)rsc_op->name, XML_LRM_TAG_RSC_OP, pcmk__str_none)) { op_list = g_list_append(op_list, rsc_op); } } op_list = g_list_sort(op_list, sort_op_by_callid); return op_list; } /*! * \internal * \brief Print resource operation/failure history * * \param[in] data_set Cluster state to display. * \param[in] node Node that ran this resource. * \param[in] rsc_entry Root of XML tree describing resource status. * \param[in] mon_ops Bitmask of mon_op_*. * \param[in] op_list A list of operations to print. */ static int print_rsc_history(pe_working_set_t *data_set, pe_node_t *node, xmlNode *rsc_entry, unsigned int mon_ops, GList *op_list) { pcmk__output_t *out = data_set->priv; GList *gIter = NULL; int rc = pcmk_rc_no_output; const char *rsc_id = crm_element_value(rsc_entry, XML_ATTR_ID); pe_resource_t *rsc = pe_find_resource(data_set->resources, rsc_id); /* Print each operation */ for (gIter = op_list; gIter != NULL; gIter = gIter->next) { xmlNode *xml_op = (xmlNode *) gIter->data; const char *task = crm_element_value(xml_op, XML_LRM_ATTR_TASK); const char *interval_ms_s = crm_element_value(xml_op, XML_LRM_ATTR_INTERVAL_MS); const char *op_rc = crm_element_value(xml_op, XML_LRM_ATTR_RC); int op_rc_i; pcmk__scan_min_int(op_rc, &op_rc_i, 0); /* Display 0-interval monitors as "probe" */ if (pcmk__str_eq(task, CRMD_ACTION_STATUS, pcmk__str_casei) && pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches | pcmk__str_casei)) { task = "probe"; } /* If this is the first printed operation, print heading for resource */ if (rc == pcmk_rc_no_output) { time_t last_failure = 0; int failcount = failure_count(data_set, node, rsc, &last_failure); out->message(out, "resource-history", rsc, rsc_id, TRUE, failcount, last_failure, TRUE); rc = pcmk_rc_ok; } /* Print the operation */ out->message(out, "op-history", xml_op, task, interval_ms_s, op_rc_i, pcmk_is_set(mon_ops, mon_op_print_timing)); } /* Free the list we created (no need to free the individual items) */ g_list_free(op_list); PCMK__OUTPUT_LIST_FOOTER(out, rc); return rc; } /*! * \internal * \brief Print node operation/failure history * * \param[in] data_set Cluster state to display. * \param[in] node_state Root of XML tree describing node status. * \param[in] operations Whether to print operations or just failcounts. * \param[in] mon_ops Bitmask of mon_op_*. */ static int print_node_history(pe_working_set_t *data_set, pe_node_t *node, xmlNode *node_state, gboolean operations, unsigned int mon_ops, GList *only_node, GList *only_rsc) { pcmk__output_t *out = data_set->priv; xmlNode *lrm_rsc = NULL; xmlNode *rsc_entry = NULL; int rc = pcmk_rc_no_output; lrm_rsc = find_xml_node(node_state, XML_CIB_TAG_LRM, FALSE); lrm_rsc = find_xml_node(lrm_rsc, XML_LRM_TAG_RESOURCES, FALSE); /* Print history of each of the node's resources */ for (rsc_entry = pcmk__xe_first_child(lrm_rsc); rsc_entry != NULL; rsc_entry = pcmk__xe_next(rsc_entry)) { const char *rsc_id = crm_element_value(rsc_entry, XML_ATTR_ID); pe_resource_t *rsc = pe_find_resource(data_set->resources, rsc_id); if (!pcmk__str_eq((const char *)rsc_entry->name, XML_LRM_TAG_RESOURCE, pcmk__str_none)) { continue; } /* We can't use is_filtered here to filter group resources. For is_filtered, * we have to decide whether to check the parent or not. If we check the * parent, all elements of a group will always be printed because that's how * is_filtered works for groups. If we do not check the parent, sometimes * this will filter everything out. * * For other resource types, is_filtered is okay. */ if (uber_parent(rsc)->variant == pe_group) { if (!pcmk__str_in_list(only_rsc, rsc_printable_id(rsc)) && !pcmk__str_in_list(only_rsc, rsc_printable_id(uber_parent(rsc)))) { continue; } } else { if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) { continue; } } if (operations == FALSE) { time_t last_failure = 0; int failcount = failure_count(data_set, node, rsc, &last_failure); if (failcount <= 0) { continue; } if (rc == pcmk_rc_no_output) { rc = pcmk_rc_ok; out->message(out, "node", node, get_resource_display_options(mon_ops), FALSE, NULL, pcmk_is_set(mon_ops, mon_op_print_clone_detail), pcmk_is_set(mon_ops, mon_op_print_brief), pcmk_is_set(mon_ops, mon_op_group_by_node), only_node, only_rsc); } out->message(out, "resource-history", rsc, rsc_id, FALSE, failcount, last_failure, FALSE); } else { GList *op_list = get_operation_list(rsc_entry); if (op_list == NULL) { continue; } if (rc == pcmk_rc_no_output) { rc = pcmk_rc_ok; out->message(out, "node", node, get_resource_display_options(mon_ops), FALSE, NULL, pcmk_is_set(mon_ops, mon_op_print_clone_detail), pcmk_is_set(mon_ops, mon_op_print_brief), pcmk_is_set(mon_ops, mon_op_group_by_node), only_node, only_rsc); } print_rsc_history(data_set, node, rsc_entry, mon_ops, op_list); } } PCMK__OUTPUT_LIST_FOOTER(out, rc); return rc; } /*! * \internal * \brief Print history for all nodes. * * \param[in] data_set Cluster state to display. * \param[in] operations Whether to print operations or just failcounts. * \param[in] mon_ops Bitmask of mon_op_*. */ static int print_node_summary(pe_working_set_t * data_set, gboolean operations, unsigned int mon_ops, GList *only_node, GList *only_rsc, gboolean print_spacer) { pcmk__output_t *out = data_set->priv; xmlNode *node_state = NULL; xmlNode *cib_status = get_object_root(XML_CIB_TAG_STATUS, data_set->input); int rc = pcmk_rc_no_output; if (xmlChildElementCount(cib_status) == 0) { return rc; } /* Print each node in the CIB status */ for (node_state = pcmk__xe_first_child(cib_status); node_state != NULL; node_state = pcmk__xe_next(node_state)) { pe_node_t *node; if (!pcmk__str_eq((const char *)node_state->name, XML_CIB_TAG_STATE, pcmk__str_none)) { continue; } node = pe_find_node_id(data_set->nodes, ID(node_state)); if (!node || !node->details || !node->details->online) { continue; } if (!pcmk__str_in_list(only_node, node->details->uname)) { continue; } PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, operations ? "Operations" : "Migration Summary"); print_node_history(data_set, node, node_state, operations, mon_ops, only_node, only_rsc); } PCMK__OUTPUT_LIST_FOOTER(out, rc); return rc; } /*! * \internal * \brief Print all tickets. * * \param[in] data_set Cluster state to display. */ static int print_cluster_tickets(pe_working_set_t * data_set, gboolean print_spacer) { pcmk__output_t *out = data_set->priv; GHashTableIter iter; gpointer key, value; if (g_hash_table_size(data_set->tickets) == 0) { return pcmk_rc_no_output; } PCMK__OUTPUT_SPACER_IF(out, print_spacer); /* Print section heading */ out->begin_list(out, NULL, NULL, "Tickets"); /* Print each ticket */ g_hash_table_iter_init(&iter, data_set->tickets); while (g_hash_table_iter_next(&iter, &key, &value)) { pe_ticket_t *ticket = (pe_ticket_t *) value; out->message(out, "ticket", ticket); } /* Close section */ out->end_list(out); return pcmk_rc_ok; } /*! * \internal * \brief Print section for negative location constraints * * \param[in] data_set Cluster state to display. * \param[in] mon_ops Bitmask of mon_op_*. * \param[in] prefix ID prefix to filter results by. */ static int print_neg_locations(pe_working_set_t *data_set, unsigned int mon_ops, const char *prefix, GList *only_rsc, gboolean print_spacer) { pcmk__output_t *out = data_set->priv; GList *gIter, *gIter2; int rc = pcmk_rc_no_output; /* Print each ban */ for (gIter = data_set->placement_constraints; gIter != NULL; gIter = gIter->next) { pe__location_t *location = gIter->data; if (prefix != NULL && !g_str_has_prefix(location->id, prefix)) continue; if (!pcmk__str_in_list(only_rsc, rsc_printable_id(location->rsc_lh)) && !pcmk__str_in_list(only_rsc, rsc_printable_id(uber_parent(location->rsc_lh)))) { continue; } for (gIter2 = location->node_list_rh; gIter2 != NULL; gIter2 = gIter2->next) { pe_node_t *node = (pe_node_t *) gIter2->data; if (node->weight < 0) { PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Negative Location Constraints"); out->message(out, "ban", node, location, pcmk_is_set(mon_ops, mon_op_print_clone_detail)); } } } PCMK__OUTPUT_LIST_FOOTER(out, rc); return rc; } #define CHECK_RC(retcode, retval) \ if (retval == pcmk_rc_ok) { \ retcode = pcmk_rc_ok; \ } /*! * \internal * \brief Top-level printing function for text/curses output. * * \param[in] data_set Cluster state to display. * \param[in] history_rc Result of getting stonith history * \param[in] stonith_history List of stonith actions. * \param[in] mon_ops Bitmask of mon_op_*. * \param[in] show Bitmask of mon_show_*. * \param[in] prefix ID prefix to filter results by. */ void print_status(pe_working_set_t *data_set, crm_exit_t history_rc, stonith_history_t *stonith_history, unsigned int mon_ops, unsigned int show, char *prefix, char *only_node, char *only_rsc) { pcmk__output_t *out = data_set->priv; GList *unames = NULL; GList *resources = NULL; unsigned int print_opts = get_resource_display_options(mon_ops); int rc = pcmk_rc_no_output; bool already_printed_failure = false; CHECK_RC(rc, out->message(out, "cluster-summary", data_set, pcmk_is_set(mon_ops, mon_op_print_clone_detail), pcmk_is_set(show, mon_show_stack), pcmk_is_set(show, mon_show_dc), pcmk_is_set(show, mon_show_times), pcmk_is_set(show, mon_show_counts), pcmk_is_set(show, mon_show_options))); unames = build_uname_list(data_set, only_node); resources = build_rsc_list(data_set, only_rsc); if (pcmk_is_set(show, mon_show_nodes) && unames) { PCMK__OUTPUT_SPACER_IF(out, rc == pcmk_rc_ok); CHECK_RC(rc, out->message(out, "node-list", data_set->nodes, unames, resources, print_opts, pcmk_is_set(mon_ops, mon_op_print_clone_detail), pcmk_is_set(mon_ops, mon_op_print_brief), pcmk_is_set(mon_ops, mon_op_group_by_node))); } /* Print resources section, if needed */ if (pcmk_is_set(show, mon_show_resources)) { CHECK_RC(rc, out->message(out, "resource-list", data_set, print_opts, pcmk_is_set(mon_ops, mon_op_group_by_node), pcmk_is_set(mon_ops, mon_op_inactive_resources), pcmk_is_set(mon_ops, mon_op_print_brief), TRUE, unames, resources, rc == pcmk_rc_ok)); } /* print Node Attributes section if requested */ if (pcmk_is_set(show, mon_show_attributes)) { CHECK_RC(rc, out->message(out, "node-attribute-list", data_set, get_resource_display_options(mon_ops), rc == pcmk_rc_ok, pcmk_is_set(mon_ops, mon_op_print_clone_detail), pcmk_is_set(mon_ops, mon_op_print_brief), pcmk_is_set(mon_ops, mon_op_group_by_node), unames, resources)); } /* If requested, print resource operations (which includes failcounts) * or just failcounts */ if (pcmk_is_set(show, mon_show_operations) || pcmk_is_set(show, mon_show_failcounts)) { CHECK_RC(rc, print_node_summary(data_set, pcmk_is_set(show, mon_show_operations), mon_ops, unames, resources, (rc == pcmk_rc_ok))); } /* If there were any failed actions, print them */ if (pcmk_is_set(show, mon_show_failures) && xml_has_children(data_set->failed)) { CHECK_RC(rc, out->message(out, "failed-action-list", data_set, unames, resources, rc == pcmk_rc_ok)); } /* Print failed stonith actions */ if (pcmk_is_set(show, mon_show_fence_failed) && pcmk_is_set(mon_ops, mon_op_fence_history)) { if (history_rc == 0) { stonith_history_t *hp = stonith__first_matching_event(stonith_history, stonith__event_state_eq, GINT_TO_POINTER(st_failed)); if (hp) { CHECK_RC(rc, out->message(out, "failed-fencing-list", stonith_history, unames, pcmk_is_set(mon_ops, mon_op_fence_full_history), rc == pcmk_rc_ok)); } } else { PCMK__OUTPUT_SPACER_IF(out, rc == pcmk_rc_ok); out->begin_list(out, NULL, NULL, "Failed Fencing Actions"); out->list_item(out, NULL, "Failed to get fencing history: %s", crm_exit_str(history_rc)); out->end_list(out); already_printed_failure = true; } } /* Print tickets if requested */ if (pcmk_is_set(show, mon_show_tickets)) { CHECK_RC(rc, print_cluster_tickets(data_set, rc == pcmk_rc_ok)); } /* Print negative location constraints if requested */ if (pcmk_is_set(show, mon_show_bans)) { CHECK_RC(rc, print_neg_locations(data_set, mon_ops, prefix, resources, rc == pcmk_rc_ok)); } /* Print stonith history */ if (pcmk_is_set(mon_ops, mon_op_fence_history)) { if (history_rc != 0) { if (!already_printed_failure) { PCMK__OUTPUT_SPACER_IF(out, rc == pcmk_rc_ok); out->begin_list(out, NULL, NULL, "Failed Fencing Actions"); out->list_item(out, NULL, "Failed to get fencing history: %s", crm_exit_str(history_rc)); out->end_list(out); } } else if (pcmk_is_set(show, mon_show_fence_worked)) { stonith_history_t *hp = stonith__first_matching_event(stonith_history, stonith__event_state_neq, GINT_TO_POINTER(st_failed)); if (hp) { CHECK_RC(rc, out->message(out, "fencing-list", hp, unames, pcmk_is_set(mon_ops, mon_op_fence_full_history), rc == pcmk_rc_ok)); } } else if (pcmk_is_set(show, mon_show_fence_pending)) { stonith_history_t *hp = stonith__first_matching_event(stonith_history, stonith__event_state_pending, NULL); if (hp) { CHECK_RC(rc, out->message(out, "pending-fencing-list", hp, unames, pcmk_is_set(mon_ops, mon_op_fence_full_history), rc == pcmk_rc_ok)); } } } g_list_free_full(unames, free); g_list_free_full(resources, free); } /*! * \internal * \brief Top-level printing function for XML output. * * \param[in] data_set Cluster state to display. * \param[in] history_rc Result of getting stonith history * \param[in] stonith_history List of stonith actions. * \param[in] mon_ops Bitmask of mon_op_*. * \param[in] show Bitmask of mon_show_*. * \param[in] prefix ID prefix to filter results by. */ void print_xml_status(pe_working_set_t *data_set, crm_exit_t history_rc, stonith_history_t *stonith_history, unsigned int mon_ops, unsigned int show, char *prefix, char *only_node, char *only_rsc) { pcmk__output_t *out = data_set->priv; GList *unames = NULL; GList *resources = NULL; unsigned int print_opts = get_resource_display_options(mon_ops); out->message(out, "cluster-summary", data_set, pcmk_is_set(mon_ops, mon_op_print_clone_detail), pcmk_is_set(show, mon_show_stack), pcmk_is_set(show, mon_show_dc), pcmk_is_set(show, mon_show_times), pcmk_is_set(show, mon_show_counts), pcmk_is_set(show, mon_show_options)); unames = build_uname_list(data_set, only_node); resources = build_rsc_list(data_set, only_rsc); /*** NODES ***/ if (pcmk_is_set(show, mon_show_nodes)) { out->message(out, "node-list", data_set->nodes, unames, resources, print_opts, pcmk_is_set(mon_ops, mon_op_print_clone_detail), pcmk_is_set(mon_ops, mon_op_print_brief), pcmk_is_set(mon_ops, mon_op_group_by_node)); } /* Print resources section, if needed */ if (pcmk_is_set(show, mon_show_resources)) { out->message(out, "resource-list", data_set, print_opts, pcmk_is_set(mon_ops, mon_op_group_by_node), pcmk_is_set(mon_ops, mon_op_inactive_resources), FALSE, FALSE, unames, resources, FALSE); } /* print Node Attributes section if requested */ if (pcmk_is_set(show, mon_show_attributes)) { out->message(out, "node-attribute-list", data_set, get_resource_display_options(mon_ops), FALSE, pcmk_is_set(mon_ops, mon_op_print_clone_detail), pcmk_is_set(mon_ops, mon_op_print_brief), pcmk_is_set(mon_ops, mon_op_group_by_node), unames, resources); } /* If requested, print resource operations (which includes failcounts) * or just failcounts */ if (pcmk_is_set(show, mon_show_operations) || pcmk_is_set(show, mon_show_failcounts)) { print_node_summary(data_set, pcmk_is_set(show, mon_show_operations), mon_ops, unames, resources, FALSE); } /* If there were any failed actions, print them */ if (pcmk_is_set(show, mon_show_failures) && xml_has_children(data_set->failed)) { out->message(out, "failed-action-list", data_set, unames, resources, FALSE); } /* Print stonith history */ if (pcmk_is_set(show, mon_show_fencing_all) && pcmk_is_set(mon_ops, mon_op_fence_history)) { out->message(out, "full-fencing-list", history_rc, stonith_history, unames, pcmk_is_set(mon_ops, mon_op_fence_full_history), FALSE); } /* Print tickets if requested */ if (pcmk_is_set(show, mon_show_tickets)) { print_cluster_tickets(data_set, FALSE); } /* Print negative location constraints if requested */ if (pcmk_is_set(show, mon_show_bans)) { print_neg_locations(data_set, mon_ops, prefix, resources, FALSE); } g_list_free_full(unames, free); g_list_free_full(resources, free); } /*! * \internal * \brief Top-level printing function for HTML output. * * \param[in] data_set Cluster state to display. * \param[in] history_rc Result of getting stonith history * \param[in] stonith_history List of stonith actions. * \param[in] mon_ops Bitmask of mon_op_*. * \param[in] show Bitmask of mon_show_*. * \param[in] prefix ID prefix to filter results by. */ int print_html_status(pe_working_set_t *data_set, crm_exit_t history_rc, stonith_history_t *stonith_history, unsigned int mon_ops, unsigned int show, char *prefix, char *only_node, char *only_rsc) { pcmk__output_t *out = data_set->priv; GList *unames = NULL; GList *resources = NULL; unsigned int print_opts = get_resource_display_options(mon_ops); bool already_printed_failure = false; out->message(out, "cluster-summary", data_set, pcmk_is_set(mon_ops, mon_op_print_clone_detail), pcmk_is_set(show, mon_show_stack), pcmk_is_set(show, mon_show_dc), pcmk_is_set(show, mon_show_times), pcmk_is_set(show, mon_show_counts), pcmk_is_set(show, mon_show_options)); unames = build_uname_list(data_set, only_node); resources = build_rsc_list(data_set, only_rsc); /*** NODE LIST ***/ if (pcmk_is_set(show, mon_show_nodes) && unames) { out->message(out, "node-list", data_set->nodes, unames, resources, print_opts, pcmk_is_set(mon_ops, mon_op_print_clone_detail), pcmk_is_set(mon_ops, mon_op_print_brief), pcmk_is_set(mon_ops, mon_op_group_by_node)); } /* Print resources section, if needed */ if (pcmk_is_set(show, mon_show_resources)) { out->message(out, "resource-list", data_set, print_opts, pcmk_is_set(mon_ops, mon_op_group_by_node), pcmk_is_set(mon_ops, mon_op_inactive_resources), pcmk_is_set(mon_ops, mon_op_print_brief), TRUE, unames, resources, FALSE); } /* print Node Attributes section if requested */ if (pcmk_is_set(show, mon_show_attributes)) { out->message(out, "node-attribute-list", data_set, get_resource_display_options(mon_ops), FALSE, pcmk_is_set(mon_ops, mon_op_print_clone_detail), pcmk_is_set(mon_ops, mon_op_print_brief), pcmk_is_set(mon_ops, mon_op_group_by_node), unames, resources); } /* If requested, print resource operations (which includes failcounts) * or just failcounts */ if (pcmk_is_set(show, mon_show_operations) || pcmk_is_set(show, mon_show_failcounts)) { print_node_summary(data_set, pcmk_is_set(show, mon_show_operations), mon_ops, unames, resources, FALSE); } /* If there were any failed actions, print them */ if (pcmk_is_set(show, mon_show_failures) && xml_has_children(data_set->failed)) { out->message(out, "failed-action-list", data_set, unames, resources, FALSE); } /* Print failed stonith actions */ if (pcmk_is_set(show, mon_show_fence_failed) && pcmk_is_set(mon_ops, mon_op_fence_history)) { if (history_rc == 0) { stonith_history_t *hp = stonith__first_matching_event(stonith_history, stonith__event_state_eq, GINT_TO_POINTER(st_failed)); if (hp) { out->message(out, "failed-fencing-list", stonith_history, unames, pcmk_is_set(mon_ops, mon_op_fence_full_history), FALSE); } } else { out->begin_list(out, NULL, NULL, "Failed Fencing Actions"); out->list_item(out, NULL, "Failed to get fencing history: %s", crm_exit_str(history_rc)); out->end_list(out); } } /* Print stonith history */ if (pcmk_is_set(mon_ops, mon_op_fence_history)) { if (history_rc != 0) { if (!already_printed_failure) { out->begin_list(out, NULL, NULL, "Failed Fencing Actions"); out->list_item(out, NULL, "Failed to get fencing history: %s", crm_exit_str(history_rc)); out->end_list(out); } } else if (pcmk_is_set(show, mon_show_fence_worked)) { stonith_history_t *hp = stonith__first_matching_event(stonith_history, stonith__event_state_neq, GINT_TO_POINTER(st_failed)); if (hp) { out->message(out, "fencing-list", hp, unames, pcmk_is_set(mon_ops, mon_op_fence_full_history), FALSE); } } else if (pcmk_is_set(show, mon_show_fence_pending)) { stonith_history_t *hp = stonith__first_matching_event(stonith_history, stonith__event_state_pending, NULL); if (hp) { out->message(out, "pending-fencing-list", hp, unames, pcmk_is_set(mon_ops, mon_op_fence_full_history), FALSE); } } } /* Print tickets if requested */ if (pcmk_is_set(show, mon_show_tickets)) { print_cluster_tickets(data_set, FALSE); } /* Print negative location constraints if requested */ if (pcmk_is_set(show, mon_show_bans)) { print_neg_locations(data_set, mon_ops, prefix, resources, FALSE); } g_list_free_full(unames, free); g_list_free_full(resources, free); return 0; } diff --git a/tools/crm_resource.c b/tools/crm_resource.c index e0b480ba0f..2a27a809ee 100644 --- a/tools/crm_resource.c +++ b/tools/crm_resource.c @@ -1,2105 +1,2106 @@ /* * Copyright 2004-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ +#include + #include #include #include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SUMMARY "crm_resource - perform tasks related to Pacemaker cluster resources" enum rsc_command { cmd_none = 0, // No command option given (yet) cmd_ban, cmd_cleanup, cmd_clear, cmd_colocations, cmd_colocations_deep, cmd_cts, cmd_delete, cmd_delete_param, cmd_digests, cmd_execute_agent, cmd_fail, cmd_get_param, cmd_get_property, cmd_list_active_ops, cmd_list_agents, cmd_list_all_ops, cmd_list_alternatives, cmd_list_instances, cmd_list_providers, cmd_list_resources, cmd_list_standards, cmd_locate, cmd_metadata, cmd_move, cmd_query_raw_xml, cmd_query_xml, cmd_refresh, cmd_restart, cmd_set_param, cmd_set_property, cmd_wait, cmd_why, }; struct { enum rsc_command rsc_cmd; // crm_resource command to perform // Infrastructure that given command needs to work gboolean require_cib; // Whether command requires CIB IPC int cib_options; // Options to use with CIB IPC calls gboolean require_crmd; // Whether command requires controller IPC gboolean require_dataset; // Whether command requires populated data set gboolean require_resource; // Whether command requires resource specified gboolean require_node; // Whether command requires node specified int find_flags; // Flags to use when searching for resource // Command-line option values gchar *rsc_id; // Value of --resource gchar *rsc_type; // Value of --resource-type gboolean force; // --force was given gboolean clear_expired; // --expired was given gboolean recursive; // --recursive was given gboolean promoted_role_only; // --promoted was given gchar *host_uname; // Value of --node gchar *interval_spec; // Value of --interval gchar *move_lifetime; // Value of --lifetime gchar *operation; // Value of --operation const char *attr_set_type; // Instance, meta, or utilization attribute gchar *prop_id; // --nvpair (attribute XML ID) char *prop_name; // Attribute name gchar *prop_set; // --set-name (attribute block XML ID) gchar *prop_value; // --parameter-value (attribute value) int timeout_ms; // Parsed from --timeout value char *agent_spec; // Standard and/or provider and/or agent gchar *xml_file; // Value of (deprecated) --xml-file // Resource configuration specified via command-line arguments gboolean cmdline_config; // Resource configuration was via arguments char *v_agent; // Value of --agent char *v_class; // Value of --class char *v_provider; // Value of --provider GHashTable *cmdline_params; // Resource parameters specified // Positional command-line arguments gchar **remainder; // Positional arguments as given GHashTable *override_params; // Resource parameter values that override config } options = { .attr_set_type = XML_TAG_ATTR_SETS, .cib_options = cib_sync_call, .require_cib = TRUE, .require_dataset = TRUE, .require_resource = TRUE, }; #if 0 // @COMPAT @TODO enable this at next backward compatibility break #define SET_COMMAND(cmd) do { \ if (options.rsc_cmd != cmd_none) { \ g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_USAGE, \ "Only one command option may be specified"); \ return FALSE; \ } \ options.rsc_cmd = (cmd); \ } while (0) #else #define SET_COMMAND(cmd) do { options.rsc_cmd = (cmd); } while (0) #endif gboolean agent_provider_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean attr_set_type_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean class_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean cleanup_refresh_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean delete_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean expired_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean list_agents_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean list_providers_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean list_standards_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean list_alternatives_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean metadata_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean option_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean flag_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean get_param_prop_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean list_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean set_delete_param_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean set_prop_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean timeout_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean validate_or_force_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean restart_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean digests_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean wait_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean why_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); static crm_exit_t exit_code = CRM_EX_OK; static pcmk__output_t *out = NULL; // Things that should be cleaned up on exit static GError *error = NULL; static GMainLoop *mainloop = NULL; static cib_t *cib_conn = NULL; static pcmk_ipc_api_t *controld_api = NULL; static pe_working_set_t *data_set = NULL; #define MESSAGE_TIMEOUT_S 60 #define INDENT " " static pcmk__supported_format_t formats[] = { PCMK__SUPPORTED_FORMAT_NONE, PCMK__SUPPORTED_FORMAT_TEXT, PCMK__SUPPORTED_FORMAT_XML, { NULL, NULL, NULL } }; // Clean up and exit static crm_exit_t bye(crm_exit_t ec) { pcmk__output_and_clear_error(error, out); if (out != NULL) { out->finish(out, ec, true, NULL); pcmk__output_free(out); } if (cib_conn != NULL) { cib_t *save_cib_conn = cib_conn; cib_conn = NULL; // Ensure we can't free this twice save_cib_conn->cmds->signoff(save_cib_conn); cib_delete(save_cib_conn); } if (controld_api != NULL) { pcmk_ipc_api_t *save_controld_api = controld_api; controld_api = NULL; // Ensure we can't free this twice pcmk_free_ipc_api(save_controld_api); } if (mainloop != NULL) { g_main_loop_unref(mainloop); mainloop = NULL; } pe_free_working_set(data_set); data_set = NULL; crm_exit(ec); return ec; } static void quit_main_loop(crm_exit_t ec) { exit_code = ec; if (mainloop != NULL) { GMainLoop *mloop = mainloop; mainloop = NULL; // Don't re-enter this block pcmk_quit_main_loop(mloop, 10); g_main_loop_unref(mloop); } } static gboolean resource_ipc_timeout(gpointer data) { // Start with newline because "Waiting for ..." message doesn't have one if (error != NULL) { g_clear_error(&error); } g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_TIMEOUT, "Aborting because no messages received in %d seconds", MESSAGE_TIMEOUT_S); quit_main_loop(CRM_EX_TIMEOUT); return FALSE; } static void controller_event_callback(pcmk_ipc_api_t *api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { switch (event_type) { case pcmk_ipc_event_disconnect: if (exit_code == CRM_EX_DISCONNECT) { // Unexpected crm_info("Connection to controller was terminated"); } quit_main_loop(exit_code); break; case pcmk_ipc_event_reply: if (status != CRM_EX_OK) { out->err(out, "Error: bad reply from controller: %s", crm_exit_str(status)); pcmk_disconnect_ipc(api); quit_main_loop(status); } else { if ((pcmk_controld_api_replies_expected(api) == 0) && mainloop && g_main_loop_is_running(mainloop)) { out->info(out, "... got reply (done)"); crm_debug("Got all the replies we expected"); pcmk_disconnect_ipc(api); quit_main_loop(CRM_EX_OK); } else { out->info(out, "... got reply"); } } break; default: break; } } static void start_mainloop(pcmk_ipc_api_t *capi) { unsigned int count = pcmk_controld_api_replies_expected(capi); if (count > 0) { out->info(out, "Waiting for %d %s from the controller", count, pcmk__plural_alt(count, "reply", "replies")); exit_code = CRM_EX_DISCONNECT; // For unexpected disconnects mainloop = g_main_loop_new(NULL, FALSE); g_timeout_add(MESSAGE_TIMEOUT_S * 1000, resource_ipc_timeout, NULL); g_main_loop_run(mainloop); } } static int compare_id(gconstpointer a, gconstpointer b) { return strcmp((const char *)a, (const char *)b); } static GList * build_constraint_list(xmlNode *root) { GList *retval = NULL; xmlNode *cib_constraints = NULL; xmlXPathObjectPtr xpathObj = NULL; int ndx = 0; cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, root); xpathObj = xpath_search(cib_constraints, "//" XML_CONS_TAG_RSC_LOCATION); for (ndx = 0; ndx < numXpathResults(xpathObj); ndx++) { xmlNode *match = getXpathResult(xpathObj, ndx); retval = g_list_insert_sorted(retval, (gpointer) ID(match), compare_id); } freeXpathObject(xpathObj); return retval; } /* short option letters still available: eEJkKXyYZ */ static GOptionEntry query_entries[] = { { "list", 'L', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_cb, "List all cluster resources with status", NULL }, { "list-raw", 'l', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_cb, "List IDs of all instantiated resources (individual members\n" INDENT "rather than groups etc.)", NULL }, { "list-cts", 'c', G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_cb, NULL, NULL }, { "list-operations", 'O', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_cb, "List active resource operations, optionally filtered by\n" INDENT "--resource and/or --node", NULL }, { "list-all-operations", 'o', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_cb, "List all resource operations, optionally filtered by\n" INDENT "--resource and/or --node", NULL }, { "list-standards", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_standards_cb, "List supported standards", NULL }, { "list-ocf-providers", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_providers_cb, "List all available OCF providers", NULL }, { "list-agents", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, list_agents_cb, "List all agents available for the named standard and/or provider", "STD/PROV" }, { "list-ocf-alternatives", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, list_alternatives_cb, "List all available providers for the named OCF agent", "AGENT" }, { "show-metadata", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, metadata_cb, "Show the metadata for the named class:provider:agent", "SPEC" }, { "query-xml", 'q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb, "Show XML configuration of resource (after any template expansion)", NULL }, { "query-xml-raw", 'w', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb, "Show XML configuration of resource (before any template expansion)", NULL }, { "get-parameter", 'g', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, get_param_prop_cb, "Display named parameter for resource (use instance attribute\n" INDENT "unless --meta or --utilization is specified)", "PARAM" }, { "get-property", 'G', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, get_param_prop_cb, "Display named property of resource ('class', 'type', or 'provider') " "(requires --resource)", "PROPERTY" }, { "locate", 'W', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb, "Show node(s) currently running resource", NULL }, { "stack", 'A', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb, "Display the (co)location constraints that apply to a resource\n" INDENT "and the resources is it colocated with", NULL }, { "constraints", 'a', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb, "Display the (co)location constraints that apply to a resource", NULL }, { "why", 'Y', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, why_cb, "Show why resources are not running, optionally filtered by\n" INDENT "--resource and/or --node", NULL }, { NULL } }; static GOptionEntry command_entries[] = { { "validate", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, validate_or_force_cb, "Validate resource configuration by calling agent's validate-all\n" INDENT "action. The configuration may be specified either by giving an\n" INDENT "existing resource name with -r, or by specifying --class,\n" INDENT "--agent, and --provider arguments, along with any number of\n" INDENT "--option arguments.", NULL }, { "cleanup", 'C', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, cleanup_refresh_cb, "If resource has any past failures, clear its history and fail\n" INDENT "count. Optionally filtered by --resource, --node, --operation\n" INDENT "and --interval (otherwise all). --operation and --interval\n" INDENT "apply to fail counts, but entire history is always clear, to\n" INDENT "allow current state to be rechecked. If the named resource is\n" INDENT "part of a group, or one numbered instance of a clone or bundled\n" INDENT "resource, the clean-up applies to the whole collective resource\n" INDENT "unless --force is given.", NULL }, { "refresh", 'R', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, cleanup_refresh_cb, "Delete resource's history (including failures) so its current state\n" INDENT "is rechecked. Optionally filtered by --resource and --node\n" INDENT "(otherwise all). If the named resource is part of a group, or one\n" INDENT "numbered instance of a clone or bundled resource, the refresh\n" INDENT "applies to the whole collective resource unless --force is given.", NULL }, { "set-parameter", 'p', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, set_delete_param_cb, "Set named parameter for resource (requires -v). Use instance\n" INDENT "attribute unless --meta or --utilization is specified.", "PARAM" }, { "delete-parameter", 'd', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, set_delete_param_cb, "Delete named parameter for resource. Use instance attribute\n" INDENT "unless --meta or --utilization is specified.", "PARAM" }, { "set-property", 'S', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, set_prop_cb, "Set named property of resource ('class', 'type', or 'provider') " "(requires -r, -t, -v)", "PROPERTY" }, { NULL } }; static GOptionEntry location_entries[] = { { "move", 'M', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb, "Create a constraint to move resource. If --node is specified,\n" INDENT "the constraint will be to move to that node, otherwise it\n" INDENT "will be to ban the current node. Unless --force is specified\n" INDENT "this will return an error if the resource is already running\n" INDENT "on the specified node. If --force is specified, this will\n" INDENT "always ban the current node.\n" INDENT "Optional: --lifetime, --promoted. NOTE: This may prevent the\n" INDENT "resource from running on its previous location until the\n" INDENT "implicit constraint expires or is removed with --clear.", NULL }, { "ban", 'B', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb, "Create a constraint to keep resource off a node.\n" INDENT "Optional: --node, --lifetime, --promoted.\n" INDENT "NOTE: This will prevent the resource from running on the\n" INDENT "affected node until the implicit constraint expires or is\n" INDENT "removed with --clear. If --node is not specified, it defaults\n" INDENT "to the node currently running the resource for primitives\n" INDENT "and groups, or the promoted instance of promotable clones with\n" INDENT "promoted-max=1 (all other situations result in an error as\n" INDENT "there is no sane default).", NULL }, { "clear", 'U', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb, "Remove all constraints created by the --ban and/or --move\n" INDENT "commands. Requires: --resource. Optional: --node, --promoted,\n" INDENT "--expired. If --node is not specified, all constraints created\n" INDENT "by --ban and --move will be removed for the named resource. If\n" INDENT "--node and --force are specified, any constraint created by\n" INDENT "--move will be cleared, even if it is not for the specified\n" INDENT "node. If --expired is specified, only those constraints whose\n" INDENT "lifetimes have expired will be removed.", NULL }, { "expired", 'e', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, expired_cb, "Modifies the --clear argument to remove constraints with\n" INDENT "expired lifetimes.", NULL }, { "lifetime", 'u', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.move_lifetime, "Lifespan (as ISO 8601 duration) of created constraints (with\n" INDENT "-B, -M) see https://en.wikipedia.org/wiki/ISO_8601#Durations)", "TIMESPEC" }, { "promoted", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.promoted_role_only, "Limit scope of command to promoted role (with -B, -M, -U). For\n" INDENT "-B and -M, previously promoted instances may remain\n" INDENT "active in the unpromoted role.", NULL }, // Deprecated since 2.1.0 { "master", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.promoted_role_only, "Deprecated: Use --promoted instead", NULL }, { NULL } }; static GOptionEntry advanced_entries[] = { { "delete", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, delete_cb, "(Advanced) Delete a resource from the CIB. Required: -t", NULL }, { "fail", 'F', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, fail_cb, "(Advanced) Tell the cluster this resource has failed", NULL }, { "restart", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, restart_cb, "(Advanced) Tell the cluster to restart this resource and\n" INDENT "anything that depends on it", NULL }, { "wait", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, wait_cb, "(Advanced) Wait until the cluster settles into a stable state", NULL }, { "digests", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, digests_cb, "(Advanced) Show parameter hashes that Pacemaker uses to detect\n" INDENT "configuration changes (only accurate if there is resource\n" INDENT "history on the specified node). Required: --resource, --node.\n" INDENT "Optional: any NAME=VALUE parameters will be used to override\n" INDENT "the configuration (to see what the hash would be with those\n" INDENT "changes).", NULL }, { "force-demote", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, validate_or_force_cb, "(Advanced) Bypass the cluster and demote a resource on the local\n" INDENT "node. Unless --force is specified, this will refuse to do so if\n" INDENT "the cluster believes the resource is a clone instance already\n" INDENT "running on the local node.", NULL }, { "force-stop", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, validate_or_force_cb, "(Advanced) Bypass the cluster and stop a resource on the local node", NULL }, { "force-start", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, validate_or_force_cb, "(Advanced) Bypass the cluster and start a resource on the local\n" INDENT "node. Unless --force is specified, this will refuse to do so if\n" INDENT "the cluster believes the resource is a clone instance already\n" INDENT "running on the local node.", NULL }, { "force-promote", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, validate_or_force_cb, "(Advanced) Bypass the cluster and promote a resource on the local\n" INDENT "node. Unless --force is specified, this will refuse to do so if\n" INDENT "the cluster believes the resource is a clone instance already\n" INDENT "running on the local node.", NULL }, { "force-check", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, validate_or_force_cb, "(Advanced) Bypass the cluster and check the state of a resource on\n" INDENT "the local node", NULL }, { NULL } }; static GOptionEntry addl_entries[] = { { "node", 'N', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.host_uname, "Node name", "NAME" }, { "recursive", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.recursive, "Follow colocation chains when using --set-parameter", NULL }, { "resource-type", 't', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.rsc_type, "Resource XML element (primitive, group, etc.) (with -D)", "ELEMENT" }, { "parameter-value", 'v', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.prop_value, "Value to use with -p", "PARAM" }, { "meta", 'm', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, attr_set_type_cb, "Use resource meta-attribute instead of instance attribute\n" INDENT "(with -p, -g, -d)", NULL }, { "utilization", 'z', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, attr_set_type_cb, "Use resource utilization attribute instead of instance attribute\n" INDENT "(with -p, -g, -d)", NULL }, { "operation", 'n', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.operation, "Operation to clear instead of all (with -C -r)", "OPERATION" }, { "interval", 'I', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.interval_spec, "Interval of operation to clear (default 0) (with -C -r -n)", "N" }, { "class", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, class_cb, "The standard the resource agent conforms to (for example, ocf).\n" INDENT "Use with --agent, --provider, --option, and --validate.", "CLASS" }, { "agent", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, agent_provider_cb, "The agent to use (for example, IPaddr). Use with --class,\n" INDENT "--provider, --option, and --validate.", "AGENT" }, { "provider", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, agent_provider_cb, "The vendor that supplies the resource agent (for example,\n" INDENT "heartbeat). Use with --class, --agent, --option, and --validate.", "PROVIDER" }, { "option", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, option_cb, "Specify a device configuration parameter as NAME=VALUE (may be\n" INDENT "specified multiple times). Use with --validate and without the\n" INDENT "-r option.", "PARAM" }, { "set-name", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.prop_set, "(Advanced) XML ID of attributes element to use (with -p, -d)", "ID" }, { "nvpair", 'i', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.prop_id, "(Advanced) XML ID of nvpair element to use (with -p, -d)", "ID" }, { "timeout", 'T', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, timeout_cb, "(Advanced) Abort if command does not finish in this time (with\n" INDENT "--restart, --wait, --force-*)", "N" }, { "force", 'f', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.force, "If making CIB changes, do so regardless of quorum. See help for\n" INDENT "individual commands for additional behavior.", NULL }, { "xml-file", 'x', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_FILENAME, &options.xml_file, NULL, "FILE" }, { "host-uname", 'H', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.host_uname, NULL, "HOST" }, { NULL } }; gboolean agent_provider_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.cmdline_config = TRUE; options.require_resource = FALSE; if (pcmk__str_eq(option_name, "--provider", pcmk__str_casei)) { if (options.v_provider) { free(options.v_provider); } options.v_provider = strdup(optarg); } else { if (options.v_agent) { free(options.v_agent); } options.v_agent = strdup(optarg); } return TRUE; } gboolean attr_set_type_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (pcmk__str_any_of(option_name, "-m", "--meta", NULL)) { options.attr_set_type = XML_TAG_META_SETS; } else if (pcmk__str_any_of(option_name, "-z", "--utilization", NULL)) { options.attr_set_type = XML_TAG_UTILIZATION; } return TRUE; } gboolean class_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (!(pcmk_get_ra_caps(optarg) & pcmk_ra_cap_params)) { if (!out->is_quiet(out)) { g_set_error(error, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, "Standard %s does not support parameters\n", optarg); } return FALSE; } else { if (options.v_class != NULL) { free(options.v_class); } options.v_class = strdup(optarg); } options.cmdline_config = TRUE; options.require_resource = FALSE; return TRUE; } gboolean cleanup_refresh_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (pcmk__str_any_of(option_name, "-C", "--cleanup", NULL)) { SET_COMMAND(cmd_cleanup); } else { SET_COMMAND(cmd_refresh); } options.require_resource = FALSE; if (getenv("CIB_file") == NULL) { options.require_crmd = TRUE; } options.find_flags = pe_find_renamed|pe_find_anon; return TRUE; } gboolean delete_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.require_dataset = FALSE; SET_COMMAND(cmd_delete); options.find_flags = pe_find_renamed|pe_find_any; return TRUE; } gboolean expired_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.clear_expired = TRUE; options.require_resource = FALSE; return TRUE; } static void get_agent_spec(const gchar *optarg) { options.require_cib = FALSE; options.require_dataset = FALSE; options.require_resource = FALSE; if (options.agent_spec != NULL) { free(options.agent_spec); options.agent_spec = NULL; } if (optarg != NULL) { options.agent_spec = strdup(optarg); } } gboolean list_agents_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { SET_COMMAND(cmd_list_agents); get_agent_spec(optarg); return TRUE; } gboolean list_providers_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { SET_COMMAND(cmd_list_providers); get_agent_spec(optarg); return TRUE; } gboolean list_standards_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { SET_COMMAND(cmd_list_standards); options.require_cib = FALSE; options.require_dataset = FALSE; options.require_resource = FALSE; return TRUE; } gboolean list_alternatives_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { SET_COMMAND(cmd_list_alternatives); get_agent_spec(optarg); return TRUE; } gboolean metadata_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { SET_COMMAND(cmd_metadata); get_agent_spec(optarg); return TRUE; } gboolean option_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { char *name = NULL; char *value = NULL; if (pcmk__scan_nvpair(optarg, &name, &value) != 2) { return FALSE; } if (options.cmdline_params == NULL) { options.cmdline_params = pcmk__strkey_table(free, free); } g_hash_table_replace(options.cmdline_params, name, value); return TRUE; } gboolean fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.require_crmd = TRUE; options.require_node = TRUE; SET_COMMAND(cmd_fail); return TRUE; } gboolean flag_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (pcmk__str_any_of(option_name, "-U", "--clear", NULL)) { options.find_flags = pe_find_renamed|pe_find_anon; SET_COMMAND(cmd_clear); } else if (pcmk__str_any_of(option_name, "-B", "--ban", NULL)) { options.find_flags = pe_find_renamed|pe_find_anon; SET_COMMAND(cmd_ban); } else if (pcmk__str_any_of(option_name, "-M", "--move", NULL)) { options.find_flags = pe_find_renamed|pe_find_anon; SET_COMMAND(cmd_move); } else if (pcmk__str_any_of(option_name, "-q", "--query-xml", NULL)) { options.find_flags = pe_find_renamed|pe_find_any; SET_COMMAND(cmd_query_xml); } else if (pcmk__str_any_of(option_name, "-w", "--query-xml-raw", NULL)) { options.find_flags = pe_find_renamed|pe_find_any; SET_COMMAND(cmd_query_raw_xml); } else if (pcmk__str_any_of(option_name, "-W", "--locate", NULL)) { options.find_flags = pe_find_renamed|pe_find_anon; SET_COMMAND(cmd_locate); } else if (pcmk__str_any_of(option_name, "-A", "--stack", NULL)) { options.find_flags = pe_find_renamed|pe_find_anon; SET_COMMAND(cmd_colocations_deep); } else { options.find_flags = pe_find_renamed|pe_find_anon; SET_COMMAND(cmd_colocations); } return TRUE; } gboolean get_param_prop_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (pcmk__str_any_of(option_name, "-g", "--get-parameter", NULL)) { SET_COMMAND(cmd_get_param); } else { SET_COMMAND(cmd_get_property); } if (options.prop_name) { free(options.prop_name); } options.prop_name = strdup(optarg); options.find_flags = pe_find_renamed|pe_find_any; return TRUE; } gboolean list_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (pcmk__str_any_of(option_name, "-c", "--list-cts", NULL)) { SET_COMMAND(cmd_cts); } else if (pcmk__str_any_of(option_name, "-L", "--list", NULL)) { SET_COMMAND(cmd_list_resources); } else if (pcmk__str_any_of(option_name, "-l", "--list-raw", NULL)) { SET_COMMAND(cmd_list_instances); } else if (pcmk__str_any_of(option_name, "-O", "--list-operations", NULL)) { SET_COMMAND(cmd_list_active_ops); } else { SET_COMMAND(cmd_list_all_ops); } options.require_resource = FALSE; return TRUE; } gboolean set_delete_param_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (pcmk__str_any_of(option_name, "-p", "--set-parameter", NULL)) { SET_COMMAND(cmd_set_param); } else { SET_COMMAND(cmd_delete_param); } if (options.prop_name) { free(options.prop_name); } options.prop_name = strdup(optarg); options.find_flags = pe_find_renamed|pe_find_any; return TRUE; } gboolean set_prop_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.require_dataset = FALSE; if (options.prop_name) { free(options.prop_name); } options.prop_name = strdup(optarg); SET_COMMAND(cmd_set_property); options.find_flags = pe_find_renamed|pe_find_any; return TRUE; } gboolean timeout_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.timeout_ms = crm_get_msec(optarg); return TRUE; } gboolean validate_or_force_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { SET_COMMAND(cmd_execute_agent); if (options.operation) { g_free(options.operation); } options.operation = g_strdup(option_name + 2); // skip "--" options.find_flags = pe_find_renamed|pe_find_anon; if (options.override_params == NULL) { options.override_params = pcmk__strkey_table(free, free); } return TRUE; } gboolean restart_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { SET_COMMAND(cmd_restart); options.find_flags = pe_find_renamed|pe_find_anon; return TRUE; } gboolean digests_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { SET_COMMAND(cmd_digests); options.find_flags = pe_find_renamed|pe_find_anon; if (options.override_params == NULL) { options.override_params = pcmk__strkey_table(free, free); } options.require_node = TRUE; options.require_dataset = TRUE; return TRUE; } gboolean wait_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { SET_COMMAND(cmd_wait); options.require_resource = FALSE; options.require_dataset = FALSE; return TRUE; } gboolean why_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.require_resource = FALSE; SET_COMMAND(cmd_why); options.find_flags = pe_find_renamed|pe_find_anon; return TRUE; } static int ban_or_move(pcmk__output_t *out, pe_resource_t *rsc, const char *move_lifetime, crm_exit_t *exit_code) { int rc = pcmk_rc_ok; pe_node_t *current = NULL; unsigned int nactive = 0; CRM_CHECK(rsc != NULL, return EINVAL); current = pe__find_active_requires(rsc, &nactive); if (nactive == 1) { rc = cli_resource_ban(out, options.rsc_id, current->details->uname, move_lifetime, NULL, cib_conn, options.cib_options, options.promoted_role_only); } else if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) { int count = 0; GList *iter = NULL; current = NULL; for(iter = rsc->children; iter; iter = iter->next) { pe_resource_t *child = (pe_resource_t *)iter->data; enum rsc_role_e child_role = child->fns->state(child, TRUE); if (child_role == RSC_ROLE_PROMOTED) { count++; current = pe__current_node(child); } } if(count == 1 && current) { rc = cli_resource_ban(out, options.rsc_id, current->details->uname, move_lifetime, NULL, cib_conn, options.cib_options, options.promoted_role_only); } else { rc = EINVAL; *exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, *exit_code, "Resource '%s' not moved: active in %d locations (promoted in %d).\n" "To prevent '%s' from running on a specific location, " "specify a node." "To prevent '%s' from being promoted at a specific " "location, specify a node and the --promoted option.", options.rsc_id, nactive, count, options.rsc_id, options.rsc_id); } } else { rc = EINVAL; *exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, *exit_code, "Resource '%s' not moved: active in %d locations.\n" "To prevent '%s' from running on a specific location, " "specify a node.", options.rsc_id, nactive, options.rsc_id); } return rc; } static void cleanup(pcmk__output_t *out, pe_resource_t *rsc) { int rc = pcmk_rc_ok; if (options.force == FALSE) { rsc = uber_parent(rsc); } crm_debug("Erasing failures of %s (%s requested) on %s", rsc->id, options.rsc_id, (options.host_uname? options.host_uname: "all nodes")); rc = cli_resource_delete(controld_api, options.host_uname, rsc, options.operation, options.interval_spec, TRUE, data_set, options.force); if ((rc == pcmk_rc_ok) && !out->is_quiet(out)) { // Show any reasons why resource might stay stopped cli_resource_check(out, cib_conn, rsc); } if (rc == pcmk_rc_ok) { start_mainloop(controld_api); } } static int clear_constraints(pcmk__output_t *out, xmlNodePtr *cib_xml_copy) { GList *before = NULL; GList *after = NULL; GList *remaining = NULL; GList *ele = NULL; pe_node_t *dest = NULL; int rc = pcmk_rc_ok; if (!out->is_quiet(out)) { before = build_constraint_list(data_set->input); } if (options.clear_expired) { rc = cli_resource_clear_all_expired(data_set->input, cib_conn, options.cib_options, options.rsc_id, options.host_uname, options.promoted_role_only); } else if (options.host_uname) { dest = pe_find_node(data_set->nodes, options.host_uname); if (dest == NULL) { rc = pcmk_rc_node_unknown; if (!out->is_quiet(out)) { g_list_free(before); } return rc; } rc = cli_resource_clear(options.rsc_id, dest->details->uname, NULL, cib_conn, options.cib_options, TRUE, options.force); } else { rc = cli_resource_clear(options.rsc_id, NULL, data_set->nodes, cib_conn, options.cib_options, TRUE, options.force); } if (!out->is_quiet(out)) { rc = cib_conn->cmds->query(cib_conn, NULL, cib_xml_copy, cib_scope_local | cib_sync_call); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, "Could not get modified CIB: %s\n", pcmk_strerror(rc)); g_list_free(before); return rc; } data_set->input = *cib_xml_copy; cluster_status(data_set); after = build_constraint_list(data_set->input); remaining = pcmk__subtract_lists(before, after, (GCompareFunc) strcmp); for (ele = remaining; ele != NULL; ele = ele->next) { out->info(out, "Removing constraint: %s", (char *) ele->data); } g_list_free(before); g_list_free(after); g_list_free(remaining); } return rc; } static int delete(void) { int rc = pcmk_rc_ok; xmlNode *msg_data = NULL; if (options.rsc_type == NULL) { rc = ENXIO; g_set_error(&error, PCMK__RC_ERROR, rc, "You need to specify a resource type with -t"); return rc; } msg_data = create_xml_node(NULL, options.rsc_type); crm_xml_add(msg_data, XML_ATTR_ID, options.rsc_id); rc = cib_conn->cmds->remove(cib_conn, XML_CIB_TAG_RESOURCES, msg_data, options.cib_options); rc = pcmk_legacy2rc(rc); free_xml(msg_data); return rc; } static int list_agents(pcmk__output_t *out, const char *agent_spec, crm_exit_t *exit_code) { int rc = pcmk_rc_ok; char *provider = strchr(agent_spec, ':'); lrmd_t *lrmd_conn = lrmd_api_new(); lrmd_list_t *list = NULL; if (provider) { *provider++ = 0; } rc = lrmd_conn->cmds->list_agents(lrmd_conn, &list, agent_spec, provider); if (rc > 0) { rc = out->message(out, "agents-list", list, agent_spec, provider); } else { rc = pcmk_rc_error; } if (rc != pcmk_rc_ok) { *exit_code = CRM_EX_NOSUCH; if (provider == NULL) { g_set_error(&error, PCMK__EXITC_ERROR, *exit_code, "No agents found for standard '%s'", agent_spec); } else { g_set_error(&error, PCMK__EXITC_ERROR, *exit_code, "No agents found for standard '%s' and provider '%s'", agent_spec, provider); } } lrmd_api_delete(lrmd_conn); return rc; } static int list_providers(pcmk__output_t *out, const char *agent_spec, crm_exit_t *exit_code) { int rc; const char *text = NULL; lrmd_t *lrmd_conn = lrmd_api_new(); lrmd_list_t *list = NULL; switch (options.rsc_cmd) { case cmd_list_alternatives: rc = lrmd_conn->cmds->list_ocf_providers(lrmd_conn, agent_spec, &list); if (rc > 0) { rc = out->message(out, "alternatives-list", list, agent_spec); } else { rc = pcmk_rc_error; } text = "OCF providers"; break; case cmd_list_standards: rc = lrmd_conn->cmds->list_standards(lrmd_conn, &list); if (rc > 0) { rc = out->message(out, "standards-list", list); } else { rc = pcmk_rc_error; } text = "standards"; break; case cmd_list_providers: rc = lrmd_conn->cmds->list_ocf_providers(lrmd_conn, agent_spec, &list); if (rc > 0) { rc = out->message(out, "providers-list", list, agent_spec); } else { rc = pcmk_rc_error; } text = "OCF providers"; break; default: *exit_code = CRM_EX_SOFTWARE; g_set_error(&error, PCMK__EXITC_ERROR, *exit_code, "Bug"); lrmd_api_delete(lrmd_conn); return pcmk_rc_error; } if (rc != pcmk_rc_ok) { if (agent_spec != NULL) { *exit_code = CRM_EX_NOSUCH; rc = pcmk_rc_error; g_set_error(&error, PCMK__EXITC_ERROR, *exit_code, "No %s found for %s", text, agent_spec); } else { *exit_code = CRM_EX_NOSUCH; rc = pcmk_rc_error; g_set_error(&error, PCMK__EXITC_ERROR, *exit_code, "No %s found", text); } } lrmd_api_delete(lrmd_conn); return rc; } static int populate_working_set(xmlNodePtr *cib_xml_copy) { int rc = pcmk_rc_ok; if (options.xml_file != NULL) { *cib_xml_copy = filename2xml(options.xml_file); } else { rc = cib_conn->cmds->query(cib_conn, NULL, cib_xml_copy, cib_scope_local | cib_sync_call); rc = pcmk_legacy2rc(rc); } if(rc != pcmk_rc_ok) { return rc; } /* Populate the working set instance */ data_set = pe_new_working_set(); if (data_set == NULL) { rc = ENOMEM; return rc; } pe__set_working_set_flags(data_set, pe_flag_no_counts|pe_flag_no_compat); data_set->priv = out; rc = update_working_set_xml(data_set, cib_xml_copy); if (rc == pcmk_rc_ok) { cluster_status(data_set); } return rc; } static int refresh(pcmk__output_t *out) { int rc = pcmk_rc_ok; const char *router_node = options.host_uname; int attr_options = pcmk__node_attr_none; if (options.host_uname) { pe_node_t *node = pe_find_node(data_set->nodes, options.host_uname); if (pe__is_guest_or_remote_node(node)) { node = pe__current_node(node->details->remote_rsc); if (node == NULL) { rc = ENXIO; g_set_error(&error, PCMK__RC_ERROR, rc, "No cluster connection to Pacemaker Remote node %s detected", options.host_uname); return rc; } router_node = node->details->uname; attr_options |= pcmk__node_attr_remote; } } if (controld_api == NULL) { out->info(out, "Dry run: skipping clean-up of %s due to CIB_file", options.host_uname? options.host_uname : "all nodes"); rc = pcmk_rc_ok; return rc; } crm_debug("Re-checking the state of all resources on %s", options.host_uname?options.host_uname:"all nodes"); rc = pcmk__node_attr_request_clear(NULL, options.host_uname, NULL, NULL, NULL, NULL, attr_options); if (pcmk_controld_api_reprobe(controld_api, options.host_uname, router_node) == pcmk_rc_ok) { start_mainloop(controld_api); } return rc; } static void refresh_resource(pcmk__output_t *out, pe_resource_t *rsc) { int rc = pcmk_rc_ok; if (options.force == FALSE) { rsc = uber_parent(rsc); } crm_debug("Re-checking the state of %s (%s requested) on %s", rsc->id, options.rsc_id, (options.host_uname? options.host_uname: "all nodes")); rc = cli_resource_delete(controld_api, options.host_uname, rsc, NULL, 0, FALSE, data_set, options.force); if ((rc == pcmk_rc_ok) && !out->is_quiet(out)) { // Show any reasons why resource might stay stopped cli_resource_check(out, cib_conn, rsc); } if (rc == pcmk_rc_ok) { start_mainloop(controld_api); } } static int set_property(void) { int rc = pcmk_rc_ok; xmlNode *msg_data = NULL; if (pcmk__str_empty(options.rsc_type)) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "Must specify -t with resource type"); rc = ENXIO; return rc; } else if (pcmk__str_empty(options.prop_value)) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "Must supply -v with new value"); rc = EINVAL; return rc; } CRM_LOG_ASSERT(options.prop_name != NULL); msg_data = create_xml_node(NULL, options.rsc_type); crm_xml_add(msg_data, XML_ATTR_ID, options.rsc_id); crm_xml_add(msg_data, options.prop_name, options.prop_value); rc = cib_conn->cmds->modify(cib_conn, XML_CIB_TAG_RESOURCES, msg_data, options.cib_options); rc = pcmk_legacy2rc(rc); free_xml(msg_data); return rc; } static int show_metadata(pcmk__output_t *out, const char *agent_spec, crm_exit_t *exit_code) { int rc = pcmk_rc_ok; char *standard = NULL; char *provider = NULL; char *type = NULL; char *metadata = NULL; lrmd_t *lrmd_conn = lrmd_api_new(); rc = crm_parse_agent_spec(agent_spec, &standard, &provider, &type); rc = pcmk_legacy2rc(rc); if (rc == pcmk_rc_ok) { rc = lrmd_conn->cmds->get_metadata(lrmd_conn, standard, provider, type, &metadata, 0); rc = pcmk_legacy2rc(rc); if (metadata) { out->output_xml(out, "metadata", metadata); } else { *exit_code = crm_errno2exit(rc); g_set_error(&error, PCMK__EXITC_ERROR, *exit_code, "Metadata query for %s failed: %s", agent_spec, pcmk_rc_str(rc)); } } else { rc = ENXIO; g_set_error(&error, PCMK__RC_ERROR, rc, "'%s' is not a valid agent specification", agent_spec); } lrmd_api_delete(lrmd_conn); return rc; } static void validate_cmdline_config(void) { // Cannot use both --resource and command-line resource configuration if (options.rsc_id != NULL) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "--resource cannot be used with --class, --agent, and --provider"); // Not all commands support command-line resource configuration } else if (options.rsc_cmd != cmd_execute_agent) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "--class, --agent, and --provider can only be used with " "--validate"); // Not all of --class, --agent, and --provider need to be given. Not all // classes support the concept of a provider. Check that what we were given // is valid. } else if (pcmk__str_eq(options.v_class, "stonith", pcmk__str_none)) { if (options.v_provider != NULL) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "stonith does not support providers"); } else if (stonith_agent_exists(options.v_agent, 0) == FALSE) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "%s is not a known stonith agent", options.v_agent ? options.v_agent : ""); } } else if (resources_agent_exists(options.v_class, options.v_provider, options.v_agent) == FALSE) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "%s:%s:%s is not a known resource", options.v_class ? options.v_class : "", options.v_provider ? options.v_provider : "", options.v_agent ? options.v_agent : ""); } if (error != NULL) { return; } if (options.cmdline_params == NULL) { options.cmdline_params = pcmk__strkey_table(free, free); } options.require_resource = FALSE; options.require_dataset = FALSE; options.require_cib = FALSE; } static GOptionContext * build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) { GOptionContext *context = NULL; GOptionEntry extra_prog_entries[] = { { "quiet", 'Q', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &(args->quiet), "Be less descriptive in output.", NULL }, { "resource", 'r', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.rsc_id, "Resource ID", "ID" }, { G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING_ARRAY, &options.remainder, NULL, NULL }, { NULL } }; const char *description = "Examples:\n\n" "List the available OCF agents:\n\n" "\t# crm_resource --list-agents ocf\n\n" "List the available OCF agents from the linux-ha project:\n\n" "\t# crm_resource --list-agents ocf:heartbeat\n\n" "Move 'myResource' to a specific node:\n\n" "\t# crm_resource --resource myResource --move --node altNode\n\n" "Allow (but not force) 'myResource' to move back to its original " "location:\n\n" "\t# crm_resource --resource myResource --clear\n\n" "Stop 'myResource' (and anything that depends on it):\n\n" "\t# crm_resource --resource myResource --set-parameter target-role " "--meta --parameter-value Stopped\n\n" "Tell the cluster not to manage 'myResource' (the cluster will not " "attempt to start or stop the\n" "resource under any circumstances; useful when performing maintenance " "tasks on a resource):\n\n" "\t# crm_resource --resource myResource --set-parameter is-managed " "--meta --parameter-value false\n\n" "Erase the operation history of 'myResource' on 'aNode' (the cluster " "will 'forget' the existing\n" "resource state, including any errors, and attempt to recover the" "resource; useful when a resource\n" "had failed permanently and has been repaired by an administrator):\n\n" "\t# crm_resource --resource myResource --cleanup --node aNode\n\n"; context = pcmk__build_arg_context(args, "text (default), xml", group, NULL); g_option_context_set_description(context, description); /* Add the -Q option, which cannot be part of the globally supported options * because some tools use that flag for something else. */ pcmk__add_main_args(context, extra_prog_entries); pcmk__add_arg_group(context, "queries", "Queries:", "Show query help", query_entries); pcmk__add_arg_group(context, "commands", "Commands:", "Show command help", command_entries); pcmk__add_arg_group(context, "locations", "Locations:", "Show location help", location_entries); pcmk__add_arg_group(context, "advanced", "Advanced:", "Show advanced option help", advanced_entries); pcmk__add_arg_group(context, "additional", "Additional Options:", "Show additional options", addl_entries); return context; } int main(int argc, char **argv) { xmlNode *cib_xml_copy = NULL; pe_resource_t *rsc = NULL; pe_node_t *node = NULL; int rc = pcmk_rc_ok; /* * Parse command line arguments */ GOptionGroup *output_group = NULL; pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); gchar **processed_args = pcmk__cmdline_preproc(argv, "GINSTdginpstuv"); GOptionContext *context = build_arg_context(args, &output_group); pcmk__register_formats(output_group, formats); if (!g_option_context_parse_strv(context, &processed_args, &error)) { exit_code = CRM_EX_USAGE; goto done; } pcmk__cli_init_logging("crm_resource", args->verbosity); rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv); if (rc != pcmk_rc_ok) { exit_code = CRM_EX_ERROR; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Error creating output format %s: %s", args->output_ty, pcmk_rc_str(rc)); goto done; } pe__register_messages(out); crm_resource_register_messages(out); lrmd__register_messages(out); pcmk__register_lib_messages(out); out->quiet = args->quiet; crm_log_args(argc, argv); /* * Validate option combinations */ // If the user didn't explicitly specify a command, list resources if (options.rsc_cmd == cmd_none) { options.rsc_cmd = cmd_list_resources; options.require_resource = FALSE; } // --expired without --clear/-U doesn't make sense if (options.clear_expired && (options.rsc_cmd != cmd_clear)) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "--expired requires --clear or -U"); goto done; } if ((options.remainder != NULL) && (options.override_params != NULL)) { // Commands that use positional arguments will create override_params for (gchar **s = options.remainder; *s; s++) { char *name = calloc(1, strlen(*s)); char *value = calloc(1, strlen(*s)); int rc = sscanf(*s, "%[^=]=%s", name, value); if (rc == 2) { g_hash_table_replace(options.override_params, name, value); } else { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "Error parsing '%s' as a name=value pair", argv[optind]); free(value); free(name); goto done; } } } else if (options.remainder != NULL) { gchar **strv = NULL; gchar *msg = NULL; int i = 1; int len = 0; for (gchar **s = options.remainder; *s; s++) { len++; } CRM_ASSERT(len > 0); strv = calloc(len, sizeof(char *)); strv[0] = strdup("non-option ARGV-elements:"); for (gchar **s = options.remainder; *s; s++) { strv[i] = crm_strdup_printf("[%d of %d] %s\n", i, len, *s); i++; } msg = g_strjoinv("", strv); g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "%s", msg); g_free(msg); for(i = 0; i < len; i++) { free(strv[i]); } free(strv); goto done; } if (pcmk__str_eq(args->output_ty, "xml", pcmk__str_none)) { /* Kind of a hack to display XML lists using a real tag instead of . This just * saves from having to write custom messages to build the lists around all these things */ switch (options.rsc_cmd) { case cmd_list_resources: case cmd_query_xml: case cmd_query_raw_xml: case cmd_list_active_ops: case cmd_list_all_ops: case cmd_colocations: case cmd_colocations_deep: pcmk__force_args(context, &error, "%s --xml-simple-list --xml-substitute", g_get_prgname()); break; default: pcmk__force_args(context, &error, "%s --xml-substitute", g_get_prgname()); break; } } else if (pcmk__str_eq(args->output_ty, "text", pcmk__str_null_matches)) { if (options.rsc_cmd == cmd_colocations || options.rsc_cmd == cmd_colocations_deep || options.rsc_cmd == cmd_list_resources) { pcmk__force_args(context, &error, "%s --text-fancy", g_get_prgname()); } } if (args->version) { out->version(out, false); goto done; } if (options.cmdline_config) { /* A resource configuration was given on the command line. Sanity-check * the values and set error if they don't make sense. */ validate_cmdline_config(); if (error != NULL) { exit_code = CRM_EX_USAGE; goto done; } } else if (options.cmdline_params != NULL) { // @COMPAT @TODO error out here when we can break backward compatibility g_hash_table_destroy(options.cmdline_params); options.cmdline_params = NULL; } if (options.require_resource && (options.rsc_id == NULL)) { rc = ENXIO; exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Must supply a resource id with -r"); goto done; } if (options.require_node && (options.host_uname == NULL)) { rc = ENXIO; exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Must supply a node name with -N"); goto done; } /* * Set up necessary connections */ if (options.force) { crm_debug("Forcing..."); cib__set_call_options(options.cib_options, crm_system_name, cib_quorum_override); } if (options.find_flags && options.rsc_id) { options.require_dataset = TRUE; } // Establish a connection to the CIB if needed if (options.require_cib) { cib_conn = cib_new(); if ((cib_conn == NULL) || (cib_conn->cmds == NULL)) { rc = pcmk_rc_error; g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_DISCONNECT, "Could not create CIB connection"); goto done; } rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, "Could not connect to the CIB: %s", pcmk_rc_str(rc)); goto done; } } /* Populate working set from XML file if specified or CIB query otherwise */ if (options.require_dataset) { rc = populate_working_set(&cib_xml_copy); if (rc != pcmk_rc_ok) { goto done; } } // If command requires that resource exist if specified, find it if (options.find_flags && options.rsc_id) { rsc = pe_find_resource_with_flags(data_set->resources, options.rsc_id, options.find_flags); if (rsc == NULL) { rc = ENXIO; g_set_error(&error, PCMK__RC_ERROR, rc, "Resource '%s' not found", options.rsc_id); goto done; } } // If user supplied a node name, check whether it exists if ((options.host_uname != NULL) && (data_set != NULL)) { node = pe_find_node(data_set->nodes, options.host_uname); } // Establish a connection to the controller if needed if (options.require_crmd) { rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, "Error connecting to the controller: %s", pcmk_rc_str(rc)); goto done; } pcmk_register_ipc_callback(controld_api, controller_event_callback, NULL); rc = pcmk_connect_ipc(controld_api, pcmk_ipc_dispatch_main); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, "Error connecting to the controller: %s", pcmk_rc_str(rc)); goto done; } } /* * Handle requested command */ switch (options.rsc_cmd) { case cmd_list_resources: { GList *all = NULL; all = g_list_prepend(all, strdup("*")); rc = out->message(out, "resource-list", data_set, pe_print_rsconly | pe_print_pending, FALSE, TRUE, FALSE, TRUE, all, all, FALSE); g_list_free_full(all, free); if (rc == pcmk_rc_no_output) { rc = ENXIO; } break; } case cmd_list_instances: rc = out->message(out, "resource-names-list", data_set->resources); if (rc != pcmk_rc_ok) { rc = ENXIO; } break; case cmd_list_standards: case cmd_list_providers: case cmd_list_alternatives: rc = list_providers(out, options.agent_spec, &exit_code); break; case cmd_list_agents: rc = list_agents(out, options.agent_spec, &exit_code); break; case cmd_metadata: rc = show_metadata(out, options.agent_spec, &exit_code); break; case cmd_restart: /* We don't pass data_set because rsc needs to stay valid for the * entire lifetime of cli_resource_restart(), but it will reset and * update the working set multiple times, so it needs to use its own * copy. */ rc = cli_resource_restart(out, rsc, options.host_uname, options.move_lifetime, options.timeout_ms, cib_conn, options.cib_options, options.promoted_role_only, options.force); break; case cmd_wait: rc = wait_till_stable(out, options.timeout_ms, cib_conn); break; case cmd_execute_agent: if (options.cmdline_config) { exit_code = cli_resource_execute_from_params(out, "test", options.v_class, options.v_provider, options.v_agent, "validate-all", options.cmdline_params, options.override_params, options.timeout_ms, args->verbosity, options.force); } else { exit_code = cli_resource_execute(rsc, options.rsc_id, options.operation, options.override_params, options.timeout_ms, cib_conn, data_set, args->verbosity, options.force); } break; case cmd_digests: node = pe_find_node(data_set->nodes, options.host_uname); if (node == NULL) { rc = pcmk_rc_node_unknown; } else { rc = pcmk__resource_digests(out, rsc, node, options.override_params, data_set); } break; case cmd_colocations: rc = out->message(out, "stacks-constraints", rsc, data_set, false); break; case cmd_colocations_deep: rc = out->message(out, "stacks-constraints", rsc, data_set, true); break; case cmd_cts: rc = pcmk_rc_ok; for (GList *lpc = data_set->resources; lpc != NULL; lpc = lpc->next) { rsc = (pe_resource_t *) lpc->data; cli_resource_print_cts(out, rsc); } cli_resource_print_cts_constraints(data_set); break; case cmd_fail: rc = cli_resource_fail(controld_api, options.host_uname, options.rsc_id, data_set); if (rc == pcmk_rc_ok) { start_mainloop(controld_api); } break; case cmd_list_active_ops: rc = cli_resource_print_operations(options.rsc_id, options.host_uname, TRUE, data_set); break; case cmd_list_all_ops: rc = cli_resource_print_operations(options.rsc_id, options.host_uname, FALSE, data_set); break; case cmd_locate: { GList *nodes = cli_resource_search(rsc, options.rsc_id, data_set); rc = out->message(out, "resource-search-list", nodes, options.rsc_id); g_list_free_full(nodes, free); break; } case cmd_query_xml: rc = cli_resource_print(rsc, data_set, TRUE); break; case cmd_query_raw_xml: rc = cli_resource_print(rsc, data_set, FALSE); break; case cmd_why: if ((options.host_uname != NULL) && (node == NULL)) { rc = pcmk_rc_node_unknown; } else { rc = out->message(out, "resource-reasons-list", cib_conn, data_set->resources, rsc, node); } break; case cmd_clear: rc = clear_constraints(out, &cib_xml_copy); break; case cmd_move: if (options.host_uname == NULL) { rc = ban_or_move(out, rsc, options.move_lifetime, &exit_code); } else { rc = cli_resource_move(rsc, options.rsc_id, options.host_uname, options.move_lifetime, cib_conn, options.cib_options, data_set, options.promoted_role_only, options.force); } break; case cmd_ban: if (options.host_uname == NULL) { rc = ban_or_move(out, rsc, options.move_lifetime, &exit_code); } else if (node == NULL) { rc = pcmk_rc_node_unknown; } else { rc = cli_resource_ban(out, options.rsc_id, node->details->uname, options.move_lifetime, NULL, cib_conn, options.cib_options, options.promoted_role_only); } break; case cmd_get_property: rc = out->message(out, "property-list", rsc, options.prop_name); if (rc == pcmk_rc_no_output) { rc = ENXIO; } break; case cmd_set_property: rc = set_property(); break; case cmd_get_param: { unsigned int count = 0; GHashTable *params = NULL; pe_node_t *current = pe__find_active_on(rsc, &count, NULL); bool free_params = true; if (count > 1) { out->err(out, "%s is active on more than one node," " returning the default value for %s", rsc->id, crm_str(options.prop_name)); current = NULL; } crm_debug("Looking up %s in %s", options.prop_name, rsc->id); if (pcmk__str_eq(options.attr_set_type, XML_TAG_ATTR_SETS, pcmk__str_casei)) { params = pe_rsc_params(rsc, current, data_set); free_params = false; } else if (pcmk__str_eq(options.attr_set_type, XML_TAG_META_SETS, pcmk__str_casei)) { params = pcmk__strkey_table(free, free); get_meta_attributes(params, rsc, current, data_set); } else { params = pcmk__strkey_table(free, free); pe__unpack_dataset_nvpairs(rsc->xml, XML_TAG_UTILIZATION, NULL, params, NULL, FALSE, data_set); } rc = out->message(out, "attribute-list", rsc, options.prop_name, params); if (free_params) { g_hash_table_destroy(params); } break; } case cmd_set_param: if (pcmk__str_empty(options.prop_value)) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "You need to supply a value with the -v option"); rc = EINVAL; goto done; } /* coverity[var_deref_model] False positive */ rc = cli_resource_update_attribute(rsc, options.rsc_id, options.prop_set, options.attr_set_type, options.prop_id, options.prop_name, options.prop_value, options.recursive, cib_conn, options.cib_options, data_set, options.force); break; case cmd_delete_param: /* coverity[var_deref_model] False positive */ rc = cli_resource_delete_attribute(rsc, options.rsc_id, options.prop_set, options.attr_set_type, options.prop_id, options.prop_name, cib_conn, options.cib_options, data_set, options.force); break; case cmd_cleanup: if (rsc == NULL) { rc = cli_cleanup_all(controld_api, options.host_uname, options.operation, options.interval_spec, data_set); if (rc == pcmk_rc_ok) { start_mainloop(controld_api); } } else { cleanup(out, rsc); } break; case cmd_refresh: if (rsc == NULL) { rc = refresh(out); } else { refresh_resource(out, rsc); } break; case cmd_delete: rc = delete(); break; default: g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_SOFTWARE, "Unimplemented command: %d", (int) options.rsc_cmd); break; } /* * Clean up and exit */ done: /* Don't do any of this for pcmk_rc_no_output (doesn't make sense to show an * error message for no output) or for CRM_EX_USAGE (we don't want to show * an "error: OK" message from pcmk_rc_str). */ if ((rc != pcmk_rc_ok && rc != pcmk_rc_no_output) || (exit_code != CRM_EX_OK && exit_code != CRM_EX_USAGE)) { if (rc == pcmk_rc_no_quorum) { g_prefix_error(&error, "To ignore quorum, use the force option.\n"); } if (error != NULL) { char *msg = crm_strdup_printf("%s\nError performing operation: %s", error->message, pcmk_rc_str(rc)); g_clear_error(&error); g_set_error(&error, PCMK__RC_ERROR, rc, "%s", msg); free(msg); } else { g_set_error(&error, PCMK__RC_ERROR, rc, "Error performing operation: %s", pcmk_rc_str(rc)); } } if (exit_code == CRM_EX_OK) { exit_code = pcmk_rc2exitc(rc); } g_free(options.host_uname); g_free(options.interval_spec); g_free(options.move_lifetime); g_free(options.operation); g_free(options.prop_id); free(options.prop_name); g_free(options.prop_set); g_free(options.prop_value); g_free(options.rsc_id); g_free(options.rsc_type); free(options.agent_spec); free(options.v_agent); free(options.v_class); free(options.v_provider); g_free(options.xml_file); g_strfreev(options.remainder); if (options.override_params != NULL) { g_hash_table_destroy(options.override_params); } /* options.cmdline_params does not need to be destroyed here. See the * comments in cli_resource_execute_from_params. */ g_strfreev(processed_args); g_option_context_free(context); return bye(exit_code); } diff --git a/tools/crm_resource_ban.c b/tools/crm_resource_ban.c index 0aca8e9757..a297d49cbe 100644 --- a/tools/crm_resource_ban.c +++ b/tools/crm_resource_ban.c @@ -1,479 +1,481 @@ /* * Copyright 2004-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ +#include + #include #define XPATH_MAX 1024 static char * parse_cli_lifetime(pcmk__output_t *out, const char *move_lifetime) { char *later_s = NULL; crm_time_t *now = NULL; crm_time_t *later = NULL; crm_time_t *duration = NULL; if (move_lifetime == NULL) { return NULL; } duration = crm_time_parse_duration(move_lifetime); if (duration == NULL) { out->err(out, "Invalid duration specified: %s\n" "Please refer to https://en.wikipedia.org/wiki/ISO_8601#Durations " "for examples of valid durations", move_lifetime); return NULL; } now = crm_time_new(NULL); later = crm_time_add(now, duration); if (later == NULL) { out->err(out, "Unable to add %s to current time\n" "Please report to " PACKAGE_BUGREPORT " as possible bug", move_lifetime); crm_time_free(now); crm_time_free(duration); return NULL; } crm_time_log(LOG_INFO, "now ", now, crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone); crm_time_log(LOG_INFO, "later ", later, crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone); crm_time_log(LOG_INFO, "duration", duration, crm_time_log_date | crm_time_log_timeofday); later_s = crm_time_as_string(later, crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone); out->info(out, "Migration will take effect until: %s", later_s); crm_time_free(duration); crm_time_free(later); crm_time_free(now); return later_s; } static const char * promoted_role_name(void) { /* This is a judgment call for what string to use. @TODO Ideally we'd * use the legacy string if the DC only supports that, and the new one * otherwise. Basing it on --enable-compat-2.0 is a decent guess. */ #ifdef PCMK__COMPAT_2_0 return RSC_ROLE_PROMOTED_LEGACY_S; #else return RSC_ROLE_PROMOTED_S; #endif } // \return Standard Pacemaker return code int cli_resource_ban(pcmk__output_t *out, const char *rsc_id, const char *host, const char *move_lifetime, GList *allnodes, cib_t * cib_conn, int cib_options, gboolean promoted_role_only) { char *later_s = NULL; int rc = pcmk_rc_ok; xmlNode *fragment = NULL; xmlNode *location = NULL; if(host == NULL) { GList *n = allnodes; for(; n && rc == pcmk_rc_ok; n = n->next) { pe_node_t *target = n->data; rc = cli_resource_ban(out, rsc_id, target->details->uname, move_lifetime, NULL, cib_conn, cib_options, promoted_role_only); } return rc; } later_s = parse_cli_lifetime(out, move_lifetime); if(move_lifetime && later_s == NULL) { return EINVAL; } fragment = create_xml_node(NULL, XML_CIB_TAG_CONSTRAINTS); location = create_xml_node(fragment, XML_CONS_TAG_RSC_LOCATION); crm_xml_set_id(location, "cli-ban-%s-on-%s", rsc_id, host); out->info(out, "WARNING: Creating rsc_location constraint '%s' with a " "score of -INFINITY for resource %s on %s.\n\tThis will " "prevent %s from %s on %s until the constraint is removed " "using the clear option or by editing the CIB with an " "appropriate tool\n\tThis will be the case even if %s " "is the last node in the cluster", ID(location), rsc_id, host, rsc_id, (promoted_role_only? "being promoted" : "running"), host, host); crm_xml_add(location, XML_LOC_ATTR_SOURCE, rsc_id); if(promoted_role_only) { crm_xml_add(location, XML_RULE_ATTR_ROLE, promoted_role_name()); } else { crm_xml_add(location, XML_RULE_ATTR_ROLE, RSC_ROLE_STARTED_S); } if (later_s == NULL) { /* Short form */ crm_xml_add(location, XML_CIB_TAG_NODE, host); crm_xml_add(location, XML_RULE_ATTR_SCORE, CRM_MINUS_INFINITY_S); } else { xmlNode *rule = create_xml_node(location, XML_TAG_RULE); xmlNode *expr = create_xml_node(rule, XML_TAG_EXPRESSION); crm_xml_set_id(rule, "cli-ban-%s-on-%s-rule", rsc_id, host); crm_xml_add(rule, XML_RULE_ATTR_SCORE, CRM_MINUS_INFINITY_S); crm_xml_add(rule, XML_RULE_ATTR_BOOLEAN_OP, "and"); crm_xml_set_id(expr, "cli-ban-%s-on-%s-expr", rsc_id, host); crm_xml_add(expr, XML_EXPR_ATTR_ATTRIBUTE, CRM_ATTR_UNAME); crm_xml_add(expr, XML_EXPR_ATTR_OPERATION, "eq"); crm_xml_add(expr, XML_EXPR_ATTR_VALUE, host); crm_xml_add(expr, XML_EXPR_ATTR_TYPE, "string"); expr = create_xml_node(rule, "date_expression"); crm_xml_set_id(expr, "cli-ban-%s-on-%s-lifetime", rsc_id, host); crm_xml_add(expr, "operation", "lt"); crm_xml_add(expr, "end", later_s); } crm_log_xml_notice(fragment, "Modify"); rc = cib_conn->cmds->update(cib_conn, XML_CIB_TAG_CONSTRAINTS, fragment, cib_options); rc = pcmk_legacy2rc(rc); free_xml(fragment); free(later_s); return rc; } // \return Standard Pacemaker return code int cli_resource_prefer(pcmk__output_t *out,const char *rsc_id, const char *host, const char *move_lifetime, cib_t * cib_conn, int cib_options, gboolean promoted_role_only) { char *later_s = parse_cli_lifetime(out, move_lifetime); int rc = pcmk_rc_ok; xmlNode *location = NULL; xmlNode *fragment = NULL; if(move_lifetime && later_s == NULL) { return EINVAL; } if(cib_conn == NULL) { free(later_s); return ENOTCONN; } fragment = create_xml_node(NULL, XML_CIB_TAG_CONSTRAINTS); location = create_xml_node(fragment, XML_CONS_TAG_RSC_LOCATION); crm_xml_set_id(location, "cli-prefer-%s", rsc_id); crm_xml_add(location, XML_LOC_ATTR_SOURCE, rsc_id); if(promoted_role_only) { crm_xml_add(location, XML_RULE_ATTR_ROLE, promoted_role_name()); } else { crm_xml_add(location, XML_RULE_ATTR_ROLE, RSC_ROLE_STARTED_S); } if (later_s == NULL) { /* Short form */ crm_xml_add(location, XML_CIB_TAG_NODE, host); crm_xml_add(location, XML_RULE_ATTR_SCORE, CRM_INFINITY_S); } else { xmlNode *rule = create_xml_node(location, XML_TAG_RULE); xmlNode *expr = create_xml_node(rule, XML_TAG_EXPRESSION); crm_xml_set_id(rule, "cli-prefer-rule-%s", rsc_id); crm_xml_add(rule, XML_RULE_ATTR_SCORE, CRM_INFINITY_S); crm_xml_add(rule, XML_RULE_ATTR_BOOLEAN_OP, "and"); crm_xml_set_id(expr, "cli-prefer-expr-%s", rsc_id); crm_xml_add(expr, XML_EXPR_ATTR_ATTRIBUTE, CRM_ATTR_UNAME); crm_xml_add(expr, XML_EXPR_ATTR_OPERATION, "eq"); crm_xml_add(expr, XML_EXPR_ATTR_VALUE, host); crm_xml_add(expr, XML_EXPR_ATTR_TYPE, "string"); expr = create_xml_node(rule, "date_expression"); crm_xml_set_id(expr, "cli-prefer-lifetime-end-%s", rsc_id); crm_xml_add(expr, "operation", "lt"); crm_xml_add(expr, "end", later_s); } crm_log_xml_info(fragment, "Modify"); rc = cib_conn->cmds->update(cib_conn, XML_CIB_TAG_CONSTRAINTS, fragment, cib_options); rc = pcmk_legacy2rc(rc); free_xml(fragment); free(later_s); return rc; } /* Nodes can be specified two different ways in the CIB, so we have two different * functions to try clearing out any constraints on them: * * (1) The node could be given by attribute=/value= in an expression XML node. * That's what resource_clear_node_in_expr handles. That XML looks like this: * * * * * * * * * (2) The mode could be given by node= in an rsc_location XML node. That's * what resource_clear_node_in_location handles. That XML looks like this: * * * * \return Standard Pacemaker return code */ static int resource_clear_node_in_expr(const char *rsc_id, const char *host, cib_t * cib_conn, int cib_options) { int rc = pcmk_rc_ok; char *xpath_string = NULL; xpath_string = crm_strdup_printf("//rsc_location[@id='cli-prefer-%s'][rule[@id='cli-prefer-rule-%s']/expression[@attribute='#uname' and @value='%s']]", rsc_id, rsc_id, host); rc = cib_conn->cmds->remove(cib_conn, xpath_string, NULL, cib_xpath | cib_options); if (rc == -ENXIO) { rc = pcmk_rc_ok; } else { rc = pcmk_legacy2rc(rc); } free(xpath_string); return rc; } // \return Standard Pacemaker return code static int resource_clear_node_in_location(const char *rsc_id, const char *host, cib_t * cib_conn, int cib_options, bool clear_ban_constraints, gboolean force) { int rc = pcmk_rc_ok; xmlNode *fragment = NULL; xmlNode *location = NULL; fragment = create_xml_node(NULL, XML_CIB_TAG_CONSTRAINTS); if (clear_ban_constraints == TRUE) { location = create_xml_node(fragment, XML_CONS_TAG_RSC_LOCATION); crm_xml_set_id(location, "cli-ban-%s-on-%s", rsc_id, host); } location = create_xml_node(fragment, XML_CONS_TAG_RSC_LOCATION); crm_xml_set_id(location, "cli-prefer-%s", rsc_id); if (force == FALSE) { crm_xml_add(location, XML_CIB_TAG_NODE, host); } crm_log_xml_info(fragment, "Delete"); rc = cib_conn->cmds->remove(cib_conn, XML_CIB_TAG_CONSTRAINTS, fragment, cib_options); if (rc == -ENXIO) { rc = pcmk_rc_ok; } else { rc = pcmk_legacy2rc(rc); } free(fragment); return rc; } // \return Standard Pacemaker return code int cli_resource_clear(const char *rsc_id, const char *host, GList *allnodes, cib_t * cib_conn, int cib_options, bool clear_ban_constraints, gboolean force) { int rc = pcmk_rc_ok; if(cib_conn == NULL) { return ENOTCONN; } if (host) { rc = resource_clear_node_in_expr(rsc_id, host, cib_conn, cib_options); /* rc does not tell us whether the previous operation did anything, only * whether it failed or not. Thus, as long as it did not fail, we need * to try the second clear method. */ if (rc == pcmk_rc_ok) { rc = resource_clear_node_in_location(rsc_id, host, cib_conn, cib_options, clear_ban_constraints, force); } } else { GList *n = allnodes; /* Iterate over all nodes, attempting to clear the constraint from each. * On the first error, abort. */ for(; n; n = n->next) { pe_node_t *target = n->data; rc = cli_resource_clear(rsc_id, target->details->uname, NULL, cib_conn, cib_options, clear_ban_constraints, force); if (rc != pcmk_rc_ok) { break; } } } return rc; } static char * build_clear_xpath_string(xmlNode *constraint_node, const char *rsc, const char *node, gboolean promoted_role_only) { int offset = 0; char *xpath_string = NULL; char *first_half = NULL; char *rsc_role_substr = NULL; char *date_substr = NULL; if (pcmk__starts_with(ID(constraint_node), "cli-ban-")) { date_substr = crm_strdup_printf("//date_expression[@id='%s-lifetime']", ID(constraint_node)); } else if (pcmk__starts_with(ID(constraint_node), "cli-prefer-")) { date_substr = crm_strdup_printf("//date_expression[@id='cli-prefer-lifetime-end-%s']", crm_element_value(constraint_node, "rsc")); } else { return NULL; } first_half = calloc(1, XPATH_MAX); offset += snprintf(first_half + offset, XPATH_MAX - offset, "//rsc_location"); if (node != NULL || rsc != NULL || promoted_role_only == TRUE) { offset += snprintf(first_half + offset, XPATH_MAX - offset, "["); if (node != NULL) { if (rsc != NULL || promoted_role_only == TRUE) { offset += snprintf(first_half + offset, XPATH_MAX - offset, "@node='%s' and ", node); } else { offset += snprintf(first_half + offset, XPATH_MAX - offset, "@node='%s'", node); } } if (rsc != NULL && promoted_role_only == TRUE) { rsc_role_substr = crm_strdup_printf("@rsc='%s' and @role='%s'", rsc, promoted_role_name()); offset += snprintf(first_half + offset, XPATH_MAX - offset, "@rsc='%s' and @role='%s']", rsc, promoted_role_name()); } else if (rsc != NULL) { rsc_role_substr = crm_strdup_printf("@rsc='%s'", rsc); offset += snprintf(first_half + offset, XPATH_MAX - offset, "@rsc='%s']", rsc); } else if (promoted_role_only == TRUE) { rsc_role_substr = crm_strdup_printf("@role='%s'", promoted_role_name()); offset += snprintf(first_half + offset, XPATH_MAX - offset, "@role='%s']", promoted_role_name()); } else { offset += snprintf(first_half + offset, XPATH_MAX - offset, "]"); } } if (node != NULL) { if (rsc_role_substr != NULL) { xpath_string = crm_strdup_printf("%s|//rsc_location[%s]/rule[expression[@attribute='#uname' and @value='%s']]%s", first_half, rsc_role_substr, node, date_substr); } else { xpath_string = crm_strdup_printf("%s|//rsc_location/rule[expression[@attribute='#uname' and @value='%s']]%s", first_half, node, date_substr); } } else { xpath_string = crm_strdup_printf("%s%s", first_half, date_substr); } free(first_half); free(date_substr); free(rsc_role_substr); return xpath_string; } // \return Standard Pacemaker return code int cli_resource_clear_all_expired(xmlNode *root, cib_t *cib_conn, int cib_options, const char *rsc, const char *node, gboolean promoted_role_only) { xmlXPathObject *xpathObj = NULL; xmlNode *cib_constraints = NULL; crm_time_t *now = crm_time_new(NULL); int i; int rc = pcmk_rc_ok; cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, root); xpathObj = xpath_search(cib_constraints, "//" XML_CONS_TAG_RSC_LOCATION); for (i = 0; i < numXpathResults(xpathObj); i++) { xmlNode *constraint_node = getXpathResult(xpathObj, i); xmlNode *date_expr_node = NULL; crm_time_t *end = NULL; char *xpath_string = NULL; xpath_string = build_clear_xpath_string(constraint_node, rsc, node, promoted_role_only); if (xpath_string == NULL) { continue; } date_expr_node = get_xpath_object(xpath_string, constraint_node, LOG_DEBUG); if (date_expr_node == NULL) { free(xpath_string); continue; } /* And then finally, see if the date expression is expired. If so, * clear the constraint. */ end = crm_time_new(crm_element_value(date_expr_node, "end")); if (crm_time_compare(now, end) == 1) { xmlNode *fragment = NULL; xmlNode *location = NULL; fragment = create_xml_node(NULL, XML_CIB_TAG_CONSTRAINTS); location = create_xml_node(fragment, XML_CONS_TAG_RSC_LOCATION); crm_xml_set_id(location, "%s", ID(constraint_node)); crm_log_xml_info(fragment, "Delete"); rc = cib_conn->cmds->remove(cib_conn, XML_CIB_TAG_CONSTRAINTS, fragment, cib_options); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { free(xpath_string); goto done; } free_xml(fragment); } crm_time_free(end); free(xpath_string); } done: freeXpathObject(xpathObj); crm_time_free(now); return rc; } diff --git a/tools/crm_resource_print.c b/tools/crm_resource_print.c index d8a39b8e36..923195f9b1 100644 --- a/tools/crm_resource_print.c +++ b/tools/crm_resource_print.c @@ -1,582 +1,582 @@ /* * Copyright 2004-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ +#include + #include #include -#include -#include #define cons_string(x) x?x:"NA" void cli_resource_print_cts_constraints(pe_working_set_t * data_set) { pcmk__output_t *out = data_set->priv; xmlNode *xml_obj = NULL; xmlNode *lifetime = NULL; xmlNode *cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, data_set->input); for (xml_obj = pcmk__xe_first_child(cib_constraints); xml_obj != NULL; xml_obj = pcmk__xe_next(xml_obj)) { const char *id = crm_element_value(xml_obj, XML_ATTR_ID); if (id == NULL) { continue; } // @COMPAT lifetime is deprecated lifetime = first_named_child(xml_obj, "lifetime"); if (pe_evaluate_rules(lifetime, NULL, data_set->now, NULL) == FALSE) { continue; } if (!pcmk__str_eq(XML_CONS_TAG_RSC_DEPEND, crm_element_name(xml_obj), pcmk__str_casei)) { continue; } out->info(out, "Constraint %s %s %s %s %s %s %s", crm_element_name(xml_obj), cons_string(crm_element_value(xml_obj, XML_ATTR_ID)), cons_string(crm_element_value(xml_obj, XML_COLOC_ATTR_SOURCE)), cons_string(crm_element_value(xml_obj, XML_COLOC_ATTR_TARGET)), cons_string(crm_element_value(xml_obj, XML_RULE_ATTR_SCORE)), cons_string(crm_element_value(xml_obj, XML_COLOC_ATTR_SOURCE_ROLE)), cons_string(crm_element_value(xml_obj, XML_COLOC_ATTR_TARGET_ROLE))); } } void cli_resource_print_cts(pcmk__output_t *out, pe_resource_t * rsc) { GList *lpc = NULL; const char *host = NULL; bool needs_quorum = TRUE; const char *rtype = crm_element_value(rsc->xml, XML_ATTR_TYPE); const char *rprov = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER); const char *rclass = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); pe_node_t *node = pe__current_node(rsc); if (pcmk__str_eq(rclass, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) { needs_quorum = FALSE; } else { // @TODO check requires in resource meta-data and rsc_defaults } if (node != NULL) { host = node->details->uname; } out->info(out, "Resource: %s %s %s %s %s %s %s %s %d %lld 0x%.16llx", crm_element_name(rsc->xml), rsc->id, rsc->clone_name ? rsc->clone_name : rsc->id, rsc->parent ? rsc->parent->id : "NA", rprov ? rprov : "NA", rclass, rtype, host ? host : "NA", needs_quorum, rsc->flags, rsc->flags); for (lpc = rsc->children; lpc != NULL; lpc = lpc->next) { pe_resource_t *child = (pe_resource_t *) lpc->data; cli_resource_print_cts(out, child); } } // \return Standard Pacemaker return code int cli_resource_print_operations(const char *rsc_id, const char *host_uname, bool active, pe_working_set_t * data_set) { pcmk__output_t *out = data_set->priv; int rc = pcmk_rc_no_output; GList *ops = find_operations(rsc_id, host_uname, active, data_set); if (!ops) { return rc; } out->begin_list(out, NULL, NULL, "Resource Operations"); rc = pcmk_rc_ok; for (GList *lpc = ops; lpc != NULL; lpc = lpc->next) { xmlNode *xml_op = (xmlNode *) lpc->data; out->message(out, "node-and-op", data_set, xml_op); } out->end_list(out); return rc; } // \return Standard Pacemaker return code int cli_resource_print(pe_resource_t *rsc, pe_working_set_t *data_set, bool expanded) { pcmk__output_t *out = data_set->priv; unsigned int opts = pe_print_pending; GList *all = NULL; all = g_list_prepend(all, strdup("*")); out->begin_list(out, NULL, NULL, "Resource Config"); out->message(out, crm_map_element_name(rsc->xml), opts, rsc, all, all); out->message(out, "resource-config", rsc, !expanded); out->end_list(out); g_list_free_full(all, free); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("attribute-list", "pe_resource_t *", "char *", "GHashTable *") static int attribute_list_default(pcmk__output_t *out, va_list args) { pe_resource_t *rsc = va_arg(args, pe_resource_t *); char *attr = va_arg(args, char *); GHashTable *params = va_arg(args, GHashTable *); const char *value = NULL; if (params != NULL) { value = g_hash_table_lookup(params, attr); } if (value != NULL) { out->begin_list(out, NULL, NULL, "Attributes"); out->list_item(out, attr, "%s", value); out->end_list(out); } else { out->err(out, "Attribute '%s' not found for '%s'", attr, rsc->id); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("attribute-list", "pe_resource_t *", "char *", "GHashTable *") static int attribute_list_text(pcmk__output_t *out, va_list args) { pe_resource_t *rsc = va_arg(args, pe_resource_t *); char *attr = va_arg(args, char *); GHashTable *params = va_arg(args, GHashTable *); const char *value = NULL; if (params != NULL) { value = g_hash_table_lookup(params, attr); } if (value != NULL) { pcmk__formatted_printf(out, "%s\n", value); } else { out->err(out, "Attribute '%s' not found for '%s'", attr, rsc->id); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("property-list", "pe_resource_t *", "char *") static int property_list_default(pcmk__output_t *out, va_list args) { pe_resource_t *rsc = va_arg(args, pe_resource_t *); char *attr = va_arg(args, char *); const char *value = crm_element_value(rsc->xml, attr); if (value != NULL) { out->begin_list(out, NULL, NULL, "Properties"); out->list_item(out, attr, "%s", value); out->end_list(out); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("property-list", "pe_resource_t *", "char *") static int property_list_text(pcmk__output_t *out, va_list args) { pe_resource_t *rsc = va_arg(args, pe_resource_t *); char *attr = va_arg(args, char *); const char *value = crm_element_value(rsc->xml, attr); if (value != NULL) { pcmk__formatted_printf(out, "%s\n", value); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("resource-check-list", "resource_checks_t *") static int resource_check_list_default(pcmk__output_t *out, va_list args) { resource_checks_t *checks = va_arg(args, resource_checks_t *); pe_resource_t *parent = uber_parent(checks->rsc); int rc = pcmk_rc_no_output; bool printed = false; if (checks->flags != 0 || checks->lock_node != NULL) { printed = true; out->begin_list(out, NULL, NULL, "Resource Checks"); } if (pcmk_is_set(checks->flags, rsc_remain_stopped)) { out->list_item(out, "check", "Configuration specifies '%s' should remain stopped", parent->id); } if (pcmk_is_set(checks->flags, rsc_unpromotable)) { out->list_item(out, "check", "Configuration specifies '%s' should not be promoted", parent->id); } if (pcmk_is_set(checks->flags, rsc_unmanaged)) { out->list_item(out, "check", "Configuration prevents cluster from stopping or starting unmanaged '%s'", parent->id); } if (checks->lock_node) { out->list_item(out, "check", "'%s' is locked to node %s due to shutdown", parent->id, checks->lock_node); } if (printed) { out->end_list(out); rc = pcmk_rc_ok; } return rc; } PCMK__OUTPUT_ARGS("resource-check-list", "resource_checks_t *") static int resource_check_list_xml(pcmk__output_t *out, va_list args) { resource_checks_t *checks = va_arg(args, resource_checks_t *); pe_resource_t *parent = uber_parent(checks->rsc); xmlNodePtr node = pcmk__output_create_xml_node(out, "check", "id", parent->id, NULL); if (pcmk_is_set(checks->flags, rsc_remain_stopped)) { crm_xml_add(node, "remain_stopped", "true"); } if (pcmk_is_set(checks->flags, rsc_unpromotable)) { crm_xml_add(node, "promotable", "false"); } if (pcmk_is_set(checks->flags, rsc_unmanaged)) { crm_xml_add(node, "unmanaged", "true"); } if (checks->lock_node) { crm_xml_add(node, "locked-to", checks->lock_node); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("resource-search-list", "GList *", "gchar *") static int resource_search_list_default(pcmk__output_t *out, va_list args) { GList *nodes = va_arg(args, GList *); gchar *requested_name = va_arg(args, gchar *); bool printed = false; int rc = pcmk_rc_no_output; if (!out->is_quiet(out) && nodes == NULL) { out->err(out, "resource %s is NOT running", requested_name); return rc; } for (GList *lpc = nodes; lpc != NULL; lpc = lpc->next) { node_info_t *ni = (node_info_t *) lpc->data; if (!printed) { out->begin_list(out, NULL, NULL, "Nodes"); printed = true; rc = pcmk_rc_ok; } if (out->is_quiet(out)) { out->list_item(out, "node", "%s", ni->node_name); } else { const char *role_text = ""; if (ni->promoted) { #ifdef PCMK__COMPAT_2_0 role_text = " " RSC_ROLE_PROMOTED_LEGACY_S; #else role_text = " " RSC_ROLE_PROMOTED_S; #endif } out->list_item(out, "node", "resource %s is running on: %s%s", requested_name, ni->node_name, role_text); } } if (printed) { out->end_list(out); } return rc; } PCMK__OUTPUT_ARGS("resource-search-list", "GList *", "gchar *") static int resource_search_list_xml(pcmk__output_t *out, va_list args) { GList *nodes = va_arg(args, GList *); gchar *requested_name = va_arg(args, gchar *); pcmk__output_xml_create_parent(out, "nodes", "resource", requested_name, NULL); for (GList *lpc = nodes; lpc != NULL; lpc = lpc->next) { node_info_t *ni = (node_info_t *) lpc->data; xmlNodePtr sub_node = pcmk__output_create_xml_text_node(out, "node", ni->node_name); if (ni->promoted) { crm_xml_add(sub_node, "state", "promoted"); } } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("resource-reasons-list", "cib_t *", "GList *", "pe_resource_t *", "pe_node_t *") static int resource_reasons_list_default(pcmk__output_t *out, va_list args) { cib_t *cib_conn = va_arg(args, cib_t *); GList *resources = va_arg(args, GList *); pe_resource_t *rsc = va_arg(args, pe_resource_t *); pe_node_t *node = va_arg(args, pe_node_t *); const char *host_uname = (node == NULL)? NULL : node->details->uname; out->begin_list(out, NULL, NULL, "Resource Reasons"); if ((rsc == NULL) && (host_uname == NULL)) { GList *lpc = NULL; GList *hosts = NULL; for (lpc = resources; lpc != NULL; lpc = lpc->next) { pe_resource_t *rsc = (pe_resource_t *) lpc->data; rsc->fns->location(rsc, &hosts, TRUE); if (hosts == NULL) { out->list_item(out, "reason", "Resource %s is not running", rsc->id); } else { out->list_item(out, "reason", "Resource %s is running", rsc->id); } cli_resource_check(out, cib_conn, rsc); g_list_free(hosts); hosts = NULL; } } else if ((rsc != NULL) && (host_uname != NULL)) { if (resource_is_running_on(rsc, host_uname)) { out->list_item(out, "reason", "Resource %s is running on host %s", rsc->id, host_uname); } else { out->list_item(out, "reason", "Resource %s is not running on host %s", rsc->id, host_uname); } cli_resource_check(out, cib_conn, rsc); } else if ((rsc == NULL) && (host_uname != NULL)) { const char* host_uname = node->details->uname; GList *allResources = node->details->allocated_rsc; GList *activeResources = node->details->running_rsc; GList *unactiveResources = pcmk__subtract_lists(allResources, activeResources, (GCompareFunc) strcmp); GList *lpc = NULL; for (lpc = activeResources; lpc != NULL; lpc = lpc->next) { pe_resource_t *rsc = (pe_resource_t *) lpc->data; out->list_item(out, "reason", "Resource %s is running on host %s", rsc->id, host_uname); cli_resource_check(out, cib_conn, rsc); } for(lpc = unactiveResources; lpc != NULL; lpc = lpc->next) { pe_resource_t *rsc = (pe_resource_t *) lpc->data; out->list_item(out, "reason", "Resource %s is assigned to host %s but not running", rsc->id, host_uname); cli_resource_check(out, cib_conn, rsc); } g_list_free(allResources); g_list_free(activeResources); g_list_free(unactiveResources); } else if ((rsc != NULL) && (host_uname == NULL)) { GList *hosts = NULL; rsc->fns->location(rsc, &hosts, TRUE); out->list_item(out, "reason", "Resource %s is %srunning", rsc->id, (hosts? "" : "not ")); cli_resource_check(out, cib_conn, rsc); g_list_free(hosts); } out->end_list(out); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("resource-reasons-list", "cib_t *", "GList *", "pe_resource_t *", "pe_node_t *") static int resource_reasons_list_xml(pcmk__output_t *out, va_list args) { cib_t *cib_conn = va_arg(args, cib_t *); GList *resources = va_arg(args, GList *); pe_resource_t *rsc = va_arg(args, pe_resource_t *); pe_node_t *node = va_arg(args, pe_node_t *); const char *host_uname = (node == NULL)? NULL : node->details->uname; xmlNodePtr xml_node = pcmk__output_xml_create_parent(out, "reason", NULL); if ((rsc == NULL) && (host_uname == NULL)) { GList *lpc = NULL; GList *hosts = NULL; pcmk__output_xml_create_parent(out, "resources", NULL); for (lpc = resources; lpc != NULL; lpc = lpc->next) { pe_resource_t *rsc = (pe_resource_t *) lpc->data; rsc->fns->location(rsc, &hosts, TRUE); pcmk__output_xml_create_parent(out, "resource", "id", rsc->id, "running", pcmk__btoa(hosts != NULL), NULL); cli_resource_check(out, cib_conn, rsc); pcmk__output_xml_pop_parent(out); g_list_free(hosts); hosts = NULL; } pcmk__output_xml_pop_parent(out); } else if ((rsc != NULL) && (host_uname != NULL)) { if (resource_is_running_on(rsc, host_uname)) { crm_xml_add(xml_node, "running_on", host_uname); } cli_resource_check(out, cib_conn, rsc); } else if ((rsc == NULL) && (host_uname != NULL)) { const char* host_uname = node->details->uname; GList *allResources = node->details->allocated_rsc; GList *activeResources = node->details->running_rsc; GList *unactiveResources = pcmk__subtract_lists(allResources, activeResources, (GCompareFunc) strcmp); GList *lpc = NULL; pcmk__output_xml_create_parent(out, "resources", NULL); for (lpc = activeResources; lpc != NULL; lpc = lpc->next) { pe_resource_t *rsc = (pe_resource_t *) lpc->data; pcmk__output_xml_create_parent(out, "resource", "id", rsc->id, "running", "true", "host", host_uname, NULL); cli_resource_check(out, cib_conn, rsc); pcmk__output_xml_pop_parent(out); } for(lpc = unactiveResources; lpc != NULL; lpc = lpc->next) { pe_resource_t *rsc = (pe_resource_t *) lpc->data; pcmk__output_xml_create_parent(out, "resource", "id", rsc->id, "running", "false", "host", host_uname, NULL); cli_resource_check(out, cib_conn, rsc); pcmk__output_xml_pop_parent(out); } pcmk__output_xml_pop_parent(out); g_list_free(allResources); g_list_free(activeResources); g_list_free(unactiveResources); } else if ((rsc != NULL) && (host_uname == NULL)) { GList *hosts = NULL; rsc->fns->location(rsc, &hosts, TRUE); crm_xml_add(xml_node, "running", pcmk__btoa(hosts != NULL)); cli_resource_check(out, cib_conn, rsc); g_list_free(hosts); } return pcmk_rc_ok; } static void add_resource_name(pcmk__output_t *out, pe_resource_t *rsc) { if (rsc->children == NULL) { out->list_item(out, "resource", "%s", rsc->id); } else { for (GList *lpc = rsc->children; lpc != NULL; lpc = lpc->next) { pe_resource_t *child = (pe_resource_t *) lpc->data; add_resource_name(out, child); } } } PCMK__OUTPUT_ARGS("resource-names-list", "GList *") static int resource_names(pcmk__output_t *out, va_list args) { GList *resources = va_arg(args, GList *); if (resources == NULL) { out->err(out, "NO resources configured\n"); return pcmk_rc_no_output; } out->begin_list(out, NULL, NULL, "Resource Names"); for (GList *lpc = resources; lpc != NULL; lpc = lpc->next) { pe_resource_t *rsc = (pe_resource_t *) lpc->data; add_resource_name(out, rsc); } out->end_list(out); return pcmk_rc_ok; } static pcmk__message_entry_t fmt_functions[] = { { "attribute-list", "default", attribute_list_default }, { "attribute-list", "text", attribute_list_text }, { "property-list", "default", property_list_default }, { "property-list", "text", property_list_text }, { "resource-check-list", "default", resource_check_list_default }, { "resource-check-list", "xml", resource_check_list_xml }, { "resource-search-list", "default", resource_search_list_default }, { "resource-search-list", "xml", resource_search_list_xml }, { "resource-reasons-list", "default", resource_reasons_list_default }, { "resource-reasons-list", "xml", resource_reasons_list_xml }, { "resource-names-list", "default", resource_names }, { NULL, NULL, NULL } }; void crm_resource_register_messages(pcmk__output_t *out) { pcmk__register_messages(out, fmt_functions); } diff --git a/tools/crm_resource_runtime.c b/tools/crm_resource_runtime.c index ecabfffb85..fe0ec98437 100644 --- a/tools/crm_resource_runtime.c +++ b/tools/crm_resource_runtime.c @@ -1,1975 +1,1976 @@ /* * Copyright 2004-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ +#include + #include #include #include -#include resource_checks_t * cli_check_resource(pe_resource_t *rsc, char *role_s, char *managed) { pe_resource_t *parent = uber_parent(rsc); resource_checks_t *rc = calloc(1, sizeof(resource_checks_t)); if (role_s) { enum rsc_role_e role = text2role(role_s); if (role == RSC_ROLE_STOPPED) { rc->flags |= rsc_remain_stopped; } else if (pcmk_is_set(parent->flags, pe_rsc_promotable) && (role == RSC_ROLE_UNPROMOTED)) { rc->flags |= rsc_unpromotable; } } if (managed && !crm_is_true(managed)) { rc->flags |= rsc_unmanaged; } if (rsc->lock_node) { rc->lock_node = rsc->lock_node->details->uname; } rc->rsc = rsc; return rc; } static GList * build_node_info_list(pe_resource_t *rsc) { GList *retval = NULL; for (GList *iter = rsc->children; iter != NULL; iter = iter->next) { pe_resource_t *child = (pe_resource_t *) iter->data; for (GList *iter2 = child->running_on; iter2 != NULL; iter2 = iter2->next) { pe_node_t *node = (pe_node_t *) iter2->data; node_info_t *ni = calloc(1, sizeof(node_info_t)); ni->node_name = node->details->uname; ni->promoted = pcmk_is_set(rsc->flags, pe_rsc_promotable) && child->fns->state(child, TRUE) == RSC_ROLE_PROMOTED; retval = g_list_prepend(retval, ni); } } return retval; } GList * cli_resource_search(pe_resource_t *rsc, const char *requested_name, pe_working_set_t *data_set) { GList *retval = NULL; pe_resource_t *parent = uber_parent(rsc); if (pe_rsc_is_clone(rsc)) { retval = build_node_info_list(rsc); /* The anonymous clone children's common ID is supplied */ } else if (pe_rsc_is_clone(parent) && !pcmk_is_set(rsc->flags, pe_rsc_unique) && rsc->clone_name && pcmk__str_eq(requested_name, rsc->clone_name, pcmk__str_casei) && !pcmk__str_eq(requested_name, rsc->id, pcmk__str_casei)) { retval = build_node_info_list(parent); } else if (rsc->running_on != NULL) { for (GList *iter = rsc->running_on; iter != NULL; iter = iter->next) { pe_node_t *node = (pe_node_t *) iter->data; node_info_t *ni = calloc(1, sizeof(node_info_t)); ni->node_name = node->details->uname; ni->promoted = (rsc->fns->state(rsc, TRUE) == RSC_ROLE_PROMOTED); retval = g_list_prepend(retval, ni); } } return retval; } #define XPATH_MAX 1024 // \return Standard Pacemaker return code static int find_resource_attr(pcmk__output_t *out, cib_t * the_cib, const char *attr, const char *rsc, const char *attr_set_type, const char *set_name, const char *attr_id, const char *attr_name, char **value) { int offset = 0; int rc = pcmk_rc_ok; xmlNode *xml_search = NULL; char *xpath_string = NULL; if(value) { *value = NULL; } if(the_cib == NULL) { return ENOTCONN; } xpath_string = calloc(1, XPATH_MAX); offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "%s", get_object_path("resources")); offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "//*[@id=\"%s\"]", rsc); if (attr_set_type) { offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "/%s", attr_set_type); if (set_name) { offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "[@id=\"%s\"]", set_name); } } offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "//nvpair["); if (attr_id) { offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "@id=\"%s\"", attr_id); } if (attr_name) { if (attr_id) { offset += snprintf(xpath_string + offset, XPATH_MAX - offset, " and "); } offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "@name=\"%s\"", attr_name); } offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "]"); CRM_LOG_ASSERT(offset > 0); rc = the_cib->cmds->query(the_cib, xpath_string, &xml_search, cib_sync_call | cib_scope_local | cib_xpath); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { goto done; } crm_log_xml_debug(xml_search, "Match"); if (xml_has_children(xml_search)) { xmlNode *child = NULL; rc = EINVAL; out->info(out, "Multiple attributes match name=%s", attr_name); for (child = pcmk__xml_first_child(xml_search); child != NULL; child = pcmk__xml_next(child)) { out->info(out, " Value: %s \t(id=%s)", crm_element_value(child, XML_NVPAIR_ATTR_VALUE), ID(child)); } out->spacer(out); } else if(value) { const char *tmp = crm_element_value(xml_search, attr); if (tmp) { *value = strdup(tmp); } } done: free(xpath_string); free_xml(xml_search); return rc; } /* PRIVATE. Use the find_matching_attr_resources instead. */ static void find_matching_attr_resources_recursive(pcmk__output_t *out, GList/* */ ** result, pe_resource_t * rsc, const char * rsc_id, const char * attr_set, const char * attr_set_type, const char * attr_id, const char * attr_name, cib_t * cib, const char * cmd, int depth) { int rc = pcmk_rc_ok; char *lookup_id = clone_strip(rsc->id); char *local_attr_id = NULL; /* visit the children */ for(GList *gIter = rsc->children; gIter; gIter = gIter->next) { find_matching_attr_resources_recursive(out, result, (pe_resource_t*)gIter->data, rsc_id, attr_set, attr_set_type, attr_id, attr_name, cib, cmd, depth+1); /* do it only once for clones */ if(pe_clone == rsc->variant) { break; } } rc = find_resource_attr(out, cib, XML_ATTR_ID, lookup_id, attr_set_type, attr_set, attr_id, attr_name, &local_attr_id); /* Post-order traversal. * The root is always on the list and it is the last item. */ if((0 == depth) || (pcmk_rc_ok == rc)) { /* push the head */ *result = g_list_append(*result, rsc); } free(local_attr_id); free(lookup_id); } /* The result is a linearized pre-ordered tree of resources. */ static GList/**/ * find_matching_attr_resources(pcmk__output_t *out, pe_resource_t * rsc, const char * rsc_id, const char * attr_set, const char * attr_set_type, const char * attr_id, const char * attr_name, cib_t * cib, const char * cmd, gboolean force) { int rc = pcmk_rc_ok; char *lookup_id = NULL; char *local_attr_id = NULL; GList * result = NULL; /* If --force is used, update only the requested resource (clone or primitive). * Otherwise, if the primitive has the attribute, use that. * Otherwise use the clone. */ if(force == TRUE) { return g_list_append(result, rsc); } if(rsc->parent && pe_clone == rsc->parent->variant) { int rc = pcmk_rc_ok; char *local_attr_id = NULL; rc = find_resource_attr(out, cib, XML_ATTR_ID, rsc_id, attr_set_type, attr_set, attr_id, attr_name, &local_attr_id); free(local_attr_id); if(rc != pcmk_rc_ok) { rsc = rsc->parent; out->info(out, "Performing %s of '%s' on '%s', the parent of '%s'", cmd, attr_name, rsc->id, rsc_id); } return g_list_append(result, rsc); } else if(rsc->parent == NULL && rsc->children && pe_clone == rsc->variant) { pe_resource_t *child = rsc->children->data; if(child->variant == pe_native) { lookup_id = clone_strip(child->id); /* Could be a cloned group! */ rc = find_resource_attr(out, cib, XML_ATTR_ID, lookup_id, attr_set_type, attr_set, attr_id, attr_name, &local_attr_id); if(rc == pcmk_rc_ok) { rsc = child; out->info(out, "A value for '%s' already exists in child '%s', performing %s on that instead of '%s'", attr_name, lookup_id, cmd, rsc_id); } free(local_attr_id); free(lookup_id); } return g_list_append(result, rsc); } /* If the resource is a group ==> children inherit the attribute if defined. */ find_matching_attr_resources_recursive(out, &result, rsc, rsc_id, attr_set, attr_set_type, attr_id, attr_name, cib, cmd, 0); return result; } // \return Standard Pacemaker return code int cli_resource_update_attribute(pe_resource_t *rsc, const char *requested_name, const char *attr_set, const char *attr_set_type, const char *attr_id, const char *attr_name, const char *attr_value, gboolean recursive, cib_t *cib, int cib_options, pe_working_set_t *data_set, gboolean force) { pcmk__output_t *out = data_set->priv; int rc = pcmk_rc_ok; static bool need_init = TRUE; char *local_attr_id = NULL; char *local_attr_set = NULL; GList/**/ *resources = NULL; const char *common_attr_id = attr_id; if (attr_id == NULL && force == FALSE) { find_resource_attr (out, cib, XML_ATTR_ID, uber_parent(rsc)->id, NULL, NULL, NULL, attr_name, NULL); } if (pcmk__str_eq(attr_set_type, XML_TAG_ATTR_SETS, pcmk__str_casei)) { if (force == FALSE) { rc = find_resource_attr(out, cib, XML_ATTR_ID, uber_parent(rsc)->id, XML_TAG_META_SETS, attr_set, attr_id, attr_name, &local_attr_id); if (rc == pcmk_rc_ok && !out->is_quiet(out)) { out->err(out, "WARNING: There is already a meta attribute for '%s' called '%s' (id=%s)", uber_parent(rsc)->id, attr_name, local_attr_id); out->err(out, " Delete '%s' first or use the force option to override", local_attr_id); } free(local_attr_id); if (rc == pcmk_rc_ok) { return ENOTUNIQ; } } resources = g_list_append(resources, rsc); } else { resources = find_matching_attr_resources(out, rsc, requested_name, attr_set, attr_set_type, attr_id, attr_name, cib, "update", force); } /* If either attr_set or attr_id is specified, * one clearly intends to modify a single resource. * It is the last item on the resource list.*/ for(GList *gIter = (attr_set||attr_id) ? g_list_last(resources) : resources ; gIter; gIter = gIter->next) { char *lookup_id = NULL; xmlNode *xml_top = NULL; xmlNode *xml_obj = NULL; local_attr_id = NULL; local_attr_set = NULL; rsc = (pe_resource_t*)gIter->data; attr_id = common_attr_id; lookup_id = clone_strip(rsc->id); /* Could be a cloned group! */ rc = find_resource_attr(out, cib, XML_ATTR_ID, lookup_id, attr_set_type, attr_set, attr_id, attr_name, &local_attr_id); if (rc == pcmk_rc_ok) { crm_debug("Found a match for name=%s: id=%s", attr_name, local_attr_id); attr_id = local_attr_id; } else if (rc != ENXIO) { free(lookup_id); free(local_attr_id); g_list_free(resources); return rc; } else { const char *tag = crm_element_name(rsc->xml); if (attr_set == NULL) { local_attr_set = crm_strdup_printf("%s-%s", lookup_id, attr_set_type); attr_set = local_attr_set; } if (attr_id == NULL) { local_attr_id = crm_strdup_printf("%s-%s", attr_set, attr_name); attr_id = local_attr_id; } xml_top = create_xml_node(NULL, tag); crm_xml_add(xml_top, XML_ATTR_ID, lookup_id); xml_obj = create_xml_node(xml_top, attr_set_type); crm_xml_add(xml_obj, XML_ATTR_ID, attr_set); } xml_obj = crm_create_nvpair_xml(xml_obj, attr_id, attr_name, attr_value); if (xml_top == NULL) { xml_top = xml_obj; } crm_log_xml_debug(xml_top, "Update"); rc = cib->cmds->modify(cib, XML_CIB_TAG_RESOURCES, xml_top, cib_options); rc = pcmk_legacy2rc(rc); if (rc == pcmk_rc_ok) { out->info(out, "Set '%s' option: id=%s%s%s%s%s value=%s", lookup_id, local_attr_id, attr_set ? " set=" : "", attr_set ? attr_set : "", attr_name ? " name=" : "", attr_name ? attr_name : "", attr_value); } free_xml(xml_top); free(lookup_id); free(local_attr_id); free(local_attr_set); if(recursive && pcmk__str_eq(attr_set_type, XML_TAG_META_SETS, pcmk__str_casei)) { GList *lpc = NULL; if(need_init) { xmlNode *cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, data_set->input); need_init = FALSE; unpack_constraints(cib_constraints, data_set); pe__clear_resource_flags_on_all(data_set, pe_rsc_allocating); } crm_debug("Looking for dependencies %p", rsc->rsc_cons_lhs); pe__set_resource_flags(rsc, pe_rsc_allocating); for (lpc = rsc->rsc_cons_lhs; lpc != NULL; lpc = lpc->next) { pcmk__colocation_t *cons = (pcmk__colocation_t *) lpc->data; pe_resource_t *peer = cons->rsc_lh; crm_debug("Checking %s %d", cons->id, cons->score); if (cons->score > 0 && !pcmk_is_set(peer->flags, pe_rsc_allocating)) { /* Don't get into colocation loops */ crm_debug("Setting %s=%s for dependent resource %s", attr_name, attr_value, peer->id); cli_resource_update_attribute(peer, peer->id, NULL, attr_set_type, NULL, attr_name, attr_value, recursive, cib, cib_options, data_set, force); } } } } g_list_free(resources); return rc; } // \return Standard Pacemaker return code int cli_resource_delete_attribute(pe_resource_t *rsc, const char *requested_name, const char *attr_set, const char *attr_set_type, const char *attr_id, const char *attr_name, cib_t *cib, int cib_options, pe_working_set_t *data_set, gboolean force) { pcmk__output_t *out = data_set->priv; int rc = pcmk_rc_ok; GList/**/ *resources = NULL; if (attr_id == NULL && force == FALSE) { find_resource_attr(out, cib, XML_ATTR_ID, uber_parent(rsc)->id, NULL, NULL, NULL, attr_name, NULL); } if(pcmk__str_eq(attr_set_type, XML_TAG_META_SETS, pcmk__str_casei)) { resources = find_matching_attr_resources(out, rsc, requested_name, attr_set, attr_set_type, attr_id, attr_name, cib, "delete", force); } else { resources = g_list_append(resources, rsc); } for(GList *gIter = resources; gIter; gIter = gIter->next) { char *lookup_id = NULL; xmlNode *xml_obj = NULL; char *local_attr_id = NULL; rsc = (pe_resource_t*)gIter->data; lookup_id = clone_strip(rsc->id); rc = find_resource_attr(out, cib, XML_ATTR_ID, lookup_id, attr_set_type, attr_set, attr_id, attr_name, &local_attr_id); if (rc == ENXIO) { free(lookup_id); rc = pcmk_rc_ok; continue; } else if (rc != pcmk_rc_ok) { free(lookup_id); g_list_free(resources); return rc; } if (attr_id == NULL) { attr_id = local_attr_id; } xml_obj = crm_create_nvpair_xml(NULL, attr_id, attr_name, NULL); crm_log_xml_debug(xml_obj, "Delete"); CRM_ASSERT(cib); rc = cib->cmds->remove(cib, XML_CIB_TAG_RESOURCES, xml_obj, cib_options); rc = pcmk_legacy2rc(rc); if (rc == pcmk_rc_ok) { out->info(out, "Deleted '%s' option: id=%s%s%s%s%s", lookup_id, local_attr_id, attr_set ? " set=" : "", attr_set ? attr_set : "", attr_name ? " name=" : "", attr_name ? attr_name : ""); } free(lookup_id); free_xml(xml_obj); free(local_attr_id); } g_list_free(resources); return rc; } // \return Standard Pacemaker return code static int send_lrm_rsc_op(pcmk_ipc_api_t *controld_api, bool do_fail_resource, const char *host_uname, const char *rsc_id, pe_working_set_t *data_set) { pcmk__output_t *out = data_set->priv; const char *router_node = host_uname; const char *rsc_api_id = NULL; const char *rsc_long_id = NULL; const char *rsc_class = NULL; const char *rsc_provider = NULL; const char *rsc_type = NULL; bool cib_only = false; pe_resource_t *rsc = pe_find_resource(data_set->resources, rsc_id); if (rsc == NULL) { out->err(out, "Resource %s not found", rsc_id); return ENXIO; } else if (rsc->variant != pe_native) { out->err(out, "We can only process primitive resources, not %s", rsc_id); return EINVAL; } rsc_class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); rsc_provider = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER), rsc_type = crm_element_value(rsc->xml, XML_ATTR_TYPE); if ((rsc_class == NULL) || (rsc_type == NULL)) { out->err(out, "Resource %s does not have a class and type", rsc_id); return EINVAL; } { pe_node_t *node = pe_find_node(data_set->nodes, host_uname); if (node == NULL) { out->err(out, "Node %s not found", host_uname); return pcmk_rc_node_unknown; } if (!(node->details->online)) { if (do_fail_resource) { out->err(out, "Node %s is not online", host_uname); return ENOTCONN; } else { cib_only = true; } } if (!cib_only && pe__is_guest_or_remote_node(node)) { node = pe__current_node(node->details->remote_rsc); if (node == NULL) { out->err(out, "No cluster connection to Pacemaker Remote node %s detected", host_uname); return ENOTCONN; } router_node = node->details->uname; } } if (rsc->clone_name) { rsc_api_id = rsc->clone_name; rsc_long_id = rsc->id; } else { rsc_api_id = rsc->id; } if (do_fail_resource) { return pcmk_controld_api_fail(controld_api, host_uname, router_node, rsc_api_id, rsc_long_id, rsc_class, rsc_provider, rsc_type); } else { return pcmk_controld_api_refresh(controld_api, host_uname, router_node, rsc_api_id, rsc_long_id, rsc_class, rsc_provider, rsc_type, cib_only); } } /*! * \internal * \brief Get resource name as used in failure-related node attributes * * \param[in] rsc Resource to check * * \return Newly allocated string containing resource's fail name * \note The caller is responsible for freeing the result. */ static inline char * rsc_fail_name(pe_resource_t *rsc) { const char *name = (rsc->clone_name? rsc->clone_name : rsc->id); return pcmk_is_set(rsc->flags, pe_rsc_unique)? strdup(name) : clone_strip(name); } // \return Standard Pacemaker return code static int clear_rsc_history(pcmk_ipc_api_t *controld_api, const char *host_uname, const char *rsc_id, pe_working_set_t *data_set) { int rc = pcmk_rc_ok; /* Erase the resource's entire LRM history in the CIB, even if we're only * clearing a single operation's fail count. If we erased only entries for a * single operation, we might wind up with a wrong idea of the current * resource state, and we might not re-probe the resource. */ rc = send_lrm_rsc_op(controld_api, false, host_uname, rsc_id, data_set); if (rc != pcmk_rc_ok) { return rc; } crm_trace("Processing %d mainloop inputs", pcmk_controld_api_replies_expected(controld_api)); while (g_main_context_iteration(NULL, FALSE)) { crm_trace("Processed mainloop input, %d still remaining", pcmk_controld_api_replies_expected(controld_api)); } return rc; } // \return Standard Pacemaker return code static int clear_rsc_failures(pcmk__output_t *out, pcmk_ipc_api_t *controld_api, const char *node_name, const char *rsc_id, const char *operation, const char *interval_spec, pe_working_set_t *data_set) { int rc = pcmk_rc_ok; const char *failed_value = NULL; const char *failed_id = NULL; const char *interval_ms_s = NULL; GHashTable *rscs = NULL; GHashTableIter iter; /* Create a hash table to use as a set of resources to clean. This lets us * clean each resource only once (per node) regardless of how many failed * operations it has. */ rscs = pcmk__strkey_table(NULL, NULL); // Normalize interval to milliseconds for comparison to history entry if (operation) { interval_ms_s = crm_strdup_printf("%u", crm_parse_interval_spec(interval_spec)); } for (xmlNode *xml_op = pcmk__xml_first_child(data_set->failed); xml_op != NULL; xml_op = pcmk__xml_next(xml_op)) { failed_id = crm_element_value(xml_op, XML_LRM_ATTR_RSCID); if (failed_id == NULL) { // Malformed history entry, should never happen continue; } // No resource specified means all resources match if (rsc_id) { pe_resource_t *fail_rsc = pe_find_resource_with_flags(data_set->resources, failed_id, pe_find_renamed|pe_find_anon); if (!fail_rsc || !pcmk__str_eq(rsc_id, fail_rsc->id, pcmk__str_casei)) { continue; } } // Host name should always have been provided by this point failed_value = crm_element_value(xml_op, XML_ATTR_UNAME); if (!pcmk__str_eq(node_name, failed_value, pcmk__str_casei)) { continue; } // No operation specified means all operations match if (operation) { failed_value = crm_element_value(xml_op, XML_LRM_ATTR_TASK); if (!pcmk__str_eq(operation, failed_value, pcmk__str_casei)) { continue; } // Interval (if operation was specified) defaults to 0 (not all) failed_value = crm_element_value(xml_op, XML_LRM_ATTR_INTERVAL_MS); if (!pcmk__str_eq(interval_ms_s, failed_value, pcmk__str_casei)) { continue; } } g_hash_table_add(rscs, (gpointer) failed_id); } g_hash_table_iter_init(&iter, rscs); while (g_hash_table_iter_next(&iter, (gpointer *) &failed_id, NULL)) { crm_debug("Erasing failures of %s on %s", failed_id, node_name); rc = clear_rsc_history(controld_api, node_name, failed_id, data_set); if (rc != pcmk_rc_ok) { return rc; } } g_hash_table_destroy(rscs); return rc; } // \return Standard Pacemaker return code static int clear_rsc_fail_attrs(pe_resource_t *rsc, const char *operation, const char *interval_spec, pe_node_t *node) { int rc = pcmk_rc_ok; int attr_options = pcmk__node_attr_none; char *rsc_name = rsc_fail_name(rsc); if (pe__is_guest_or_remote_node(node)) { attr_options |= pcmk__node_attr_remote; } rc = pcmk__node_attr_request_clear(NULL, node->details->uname, rsc_name, operation, interval_spec, NULL, attr_options); free(rsc_name); return rc; } // \return Standard Pacemaker return code int cli_resource_delete(pcmk_ipc_api_t *controld_api, const char *host_uname, pe_resource_t *rsc, const char *operation, const char *interval_spec, bool just_failures, pe_working_set_t *data_set, gboolean force) { pcmk__output_t *out = data_set->priv; int rc = pcmk_rc_ok; pe_node_t *node = NULL; if (rsc == NULL) { return ENXIO; } else if (rsc->children) { GList *lpc = NULL; for (lpc = rsc->children; lpc != NULL; lpc = lpc->next) { pe_resource_t *child = (pe_resource_t *) lpc->data; rc = cli_resource_delete(controld_api, host_uname, child, operation, interval_spec, just_failures, data_set, force); if (rc != pcmk_rc_ok) { return rc; } } return pcmk_rc_ok; } else if (host_uname == NULL) { GList *lpc = NULL; GList *nodes = g_hash_table_get_values(rsc->known_on); if(nodes == NULL && force) { nodes = pcmk__copy_node_list(data_set->nodes, false); } else if(nodes == NULL && rsc->exclusive_discover) { GHashTableIter iter; pe_node_t *node = NULL; g_hash_table_iter_init(&iter, rsc->allowed_nodes); while (g_hash_table_iter_next(&iter, NULL, (void**)&node)) { if(node->weight >= 0) { nodes = g_list_prepend(nodes, node); } } } else if(nodes == NULL) { nodes = g_hash_table_get_values(rsc->allowed_nodes); } for (lpc = nodes; lpc != NULL; lpc = lpc->next) { node = (pe_node_t *) lpc->data; if (node->details->online) { rc = cli_resource_delete(controld_api, node->details->uname, rsc, operation, interval_spec, just_failures, data_set, force); } if (rc != pcmk_rc_ok) { g_list_free(nodes); return rc; } } g_list_free(nodes); return pcmk_rc_ok; } node = pe_find_node(data_set->nodes, host_uname); if (node == NULL) { out->err(out, "Unable to clean up %s because node %s not found", rsc->id, host_uname); return ENODEV; } if (!node->details->rsc_discovery_enabled) { out->err(out, "Unable to clean up %s because resource discovery disabled on %s", rsc->id, host_uname); return EOPNOTSUPP; } if (controld_api == NULL) { out->err(out, "Dry run: skipping clean-up of %s on %s due to CIB_file", rsc->id, host_uname); return pcmk_rc_ok; } rc = clear_rsc_fail_attrs(rsc, operation, interval_spec, node); if (rc != pcmk_rc_ok) { out->err(out, "Unable to clean up %s failures on %s: %s", rsc->id, host_uname, pcmk_rc_str(rc)); return rc; } if (just_failures) { rc = clear_rsc_failures(out, controld_api, host_uname, rsc->id, operation, interval_spec, data_set); } else { rc = clear_rsc_history(controld_api, host_uname, rsc->id, data_set); } if (rc != pcmk_rc_ok) { out->err(out, "Cleaned %s failures on %s, but unable to clean history: %s", rsc->id, host_uname, pcmk_strerror(rc)); } else { out->info(out, "Cleaned up %s on %s", rsc->id, host_uname); } return rc; } // \return Standard Pacemaker return code int cli_cleanup_all(pcmk_ipc_api_t *controld_api, const char *node_name, const char *operation, const char *interval_spec, pe_working_set_t *data_set) { pcmk__output_t *out = data_set->priv; int rc = pcmk_rc_ok; int attr_options = pcmk__node_attr_none; const char *display_name = node_name? node_name : "all nodes"; if (controld_api == NULL) { out->info(out, "Dry run: skipping clean-up of %s due to CIB_file", display_name); return rc; } if (node_name) { pe_node_t *node = pe_find_node(data_set->nodes, node_name); if (node == NULL) { out->err(out, "Unknown node: %s", node_name); return ENXIO; } if (pe__is_guest_or_remote_node(node)) { attr_options |= pcmk__node_attr_remote; } } rc = pcmk__node_attr_request_clear(NULL, node_name, NULL, operation, interval_spec, NULL, attr_options); if (rc != pcmk_rc_ok) { out->err(out, "Unable to clean up all failures on %s: %s", display_name, pcmk_rc_str(rc)); return rc; } if (node_name) { rc = clear_rsc_failures(out, controld_api, node_name, NULL, operation, interval_spec, data_set); if (rc != pcmk_rc_ok) { out->err(out, "Cleaned all resource failures on %s, but unable to clean history: %s", node_name, pcmk_strerror(rc)); return rc; } } else { for (GList *iter = data_set->nodes; iter; iter = iter->next) { pe_node_t *node = (pe_node_t *) iter->data; rc = clear_rsc_failures(out, controld_api, node->details->uname, NULL, operation, interval_spec, data_set); if (rc != pcmk_rc_ok) { out->err(out, "Cleaned all resource failures on all nodes, but unable to clean history: %s", pcmk_strerror(rc)); return rc; } } } out->info(out, "Cleaned up all resources on %s", display_name); return rc; } int cli_resource_check(pcmk__output_t *out, cib_t * cib_conn, pe_resource_t *rsc) { char *role_s = NULL; char *managed = NULL; pe_resource_t *parent = uber_parent(rsc); int rc = pcmk_rc_no_output; resource_checks_t *checks = NULL; find_resource_attr(out, cib_conn, XML_NVPAIR_ATTR_VALUE, parent->id, NULL, NULL, NULL, XML_RSC_ATTR_MANAGED, &managed); find_resource_attr(out, cib_conn, XML_NVPAIR_ATTR_VALUE, parent->id, NULL, NULL, NULL, XML_RSC_ATTR_TARGET_ROLE, &role_s); checks = cli_check_resource(rsc, role_s, managed); if (checks->flags != 0 || checks->lock_node != NULL) { rc = out->message(out, "resource-check-list", checks); } free(role_s); free(managed); free(checks); return rc; } // \return Standard Pacemaker return code int cli_resource_fail(pcmk_ipc_api_t *controld_api, const char *host_uname, const char *rsc_id, pe_working_set_t *data_set) { crm_notice("Failing %s on %s", rsc_id, host_uname); return send_lrm_rsc_op(controld_api, true, host_uname, rsc_id, data_set); } static GHashTable * generate_resource_params(pe_resource_t *rsc, pe_node_t *node, pe_working_set_t *data_set) { GHashTable *params = NULL; GHashTable *meta = NULL; GHashTable *combined = NULL; GHashTableIter iter; char *key = NULL; char *value = NULL; combined = pcmk__strkey_table(free, free); params = pe_rsc_params(rsc, node, data_set); if (params != NULL) { g_hash_table_iter_init(&iter, params); while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) { g_hash_table_insert(combined, strdup(key), strdup(value)); } } meta = pcmk__strkey_table(free, free); get_meta_attributes(meta, rsc, node, data_set); if (meta != NULL) { g_hash_table_iter_init(&iter, meta); while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) { char *crm_name = crm_meta_name(key); g_hash_table_insert(combined, crm_name, strdup(value)); } g_hash_table_destroy(meta); } return combined; } bool resource_is_running_on(pe_resource_t *rsc, const char *host) { bool found = TRUE; GList *hIter = NULL; GList *hosts = NULL; if(rsc == NULL) { return FALSE; } rsc->fns->location(rsc, &hosts, TRUE); for (hIter = hosts; host != NULL && hIter != NULL; hIter = hIter->next) { pe_node_t *node = (pe_node_t *) hIter->data; if(strcmp(host, node->details->uname) == 0) { crm_trace("Resource %s is running on %s\n", rsc->id, host); goto done; } else if(strcmp(host, node->details->id) == 0) { crm_trace("Resource %s is running on %s\n", rsc->id, host); goto done; } } if(host != NULL) { crm_trace("Resource %s is not running on: %s\n", rsc->id, host); found = FALSE; } else if(host == NULL && hosts == NULL) { crm_trace("Resource %s is not running\n", rsc->id); found = FALSE; } done: g_list_free(hosts); return found; } /*! * \internal * \brief Create a list of all resources active on host from a given list * * \param[in] host Name of host to check whether resources are active * \param[in] rsc_list List of resources to check * * \return New list of resources from list that are active on host */ static GList * get_active_resources(const char *host, GList *rsc_list) { GList *rIter = NULL; GList *active = NULL; for (rIter = rsc_list; rIter != NULL; rIter = rIter->next) { pe_resource_t *rsc = (pe_resource_t *) rIter->data; /* Expand groups to their members, because if we're restarting a member * other than the first, we can't otherwise tell which resources are * stopping and starting. */ if (rsc->variant == pe_group) { active = g_list_concat(active, get_active_resources(host, rsc->children)); } else if (resource_is_running_on(rsc, host)) { active = g_list_append(active, strdup(rsc->id)); } } return active; } static void dump_list(GList *items, const char *tag) { int lpc = 0; GList *item = NULL; for (item = items; item != NULL; item = item->next) { crm_trace("%s[%d]: %s", tag, lpc, (char*)item->data); lpc++; } } static void display_list(pcmk__output_t *out, GList *items, const char *tag) { GList *item = NULL; for (item = items; item != NULL; item = item->next) { out->info(out, "%s%s", tag, (const char *)item->data); } } /*! * \internal * \brief Upgrade XML to latest schema version and use it as working set input * * This also updates the working set timestamp to the current time. * * \param[in] data_set Working set instance to update * \param[in] xml XML to use as input * * \return Standard Pacemaker return code * \note On success, caller is responsible for freeing memory allocated for * data_set->now. * \todo This follows the example of other callers of cli_config_update() * and returns ENOKEY ("Required key not available") if that fails, * but perhaps pcmk_rc_schema_validation would be better in that case. */ int update_working_set_xml(pe_working_set_t *data_set, xmlNode **xml) { if (cli_config_update(xml, NULL, FALSE) == FALSE) { return ENOKEY; } data_set->input = *xml; data_set->now = crm_time_new(NULL); return pcmk_rc_ok; } /*! * \internal * \brief Update a working set's XML input based on a CIB query * * \param[in] data_set Data set instance to initialize * \param[in] cib Connection to the CIB manager * * \return Standard Pacemaker return code * \note On success, caller is responsible for freeing memory allocated for * data_set->input and data_set->now. */ static int update_working_set_from_cib(pcmk__output_t *out, pe_working_set_t * data_set, cib_t *cib) { xmlNode *cib_xml_copy = NULL; int rc = pcmk_rc_ok; rc = cib->cmds->query(cib, NULL, &cib_xml_copy, cib_scope_local | cib_sync_call); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { out->err(out, "Could not obtain the current CIB: %s (%d)", pcmk_strerror(rc), rc); return rc; } rc = update_working_set_xml(data_set, &cib_xml_copy); if (rc != pcmk_rc_ok) { out->err(out, "Could not upgrade the current CIB XML"); free_xml(cib_xml_copy); return rc; } return rc; } // \return Standard Pacemaker return code static int update_dataset(cib_t *cib, pe_working_set_t * data_set, bool simulate) { char *pid = NULL; char *shadow_file = NULL; cib_t *shadow_cib = NULL; int rc = pcmk_rc_ok; pcmk__output_t *out = data_set->priv; pe_reset_working_set(data_set); rc = update_working_set_from_cib(out, data_set, cib); if (rc != pcmk_rc_ok) { return rc; } if(simulate) { bool prev_quiet = false; pid = pcmk__getpid_s(); shadow_cib = cib_shadow_new(pid); shadow_file = get_shadow_file(pid); if (shadow_cib == NULL) { out->err(out, "Could not create shadow cib: '%s'", pid); rc = ENXIO; goto done; } rc = write_xml_file(data_set->input, shadow_file, FALSE); if (rc < 0) { out->err(out, "Could not populate shadow cib: %s (%d)", pcmk_strerror(rc), rc); goto done; } rc = shadow_cib->cmds->signon(shadow_cib, crm_system_name, cib_command); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { out->err(out, "Could not connect to shadow cib: %s (%d)", pcmk_strerror(rc), rc); goto done; } pcmk__schedule_actions(data_set, data_set->input, NULL); prev_quiet = out->is_quiet(out); out->quiet = true; run_simulation(data_set, shadow_cib, NULL); out->quiet = prev_quiet; rc = update_dataset(shadow_cib, data_set, FALSE); } else { cluster_status(data_set); } done: /* Do not free data_set->input here, we need rsc->xml to be valid later on */ cib_delete(shadow_cib); free(pid); if(shadow_file) { unlink(shadow_file); free(shadow_file); } return rc; } static int max_delay_for_resource(pe_working_set_t * data_set, pe_resource_t *rsc) { int delay = 0; int max_delay = 0; if(rsc && rsc->children) { GList *iter = NULL; for(iter = rsc->children; iter; iter = iter->next) { pe_resource_t *child = (pe_resource_t *)iter->data; delay = max_delay_for_resource(data_set, child); if(delay > max_delay) { double seconds = delay / 1000.0; crm_trace("Calculated new delay of %.1fs due to %s", seconds, child->id); max_delay = delay; } } } else if(rsc) { char *key = crm_strdup_printf("%s_%s_0", rsc->id, RSC_STOP); pe_action_t *stop = custom_action(rsc, key, RSC_STOP, NULL, TRUE, FALSE, data_set); const char *value = g_hash_table_lookup(stop->meta, XML_ATTR_TIMEOUT); long long result_ll; if ((pcmk__scan_ll(value, &result_ll, -1LL) == pcmk_rc_ok) && (result_ll >= 0) && (result_ll <= INT_MAX)) { max_delay = (int) result_ll; } else { max_delay = -1; } pe_free_action(stop); } return max_delay; } static int max_delay_in(pe_working_set_t * data_set, GList *resources) { int max_delay = 0; GList *item = NULL; for (item = resources; item != NULL; item = item->next) { int delay = 0; pe_resource_t *rsc = pe_find_resource(data_set->resources, (const char *)item->data); delay = max_delay_for_resource(data_set, rsc); if(delay > max_delay) { double seconds = delay / 1000.0; crm_trace("Calculated new delay of %.1fs due to %s", seconds, rsc->id); max_delay = delay; } } return 5 + (max_delay / 1000); } #define waiting_for_starts(d, r, h) ((d != NULL) || \ (resource_is_running_on((r), (h)) == FALSE)) /*! * \internal * \brief Restart a resource (on a particular host if requested). * * \param[in] rsc The resource to restart * \param[in] host The host to restart the resource on (or NULL for all) * \param[in] timeout_ms Consider failed if actions do not complete in this time * (specified in milliseconds, but a two-second * granularity is actually used; if 0, a timeout will be * calculated based on the resource timeout) * \param[in] cib Connection to the CIB manager * * \return Standard Pacemaker return code (exits on certain failures) */ int cli_resource_restart(pcmk__output_t *out, pe_resource_t *rsc, const char *host, const char *move_lifetime, int timeout_ms, cib_t *cib, int cib_options, gboolean promoted_role_only, gboolean force) { int rc = pcmk_rc_ok; int lpc = 0; int before = 0; int step_timeout_s = 0; int sleep_interval = 2; int timeout = timeout_ms / 1000; bool stop_via_ban = FALSE; char *rsc_id = NULL; char *orig_target_role = NULL; GList *list_delta = NULL; GList *target_active = NULL; GList *current_active = NULL; GList *restart_target_active = NULL; pe_working_set_t *data_set = NULL; if(resource_is_running_on(rsc, host) == FALSE) { const char *id = rsc->clone_name?rsc->clone_name:rsc->id; if(host) { out->err(out, "%s is not running on %s and so cannot be restarted", id, host); } else { out->err(out, "%s is not running anywhere and so cannot be restarted", id); } return ENXIO; } rsc_id = strdup(rsc->id); if ((pe_rsc_is_clone(rsc) || pe_bundle_replicas(rsc)) && host) { stop_via_ban = TRUE; } /* grab full cib determine originally active resources disable or ban poll cib and watch for affected resources to get stopped without --timeout, calculate the stop timeout for each step and wait for that if we hit --timeout or the service timeout, re-enable or un-ban, report failure and indicate which resources we couldn't take down if everything stopped, re-enable or un-ban poll cib and watch for affected resources to get started without --timeout, calculate the start timeout for each step and wait for that if we hit --timeout or the service timeout, report (different) failure and indicate which resources we couldn't bring back up report success Optimizations: - use constraints to determine ordered list of affected resources - Allow a --no-deps option (aka. --force-restart) */ data_set = pe_new_working_set(); if (data_set == NULL) { crm_perror(LOG_ERR, "Could not allocate working set"); rc = ENOMEM; goto done; } data_set->priv = out; pe__set_working_set_flags(data_set, pe_flag_no_counts|pe_flag_no_compat); rc = update_dataset(cib, data_set, FALSE); if(rc != pcmk_rc_ok) { out->err(out, "Could not get new resource list: %s (%d)", pcmk_strerror(rc), rc); goto done; } restart_target_active = get_active_resources(host, data_set->resources); current_active = get_active_resources(host, data_set->resources); dump_list(current_active, "Origin"); if (stop_via_ban) { /* Stop the clone or bundle instance by banning it from the host */ out->quiet = true; rc = cli_resource_ban(out, rsc_id, host, move_lifetime, NULL, cib, cib_options, promoted_role_only); } else { /* Stop the resource by setting target-role to Stopped. * Remember any existing target-role so we can restore it later * (though it only makes any difference if it's Unpromoted). */ char *lookup_id = clone_strip(rsc->id); find_resource_attr(out, cib, XML_NVPAIR_ATTR_VALUE, lookup_id, NULL, NULL, NULL, XML_RSC_ATTR_TARGET_ROLE, &orig_target_role); free(lookup_id); rc = cli_resource_update_attribute(rsc, rsc_id, NULL, XML_TAG_META_SETS, NULL, XML_RSC_ATTR_TARGET_ROLE, RSC_STOPPED, FALSE, cib, cib_options, data_set, force); } if(rc != pcmk_rc_ok) { out->err(out, "Could not set target-role for %s: %s (%d)", rsc_id, pcmk_strerror(rc), rc); if (current_active) { g_list_free_full(current_active, free); } if (restart_target_active) { g_list_free_full(restart_target_active, free); } goto done; } rc = update_dataset(cib, data_set, TRUE); if(rc != pcmk_rc_ok) { out->err(out, "Could not determine which resources would be stopped"); goto failure; } target_active = get_active_resources(host, data_set->resources); dump_list(target_active, "Target"); list_delta = pcmk__subtract_lists(current_active, target_active, (GCompareFunc) strcmp); out->info(out, "Waiting for %d resources to stop:", g_list_length(list_delta)); display_list(out, list_delta, " * "); step_timeout_s = timeout / sleep_interval; while (list_delta != NULL) { before = g_list_length(list_delta); if(timeout_ms == 0) { step_timeout_s = max_delay_in(data_set, list_delta) / sleep_interval; } /* We probably don't need the entire step timeout */ for(lpc = 0; (lpc < step_timeout_s) && (list_delta != NULL); lpc++) { sleep(sleep_interval); if(timeout) { timeout -= sleep_interval; crm_trace("%ds remaining", timeout); } rc = update_dataset(cib, data_set, FALSE); if(rc != pcmk_rc_ok) { out->err(out, "Could not determine which resources were stopped"); goto failure; } if (current_active) { g_list_free_full(current_active, free); } current_active = get_active_resources(host, data_set->resources); g_list_free(list_delta); list_delta = pcmk__subtract_lists(current_active, target_active, (GCompareFunc) strcmp); dump_list(current_active, "Current"); dump_list(list_delta, "Delta"); } crm_trace("%d (was %d) resources remaining", g_list_length(list_delta), before); if(before == g_list_length(list_delta)) { /* aborted during stop phase, print the contents of list_delta */ out->err(out, "Could not complete shutdown of %s, %d resources remaining", rsc_id, g_list_length(list_delta)); display_list(out, list_delta, " * "); rc = ETIME; goto failure; } } if (stop_via_ban) { rc = cli_resource_clear(rsc_id, host, NULL, cib, cib_options, TRUE, force); } else if (orig_target_role) { rc = cli_resource_update_attribute(rsc, rsc_id, NULL, XML_TAG_META_SETS, NULL, XML_RSC_ATTR_TARGET_ROLE, orig_target_role, FALSE, cib, cib_options, data_set, force); free(orig_target_role); orig_target_role = NULL; } else { rc = cli_resource_delete_attribute(rsc, rsc_id, NULL, XML_TAG_META_SETS, NULL, XML_RSC_ATTR_TARGET_ROLE, cib, cib_options, data_set, force); } if(rc != pcmk_rc_ok) { out->err(out, "Could not unset target-role for %s: %s (%d)", rsc_id, pcmk_strerror(rc), rc); goto done; } if (target_active) { g_list_free_full(target_active, free); } target_active = restart_target_active; list_delta = pcmk__subtract_lists(target_active, current_active, (GCompareFunc) strcmp); out->info(out, "Waiting for %d resources to start again:", g_list_length(list_delta)); display_list(out, list_delta, " * "); step_timeout_s = timeout / sleep_interval; while (waiting_for_starts(list_delta, rsc, host)) { before = g_list_length(list_delta); if(timeout_ms == 0) { step_timeout_s = max_delay_in(data_set, list_delta) / sleep_interval; } /* We probably don't need the entire step timeout */ for (lpc = 0; (lpc < step_timeout_s) && waiting_for_starts(list_delta, rsc, host); lpc++) { sleep(sleep_interval); if(timeout) { timeout -= sleep_interval; crm_trace("%ds remaining", timeout); } rc = update_dataset(cib, data_set, FALSE); if(rc != pcmk_rc_ok) { out->err(out, "Could not determine which resources were started"); goto failure; } if (current_active) { g_list_free_full(current_active, free); } /* It's OK if dependent resources moved to a different node, * so we check active resources on all nodes. */ current_active = get_active_resources(NULL, data_set->resources); g_list_free(list_delta); list_delta = pcmk__subtract_lists(target_active, current_active, (GCompareFunc) strcmp); dump_list(current_active, "Current"); dump_list(list_delta, "Delta"); } if(before == g_list_length(list_delta)) { /* aborted during start phase, print the contents of list_delta */ out->err(out, "Could not complete restart of %s, %d resources remaining", rsc_id, g_list_length(list_delta)); display_list(out, list_delta, " * "); rc = ETIME; goto failure; } } rc = pcmk_rc_ok; goto done; failure: if (stop_via_ban) { cli_resource_clear(rsc_id, host, NULL, cib, cib_options, TRUE, force); } else if (orig_target_role) { cli_resource_update_attribute(rsc, rsc_id, NULL, XML_TAG_META_SETS, NULL, XML_RSC_ATTR_TARGET_ROLE, orig_target_role, FALSE, cib, cib_options, data_set, force); free(orig_target_role); } else { cli_resource_delete_attribute(rsc, rsc_id, NULL, XML_TAG_META_SETS, NULL, XML_RSC_ATTR_TARGET_ROLE, cib, cib_options, data_set, force); } done: if (list_delta) { g_list_free(list_delta); } if (current_active) { g_list_free_full(current_active, free); } if (target_active && (target_active != restart_target_active)) { g_list_free_full(target_active, free); } if (restart_target_active) { g_list_free_full(restart_target_active, free); } free(rsc_id); pe_free_working_set(data_set); return rc; } static inline bool action_is_pending(pe_action_t *action) { if (pcmk_any_flags_set(action->flags, pe_action_optional|pe_action_pseudo) || !pcmk_is_set(action->flags, pe_action_runnable) || pcmk__str_eq("notify", action->task, pcmk__str_casei)) { return false; } return true; } /*! * \internal * \brief Return TRUE if any actions in a list are pending * * \param[in] actions List of actions to check * * \return TRUE if any actions in the list are pending, FALSE otherwise */ static bool actions_are_pending(GList *actions) { GList *action; for (action = actions; action != NULL; action = action->next) { pe_action_t *a = (pe_action_t *)action->data; if (action_is_pending(a)) { crm_notice("Waiting for %s (flags=0x%.8x)", a->uuid, a->flags); return TRUE; } } return FALSE; } static void print_pending_actions(pcmk__output_t *out, GList *actions) { GList *action; out->info(out, "Pending actions:"); for (action = actions; action != NULL; action = action->next) { pe_action_t *a = (pe_action_t *) action->data; if (!action_is_pending(a)) { continue; } if (a->node) { out->info(out, "\tAction %d: %s\ton %s", a->id, a->uuid, a->node->details->uname); } else { out->info(out, "\tAction %d: %s", a->id, a->uuid); } } } /* For --wait, timeout (in seconds) to use if caller doesn't specify one */ #define WAIT_DEFAULT_TIMEOUT_S (60 * 60) /* For --wait, how long to sleep between cluster state checks */ #define WAIT_SLEEP_S (2) /*! * \internal * \brief Wait until all pending cluster actions are complete * * This waits until either the CIB's transition graph is idle or a timeout is * reached. * * \param[in] timeout_ms Consider failed if actions do not complete in this time * (specified in milliseconds, but one-second granularity * is actually used; if 0, a default will be used) * \param[in] cib Connection to the CIB manager * * \return Standard Pacemaker return code */ int wait_till_stable(pcmk__output_t *out, int timeout_ms, cib_t * cib) { pe_working_set_t *data_set = NULL; int rc = pcmk_rc_ok; int timeout_s = timeout_ms? ((timeout_ms + 999) / 1000) : WAIT_DEFAULT_TIMEOUT_S; time_t expire_time = time(NULL) + timeout_s; time_t time_diff; bool printed_version_warning = out->is_quiet(out); // i.e. don't print if quiet data_set = pe_new_working_set(); if (data_set == NULL) { return ENOMEM; } pe__set_working_set_flags(data_set, pe_flag_no_counts|pe_flag_no_compat); do { /* Abort if timeout is reached */ time_diff = expire_time - time(NULL); if (time_diff > 0) { crm_info("Waiting up to %ld seconds for cluster actions to complete", time_diff); } else { print_pending_actions(out, data_set->actions); pe_free_working_set(data_set); return ETIME; } if (rc == pcmk_rc_ok) { /* this avoids sleep on first loop iteration */ sleep(WAIT_SLEEP_S); } /* Get latest transition graph */ pe_reset_working_set(data_set); rc = update_working_set_from_cib(out, data_set, cib); if (rc != pcmk_rc_ok) { pe_free_working_set(data_set); return rc; } pcmk__schedule_actions(data_set, data_set->input, NULL); if (!printed_version_warning) { /* If the DC has a different version than the local node, the two * could come to different conclusions about what actions need to be * done. Warn the user in this case. * * @TODO A possible long-term solution would be to reimplement the * wait as a new controller operation that would be forwarded to the * DC. However, that would have potential problems of its own. */ const char *dc_version = g_hash_table_lookup(data_set->config_hash, "dc-version"); if (!pcmk__str_eq(dc_version, PACEMAKER_VERSION "-" BUILD_VERSION, pcmk__str_casei)) { out->info(out, "warning: wait option may not work properly in " "mixed-version cluster"); printed_version_warning = TRUE; } } } while (actions_are_pending(data_set->actions)); pe_free_working_set(data_set); return rc; } crm_exit_t cli_resource_execute_from_params(pcmk__output_t *out, const char *rsc_name, const char *rsc_class, const char *rsc_prov, const char *rsc_type, const char *action, GHashTable *params, GHashTable *override_hash, int timeout_ms, int resource_verbose, gboolean force) { GHashTable *params_copy = NULL; crm_exit_t exit_code = CRM_EX_OK; svc_action_t *op = NULL; if (pcmk__str_eq(rsc_class, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) { out->err(out, "Sorry, the %s option doesn't support %s resources yet", action, rsc_class); crm_exit(CRM_EX_UNIMPLEMENT_FEATURE); } /* If no timeout was provided, grab the default. */ if (timeout_ms == 0) { timeout_ms = crm_get_msec(CRM_DEFAULT_OP_TIMEOUT_S); } /* add meta_timeout env needed by some resource agents */ g_hash_table_insert(params, strdup("CRM_meta_timeout"), crm_strdup_printf("%d", timeout_ms)); /* add crm_feature_set env needed by some resource agents */ g_hash_table_insert(params, strdup(XML_ATTR_CRM_VERSION), strdup(CRM_FEATURE_SET)); /* resources_action_create frees the params hash table it's passed, but we * may need to reuse it in a second call to resources_action_create. Thus * we'll make a copy here so that gets freed and the original remains for * reuse. */ params_copy = pcmk__str_table_dup(params); op = resources_action_create(rsc_name, rsc_class, rsc_prov, rsc_type, action, 0, timeout_ms, params_copy, 0); if (op == NULL) { /* Re-run with stderr enabled so we can display a sane error message */ crm_enable_stderr(TRUE); params_copy = pcmk__str_table_dup(params); op = resources_action_create(rsc_name, rsc_class, rsc_prov, rsc_type, action, 0, timeout_ms, params_copy, 0); /* Callers of cli_resource_execute expect that the params hash table will * be freed. That function uses this one, so for that reason and for * making the two act the same, we should free the hash table here too. */ g_hash_table_destroy(params); /* We know op will be NULL, but this makes static analysis happy */ services_action_free(op); crm_exit(CRM_EX_DATAERR); return exit_code; // Never reached, but helps static analysis } setenv("HA_debug", resource_verbose > 0 ? "1" : "0", 1); if(resource_verbose > 1) { setenv("OCF_TRACE_RA", "1", 1); } /* A resource agent using the standard ocf-shellfuncs library will not print * messages to stderr if it doesn't have a controlling terminal (e.g. if * crm_resource is called via script or ssh). This forces it to do so. */ setenv("OCF_TRACE_FILE", "/dev/stderr", 0); if (override_hash) { GHashTableIter iter; char *name = NULL; char *value = NULL; g_hash_table_iter_init(&iter, override_hash); while (g_hash_table_iter_next(&iter, (gpointer *) & name, (gpointer *) & value)) { out->info(out, "Overriding the cluster configuration for '%s' with '%s' = '%s'", rsc_name, name, value); g_hash_table_replace(op->params, strdup(name), strdup(value)); } } if (services_action_sync(op)) { exit_code = op->rc; if (op->status == PCMK_LRM_OP_DONE) { out->info(out, "Operation %s for %s (%s:%s:%s) returned: '%s' (%d)", action, rsc_name, rsc_class, rsc_prov ? rsc_prov : "", rsc_type, services_ocf_exitcode_str(op->rc), op->rc); } else { out->err(out, "Operation %s for %s (%s:%s:%s) failed: '%s' (%d)", action, rsc_name, rsc_class, rsc_prov ? rsc_prov : "", rsc_type, services_lrm_status_str(op->status), op->status); } /* hide output for validate-all if not in verbose */ if (resource_verbose == 0 && pcmk__str_eq(action, "validate-all", pcmk__str_casei)) goto done; if (op->stdout_data || op->stderr_data) { out->subprocess_output(out, op->rc, op->stdout_data, op->stderr_data); } } else { exit_code = op->rc == 0 ? CRM_EX_ERROR : op->rc; } done: services_action_free(op); /* See comment above about why we free params here. */ g_hash_table_destroy(params); return exit_code; } crm_exit_t cli_resource_execute(pe_resource_t *rsc, const char *requested_name, const char *rsc_action, GHashTable *override_hash, int timeout_ms, cib_t * cib, pe_working_set_t *data_set, int resource_verbose, gboolean force) { pcmk__output_t *out = data_set->priv; crm_exit_t exit_code = CRM_EX_OK; const char *rid = NULL; const char *rtype = NULL; const char *rprov = NULL; const char *rclass = NULL; const char *action = NULL; GHashTable *params = NULL; if (pcmk__str_eq(rsc_action, "validate", pcmk__str_casei)) { action = "validate-all"; } else if (pcmk__str_eq(rsc_action, "force-check", pcmk__str_casei)) { action = "monitor"; } else if (pcmk__str_eq(rsc_action, "force-stop", pcmk__str_casei)) { action = rsc_action+6; } else if (pcmk__strcase_any_of(rsc_action, "force-start", "force-demote", "force-promote", NULL)) { action = rsc_action+6; if(pe_rsc_is_clone(rsc)) { GList *nodes = cli_resource_search(rsc, requested_name, data_set); if(nodes != NULL && force == FALSE) { out->err(out, "It is not safe to %s %s here: the cluster claims it is already active", action, rsc->id); out->err(out, "Try setting target-role=Stopped first or specifying " "the force option"); return CRM_EX_UNSAFE; } g_list_free_full(nodes, free); } } else { action = rsc_action; } if(pe_rsc_is_clone(rsc)) { /* Grab the first child resource in the hope it's not a group */ rsc = rsc->children->data; } if(rsc->variant == pe_group) { out->err(out, "Sorry, the %s option doesn't support group resources", rsc_action); return CRM_EX_UNIMPLEMENT_FEATURE; } rclass = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); rprov = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER); rtype = crm_element_value(rsc->xml, XML_ATTR_TYPE); params = generate_resource_params(rsc, NULL /* @TODO use local node */, data_set); if (timeout_ms == 0) { timeout_ms = pe_get_configured_timeout(rsc, action, data_set); } rid = pe_rsc_is_anon_clone(rsc->parent)? requested_name : rsc->id; exit_code = cli_resource_execute_from_params(out, rid, rclass, rprov, rtype, action, params, override_hash, timeout_ms, resource_verbose, force); return exit_code; } // \return Standard Pacemaker return code int cli_resource_move(pe_resource_t *rsc, const char *rsc_id, const char *host_name, const char *move_lifetime, cib_t *cib, int cib_options, pe_working_set_t *data_set, gboolean promoted_role_only, gboolean force) { pcmk__output_t *out = data_set->priv; int rc = pcmk_rc_ok; unsigned int count = 0; pe_node_t *current = NULL; pe_node_t *dest = pe_find_node(data_set->nodes, host_name); bool cur_is_dest = FALSE; if (dest == NULL) { return pcmk_rc_node_unknown; } if (promoted_role_only && !pcmk_is_set(rsc->flags, pe_rsc_promotable)) { pe_resource_t *p = uber_parent(rsc); if (pcmk_is_set(p->flags, pe_rsc_promotable)) { out->info(out, "Using parent '%s' for move instead of '%s'.", rsc->id, rsc_id); rsc_id = p->id; rsc = p; } else { out->info(out, "Ignoring master option: %s is not promotable", rsc_id); promoted_role_only = FALSE; } } current = pe__find_active_requires(rsc, &count); if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) { GList *iter = NULL; unsigned int promoted_count = 0; pe_node_t *promoted_node = NULL; for(iter = rsc->children; iter; iter = iter->next) { pe_resource_t *child = (pe_resource_t *)iter->data; enum rsc_role_e child_role = child->fns->state(child, TRUE); if (child_role == RSC_ROLE_PROMOTED) { rsc = child; promoted_node = pe__current_node(child); promoted_count++; } } if (promoted_role_only || (promoted_count != 0)) { count = promoted_count; current = promoted_node; } } if (count > 1) { if (pe_rsc_is_clone(rsc)) { current = NULL; } else { return pcmk_rc_multiple; } } if (current && (current->details == dest->details)) { cur_is_dest = TRUE; if (force) { crm_info("%s is already %s on %s, reinforcing placement with location constraint.", rsc_id, promoted_role_only?"promoted":"active", dest->details->uname); } else { return pcmk_rc_already; } } /* Clear any previous prefer constraints across all nodes. */ cli_resource_clear(rsc_id, NULL, data_set->nodes, cib, cib_options, FALSE, force); /* Clear any previous ban constraints on 'dest'. */ cli_resource_clear(rsc_id, dest->details->uname, data_set->nodes, cib, cib_options, TRUE, force); /* Record an explicit preference for 'dest' */ rc = cli_resource_prefer(out, rsc_id, dest->details->uname, move_lifetime, cib, cib_options, promoted_role_only); crm_trace("%s%s now prefers node %s%s", rsc->id, (promoted_role_only? " (promoted)" : ""), dest->details->uname, force?"(forced)":""); /* only ban the previous location if current location != destination location. * it is possible to use -M to enforce a location without regard of where the * resource is currently located */ if(force && (cur_is_dest == FALSE)) { /* Ban the original location if possible */ if(current) { (void)cli_resource_ban(out, rsc_id, current->details->uname, move_lifetime, NULL, cib, cib_options, promoted_role_only); } else if(count > 1) { out->info(out, "Resource '%s' is currently %s in %d locations. " "One may now move to %s", rsc_id, (promoted_role_only? "promoted" : "active"), count, dest->details->uname); out->info(out, "To prevent '%s' from being %s at a specific location, " "specify a node.", rsc_id, (promoted_role_only? "promoted" : "active")); } else { crm_trace("Not banning %s from its current location: not active", rsc_id); } } return rc; }