diff --git a/daemons/execd/cts-exec-helper.c b/daemons/execd/cts-exec-helper.c index ef80844570..ed51b76add 100644 --- a/daemons/execd/cts-exec-helper.c +++ b/daemons/execd/cts-exec-helper.c @@ -1,691 +1,674 @@ /* * Copyright 2012-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include +#include #include static pcmk__cli_option_t long_options[] = { // long option, argument type, storage, short option, description, flags { "help", no_argument, NULL, '?', NULL, pcmk__option_default }, { "verbose", no_argument, NULL, 'V', "\t\tPrint out logs and events to screen", pcmk__option_default }, { "quiet", no_argument, NULL, 'Q', "\t\tSuppress all output to screen", pcmk__option_default }, { "tls", no_argument, NULL, 'S', "\t\tUse TLS backend for local connection", pcmk__option_default }, { "listen", required_argument, NULL, 'l', "\tListen for a specific event string", pcmk__option_default }, { "api-call", required_argument, NULL, 'c', "\tDirectly relates to executor API functions", pcmk__option_default }, { "no-wait", no_argument, NULL, 'w', "\tMake api call and do not wait for result", pcmk__option_default }, { "is-running", no_argument, NULL, 'R', "\tDetermine if a resource is registered and running", pcmk__option_default }, { "notify-orig", no_argument, NULL, 'n', "\tOnly notify this client the results of an API action", pcmk__option_default }, { "notify-changes", no_argument, NULL, 'o', "\tOnly notify client changes to recurring operations", pcmk__option_default }, { "-spacer-", no_argument, NULL, '-', "\nParameters for api-call option", pcmk__option_default }, { "action", required_argument, NULL, 'a', NULL, pcmk__option_default }, { "rsc-id", required_argument, NULL, 'r', NULL, pcmk__option_default }, { "cancel-call-id", required_argument, NULL, 'x', NULL, pcmk__option_default }, { "provider", required_argument, NULL, 'P', NULL, pcmk__option_default }, { "class", required_argument, NULL, 'C', NULL, pcmk__option_default }, { "type", required_argument, NULL, 'T', NULL, pcmk__option_default }, { "interval", required_argument, NULL, 'i', NULL, pcmk__option_default }, { "timeout", required_argument, NULL, 't', NULL, pcmk__option_default }, { "start-delay", required_argument, NULL, 's', NULL, pcmk__option_default }, { "param-key", required_argument, NULL, 'k', NULL, pcmk__option_default }, { "param-val", required_argument, NULL, 'v', NULL, pcmk__option_default }, { "-spacer-", no_argument, NULL, '-', NULL, pcmk__option_default }, { 0, 0, 0, 0 } }; -static cib_t *cib_conn = NULL; static int exec_call_id = 0; static int exec_call_opts = 0; static gboolean start_test(gpointer user_data); static void try_connect(void); static struct { int verbose; int quiet; guint interval_ms; int timeout; int start_delay; int cancel_call_id; int no_wait; int is_running; int no_connect; const char *api_call; const char *rsc_id; const char *provider; const char *class; const char *type; const char *action; const char *listen; lrmd_key_value_t *params; } options; static GMainLoop *mainloop = NULL; static lrmd_t *lrmd_conn = NULL; static char event_buf_v0[1024]; static void test_exit(crm_exit_t exit_code) { lrmd_api_delete(lrmd_conn); crm_exit(exit_code); } #define print_result(result) \ if (!options.quiet) { \ result; \ } \ #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(printf("LISTEN EVENT SUCCESSFUL\n")); 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(printf("API-CALL SUCCESSFUL for 'exec'\n")); } else { print_result(printf("API-CALL FAILURE for 'exec', rc:%d lrmd_op_status:%s\n", 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(printf("LISTEN EVENT FAILURE - timeout occurred, never found.\n")); 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(printf("API CONNECTION FAILURE\n")); 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, exec_call_opts, options.params); if (rc > 0) { exec_call_id = rc; print_result(printf("API-CALL 'exec' action pending, waiting on response\n")); } } 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(printf("RSC_INFO: id:%s class:%s provider:%s type:%s\n", 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(printf("%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(printf("%d agents found\n", rc)); for (iter = list; iter != NULL; iter = iter->next) { print_result(printf("%s\n", iter->val)); } lrmd_list_freeall(list); rc = 0; } else { print_result(printf("API_CALL FAILURE - no agents found\n")); 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(printf("%d providers found\n", rc)); for (iter = list; iter != NULL; iter = iter->next) { print_result(printf("%s\n", iter->val)); } lrmd_list_freeall(list); rc = 0; } else { print_result(printf("API_CALL FAILURE - no providers found\n")); 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(printf("%d standards found\n", rc)); for (iter = list; iter != NULL; iter = iter->next) { print_result(printf("%s\n", iter->val)); } lrmd_list_freeall(list); rc = 0; } else { print_result(printf("API_CALL FAILURE - no providers found\n")); 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(printf("RECURRING_OP: %s_%s_%s timeout=%sms\n", 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(printf("API-CALL FAILURE unknown action '%s'\n", options.action)); test_exit(CRM_EX_ERROR); } if (rc < 0) { print_result(printf("API-CALL FAILURE for '%s' api_rc:%d\n", options.api_call, rc)); test_exit(CRM_EX_ERROR); } if (options.api_call && rc == pcmk_ok) { print_result(printf("API-CALL SUCCESSFUL for '%s'\n", 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; } static int generate_params(void) { int rc = 0; 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; if (options.params) { return 0; } 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); - cib_conn = cib_new(); - rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_query); - if (rc != pcmk_ok) { - crm_err("Could not connect to the CIB manager: %s", pcmk_strerror(rc)); - rc = -1; - goto param_gen_bail; - } + rc = cib__signon_query(NULL, &cib_xml_copy); - rc = cib_conn->cmds->query(cib_conn, NULL, &cib_xml_copy, cib_scope_local | cib_sync_call); - if (rc != pcmk_ok) { - crm_err("Error retrieving cib copy: %s (%d)", pcmk_strerror(rc), rc); - goto param_gen_bail; - - } else if (cib_xml_copy == NULL) { - rc = -ENODATA; - crm_err("Error retrieving cib copy: %s (%d)", pcmk_strerror(rc), rc); + if (rc != pcmk_rc_ok) { + crm_err("CIB query failed: %s", pcmk_rc_str(rc)); goto param_gen_bail; } if (cli_config_update(&cib_xml_copy, NULL, FALSE) == FALSE) { crm_err("Error updating cib configuration"); rc = -1; goto param_gen_bail; } data_set->input = cib_xml_copy; data_set->now = crm_time_new(NULL); cluster_status(data_set); if (options.rsc_id) { rsc = pe_find_resource_with_flags(data_set->resources, options.rsc_id, pe_find_renamed|pe_find_any); } if (!rsc) { crm_err("Resource does not exist in config"); rc = -1; goto param_gen_bail; } params = pe_rsc_params(rsc, NULL, data_set); meta = pcmk__strkey_table(free, free); get_meta_attributes(meta, rsc, NULL, data_set); if (params != NULL) { char *key = NULL; char *value = 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); } } if (meta) { char *key = NULL; char *value = 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); options.params = lrmd_key_value_add(options.params, crm_name, value); free(crm_name); } g_hash_table_destroy(meta); } param_gen_bail: pe_free_working_set(data_set); return rc; } int main(int argc, char **argv) { int option_index = 0; int argerr = 0; int flag; char *key = NULL; char *val = NULL; gboolean use_tls = FALSE; crm_trigger_t *trig; pcmk__cli_init_logging("cts-exec-helper", 0); pcmk__set_cli_options(NULL, " [options]", long_options, "inject commands into the Pacemaker executor, " "and watch for events"); while (1) { flag = pcmk__next_cli_option(argc, argv, &option_index, NULL); if (flag == -1) break; switch (flag) { case '?': pcmk__cli_help(flag, CRM_EX_OK); break; case 'V': ++options.verbose; crm_bump_log_level(argc, argv); break; case 'Q': options.quiet = 1; options.verbose = 0; break; case 'l': options.listen = optarg; break; case 'w': options.no_wait = 1; break; case 'R': options.is_running = 1; break; case 'n': exec_call_opts = lrmd_opt_notify_orig_only; break; case 'o': exec_call_opts = lrmd_opt_notify_changes_only; break; case 'c': options.api_call = optarg; break; case 'a': options.action = optarg; break; case 'r': options.rsc_id = optarg; break; case 'x': if(optarg) { options.cancel_call_id = atoi(optarg); } break; case 'P': options.provider = optarg; break; case 'C': options.class = optarg; break; case 'T': options.type = optarg; break; case 'i': if(optarg) { options.interval_ms = crm_parse_interval_spec(optarg); } break; case 't': if(optarg) { options.timeout = atoi(optarg); } break; case 's': if(optarg) { options.start_delay = atoi(optarg); } break; case 'k': key = optarg; if (key && val) { options.params = lrmd_key_value_add(options.params, key, val); key = val = NULL; } break; case 'v': val = optarg; if (key && val) { options.params = lrmd_key_value_add(options.params, key, val); key = val = NULL; } break; case 'S': use_tls = TRUE; break; default: ++argerr; break; } } if (argerr) { pcmk__cli_help('?', CRM_EX_USAGE); } if (optind > argc) { ++argerr; } if (!options.listen && pcmk__strcase_any_of(options.api_call, "metadata", "list_agents", "list_standards", "list_ocf_providers", NULL)) { options.no_connect = 1; } crm_log_init(NULL, LOG_INFO, TRUE, (options.verbose? TRUE : FALSE), argc, argv, FALSE); if (options.is_running) { if (!options.timeout) { options.timeout = 30000; } options.interval_ms = 0; if (!options.rsc_id) { crm_err("rsc-id must be given when is-running is used"); test_exit(CRM_EX_ERROR); } if (generate_params()) { print_result(printf ("Failed to retrieve rsc parameters from cib, can not determine if rsc is running.\n")); test_exit(CRM_EX_ERROR); } options.api_call = "exec"; options.action = "monitor"; exec_call_opts = lrmd_opt_notify_orig_only; } /* if we can't perform an api_call or listen for events, * there is nothing to do */ if (!options.api_call && !options.listen) { crm_err("Nothing to be done. Please specify 'api-call' and/or 'listen'"); return CRM_EX_OK; } if (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); - if (cib_conn != NULL) { - cib_conn->cmds->signoff(cib_conn); - cib_delete(cib_conn); - } - test_exit(CRM_EX_OK); return CRM_EX_OK; } diff --git a/include/crm/cib/internal.h b/include/crm/cib/internal.h index 365fa28c5e..fa28d2dc7a 100644 --- a/include/crm/cib/internal.h +++ b/include/crm/cib/internal.h @@ -1,219 +1,233 @@ /* * Copyright 2004-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef CIB_INTERNAL__H # define CIB_INTERNAL__H # include # include # include # define CIB_OP_SLAVE "cib_slave" # define CIB_OP_SLAVEALL "cib_slave_all" # define CIB_OP_MASTER "cib_master" # define CIB_OP_SYNC "cib_sync" # define CIB_OP_SYNC_ONE "cib_sync_one" # define CIB_OP_ISMASTER "cib_ismaster" # define CIB_OP_BUMP "cib_bump" # define CIB_OP_QUERY "cib_query" # define CIB_OP_CREATE "cib_create" # define CIB_OP_MODIFY "cib_modify" # define CIB_OP_DELETE "cib_delete" # define CIB_OP_ERASE "cib_erase" # define CIB_OP_REPLACE "cib_replace" # define CIB_OP_APPLY_DIFF "cib_apply_diff" # define CIB_OP_UPGRADE "cib_upgrade" # define CIB_OP_DELETE_ALT "cib_delete_alt" # define F_CIB_CLIENTID "cib_clientid" # define F_CIB_CALLOPTS "cib_callopt" # define F_CIB_CALLID "cib_callid" # define F_CIB_CALLDATA "cib_calldata" # define F_CIB_OPERATION "cib_op" # define F_CIB_ISREPLY "cib_isreplyto" # define F_CIB_SECTION "cib_section" # define F_CIB_HOST "cib_host" # define F_CIB_RC "cib_rc" # define F_CIB_UPGRADE_RC "cib_upgrade_rc" # define F_CIB_DELEGATED "cib_delegated_from" # define F_CIB_OBJID "cib_object" # define F_CIB_OBJTYPE "cib_object_type" # define F_CIB_EXISTING "cib_existing_object" # define F_CIB_SEENCOUNT "cib_seen" # define F_CIB_TIMEOUT "cib_timeout" # define F_CIB_UPDATE "cib_update" # define F_CIB_CALLBACK_TOKEN "cib_async_id" # define F_CIB_GLOBAL_UPDATE "cib_update" # define F_CIB_UPDATE_RESULT "cib_update_result" # define F_CIB_CLIENTNAME "cib_clientname" # define F_CIB_NOTIFY_TYPE "cib_notify_type" # define F_CIB_NOTIFY_ACTIVATE "cib_notify_activate" # define F_CIB_UPDATE_DIFF "cib_update_diff" # define F_CIB_USER "cib_user" # define F_CIB_LOCAL_NOTIFY_ID "cib_local_notify_id" # define F_CIB_PING_ID "cib_ping_id" # define F_CIB_SCHEMA_MAX "cib_schema_max" # define T_CIB "cib" # define T_CIB_NOTIFY "cib_notify" /* notify sub-types */ # define T_CIB_PRE_NOTIFY "cib_pre_notify" # define T_CIB_POST_NOTIFY "cib_post_notify" # define T_CIB_UPDATE_CONFIRM "cib_update_confirmation" # define T_CIB_REPLACE_NOTIFY "cib_refresh_notify" gboolean cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *updates, int *_admin_epoch, int *_epoch, int *_updates); gboolean cib_read_config(GHashTable * options, xmlNode * current_cib); void verify_cib_options(GHashTable * options); gboolean cib_internal_config_changed(xmlNode * diff); typedef struct cib_notify_client_s { const char *event; const char *obj_id; /* implement one day */ const char *obj_type; /* implement one day */ void (*callback) (const char *event, xmlNode * msg); } cib_notify_client_t; typedef struct cib_callback_client_s { void (*callback) (xmlNode *, int, int, xmlNode *, void *); const char *id; void *user_data; gboolean only_success; struct timer_rec_s *timer; void (*free_func)(void *); } cib_callback_client_t; struct timer_rec_s { int call_id; int timeout; guint ref; cib_t *cib; }; #define cib__set_call_options(cib_call_opts, call_for, flags_to_set) do { \ cib_call_opts = pcmk__set_flags_as(__func__, __LINE__, \ LOG_TRACE, "CIB call", (call_for), (cib_call_opts), \ (flags_to_set), #flags_to_set); \ } while (0) #define cib__clear_call_options(cib_call_opts, call_for, flags_to_clear) do { \ cib_call_opts = pcmk__clear_flags_as(__func__, __LINE__, \ LOG_TRACE, "CIB call", (call_for), (cib_call_opts), \ (flags_to_clear), #flags_to_clear); \ } while (0) typedef int (*cib_op_t) (const char *, int, const char *, xmlNode *, xmlNode *, xmlNode *, xmlNode **, xmlNode **); cib_t *cib_new_variant(void); int cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_query, const char *section, xmlNode * req, xmlNode * input, gboolean manage_counters, gboolean * config_changed, xmlNode * current_cib, xmlNode ** result_cib, xmlNode ** diff, xmlNode ** output); xmlNode *cib_create_op(int call_id, const char *token, const char *op, const char *host, const char *section, xmlNode * data, int call_options, const char *user_name); void cib_native_callback(cib_t * cib, xmlNode * msg, int call_id, int rc); void cib_native_notify(gpointer data, gpointer user_data); int cib_native_register_notification(cib_t * cib, const char *callback, int enabled); gboolean cib_client_register_callback(cib_t * cib, int call_id, int timeout, gboolean only_success, void *user_data, const char *callback_name, void (*callback) (xmlNode *, int, int, xmlNode *, void *)); gboolean cib_client_register_callback_full(cib_t *cib, int call_id, int timeout, gboolean only_success, void *user_data, const char *callback_name, void (*callback)(xmlNode *, int, int, xmlNode *, void *), void (*free_func)(void *)); int cib_process_query(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_erase(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_bump(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_replace(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_create(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_modify(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_delete(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_diff(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_upgrade(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); /*! * \internal * \brief Core function to manipulate with/query CIB/XML per xpath + arguments * \param[in] op, the operation to be performed: * CIB_OP_{CREATE,DELETE,MODIFY,QUERY,REPLACE} * \param[in] options, ORed flags per relevant \c cib_call_options enumeration: * cib_{multiple,no_children,xpath_address} * \param[in] section, xpath defining place of interest in * {existing,result}_cib * \param[in] req, UNUSED * \param[in] input, the input operand for * CIB_OP_{CREATE,MODIFY,REPLACE} * \param[in] existing_cib, the input operand (CIB) for \c CIB_OP_QUERY * \param[inout] result_cib, the operand and result for * CIB_OP_{CREATE,DELETE,MODIFY,REPLACE} * \param[out] answer, the result for \c CIB_OP_QUERY, structured per \c options * * \retval \c pcmk_ok (0) for success, different value for failure */ int cib_process_xpath(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); gboolean cib_config_changed(xmlNode * last, xmlNode * next, xmlNode ** diff); gboolean update_results(xmlNode * failed, xmlNode * target, const char *operation, int return_code); int cib_update_counter(xmlNode * xml_obj, const char *field, gboolean reset); int cib_internal_op(cib_t * cib, const char *op, const char *host, const char *section, xmlNode * data, xmlNode ** output_data, int call_options, const char *user_name); int cib_file_read_and_verify(const char *filename, const char *sigfile, xmlNode **root); int cib_file_write_with_digest(xmlNode *cib_root, const char *cib_dirname, const char *cib_filename); void cib__set_output(cib_t *cib, pcmk__output_t *out); cib_callback_client_t* cib__lookup_id (int call_id); +/*! + * \internal + * \brief Connect to, query, and optionally disconnect from the CIB, returning + * the resulting XML object. + * + * \param[out] cib If non-NULL, a pointer to where to store the CIB + * connection. In this case, it is up to the caller to + * disconnect from the CIB when finished. + * \param[out] cib_object A pointer to where to store the XML query result. + * + * \return A standard Pacemaker return code + */ +int cib__signon_query(cib_t **cib, xmlNode **cib_object); + #endif diff --git a/lib/cib/cib_utils.c b/lib/cib/cib_utils.c index b9fbaaa3ba..3dd68e3b4d 100644 --- a/lib/cib/cib_utils.c +++ b/lib/cib/cib_utils.c @@ -1,788 +1,826 @@ /* * Original copyright 2004 International Business Machines * Later changes copyright 2008-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct config_root_s { const char *name; const char *parent; const char *path; }; /* * "//crm_config" will also work in place of "/cib/configuration/crm_config" * The / prefix means find starting from the root, whereas the // prefix means * find anywhere and risks multiple matches */ /* *INDENT-OFF* */ static struct config_root_s known_paths[] = { { NULL, NULL, "//cib" }, { XML_TAG_CIB, NULL, "//cib" }, { XML_CIB_TAG_STATUS, "/cib", "//cib/status" }, { XML_CIB_TAG_CONFIGURATION, "/cib", "//cib/configuration" }, { XML_CIB_TAG_CRMCONFIG, "/cib/configuration", "//cib/configuration/crm_config" }, { XML_CIB_TAG_NODES, "/cib/configuration", "//cib/configuration/nodes" }, { XML_CIB_TAG_RESOURCES, "/cib/configuration", "//cib/configuration/resources" }, { XML_CIB_TAG_CONSTRAINTS, "/cib/configuration", "//cib/configuration/constraints" }, { XML_CIB_TAG_OPCONFIG, "/cib/configuration", "//cib/configuration/op_defaults" }, { XML_CIB_TAG_RSCCONFIG, "/cib/configuration", "//cib/configuration/rsc_defaults" }, { XML_CIB_TAG_ACLS, "/cib/configuration", "//cib/configuration/acls" }, { XML_TAG_FENCING_TOPOLOGY, "/cib/configuration", "//cib/configuration/fencing-topology" }, { XML_CIB_TAG_TAGS, "/cib/configuration", "//cib/configuration/tags" }, { XML_CIB_TAG_ALERTS, "/cib/configuration", "//cib/configuration/alerts" }, { XML_CIB_TAG_SECTION_ALL, NULL, "//cib" }, }; /* *INDENT-ON* */ xmlNode * cib_get_generation(cib_t * cib) { xmlNode *the_cib = NULL; xmlNode *generation = create_xml_node(NULL, XML_CIB_TAG_GENERATION_TUPPLE); cib->cmds->query(cib, NULL, &the_cib, cib_scope_local | cib_sync_call); if (the_cib != NULL) { copy_in_properties(generation, the_cib); free_xml(the_cib); } return generation; } gboolean cib_version_details(xmlNode * cib, int *admin_epoch, int *epoch, int *updates) { *epoch = -1; *updates = -1; *admin_epoch = -1; if (cib == NULL) { return FALSE; } else { crm_element_value_int(cib, XML_ATTR_GENERATION, epoch); crm_element_value_int(cib, XML_ATTR_NUMUPDATES, updates); crm_element_value_int(cib, XML_ATTR_GENERATION_ADMIN, admin_epoch); } return TRUE; } gboolean cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *updates, int *_admin_epoch, int *_epoch, int *_updates) { int add[] = { 0, 0, 0 }; int del[] = { 0, 0, 0 }; xml_patch_versions(diff, add, del); *admin_epoch = add[0]; *epoch = add[1]; *updates = add[2]; *_admin_epoch = del[0]; *_epoch = del[1]; *_updates = del[2]; return TRUE; } /* * The caller should never free the return value */ const char * get_object_path(const char *object_type) { int lpc = 0; int max = PCMK__NELEM(known_paths); for (; lpc < max; lpc++) { if ((object_type == NULL && known_paths[lpc].name == NULL) || pcmk__str_eq(object_type, known_paths[lpc].name, pcmk__str_casei)) { return known_paths[lpc].path; } } return NULL; } const char * get_object_parent(const char *object_type) { int lpc = 0; int max = PCMK__NELEM(known_paths); for (; lpc < max; lpc++) { if (pcmk__str_eq(object_type, known_paths[lpc].name, pcmk__str_casei)) { return known_paths[lpc].parent; } } return NULL; } xmlNode * get_object_root(const char *object_type, xmlNode * the_root) { const char *xpath = get_object_path(object_type); if (xpath == NULL) { return the_root; /* or return NULL? */ } return get_xpath_object(xpath, the_root, LOG_TRACE); } /*! * \brief Create XML for a new (empty) CIB * * \param[in] cib_epoch What to use as "epoch" CIB property * * \return Newly created XML for empty CIB * \note It is the caller's responsibility to free the result with free_xml(). */ xmlNode * createEmptyCib(int cib_epoch) { xmlNode *cib_root = NULL, *config = NULL; cib_root = create_xml_node(NULL, XML_TAG_CIB); crm_xml_add(cib_root, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET); crm_xml_add(cib_root, XML_ATTR_VALIDATION, xml_latest_schema()); crm_xml_add_int(cib_root, XML_ATTR_GENERATION, cib_epoch); crm_xml_add_int(cib_root, XML_ATTR_NUMUPDATES, 0); crm_xml_add_int(cib_root, XML_ATTR_GENERATION_ADMIN, 0); config = create_xml_node(cib_root, XML_CIB_TAG_CONFIGURATION); create_xml_node(cib_root, XML_CIB_TAG_STATUS); create_xml_node(config, XML_CIB_TAG_CRMCONFIG); create_xml_node(config, XML_CIB_TAG_NODES); create_xml_node(config, XML_CIB_TAG_RESOURCES); create_xml_node(config, XML_CIB_TAG_CONSTRAINTS); #if PCMK__RESOURCE_STICKINESS_DEFAULT != 0 { xmlNode *rsc_defaults = create_xml_node(config, XML_CIB_TAG_RSCCONFIG); xmlNode *meta = create_xml_node(rsc_defaults, XML_TAG_META_SETS); xmlNode *nvpair = create_xml_node(meta, XML_CIB_TAG_NVPAIR); crm_xml_add(meta, XML_ATTR_ID, "build-resource-defaults"); crm_xml_add(nvpair, XML_ATTR_ID, "build-" XML_RSC_ATTR_STICKINESS); crm_xml_add(nvpair, XML_NVPAIR_ATTR_NAME, XML_RSC_ATTR_STICKINESS); crm_xml_add_int(nvpair, XML_NVPAIR_ATTR_VALUE, PCMK__RESOURCE_STICKINESS_DEFAULT); } #endif return cib_root; } static bool cib_acl_enabled(xmlNode *xml, const char *user) { bool rc = FALSE; if(pcmk_acl_required(user)) { const char *value = NULL; GHashTable *options = pcmk__strkey_table(free, free); cib_read_config(options, xml); value = cib_pref(options, "enable-acl"); rc = crm_is_true(value); g_hash_table_destroy(options); } crm_trace("CIB ACL is %s", rc ? "enabled" : "disabled"); return rc; } int cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_query, const char *section, xmlNode * req, xmlNode * input, gboolean manage_counters, gboolean * config_changed, xmlNode * current_cib, xmlNode ** result_cib, xmlNode ** diff, xmlNode ** output) { int rc = pcmk_ok; gboolean check_schema = TRUE; xmlNode *top = NULL; xmlNode *scratch = NULL; xmlNode *local_diff = NULL; const char *new_version = NULL; static struct qb_log_callsite *diff_cs = NULL; const char *user = crm_element_value(req, F_CIB_USER); bool with_digest = FALSE; crm_trace("Begin %s%s%s op", (pcmk_is_set(call_options, cib_dryrun)? "dry run of " : ""), (is_query? "read-only " : ""), op); CRM_CHECK(output != NULL, return -ENOMSG); CRM_CHECK(result_cib != NULL, return -ENOMSG); CRM_CHECK(config_changed != NULL, return -ENOMSG); if(output) { *output = NULL; } *result_cib = NULL; *config_changed = FALSE; if (fn == NULL) { return -EINVAL; } if (is_query) { xmlNode *cib_ro = current_cib; xmlNode *cib_filtered = NULL; if(cib_acl_enabled(cib_ro, user)) { if(xml_acl_filtered_copy(user, current_cib, current_cib, &cib_filtered)) { if (cib_filtered == NULL) { crm_debug("Pre-filtered the entire cib"); return -EACCES; } cib_ro = cib_filtered; crm_log_xml_trace(cib_ro, "filtered"); } } rc = (*fn) (op, call_options, section, req, input, cib_ro, result_cib, output); if(output == NULL || *output == NULL) { /* nothing */ } else if(cib_filtered == *output) { cib_filtered = NULL; /* Let them have this copy */ } else if(*output == current_cib) { /* They already know not to free it */ } else if(cib_filtered && (*output)->doc == cib_filtered->doc) { /* We're about to free the document of which *output is a part */ *output = copy_xml(*output); } else if((*output)->doc == current_cib->doc) { /* Give them a copy they can free */ *output = copy_xml(*output); } free_xml(cib_filtered); return rc; } if (pcmk_is_set(call_options, cib_zero_copy)) { /* Conditional on v2 patch style */ scratch = current_cib; /* Create a shallow copy of current_cib for the version details */ current_cib = create_xml_node(NULL, (const char *)scratch->name); copy_in_properties(current_cib, scratch); top = current_cib; xml_track_changes(scratch, user, NULL, cib_acl_enabled(scratch, user)); rc = (*fn) (op, call_options, section, req, input, scratch, &scratch, output); } else { scratch = copy_xml(current_cib); xml_track_changes(scratch, user, NULL, cib_acl_enabled(scratch, user)); rc = (*fn) (op, call_options, section, req, input, current_cib, &scratch, output); if(scratch && xml_tracking_changes(scratch) == FALSE) { crm_trace("Inferring changes after %s op", op); xml_track_changes(scratch, user, current_cib, cib_acl_enabled(current_cib, user)); xml_calculate_changes(current_cib, scratch); } CRM_CHECK(current_cib != scratch, return -EINVAL); } xml_acl_disable(scratch); /* Allow the system to make any additional changes */ if (rc == pcmk_ok && scratch == NULL) { rc = -EINVAL; goto done; } else if(rc == pcmk_ok && xml_acl_denied(scratch)) { crm_trace("ACL rejected part or all of the proposed changes"); rc = -EACCES; goto done; } else if (rc != pcmk_ok) { goto done; } if (scratch) { new_version = crm_element_value(scratch, XML_ATTR_CRM_VERSION); if (new_version && compare_version(new_version, CRM_FEATURE_SET) > 0) { crm_err("Discarding update with feature set '%s' greater than our own '%s'", new_version, CRM_FEATURE_SET); rc = -EPROTONOSUPPORT; goto done; } } if (current_cib) { int old = 0; int new = 0; crm_element_value_int(scratch, XML_ATTR_GENERATION_ADMIN, &new); crm_element_value_int(current_cib, XML_ATTR_GENERATION_ADMIN, &old); if (old > new) { crm_err("%s went backwards: %d -> %d (Opts: 0x%x)", XML_ATTR_GENERATION_ADMIN, old, new, call_options); crm_log_xml_warn(req, "Bad Op"); crm_log_xml_warn(input, "Bad Data"); rc = -pcmk_err_old_data; } else if (old == new) { crm_element_value_int(scratch, XML_ATTR_GENERATION, &new); crm_element_value_int(current_cib, XML_ATTR_GENERATION, &old); if (old > new) { crm_err("%s went backwards: %d -> %d (Opts: 0x%x)", XML_ATTR_GENERATION, old, new, call_options); crm_log_xml_warn(req, "Bad Op"); crm_log_xml_warn(input, "Bad Data"); rc = -pcmk_err_old_data; } } } crm_trace("Massaging CIB contents"); pcmk__strip_xml_text(scratch); fix_plus_plus_recursive(scratch); if (pcmk_is_set(call_options, cib_zero_copy)) { /* At this point, current_cib is just the 'cib' tag and its properties, * * The v1 format would barf on this, but we know the v2 patch * format only needs it for the top-level version fields */ local_diff = xml_create_patchset(2, current_cib, scratch, (bool*)config_changed, manage_counters); } else { static time_t expires = 0; time_t tm_now = time(NULL); if (expires < tm_now) { expires = tm_now + 60; /* Validate clients are correctly applying v2-style diffs at most once a minute */ with_digest = TRUE; } local_diff = xml_create_patchset(0, current_cib, scratch, (bool*)config_changed, manage_counters); } xml_log_changes(LOG_TRACE, __func__, scratch); xml_accept_changes(scratch); if (diff_cs == NULL) { diff_cs = qb_log_callsite_get(__PRETTY_FUNCTION__, __FILE__, "diff-validation", LOG_DEBUG, __LINE__, crm_trace_nonlog); } if(local_diff) { patchset_process_digest(local_diff, current_cib, scratch, with_digest); xml_log_patchset(LOG_INFO, __func__, local_diff); crm_log_xml_trace(local_diff, "raw patch"); } if (!pcmk_is_set(call_options, cib_zero_copy) // Original to compare against doesn't exist && local_diff && crm_is_callsite_active(diff_cs, LOG_TRACE, 0)) { /* Validate the calculated patch set */ int test_rc, format = 1; xmlNode * c = copy_xml(current_cib); crm_element_value_int(local_diff, "format", &format); test_rc = xml_apply_patchset(c, local_diff, manage_counters); if(test_rc != pcmk_ok) { save_xml_to_file(c, "PatchApply:calculated", NULL); save_xml_to_file(current_cib, "PatchApply:input", NULL); save_xml_to_file(scratch, "PatchApply:actual", NULL); save_xml_to_file(local_diff, "PatchApply:diff", NULL); crm_err("v%d patchset error, patch failed to apply: %s (%d)", format, pcmk_strerror(test_rc), test_rc); } free_xml(c); } if (pcmk__str_eq(section, XML_CIB_TAG_STATUS, pcmk__str_casei)) { /* Throttle the amount of costly validation we perform due to status updates * a) we don't really care whats in the status section * b) we don't validate any of its contents at the moment anyway */ check_schema = FALSE; } /* === scratch must not be modified after this point === * Exceptions, anything in: static filter_t filter[] = { { 0, XML_ATTR_ORIGIN }, { 0, XML_CIB_ATTR_WRITTEN }, { 0, XML_ATTR_UPDATE_ORIG }, { 0, XML_ATTR_UPDATE_CLIENT }, { 0, XML_ATTR_UPDATE_USER }, }; */ if (*config_changed && !pcmk_is_set(call_options, cib_no_mtime)) { const char *schema = crm_element_value(scratch, XML_ATTR_VALIDATION); pcmk__xe_add_last_written(scratch); if (schema) { static int minimum_schema = 0; int current_schema = get_schema_version(schema); if (minimum_schema == 0) { minimum_schema = get_schema_version("pacemaker-1.2"); } /* Does the CIB support the "update-*" attributes... */ if (current_schema >= minimum_schema) { const char *origin = crm_element_value(req, F_ORIG); CRM_LOG_ASSERT(origin != NULL); crm_xml_replace(scratch, XML_ATTR_UPDATE_ORIG, origin); crm_xml_replace(scratch, XML_ATTR_UPDATE_CLIENT, crm_element_value(req, F_CIB_CLIENTNAME)); crm_xml_replace(scratch, XML_ATTR_UPDATE_USER, crm_element_value(req, F_CIB_USER)); } } } crm_trace("Perform validation: %s", pcmk__btoa(check_schema)); if ((rc == pcmk_ok) && check_schema && !validate_xml(scratch, NULL, TRUE)) { const char *current_schema = crm_element_value(scratch, XML_ATTR_VALIDATION); crm_warn("Updated CIB does not validate against %s schema", crm_str(current_schema)); rc = -pcmk_err_schema_validation; } done: *result_cib = scratch; if(rc != pcmk_ok && cib_acl_enabled(current_cib, user)) { if(xml_acl_filtered_copy(user, current_cib, scratch, result_cib)) { if (*result_cib == NULL) { crm_debug("Pre-filtered the entire cib result"); } free_xml(scratch); } } if(diff) { *diff = local_diff; } else { free_xml(local_diff); } free_xml(top); crm_trace("Done"); return rc; } xmlNode * cib_create_op(int call_id, const char *token, const char *op, const char *host, const char *section, xmlNode * data, int call_options, const char *user_name) { xmlNode *op_msg = create_xml_node(NULL, "cib_command"); CRM_CHECK(op_msg != NULL, return NULL); CRM_CHECK(token != NULL, return NULL); crm_xml_add(op_msg, F_XML_TAGNAME, "cib_command"); crm_xml_add(op_msg, F_TYPE, T_CIB); crm_xml_add(op_msg, F_CIB_CALLBACK_TOKEN, token); crm_xml_add(op_msg, F_CIB_OPERATION, op); crm_xml_add(op_msg, F_CIB_HOST, host); crm_xml_add(op_msg, F_CIB_SECTION, section); crm_xml_add_int(op_msg, F_CIB_CALLID, call_id); if (user_name) { crm_xml_add(op_msg, F_CIB_USER, user_name); } crm_trace("Sending call options: %.8lx, %d", (long)call_options, call_options); crm_xml_add_int(op_msg, F_CIB_CALLOPTS, call_options); if (data != NULL) { add_message_xml(op_msg, F_CIB_CALLDATA, data); } if (call_options & cib_inhibit_bcast) { CRM_CHECK((call_options & cib_scope_local), return NULL); } return op_msg; } void cib_native_callback(cib_t * cib, xmlNode * msg, int call_id, int rc) { xmlNode *output = NULL; cib_callback_client_t *blob = NULL; if (msg != NULL) { crm_element_value_int(msg, F_CIB_RC, &rc); crm_element_value_int(msg, F_CIB_CALLID, &call_id); output = get_message_xml(msg, F_CIB_CALLDATA); } blob = cib__lookup_id(call_id); if (blob == NULL) { crm_trace("No callback found for call %d", call_id); } if (cib == NULL) { crm_debug("No cib object supplied"); } if (rc == -pcmk_err_diff_resync) { /* This is an internal value that clients do not and should not care about */ rc = pcmk_ok; } if (blob && blob->callback && (rc == pcmk_ok || blob->only_success == FALSE)) { crm_trace("Invoking callback %s for call %d", crm_str(blob->id), call_id); blob->callback(msg, call_id, rc, output, blob->user_data); } else if (cib && cib->op_callback == NULL && rc != pcmk_ok) { crm_warn("CIB command failed: %s", pcmk_strerror(rc)); crm_log_xml_debug(msg, "Failed CIB Update"); } /* This may free user_data, so do it after the callback */ if (blob) { remove_cib_op_callback(call_id, FALSE); } if (cib && cib->op_callback != NULL) { crm_trace("Invoking global callback for call %d", call_id); cib->op_callback(msg, call_id, rc, output); } crm_trace("OP callback activated for %d", call_id); } void cib_native_notify(gpointer data, gpointer user_data) { xmlNode *msg = user_data; cib_notify_client_t *entry = data; const char *event = NULL; if (msg == NULL) { crm_warn("Skipping callback - NULL message"); return; } event = crm_element_value(msg, F_SUBTYPE); if (entry == NULL) { crm_warn("Skipping callback - NULL callback client"); return; } else if (entry->callback == NULL) { crm_warn("Skipping callback - NULL callback"); return; } else if (!pcmk__str_eq(entry->event, event, pcmk__str_casei)) { crm_trace("Skipping callback - event mismatch %p/%s vs. %s", entry, entry->event, event); return; } crm_trace("Invoking callback for %p/%s event...", entry, event); entry->callback(event, msg); crm_trace("Callback invoked..."); } static pcmk__cluster_option_t cib_opts[] = { /* name, legacy name, type, allowed values, * default value, validator, * short description, * long description */ { "enable-acl", NULL, "boolean", NULL, "false", pcmk__valid_boolean, "Enable Access Control Lists (ACLs) for the CIB", NULL }, { "cluster-ipc-limit", NULL, "integer", NULL, "500", pcmk__valid_positive_number, "Maximum IPC message backlog before disconnecting a cluster daemon", "Raise this if log has \"Evicting client\" messages for cluster daemon" " PIDs (a good value is the number of resources in the cluster" " multiplied by the number of nodes)." }, }; void cib_metadata(void) { pcmk__print_option_metadata("pacemaker-based", "1.0", "Cluster Information Base manager options", "Cluster options used by Pacemaker's " "Cluster Information Base manager", cib_opts, PCMK__NELEM(cib_opts)); } void verify_cib_options(GHashTable * options) { pcmk__validate_cluster_options(options, cib_opts, PCMK__NELEM(cib_opts)); } const char * cib_pref(GHashTable * options, const char *name) { return pcmk__cluster_option(options, cib_opts, PCMK__NELEM(cib_opts), name); } gboolean cib_read_config(GHashTable * options, xmlNode * current_cib) { xmlNode *config = NULL; crm_time_t *now = NULL; if (options == NULL || current_cib == NULL) { return FALSE; } now = crm_time_new(NULL); g_hash_table_remove_all(options); config = get_object_root(XML_CIB_TAG_CRMCONFIG, current_cib); if (config) { pe_unpack_nvpairs(current_cib, config, XML_CIB_TAG_PROPSET, NULL, options, CIB_OPTIONS_FIRST, TRUE, now, NULL); } verify_cib_options(options); crm_time_free(now); return TRUE; } /* v2 and v2 patch formats */ #define XPATH_CONFIG_CHANGE \ "//" XML_CIB_TAG_CRMCONFIG " | " \ "//" XML_DIFF_CHANGE "[contains(@" XML_DIFF_PATH ",'/" XML_CIB_TAG_CRMCONFIG "/')]" gboolean cib_internal_config_changed(xmlNode *diff) { gboolean changed = FALSE; if (diff) { xmlXPathObject *xpathObj = xpath_search(diff, XPATH_CONFIG_CHANGE); if (numXpathResults(xpathObj) > 0) { changed = TRUE; } freeXpathObject(xpathObj); } return changed; } int cib_internal_op(cib_t * cib, const char *op, const char *host, const char *section, xmlNode * data, xmlNode ** output_data, int call_options, const char *user_name) { int (*delegate) (cib_t * cib, const char *op, const char *host, const char *section, xmlNode * data, xmlNode ** output_data, int call_options, const char *user_name) = cib->delegate_fn; if(user_name == NULL) { user_name = getenv("CIB_user"); } return delegate(cib, op, host, section, data, output_data, call_options, user_name); } /*! * \brief Apply a CIB update patch to a given CIB * * \param[in] event CIB update patch * \param[in] input CIB to patch * \param[out] output Resulting CIB after patch * \param[in] level Log the patch at this log level (unless LOG_CRIT) * * \return Legacy Pacemaker return code * \note sbd calls this function */ int cib_apply_patch_event(xmlNode *event, xmlNode *input, xmlNode **output, int level) { int rc = pcmk_err_generic; xmlNode *diff = NULL; CRM_ASSERT(event); CRM_ASSERT(input); CRM_ASSERT(output); crm_element_value_int(event, F_CIB_RC, &rc); diff = get_message_xml(event, F_CIB_UPDATE_RESULT); if (rc < pcmk_ok || diff == NULL) { return rc; } if (level > LOG_CRIT) { xml_log_patchset(level, "Config update", diff); } if (input != NULL) { rc = cib_process_diff(NULL, cib_none, NULL, event, diff, input, output, NULL); if (rc != pcmk_ok) { crm_debug("Update didn't apply: %s (%d) %p", pcmk_strerror(rc), rc, *output); if (rc == -pcmk_err_old_data) { crm_trace("Masking error, we already have the supplied update"); return pcmk_ok; } free_xml(*output); *output = NULL; return rc; } } return rc; } + +int +cib__signon_query(cib_t **cib, xmlNode **cib_object) +{ + int rc = pcmk_rc_ok; + cib_t *cib_conn = NULL; + + if (cib == NULL) { + cib_conn = cib_new(); + } else { + *cib = cib_new(); + cib_conn = *cib; + } + + if (cib_conn == NULL) { + return ENOMEM; + } + + rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command); + rc = pcmk_legacy2rc(rc); + + if (rc == pcmk_rc_ok) { + rc = cib_conn->cmds->query(cib_conn, NULL, cib_object, cib_scope_local | cib_sync_call); + rc = pcmk_legacy2rc(rc); + } + + if (cib == NULL) { + cib_conn->cmds->signoff(cib_conn); + cib_delete(cib_conn); + cib_conn = NULL; + } + + if (cib_object == NULL) { + return pcmk_rc_no_input; + } else { + return rc; + } +} diff --git a/lib/pacemaker/pcmk_cluster_queries.c b/lib/pacemaker/pcmk_cluster_queries.c index 46e553852c..c30a9b889a 100644 --- a/lib/pacemaker/pcmk_cluster_queries.c +++ b/lib/pacemaker/pcmk_cluster_queries.c @@ -1,512 +1,502 @@ /* * Copyright 2020-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include // gboolean, GMainLoop, etc. #include // xmlNode #include #include #include #include +#include #include #include #include #include #include #include #include #include #define DEFAULT_MESSAGE_TIMEOUT_MS 30000 typedef struct { pcmk__output_t *out; GMainLoop *mainloop; int rc; guint message_timer_id; guint message_timeout_ms; } data_t; static void quit_main_loop(data_t *data) { if (data->mainloop != NULL) { GMainLoop *mloop = data->mainloop; data->mainloop = NULL; // Don't re-enter this block pcmk_quit_main_loop(mloop, 10); g_main_loop_unref(mloop); } } static gboolean admin_message_timeout(gpointer user_data) { data_t *data = user_data; pcmk__output_t *out = data->out; out->err(out, "error: No reply received from controller before timeout (%dms)", data->message_timeout_ms); data->message_timer_id = 0; data->rc = ETIMEDOUT; quit_main_loop(data); return FALSE; // Tells glib to remove source } static void start_main_loop(data_t *data) { if (data->message_timeout_ms < 1) { data->message_timeout_ms = DEFAULT_MESSAGE_TIMEOUT_MS; } data->rc = ECONNRESET; // For unexpected disconnects data->mainloop = g_main_loop_new(NULL, FALSE); data->message_timer_id = g_timeout_add(data->message_timeout_ms, admin_message_timeout, data); g_main_loop_run(data->mainloop); } static void event_done(data_t *data, pcmk_ipc_api_t *api) { pcmk_disconnect_ipc(api); quit_main_loop(data); } static pcmk_controld_api_reply_t * controld_event_reply(data_t *data, pcmk_ipc_api_t *controld_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data) { pcmk__output_t *out = data->out; pcmk_controld_api_reply_t *reply = event_data; switch (event_type) { case pcmk_ipc_event_disconnect: if (data->rc == ECONNRESET) { // Unexpected out->err(out, "error: Lost connection to controller"); } event_done(data, controld_api); return NULL; case pcmk_ipc_event_reply: break; default: return NULL; } if (data->message_timer_id != 0) { g_source_remove(data->message_timer_id); data->message_timer_id = 0; } if (status != CRM_EX_OK) { out->err(out, "error: Bad reply from controller: %s", crm_exit_str(status)); data->rc = EBADMSG; event_done(data, controld_api); return NULL; } if (reply->reply_type != pcmk_controld_reply_ping) { out->err(out, "error: Unknown reply type %d from controller", reply->reply_type); data->rc = EBADMSG; event_done(data, controld_api); return NULL; } return reply; } static void controller_status_event_cb(pcmk_ipc_api_t *controld_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { data_t *data = user_data; pcmk__output_t *out = data->out; pcmk_controld_api_reply_t *reply = controld_event_reply(data, controld_api, event_type, status, event_data); if (reply != NULL) { out->message(out, "health", reply->data.ping.sys_from, reply->host_from, reply->data.ping.fsa_state, reply->data.ping.result); data->rc = pcmk_rc_ok; } event_done(data, controld_api); } static void designated_controller_event_cb(pcmk_ipc_api_t *controld_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { data_t *data = user_data; pcmk__output_t *out = data->out; pcmk_controld_api_reply_t *reply = controld_event_reply(data, controld_api, event_type, status, event_data); if (reply != NULL) { out->message(out, "dc", reply->host_from); data->rc = pcmk_rc_ok; } event_done(data, controld_api); } static void pacemakerd_event_cb(pcmk_ipc_api_t *pacemakerd_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { data_t *data = user_data; pcmk__output_t *out = data->out; pcmk_pacemakerd_api_reply_t *reply = event_data; crm_time_t *crm_when; char *pinged_buf = NULL; switch (event_type) { case pcmk_ipc_event_disconnect: if (data->rc == ECONNRESET) { // Unexpected out->err(out, "error: Lost connection to pacemakerd"); } event_done(data, pacemakerd_api); return; case pcmk_ipc_event_reply: break; default: return; } if (data->message_timer_id != 0) { g_source_remove(data->message_timer_id); data->message_timer_id = 0; } if (status != CRM_EX_OK) { out->err(out, "error: Bad reply from pacemakerd: %s", crm_exit_str(status)); event_done(data, pacemakerd_api); return; } if (reply->reply_type != pcmk_pacemakerd_reply_ping) { out->err(out, "error: Unknown reply type %d from pacemakerd", reply->reply_type); event_done(data, pacemakerd_api); return; } // Parse desired information from reply crm_when = crm_time_new(NULL); crm_time_set_timet(crm_when, &reply->data.ping.last_good); pinged_buf = crm_time_as_string(crm_when, crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone); out->message(out, "pacemakerd-health", reply->data.ping.sys_from, (reply->data.ping.status == pcmk_rc_ok)? pcmk_pacemakerd_api_daemon_state_enum2text( reply->data.ping.state):"query failed", (reply->data.ping.status == pcmk_rc_ok)?pinged_buf:""); data->rc = pcmk_rc_ok; crm_time_free(crm_when); free(pinged_buf); event_done(data, pacemakerd_api); } static pcmk_ipc_api_t * ipc_connect(data_t *data, enum pcmk_ipc_server server, pcmk_ipc_callback_t cb) { int rc; pcmk__output_t *out = data->out; pcmk_ipc_api_t *api = NULL; rc = pcmk_new_ipc_api(&api, server); if (api == NULL) { out->err(out, "error: Could not connect to %s: %s", pcmk_ipc_name(api, true), pcmk_rc_str(rc)); data->rc = rc; return NULL; } if (cb != NULL) { pcmk_register_ipc_callback(api, cb, data); } rc = pcmk_connect_ipc(api, pcmk_ipc_dispatch_main); if (rc != pcmk_rc_ok) { out->err(out, "error: Could not connect to %s: %s", pcmk_ipc_name(api, true), pcmk_rc_str(rc)); data->rc = rc; return NULL; } return api; } int pcmk__controller_status(pcmk__output_t *out, char *dest_node, guint message_timeout_ms) { data_t data = { .out = out, .mainloop = NULL, .rc = pcmk_rc_ok, .message_timer_id = 0, .message_timeout_ms = message_timeout_ms }; pcmk_ipc_api_t *controld_api = ipc_connect(&data, pcmk_ipc_controld, controller_status_event_cb); if (controld_api != NULL) { int rc = pcmk_controld_api_ping(controld_api, dest_node); if (rc != pcmk_rc_ok) { out->err(out, "error: Command failed: %s", pcmk_rc_str(rc)); data.rc = rc; } start_main_loop(&data); pcmk_free_ipc_api(controld_api); } return data.rc; } int pcmk_controller_status(xmlNodePtr *xml, char *dest_node, unsigned int message_timeout_ms) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__out_prologue(&out, xml); if (rc != pcmk_rc_ok) { return rc; } pcmk__register_lib_messages(out); rc = pcmk__controller_status(out, dest_node, (guint) message_timeout_ms); pcmk__out_epilogue(out, xml, rc); return rc; } int pcmk__designated_controller(pcmk__output_t *out, guint message_timeout_ms) { data_t data = { .out = out, .mainloop = NULL, .rc = pcmk_rc_ok, .message_timer_id = 0, .message_timeout_ms = message_timeout_ms }; pcmk_ipc_api_t *controld_api = ipc_connect(&data, pcmk_ipc_controld, designated_controller_event_cb); if (controld_api != NULL) { int rc = pcmk_controld_api_ping(controld_api, NULL); if (rc != pcmk_rc_ok) { out->err(out, "error: Command failed: %s", pcmk_rc_str(rc)); data.rc = rc; } start_main_loop(&data); pcmk_free_ipc_api(controld_api); } return data.rc; } int pcmk_designated_controller(xmlNodePtr *xml, unsigned int message_timeout_ms) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__out_prologue(&out, xml); if (rc != pcmk_rc_ok) { return rc; } pcmk__register_lib_messages(out); rc = pcmk__designated_controller(out, (guint) message_timeout_ms); pcmk__out_epilogue(out, xml, rc); return rc; } int pcmk__pacemakerd_status(pcmk__output_t *out, char *ipc_name, guint message_timeout_ms) { data_t data = { .out = out, .mainloop = NULL, .rc = pcmk_rc_ok, .message_timer_id = 0, .message_timeout_ms = message_timeout_ms }; pcmk_ipc_api_t *pacemakerd_api = ipc_connect(&data, pcmk_ipc_pacemakerd, pacemakerd_event_cb); if (pacemakerd_api != NULL) { int rc = pcmk_pacemakerd_api_ping(pacemakerd_api, ipc_name); if (rc != pcmk_rc_ok) { out->err(out, "error: Command failed: %s", pcmk_rc_str(rc)); data.rc = rc; } start_main_loop(&data); pcmk_free_ipc_api(pacemakerd_api); } return data.rc; } int pcmk_pacemakerd_status(xmlNodePtr *xml, char *ipc_name, unsigned int message_timeout_ms) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__out_prologue(&out, xml); if (rc != pcmk_rc_ok) { return rc; } pcmk__register_lib_messages(out); rc = pcmk__pacemakerd_status(out, ipc_name, (guint) message_timeout_ms); pcmk__out_epilogue(out, xml, rc); return rc; } /* user data for looping through remote node xpath searches */ struct node_data { pcmk__output_t *out; int found; const char *field; /* XML attribute to check for node name */ const char *type; gboolean BASH_EXPORT; }; static void remote_node_print_helper(xmlNode *result, void *user_data) { struct node_data *data = user_data; pcmk__output_t *out = data->out; const char *name = crm_element_value(result, XML_ATTR_UNAME); const char *id = crm_element_value(result, data->field); // node name and node id are the same for remote/guest nodes out->message(out, "crmadmin-node", data->type, name ? name : id, id, data->BASH_EXPORT); data->found++; } // \return Standard Pacemaker return code int pcmk__list_nodes(pcmk__output_t *out, char *node_types, gboolean BASH_EXPORT) { - cib_t *the_cib = cib_new(); xmlNode *xml_node = NULL; int rc; - if (the_cib == NULL) { - return ENOMEM; - } - rc = the_cib->cmds->signon(the_cib, crm_system_name, cib_command); - if (rc != pcmk_ok) { - cib_delete(the_cib); - return pcmk_legacy2rc(rc); - } + rc = cib__signon_query(NULL, &xml_node); - rc = the_cib->cmds->query(the_cib, NULL, &xml_node, - cib_scope_local | cib_sync_call); - if (rc == pcmk_ok) { + if (rc == pcmk_rc_ok) { struct node_data data = { .out = out, .found = 0, .BASH_EXPORT = BASH_EXPORT }; out->begin_list(out, NULL, NULL, "nodes"); if (!pcmk__str_empty(node_types) && strstr(node_types, "all")) { node_types = NULL; } if (pcmk__str_empty(node_types) || strstr(node_types, "cluster")) { data.field = "id"; data.type = "cluster"; crm_foreach_xpath_result(xml_node, PCMK__XP_MEMBER_NODE_CONFIG, remote_node_print_helper, &data); } if (pcmk__str_empty(node_types) || strstr(node_types, "guest")) { data.field = "value"; data.type = "guest"; crm_foreach_xpath_result(xml_node, PCMK__XP_GUEST_NODE_CONFIG, remote_node_print_helper, &data); } if (pcmk__str_empty(node_types) || !pcmk__strcmp(node_types, ",|^remote", pcmk__str_regex)) { data.field = "id"; data.type = "remote"; crm_foreach_xpath_result(xml_node, PCMK__XP_REMOTE_NODE_CONFIG, remote_node_print_helper, &data); } out->end_list(out); if (data.found == 0) { out->info(out, "No nodes configured"); } free_xml(xml_node); } - the_cib->cmds->signoff(the_cib); - cib_delete(the_cib); - return pcmk_legacy2rc(rc); + + return rc; } int pcmk_list_nodes(xmlNodePtr *xml, char *node_types) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__out_prologue(&out, xml); if (rc != pcmk_rc_ok) { return rc; } pcmk__register_lib_messages(out); rc = pcmk__list_nodes(out, node_types, FALSE); pcmk__out_epilogue(out, xml, rc); return rc; } diff --git a/tools/crm_rule.c b/tools/crm_rule.c index 6b2694a6c5..a1c7a78dac 100644 --- a/tools/crm_rule.c +++ b/tools/crm_rule.c @@ -1,372 +1,357 @@ /* * Copyright 2019-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include +#include #include #include #include #include #include #include #include #include #define SUMMARY "evaluate rules from the Pacemaker configuration" enum crm_rule_mode { crm_rule_mode_none, crm_rule_mode_check }; struct { char *date; char *input_xml; enum crm_rule_mode mode; char *rule; } options = { .mode = crm_rule_mode_none }; static int crm_rule_check(pe_working_set_t *data_set, const char *rule_id, crm_time_t *effective_date); static gboolean mode_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); static GOptionEntry mode_entries[] = { { "check", 'c', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, mode_cb, "Check whether a rule is in effect", NULL }, { NULL } }; static GOptionEntry data_entries[] = { { "xml-text", 'X', 0, G_OPTION_ARG_STRING, &options.input_xml, "Use argument for XML (or stdin if '-')", NULL }, { NULL } }; static GOptionEntry addl_entries[] = { { "date", 'd', 0, G_OPTION_ARG_STRING, &options.date, "Whether the rule is in effect on a given date", NULL }, { "rule", 'r', 0, G_OPTION_ARG_STRING, &options.rule, "The ID of the rule to check", NULL }, { NULL } }; static gboolean mode_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (strcmp(option_name, "c")) { options.mode = crm_rule_mode_check; } return TRUE; } /*! * \internal * \brief Evaluate a date expression for a specific time * * \param[in] time_expr date_expression XML * \param[in] now Time for which to evaluate expression * \param[out] next_change If not NULL, set to when evaluation will change * * \return Standard Pacemaker return code */ static int eval_date_expression(xmlNode *expr, crm_time_t *now, crm_time_t *next_change) { pe_rule_eval_data_t rule_data = { .node_hash = NULL, .role = RSC_ROLE_UNKNOWN, .now = now, .match_data = NULL, .rsc_data = NULL, .op_data = NULL }; return pe__eval_date_expr(expr, &rule_data, next_change); } static int crm_rule_check(pe_working_set_t *data_set, const char *rule_id, crm_time_t *effective_date) { xmlNode *cib_constraints = NULL; xmlNode *match = NULL; xmlXPathObjectPtr xpathObj = NULL; char *xpath = NULL; int rc = pcmk_rc_ok; int max = 0; /* Rules are under the constraints node in the XML, so first find that. */ cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, data_set->input); /* Get all rules matching the given ID which are also simple enough for us to check. * For the moment, these rules must only have a single date_expression child and: * - Do not have a date_spec operation, or * - Have a date_spec operation that contains years= but does not contain moon=. * * We do this in steps to provide better error messages. First, check that there's * any rule with the given ID. */ xpath = crm_strdup_printf("//rule[@id='%s']", rule_id); xpathObj = xpath_search(cib_constraints, xpath); max = numXpathResults(xpathObj); if (max == 0) { CMD_ERR("No rule found with ID=%s", rule_id); rc = ENXIO; goto done; } else if (max > 1) { CMD_ERR("More than one rule with ID=%s found", rule_id); rc = ENXIO; goto done; } free(xpath); freeXpathObject(xpathObj); /* Next, make sure it has exactly one date_expression. */ xpath = crm_strdup_printf("//rule[@id='%s']//date_expression", rule_id); xpathObj = xpath_search(cib_constraints, xpath); max = numXpathResults(xpathObj); if (max != 1) { CMD_ERR("Can't check rule %s because it does not have exactly one date_expression", rule_id); rc = EOPNOTSUPP; goto done; } free(xpath); freeXpathObject(xpathObj); /* Then, check that it's something we actually support. */ xpath = crm_strdup_printf("//rule[@id='%s']//date_expression[@operation!='date_spec']", rule_id); xpathObj = xpath_search(cib_constraints, xpath); max = numXpathResults(xpathObj); if (max == 0) { free(xpath); freeXpathObject(xpathObj); xpath = crm_strdup_printf("//rule[@id='%s']//date_expression[@operation='date_spec' and date_spec/@years and not(date_spec/@moon)]", rule_id); xpathObj = xpath_search(cib_constraints, xpath); max = numXpathResults(xpathObj); if (max == 0) { CMD_ERR("Rule either must not use date_spec, or use date_spec with years= but not moon="); rc = ENXIO; goto done; } } match = getXpathResult(xpathObj, 0); /* We should have ensured both of these pass with the xpath query above, but * double checking can't hurt. */ CRM_ASSERT(match != NULL); CRM_ASSERT(find_expression_type(match) == time_expr); rc = eval_date_expression(match, effective_date, NULL); if (rc == pcmk_rc_within_range) { printf("Rule %s is still in effect\n", rule_id); rc = pcmk_rc_ok; } else if (rc == pcmk_rc_ok) { printf("Rule %s satisfies conditions\n", rule_id); } else if (rc == pcmk_rc_after_range) { printf("Rule %s is expired\n", rule_id); } else if (rc == pcmk_rc_before_range) { printf("Rule %s has not yet taken effect\n", rule_id); } else if (rc == pcmk_rc_op_unsatisfied) { printf("Rule %s does not satisfy conditions\n", rule_id); } else { printf("Could not determine whether rule %s is expired\n", rule_id); } done: free(xpath); freeXpathObject(xpathObj); return rc; } static GOptionContext * build_arg_context(pcmk__common_args_t *args) { GOptionContext *context = NULL; const char *description = "This tool is currently experimental.\n" "The interface, behavior, and output may change " "with any version of Pacemaker."; context = pcmk__build_arg_context(args, NULL, NULL, NULL); g_option_context_set_description(context, description); pcmk__add_arg_group(context, "modes", "Modes (mutually exclusive):", "Show modes of operation", mode_entries); pcmk__add_arg_group(context, "data", "Data:", "Show data options", data_entries); pcmk__add_arg_group(context, "additional", "Additional Options:", "Show additional options", addl_entries); return context; } int main(int argc, char **argv) { - cib_t *cib_conn = NULL; pe_working_set_t *data_set = NULL; crm_time_t *rule_date = NULL; xmlNode *input = NULL; int rc = pcmk_ok; crm_exit_t exit_code = CRM_EX_OK; GError *error = NULL; pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); GOptionContext *context = build_arg_context(args); gchar **processed_args = pcmk__cmdline_preproc(argv, "drX"); if (!g_option_context_parse_strv(context, &processed_args, &error)) { exit_code = CRM_EX_USAGE; goto done; } pcmk__cli_init_logging("crm_rule", args->verbosity); if (args->version) { g_strfreev(processed_args); pcmk__free_arg_context(context); /* FIXME: When crm_rule is converted to use formatted output, this can go. */ pcmk__cli_help('v', CRM_EX_OK); } /* Check command line arguments before opening a connection to * the CIB manager or doing anything else important. */ switch(options.mode) { case crm_rule_mode_check: if (options.rule == NULL) { CMD_ERR("--check requires use of --rule="); exit_code = CRM_EX_USAGE; goto done; } break; default: CMD_ERR("No mode operation given"); exit_code = CRM_EX_USAGE; goto done; break; } /* Set up some defaults. */ rule_date = crm_time_new(options.date); if (rule_date == NULL) { CMD_ERR("No --date given and can't determine current date"); exit_code = CRM_EX_DATAERR; goto done; } /* Where does the XML come from? If one of various command line options were * given, use those. Otherwise, connect to the CIB and use that. */ if (pcmk__str_eq(options.input_xml, "-", pcmk__str_casei)) { input = stdin2xml(); if (input == NULL) { CMD_ERR("Couldn't parse input from STDIN\n"); exit_code = CRM_EX_DATAERR; goto done; } } else if (options.input_xml != NULL) { input = string2xml(options.input_xml); if (input == NULL) { CMD_ERR("Couldn't parse input string: %s\n", options.input_xml); exit_code = CRM_EX_DATAERR; goto done; } } else { - // Establish a connection to the CIB - cib_conn = cib_new(); - rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command); - if (rc != pcmk_ok) { - CMD_ERR("Could not connect to CIB: %s", pcmk_strerror(rc)); - exit_code = crm_errno2exit(rc); - goto done; - } - } + rc = cib__signon_query(NULL, &input); - /* Populate working set from CIB query */ - if (input == NULL) { - rc = cib_conn->cmds->query(cib_conn, NULL, &input, cib_scope_local | cib_sync_call); - if (rc != pcmk_ok) { - exit_code = crm_errno2exit(rc); + if (rc != pcmk_rc_ok) { + CMD_ERR("CIB query failed: %s", pcmk_rc_str(rc)); + exit_code = pcmk_rc2exitc(rc); goto done; } } /* Populate the working set instance */ data_set = pe_new_working_set(); if (data_set == NULL) { exit_code = crm_errno2exit(ENOMEM); goto done; } pe__set_working_set_flags(data_set, pe_flag_no_counts|pe_flag_no_compat); data_set->input = input; data_set->now = rule_date; /* Unpack everything. */ cluster_status(data_set); /* Now do whichever operation mode was asked for. There's only one at the * moment so this looks a little silly, but I expect there will be more * modes in the future. */ switch(options.mode) { case crm_rule_mode_check: rc = crm_rule_check(data_set, options.rule, rule_date); if (rc > 0) { CMD_ERR("Error checking rule: %s", pcmk_rc_str(rc)); } exit_code = pcmk_rc2exitc(rc); break; default: break; } done: - if (cib_conn != NULL) { - cib_conn->cmds->signoff(cib_conn); - cib_delete(cib_conn); - } - g_strfreev(processed_args); pcmk__free_arg_context(context); pe_free_working_set(data_set); pcmk__output_and_clear_error(error, NULL); crm_exit(exit_code); } diff --git a/tools/crm_simulate.c b/tools/crm_simulate.c index ad4aeea573..42f90f81ee 100644 --- a/tools/crm_simulate.c +++ b/tools/crm_simulate.c @@ -1,837 +1,810 @@ /* * Copyright 2009-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #define SUMMARY "crm_simulate - simulate a Pacemaker cluster's response to events" struct { gboolean all_actions; char *dot_file; char *graph_file; gchar *input_file; guint modified; GList *node_up; GList *node_down; GList *node_fail; GList *op_fail; GList *op_inject; gchar *output_file; gboolean print_pending; gboolean process; char *quorum; long long repeat; gboolean show_attrs; gboolean show_failcounts; gboolean show_scores; gboolean show_utilization; gboolean simulate; gboolean store; gchar *test_dir; GList *ticket_grant; GList *ticket_revoke; GList *ticket_standby; GList *ticket_activate; char *use_date; char *watchdog; char *xml_file; } options = { .print_pending = TRUE, .repeat = 1 }; cib_t *global_cib = NULL; char *temp_shadow = NULL; extern gboolean bringing_nodes_online; crm_exit_t exit_code = CRM_EX_OK; #define INDENT " " static pcmk__supported_format_t formats[] = { PCMK__SUPPORTED_FORMAT_NONE, PCMK__SUPPORTED_FORMAT_TEXT, PCMK__SUPPORTED_FORMAT_XML, { NULL, NULL, NULL } }; static gboolean in_place_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.store = TRUE; options.process = TRUE; options.simulate = TRUE; return TRUE; } static gboolean live_check_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (options.xml_file) { free(options.xml_file); } options.xml_file = NULL; return TRUE; } static gboolean node_down_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.modified++; options.node_down = g_list_append(options.node_down, (gchar *) g_strdup(optarg)); return TRUE; } static gboolean node_fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.modified++; options.node_fail = g_list_append(options.node_fail, (gchar *) g_strdup(optarg)); return TRUE; } static gboolean node_up_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.modified++; bringing_nodes_online = TRUE; options.node_up = g_list_append(options.node_up, (gchar *) g_strdup(optarg)); return TRUE; } static gboolean op_fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.process = TRUE; options.simulate = TRUE; options.op_fail = g_list_append(options.op_fail, (gchar *) g_strdup(optarg)); return TRUE; } static gboolean op_inject_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.modified++; options.op_inject = g_list_append(options.op_inject, (gchar *) g_strdup(optarg)); return TRUE; } static gboolean quorum_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (options.quorum) { free(options.quorum); } options.modified++; options.quorum = strdup(optarg); return TRUE; } static gboolean save_dotfile_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (options.dot_file) { free(options.dot_file); } options.process = TRUE; options.dot_file = strdup(optarg); return TRUE; } static gboolean save_graph_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (options.graph_file) { free(options.graph_file); } options.process = TRUE; options.graph_file = strdup(optarg); return TRUE; } static gboolean show_scores_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.process = TRUE; options.show_scores = TRUE; return TRUE; } static gboolean simulate_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.process = TRUE; options.simulate = TRUE; return TRUE; } static gboolean ticket_activate_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.modified++; options.ticket_activate = g_list_append(options.ticket_activate, (gchar *) g_strdup(optarg)); return TRUE; } static gboolean ticket_grant_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.modified++; options.ticket_grant = g_list_append(options.ticket_grant, (gchar *) g_strdup(optarg)); return TRUE; } static gboolean ticket_revoke_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.modified++; options.ticket_revoke = g_list_append(options.ticket_revoke, (gchar *) g_strdup(optarg)); return TRUE; } static gboolean ticket_standby_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.modified++; options.ticket_standby = g_list_append(options.ticket_standby, (gchar *) g_strdup(optarg)); return TRUE; } static gboolean utilization_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.process = TRUE; options.show_utilization = TRUE; return TRUE; } static gboolean watchdog_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (options.watchdog) { free(options.watchdog); } options.modified++; options.watchdog = strdup(optarg); return TRUE; } static gboolean xml_file_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (options.xml_file) { free(options.xml_file); } options.xml_file = strdup(optarg); return TRUE; } static gboolean xml_pipe_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (options.xml_file) { free(options.xml_file); } options.xml_file = strdup("-"); return TRUE; } static GOptionEntry operation_entries[] = { { "run", 'R', 0, G_OPTION_ARG_NONE, &options.process, "Process the supplied input and show what actions the cluster will take in response", NULL }, { "simulate", 'S', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, simulate_cb, "Like --run, but also simulate taking those actions and show the resulting new status", NULL }, { "in-place", 'X', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, in_place_cb, "Like --simulate, but also store the results back to the input file", NULL }, { "show-attrs", 'A', 0, G_OPTION_ARG_NONE, &options.show_attrs, "Show node attributes", NULL }, { "show-failcounts", 'c', 0, G_OPTION_ARG_NONE, &options.show_failcounts, "Show resource fail counts", NULL }, { "show-scores", 's', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_scores_cb, "Show allocation scores", NULL }, { "show-utilization", 'U', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, utilization_cb, "Show utilization information", NULL }, { "profile", 'P', 0, G_OPTION_ARG_FILENAME, &options.test_dir, "Process all the XML files in the named directory to create profiling data", "DIR" }, { "repeat", 'N', 0, G_OPTION_ARG_INT, &options.repeat, "With --profile, repeat each test N times and print timings", "N" }, /* Deprecated */ { "pending", 'j', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.print_pending, "Display pending state if 'record-pending' is enabled", NULL }, { NULL } }; static GOptionEntry synthetic_entries[] = { { "node-up", 'u', 0, G_OPTION_ARG_CALLBACK, node_up_cb, "Simulate bringing a node online", "NODE" }, { "node-down", 'd', 0, G_OPTION_ARG_CALLBACK, node_down_cb, "Simulate taking a node offline", "NODE" }, { "node-fail", 'f', 0, G_OPTION_ARG_CALLBACK, node_fail_cb, "Simulate a node failing", "NODE" }, { "op-inject", 'i', 0, G_OPTION_ARG_CALLBACK, op_inject_cb, "Generate a failure for the cluster to react to in the simulation.\n" INDENT "See `Operation Specification` help for more information.", "OPSPEC" }, { "op-fail", 'F', 0, G_OPTION_ARG_CALLBACK, op_fail_cb, "If the specified task occurs during the simulation, have it fail with return code ${rc}.\n" INDENT "The transition will normally stop at the failed action.\n" INDENT "Save the result with --save-output and re-run with --xml-file.\n" INDENT "See `Operation Specification` help for more information.", "OPSPEC" }, { "set-datetime", 't', 0, G_OPTION_ARG_STRING, &options.use_date, "Set date/time (ISO 8601 format, see https://en.wikipedia.org/wiki/ISO_8601)", "DATETIME" }, { "quorum", 'q', 0, G_OPTION_ARG_CALLBACK, quorum_cb, "Set to '1' (or 'true') to indicate cluster has quorum", "QUORUM" }, { "watchdog", 'w', 0, G_OPTION_ARG_CALLBACK, watchdog_cb, "Set to '1' (or 'true') to indicate cluster has an active watchdog device", "DEVICE" }, { "ticket-grant", 'g', 0, G_OPTION_ARG_CALLBACK, ticket_grant_cb, "Simulate granting a ticket", "TICKET" }, { "ticket-revoke", 'r', 0, G_OPTION_ARG_CALLBACK, ticket_revoke_cb, "Simulate revoking a ticket", "TICKET" }, { "ticket-standby", 'b', 0, G_OPTION_ARG_CALLBACK, ticket_standby_cb, "Simulate making a ticket standby", "TICKET" }, { "ticket-activate", 'e', 0, G_OPTION_ARG_CALLBACK, ticket_activate_cb, "Simulate activating a ticket", "TICKET" }, { NULL } }; static GOptionEntry artifact_entries[] = { { "save-input", 'I', 0, G_OPTION_ARG_FILENAME, &options.input_file, "Save the input configuration to the named file", "FILE" }, { "save-output", 'O', 0, G_OPTION_ARG_FILENAME, &options.output_file, "Save the output configuration to the named file", "FILE" }, { "save-graph", 'G', 0, G_OPTION_ARG_CALLBACK, save_graph_cb, "Save the transition graph (XML format) to the named file", "FILE" }, { "save-dotfile", 'D', 0, G_OPTION_ARG_CALLBACK, save_dotfile_cb, "Save the transition graph (DOT format) to the named file", "FILE" }, { "all-actions", 'a', 0, G_OPTION_ARG_NONE, &options.all_actions, "Display all possible actions in DOT graph (even if not part of transition)", NULL }, { NULL } }; static GOptionEntry source_entries[] = { { "live-check", 'L', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, live_check_cb, "Connect to CIB manager and use the current CIB contents as input", NULL }, { "xml-file", 'x', 0, G_OPTION_ARG_CALLBACK, xml_file_cb, "Retrieve XML from the named file", "FILE" }, { "xml-pipe", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, xml_pipe_cb, "Retrieve XML from stdin", NULL }, { NULL } }; static void print_cluster_status(pe_working_set_t * data_set, unsigned int show_opts, const char *title, bool print_spacer) { pcmk__output_t *out = data_set->priv; unsigned int section_opts = pcmk_section_nodes | pcmk_section_resources; GList *all = NULL; all = g_list_prepend(all, (gpointer) "*"); if (options.show_attrs) { section_opts |= pcmk_section_attributes; } if (options.show_failcounts) { section_opts |= pcmk_section_failcounts | pcmk_section_failures; } PCMK__OUTPUT_SPACER_IF(out, print_spacer); out->begin_list(out, NULL, NULL, "%s", title); out->message(out, "cluster-status", data_set, 0, NULL, FALSE, section_opts, show_opts | pcmk_show_inactive_rscs, NULL, all, all); out->end_list(out); g_list_free(all); } static void print_transition_summary(pe_working_set_t *data_set, bool print_spacer) { pcmk__output_t *out = data_set->priv; PCMK__OUTPUT_SPACER_IF(out, print_spacer); out->begin_list(out, NULL, NULL, "Transition Summary"); LogNodeActions(data_set); g_list_foreach(data_set->resources, (GFunc) LogActions, data_set); out->end_list(out); } static int setup_input(const char *input, const char *output, GError **error) { int rc = pcmk_rc_ok; - cib_t *cib_conn = NULL; xmlNode *cib_object = NULL; char *local_output = NULL; if (input == NULL) { /* Use live CIB */ - cib_conn = cib_new(); - rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command); - rc = pcmk_legacy2rc(rc); - - if (rc == pcmk_rc_ok) { - rc = cib_conn->cmds->query(cib_conn, NULL, &cib_object, cib_scope_local | cib_sync_call); - } - - cib_conn->cmds->signoff(cib_conn); - cib_delete(cib_conn); - cib_conn = NULL; - + rc = cib__signon_query(NULL, &cib_object); if (rc != pcmk_rc_ok) { - rc = pcmk_legacy2rc(rc); g_set_error(error, PCMK__RC_ERROR, rc, - "Live CIB query failed: %s (%d)", pcmk_rc_str(rc), rc); + "CIB query failed: %s", pcmk_rc_str(rc)); return rc; - - } else if (cib_object == NULL) { - g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_NOINPUT, - "Live CIB query failed: empty result"); - return pcmk_rc_no_input; } } else if (pcmk__str_eq(input, "-", pcmk__str_casei)) { cib_object = filename2xml(NULL); } else { cib_object = filename2xml(input); } if (get_object_root(XML_CIB_TAG_STATUS, cib_object) == NULL) { create_xml_node(cib_object, XML_CIB_TAG_STATUS); } if (cli_config_update(&cib_object, NULL, FALSE) == FALSE) { free_xml(cib_object); return pcmk_rc_transform_failed; } if (validate_xml(cib_object, NULL, FALSE) != TRUE) { free_xml(cib_object); return pcmk_rc_schema_validation; } if (output == NULL) { char *pid = pcmk__getpid_s(); local_output = get_shadow_file(pid); temp_shadow = strdup(local_output); output = local_output; free(pid); } rc = write_xml_file(cib_object, output, FALSE); free_xml(cib_object); cib_object = NULL; if (rc < 0) { rc = pcmk_legacy2rc(rc); g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_CANTCREAT, "Could not create '%s': %s", output, pcmk_rc_str(rc)); return rc; } else { setenv("CIB_file", output, 1); free(local_output); return pcmk_rc_ok; } } static GOptionContext * build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) { GOptionContext *context = NULL; GOptionEntry extra_prog_entries[] = { { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &(args->quiet), "Display only essential output", NULL }, { NULL } }; const char *description = "Operation Specification:\n\n" "The OPSPEC in any command line option is of the form\n" "${resource}_${task}_${interval_in_ms}@${node}=${rc}\n" "(memcached_monitor_20000@bart.example.com=7, for example).\n" "${rc} is an OCF return code. For more information on these\n" "return codes, refer to https://clusterlabs.org/pacemaker/doc/2.1/Pacemaker_Administration/html/agents.html#ocf-return-codes\n\n" "Examples:\n\n" "Pretend a recurring monitor action found memcached stopped on node\n" "fred.example.com and, during recovery, that the memcached stop\n" "action failed:\n\n" "\tcrm_simulate -LS --op-inject memcached:0_monitor_20000@bart.example.com=7 " "--op-fail memcached:0_stop_0@fred.example.com=1 --save-output /tmp/memcached-test.xml\n\n" "Now see what the reaction to the stop failed would be:\n\n" "\tcrm_simulate -S --xml-file /tmp/memcached-test.xml\n\n"; context = pcmk__build_arg_context(args, "text (default), xml", group, NULL); pcmk__add_main_args(context, extra_prog_entries); g_option_context_set_description(context, description); pcmk__add_arg_group(context, "operations", "Operations:", "Show operations options", operation_entries); pcmk__add_arg_group(context, "synthetic", "Synthetic Cluster Events:", "Show synthetic cluster event options", synthetic_entries); pcmk__add_arg_group(context, "artifact", "Artifact Options:", "Show artifact options", artifact_entries); pcmk__add_arg_group(context, "source", "Data Source:", "Show data source options", source_entries); return context; } static void reset(pe_working_set_t *data_set, xmlNodePtr input, pcmk__output_t *out) { data_set->input = input; data_set->priv = out; pcmk__set_effective_date(data_set, true, options.use_date); if(options.xml_file) { pe__set_working_set_flags(data_set, pe_flag_sanitized); } if (options.show_scores) { pe__set_working_set_flags(data_set, pe_flag_show_scores); } if (options.show_utilization) { pe__set_working_set_flags(data_set, pe_flag_show_utilization); } } int main(int argc, char **argv) { int printed = pcmk_rc_no_output; int rc = pcmk_rc_ok; pe_working_set_t *data_set = NULL; pcmk__output_t *out = NULL; xmlNode *input = NULL; GError *error = NULL; GOptionGroup *output_group = NULL; pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); gchar **processed_args = pcmk__cmdline_preproc(argv, "bdefgiqrtuwxDFGINOP"); GOptionContext *context = build_arg_context(args, &output_group); /* This must come before g_option_context_parse_strv. */ options.xml_file = strdup("-"); 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_simulate", args->verbosity); rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv); if (rc != pcmk_rc_ok) { fprintf(stderr, "Error creating output format %s: %s\n", args->output_ty, pcmk_rc_str(rc)); exit_code = CRM_EX_ERROR; goto done; } if (pcmk__str_eq(args->output_ty, "text", pcmk__str_null_matches) && !options.show_scores && !options.show_utilization) { pcmk__force_args(context, &error, "%s --text-fancy", g_get_prgname()); } else if (pcmk__str_eq(args->output_ty, "xml", pcmk__str_none)) { pcmk__force_args(context, &error, "%s --xml-simple-list --xml-substitute", g_get_prgname()); } pe__register_messages(out); pcmk__register_lib_messages(out); out->quiet = args->quiet; if (args->version) { out->version(out, false); goto done; } if (args->verbosity > 0) { #ifdef PCMK__COMPAT_2_0 /* Redirect stderr to stdout so we can grep the output */ close(STDERR_FILENO); dup2(STDOUT_FILENO, STDERR_FILENO); #endif } data_set = pe_new_working_set(); if (data_set == NULL) { rc = ENOMEM; g_set_error(&error, PCMK__RC_ERROR, rc, "Could not allocate working set"); goto done; } if (options.show_scores) { pe__set_working_set_flags(data_set, pe_flag_show_scores); } if (options.show_utilization) { pe__set_working_set_flags(data_set, pe_flag_show_utilization); } pe__set_working_set_flags(data_set, pe_flag_no_compat); if (options.test_dir != NULL) { data_set->priv = out; pcmk__profile_dir(options.test_dir, options.repeat, data_set, options.use_date); rc = pcmk_rc_ok; goto done; } rc = setup_input(options.xml_file, options.store ? options.xml_file : options.output_file, &error); if (rc != pcmk_rc_ok) { goto done; } - global_cib = cib_new(); - rc = global_cib->cmds->signon(global_cib, crm_system_name, cib_command); + rc = cib__signon_query(&global_cib, &input); if (rc != pcmk_rc_ok) { - rc = pcmk_legacy2rc(rc); - g_set_error(&error, PCMK__RC_ERROR, rc, - "Could not connect to the CIB: %s", pcmk_rc_str(rc)); - goto done; - } - - rc = global_cib->cmds->query(global_cib, NULL, &input, cib_sync_call | cib_scope_local); - if (rc != pcmk_rc_ok) { - rc = pcmk_legacy2rc(rc); g_set_error(&error, PCMK__RC_ERROR, rc, - "Could not get local CIB: %s", pcmk_rc_str(rc)); + "CIB query failed: %s", pcmk_rc_str(rc)); goto done; } reset(data_set, input, out); cluster_status(data_set); if (!out->is_quiet(out)) { if (pcmk_is_set(data_set->flags, pe_flag_maintenance_mode)) { printed = out->message(out, "maint-mode", data_set->flags); } if (data_set->disabled_resources || data_set->blocked_resources) { PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok); printed = out->info(out, "%d of %d resource instances DISABLED and %d BLOCKED " "from further action due to failure", data_set->disabled_resources, data_set->ninstances, data_set->blocked_resources); } /* Most formatted output headers use caps for each word, but this one * only has the first word capitalized for compatibility with pcs. */ print_cluster_status(data_set, options.print_pending ? pcmk_show_pending : 0, "Current cluster status", printed == pcmk_rc_ok); printed = pcmk_rc_ok; } if (options.modified) { PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok); modify_configuration(data_set, global_cib, options.quorum, options.watchdog, options.node_up, options.node_down, options.node_fail, options.op_inject, options.ticket_grant, options.ticket_revoke, options.ticket_standby, options.ticket_activate); printed = pcmk_rc_ok; rc = global_cib->cmds->query(global_cib, NULL, &input, cib_sync_call); if (rc != pcmk_rc_ok) { rc = pcmk_legacy2rc(rc); g_set_error(&error, PCMK__RC_ERROR, rc, "Could not get modified CIB: %s", pcmk_rc_str(rc)); goto done; } cleanup_calculations(data_set); reset(data_set, input, out); cluster_status(data_set); } if (options.input_file != NULL) { rc = write_xml_file(input, options.input_file, FALSE); if (rc < 0) { rc = pcmk_legacy2rc(rc); g_set_error(&error, PCMK__RC_ERROR, rc, "Could not create '%s': %s", options.input_file, pcmk_rc_str(rc)); goto done; } } if (options.process || options.simulate) { crm_time_t *local_date = NULL; pcmk__output_t *logger_out = NULL; if (pcmk_all_flags_set(data_set->flags, pe_flag_show_scores|pe_flag_show_utilization)) { PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok); out->begin_list(out, NULL, NULL, "Allocation Scores and Utilization Information"); printed = pcmk_rc_ok; } else if (pcmk_is_set(data_set->flags, pe_flag_show_scores)) { PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok); out->begin_list(out, NULL, NULL, "Allocation Scores"); printed = pcmk_rc_ok; } else if (pcmk_is_set(data_set->flags, pe_flag_show_utilization)) { PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok); out->begin_list(out, NULL, NULL, "Utilization Information"); printed = pcmk_rc_ok; } else { logger_out = pcmk__new_logger(); if (logger_out == NULL) { goto done; } data_set->priv = logger_out; } pcmk__schedule_actions(data_set, input, local_date); if (logger_out == NULL) { out->end_list(out); } else { logger_out->finish(logger_out, CRM_EX_OK, true, NULL); pcmk__output_free(logger_out); data_set->priv = out; } input = NULL; /* Don't try and free it twice */ if (options.graph_file != NULL) { write_xml_file(data_set->graph, options.graph_file, FALSE); } if (options.dot_file != NULL) { rc = pcmk__write_sim_dotfile(data_set, options.dot_file, options.all_actions, args->verbosity > 0); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, "Could not open %s for writing: %s", options.dot_file, pcmk_rc_str(rc)); goto done; } } if (!out->is_quiet(out)) { print_transition_summary(data_set, printed == pcmk_rc_ok); } } rc = pcmk_rc_ok; if (options.simulate) { PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok); if (run_simulation(data_set, global_cib, options.op_fail) != pcmk_rc_ok) { rc = pcmk_rc_error; } if (!out->is_quiet(out)) { pcmk__set_effective_date(data_set, true, options.use_date); if (options.show_scores) { pe__set_working_set_flags(data_set, pe_flag_show_scores); } if (options.show_utilization) { pe__set_working_set_flags(data_set, pe_flag_show_utilization); } cluster_status(data_set); print_cluster_status(data_set, 0, "Revised Cluster Status", true); } } done: pcmk__output_and_clear_error(error, NULL); /* There sure is a lot to free in options. */ free(options.dot_file); free(options.graph_file); g_free(options.input_file); g_list_free_full(options.node_up, g_free); g_list_free_full(options.node_down, g_free); g_list_free_full(options.node_fail, g_free); g_list_free_full(options.op_fail, g_free); g_list_free_full(options.op_inject, g_free); g_free(options.output_file); free(options.quorum); g_free(options.test_dir); g_list_free_full(options.ticket_grant, g_free); g_list_free_full(options.ticket_revoke, g_free); g_list_free_full(options.ticket_standby, g_free); g_list_free_full(options.ticket_activate, g_free); free(options.use_date); free(options.watchdog); free(options.xml_file); pcmk__free_arg_context(context); g_strfreev(processed_args); if (data_set) { pe_free_working_set(data_set); } if (global_cib) { global_cib->cmds->signoff(global_cib); cib_delete(global_cib); } fflush(stderr); if (temp_shadow) { unlink(temp_shadow); free(temp_shadow); } if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); } if (out != NULL) { out->finish(out, exit_code, true, NULL); pcmk__output_free(out); } crm_exit(exit_code); } diff --git a/tools/crm_verify.c b/tools/crm_verify.c index 2cc9c985e9..d7532c7ca6 100644 --- a/tools/crm_verify.c +++ b/tools/crm_verify.c @@ -1,304 +1,283 @@ /* * Copyright 2004-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include const char *SUMMARY = "Check a Pacemaker configuration for errors\n\n" "Check the well-formedness of a complete Pacemaker XML configuration,\n" "its conformance to the configured schema, and the presence of common\n" "misconfigurations. Problems reported as errors must be fixed before the\n" "cluster will work properly. It is left to the administrator to decide\n" "whether to fix problems reported as warnings."; struct { char *cib_save; gboolean use_live_cib; char *xml_file; gboolean xml_stdin; char *xml_string; } options; extern gboolean stage0(pe_working_set_t * data_set); static GOptionEntry data_entries[] = { { "live-check", 'L', 0, G_OPTION_ARG_NONE, &options.use_live_cib, "Check the configuration used by the running cluster", NULL }, { "xml-file", 'x', 0, G_OPTION_ARG_FILENAME, &options.xml_file, "Check the configuration in the named file", "FILE" }, { "xml-pipe", 'p', 0, G_OPTION_ARG_NONE, &options.xml_stdin, "Check the configuration piped in via stdin", NULL }, { "xml-text", 'X', 0, G_OPTION_ARG_STRING, &options.xml_string, "Check the configuration in the supplied string", "XML" }, { NULL } }; static GOptionEntry addl_entries[] = { { "save-xml", 'S', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &options.cib_save, "Save verified XML to named file (most useful with -L)", "FILE" }, { NULL } }; static pcmk__supported_format_t formats[] = { PCMK__SUPPORTED_FORMAT_NONE, PCMK__SUPPORTED_FORMAT_TEXT, PCMK__SUPPORTED_FORMAT_XML, { NULL, NULL, NULL } }; static GOptionContext * build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) { GOptionContext *context = NULL; const char *description = "Examples:\n\n" "Check the consistency of the configuration in the running cluster:\n\n" "\tcrm_verify --live-check\n\n" "Check the consistency of the configuration in a given file and " "produce verbose output:\n\n" "\tcrm_verify --xml-file file.xml --verbose\n\n"; context = pcmk__build_arg_context(args, "text (default), xml", group, NULL); g_option_context_set_description(context, description); pcmk__add_arg_group(context, "data", "Data sources:", "Show data options", data_entries); pcmk__add_arg_group(context, "additional", "Additional options:", "Show additional options", addl_entries); return context; } int main(int argc, char **argv) { xmlNode *cib_object = NULL; xmlNode *status = NULL; pe_working_set_t *data_set = NULL; - cib_t *cib_conn = NULL; const char *xml_tag = NULL; int rc = pcmk_rc_ok; crm_exit_t exit_code = CRM_EX_OK; GError *error = NULL; pcmk__output_t *out = NULL; GOptionGroup *output_group = NULL; pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); gchar **processed_args = pcmk__cmdline_preproc(argv, "xSX"); GOptionContext *context = build_arg_context(args, &output_group); pcmk__register_formats(output_group, formats); if (!g_option_context_parse_strv(context, &processed_args, &error)) { exit_code = CRM_EX_USAGE; goto done; } pcmk__cli_init_logging("crm_verify", 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; } if (args->version) { out->version(out, false); goto done; } pcmk__register_lib_messages(out); crm_info("=#=#=#=#= Getting XML =#=#=#=#="); if (options.use_live_cib) { - cib_conn = cib_new(); - rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command); - rc = pcmk_legacy2rc(rc); - } - - if (options.use_live_cib) { - if (rc == pcmk_rc_ok) { - int options = cib_scope_local | cib_sync_call; - - crm_info("Reading XML from: live cluster"); - rc = cib_conn->cmds->query(cib_conn, NULL, &cib_object, options); - rc = pcmk_legacy2rc(rc); - } + crm_info("Reading XML from: live cluster"); + rc = cib__signon_query(NULL, &cib_object); if (rc != pcmk_rc_ok) { - g_set_error(&error, PCMK__RC_ERROR, rc, "Live CIB query failed: %s", pcmk_rc_str(rc)); - goto done; - } - if (cib_object == NULL) { - rc = ENOMSG; - g_set_error(&error, PCMK__RC_ERROR, rc, "Live CIB query failed: empty result"); + g_set_error(&error, PCMK__RC_ERROR, rc, "CIB query failed: %s", pcmk_rc_str(rc)); goto done; } } else if (options.xml_file != NULL) { cib_object = filename2xml(options.xml_file); if (cib_object == NULL) { rc = ENODATA; g_set_error(&error, PCMK__RC_ERROR, rc, "Couldn't parse input file: %s", options.xml_file); goto done; } } else if (options.xml_string != NULL) { cib_object = string2xml(options.xml_string); if (cib_object == NULL) { rc = ENODATA; g_set_error(&error, PCMK__RC_ERROR, rc, "Couldn't parse input string: %s", options.xml_string); goto done; } } else if (options.xml_stdin) { cib_object = stdin2xml(); if (cib_object == NULL) { rc = ENODATA; g_set_error(&error, PCMK__RC_ERROR, rc, "Couldn't parse input from STDIN."); goto done; } } else { rc = ENODATA; g_set_error(&error, PCMK__RC_ERROR, rc, "No configuration source specified. Use --help for usage information."); goto done; } xml_tag = crm_element_name(cib_object); if (!pcmk__str_eq(xml_tag, XML_TAG_CIB, pcmk__str_casei)) { rc = EBADMSG; g_set_error(&error, PCMK__RC_ERROR, rc, "This tool can only check complete configurations (i.e. those starting with )."); goto done; } if (options.cib_save != NULL) { write_xml_file(cib_object, options.cib_save, FALSE); } status = get_object_root(XML_CIB_TAG_STATUS, cib_object); if (status == NULL) { create_xml_node(cib_object, XML_CIB_TAG_STATUS); } if (validate_xml(cib_object, NULL, FALSE) == FALSE) { pcmk__config_err("CIB did not pass schema validation"); free_xml(cib_object); cib_object = NULL; } else if (cli_config_update(&cib_object, NULL, FALSE) == FALSE) { crm_config_error = TRUE; free_xml(cib_object); cib_object = NULL; out->err(out, "The cluster will NOT be able to use this configuration.\n" "Please manually update the configuration to conform to the %s syntax.", xml_latest_schema()); } data_set = pe_new_working_set(); if (data_set == NULL) { rc = errno; crm_perror(LOG_CRIT, "Unable to allocate working set"); goto done; } pe__set_working_set_flags(data_set, pe_flag_no_counts|pe_flag_no_compat); data_set->priv = out; if (cib_object == NULL) { } else if (status != NULL || options.use_live_cib) { /* live queries will always have a status section and can do a full simulation */ pcmk__schedule_actions(data_set, cib_object, NULL); } else { data_set->now = crm_time_new(NULL); data_set->input = cib_object; stage0(data_set); } pe_free_working_set(data_set); if (crm_config_error) { rc = pcmk_rc_schema_validation; if (args->verbosity > 0) { g_set_error(&error, PCMK__RC_ERROR, rc, "Errors found during check: config not valid"); } else { g_set_error(&error, PCMK__RC_ERROR, rc, "Errors found during check: config not valid\n-V may provide more details"); } } else if (crm_config_warning) { rc = pcmk_rc_schema_validation; if (args->verbosity > 0) { g_set_error(&error, PCMK__RC_ERROR, rc, "Warnings found during check: config may not be valid"); } else { g_set_error(&error, PCMK__RC_ERROR, rc, "Warnings found during check: config may not be valid\n-V may provide more details"); } } - if (options.use_live_cib && cib_conn) { - cib_conn->cmds->signoff(cib_conn); - cib_delete(cib_conn); - } - done: g_strfreev(processed_args); pcmk__free_arg_context(context); free(options.cib_save); free(options.xml_file); free(options.xml_string); if (exit_code == CRM_EX_OK) { exit_code = pcmk_rc2exitc(rc); } pcmk__output_and_clear_error(error, NULL); if (out != NULL) { out->finish(out, exit_code, true, NULL); pcmk__output_free(out); } crm_exit(exit_code); }