diff --git a/daemons/execd/cts-exec-helper.c b/daemons/execd/cts-exec-helper.c index 5a30594c5a..b7f95ec8cd 100644 --- a/daemons/execd/cts-exec-helper.c +++ b/daemons/execd/cts-exec-helper.c @@ -1,624 +1,625 @@ /* * Copyright 2012-2023 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 #include #include #include #include #define SUMMARY "cts-exec-helper - inject commands into the Pacemaker executor and watch for events" static int exec_call_id = 0; static gboolean start_test(gpointer user_data); static void try_connect(void); static char *key = NULL; static char *val = NULL; static struct { int verbose; int quiet; guint interval_ms; int timeout; int start_delay; int cancel_call_id; gboolean no_wait; gboolean is_running; gboolean no_connect; int exec_call_opts; const char *api_call; const char *rsc_id; const char *provider; const char *class; const char *type; const char *action; const char *listen; gboolean use_tls; lrmd_key_value_t *params; } options; static gboolean interval_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.interval_ms = crm_parse_interval_spec(optarg); return errno == 0; } static gboolean notify_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (pcmk__str_any_of(option_name, "--notify-orig", "-n", NULL)) { options.exec_call_opts = lrmd_opt_notify_orig_only; } else if (pcmk__str_any_of(option_name, "--notify-changes", "-o", NULL)) { options.exec_call_opts = lrmd_opt_notify_changes_only; } return TRUE; } static gboolean param_key_val_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (pcmk__str_any_of(option_name, "--param-key", "-k", NULL)) { pcmk__str_update(&key, optarg); } else if (pcmk__str_any_of(option_name, "--param-val", "-v", NULL)) { pcmk__str_update(&val, optarg); } if (key != NULL && val != NULL) { options.params = lrmd_key_value_add(options.params, key, val); pcmk__str_update(&key, NULL); pcmk__str_update(&val, NULL); } return TRUE; } static GOptionEntry basic_entries[] = { { "api-call", 'c', 0, G_OPTION_ARG_STRING, &options.api_call, "Directly relates to executor API functions", NULL }, { "is-running", 'R', 0, G_OPTION_ARG_NONE, &options.is_running, "Determine if a resource is registered and running", NULL }, { "listen", 'l', 0, G_OPTION_ARG_STRING, &options.listen, "Listen for a specific event string", NULL }, { "no-wait", 'w', 0, G_OPTION_ARG_NONE, &options.no_wait, "Make api call and do not wait for result", NULL }, { "notify-changes", 'o', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, notify_cb, "Only notify client changes to recurring operations", NULL }, { "notify-orig", 'n', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, notify_cb, "Only notify this client of the results of an API action", NULL }, { "tls", 'S', 0, G_OPTION_ARG_NONE, &options.use_tls, "Use TLS backend for local connection", NULL }, { NULL } }; static GOptionEntry api_call_entries[] = { { "action", 'a', 0, G_OPTION_ARG_STRING, &options.action, NULL, NULL }, { "cancel-call-id", 'x', 0, G_OPTION_ARG_INT, &options.cancel_call_id, NULL, NULL }, { "class", 'C', 0, G_OPTION_ARG_STRING, &options.class, NULL, NULL }, { "interval", 'i', 0, G_OPTION_ARG_CALLBACK, interval_cb, NULL, NULL }, { "param-key", 'k', 0, G_OPTION_ARG_CALLBACK, param_key_val_cb, NULL, NULL }, { "param-val", 'v', 0, G_OPTION_ARG_CALLBACK, param_key_val_cb, NULL, NULL }, { "provider", 'P', 0, G_OPTION_ARG_STRING, &options.provider, NULL, NULL }, { "rsc-id", 'r', 0, G_OPTION_ARG_STRING, &options.rsc_id, NULL, NULL }, { "start-delay", 's', 0, G_OPTION_ARG_INT, &options.start_delay, NULL, NULL }, { "timeout", 't', 0, G_OPTION_ARG_INT, &options.timeout, NULL, NULL }, { "type", 'T', 0, G_OPTION_ARG_STRING, &options.type, NULL, NULL }, { NULL } }; static GMainLoop *mainloop = NULL; static lrmd_t *lrmd_conn = NULL; static char event_buf_v0[1024]; static crm_exit_t test_exit(crm_exit_t exit_code) { lrmd_api_delete(lrmd_conn); return crm_exit(exit_code); } #define print_result(fmt, args...) \ if (!options.quiet) { \ printf(fmt "\n" , ##args); \ } #define report_event(event) \ snprintf(event_buf_v0, sizeof(event_buf_v0), "NEW_EVENT event_type:%s rsc_id:%s action:%s rc:%s op_status:%s", \ lrmd_event_type2str(event->type), \ event->rsc_id, \ event->op_type ? event->op_type : "none", \ services_ocf_exitcode_str(event->rc), \ pcmk_exec_status_str(event->op_status)); \ crm_info("%s", event_buf_v0); static void test_shutdown(int nsig) { lrmd_api_delete(lrmd_conn); lrmd_conn = NULL; } static void read_events(lrmd_event_data_t * event) { report_event(event); if (options.listen) { if (pcmk__str_eq(options.listen, event_buf_v0, pcmk__str_casei)) { print_result("LISTEN EVENT SUCCESSFUL"); test_exit(CRM_EX_OK); } } if (exec_call_id && (event->call_id == exec_call_id)) { if (event->op_status == 0 && event->rc == 0) { print_result("API-CALL SUCCESSFUL for 'exec'"); } else { print_result("API-CALL FAILURE for 'exec', rc:%d lrmd_op_status:%s", event->rc, pcmk_exec_status_str(event->op_status)); test_exit(CRM_EX_ERROR); } if (!options.listen) { test_exit(CRM_EX_OK); } } } static gboolean timeout_err(gpointer data) { print_result("LISTEN EVENT FAILURE - timeout occurred, never found"); test_exit(CRM_EX_TIMEOUT); return FALSE; } static void connection_events(lrmd_event_data_t * event) { int rc = event->connection_rc; if (event->type != lrmd_event_connect) { /* ignore */ return; } if (!rc) { crm_info("Executor client connection established"); start_test(NULL); return; } else { sleep(1); try_connect(); crm_notice("Executor client connection failed"); } } static void try_connect(void) { int tries = 10; static int num_tries = 0; int rc = 0; lrmd_conn->cmds->set_callback(lrmd_conn, connection_events); for (; num_tries < tries; num_tries++) { rc = lrmd_conn->cmds->connect_async(lrmd_conn, crm_system_name, 3000); if (!rc) { return; /* we'll hear back in async callback */ } sleep(1); } print_result("API CONNECTION FAILURE"); test_exit(CRM_EX_ERROR); } static gboolean start_test(gpointer user_data) { int rc = 0; if (!options.no_connect) { if (!lrmd_conn->cmds->is_connected(lrmd_conn)) { try_connect(); /* async connect -- this function will get called back into */ return 0; } } lrmd_conn->cmds->set_callback(lrmd_conn, read_events); if (options.timeout) { g_timeout_add(options.timeout, timeout_err, NULL); } if (!options.api_call) { return 0; } if (pcmk__str_eq(options.api_call, "exec", pcmk__str_casei)) { rc = lrmd_conn->cmds->exec(lrmd_conn, options.rsc_id, options.action, NULL, options.interval_ms, options.timeout, options.start_delay, options.exec_call_opts, options.params); if (rc > 0) { exec_call_id = rc; print_result("API-CALL 'exec' action pending, waiting on response"); } } else if (pcmk__str_eq(options.api_call, "register_rsc", pcmk__str_casei)) { rc = lrmd_conn->cmds->register_rsc(lrmd_conn, options.rsc_id, options.class, options.provider, options.type, 0); } else if (pcmk__str_eq(options.api_call, "get_rsc_info", pcmk__str_casei)) { lrmd_rsc_info_t *rsc_info; rsc_info = lrmd_conn->cmds->get_rsc_info(lrmd_conn, options.rsc_id, 0); if (rsc_info) { print_result("RSC_INFO: id:%s class:%s provider:%s type:%s", rsc_info->id, rsc_info->standard, (rsc_info->provider? rsc_info->provider : ""), rsc_info->type); lrmd_free_rsc_info(rsc_info); rc = pcmk_ok; } else { rc = -1; } } else if (pcmk__str_eq(options.api_call, "unregister_rsc", pcmk__str_casei)) { rc = lrmd_conn->cmds->unregister_rsc(lrmd_conn, options.rsc_id, 0); } else if (pcmk__str_eq(options.api_call, "cancel", pcmk__str_casei)) { rc = lrmd_conn->cmds->cancel(lrmd_conn, options.rsc_id, options.action, options.interval_ms); } else if (pcmk__str_eq(options.api_call, "metadata", pcmk__str_casei)) { char *output = NULL; rc = lrmd_conn->cmds->get_metadata(lrmd_conn, options.class, options.provider, options.type, &output, 0); if (rc == pcmk_ok) { print_result("%s", output); free(output); } } else if (pcmk__str_eq(options.api_call, "list_agents", pcmk__str_casei)) { lrmd_list_t *list = NULL; lrmd_list_t *iter = NULL; rc = lrmd_conn->cmds->list_agents(lrmd_conn, &list, options.class, options.provider); if (rc > 0) { print_result("%d agents found", rc); for (iter = list; iter != NULL; iter = iter->next) { print_result("%s", iter->val); } lrmd_list_freeall(list); rc = 0; } else { print_result("API_CALL FAILURE - no agents found"); rc = -1; } } else if (pcmk__str_eq(options.api_call, "list_ocf_providers", pcmk__str_casei)) { lrmd_list_t *list = NULL; lrmd_list_t *iter = NULL; rc = lrmd_conn->cmds->list_ocf_providers(lrmd_conn, options.type, &list); if (rc > 0) { print_result("%d providers found", rc); for (iter = list; iter != NULL; iter = iter->next) { print_result("%s", iter->val); } lrmd_list_freeall(list); rc = 0; } else { print_result("API_CALL FAILURE - no providers found"); rc = -1; } } else if (pcmk__str_eq(options.api_call, "list_standards", pcmk__str_casei)) { lrmd_list_t *list = NULL; lrmd_list_t *iter = NULL; rc = lrmd_conn->cmds->list_standards(lrmd_conn, &list); if (rc > 0) { print_result("%d standards found", rc); for (iter = list; iter != NULL; iter = iter->next) { print_result("%s", iter->val); } lrmd_list_freeall(list); rc = 0; } else { print_result("API_CALL FAILURE - no providers found"); rc = -1; } } else if (pcmk__str_eq(options.api_call, "get_recurring_ops", pcmk__str_casei)) { GList *op_list = NULL; GList *op_item = NULL; rc = lrmd_conn->cmds->get_recurring_ops(lrmd_conn, options.rsc_id, 0, 0, &op_list); for (op_item = op_list; op_item != NULL; op_item = op_item->next) { lrmd_op_info_t *op_info = op_item->data; print_result("RECURRING_OP: %s_%s_%s timeout=%sms", op_info->rsc_id, op_info->action, op_info->interval_ms_s, op_info->timeout_ms_s); lrmd_free_op_info(op_info); } g_list_free(op_list); } else if (options.api_call) { print_result("API-CALL FAILURE unknown action '%s'", options.action); test_exit(CRM_EX_ERROR); } if (rc < 0) { print_result("API-CALL FAILURE for '%s' api_rc:%d", options.api_call, rc); test_exit(CRM_EX_ERROR); } if (options.api_call && rc == pcmk_ok) { print_result("API-CALL SUCCESSFUL for '%s'", options.api_call); if (!options.listen) { test_exit(CRM_EX_OK); } } if (options.no_wait) { /* just make the call and exit regardless of anything else. */ test_exit(CRM_EX_OK); } return 0; } /*! * \internal * \brief Generate resource parameters from CIB if none explicitly given * * \return Standard Pacemaker return code */ static int generate_params(void) { int rc = pcmk_rc_ok; pe_working_set_t *data_set = NULL; xmlNode *cib_xml_copy = NULL; pe_resource_t *rsc = NULL; GHashTable *params = NULL; GHashTable *meta = NULL; GHashTableIter iter; char *key = NULL; char *value = NULL; if (options.params != NULL) { return pcmk_rc_ok; // User specified parameters explicitly } // Retrieve and update CIB rc = cib__signon_query(NULL, NULL, &cib_xml_copy); if (rc != pcmk_rc_ok) { return rc; } if (!cli_config_update(&cib_xml_copy, NULL, FALSE)) { crm_err("Could not update CIB"); return pcmk_rc_cib_corrupt; } // Calculate cluster status data_set = pe_new_working_set(); if (data_set == NULL) { crm_crit("Could not allocate working set"); return ENOMEM; } pe__set_working_set_flags(data_set, pe_flag_no_counts|pe_flag_no_compat); data_set->input = cib_xml_copy; data_set->now = crm_time_new(NULL); cluster_status(data_set); // Find resource in CIB rsc = pe_find_resource_with_flags(data_set->resources, options.rsc_id, - pcmk_rsc_match_history|pe_find_any); + pcmk_rsc_match_history + |pcmk_rsc_match_basename); if (rsc == NULL) { crm_err("Resource does not exist in config"); pe_free_working_set(data_set); return EINVAL; } // Add resource instance parameters to options.params params = pe_rsc_params(rsc, NULL, data_set); if (params != NULL) { g_hash_table_iter_init(&iter, params); while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value)) { options.params = lrmd_key_value_add(options.params, key, value); } } // Add resource meta-attributes to options.params meta = pcmk__strkey_table(free, free); get_meta_attributes(meta, rsc, NULL, data_set); 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); options.params = lrmd_key_value_add(options.params, crm_name, value); free(crm_name); } g_hash_table_destroy(meta); pe_free_working_set(data_set); return rc; } static GOptionContext * build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) { GOptionContext *context = NULL; context = pcmk__build_arg_context(args, NULL, group, NULL); pcmk__add_main_args(context, basic_entries); pcmk__add_arg_group(context, "api-call", "API Call Options:", "Parameters for api-call option", api_call_entries); return context; } int main(int argc, char **argv) { GError *error = NULL; crm_exit_t exit_code = CRM_EX_OK; crm_trigger_t *trig = NULL; pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); /* Typically we'd pass all the single character options that take an argument * as the second parameter here (and there's a bunch of those in this tool). * However, we control how this program is called so we can just not call it * in a way where the preprocessing ever matters. */ gchar **processed_args = pcmk__cmdline_preproc(argv, NULL); GOptionContext *context = build_arg_context(args, NULL); if (!g_option_context_parse_strv(context, &processed_args, &error)) { exit_code = CRM_EX_USAGE; goto done; } /* We have to use crm_log_init here to set up the logging because there's * different handling for daemons vs. command line programs, and * pcmk__cli_init_logging is set up to only handle the latter. */ crm_log_init(NULL, LOG_INFO, TRUE, (args->verbosity? TRUE : FALSE), argc, argv, FALSE); for (int i = 0; i < args->verbosity; i++) { crm_bump_log_level(argc, argv); } if (!options.listen && pcmk__strcase_any_of(options.api_call, "metadata", "list_agents", "list_standards", "list_ocf_providers", NULL)) { options.no_connect = TRUE; } if (options.is_running) { int rc = pcmk_rc_ok; if (options.rsc_id == NULL) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "--is-running requires --rsc-id"); goto done; } options.interval_ms = 0; if (options.timeout == 0) { options.timeout = 30000; } rc = generate_params(); if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Can not determine resource status: " "unable to get parameters from CIB"); goto done; } options.api_call = "exec"; options.action = PCMK_ACTION_MONITOR; options.exec_call_opts = lrmd_opt_notify_orig_only; } if (!options.api_call && !options.listen) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Must specify at least one of --api-call, --listen, " "or --is-running"); goto done; } if (options.use_tls) { lrmd_conn = lrmd_remote_api_new(NULL, "localhost", 0); } else { lrmd_conn = lrmd_api_new(); } trig = mainloop_add_trigger(G_PRIORITY_HIGH, start_test, NULL); mainloop_set_trigger(trig); mainloop_add_signal(SIGTERM, test_shutdown); crm_info("Starting"); mainloop = g_main_loop_new(NULL, FALSE); g_main_loop_run(mainloop); done: g_strfreev(processed_args); pcmk__free_arg_context(context); free(key); free(val); pcmk__output_and_clear_error(&error, NULL); return test_exit(exit_code); } diff --git a/include/crm/common/resources.h b/include/crm/common/resources.h index bbc9f5eddb..bb0ceb3998 100644 --- a/include/crm/common/resources.h +++ b/include/crm/common/resources.h @@ -1,154 +1,157 @@ /* * Copyright 2004-2023 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 PCMK__CRM_COMMON_RESOURCES__H # define PCMK__CRM_COMMON_RESOURCES__H #ifdef __cplusplus extern "C" { #endif /*! * \file * \brief Scheduler API for resources * \ingroup core */ //! Resource variants supported by Pacemaker enum pe_obj_types { // Order matters: some code compares greater or lesser than pcmk_rsc_variant_unknown = -1, //!< Unknown resource variant pcmk_rsc_variant_primitive = 0, //!< Primitive resource pcmk_rsc_variant_group = 1, //!< Group resource pcmk_rsc_variant_clone = 2, //!< Clone resource pcmk_rsc_variant_bundle = 3, //!< Bundle resource #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) //! \deprecated Use pcmk_rsc_variant_unknown instead pe_unknown = pcmk_rsc_variant_unknown, //! \deprecated Use pcmk_rsc_variant_primitive instead pe_native = pcmk_rsc_variant_primitive, //! \deprecated Use pcmk_rsc_variant_group instead pe_group = pcmk_rsc_variant_group, //! \deprecated Use pcmk_rsc_variant_clone instead pe_clone = pcmk_rsc_variant_clone, //! \deprecated Use pcmk_rsc_variant_bundle instead pe_container = pcmk_rsc_variant_bundle, #endif }; //! What resource needs before it can be recovered from a failed node enum rsc_start_requirement { pcmk_requires_nothing = 0, //!< Resource can be recovered immediately pcmk_requires_quorum = 1, //!< Resource can be recovered if quorate pcmk_requires_fencing = 2, //!< Resource can be recovered after fencing #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) //! \deprecated Use pcmk_requires_nothing instead rsc_req_nothing = pcmk_requires_nothing, //! \deprecated Use pcmk_requires_quorum instead rsc_req_quorum = pcmk_requires_quorum, //! \deprecated Use pcmk_requires_fencing instead rsc_req_stonith = pcmk_requires_fencing, #endif }; //! How to recover a resource that is incorrectly active on multiple nodes enum rsc_recovery_type { pcmk_multiply_active_restart = 0, //!< Stop on all, start on desired pcmk_multiply_active_stop = 1, //!< Stop on all and leave stopped pcmk_multiply_active_block = 2, //!< Do nothing to resource pcmk_multiply_active_unexpected = 3, //!< Stop unexpected instances #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) //! \deprecated Use pcmk_multiply_active_restart instead recovery_stop_start = pcmk_multiply_active_restart, //! \deprecated Use pcmk_multiply_active_stop instead recovery_stop_only = pcmk_multiply_active_stop, //! \deprecated Use pcmk_multiply_active_block instead recovery_block = pcmk_multiply_active_block, //! \deprecated Use pcmk_multiply_active_unexpected instead recovery_stop_unexpected = pcmk_multiply_active_unexpected, #endif }; //! Search options for resources (exact resource ID always matches) enum pe_find { //! Also match clone instance ID from resource history pcmk_rsc_match_history = (1 << 0), //! Also match anonymous clone instances by base name pcmk_rsc_match_anon_basename = (1 << 1), //! Match only clones and their instances, by either clone or instance ID pcmk_rsc_match_clone_only = (1 << 2), //! If matching by node, compare current node instead of assigned node pcmk_rsc_match_current_node = (1 << 3), //! \deprecated Do not use pe_find_inactive = (1 << 4), + //! Match clone instances (even unique) by base name as well as exact ID + pcmk_rsc_match_basename = (1 << 5), + #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) //! \deprecated Use pcmk_rsc_match_history instead pe_find_renamed = pcmk_rsc_match_history, //! \deprecated Use pcmk_rsc_match_anon_basename instead pe_find_anon = pcmk_rsc_match_anon_basename, //! \deprecated Use pcmk_rsc_match_clone_only instead pe_find_clone = pcmk_rsc_match_clone_only, //! \deprecated Use pcmk_rsc_match_current_node instead pe_find_current = pcmk_rsc_match_current_node, #endif - pe_find_any = 0x020, //!< match base name of any clone instance + pe_find_any = pcmk_rsc_match_basename, }; //!@{ //! \deprecated Do not use enum pe_restart { pe_restart_restart, pe_restart_ignore, }; enum pe_print_options { pe_print_log = (1 << 0), pe_print_html = (1 << 1), pe_print_ncurses = (1 << 2), pe_print_printf = (1 << 3), pe_print_dev = (1 << 4), // Ignored pe_print_details = (1 << 5), // Ignored pe_print_max_details = (1 << 6), // Ignored pe_print_rsconly = (1 << 7), pe_print_ops = (1 << 8), pe_print_suppres_nl = (1 << 9), pe_print_xml = (1 << 10), pe_print_brief = (1 << 11), pe_print_pending = (1 << 12), pe_print_clone_details = (1 << 13), pe_print_clone_active = (1 << 14), // Print clone instances only if active pe_print_implicit = (1 << 15) // Print implicitly created resources }; //!@} #ifdef __cplusplus } #endif #endif // PCMK__CRM_COMMON_RESOURCES__H diff --git a/lib/pengine/native.c b/lib/pengine/native.c index 636b2df56a..31c090a849 100644 --- a/lib/pengine/native.c +++ b/lib/pengine/native.c @@ -1,1442 +1,1442 @@ /* * Copyright 2004-2023 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 #include #ifdef PCMK__COMPAT_2_0 #define PROVIDER_SEP "::" #else #define PROVIDER_SEP ":" #endif /*! * \internal * \brief Check whether a resource is active on multiple nodes */ static bool is_multiply_active(const pe_resource_t *rsc) { unsigned int count = 0; if (rsc->variant == pcmk_rsc_variant_primitive) { pe__find_active_requires(rsc, &count); } return count > 1; } static void native_priority_to_node(pe_resource_t * rsc, pe_node_t * node, gboolean failed) { int priority = 0; if ((rsc->priority == 0) || (failed == TRUE)) { return; } if (rsc->role == pcmk_role_promoted) { // Promoted instance takes base priority + 1 priority = rsc->priority + 1; } else { priority = rsc->priority; } node->details->priority += priority; pe_rsc_trace(rsc, "%s now has priority %d with %s'%s' (priority: %d%s)", pe__node_name(node), node->details->priority, (rsc->role == pcmk_role_promoted)? "promoted " : "", rsc->id, rsc->priority, (rsc->role == pcmk_role_promoted)? " + 1" : ""); /* Priority of a resource running on a guest node is added to the cluster * node as well. */ if (node->details->remote_rsc && node->details->remote_rsc->container) { GList *gIter = node->details->remote_rsc->container->running_on; for (; gIter != NULL; gIter = gIter->next) { pe_node_t *a_node = gIter->data; a_node->details->priority += priority; pe_rsc_trace(rsc, "%s now has priority %d with %s'%s' (priority: %d%s) " "from guest node %s", pe__node_name(a_node), a_node->details->priority, (rsc->role == pcmk_role_promoted)? "promoted " : "", rsc->id, rsc->priority, (rsc->role == pcmk_role_promoted)? " + 1" : "", pe__node_name(node)); } } } void native_add_running(pe_resource_t * rsc, pe_node_t * node, pe_working_set_t * data_set, gboolean failed) { GList *gIter = rsc->running_on; CRM_CHECK(node != NULL, return); for (; gIter != NULL; gIter = gIter->next) { pe_node_t *a_node = (pe_node_t *) gIter->data; CRM_CHECK(a_node != NULL, return); if (pcmk__str_eq(a_node->details->id, node->details->id, pcmk__str_casei)) { return; } } pe_rsc_trace(rsc, "Adding %s to %s %s", rsc->id, pe__node_name(node), pcmk_is_set(rsc->flags, pe_rsc_managed)? "" : "(unmanaged)"); rsc->running_on = g_list_append(rsc->running_on, node); if (rsc->variant == pcmk_rsc_variant_primitive) { node->details->running_rsc = g_list_append(node->details->running_rsc, rsc); native_priority_to_node(rsc, node, failed); } if ((rsc->variant == pcmk_rsc_variant_primitive) && node->details->maintenance) { pe__clear_resource_flags(rsc, pe_rsc_managed); pe__set_resource_flags(rsc, pe_rsc_maintenance); } if (!pcmk_is_set(rsc->flags, pe_rsc_managed)) { pe_resource_t *p = rsc->parent; pe_rsc_info(rsc, "resource %s isn't managed", rsc->id); resource_location(rsc, node, INFINITY, "not_managed_default", data_set); while(p && node->details->online) { /* add without the additional location constraint */ p->running_on = g_list_append(p->running_on, node); p = p->parent; } return; } if (is_multiply_active(rsc)) { switch (rsc->recovery_type) { case pcmk_multiply_active_stop: { GHashTableIter gIter; pe_node_t *local_node = NULL; /* make sure it doesn't come up again */ if (rsc->allowed_nodes != NULL) { g_hash_table_destroy(rsc->allowed_nodes); } rsc->allowed_nodes = pe__node_list2table(data_set->nodes); g_hash_table_iter_init(&gIter, rsc->allowed_nodes); while (g_hash_table_iter_next(&gIter, NULL, (void **)&local_node)) { local_node->weight = -INFINITY; } } break; case pcmk_multiply_active_block: pe__clear_resource_flags(rsc, pe_rsc_managed); pe__set_resource_flags(rsc, pe_rsc_block); /* If the resource belongs to a group or bundle configured with * multiple-active=block, block the entire entity. */ if (rsc->parent && ((rsc->parent->variant == pcmk_rsc_variant_group) || (rsc->parent->variant == pcmk_rsc_variant_bundle)) && (rsc->parent->recovery_type == pcmk_multiply_active_block)) { GList *gIter = rsc->parent->children; for (; gIter != NULL; gIter = gIter->next) { pe_resource_t *child = (pe_resource_t *) gIter->data; pe__clear_resource_flags(child, pe_rsc_managed); pe__set_resource_flags(child, pe_rsc_block); } } break; // pcmk_multiply_active_restart, pcmk_multiply_active_unexpected default: /* The scheduler will do the right thing because the relevant * variables and flags are set when unpacking the history. */ break; } crm_debug("%s is active on multiple nodes including %s: %s", rsc->id, pe__node_name(node), recovery2text(rsc->recovery_type)); } else { pe_rsc_trace(rsc, "Resource %s is active on %s", rsc->id, pe__node_name(node)); } if (rsc->parent != NULL) { native_add_running(rsc->parent, node, data_set, FALSE); } } static void recursive_clear_unique(pe_resource_t *rsc, gpointer user_data) { pe__clear_resource_flags(rsc, pe_rsc_unique); add_hash_param(rsc->meta, XML_RSC_ATTR_UNIQUE, XML_BOOLEAN_FALSE); g_list_foreach(rsc->children, (GFunc) recursive_clear_unique, NULL); } gboolean native_unpack(pe_resource_t * rsc, pe_working_set_t * data_set) { pe_resource_t *parent = uber_parent(rsc); const char *standard = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); uint32_t ra_caps = pcmk_get_ra_caps(standard); pe_rsc_trace(rsc, "Processing resource %s...", rsc->id); // Only some agent standards support unique and promotable clones if (!pcmk_is_set(ra_caps, pcmk_ra_cap_unique) && pcmk_is_set(rsc->flags, pe_rsc_unique) && pe_rsc_is_clone(parent)) { /* @COMPAT We should probably reject this situation as an error (as we * do for promotable below) rather than warn and convert, but that would * be a backward-incompatible change that we should probably do with a * transform at a schema major version bump. */ pe__force_anon(standard, parent, rsc->id, data_set); /* Clear globally-unique on the parent and all its descendants unpacked * so far (clearing the parent should make any future children unpacking * correct). We have to clear this resource explicitly because it isn't * hooked into the parent's children yet. */ recursive_clear_unique(parent, NULL); recursive_clear_unique(rsc, NULL); } if (!pcmk_is_set(ra_caps, pcmk_ra_cap_promotable) && pcmk_is_set(parent->flags, pe_rsc_promotable)) { pe_err("Resource %s is of type %s and therefore " "cannot be used as a promotable clone resource", rsc->id, standard); return FALSE; } return TRUE; } static bool rsc_is_on_node(pe_resource_t *rsc, const pe_node_t *node, int flags) { pe_rsc_trace(rsc, "Checking whether %s is on %s", rsc->id, pe__node_name(node)); if (pcmk_is_set(flags, pcmk_rsc_match_current_node) && (rsc->running_on != NULL)) { for (GList *iter = rsc->running_on; iter; iter = iter->next) { pe_node_t *loc = (pe_node_t *) iter->data; if (loc->details == node->details) { return true; } } } else if (pcmk_is_set(flags, pe_find_inactive) // @COMPAT deprecated && (rsc->running_on == NULL)) { return true; } else if (!pcmk_is_set(flags, pcmk_rsc_match_current_node) && (rsc->allocated_to != NULL) && (rsc->allocated_to->details == node->details)) { return true; } return false; } pe_resource_t * native_find_rsc(pe_resource_t * rsc, const char *id, const pe_node_t *on_node, int flags) { bool match = false; pe_resource_t *result = NULL; CRM_CHECK(id && rsc && rsc->id, return NULL); if (pcmk_is_set(flags, pcmk_rsc_match_clone_only)) { const char *rid = ID(rsc->xml); if (!pe_rsc_is_clone(pe__const_top_resource(rsc, false))) { match = false; } else if (!strcmp(id, rsc->id) || pcmk__str_eq(id, rid, pcmk__str_none)) { match = true; } } else if (!strcmp(id, rsc->id)) { match = true; } else if (pcmk_is_set(flags, pcmk_rsc_match_history) && rsc->clone_name && strcmp(rsc->clone_name, id) == 0) { match = true; - } else if (pcmk_is_set(flags, pe_find_any) + } else if (pcmk_is_set(flags, pcmk_rsc_match_basename) || (pcmk_is_set(flags, pcmk_rsc_match_anon_basename) && !pcmk_is_set(rsc->flags, pe_rsc_unique))) { match = pe_base_name_eq(rsc, id); } if (match && on_node) { if (!rsc_is_on_node(rsc, on_node, flags)) { match = false; } } if (match) { return rsc; } for (GList *gIter = rsc->children; gIter != NULL; gIter = gIter->next) { pe_resource_t *child = (pe_resource_t *) gIter->data; result = rsc->fns->find_rsc(child, id, on_node, flags); if (result) { return result; } } return NULL; } // create is ignored char * native_parameter(pe_resource_t * rsc, pe_node_t * node, gboolean create, const char *name, pe_working_set_t * data_set) { char *value_copy = NULL; const char *value = NULL; GHashTable *params = NULL; CRM_CHECK(rsc != NULL, return NULL); CRM_CHECK(name != NULL && strlen(name) != 0, return NULL); pe_rsc_trace(rsc, "Looking up %s in %s", name, rsc->id); params = pe_rsc_params(rsc, node, data_set); value = g_hash_table_lookup(params, name); if (value == NULL) { /* try meta attributes instead */ value = g_hash_table_lookup(rsc->meta, name); } pcmk__str_update(&value_copy, value); return value_copy; } gboolean native_active(pe_resource_t * rsc, gboolean all) { for (GList *gIter = rsc->running_on; gIter != NULL; gIter = gIter->next) { pe_node_t *a_node = (pe_node_t *) gIter->data; if (a_node->details->unclean) { pe_rsc_trace(rsc, "Resource %s: %s is unclean", rsc->id, pe__node_name(a_node)); return TRUE; } else if (a_node->details->online == FALSE && pcmk_is_set(rsc->flags, pe_rsc_managed)) { pe_rsc_trace(rsc, "Resource %s: %s is offline", rsc->id, pe__node_name(a_node)); } else { pe_rsc_trace(rsc, "Resource %s active on %s", rsc->id, pe__node_name(a_node)); return TRUE; } } return FALSE; } struct print_data_s { long options; void *print_data; }; static const char * native_pending_state(const pe_resource_t *rsc) { const char *pending_state = NULL; if (pcmk__str_eq(rsc->pending_task, PCMK_ACTION_START, pcmk__str_casei)) { pending_state = "Starting"; } else if (pcmk__str_eq(rsc->pending_task, PCMK_ACTION_STOP, pcmk__str_casei)) { pending_state = "Stopping"; } else if (pcmk__str_eq(rsc->pending_task, PCMK_ACTION_MIGRATE_TO, pcmk__str_casei)) { pending_state = "Migrating"; } else if (pcmk__str_eq(rsc->pending_task, PCMK_ACTION_MIGRATE_FROM, pcmk__str_casei)) { /* Work might be done in here. */ pending_state = "Migrating"; } else if (pcmk__str_eq(rsc->pending_task, PCMK_ACTION_PROMOTE, pcmk__str_casei)) { pending_state = "Promoting"; } else if (pcmk__str_eq(rsc->pending_task, PCMK_ACTION_DEMOTE, pcmk__str_casei)) { pending_state = "Demoting"; } return pending_state; } static const char * native_pending_task(const pe_resource_t *rsc) { const char *pending_task = NULL; if (pcmk__str_eq(rsc->pending_task, PCMK_ACTION_MONITOR, pcmk__str_casei)) { pending_task = "Monitoring"; /* Pending probes are not printed, even if pending * operations are requested. If someone ever requests that * behavior, uncomment this and the corresponding part of * unpack.c:unpack_rsc_op(). */ /* } else if (pcmk__str_eq(rsc->pending_task, "probe", pcmk__str_casei)) { pending_task = "Checking"; */ } return pending_task; } static enum rsc_role_e native_displayable_role(const pe_resource_t *rsc) { enum rsc_role_e role = rsc->role; if ((role == pcmk_role_started) && pcmk_is_set(pe__const_top_resource(rsc, false)->flags, pe_rsc_promotable)) { role = pcmk_role_unpromoted; } return role; } static const char * native_displayable_state(const pe_resource_t *rsc, bool print_pending) { const char *rsc_state = NULL; if (print_pending) { rsc_state = native_pending_state(rsc); } if (rsc_state == NULL) { rsc_state = role2text(native_displayable_role(rsc)); } return rsc_state; } /*! * \internal * \deprecated This function will be removed in a future release */ static void native_print_xml(pe_resource_t *rsc, const char *pre_text, long options, void *print_data) { const char *class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); const char *prov = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER); const char *rsc_state = native_displayable_state(rsc, pcmk_is_set(options, pe_print_pending)); const char *target_role = NULL; /* resource information. */ status_print("%sxml, XML_ATTR_TYPE)); status_print("role=\"%s\" ", rsc_state); if (rsc->meta) { target_role = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET_ROLE); } if (target_role) { status_print("target_role=\"%s\" ", target_role); } status_print("active=\"%s\" ", pcmk__btoa(rsc->fns->active(rsc, TRUE))); status_print("orphaned=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_orphan)); status_print("blocked=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_block)); status_print("managed=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_managed)); status_print("failed=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_failed)); status_print("failure_ignored=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_failure_ignored)); status_print("nodes_running_on=\"%d\" ", g_list_length(rsc->running_on)); if (options & pe_print_pending) { const char *pending_task = native_pending_task(rsc); if (pending_task) { status_print("pending=\"%s\" ", pending_task); } } /* print out the nodes this resource is running on */ if (options & pe_print_rsconly) { status_print("/>\n"); /* do nothing */ } else if (rsc->running_on != NULL) { GList *gIter = rsc->running_on; status_print(">\n"); for (; gIter != NULL; gIter = gIter->next) { pe_node_t *node = (pe_node_t *) gIter->data; status_print("%s \n", pre_text, pcmk__s(node->details->uname, ""), node->details->id, pcmk__btoa(!node->details->online)); } status_print("%s\n", pre_text); } else { status_print("/>\n"); } } // Append a flag to resource description string's flags list static bool add_output_flag(GString *s, const char *flag_desc, bool have_flags) { g_string_append(s, (have_flags? ", " : " (")); g_string_append(s, flag_desc); return true; } // Append a node name to resource description string's node list static bool add_output_node(GString *s, const char *node, bool have_nodes) { g_string_append(s, (have_nodes? " " : " [ ")); g_string_append(s, node); return true; } /*! * \internal * \brief Create a string description of a resource * * \param[in] rsc Resource to describe * \param[in] name Desired identifier for the resource * \param[in] node If not NULL, node that resource is "on" * \param[in] show_opts Bitmask of pcmk_show_opt_e. * \param[in] target_role Resource's target role * \param[in] show_nodes Whether to display nodes when multiply active * * \return Newly allocated string description of resource * \note Caller must free the result with g_free(). */ gchar * pcmk__native_output_string(const pe_resource_t *rsc, const char *name, const pe_node_t *node, uint32_t show_opts, const char *target_role, bool show_nodes) { const char *class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); const char *provider = NULL; const char *kind = crm_element_value(rsc->xml, XML_ATTR_TYPE); GString *outstr = NULL; bool have_flags = false; if (rsc->variant != pcmk_rsc_variant_primitive) { return NULL; } CRM_CHECK(name != NULL, name = "unknown"); CRM_CHECK(kind != NULL, kind = "unknown"); CRM_CHECK(class != NULL, class = "unknown"); if (pcmk_is_set(pcmk_get_ra_caps(class), pcmk_ra_cap_provider)) { provider = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER); } if ((node == NULL) && (rsc->lock_node != NULL)) { node = rsc->lock_node; } if (pcmk_any_flags_set(show_opts, pcmk_show_rsc_only) || pcmk__list_of_multiple(rsc->running_on)) { node = NULL; } outstr = g_string_sized_new(128); // Resource name and agent pcmk__g_strcat(outstr, name, "\t(", class, ((provider == NULL)? "" : PROVIDER_SEP), pcmk__s(provider, ""), ":", kind, "):\t", NULL); // State on node if (pcmk_is_set(rsc->flags, pe_rsc_orphan)) { g_string_append(outstr, " ORPHANED"); } if (pcmk_is_set(rsc->flags, pe_rsc_failed)) { enum rsc_role_e role = native_displayable_role(rsc); g_string_append(outstr, " FAILED"); if (role > pcmk_role_unpromoted) { pcmk__add_word(&outstr, 0, role2text(role)); } } else { bool show_pending = pcmk_is_set(show_opts, pcmk_show_pending); pcmk__add_word(&outstr, 0, native_displayable_state(rsc, show_pending)); } if (node) { pcmk__add_word(&outstr, 0, pe__node_name(node)); } // Failed probe operation if (native_displayable_role(rsc) == pcmk_role_stopped) { xmlNode *probe_op = pe__failed_probe_for_rsc(rsc, node ? node->details->uname : NULL); if (probe_op != NULL) { int rc; pcmk__scan_min_int(crm_element_value(probe_op, XML_LRM_ATTR_RC), &rc, 0); pcmk__g_strcat(outstr, " (", services_ocf_exitcode_str(rc), ") ", NULL); } } // Flags, as: ( [...]) if (node && !(node->details->online) && node->details->unclean) { have_flags = add_output_flag(outstr, "UNCLEAN", have_flags); } if (node && (node == rsc->lock_node)) { have_flags = add_output_flag(outstr, "LOCKED", have_flags); } if (pcmk_is_set(show_opts, pcmk_show_pending)) { const char *pending_task = native_pending_task(rsc); if (pending_task) { have_flags = add_output_flag(outstr, pending_task, have_flags); } } if (target_role) { enum rsc_role_e target_role_e = text2role(target_role); /* Only show target role if it limits our abilities (i.e. ignore * Started, as it is the default anyways, and doesn't prevent the * resource from becoming promoted). */ if (target_role_e == pcmk_role_stopped) { have_flags = add_output_flag(outstr, "disabled", have_flags); } else if (pcmk_is_set(pe__const_top_resource(rsc, false)->flags, pe_rsc_promotable) && (target_role_e == pcmk_role_unpromoted)) { have_flags = add_output_flag(outstr, "target-role:", have_flags); g_string_append(outstr, target_role); } } // Blocked or maintenance implies unmanaged if (pcmk_any_flags_set(rsc->flags, pe_rsc_block|pe_rsc_maintenance)) { if (pcmk_is_set(rsc->flags, pe_rsc_block)) { have_flags = add_output_flag(outstr, "blocked", have_flags); } else if (pcmk_is_set(rsc->flags, pe_rsc_maintenance)) { have_flags = add_output_flag(outstr, "maintenance", have_flags); } } else if (!pcmk_is_set(rsc->flags, pe_rsc_managed)) { have_flags = add_output_flag(outstr, "unmanaged", have_flags); } if (pcmk_is_set(rsc->flags, pe_rsc_failure_ignored)) { have_flags = add_output_flag(outstr, "failure ignored", have_flags); } if (have_flags) { g_string_append_c(outstr, ')'); } // User-supplied description if (pcmk_any_flags_set(show_opts, pcmk_show_rsc_only|pcmk_show_description) || pcmk__list_of_multiple(rsc->running_on)) { const char *desc = crm_element_value(rsc->xml, XML_ATTR_DESC); if (desc) { g_string_append(outstr, " ("); g_string_append(outstr, desc); g_string_append(outstr, ")"); } } if (show_nodes && !pcmk_is_set(show_opts, pcmk_show_rsc_only) && pcmk__list_of_multiple(rsc->running_on)) { bool have_nodes = false; for (GList *iter = rsc->running_on; iter != NULL; iter = iter->next) { pe_node_t *n = (pe_node_t *) iter->data; have_nodes = add_output_node(outstr, n->details->uname, have_nodes); } if (have_nodes) { g_string_append(outstr, " ]"); } } return g_string_free(outstr, FALSE); } int pe__common_output_html(pcmk__output_t *out, const pe_resource_t *rsc, const char *name, const pe_node_t *node, uint32_t show_opts) { const char *kind = crm_element_value(rsc->xml, XML_ATTR_TYPE); const char *target_role = NULL; xmlNodePtr list_node = NULL; const char *cl = NULL; CRM_ASSERT(rsc->variant == pcmk_rsc_variant_primitive); CRM_ASSERT(kind != NULL); if (rsc->meta) { const char *is_internal = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_INTERNAL_RSC); if (crm_is_true(is_internal) && !pcmk_is_set(show_opts, pcmk_show_implicit_rscs)) { crm_trace("skipping print of internal resource %s", rsc->id); return pcmk_rc_no_output; } target_role = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET_ROLE); } if (!pcmk_is_set(rsc->flags, pe_rsc_managed)) { cl = "rsc-managed"; } else if (pcmk_is_set(rsc->flags, pe_rsc_failed)) { cl = "rsc-failed"; } else if ((rsc->variant == pcmk_rsc_variant_primitive) && (rsc->running_on == NULL)) { cl = "rsc-failed"; } else if (pcmk__list_of_multiple(rsc->running_on)) { cl = "rsc-multiple"; } else if (pcmk_is_set(rsc->flags, pe_rsc_failure_ignored)) { cl = "rsc-failure-ignored"; } else { cl = "rsc-ok"; } { gchar *s = pcmk__native_output_string(rsc, name, node, show_opts, target_role, true); list_node = pcmk__output_create_html_node(out, "li", NULL, NULL, NULL); pcmk_create_html_node(list_node, "span", NULL, cl, s); g_free(s); } return pcmk_rc_ok; } int pe__common_output_text(pcmk__output_t *out, const pe_resource_t *rsc, const char *name, const pe_node_t *node, uint32_t show_opts) { const char *target_role = NULL; CRM_ASSERT(rsc->variant == pcmk_rsc_variant_primitive); if (rsc->meta) { const char *is_internal = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_INTERNAL_RSC); if (crm_is_true(is_internal) && !pcmk_is_set(show_opts, pcmk_show_implicit_rscs)) { crm_trace("skipping print of internal resource %s", rsc->id); return pcmk_rc_no_output; } target_role = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET_ROLE); } { gchar *s = pcmk__native_output_string(rsc, name, node, show_opts, target_role, true); out->list_item(out, NULL, "%s", s); g_free(s); } return pcmk_rc_ok; } /*! * \internal * \deprecated This function will be removed in a future release */ void common_print(pe_resource_t *rsc, const char *pre_text, const char *name, const pe_node_t *node, long options, void *print_data) { const char *target_role = NULL; CRM_ASSERT(rsc->variant == pcmk_rsc_variant_primitive); if (rsc->meta) { const char *is_internal = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_INTERNAL_RSC); if (crm_is_true(is_internal) && !pcmk_is_set(options, pe_print_implicit)) { crm_trace("skipping print of internal resource %s", rsc->id); return; } target_role = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET_ROLE); } if (options & pe_print_xml) { native_print_xml(rsc, pre_text, options, print_data); return; } if ((pre_text == NULL) && (options & pe_print_printf)) { pre_text = " "; } if (options & pe_print_html) { if (!pcmk_is_set(rsc->flags, pe_rsc_managed)) { status_print(""); } else if (pcmk_is_set(rsc->flags, pe_rsc_failed)) { status_print(""); } else if (rsc->running_on == NULL) { status_print(""); } else if (pcmk__list_of_multiple(rsc->running_on)) { status_print(""); } else if (pcmk_is_set(rsc->flags, pe_rsc_failure_ignored)) { status_print(""); } else { status_print(""); } } { gchar *resource_s = pcmk__native_output_string(rsc, name, node, options, target_role, false); status_print("%s%s", (pre_text? pre_text : ""), resource_s); g_free(resource_s); } if (pcmk_is_set(options, pe_print_html)) { status_print(" "); } if (!pcmk_is_set(options, pe_print_rsconly) && pcmk__list_of_multiple(rsc->running_on)) { GList *gIter = rsc->running_on; int counter = 0; if (options & pe_print_html) { status_print("
    \n"); } else if ((options & pe_print_printf) || (options & pe_print_ncurses)) { status_print("["); } for (; gIter != NULL; gIter = gIter->next) { pe_node_t *n = (pe_node_t *) gIter->data; counter++; if (options & pe_print_html) { status_print("
  • \n%s", pe__node_name(n)); } else if ((options & pe_print_printf) || (options & pe_print_ncurses)) { status_print(" %s", pe__node_name(n)); } else if ((options & pe_print_log)) { status_print("\t%d : %s", counter, pe__node_name(n)); } else { status_print("%s", pe__node_name(n)); } if (options & pe_print_html) { status_print("
  • \n"); } } if (options & pe_print_html) { status_print("
\n"); } else if ((options & pe_print_printf) || (options & pe_print_ncurses)) { status_print(" ]"); } } if (options & pe_print_html) { status_print("
\n"); } else if (options & pe_print_suppres_nl) { /* nothing */ } else if ((options & pe_print_printf) || (options & pe_print_ncurses)) { status_print("\n"); } } /*! * \internal * \deprecated This function will be removed in a future release */ void native_print(pe_resource_t *rsc, const char *pre_text, long options, void *print_data) { const pe_node_t *node = NULL; CRM_ASSERT(rsc->variant == pcmk_rsc_variant_primitive); if (options & pe_print_xml) { native_print_xml(rsc, pre_text, options, print_data); return; } node = pe__current_node(rsc); if (node == NULL) { // This is set only if a non-probe action is pending on this node node = rsc->pending_node; } common_print(rsc, pre_text, rsc_printable_id(rsc), node, options, print_data); } PCMK__OUTPUT_ARGS("primitive", "uint32_t", "pe_resource_t *", "GList *", "GList *") int pe__resource_xml(pcmk__output_t *out, va_list args) { uint32_t show_opts = va_arg(args, uint32_t); pe_resource_t *rsc = va_arg(args, pe_resource_t *); GList *only_node G_GNUC_UNUSED = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); bool print_pending = pcmk_is_set(show_opts, pcmk_show_pending); const char *class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); const char *prov = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER); const char *rsc_state = native_displayable_state(rsc, print_pending); const char *desc = NULL; char ra_name[LINE_MAX]; char *nodes_running_on = NULL; const char *lock_node_name = NULL; int rc = pcmk_rc_no_output; const char *target_role = NULL; desc = pe__resource_description(rsc, show_opts); if (rsc->meta != NULL) { target_role = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET_ROLE); } CRM_ASSERT(rsc->variant == pcmk_rsc_variant_primitive); if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) { return pcmk_rc_no_output; } /* resource information. */ snprintf(ra_name, LINE_MAX, "%s%s%s:%s", class, ((prov == NULL)? "" : PROVIDER_SEP), ((prov == NULL)? "" : prov), crm_element_value(rsc->xml, XML_ATTR_TYPE)); nodes_running_on = pcmk__itoa(g_list_length(rsc->running_on)); if (rsc->lock_node != NULL) { lock_node_name = rsc->lock_node->details->uname; } rc = pe__name_and_nvpairs_xml(out, true, "resource", 15, "id", rsc_printable_id(rsc), "resource_agent", ra_name, "role", rsc_state, "target_role", target_role, "active", pcmk__btoa(rsc->fns->active(rsc, TRUE)), "orphaned", pe__rsc_bool_str(rsc, pe_rsc_orphan), "blocked", pe__rsc_bool_str(rsc, pe_rsc_block), "maintenance", pe__rsc_bool_str(rsc, pe_rsc_maintenance), "managed", pe__rsc_bool_str(rsc, pe_rsc_managed), "failed", pe__rsc_bool_str(rsc, pe_rsc_failed), "failure_ignored", pe__rsc_bool_str(rsc, pe_rsc_failure_ignored), "nodes_running_on", nodes_running_on, "pending", (print_pending? native_pending_task(rsc) : NULL), "locked_to", lock_node_name, "description", desc); free(nodes_running_on); CRM_ASSERT(rc == pcmk_rc_ok); if (rsc->running_on != NULL) { GList *gIter = rsc->running_on; for (; gIter != NULL; gIter = gIter->next) { pe_node_t *node = (pe_node_t *) gIter->data; rc = pe__name_and_nvpairs_xml(out, false, "node", 3, "name", node->details->uname, "id", node->details->id, "cached", pcmk__btoa(node->details->online)); CRM_ASSERT(rc == pcmk_rc_ok); } } pcmk__output_xml_pop_parent(out); return rc; } PCMK__OUTPUT_ARGS("primitive", "uint32_t", "pe_resource_t *", "GList *", "GList *") int pe__resource_html(pcmk__output_t *out, va_list args) { uint32_t show_opts = va_arg(args, uint32_t); pe_resource_t *rsc = va_arg(args, pe_resource_t *); GList *only_node G_GNUC_UNUSED = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); const pe_node_t *node = pe__current_node(rsc); if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) { return pcmk_rc_no_output; } CRM_ASSERT(rsc->variant == pcmk_rsc_variant_primitive); if (node == NULL) { // This is set only if a non-probe action is pending on this node node = rsc->pending_node; } return pe__common_output_html(out, rsc, rsc_printable_id(rsc), node, show_opts); } PCMK__OUTPUT_ARGS("primitive", "uint32_t", "pe_resource_t *", "GList *", "GList *") int pe__resource_text(pcmk__output_t *out, va_list args) { uint32_t show_opts = va_arg(args, uint32_t); pe_resource_t *rsc = va_arg(args, pe_resource_t *); GList *only_node G_GNUC_UNUSED = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); const pe_node_t *node = pe__current_node(rsc); CRM_ASSERT(rsc->variant == pcmk_rsc_variant_primitive); if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) { return pcmk_rc_no_output; } if (node == NULL) { // This is set only if a non-probe action is pending on this node node = rsc->pending_node; } return pe__common_output_text(out, rsc, rsc_printable_id(rsc), node, show_opts); } void native_free(pe_resource_t * rsc) { pe_rsc_trace(rsc, "Freeing resource action list (not the data)"); common_free(rsc); } enum rsc_role_e native_resource_state(const pe_resource_t * rsc, gboolean current) { enum rsc_role_e role = rsc->next_role; if (current) { role = rsc->role; } pe_rsc_trace(rsc, "%s state: %s", rsc->id, role2text(role)); return role; } /*! * \internal * \brief List nodes where a resource (or any of its children) is * * \param[in] rsc Resource to check * \param[out] list List to add result to * \param[in] current 0 = where allocated, 1 = where running, * 2 = where running or pending * * \return If list contains only one node, that node, or NULL otherwise */ pe_node_t * native_location(const pe_resource_t *rsc, GList **list, int current) { // @COMPAT: Accept a pe__rsc_node argument instead of int current pe_node_t *one = NULL; GList *result = NULL; if (rsc->children) { GList *gIter = rsc->children; for (; gIter != NULL; gIter = gIter->next) { pe_resource_t *child = (pe_resource_t *) gIter->data; child->fns->location(child, &result, current); } } else if (current) { if (rsc->running_on) { result = g_list_copy(rsc->running_on); } if ((current == 2) && rsc->pending_node && !pe_find_node_id(result, rsc->pending_node->details->id)) { result = g_list_append(result, rsc->pending_node); } } else if (current == FALSE && rsc->allocated_to) { result = g_list_append(NULL, rsc->allocated_to); } if (result && (result->next == NULL)) { one = result->data; } if (list) { GList *gIter = result; for (; gIter != NULL; gIter = gIter->next) { pe_node_t *node = (pe_node_t *) gIter->data; if (*list == NULL || pe_find_node_id(*list, node->details->id) == NULL) { *list = g_list_append(*list, node); } } } g_list_free(result); return one; } static void get_rscs_brief(GList *rsc_list, GHashTable * rsc_table, GHashTable * active_table) { GList *gIter = rsc_list; for (; gIter != NULL; gIter = gIter->next) { pe_resource_t *rsc = (pe_resource_t *) gIter->data; const char *class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); const char *kind = crm_element_value(rsc->xml, XML_ATTR_TYPE); int offset = 0; char buffer[LINE_MAX]; int *rsc_counter = NULL; int *active_counter = NULL; if (rsc->variant != pcmk_rsc_variant_primitive) { continue; } offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", class); if (pcmk_is_set(pcmk_get_ra_caps(class), pcmk_ra_cap_provider)) { const char *prov = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER); if (prov != NULL) { offset += snprintf(buffer + offset, LINE_MAX - offset, PROVIDER_SEP "%s", prov); } } offset += snprintf(buffer + offset, LINE_MAX - offset, ":%s", kind); CRM_LOG_ASSERT(offset > 0); if (rsc_table) { rsc_counter = g_hash_table_lookup(rsc_table, buffer); if (rsc_counter == NULL) { rsc_counter = calloc(1, sizeof(int)); *rsc_counter = 0; g_hash_table_insert(rsc_table, strdup(buffer), rsc_counter); } (*rsc_counter)++; } if (active_table) { GList *gIter2 = rsc->running_on; for (; gIter2 != NULL; gIter2 = gIter2->next) { pe_node_t *node = (pe_node_t *) gIter2->data; GHashTable *node_table = NULL; if (node->details->unclean == FALSE && node->details->online == FALSE && pcmk_is_set(rsc->flags, pe_rsc_managed)) { continue; } node_table = g_hash_table_lookup(active_table, node->details->uname); if (node_table == NULL) { node_table = pcmk__strkey_table(free, free); g_hash_table_insert(active_table, strdup(node->details->uname), node_table); } active_counter = g_hash_table_lookup(node_table, buffer); if (active_counter == NULL) { active_counter = calloc(1, sizeof(int)); *active_counter = 0; g_hash_table_insert(node_table, strdup(buffer), active_counter); } (*active_counter)++; } } } } static void destroy_node_table(gpointer data) { GHashTable *node_table = data; if (node_table) { g_hash_table_destroy(node_table); } } /*! * \internal * \deprecated This function will be removed in a future release */ void print_rscs_brief(GList *rsc_list, const char *pre_text, long options, void *print_data, gboolean print_all) { GHashTable *rsc_table = pcmk__strkey_table(free, free); GHashTable *active_table = pcmk__strkey_table(free, destroy_node_table); GHashTableIter hash_iter; char *type = NULL; int *rsc_counter = NULL; get_rscs_brief(rsc_list, rsc_table, active_table); g_hash_table_iter_init(&hash_iter, rsc_table); while (g_hash_table_iter_next(&hash_iter, (gpointer *)&type, (gpointer *)&rsc_counter)) { GHashTableIter hash_iter2; char *node_name = NULL; GHashTable *node_table = NULL; int active_counter_all = 0; g_hash_table_iter_init(&hash_iter2, active_table); while (g_hash_table_iter_next(&hash_iter2, (gpointer *)&node_name, (gpointer *)&node_table)) { int *active_counter = g_hash_table_lookup(node_table, type); if (active_counter == NULL || *active_counter == 0) { continue; } else { active_counter_all += *active_counter; } if (options & pe_print_rsconly) { node_name = NULL; } if (options & pe_print_html) { status_print("
  • \n"); } if (print_all) { status_print("%s%d/%d\t(%s):\tActive %s\n", pre_text ? pre_text : "", active_counter ? *active_counter : 0, rsc_counter ? *rsc_counter : 0, type, active_counter && (*active_counter > 0) && node_name ? node_name : ""); } else { status_print("%s%d\t(%s):\tActive %s\n", pre_text ? pre_text : "", active_counter ? *active_counter : 0, type, active_counter && (*active_counter > 0) && node_name ? node_name : ""); } if (options & pe_print_html) { status_print("
  • \n"); } } if (print_all && active_counter_all == 0) { if (options & pe_print_html) { status_print("
  • \n"); } status_print("%s%d/%d\t(%s):\tActive\n", pre_text ? pre_text : "", active_counter_all, rsc_counter ? *rsc_counter : 0, type); if (options & pe_print_html) { status_print("
  • \n"); } } } if (rsc_table) { g_hash_table_destroy(rsc_table); rsc_table = NULL; } if (active_table) { g_hash_table_destroy(active_table); active_table = NULL; } } int pe__rscs_brief_output(pcmk__output_t *out, GList *rsc_list, uint32_t show_opts) { GHashTable *rsc_table = pcmk__strkey_table(free, free); GHashTable *active_table = pcmk__strkey_table(free, destroy_node_table); GList *sorted_rscs; int rc = pcmk_rc_no_output; get_rscs_brief(rsc_list, rsc_table, active_table); /* Make a list of the rsc_table keys so that it can be sorted. This is to make sure * output order stays consistent between systems. */ sorted_rscs = g_hash_table_get_keys(rsc_table); sorted_rscs = g_list_sort(sorted_rscs, (GCompareFunc) strcmp); for (GList *gIter = sorted_rscs; gIter; gIter = gIter->next) { char *type = (char *) gIter->data; int *rsc_counter = g_hash_table_lookup(rsc_table, type); GList *sorted_nodes = NULL; int active_counter_all = 0; /* Also make a list of the active_table keys so it can be sorted. If there's * more than one instance of a type of resource running, we need the nodes to * be sorted to make sure output order stays consistent between systems. */ sorted_nodes = g_hash_table_get_keys(active_table); sorted_nodes = g_list_sort(sorted_nodes, (GCompareFunc) pcmk__numeric_strcasecmp); for (GList *gIter2 = sorted_nodes; gIter2; gIter2 = gIter2->next) { char *node_name = (char *) gIter2->data; GHashTable *node_table = g_hash_table_lookup(active_table, node_name); int *active_counter = NULL; if (node_table == NULL) { continue; } active_counter = g_hash_table_lookup(node_table, type); if (active_counter == NULL || *active_counter == 0) { continue; } else { active_counter_all += *active_counter; } if (pcmk_is_set(show_opts, pcmk_show_rsc_only)) { node_name = NULL; } if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) { out->list_item(out, NULL, "%d/%d\t(%s):\tActive %s", *active_counter, rsc_counter ? *rsc_counter : 0, type, (*active_counter > 0) && node_name ? node_name : ""); } else { out->list_item(out, NULL, "%d\t(%s):\tActive %s", *active_counter, type, (*active_counter > 0) && node_name ? node_name : ""); } rc = pcmk_rc_ok; } if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs) && active_counter_all == 0) { out->list_item(out, NULL, "%d/%d\t(%s):\tActive", active_counter_all, rsc_counter ? *rsc_counter : 0, type); rc = pcmk_rc_ok; } if (sorted_nodes) { g_list_free(sorted_nodes); } } if (rsc_table) { g_hash_table_destroy(rsc_table); rsc_table = NULL; } if (active_table) { g_hash_table_destroy(active_table); active_table = NULL; } if (sorted_rscs) { g_list_free(sorted_rscs); } return rc; } gboolean pe__native_is_filtered(const pe_resource_t *rsc, GList *only_rsc, gboolean check_parent) { if (pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches) || pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches)) { return FALSE; } else if (check_parent && rsc->parent) { const pe_resource_t *up = pe__const_top_resource(rsc, true); return up->fns->is_filtered(up, only_rsc, FALSE); } return TRUE; } /*! * \internal * \brief Get maximum primitive resource instances per node * * \param[in] rsc Primitive resource to check * * \return Maximum number of \p rsc instances that can be active on one node */ unsigned int pe__primitive_max_per_node(const pe_resource_t *rsc) { CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_primitive)); return 1U; } diff --git a/lib/pengine/tags.c b/lib/pengine/tags.c index 3de66c60dc..0ac6239284 100644 --- a/lib/pengine/tags.c +++ b/lib/pengine/tags.c @@ -1,112 +1,112 @@ /* * Copyright 2020-2023 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, - pcmk_rsc_match_history - |pe_find_any); + const uint32_t flags = pcmk_rsc_match_history|pcmk_rsc_match_basename; + pe_resource_t *rsc = pe_find_resource_with_flags(data_set->resources, + id, flags); 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/lib/pengine/tests/native/native_find_rsc_test.c b/lib/pengine/tests/native/native_find_rsc_test.c index 7dfb98c17f..d75b506ed7 100644 --- a/lib/pengine/tests/native/native_find_rsc_test.c +++ b/lib/pengine/tests/native/native_find_rsc_test.c @@ -1,890 +1,915 @@ /* * Copyright 2022-2023 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 xmlNode *input = NULL; pe_working_set_t *data_set = NULL; pe_node_t *cluster01, *cluster02, *httpd_bundle_0; pe_resource_t *exim_group, *inactive_group, *promotable_clone, *inactive_clone; pe_resource_t *httpd_bundle, *mysql_clone_group; static int setup(void **state) { char *path = NULL; crm_xml_init(); path = crm_strdup_printf("%s/crm_mon.xml", getenv("PCMK_CTS_CLI_DIR")); input = filename2xml(path); free(path); if (input == NULL) { return 1; } data_set = pe_new_working_set(); if (data_set == NULL) { return 1; } pe__set_working_set_flags(data_set, pe_flag_no_counts|pe_flag_no_compat); data_set->input = input; cluster_status(data_set); /* Get references to the cluster nodes so we don't have to find them repeatedly. */ cluster01 = pe_find_node(data_set->nodes, "cluster01"); cluster02 = pe_find_node(data_set->nodes, "cluster02"); httpd_bundle_0 = pe_find_node(data_set->nodes, "httpd-bundle-0"); /* Get references to several resources we use frequently. */ for (GList *iter = data_set->resources; iter != NULL; iter = iter->next) { pe_resource_t *rsc = (pe_resource_t *) iter->data; if (strcmp(rsc->id, "exim-group") == 0) { exim_group = rsc; } else if (strcmp(rsc->id, "httpd-bundle") == 0) { httpd_bundle = rsc; } else if (strcmp(rsc->id, "inactive-clone") == 0) { inactive_clone = rsc; } else if (strcmp(rsc->id, "inactive-group") == 0) { inactive_group = rsc; } else if (strcmp(rsc->id, "mysql-clone-group") == 0) { mysql_clone_group = rsc; } else if (strcmp(rsc->id, "promotable-clone") == 0) { promotable_clone = rsc; } } return 0; } static int teardown(void **state) { pe_free_working_set(data_set); return 0; } static void bad_args(void **state) { pe_resource_t *rsc = (pe_resource_t *) g_list_first(data_set->resources)->data; char *id = rsc->id; char *name = NULL; assert_non_null(rsc); assert_null(native_find_rsc(NULL, "dummy", NULL, 0)); assert_null(native_find_rsc(rsc, NULL, NULL, 0)); /* No resources exist with these names. */ name = crm_strdup_printf("%sX", rsc->id); assert_null(native_find_rsc(rsc, name, NULL, 0)); free(name); name = crm_strdup_printf("x%s", rsc->id); assert_null(native_find_rsc(rsc, name, NULL, 0)); free(name); name = g_ascii_strup(rsc->id, -1); assert_null(native_find_rsc(rsc, name, NULL, 0)); g_free(name); /* Fails because resource ID is NULL. */ rsc->id = NULL; assert_null(native_find_rsc(rsc, id, NULL, 0)); rsc->id = id; } static void primitive_rsc(void **state) { pe_resource_t *dummy = NULL; /* Find the "dummy" resource, which is the only one with that ID in the set. */ for (GList *iter = data_set->resources; iter != NULL; iter = iter->next) { pe_resource_t *rsc = (pe_resource_t *) iter->data; if (strcmp(rsc->id, "dummy") == 0) { dummy = rsc; break; } } assert_non_null(dummy); /* Passes because NULL was passed for node, regardless of flags. */ assert_ptr_equal(dummy, native_find_rsc(dummy, "dummy", NULL, 0)); assert_ptr_equal(dummy, native_find_rsc(dummy, "dummy", NULL, pcmk_rsc_match_current_node)); /* Fails because resource is not a clone (nor cloned). */ assert_null(native_find_rsc(dummy, "dummy", NULL, pcmk_rsc_match_clone_only)); assert_null(native_find_rsc(dummy, "dummy", cluster02, pcmk_rsc_match_clone_only)); /* Fails because dummy is not running on cluster01, even with the right flags. */ assert_null(native_find_rsc(dummy, "dummy", cluster01, pcmk_rsc_match_current_node)); // Fails because pcmk_rsc_match_current_node is required if a node is given assert_null(native_find_rsc(dummy, "dummy", cluster02, 0)); /* Passes because dummy is running on cluster02. */ assert_ptr_equal(dummy, native_find_rsc(dummy, "dummy", cluster02, pcmk_rsc_match_current_node)); } static void group_rsc(void **state) { assert_non_null(exim_group); /* Passes because NULL was passed for node, regardless of flags. */ assert_ptr_equal(exim_group, native_find_rsc(exim_group, "exim-group", NULL, 0)); assert_ptr_equal(exim_group, native_find_rsc(exim_group, "exim-group", NULL, pcmk_rsc_match_current_node)); /* Fails because resource is not a clone (nor cloned). */ assert_null(native_find_rsc(exim_group, "exim-group", NULL, pcmk_rsc_match_clone_only)); assert_null(native_find_rsc(exim_group, "exim-group", cluster01, pcmk_rsc_match_clone_only)); /* Fails because none of exim-group's children are running on cluster01, even with the right flags. */ assert_null(native_find_rsc(exim_group, "exim-group", cluster01, pcmk_rsc_match_current_node)); // Fails because pcmk_rsc_match_current_node is required if a node is given assert_null(native_find_rsc(exim_group, "exim-group", cluster01, 0)); /* Passes because one of exim-group's children is running on cluster02. */ assert_ptr_equal(exim_group, native_find_rsc(exim_group, "exim-group", cluster02, pcmk_rsc_match_current_node)); } static void inactive_group_rsc(void **state) { assert_non_null(inactive_group); /* Passes because NULL was passed for node, regardless of flags. */ assert_ptr_equal(inactive_group, native_find_rsc(inactive_group, "inactive-group", NULL, 0)); assert_ptr_equal(inactive_group, native_find_rsc(inactive_group, "inactive-group", NULL, pcmk_rsc_match_current_node)); /* Fails because resource is not a clone (nor cloned). */ assert_null(native_find_rsc(inactive_group, "inactive-group", NULL, pcmk_rsc_match_clone_only)); assert_null(native_find_rsc(inactive_group, "inactive-group", cluster01, pcmk_rsc_match_clone_only)); /* Fails because none of inactive-group's children are running. */ assert_null(native_find_rsc(inactive_group, "inactive-group", cluster01, pcmk_rsc_match_current_node)); assert_null(native_find_rsc(inactive_group, "inactive-group", cluster02, pcmk_rsc_match_current_node)); } static void group_member_rsc(void **state) { pe_resource_t *public_ip = NULL; /* Find the "Public-IP" resource, a member of "exim-group". */ for (GList *iter = exim_group->children; iter != NULL; iter = iter->next) { pe_resource_t *rsc = (pe_resource_t *) iter->data; if (strcmp(rsc->id, "Public-IP") == 0) { public_ip = rsc; break; } } assert_non_null(public_ip); /* Passes because NULL was passed for node, regardless of flags. */ assert_ptr_equal(public_ip, native_find_rsc(public_ip, "Public-IP", NULL, 0)); assert_ptr_equal(public_ip, native_find_rsc(public_ip, "Public-IP", NULL, pcmk_rsc_match_current_node)); /* Fails because resource is not a clone (nor cloned). */ assert_null(native_find_rsc(public_ip, "Public-IP", NULL, pcmk_rsc_match_clone_only)); assert_null(native_find_rsc(public_ip, "Public-IP", cluster02, pcmk_rsc_match_clone_only)); /* Fails because Public-IP is not running on cluster01, even with the right flags. */ assert_null(native_find_rsc(public_ip, "Public-IP", cluster01, pcmk_rsc_match_current_node)); // Fails because pcmk_rsc_match_current_node is required if a node is given assert_null(native_find_rsc(public_ip, "Public-IP", cluster02, 0)); /* Passes because Public-IP is running on cluster02. */ assert_ptr_equal(public_ip, native_find_rsc(public_ip, "Public-IP", cluster02, pcmk_rsc_match_current_node)); } static void inactive_group_member_rsc(void **state) { pe_resource_t *inactive_dummy_1 = NULL; /* Find the "inactive-dummy-1" resource, a member of "inactive-group". */ for (GList *iter = inactive_group->children; iter != NULL; iter = iter->next) { pe_resource_t *rsc = (pe_resource_t *) iter->data; if (strcmp(rsc->id, "inactive-dummy-1") == 0) { inactive_dummy_1 = rsc; break; } } assert_non_null(inactive_dummy_1); /* Passes because NULL was passed for node, regardless of flags. */ assert_ptr_equal(inactive_dummy_1, native_find_rsc(inactive_dummy_1, "inactive-dummy-1", NULL, 0)); assert_ptr_equal(inactive_dummy_1, native_find_rsc(inactive_dummy_1, "inactive-dummy-1", NULL, pcmk_rsc_match_current_node)); /* Fails because resource is not a clone (nor cloned). */ assert_null(native_find_rsc(inactive_dummy_1, "inactive-dummy-1", NULL, pcmk_rsc_match_clone_only)); assert_null(native_find_rsc(inactive_dummy_1, "inactive-dummy-1", cluster01, pcmk_rsc_match_clone_only)); /* Fails because inactive-dummy-1 is not running. */ assert_null(native_find_rsc(inactive_dummy_1, "inactive-dummy-1", cluster01, pcmk_rsc_match_current_node)); assert_null(native_find_rsc(inactive_dummy_1, "inactive-dummy-1", cluster02, pcmk_rsc_match_current_node)); } static void clone_rsc(void **state) { assert_non_null(promotable_clone); /* Passes because NULL was passed for node, regardless of flags. */ assert_ptr_equal(promotable_clone, native_find_rsc(promotable_clone, "promotable-clone", NULL, 0)); assert_ptr_equal(promotable_clone, native_find_rsc(promotable_clone, "promotable-clone", NULL, pcmk_rsc_match_current_node)); assert_ptr_equal(promotable_clone, native_find_rsc(promotable_clone, "promotable-clone", NULL, pcmk_rsc_match_clone_only)); // Fails because pcmk_rsc_match_current_node is required if a node is given assert_null(native_find_rsc(promotable_clone, "promotable-clone", cluster01, 0)); /* Passes because one of ping-clone's children is running on cluster01. */ assert_ptr_equal(promotable_clone, native_find_rsc(promotable_clone, "promotable-clone", cluster01, pcmk_rsc_match_current_node)); // Fails because pcmk_rsc_match_current_node is required if a node is given assert_null(native_find_rsc(promotable_clone, "promotable-clone", cluster02, 0)); /* Passes because one of ping_clone's children is running on cluster02. */ assert_ptr_equal(promotable_clone, native_find_rsc(promotable_clone, "promotable-clone", cluster02, pcmk_rsc_match_current_node)); // Passes for previous reasons, plus includes pcmk_rsc_match_clone_only assert_ptr_equal(promotable_clone, native_find_rsc(promotable_clone, "promotable-clone", cluster01, pcmk_rsc_match_clone_only |pcmk_rsc_match_current_node)); assert_ptr_equal(promotable_clone, native_find_rsc(promotable_clone, "promotable-clone", cluster02, pcmk_rsc_match_clone_only |pcmk_rsc_match_current_node)); } static void inactive_clone_rsc(void **state) { assert_non_null(inactive_clone); /* Passes because NULL was passed for node, regardless of flags. */ assert_ptr_equal(inactive_clone, native_find_rsc(inactive_clone, "inactive-clone", NULL, 0)); assert_ptr_equal(inactive_clone, native_find_rsc(inactive_clone, "inactive-clone", NULL, pcmk_rsc_match_current_node)); assert_ptr_equal(inactive_clone, native_find_rsc(inactive_clone, "inactive-clone", NULL, pcmk_rsc_match_clone_only)); /* Fails because none of inactive-clone's children are running. */ assert_null(native_find_rsc(inactive_clone, "inactive-clone", cluster01, pcmk_rsc_match_current_node |pcmk_rsc_match_clone_only)); assert_null(native_find_rsc(inactive_clone, "inactive-clone", cluster02, pcmk_rsc_match_current_node |pcmk_rsc_match_clone_only)); } static void clone_instance_rsc(void **state) { pe_resource_t *promotable_0 = NULL; pe_resource_t *promotable_1 = NULL; /* Find the "promotable-rsc:0" and "promotable-rsc:1" resources, members of "promotable-clone". */ for (GList *iter = promotable_clone->children; iter != NULL; iter = iter->next) { pe_resource_t *rsc = (pe_resource_t *) iter->data; if (strcmp(rsc->id, "promotable-rsc:0") == 0) { promotable_0 = rsc; } else if (strcmp(rsc->id, "promotable-rsc:1") == 0) { promotable_1 = rsc; } } assert_non_null(promotable_0); assert_non_null(promotable_1); /* Passes because NULL was passed for node, regardless of flags. */ assert_ptr_equal(promotable_0, native_find_rsc(promotable_0, "promotable-rsc:0", NULL, 0)); assert_ptr_equal(promotable_0, native_find_rsc(promotable_0, "promotable-rsc:0", NULL, pcmk_rsc_match_current_node)); assert_ptr_equal(promotable_1, native_find_rsc(promotable_1, "promotable-rsc:1", NULL, 0)); assert_ptr_equal(promotable_1, native_find_rsc(promotable_1, "promotable-rsc:1", NULL, pcmk_rsc_match_current_node)); // Fails because pcmk_rsc_match_current_node is required if a node is given assert_null(native_find_rsc(promotable_0, "promotable-rsc:0", cluster02, 0)); assert_null(native_find_rsc(promotable_1, "promotable-rsc:1", cluster01, 0)); /* Check that the resource is running on the node we expect. */ assert_ptr_equal(promotable_0, native_find_rsc(promotable_0, "promotable-rsc:0", cluster02, pcmk_rsc_match_current_node)); assert_null(native_find_rsc(promotable_0, "promotable-rsc:0", cluster01, pcmk_rsc_match_current_node)); assert_ptr_equal(promotable_1, native_find_rsc(promotable_1, "promotable-rsc:1", cluster01, pcmk_rsc_match_current_node)); assert_null(native_find_rsc(promotable_1, "promotable-rsc:1", cluster02, pcmk_rsc_match_current_node)); /* Passes because NULL was passed for node and primitive name was given, with correct flags. */ assert_ptr_equal(promotable_0, native_find_rsc(promotable_0, "promotable-rsc", NULL, pcmk_rsc_match_clone_only)); - /* Passes because pe_find_any matches any instance's base name. */ - assert_ptr_equal(promotable_0, native_find_rsc(promotable_0, "promotable-rsc", NULL, pe_find_any)); - assert_ptr_equal(promotable_1, native_find_rsc(promotable_1, "promotable-rsc", NULL, pe_find_any)); + // Passes because pcmk_rsc_match_basename matches any instance's base name + assert_ptr_equal(promotable_0, + native_find_rsc(promotable_0, "promotable-rsc", NULL, + pcmk_rsc_match_basename)); + assert_ptr_equal(promotable_1, + native_find_rsc(promotable_1, "promotable-rsc", NULL, + pcmk_rsc_match_basename)); // Passes because pcmk_rsc_match_anon_basename matches assert_ptr_equal(promotable_0, native_find_rsc(promotable_0, "promotable-rsc", NULL, pcmk_rsc_match_anon_basename)); assert_ptr_equal(promotable_1, native_find_rsc(promotable_1, "promotable-rsc", NULL, pcmk_rsc_match_anon_basename)); /* Check that the resource is running on the node we expect. */ assert_ptr_equal(promotable_0, native_find_rsc(promotable_0, "promotable-rsc", cluster02, - pe_find_any|pcmk_rsc_match_current_node)); + pcmk_rsc_match_basename + |pcmk_rsc_match_current_node)); assert_ptr_equal(promotable_0, native_find_rsc(promotable_0, "promotable-rsc", cluster02, pcmk_rsc_match_anon_basename |pcmk_rsc_match_current_node)); assert_null(native_find_rsc(promotable_0, "promotable-rsc", cluster01, - pe_find_any|pcmk_rsc_match_current_node)); + pcmk_rsc_match_basename + |pcmk_rsc_match_current_node)); assert_null(native_find_rsc(promotable_0, "promotable-rsc", cluster01, pcmk_rsc_match_anon_basename |pcmk_rsc_match_current_node)); assert_ptr_equal(promotable_1, native_find_rsc(promotable_1, "promotable-rsc", cluster01, - pe_find_any|pcmk_rsc_match_current_node)); + pcmk_rsc_match_basename + |pcmk_rsc_match_current_node)); assert_ptr_equal(promotable_1, native_find_rsc(promotable_1, "promotable-rsc", cluster01, pcmk_rsc_match_anon_basename |pcmk_rsc_match_current_node)); assert_null(native_find_rsc(promotable_1, "promotable-rsc", cluster02, - pe_find_any|pcmk_rsc_match_current_node)); + pcmk_rsc_match_basename + |pcmk_rsc_match_current_node)); assert_null(native_find_rsc(promotable_1, "promotable-rsc", cluster02, pcmk_rsc_match_anon_basename |pcmk_rsc_match_current_node)); /* Fails because incorrect flags were given along with primitive name. */ assert_null(native_find_rsc(promotable_0, "promotable-rsc", NULL, pcmk_rsc_match_current_node)); assert_null(native_find_rsc(promotable_1, "promotable-rsc", NULL, pcmk_rsc_match_current_node)); /* And then we check failure possibilities again, except passing promotable_clone * instead of promotable_X as the first argument to native_find_rsc. */ // Fails because pcmk_rsc_match_current_node is required if a node is given assert_null(native_find_rsc(promotable_clone, "promotable-rsc:0", cluster02, 0)); assert_null(native_find_rsc(promotable_clone, "promotable-rsc:1", cluster01, 0)); /* Check that the resource is running on the node we expect. */ assert_ptr_equal(promotable_0, native_find_rsc(promotable_clone, "promotable-rsc:0", cluster02, pcmk_rsc_match_current_node)); assert_ptr_equal(promotable_0, native_find_rsc(promotable_clone, "promotable-rsc", cluster02, - pe_find_any|pcmk_rsc_match_current_node)); + pcmk_rsc_match_basename + |pcmk_rsc_match_current_node)); assert_ptr_equal(promotable_0, native_find_rsc(promotable_clone, "promotable-rsc", cluster02, pcmk_rsc_match_anon_basename |pcmk_rsc_match_current_node)); assert_ptr_equal(promotable_1, native_find_rsc(promotable_clone, "promotable-rsc:1", cluster01, pcmk_rsc_match_current_node)); assert_ptr_equal(promotable_1, native_find_rsc(promotable_clone, "promotable-rsc", cluster01, - pe_find_any|pcmk_rsc_match_current_node)); + pcmk_rsc_match_basename + |pcmk_rsc_match_current_node)); assert_ptr_equal(promotable_1, native_find_rsc(promotable_clone, "promotable-rsc", cluster01, pcmk_rsc_match_anon_basename |pcmk_rsc_match_current_node)); } static void renamed_rsc(void **state) { pe_resource_t *promotable_0 = NULL; pe_resource_t *promotable_1 = NULL; /* Find the "promotable-rsc:0" and "promotable-rsc:1" resources, members of "promotable-clone". */ for (GList *iter = promotable_clone->children; iter != NULL; iter = iter->next) { pe_resource_t *rsc = (pe_resource_t *) iter->data; if (strcmp(rsc->id, "promotable-rsc:0") == 0) { promotable_0 = rsc; } else if (strcmp(rsc->id, "promotable-rsc:1") == 0) { promotable_1 = rsc; } } assert_non_null(promotable_0); assert_non_null(promotable_1); // Passes because pcmk_rsc_match_history means base name matches clone_name assert_ptr_equal(promotable_0, native_find_rsc(promotable_0, "promotable-rsc", NULL, pcmk_rsc_match_history)); assert_ptr_equal(promotable_1, native_find_rsc(promotable_1, "promotable-rsc", NULL, pcmk_rsc_match_history)); } static void bundle_rsc(void **state) { assert_non_null(httpd_bundle); /* Passes because NULL was passed for node, regardless of flags. */ assert_ptr_equal(httpd_bundle, native_find_rsc(httpd_bundle, "httpd-bundle", NULL, 0)); assert_ptr_equal(httpd_bundle, native_find_rsc(httpd_bundle, "httpd-bundle", NULL, pcmk_rsc_match_current_node)); /* Fails because resource is not a clone (nor cloned). */ assert_null(native_find_rsc(httpd_bundle, "httpd-bundle", NULL, pcmk_rsc_match_clone_only)); assert_null(native_find_rsc(httpd_bundle, "httpd-bundle", cluster01, pcmk_rsc_match_clone_only)); // Fails because pcmk_rsc_match_current_node is required if a node is given assert_null(native_find_rsc(httpd_bundle, "httpd-bundle", cluster01, 0)); /* Passes because one of httpd_bundle's children is running on cluster01. */ assert_ptr_equal(httpd_bundle, native_find_rsc(httpd_bundle, "httpd-bundle", cluster01, pcmk_rsc_match_current_node)); } static bool bundle_first_replica(pe__bundle_replica_t *replica, void *user_data) { pe_resource_t *ip_0 = replica->ip; pe_resource_t *child_0 = replica->child; pe_resource_t *container_0 = replica->container; pe_resource_t *remote_0 = replica->remote; assert_non_null(ip_0); assert_non_null(child_0); assert_non_null(container_0); assert_non_null(remote_0); /* Passes because NULL was passed for node, regardless of flags. */ assert_ptr_equal(ip_0, native_find_rsc(ip_0, "httpd-bundle-ip-192.168.122.131", NULL, 0)); assert_ptr_equal(child_0, native_find_rsc(child_0, "httpd:0", NULL, 0)); assert_ptr_equal(container_0, native_find_rsc(container_0, "httpd-bundle-docker-0", NULL, 0)); assert_ptr_equal(remote_0, native_find_rsc(remote_0, "httpd-bundle-0", NULL, 0)); // Fails because pcmk_rsc_match_current_node is required if a node is given assert_null(native_find_rsc(ip_0, "httpd-bundle-ip-192.168.122.131", cluster01, 0)); assert_null(native_find_rsc(child_0, "httpd:0", httpd_bundle_0, 0)); assert_null(native_find_rsc(container_0, "httpd-bundle-docker-0", cluster01, 0)); assert_null(native_find_rsc(remote_0, "httpd-bundle-0", cluster01, 0)); /* Check that the resource is running on the node we expect. */ assert_ptr_equal(ip_0, native_find_rsc(ip_0, "httpd-bundle-ip-192.168.122.131", cluster01, pcmk_rsc_match_current_node)); assert_null(native_find_rsc(ip_0, "httpd-bundle-ip-192.168.122.131", cluster02, pcmk_rsc_match_current_node)); assert_null(native_find_rsc(ip_0, "httpd-bundle-ip-192.168.122.131", httpd_bundle_0, pcmk_rsc_match_current_node)); assert_ptr_equal(child_0, native_find_rsc(child_0, "httpd:0", httpd_bundle_0, pcmk_rsc_match_current_node)); assert_null(native_find_rsc(child_0, "httpd:0", cluster01, pcmk_rsc_match_current_node)); assert_null(native_find_rsc(child_0, "httpd:0", cluster02, pcmk_rsc_match_current_node)); assert_ptr_equal(container_0, native_find_rsc(container_0, "httpd-bundle-docker-0", cluster01, pcmk_rsc_match_current_node)); assert_null(native_find_rsc(container_0, "httpd-bundle-docker-0", cluster02, pcmk_rsc_match_current_node)); assert_null(native_find_rsc(container_0, "httpd-bundle-docker-0", httpd_bundle_0, pcmk_rsc_match_current_node)); assert_ptr_equal(remote_0, native_find_rsc(remote_0, "httpd-bundle-0", cluster01, pcmk_rsc_match_current_node)); assert_null(native_find_rsc(remote_0, "httpd-bundle-0", cluster02, pcmk_rsc_match_current_node)); assert_null(native_find_rsc(remote_0, "httpd-bundle-0", httpd_bundle_0, pcmk_rsc_match_current_node)); - /* Passes because pe_find_any matches any replica's base name. */ - assert_ptr_equal(child_0, native_find_rsc(child_0, "httpd", NULL, pe_find_any)); + // Passes because pcmk_rsc_match_basename matches any replica's base name + assert_ptr_equal(child_0, + native_find_rsc(child_0, "httpd", NULL, + pcmk_rsc_match_basename)); // Passes because pcmk_rsc_match_anon_basename matches assert_ptr_equal(child_0, native_find_rsc(child_0, "httpd", NULL, pcmk_rsc_match_anon_basename)); /* Check that the resource is running on the node we expect. */ assert_ptr_equal(child_0, native_find_rsc(child_0, "httpd", httpd_bundle_0, - pe_find_any|pcmk_rsc_match_current_node)); + pcmk_rsc_match_basename + |pcmk_rsc_match_current_node)); assert_ptr_equal(child_0, native_find_rsc(child_0, "httpd", httpd_bundle_0, pcmk_rsc_match_anon_basename |pcmk_rsc_match_current_node)); assert_null(native_find_rsc(child_0, "httpd", cluster01, - pe_find_any|pcmk_rsc_match_current_node)); + pcmk_rsc_match_basename + |pcmk_rsc_match_current_node)); assert_null(native_find_rsc(child_0, "httpd", cluster01, pcmk_rsc_match_anon_basename |pcmk_rsc_match_current_node)); assert_null(native_find_rsc(child_0, "httpd", cluster02, - pe_find_any|pcmk_rsc_match_current_node)); + pcmk_rsc_match_basename + |pcmk_rsc_match_current_node)); assert_null(native_find_rsc(child_0, "httpd", cluster02, pcmk_rsc_match_anon_basename |pcmk_rsc_match_current_node)); /* Fails because incorrect flags were given along with base name. */ assert_null(native_find_rsc(child_0, "httpd", NULL, pcmk_rsc_match_current_node)); /* And then we check failure possibilities again, except passing httpd-bundle * instead of X_0 as the first argument to native_find_rsc. */ // Fails because pcmk_rsc_match_current_node is required if a node is given assert_null(native_find_rsc(httpd_bundle, "httpd-bundle-ip-192.168.122.131", cluster01, 0)); assert_null(native_find_rsc(httpd_bundle, "httpd:0", httpd_bundle_0, 0)); assert_null(native_find_rsc(httpd_bundle, "httpd-bundle-docker-0", cluster01, 0)); assert_null(native_find_rsc(httpd_bundle, "httpd-bundle-0", cluster01, 0)); /* Check that the resource is running on the node we expect. */ assert_ptr_equal(ip_0, native_find_rsc(httpd_bundle, "httpd-bundle-ip-192.168.122.131", cluster01, pcmk_rsc_match_current_node)); assert_ptr_equal(child_0, native_find_rsc(httpd_bundle, "httpd:0", httpd_bundle_0, pcmk_rsc_match_current_node)); assert_ptr_equal(container_0, native_find_rsc(httpd_bundle, "httpd-bundle-docker-0", cluster01, pcmk_rsc_match_current_node)); assert_ptr_equal(remote_0, native_find_rsc(httpd_bundle, "httpd-bundle-0", cluster01, pcmk_rsc_match_current_node)); return false; // Do not iterate through any further replicas } static void bundle_replica_rsc(void **state) { pe__foreach_bundle_replica(httpd_bundle, bundle_first_replica, NULL); } static void clone_group_rsc(void **rsc) { assert_non_null(mysql_clone_group); /* Passes because NULL was passed for node, regardless of flags. */ assert_ptr_equal(mysql_clone_group, native_find_rsc(mysql_clone_group, "mysql-clone-group", NULL, 0)); assert_ptr_equal(mysql_clone_group, native_find_rsc(mysql_clone_group, "mysql-clone-group", NULL, pcmk_rsc_match_current_node)); assert_ptr_equal(mysql_clone_group, native_find_rsc(mysql_clone_group, "mysql-clone-group", NULL, pcmk_rsc_match_clone_only)); // Fails because pcmk_rsc_match_current_node is required if a node is given assert_null(native_find_rsc(mysql_clone_group, "mysql-clone-group", cluster01, 0)); /* Passes because one of mysql-clone-group's children is running on cluster01. */ assert_ptr_equal(mysql_clone_group, native_find_rsc(mysql_clone_group, "mysql-clone-group", cluster01, pcmk_rsc_match_current_node)); // Fails because pcmk_rsc_match_current_node is required if a node is given assert_null(native_find_rsc(mysql_clone_group, "mysql-clone-group", cluster02, 0)); /* Passes because one of mysql-clone-group's children is running on cluster02. */ assert_ptr_equal(mysql_clone_group, native_find_rsc(mysql_clone_group, "mysql-clone-group", cluster02, pcmk_rsc_match_current_node)); // Passes for previous reasons, plus includes pcmk_rsc_match_clone_only assert_ptr_equal(mysql_clone_group, native_find_rsc(mysql_clone_group, "mysql-clone-group", cluster01, pcmk_rsc_match_clone_only |pcmk_rsc_match_current_node)); assert_ptr_equal(mysql_clone_group, native_find_rsc(mysql_clone_group, "mysql-clone-group", cluster02, pcmk_rsc_match_clone_only |pcmk_rsc_match_current_node)); } static void clone_group_instance_rsc(void **rsc) { pe_resource_t *mysql_group_0 = NULL; pe_resource_t *mysql_group_1 = NULL; /* Find the "mysql-group:0" and "mysql-group:1" resources, members of "mysql-clone-group". */ for (GList *iter = mysql_clone_group->children; iter != NULL; iter = iter->next) { pe_resource_t *rsc = (pe_resource_t *) iter->data; if (strcmp(rsc->id, "mysql-group:0") == 0) { mysql_group_0 = rsc; } else if (strcmp(rsc->id, "mysql-group:1") == 0) { mysql_group_1 = rsc; } } assert_non_null(mysql_group_0); assert_non_null(mysql_group_1); /* Passes because NULL was passed for node, regardless of flags. */ assert_ptr_equal(mysql_group_0, native_find_rsc(mysql_group_0, "mysql-group:0", NULL, 0)); assert_ptr_equal(mysql_group_0, native_find_rsc(mysql_group_0, "mysql-group:0", NULL, pcmk_rsc_match_current_node)); assert_ptr_equal(mysql_group_1, native_find_rsc(mysql_group_1, "mysql-group:1", NULL, 0)); assert_ptr_equal(mysql_group_1, native_find_rsc(mysql_group_1, "mysql-group:1", NULL, pcmk_rsc_match_current_node)); // Fails because pcmk_rsc_match_current_node is required if a node is given assert_null(native_find_rsc(mysql_group_0, "mysql-group:0", cluster02, 0)); assert_null(native_find_rsc(mysql_group_1, "mysql-group:1", cluster01, 0)); /* Check that the resource is running on the node we expect. */ assert_ptr_equal(mysql_group_0, native_find_rsc(mysql_group_0, "mysql-group:0", cluster02, pcmk_rsc_match_current_node)); assert_null(native_find_rsc(mysql_group_0, "mysql-group:0", cluster01, pcmk_rsc_match_current_node)); assert_ptr_equal(mysql_group_1, native_find_rsc(mysql_group_1, "mysql-group:1", cluster01, pcmk_rsc_match_current_node)); assert_null(native_find_rsc(mysql_group_1, "mysql-group:1", cluster02, pcmk_rsc_match_current_node)); /* Passes because NULL was passed for node and base name was given, with correct flags. */ assert_ptr_equal(mysql_group_0, native_find_rsc(mysql_group_0, "mysql-group" , NULL, pcmk_rsc_match_clone_only)); - /* Passes because pe_find_any matches any base name. */ - assert_ptr_equal(mysql_group_0, native_find_rsc(mysql_group_0, "mysql-group" , NULL, pe_find_any)); - assert_ptr_equal(mysql_group_1, native_find_rsc(mysql_group_1, "mysql-group" , NULL, pe_find_any)); + // Passes because pcmk_rsc_match_basename matches any base name + assert_ptr_equal(mysql_group_0, + native_find_rsc(mysql_group_0, "mysql-group" , NULL, + pcmk_rsc_match_basename)); + assert_ptr_equal(mysql_group_1, + native_find_rsc(mysql_group_1, "mysql-group" , NULL, + pcmk_rsc_match_basename)); // Passes because pcmk_rsc_match_anon_basename matches assert_ptr_equal(mysql_group_0, native_find_rsc(mysql_group_0, "mysql-group" , NULL, pcmk_rsc_match_anon_basename)); assert_ptr_equal(mysql_group_1, native_find_rsc(mysql_group_1, "mysql-group" , NULL, pcmk_rsc_match_anon_basename)); /* Check that the resource is running on the node we expect. */ assert_ptr_equal(mysql_group_0, native_find_rsc(mysql_group_0, "mysql-group", cluster02, - pe_find_any|pcmk_rsc_match_current_node)); + pcmk_rsc_match_basename + |pcmk_rsc_match_current_node)); assert_ptr_equal(mysql_group_0, native_find_rsc(mysql_group_0, "mysql-group", cluster02, pcmk_rsc_match_anon_basename |pcmk_rsc_match_current_node)); assert_null(native_find_rsc(mysql_group_0, "mysql-group", cluster01, - pe_find_any|pcmk_rsc_match_current_node)); + pcmk_rsc_match_basename + |pcmk_rsc_match_current_node)); assert_null(native_find_rsc(mysql_group_0, "mysql-group", cluster01, pcmk_rsc_match_anon_basename |pcmk_rsc_match_current_node)); assert_ptr_equal(mysql_group_1, native_find_rsc(mysql_group_1, "mysql-group", cluster01, - pe_find_any|pcmk_rsc_match_current_node)); + pcmk_rsc_match_basename + |pcmk_rsc_match_current_node)); assert_ptr_equal(mysql_group_1, native_find_rsc(mysql_group_1, "mysql-group", cluster01, pcmk_rsc_match_anon_basename |pcmk_rsc_match_current_node)); assert_null(native_find_rsc(mysql_group_1, "mysql-group", cluster02, - pe_find_any|pcmk_rsc_match_current_node)); + pcmk_rsc_match_basename + |pcmk_rsc_match_current_node)); assert_null(native_find_rsc(mysql_group_1, "mysql-group", cluster02, pcmk_rsc_match_anon_basename |pcmk_rsc_match_current_node)); /* Fails because incorrect flags were given along with base name. */ assert_null(native_find_rsc(mysql_group_0, "mysql-group", NULL, pcmk_rsc_match_current_node)); assert_null(native_find_rsc(mysql_group_1, "mysql-group", NULL, pcmk_rsc_match_current_node)); /* And then we check failure possibilities again, except passing mysql_clone_group * instead of mysql_group_X as the first argument to native_find_rsc. */ // Fails because pcmk_rsc_match_current_node is required if a node is given assert_null(native_find_rsc(mysql_clone_group, "mysql-group:0", cluster02, 0)); assert_null(native_find_rsc(mysql_clone_group, "mysql-group:1", cluster01, 0)); /* Check that the resource is running on the node we expect. */ assert_ptr_equal(mysql_group_0, native_find_rsc(mysql_clone_group, "mysql-group:0", cluster02, pcmk_rsc_match_current_node)); assert_ptr_equal(mysql_group_0, native_find_rsc(mysql_clone_group, "mysql-group", cluster02, - pe_find_any|pcmk_rsc_match_current_node)); + pcmk_rsc_match_basename + |pcmk_rsc_match_current_node)); assert_ptr_equal(mysql_group_0, native_find_rsc(mysql_clone_group, "mysql-group", cluster02, pcmk_rsc_match_anon_basename |pcmk_rsc_match_current_node)); assert_ptr_equal(mysql_group_1, native_find_rsc(mysql_clone_group, "mysql-group:1", cluster01, pcmk_rsc_match_current_node)); assert_ptr_equal(mysql_group_1, native_find_rsc(mysql_clone_group, "mysql-group", cluster01, - pe_find_any|pcmk_rsc_match_current_node)); + pcmk_rsc_match_basename + |pcmk_rsc_match_current_node)); assert_ptr_equal(mysql_group_1, native_find_rsc(mysql_clone_group, "mysql-group", cluster01, pcmk_rsc_match_anon_basename |pcmk_rsc_match_current_node)); } static void clone_group_member_rsc(void **state) { pe_resource_t *mysql_proxy = NULL; /* Find the "mysql-proxy" resource, a member of "mysql-group". */ for (GList *iter = mysql_clone_group->children; iter != NULL; iter = iter->next) { pe_resource_t *rsc = (pe_resource_t *) iter->data; if (strcmp(rsc->id, "mysql-group:0") == 0) { for (GList *iter2 = rsc->children; iter2 != NULL; iter2 = iter2->next) { pe_resource_t *child = (pe_resource_t *) iter2->data; if (strcmp(child->id, "mysql-proxy:0") == 0) { mysql_proxy = child; break; } } break; } } assert_non_null(mysql_proxy); /* Passes because NULL was passed for node, regardless of flags. */ assert_ptr_equal(mysql_proxy, native_find_rsc(mysql_proxy, "mysql-proxy:0", NULL, 0)); assert_ptr_equal(mysql_proxy, native_find_rsc(mysql_proxy, "mysql-proxy:0", NULL, pcmk_rsc_match_current_node)); /* Passes because resource's parent is a clone. */ assert_ptr_equal(mysql_proxy, native_find_rsc(mysql_proxy, "mysql-proxy:0", NULL, pcmk_rsc_match_clone_only)); assert_ptr_equal(mysql_proxy, native_find_rsc(mysql_proxy, "mysql-proxy:0", cluster02, pcmk_rsc_match_clone_only |pcmk_rsc_match_current_node)); /* Fails because mysql-proxy:0 is not running on cluster01, even with the right flags. */ assert_null(native_find_rsc(mysql_proxy, "mysql-proxy:0", cluster01, pcmk_rsc_match_current_node)); // Fails because pcmk_rsc_match_current_node is required if a node is given assert_null(native_find_rsc(mysql_proxy, "mysql-proxy:0", cluster02, 0)); /* Passes because mysql-proxy:0 is running on cluster02. */ assert_ptr_equal(mysql_proxy, native_find_rsc(mysql_proxy, "mysql-proxy:0", cluster02, pcmk_rsc_match_current_node)); } /* TODO: Add tests for finding on assigned node (passing a node without * pcmk_rsc_match_current_node, after scheduling, for a resource that is * starting/stopping/moving. */ PCMK__UNIT_TEST(setup, teardown, cmocka_unit_test(bad_args), cmocka_unit_test(primitive_rsc), cmocka_unit_test(group_rsc), cmocka_unit_test(inactive_group_rsc), cmocka_unit_test(group_member_rsc), cmocka_unit_test(inactive_group_member_rsc), cmocka_unit_test(clone_rsc), cmocka_unit_test(inactive_clone_rsc), cmocka_unit_test(clone_instance_rsc), cmocka_unit_test(renamed_rsc), cmocka_unit_test(bundle_rsc), cmocka_unit_test(bundle_replica_rsc), cmocka_unit_test(clone_group_rsc), cmocka_unit_test(clone_group_instance_rsc), cmocka_unit_test(clone_group_member_rsc)) diff --git a/lib/pengine/utils.c b/lib/pengine/utils.c index 4c84843429..e244f70b09 100644 --- a/lib/pengine/utils.c +++ b/lib/pengine/utils.c @@ -1,900 +1,900 @@ /* * Copyright 2004-2023 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 "pe_status_private.h" extern bool pcmk__is_daemon; gboolean ghash_free_str_str(gpointer key, gpointer value, gpointer user_data); /*! * \internal * \brief Check whether we can fence a particular node * * \param[in] data_set Working set for cluster * \param[in] node Name of node to check * * \return true if node can be fenced, false otherwise */ bool pe_can_fence(const pe_working_set_t *data_set, const pe_node_t *node) { if (pe__is_guest_node(node)) { /* Guest nodes are fenced by stopping their container resource. We can * do that if the container's host is either online or fenceable. */ pe_resource_t *rsc = node->details->remote_rsc->container; for (GList *n = rsc->running_on; n != NULL; n = n->next) { pe_node_t *container_node = n->data; if (!container_node->details->online && !pe_can_fence(data_set, container_node)) { return false; } } return true; } else if (!pcmk_is_set(data_set->flags, pe_flag_stonith_enabled)) { return false; /* Turned off */ } else if (!pcmk_is_set(data_set->flags, pe_flag_have_stonith_resource)) { return false; /* No devices */ } else if (pcmk_is_set(data_set->flags, pe_flag_have_quorum)) { return true; } else if (data_set->no_quorum_policy == pcmk_no_quorum_ignore) { return true; } else if(node == NULL) { return false; } else if(node->details->online) { crm_notice("We can fence %s without quorum because they're in our membership", pe__node_name(node)); return true; } crm_trace("Cannot fence %s", pe__node_name(node)); return false; } /*! * \internal * \brief Copy a node object * * \param[in] this_node Node object to copy * * \return Newly allocated shallow copy of this_node * \note This function asserts on errors and is guaranteed to return non-NULL. */ pe_node_t * pe__copy_node(const pe_node_t *this_node) { pe_node_t *new_node = NULL; CRM_ASSERT(this_node != NULL); new_node = calloc(1, sizeof(pe_node_t)); CRM_ASSERT(new_node != NULL); new_node->rsc_discover_mode = this_node->rsc_discover_mode; new_node->weight = this_node->weight; new_node->fixed = this_node->fixed; // @COMPAT deprecated and unused new_node->count = this_node->count; new_node->details = this_node->details; return new_node; } /*! * \internal * \brief Create a node hash table from a node list * * \param[in] list Node list * * \return Hash table equivalent of node list */ GHashTable * pe__node_list2table(const GList *list) { GHashTable *result = NULL; result = pcmk__strkey_table(NULL, free); for (const GList *gIter = list; gIter != NULL; gIter = gIter->next) { pe_node_t *new_node = pe__copy_node((const pe_node_t *) gIter->data); g_hash_table_insert(result, (gpointer) new_node->details->id, new_node); } return result; } /*! * \internal * \brief Compare two nodes by name, with numeric portions sorted numerically * * Sort two node names case-insensitively like strcasecmp(), but with any * numeric portions of the name sorted numerically. For example, "node10" will * sort higher than "node9" but lower than "remotenode9". * * \param[in] a First node to compare (can be \c NULL) * \param[in] b Second node to compare (can be \c NULL) * * \retval -1 \c a comes before \c b (or \c a is \c NULL and \c b is not) * \retval 0 \c a and \c b are equal (or both are \c NULL) * \retval 1 \c a comes after \c b (or \c b is \c NULL and \c a is not) */ gint pe__cmp_node_name(gconstpointer a, gconstpointer b) { const pe_node_t *node1 = (const pe_node_t *) a; const pe_node_t *node2 = (const pe_node_t *) b; if ((node1 == NULL) && (node2 == NULL)) { return 0; } if (node1 == NULL) { return -1; } if (node2 == NULL) { return 1; } return pcmk__numeric_strcasecmp(node1->details->uname, node2->details->uname); } /*! * \internal * \brief Output node weights to stdout * * \param[in] rsc Use allowed nodes for this resource * \param[in] comment Text description to prefix lines with * \param[in] nodes If rsc is not specified, use these nodes * \param[in,out] data_set Cluster working set */ static void pe__output_node_weights(const pe_resource_t *rsc, const char *comment, GHashTable *nodes, pe_working_set_t *data_set) { pcmk__output_t *out = data_set->priv; // Sort the nodes so the output is consistent for regression tests GList *list = g_list_sort(g_hash_table_get_values(nodes), pe__cmp_node_name); for (const GList *gIter = list; gIter != NULL; gIter = gIter->next) { const pe_node_t *node = (const pe_node_t *) gIter->data; out->message(out, "node-weight", rsc, comment, node->details->uname, pcmk_readable_score(node->weight)); } g_list_free(list); } /*! * \internal * \brief Log node weights at trace level * * \param[in] file Caller's filename * \param[in] function Caller's function name * \param[in] line Caller's line number * \param[in] rsc If not NULL, include this resource's ID in logs * \param[in] comment Text description to prefix lines with * \param[in] nodes Nodes whose scores should be logged */ static void pe__log_node_weights(const char *file, const char *function, int line, const pe_resource_t *rsc, const char *comment, GHashTable *nodes) { GHashTableIter iter; pe_node_t *node = NULL; // Don't waste time if we're not tracing at this point pcmk__if_tracing({}, return); g_hash_table_iter_init(&iter, nodes); while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) { if (rsc) { qb_log_from_external_source(function, file, "%s: %s allocation score on %s: %s", LOG_TRACE, line, 0, comment, rsc->id, pe__node_name(node), pcmk_readable_score(node->weight)); } else { qb_log_from_external_source(function, file, "%s: %s = %s", LOG_TRACE, line, 0, comment, pe__node_name(node), pcmk_readable_score(node->weight)); } } } /*! * \internal * \brief Log or output node weights * * \param[in] file Caller's filename * \param[in] function Caller's function name * \param[in] line Caller's line number * \param[in] to_log Log if true, otherwise output * \param[in] rsc If not NULL, use this resource's ID in logs, * and show scores recursively for any children * \param[in] comment Text description to prefix lines with * \param[in] nodes Nodes whose scores should be shown * \param[in,out] data_set Cluster working set */ void pe__show_node_scores_as(const char *file, const char *function, int line, bool to_log, const pe_resource_t *rsc, const char *comment, GHashTable *nodes, pe_working_set_t *data_set) { if (rsc != NULL && pcmk_is_set(rsc->flags, pe_rsc_orphan)) { // Don't show allocation scores for orphans return; } if (nodes == NULL) { // Nothing to show return; } if (to_log) { pe__log_node_weights(file, function, line, rsc, comment, nodes); } else { pe__output_node_weights(rsc, comment, nodes, data_set); } // If this resource has children, repeat recursively for each if (rsc && rsc->children) { for (GList *gIter = rsc->children; gIter != NULL; gIter = gIter->next) { pe_resource_t *child = (pe_resource_t *) gIter->data; pe__show_node_scores_as(file, function, line, to_log, child, comment, child->allowed_nodes, data_set); } } } /*! * \internal * \brief Compare two resources by priority * * \param[in] a First resource to compare (can be \c NULL) * \param[in] b Second resource to compare (can be \c NULL) * * \retval -1 \c a->priority > \c b->priority (or \c b is \c NULL and \c a is * not) * \retval 0 \c a->priority == \c b->priority (or both \c a and \c b are * \c NULL) * \retval 1 \c a->priority < \c b->priority (or \c a is \c NULL and \c b is * not) */ gint pe__cmp_rsc_priority(gconstpointer a, gconstpointer b) { const pe_resource_t *resource1 = (const pe_resource_t *)a; const pe_resource_t *resource2 = (const pe_resource_t *)b; if (a == NULL && b == NULL) { return 0; } if (a == NULL) { return 1; } if (b == NULL) { return -1; } if (resource1->priority > resource2->priority) { return -1; } if (resource1->priority < resource2->priority) { return 1; } return 0; } static void resource_node_score(pe_resource_t *rsc, const pe_node_t *node, int score, const char *tag) { pe_node_t *match = NULL; if ((rsc->exclusive_discover || (node->rsc_discover_mode == pe_discover_never)) && pcmk__str_eq(tag, "symmetric_default", pcmk__str_casei)) { /* This string comparision may be fragile, but exclusive resources and * exclusive nodes should not have the symmetric_default constraint * applied to them. */ return; } else if (rsc->children) { GList *gIter = rsc->children; for (; gIter != NULL; gIter = gIter->next) { pe_resource_t *child_rsc = (pe_resource_t *) gIter->data; resource_node_score(child_rsc, node, score, tag); } } match = g_hash_table_lookup(rsc->allowed_nodes, node->details->id); if (match == NULL) { match = pe__copy_node(node); g_hash_table_insert(rsc->allowed_nodes, (gpointer) match->details->id, match); } match->weight = pcmk__add_scores(match->weight, score); pe_rsc_trace(rsc, "Enabling %s preference (%s) for %s on %s (now %s)", tag, pcmk_readable_score(score), rsc->id, pe__node_name(node), pcmk_readable_score(match->weight)); } void resource_location(pe_resource_t *rsc, const pe_node_t *node, int score, const char *tag, pe_working_set_t *data_set) { if (node != NULL) { resource_node_score(rsc, node, score, tag); } else if (data_set != NULL) { GList *gIter = data_set->nodes; for (; gIter != NULL; gIter = gIter->next) { pe_node_t *node_iter = (pe_node_t *) gIter->data; resource_node_score(rsc, node_iter, score, tag); } } else { GHashTableIter iter; pe_node_t *node_iter = NULL; g_hash_table_iter_init(&iter, rsc->allowed_nodes); while (g_hash_table_iter_next(&iter, NULL, (void **)&node_iter)) { resource_node_score(rsc, node_iter, score, tag); } } if (node == NULL && score == -INFINITY) { if (rsc->allocated_to) { crm_info("Deallocating %s from %s", rsc->id, pe__node_name(rsc->allocated_to)); free(rsc->allocated_to); rsc->allocated_to = NULL; } } } time_t get_effective_time(pe_working_set_t * data_set) { if(data_set) { if (data_set->now == NULL) { crm_trace("Recording a new 'now'"); data_set->now = crm_time_new(NULL); } return crm_time_get_seconds_since_epoch(data_set->now); } crm_trace("Defaulting to 'now'"); return time(NULL); } gboolean get_target_role(const pe_resource_t *rsc, enum rsc_role_e *role) { enum rsc_role_e local_role = pcmk_role_unknown; const char *value = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET_ROLE); CRM_CHECK(role != NULL, return FALSE); if (pcmk__str_eq(value, "started", pcmk__str_null_matches | pcmk__str_casei) || pcmk__str_eq("default", value, pcmk__str_casei)) { return FALSE; } local_role = text2role(value); if (local_role == pcmk_role_unknown) { pcmk__config_err("Ignoring '" XML_RSC_ATTR_TARGET_ROLE "' for %s " "because '%s' is not valid", rsc->id, value); return FALSE; } else if (local_role > pcmk_role_started) { if (pcmk_is_set(pe__const_top_resource(rsc, false)->flags, pe_rsc_promotable)) { if (local_role > pcmk_role_unpromoted) { /* This is what we'd do anyway, just leave the default to avoid messing up the placement algorithm */ return FALSE; } } else { pcmk__config_err("Ignoring '" XML_RSC_ATTR_TARGET_ROLE "' for %s " "because '%s' only makes sense for promotable " "clones", rsc->id, value); return FALSE; } } *role = local_role; return TRUE; } gboolean order_actions(pe_action_t * lh_action, pe_action_t * rh_action, enum pe_ordering order) { GList *gIter = NULL; pe_action_wrapper_t *wrapper = NULL; GList *list = NULL; if (order == pe_order_none) { return FALSE; } if (lh_action == NULL || rh_action == NULL) { return FALSE; } crm_trace("Creating action wrappers for ordering: %s then %s", lh_action->uuid, rh_action->uuid); /* Ensure we never create a dependency on ourselves... it's happened */ CRM_ASSERT(lh_action != rh_action); /* Filter dups, otherwise update_action_states() has too much work to do */ gIter = lh_action->actions_after; for (; gIter != NULL; gIter = gIter->next) { pe_action_wrapper_t *after = (pe_action_wrapper_t *) gIter->data; if (after->action == rh_action && (after->type & order)) { return FALSE; } } wrapper = calloc(1, sizeof(pe_action_wrapper_t)); wrapper->action = rh_action; wrapper->type = order; list = lh_action->actions_after; list = g_list_prepend(list, wrapper); lh_action->actions_after = list; wrapper = calloc(1, sizeof(pe_action_wrapper_t)); wrapper->action = lh_action; wrapper->type = order; list = rh_action->actions_before; list = g_list_prepend(list, wrapper); rh_action->actions_before = list; return TRUE; } void destroy_ticket(gpointer data) { pe_ticket_t *ticket = data; if (ticket->state) { g_hash_table_destroy(ticket->state); } free(ticket->id); free(ticket); } pe_ticket_t * ticket_new(const char *ticket_id, pe_working_set_t * data_set) { pe_ticket_t *ticket = NULL; if (pcmk__str_empty(ticket_id)) { return NULL; } if (data_set->tickets == NULL) { data_set->tickets = pcmk__strkey_table(free, destroy_ticket); } ticket = g_hash_table_lookup(data_set->tickets, ticket_id); if (ticket == NULL) { ticket = calloc(1, sizeof(pe_ticket_t)); if (ticket == NULL) { crm_err("Cannot allocate ticket '%s'", ticket_id); return NULL; } crm_trace("Creaing ticket entry for %s", ticket_id); ticket->id = strdup(ticket_id); ticket->granted = FALSE; ticket->last_granted = -1; ticket->standby = FALSE; ticket->state = pcmk__strkey_table(free, free); g_hash_table_insert(data_set->tickets, strdup(ticket->id), ticket); } return ticket; } const char * rsc_printable_id(const pe_resource_t *rsc) { return pcmk_is_set(rsc->flags, pe_rsc_unique)? rsc->id : ID(rsc->xml); } void pe__clear_resource_flags_recursive(pe_resource_t *rsc, uint64_t flags) { pe__clear_resource_flags(rsc, flags); for (GList *gIter = rsc->children; gIter != NULL; gIter = gIter->next) { pe__clear_resource_flags_recursive((pe_resource_t *) gIter->data, flags); } } void pe__clear_resource_flags_on_all(pe_working_set_t *data_set, uint64_t flag) { for (GList *lpc = data_set->resources; lpc != NULL; lpc = lpc->next) { pe_resource_t *r = (pe_resource_t *) lpc->data; pe__clear_resource_flags_recursive(r, flag); } } void pe__set_resource_flags_recursive(pe_resource_t *rsc, uint64_t flags) { pe__set_resource_flags(rsc, flags); for (GList *gIter = rsc->children; gIter != NULL; gIter = gIter->next) { pe__set_resource_flags_recursive((pe_resource_t *) gIter->data, flags); } } void trigger_unfencing(pe_resource_t *rsc, pe_node_t *node, const char *reason, pe_action_t *dependency, pe_working_set_t *data_set) { if (!pcmk_is_set(data_set->flags, pe_flag_enable_unfencing)) { /* No resources require it */ return; } else if ((rsc != NULL) && !pcmk_is_set(rsc->flags, pe_rsc_fence_device)) { /* Wasn't a stonith device */ return; } else if(node && node->details->online && node->details->unclean == FALSE && node->details->shutdown == FALSE) { pe_action_t *unfence = pe_fence_op(node, PCMK_ACTION_ON, FALSE, reason, FALSE, data_set); if(dependency) { order_actions(unfence, dependency, pe_order_optional); } } else if(rsc) { GHashTableIter iter; g_hash_table_iter_init(&iter, rsc->allowed_nodes); while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) { if(node->details->online && node->details->unclean == FALSE && node->details->shutdown == FALSE) { trigger_unfencing(rsc, node, reason, dependency, data_set); } } } } gboolean add_tag_ref(GHashTable * tags, const char * tag_name, const char * obj_ref) { pe_tag_t *tag = NULL; GList *gIter = NULL; gboolean is_existing = FALSE; CRM_CHECK(tags && tag_name && obj_ref, return FALSE); tag = g_hash_table_lookup(tags, tag_name); if (tag == NULL) { tag = calloc(1, sizeof(pe_tag_t)); if (tag == NULL) { return FALSE; } tag->id = strdup(tag_name); tag->refs = NULL; g_hash_table_insert(tags, strdup(tag_name), tag); } for (gIter = tag->refs; gIter != NULL; gIter = gIter->next) { const char *existing_ref = (const char *) gIter->data; if (pcmk__str_eq(existing_ref, obj_ref, pcmk__str_none)){ is_existing = TRUE; break; } } if (is_existing == FALSE) { tag->refs = g_list_append(tag->refs, strdup(obj_ref)); crm_trace("Added: tag=%s ref=%s", tag->id, obj_ref); } return TRUE; } /*! * \internal * \brief Check whether shutdown has been requested for a node * * \param[in] node Node to check * * \return TRUE if node has shutdown attribute set and nonzero, FALSE otherwise * \note This differs from simply using node->details->shutdown in that it can * be used before that has been determined (and in fact to determine it), * and it can also be used to distinguish requested shutdown from implicit * shutdown of remote nodes by virtue of their connection stopping. */ bool pe__shutdown_requested(const pe_node_t *node) { const char *shutdown = pe_node_attribute_raw(node, XML_CIB_ATTR_SHUTDOWN); return !pcmk__str_eq(shutdown, "0", pcmk__str_null_matches); } /*! * \internal * \brief Update a data set's "recheck by" time * * \param[in] recheck Epoch time when recheck should happen * \param[in,out] data_set Current working set */ void pe__update_recheck_time(time_t recheck, pe_working_set_t *data_set) { if ((recheck > get_effective_time(data_set)) && ((data_set->recheck_by == 0) || (data_set->recheck_by > recheck))) { data_set->recheck_by = recheck; } } /*! * \internal * \brief Extract nvpair blocks contained by a CIB XML element into a hash table * * \param[in] xml_obj XML element containing blocks of nvpair elements * \param[in] set_name If not NULL, only use blocks of this element * \param[in] rule_data Matching parameters to use when unpacking * \param[out] hash Where to store extracted name/value pairs * \param[in] always_first If not NULL, process block with this ID first * \param[in] overwrite Whether to replace existing values with same name * \param[in,out] data_set Cluster working set containing \p xml_obj */ void pe__unpack_dataset_nvpairs(const xmlNode *xml_obj, const char *set_name, const pe_rule_eval_data_t *rule_data, GHashTable *hash, const char *always_first, gboolean overwrite, pe_working_set_t *data_set) { crm_time_t *next_change = crm_time_new_undefined(); pe_eval_nvpairs(data_set->input, xml_obj, set_name, rule_data, hash, always_first, overwrite, next_change); if (crm_time_is_defined(next_change)) { time_t recheck = (time_t) crm_time_get_seconds_since_epoch(next_change); pe__update_recheck_time(recheck, data_set); } crm_time_free(next_change); } bool pe__resource_is_disabled(const pe_resource_t *rsc) { const char *target_role = NULL; CRM_CHECK(rsc != NULL, return false); target_role = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET_ROLE); if (target_role) { enum rsc_role_e target_role_e = text2role(target_role); if ((target_role_e == pcmk_role_stopped) || ((target_role_e == pcmk_role_unpromoted) && pcmk_is_set(pe__const_top_resource(rsc, false)->flags, pe_rsc_promotable))) { return true; } } return false; } /*! * \internal * \brief Check whether a resource is running only on given node * * \param[in] rsc Resource to check * \param[in] node Node to check * * \return true if \p rsc is running only on \p node, otherwise false */ bool pe__rsc_running_on_only(const pe_resource_t *rsc, const pe_node_t *node) { return (rsc != NULL) && pcmk__list_of_1(rsc->running_on) && pe__same_node((const pe_node_t *) rsc->running_on->data, node); } bool pe__rsc_running_on_any(pe_resource_t *rsc, GList *node_list) { for (GList *ele = rsc->running_on; ele; ele = ele->next) { pe_node_t *node = (pe_node_t *) ele->data; if (pcmk__str_in_list(node->details->uname, node_list, pcmk__str_star_matches|pcmk__str_casei)) { return true; } } return false; } bool pcmk__rsc_filtered_by_node(pe_resource_t *rsc, GList *only_node) { return (rsc->fns->active(rsc, FALSE) && !pe__rsc_running_on_any(rsc, only_node)); } GList * pe__filter_rsc_list(GList *rscs, GList *filter) { GList *retval = NULL; for (GList *gIter = rscs; gIter; gIter = gIter->next) { pe_resource_t *rsc = (pe_resource_t *) gIter->data; /* I think the second condition is safe here for all callers of this * function. If not, it needs to move into pe__node_text. */ if (pcmk__str_in_list(rsc_printable_id(rsc), filter, pcmk__str_star_matches) || (rsc->parent && pcmk__str_in_list(rsc_printable_id(rsc->parent), filter, pcmk__str_star_matches))) { retval = g_list_prepend(retval, rsc); } } return retval; } GList * pe__build_node_name_list(pe_working_set_t *data_set, const char *s) { GList *nodes = 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. */ nodes = g_list_prepend(nodes, 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. */ nodes = g_list_prepend(nodes, 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. */ nodes = pe__unames_with_tag(data_set, s); } } return nodes; } GList * pe__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 { + const uint32_t flags = pcmk_rsc_match_history|pcmk_rsc_match_basename; pe_resource_t *rsc = pe_find_resource_with_flags(data_set->resources, s, - pcmk_rsc_match_history - |pe_find_any); + flags); 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 a tag or a * typo or something. See pe__build_node_name_list() for more * detail. */ resources = pe__rscs_with_tag(data_set, s); } } return resources; } xmlNode * pe__failed_probe_for_rsc(const pe_resource_t *rsc, const char *name) { const pe_resource_t *parent = pe__const_top_resource(rsc, false); const char *rsc_id = rsc->id; if (parent->variant == pcmk_rsc_variant_clone) { rsc_id = pe__clone_child_id(parent); } for (xmlNode *xml_op = pcmk__xml_first_child(rsc->cluster->failed); xml_op != NULL; xml_op = pcmk__xml_next(xml_op)) { const char *value = NULL; char *op_id = NULL; /* This resource operation is not a failed probe. */ if (!pcmk_xe_mask_probe_failure(xml_op)) { continue; } /* This resource operation was not run on the given node. Note that if name is * NULL, this will always succeed. */ value = crm_element_value(xml_op, XML_LRM_ATTR_TARGET); if (value == NULL || !pcmk__str_eq(value, name, pcmk__str_casei|pcmk__str_null_matches)) { continue; } if (!parse_op_key(pe__xe_history_key(xml_op), &op_id, NULL, NULL)) { continue; // This history entry is missing an operation key } /* This resource operation's ID does not match the rsc_id we are looking for. */ if (!pcmk__str_eq(op_id, rsc_id, pcmk__str_none)) { free(op_id); continue; } free(op_id); return xml_op; } return NULL; } diff --git a/tools/crm_resource.c b/tools/crm_resource.c index 55f9e4000b..27aae3d894 100644 --- a/tools/crm_resource.c +++ b/tools/crm_resource.c @@ -1,2189 +1,2189 @@ /* * Copyright 2004-2023 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 #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_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, utilization, or element 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 int check_level; // Optional value of --validate or --force-check // 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, .check_level = -1, .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 { \ if (options.rsc_cmd != cmd_none) { \ reset_options(); \ } \ 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; static pcmk__common_args_t *args = 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); } pcmk__unregister_formats(); if (cib_conn != NULL) { cib_t *save_cib_conn = cib_conn; cib_conn = NULL; // Ensure we can't free this twice cib__clean_up_connection(&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 %u %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 = pcmk_find_cib_element(root, XML_CIB_TAG_CONSTRAINTS); 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 --element, --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 }, { "constraints", 'a', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb, "Display the location and colocation constraints that apply to a\n" INDENT "resource, and if --recursive is specified, to the resources\n" INDENT "directly or indirectly involved in those colocations.\n" INDENT "If the named resource is part of a group, or a clone or\n" INDENT "bundle instance, constraints for the collective resource\n" INDENT "will be shown unless --force is given.", NULL }, { "stack", 'A', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb, "Equivalent to --constraints --recursive", 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_OPTIONAL_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. An optional LEVEL argument can be given\n" INDENT "to control the level of checking performed.", "LEVEL" }, { "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 --element, --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 --element, --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_OPTIONAL_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. An optional LEVEL argument can be given\n" INDENT "to control the level of checking performed.", "LEVEL" }, { 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 or --constraints", 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 }, { "element", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, attr_set_type_cb, "Use resource element 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, "Force the action to be performed. See help for individual commands for\n" INDENT "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 } }; static void reset_options(void) { options.require_crmd = FALSE; options.require_node = FALSE; options.require_cib = TRUE; options.require_dataset = TRUE; options.require_resource = TRUE; options.find_flags = 0; } 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)) { pcmk__str_update(&options.v_provider, optarg); } else { pcmk__str_update(&options.v_agent, 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; } else if (pcmk__str_eq(option_name, "--element", pcmk__str_casei)) { options.attr_set_type = ATTR_SET_ELEMENT; } return TRUE; } gboolean class_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { pcmk__str_update(&options.v_class, 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 = pcmk_rsc_match_history|pcmk_rsc_match_anon_basename; return TRUE; } gboolean delete_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { SET_COMMAND(cmd_delete); options.require_dataset = FALSE; - options.find_flags = pcmk_rsc_match_history|pe_find_any; + options.find_flags = pcmk_rsc_match_history|pcmk_rsc_match_basename; 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; pcmk__str_update(&options.agent_spec, 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) { SET_COMMAND(cmd_fail); options.require_crmd = TRUE; options.require_node = TRUE; 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)) { SET_COMMAND(cmd_clear); options.find_flags = pcmk_rsc_match_history |pcmk_rsc_match_anon_basename; } else if (pcmk__str_any_of(option_name, "-B", "--ban", NULL)) { SET_COMMAND(cmd_ban); options.find_flags = pcmk_rsc_match_history |pcmk_rsc_match_anon_basename; } else if (pcmk__str_any_of(option_name, "-M", "--move", NULL)) { SET_COMMAND(cmd_move); options.find_flags = pcmk_rsc_match_history |pcmk_rsc_match_anon_basename; } else if (pcmk__str_any_of(option_name, "-q", "--query-xml", NULL)) { SET_COMMAND(cmd_query_xml); - options.find_flags = pcmk_rsc_match_history|pe_find_any; + options.find_flags = pcmk_rsc_match_history|pcmk_rsc_match_basename; } else if (pcmk__str_any_of(option_name, "-w", "--query-xml-raw", NULL)) { SET_COMMAND(cmd_query_raw_xml); - options.find_flags = pcmk_rsc_match_history|pe_find_any; + options.find_flags = pcmk_rsc_match_history|pcmk_rsc_match_basename; } else if (pcmk__str_any_of(option_name, "-W", "--locate", NULL)) { SET_COMMAND(cmd_locate); options.find_flags = pcmk_rsc_match_history |pcmk_rsc_match_anon_basename; } else if (pcmk__str_any_of(option_name, "-a", "--constraints", NULL)) { SET_COMMAND(cmd_colocations); options.find_flags = pcmk_rsc_match_history |pcmk_rsc_match_anon_basename; } else if (pcmk__str_any_of(option_name, "-A", "--stack", NULL)) { SET_COMMAND(cmd_colocations); options.find_flags = pcmk_rsc_match_history |pcmk_rsc_match_anon_basename; options.recursive = TRUE; } 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); } pcmk__str_update(&options.prop_name, optarg); - options.find_flags = pcmk_rsc_match_history|pe_find_any; + options.find_flags = pcmk_rsc_match_history|pcmk_rsc_match_basename; 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); } pcmk__str_update(&options.prop_name, optarg); - options.find_flags = pcmk_rsc_match_history|pe_find_any; + options.find_flags = pcmk_rsc_match_history|pcmk_rsc_match_basename; return TRUE; } gboolean set_prop_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { SET_COMMAND(cmd_set_property); options.require_dataset = FALSE; pcmk__str_update(&options.prop_name, optarg); - options.find_flags = pcmk_rsc_match_history|pe_find_any; + options.find_flags = pcmk_rsc_match_history|pcmk_rsc_match_basename; 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 = pcmk_rsc_match_history|pcmk_rsc_match_anon_basename; if (options.override_params == NULL) { options.override_params = pcmk__strkey_table(free, free); } if (optarg != NULL) { if (pcmk__scan_min_int(optarg, &options.check_level, 0) != pcmk_rc_ok) { g_set_error(error, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, _("Invalid check level setting: %s"), optarg); return FALSE; } } return TRUE; } gboolean restart_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { SET_COMMAND(cmd_restart); options.find_flags = pcmk_rsc_match_history|pcmk_rsc_match_anon_basename; return TRUE; } gboolean digests_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { SET_COMMAND(cmd_digests); options.find_flags = pcmk_rsc_match_history|pcmk_rsc_match_anon_basename; 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) { SET_COMMAND(cmd_why); options.require_resource = FALSE; options.find_flags = pcmk_rsc_match_history|pcmk_rsc_match_anon_basename; return TRUE; } static int ban_or_move(pcmk__output_t *out, pe_resource_t *rsc, const char *move_lifetime) { 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 == pcmk_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; g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, _("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; g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, _("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, pe_node_t *node) { 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, rsc, node); } 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); free_xml(*cib_xml_copy); *cib_xml_copy = NULL; 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) { int rc = pcmk_rc_ok; char *provider = strchr(agent_spec, ':'); lrmd_t *lrmd_conn = NULL; lrmd_list_t *list = NULL; rc = lrmd__new(&lrmd_conn, NULL, NULL, 0); if (rc != pcmk_rc_ok) { goto error; } 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; } error: if (rc != pcmk_rc_ok) { if (provider == NULL) { g_set_error(&error, PCMK__RC_ERROR, rc, _("No agents found for standard '%s'"), agent_spec); } else { g_set_error(&error, PCMK__RC_ERROR, rc, _("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) { int rc; const char *text = NULL; lrmd_t *lrmd_conn = NULL; lrmd_list_t *list = NULL; rc = lrmd__new(&lrmd_conn, NULL, NULL, 0); if (rc != pcmk_rc_ok) { goto error; } 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: g_set_error(&error, PCMK__RC_ERROR, pcmk_rc_error, "Bug"); lrmd_api_delete(lrmd_conn); return pcmk_rc_error; } error: if (rc != pcmk_rc_ok) { if (agent_spec != NULL) { rc = ENXIO; g_set_error(&error, PCMK__RC_ERROR, rc, _("No %s found for %s"), text, agent_spec); } else { rc = ENXIO; g_set_error(&error, PCMK__RC_ERROR, rc, _("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); if (*cib_xml_copy == NULL) { rc = pcmk_rc_cib_corrupt; } } 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) { data_set = pe_new_working_set(); if (data_set == NULL) { rc = ENOMEM; } else { 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) { free_xml(*cib_xml_copy); *cib_xml_copy = NULL; return rc; } cluster_status(data_set); return pcmk_rc_ok; } 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__attrd_api_clear_failures(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, pe_node_t *node) { 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, rsc, node); } 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 = ENXIO; 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) { int rc = pcmk_rc_ok; char *standard = NULL; char *provider = NULL; char *type = NULL; char *metadata = NULL; lrmd_t *lrmd_conn = NULL; rc = lrmd__new(&lrmd_conn, NULL, NULL, 0); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, _("Could not create executor connection")); lrmd_api_delete(lrmd_conn); return rc; } 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); free(metadata); } else { /* We were given a validly formatted spec, but it doesn't necessarily * match up with anything that exists. Use ENXIO as the return code * here because that maps to an exit code of CRM_EX_NOSUCH, which * probably is the most common reason to get here. */ rc = ENXIO; g_set_error(&error, PCMK__RC_ERROR, rc, _("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 and --force-*")); // 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; GOptionGroup *output_group = NULL; gchar **processed_args = NULL; GOptionContext *context = NULL; /* * Parse command line arguments */ args = pcmk__new_common_args(SUMMARY); processed_args = pcmk__cmdline_preproc(argv, "GHINSTdginpstuvx"); 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)) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("--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 { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("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); /* Add 1 for the strv[0] string below, and add another 1 for the NULL * at the end of the array so g_strjoinv knows when to stop. */ strv = calloc(len+2, sizeof(char *)); strv[0] = strdup("non-option ARGV-elements:\n"); for (gchar **s = options.remainder; *s; s++) { strv[i] = crm_strdup_printf("[%d of %d] %s\n", i, len, *s); i++; } strv[i] = NULL; exit_code = CRM_EX_USAGE; msg = g_strjoinv("", strv); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "%s", msg); g_free(msg); /* Don't try to free the last element, which is just NULL. */ for(i = 0; i < len+1; 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_execute_agent: 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: 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_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)) { 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)) { 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.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)) { exit_code = CRM_EX_DISCONNECT; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("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) { exit_code = pcmk_rc2exitc(rc); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("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) { exit_code = pcmk_rc2exitc(rc); 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) { exit_code = CRM_EX_NOSUCH; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("Resource '%s' not found"), options.rsc_id); goto done; } /* The --ban, --clear, --move, and --restart commands do not work with * instances of clone resourcs. */ if (strchr(options.rsc_id, ':') != NULL && pe_rsc_is_clone(rsc->parent) && (options.rsc_cmd == cmd_ban || options.rsc_cmd == cmd_clear || options.rsc_cmd == cmd_move || options.rsc_cmd == cmd_restart)) { exit_code = CRM_EX_INVALID_PARAM; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("Cannot operate on clone resource instance '%s'"), 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); if (node == NULL) { exit_code = CRM_EX_NOSUCH; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("Node '%s' not found"), options.host_uname); goto done; } } // 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) { exit_code = pcmk_rc2exitc(rc); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("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, 5); if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("Error connecting to %s: %s"), pcmk_ipc_name(controld_api, true), 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, (gpointer) "*"); rc = out->message(out, "resource-list", data_set, pcmk_show_inactive_rscs | pcmk_show_rsc_only | pcmk_show_pending, true, all, all, false); g_list_free(all); 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); break; case cmd_list_agents: rc = list_agents(out, options.agent_spec); break; case cmd_metadata: rc = show_metadata(out, options.agent_spec); 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, node, 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, NULL, options.v_class, options.v_provider, options.v_agent, options.operation, options.cmdline_params, options.override_params, options.timeout_ms, args->verbosity, options.force, options.check_level); } 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, options.check_level); } goto done; 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); } break; case cmd_colocations: rc = out->message(out, "locations-and-colocations", rsc, options.recursive, (bool) options.force); break; case cmd_cts: rc = pcmk_rc_ok; g_list_foreach(data_set->resources, (GFunc) cli_resource_print_cts, out); 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", 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); } 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); } if (rc == EINVAL) { exit_code = CRM_EX_USAGE; goto done; } break; case cmd_ban: if (options.host_uname == NULL) { rc = ban_or_move(out, rsc, options.move_lifetime); } 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); } if (rc == EINVAL) { exit_code = CRM_EX_USAGE; goto done; } 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 = rsc->fns->active_node(rsc, &count, NULL); bool free_params = true; const char* value = NULL; if (count > 1) { out->err(out, "%s is active on more than one node," " returning the default value for %s", rsc->id, pcmk__s(options.prop_name, "unspecified property")); 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_none)) { params = pe_rsc_params(rsc, current, data_set); free_params = false; value = g_hash_table_lookup(params, options.prop_name); } else if (pcmk__str_eq(options.attr_set_type, XML_TAG_META_SETS, pcmk__str_none)) { params = pcmk__strkey_table(free, free); get_meta_attributes(params, rsc, current, data_set); value = g_hash_table_lookup(params, options.prop_name); } else if (pcmk__str_eq(options.attr_set_type, ATTR_SET_ELEMENT, pcmk__str_none)) { value = crm_element_value(rsc->xml, options.prop_name); free_params = false; } else { params = pcmk__strkey_table(free, free); pe__unpack_dataset_nvpairs(rsc->xml, XML_TAG_UTILIZATION, NULL, params, NULL, FALSE, data_set); value = g_hash_table_lookup(params, options.prop_name); } rc = out->message(out, "attribute-list", rsc, options.prop_name, value); if (free_params) { g_hash_table_destroy(params); } break; } case cmd_set_param: if (pcmk__str_empty(options.prop_value)) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("You need to supply a value with the -v option")); 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, 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, 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, node); } break; case cmd_refresh: if (rsc == NULL) { rc = refresh(out); } else { refresh_resource(out, rsc, node); } break; case cmd_delete: rc = delete(); break; default: exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("Unimplemented command: %d"), (int) options.rsc_cmd); goto done; } /* Convert rc into an exit code. */ if (rc != pcmk_rc_ok && rc != pcmk_rc_no_output) { exit_code = pcmk_rc2exitc(rc); } /* * Clean up and exit */ done: /* When we get here, exit_code has been set one of two ways - either at one of * the spots where there's a "goto done" (which itself could have happened either * directly or by calling pcmk_rc2exitc), or just up above after any of the break * statements. * * Thus, we can use just exit_code here to decide what to do. */ if (exit_code != CRM_EX_OK && exit_code != CRM_EX_USAGE) { if (error != NULL) { char *msg = crm_strdup_printf("%s\nError performing operation: %s", error->message, crm_exit_str(exit_code)); g_clear_error(&error); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "%s", msg); free(msg); } else { g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("Error performing operation: %s"), crm_exit_str(exit_code)); } } 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_runtime.c b/tools/crm_resource_runtime.c index c51f9fe7c6..972e1913d6 100644 --- a/tools/crm_resource_runtime.c +++ b/tools/crm_resource_runtime.c @@ -1,2214 +1,2215 @@ /* * Copyright 2004-2023 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 static GList * build_node_info_list(const pe_resource_t *rsc) { GList *retval = NULL; for (const GList *iter = rsc->children; iter != NULL; iter = iter->next) { const pe_resource_t *child = (const pe_resource_t *) iter->data; for (const GList *iter2 = child->running_on; iter2 != NULL; iter2 = iter2->next) { const pe_node_t *node = (const 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) == pcmk_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; const pe_resource_t *parent = pe__const_top_resource(rsc, false); 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) == pcmk_role_promoted); retval = g_list_prepend(retval, ni); } } return retval; } // \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 rc = pcmk_rc_ok; xmlNode *xml_search = NULL; GString *xpath = NULL; const char *xpath_base = NULL; if(value) { *value = NULL; } if(the_cib == NULL) { return ENOTCONN; } xpath_base = pcmk_cib_xpath_for(XML_CIB_TAG_RESOURCES); if (xpath_base == NULL) { crm_err(XML_CIB_TAG_RESOURCES " CIB element not known (bug?)"); return ENOMSG; } xpath = g_string_sized_new(1024); pcmk__g_strcat(xpath, xpath_base, "//*[@" XML_ATTR_ID "=\"", rsc, "\"]", NULL); if (attr_set_type != NULL) { pcmk__g_strcat(xpath, "/", attr_set_type, NULL); if (set_name != NULL) { pcmk__g_strcat(xpath, "[@" XML_ATTR_ID "=\"", set_name, "\"]", NULL); } } g_string_append(xpath, "//" XML_CIB_TAG_NVPAIR "["); if (attr_id != NULL) { pcmk__g_strcat(xpath, "@" XML_ATTR_ID "=\"", attr_id, "\"", NULL); } if (attr_name != NULL) { if (attr_id != NULL) { g_string_append(xpath, " and "); } pcmk__g_strcat(xpath, "@" XML_NVPAIR_ATTR_NAME "=\"", attr_name, "\"", NULL); } g_string_append_c(xpath, ']'); rc = the_cib->cmds->query(the_cib, (const char *) xpath->str, &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_search->children != NULL) { xmlNode *child = NULL; rc = ENOTUNIQ; 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) { pcmk__str_update(value, crm_element_value(xml_search, attr)); } done: g_string_free(xpath, TRUE); 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 (rsc->variant == pcmk_rsc_variant_clone) { 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 != NULL) && (rsc->parent->variant == pcmk_rsc_variant_clone)) { 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 != NULL) && (rsc->variant == pcmk_rsc_variant_clone)) { pe_resource_t *child = rsc->children->data; if (child->variant == pcmk_rsc_variant_primitive) { 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, gboolean force) { pcmk__output_t *out = rsc->cluster->priv; int rc = pcmk_rc_ok; char *found_attr_id = NULL; GList/**/ *resources = NULL; const char *top_id = pe__const_top_resource(rsc, false)->id; if ((attr_id == NULL) && !force) { find_resource_attr(out, cib, XML_ATTR_ID, top_id, NULL, NULL, NULL, attr_name, NULL); } if (pcmk__str_eq(attr_set_type, XML_TAG_ATTR_SETS, pcmk__str_casei)) { if (!force) { rc = find_resource_attr(out, cib, XML_ATTR_ID, top_id, XML_TAG_META_SETS, attr_set, attr_id, attr_name, &found_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)", top_id, attr_name, found_attr_id); out->err(out, " Delete '%s' first or use the force option " "to override", found_attr_id); } free(found_attr_id); if (rc == pcmk_rc_ok) { return ENOTUNIQ; } } resources = g_list_append(resources, rsc); } else if (pcmk__str_eq(attr_set_type, ATTR_SET_ELEMENT, pcmk__str_none)) { crm_xml_add(rsc->xml, attr_name, attr_value); CRM_ASSERT(cib != NULL); rc = cib->cmds->replace(cib, XML_CIB_TAG_RESOURCES, rsc->xml, cib_options); rc = pcmk_legacy2rc(rc); if (rc == pcmk_rc_ok) { out->info(out, "Set attribute: name=%s value=%s", attr_name, attr_value); } return rc; } else { resources = find_matching_attr_resources(out, rsc, requested_name, attr_set, attr_set_type, attr_id, attr_name, cib, "update", force); } /* If the user specified attr_set or attr_id, the intent is to modify a * single resource, which will be the last item in the list. */ if ((attr_set != NULL) || (attr_id != NULL)) { GList *last = g_list_last(resources); resources = g_list_remove_link(resources, last); g_list_free(resources); resources = last; } for (GList *iter = resources; iter != NULL; iter = iter->next) { char *lookup_id = NULL; char *local_attr_set = NULL; const char *rsc_attr_id = attr_id; const char *rsc_attr_set = attr_set; xmlNode *xml_top = NULL; xmlNode *xml_obj = NULL; found_attr_id = NULL; rsc = (pe_resource_t *) iter->data; 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, &found_attr_id); switch (rc) { case pcmk_rc_ok: crm_debug("Found a match for name=%s: id=%s", attr_name, found_attr_id); rsc_attr_id = found_attr_id; break; case ENXIO: if (rsc_attr_set == NULL) { local_attr_set = crm_strdup_printf("%s-%s", lookup_id, attr_set_type); rsc_attr_set = local_attr_set; } if (rsc_attr_id == NULL) { found_attr_id = crm_strdup_printf("%s-%s", rsc_attr_set, attr_name); rsc_attr_id = found_attr_id; } xml_top = create_xml_node(NULL, (const char *) rsc->xml->name); 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, rsc_attr_set); break; default: free(lookup_id); free(found_attr_id); g_list_free(resources); return rc; } xml_obj = crm_create_nvpair_xml(xml_obj, rsc_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, found_attr_id, ((rsc_attr_set == NULL)? "" : " set="), pcmk__s(rsc_attr_set, ""), ((attr_name == NULL)? "" : " name="), pcmk__s(attr_name, ""), attr_value); } free_xml(xml_top); free(lookup_id); free(found_attr_id); free(local_attr_set); if (recursive && pcmk__str_eq(attr_set_type, XML_TAG_META_SETS, pcmk__str_casei)) { GList *lpc = NULL; static bool need_init = true; if (need_init) { need_init = false; pcmk__unpack_constraints(rsc->cluster); pe__clear_resource_flags_on_all(rsc->cluster, pe_rsc_detect_loop); } /* We want to set the attribute only on resources explicitly * colocated with this one, so we use rsc->rsc_cons_lhs directly * rather than the with_this_colocations() method. */ pe__set_resource_flags(rsc, pe_rsc_detect_loop); for (lpc = rsc->rsc_cons_lhs; lpc != NULL; lpc = lpc->next) { pcmk__colocation_t *cons = (pcmk__colocation_t *) lpc->data; crm_debug("Checking %s %d", cons->id, cons->score); if (!pcmk_is_set(cons->dependent->flags, pe_rsc_detect_loop) && (cons->score > 0)) { crm_debug("Setting %s=%s for dependent resource %s", attr_name, attr_value, cons->dependent->id); cli_resource_update_attribute(cons->dependent, cons->dependent->id, NULL, attr_set_type, NULL, attr_name, attr_value, recursive, cib, cib_options, 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, gboolean force) { pcmk__output_t *out = rsc->cluster->priv; int rc = pcmk_rc_ok; GList/**/ *resources = NULL; if ((attr_id == NULL) && !force) { find_resource_attr(out, cib, XML_ATTR_ID, pe__const_top_resource(rsc, false)->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 if (pcmk__str_eq(attr_set_type, ATTR_SET_ELEMENT, pcmk__str_none)) { xml_remove_prop(rsc->xml, attr_name); CRM_ASSERT(cib != NULL); rc = cib->cmds->replace(cib, XML_CIB_TAG_RESOURCES, rsc->xml, cib_options); rc = pcmk_legacy2rc(rc); if (rc == pcmk_rc_ok) { out->info(out, "Deleted attribute: %s", attr_name); } return rc; } else { resources = g_list_append(resources, rsc); } for (GList *iter = resources; iter != NULL; iter = iter->next) { char *lookup_id = NULL; xmlNode *xml_obj = NULL; char *found_attr_id = NULL; const char *rsc_attr_id = attr_id; rsc = (pe_resource_t *) iter->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, &found_attr_id); switch (rc) { case pcmk_rc_ok: break; case ENXIO: free(lookup_id); rc = pcmk_rc_ok; continue; default: free(lookup_id); g_list_free(resources); return rc; } if (rsc_attr_id == NULL) { rsc_attr_id = found_attr_id; } xml_obj = crm_create_nvpair_xml(NULL, rsc_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, found_attr_id, ((attr_set == NULL)? "" : " set="), pcmk__s(attr_set, ""), ((attr_name == NULL)? "" : " name="), pcmk__s(attr_name, "")); } free(lookup_id); free_xml(xml_obj); free(found_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 != pcmk_rsc_variant_primitive) { 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(const 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 = NULL; fail_rsc = pe_find_resource_with_flags(data_set->resources, failed_id, pcmk_rsc_match_history |pcmk_rsc_match_anon_basename); 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(const pe_resource_t *rsc, const char *operation, const char *interval_spec, const 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__attrd_api_clear_failures(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, const 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) { for (const GList *lpc = rsc->children; lpc != NULL; lpc = lpc->next) { const pe_resource_t *child = (const 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__attrd_api_clear_failures(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; } static void check_role(resource_checks_t *checks) { const char *role_s = g_hash_table_lookup(checks->rsc->meta, XML_RSC_ATTR_TARGET_ROLE); if (role_s == NULL) { return; } switch (text2role(role_s)) { case pcmk_role_stopped: checks->flags |= rsc_remain_stopped; break; case pcmk_role_unpromoted: if (pcmk_is_set(pe__const_top_resource(checks->rsc, false)->flags, pe_rsc_promotable)) { checks->flags |= rsc_unpromotable; } break; default: break; } } static void check_managed(resource_checks_t *checks) { const char *managed_s = g_hash_table_lookup(checks->rsc->meta, XML_RSC_ATTR_MANAGED); if ((managed_s != NULL) && !crm_is_true(managed_s)) { checks->flags |= rsc_unmanaged; } } static void check_locked(resource_checks_t *checks) { if (checks->rsc->lock_node != NULL) { checks->flags |= rsc_locked; checks->lock_node = checks->rsc->lock_node->details->uname; } } static bool node_is_unhealthy(pe_node_t *node) { switch (pe__health_strategy(node->details->data_set)) { case pcmk__health_strategy_none: break; case pcmk__health_strategy_no_red: if (pe__node_health(node) < 0) { return true; } break; case pcmk__health_strategy_only_green: if (pe__node_health(node) <= 0) { return true; } break; case pcmk__health_strategy_progressive: case pcmk__health_strategy_custom: /* @TODO These are finite scores, possibly with rules, and possibly * combining with other scores, so attributing these as a cause is * nontrivial. */ break; } return false; } static void check_node_health(resource_checks_t *checks, pe_node_t *node) { if (node == NULL) { GHashTableIter iter; bool allowed = false; bool all_nodes_unhealthy = true; g_hash_table_iter_init(&iter, checks->rsc->allowed_nodes); while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) { allowed = true; if (!node_is_unhealthy(node)) { all_nodes_unhealthy = false; break; } } if (allowed && all_nodes_unhealthy) { checks->flags |= rsc_node_health; } } else if (node_is_unhealthy(node)) { checks->flags |= rsc_node_health; } } int cli_resource_check(pcmk__output_t *out, pe_resource_t *rsc, pe_node_t *node) { resource_checks_t checks = { .rsc = rsc }; check_role(&checks); check_managed(&checks); check_locked(&checks); check_node_health(&checks, node); return out->message(out, "resource-check-list", &checks); } // \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 (pcmk__strcase_any_of(host, node->details->uname, node->details->id, NULL)) { 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 == pcmk_rsc_variant_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,out] data_set Working set instance to update * \param[in,out] 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); pe__set_working_set_flags(data_set, pe_flag_no_counts|pe_flag_no_compat); 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->input, pe_flag_no_counts|pe_flag_no_compat, data_set); prev_quiet = out->is_quiet(out); out->quiet = true; pcmk__simulate_transition(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; } /*! * \internal * \brief Find the maximum stop timeout of a resource and its children (if any) * * \param[in,out] rsc Resource to get timeout for * * \return Maximum stop timeout for \p rsc (in milliseconds) */ static int max_rsc_stop_timeout(pe_resource_t *rsc) { pe_action_t *stop = NULL; long long result_ll; int max_delay = 0; if (rsc == NULL) { return 0; } // If resource is collective, use maximum of its children's stop timeouts if (rsc->children != NULL) { for (GList *iter = rsc->children; iter; iter = iter->next) { pe_resource_t *child = iter->data; int delay = max_rsc_stop_timeout(child); if (delay > max_delay) { pe_rsc_trace(rsc, "Maximum stop timeout for %s is now %s due to %s", rsc->id, pcmk__readable_interval(delay), child->id); max_delay = delay; } } return max_delay; } /* Create a (transient) instance of the resource's stop action, to fully * evaluate its timeout for rules, defaults, etc. * * @TODO This currently ignores node (which might matter for rules) */ stop = custom_action(rsc, stop_key(rsc), PCMK_ACTION_STOP, NULL, TRUE, FALSE, rsc->cluster); if ((pcmk__scan_ll(g_hash_table_lookup(stop->meta, XML_ATTR_TIMEOUT), &result_ll, -1LL) == pcmk_rc_ok) && (result_ll >= 0) && (result_ll <= INT_MAX)) { max_delay = (int) result_ll; } pe_free_action(stop); return max_delay; } /*! * \internal * \brief Find a reasonable waiting time for stopping any one resource in a list * * \param[in,out] data_set Cluster working set * \param[in] resources List of names of resources that will be stopped * * \return Rough estimate of a reasonable time to wait (in seconds) to stop any * one resource in \p resources * \note This estimate is very rough, simply the maximum stop timeout of all * given resources and their children, plus a small fudge factor. It does * not account for children that must be stopped in sequence, action * throttling, or any demotions needed. It checks the stop timeout, even * if the resources in question are actually being started. */ static int wait_time_estimate(pe_working_set_t *data_set, const GList *resources) { int max_delay = 0; // Find maximum stop timeout in milliseconds for (const GList *item = resources; item != NULL; item = item->next) { pe_resource_t *rsc = pe_find_resource(data_set->resources, (const char *) (item->data)); int delay = max_rsc_stop_timeout(rsc); if (delay > max_delay) { pe_rsc_trace(rsc, "Wait time is now %s due to %s", pcmk__readable_interval(delay), rsc->id); max_delay = delay; } } return (max_delay / 1000) + 5; } #define waiting_for_starts(d, r, h) ((d != NULL) || \ (!resource_is_running_on((r), (h)))) /*! * \internal * \brief Restart a resource (on a particular host if requested). * * \param[in,out] out Output object * \param[in,out] rsc The resource to restart * \param[in] node Node to restart resource on (NULL for all) * \param[in] move_lifetime If not NULL, how long constraint should * remain in effect (as ISO 8601 string) * \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, it will be calculated based on * the resource timeout) * \param[in,out] cib Connection to the CIB manager * \param[in] cib_options Group of enum cib_call_options flags to * use with CIB calls * \param[in] promoted_role_only If true, limit to promoted instances * \param[in] force If true, apply only to requested instance * if part of a collective resource * * \return Standard Pacemaker return code (exits on certain failures) */ int cli_resource_restart(pcmk__output_t *out, pe_resource_t *rsc, const pe_node_t *node, 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 *lookup_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; pe_resource_t *parent = uber_parent(rsc); bool running = false; const char *id = rsc->clone_name ? rsc->clone_name : rsc->id; const char *host = node ? node->details->uname : NULL; /* If the implicit resource or primitive resource of a bundle is given, operate on the * bundle itself instead. */ if (pe_rsc_is_bundled(rsc)) { rsc = parent->parent; } running = resource_is_running_on(rsc, host); if (pe_rsc_is_clone(parent) && !running) { if (pe_rsc_is_unique_clone(parent)) { lookup_id = strdup(rsc->id); } else { lookup_id = clone_strip(rsc->id); } rsc = parent->fns->find_rsc(parent, lookup_id, node, - pe_find_any|pcmk_rsc_match_current_node); + pcmk_rsc_match_basename + |pcmk_rsc_match_current_node); free(lookup_id); running = resource_is_running_on(rsc, host); } if (!running) { 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_unique_clone(parent)) { lookup_id = strdup(rsc->id); } else { lookup_id = clone_strip(rsc->id); } if (host) { if (pe_rsc_is_clone(rsc) || pe_bundle_replicas(rsc)) { stop_via_ban = true; } else if (pe_rsc_is_clone(parent)) { stop_via_ban = true; free(lookup_id); lookup_id = strdup(parent->id); } } /* 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; 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, lookup_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). */ find_resource_attr(out, cib, XML_NVPAIR_ATTR_VALUE, lookup_id, NULL, NULL, NULL, XML_RSC_ATTR_TARGET_ROLE, &orig_target_role); rc = cli_resource_update_attribute(rsc, rsc_id, NULL, XML_TAG_META_SETS, NULL, XML_RSC_ATTR_TARGET_ROLE, PCMK_ACTION_STOPPED, FALSE, cib, cib_options, 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 != NULL) { g_list_free_full(current_active, free); current_active = NULL; } if (restart_target_active != NULL) { g_list_free_full(restart_target_active, free); restart_target_active = NULL; } 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 = wait_time_estimate(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 != NULL) { g_list_free_full(current_active, free); current_active = NULL; } current_active = get_active_resources(host, data_set->resources); g_list_free(list_delta); list_delta = NULL; 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(lookup_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, 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, 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 != NULL) { g_list_free_full(target_active, free); target_active = NULL; } 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 = wait_time_estimate(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 != NULL) { g_list_free_full(current_active, free); current_active = NULL; } /* 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(lookup_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, 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, force); } done: if (list_delta != NULL) { g_list_free(list_delta); } if (current_active != NULL) { g_list_free_full(current_active, free); } if (target_active != NULL && (target_active != restart_target_active)) { g_list_free_full(target_active, free); } if (restart_target_active != NULL) { g_list_free_full(restart_target_active, free); } free(rsc_id); free(lookup_id); pe_free_working_set(data_set); return rc; } static inline bool action_is_pending(const 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(PCMK_ACTION_NOTIFY, action->task, pcmk__str_casei)) { return false; } return true; } /*! * \internal * \brief Check whether 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, otherwise false */ static bool actions_are_pending(const GList *actions) { for (const GList *action = actions; action != NULL; action = action->next) { const pe_action_t *a = (const pe_action_t *) action->data; if (action_is_pending(a)) { crm_notice("Waiting for %s (flags=%#.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, pe__node_name(a->node)); } 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,out] out Output object * \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,out] 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; } do { /* Abort if timeout is reached */ time_diff = expire_time - time(NULL); if (time_diff > 0) { crm_info("Waiting up to %lld seconds for cluster actions to complete", (long long) 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->input, pe_flag_no_counts|pe_flag_no_compat, data_set); 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; } static const char * get_action(const char *rsc_action) { const char *action = NULL; if (pcmk__str_eq(rsc_action, "validate", pcmk__str_casei)) { action = PCMK_ACTION_VALIDATE_ALL; } else if (pcmk__str_eq(rsc_action, "force-check", pcmk__str_casei)) { action = PCMK_ACTION_MONITOR; } else if (pcmk__strcase_any_of(rsc_action, "force-start", "force-stop", "force-demote", "force-promote", NULL)) { action = rsc_action+6; } else { action = rsc_action; } return action; } /*! * \brief Set up environment variables as expected by resource agents * * When the cluster executes resource agents, it adds certain environment * variables (directly or via resource meta-attributes) expected by some * resource agents. Add the essential ones that many resource agents expect, so * the behavior is the same for command-line execution. * * \param[in,out] params Resource parameters that will be passed to agent * \param[in] timeout_ms Action timeout (in milliseconds) * \param[in] check_level OCF check level * \param[in] verbosity Verbosity level */ static void set_agent_environment(GHashTable *params, int timeout_ms, int check_level, int verbosity) { g_hash_table_insert(params, strdup("CRM_meta_timeout"), crm_strdup_printf("%d", timeout_ms)); g_hash_table_insert(params, strdup(XML_ATTR_CRM_VERSION), strdup(CRM_FEATURE_SET)); if (check_level >= 0) { char *level = crm_strdup_printf("%d", check_level); setenv("OCF_CHECK_LEVEL", level, 1); free(level); } setenv("HA_debug", (verbosity > 0)? "1" : "0", 1); if (verbosity > 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); } /*! * \internal * \brief Apply command-line overrides to resource parameters * * \param[in,out] params Parameters to be passed to agent * \param[in] overrides Parameters to override (or NULL if none) */ static void apply_overrides(GHashTable *params, GHashTable *overrides) { if (overrides != NULL) { GHashTableIter iter; char *name = NULL; char *value = NULL; g_hash_table_iter_init(&iter, overrides); while (g_hash_table_iter_next(&iter, (gpointer *) &name, (gpointer *) &value)) { g_hash_table_replace(params, strdup(name), strdup(value)); } } } 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 *rsc_action, GHashTable *params, GHashTable *override_hash, int timeout_ms, int resource_verbose, gboolean force, int check_level) { const char *class = rsc_class; const char *action = get_action(rsc_action); crm_exit_t exit_code = CRM_EX_OK; svc_action_t *op = NULL; // If no timeout was provided, use the same default as the cluster if (timeout_ms == 0) { timeout_ms = PCMK_DEFAULT_ACTION_TIMEOUT_MS; } set_agent_environment(params, timeout_ms, check_level, resource_verbose); apply_overrides(params, override_hash); op = services__create_resource_action(rsc_name? rsc_name : "test", rsc_class, rsc_prov, rsc_type, action, 0, timeout_ms, params, 0); if (op == NULL) { out->err(out, "Could not execute %s using %s%s%s:%s: %s", action, rsc_class, (rsc_prov? ":" : ""), (rsc_prov? rsc_prov : ""), rsc_type, strerror(ENOMEM)); g_hash_table_destroy(params); return CRM_EX_OSERR; } if (pcmk__str_eq(rsc_class, PCMK_RESOURCE_CLASS_SERVICE, pcmk__str_casei)) { class = resources_find_service_class(rsc_type); } if (!pcmk__strcase_any_of(class, PCMK_RESOURCE_CLASS_OCF, PCMK_RESOURCE_CLASS_LSB, NULL)) { services__format_result(op, CRM_EX_UNIMPLEMENT_FEATURE, PCMK_EXEC_ERROR, "Manual execution of the %s standard is " "unsupported", pcmk__s(class, "unspecified")); } if (op->rc != PCMK_OCF_UNKNOWN) { exit_code = op->rc; goto done; } services_action_sync(op); // Map results to OCF codes for consistent reporting to user { enum ocf_exitcode ocf_code = services_result2ocf(class, action, op->rc); // Cast variable instead of function return to keep compilers happy exit_code = (crm_exit_t) ocf_code; } done: out->message(out, "resource-agent-action", resource_verbose, rsc_class, rsc_prov, rsc_type, rsc_name, rsc_action, override_hash, exit_code, op->status, services__exit_reason(op), op->stdout_data, op->stderr_data); services_action_free(op); 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, int check_level) { 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; GHashTable *params = NULL; if (pcmk__strcase_any_of(rsc_action, "force-start", "force-demote", "force-promote", NULL)) { 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", rsc_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); } } 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 == pcmk_rsc_variant_group) { out->err(out, "Sorry, the %s option doesn't support group resources", rsc_action); return CRM_EX_UNIMPLEMENT_FEATURE; } else if (pe_rsc_is_bundled(rsc)) { out->err(out, "Sorry, the %s option doesn't support bundled 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, get_action(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, rsc_action, params, override_hash, timeout_ms, resource_verbose, force, check_level); return exit_code; } // \return Standard Pacemaker return code int cli_resource_move(const 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)) { const pe_resource_t *p = pe__const_top_resource(rsc, false); 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 --promoted 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)) { unsigned int promoted_count = 0; pe_node_t *promoted_node = NULL; for (const GList *iter = rsc->children; iter; iter = iter->next) { const pe_resource_t *child = (const pe_resource_t *) iter->data; enum rsc_role_e child_role = child->fns->state(child, TRUE); if (child_role == pcmk_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", pe__node_name(dest)); } 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 %s%s", rsc->id, (promoted_role_only? " (promoted)" : ""), pe__node_name(dest), 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) { /* 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, pe__node_name(dest)); 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; }