diff --git a/daemons/attrd/pacemaker-attrd.c b/daemons/attrd/pacemaker-attrd.c index 4318e194a2..fcf17815fd 100644 --- a/daemons/attrd/pacemaker-attrd.c +++ b/daemons/attrd/pacemaker-attrd.c @@ -1,446 +1,449 @@ /* * Copyright 2013-2020 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pacemaker-attrd.h" lrmd_t *the_lrmd = NULL; crm_cluster_t *attrd_cluster = NULL; crm_trigger_t *attrd_config_read = NULL; static crm_exit_t attrd_exit_status = CRM_EX_OK; static void attrd_cpg_dispatch(cpg_handle_t handle, const struct cpg_name *groupName, uint32_t nodeid, uint32_t pid, void *msg, size_t msg_len) { uint32_t kind = 0; xmlNode *xml = NULL; const char *from = NULL; char *data = pcmk_message_common_cs(handle, nodeid, pid, msg, &kind, &from); if(data == NULL) { return; } if (kind == crm_class_cluster) { xml = string2xml(data); } if (xml == NULL) { crm_err("Bad message of class %d received from %s[%u]: '%.120s'", kind, from, nodeid, data); } else { crm_node_t *peer = crm_get_peer(nodeid, from); attrd_peer_message(peer, xml); } free_xml(xml); free(data); } static void attrd_cpg_destroy(gpointer unused) { if (attrd_shutting_down()) { crm_info("Corosync disconnection complete"); } else { crm_crit("Lost connection to cluster layer, shutting down"); attrd_exit_status = CRM_EX_DISCONNECT; attrd_shutdown(0); } } static void attrd_cib_replaced_cb(const char *event, xmlNode * msg) { if (attrd_shutting_down()) { return; } if (attrd_election_won()) { crm_notice("Updating all attributes after %s event", event); write_attributes(TRUE, FALSE); } // Check for changes in alerts mainloop_set_trigger(attrd_config_read); } static void attrd_cib_destroy_cb(gpointer user_data) { cib_t *conn = user_data; conn->cmds->signoff(conn); /* Ensure IPC is cleaned up */ if (attrd_shutting_down()) { crm_info("Connection disconnection complete"); } else { /* eventually this should trigger a reconnect, not a shutdown */ crm_crit("Lost connection to the CIB manager, shutting down"); attrd_exit_status = CRM_EX_DISCONNECT; attrd_shutdown(0); } return; } static void attrd_erase_cb(xmlNode *msg, int call_id, int rc, xmlNode *output, void *user_data) { do_crm_log_unlikely((rc? LOG_NOTICE : LOG_DEBUG), "Cleared transient attributes: %s " CRM_XS " xpath=%s rc=%d", pcmk_strerror(rc), (char *) user_data, rc); } #define XPATH_TRANSIENT "//node_state[@uname='%s']/" XML_TAG_TRANSIENT_NODEATTRS /*! * \internal * \brief Wipe all transient attributes for this node from the CIB * * Clear any previous transient node attributes from the CIB. This is * normally done by the DC's controller when this node leaves the cluster, but * this handles the case where the node restarted so quickly that the * cluster layer didn't notice. * * \todo If pacemaker-attrd respawns after crashing (see PCMK_respawned), * ideally we'd skip this and sync our attributes from the writer. * However, currently we reject any values for us that the writer has, in * attrd_peer_update(). */ static void attrd_erase_attrs() { int call_id; char *xpath = crm_strdup_printf(XPATH_TRANSIENT, attrd_cluster->uname); crm_info("Clearing transient attributes from CIB " CRM_XS " xpath=%s", xpath); call_id = the_cib->cmds->remove(the_cib, xpath, NULL, cib_quorum_override | cib_xpath); the_cib->cmds->register_callback_full(the_cib, call_id, 120, FALSE, xpath, "attrd_erase_cb", attrd_erase_cb, free); } static int attrd_cib_connect(int max_retry) { static int attempts = 0; int rc = -ENOTCONN; the_cib = cib_new(); if (the_cib == NULL) { return -ENOTCONN; } do { if(attempts > 0) { sleep(attempts); } attempts++; crm_debug("Connection attempt %d to the CIB manager", attempts); rc = the_cib->cmds->signon(the_cib, T_ATTRD, cib_command); } while(rc != pcmk_ok && attempts < max_retry); if (rc != pcmk_ok) { crm_err("Connection to the CIB manager failed: %s " CRM_XS " rc=%d", pcmk_strerror(rc), rc); goto cleanup; } crm_debug("Connected to the CIB manager after %d attempts", attempts); rc = the_cib->cmds->set_connection_dnotify(the_cib, attrd_cib_destroy_cb); if (rc != pcmk_ok) { crm_err("Could not set disconnection callback"); goto cleanup; } rc = the_cib->cmds->add_notify_callback(the_cib, T_CIB_REPLACE_NOTIFY, attrd_cib_replaced_cb); if(rc != pcmk_ok) { crm_err("Could not set CIB notification callback"); goto cleanup; } rc = the_cib->cmds->add_notify_callback(the_cib, T_CIB_DIFF_NOTIFY, attrd_cib_updated_cb); if (rc != pcmk_ok) { crm_err("Could not set CIB notification callback (update)"); goto cleanup; } return pcmk_ok; cleanup: the_cib->cmds->signoff(the_cib); cib_delete(the_cib); the_cib = NULL; return -ENOTCONN; } /*! * \internal * \brief Prepare the CIB after cluster is connected */ static void attrd_cib_init() { // We have no attribute values in memory, wipe the CIB to match attrd_erase_attrs(); // Set a trigger for reading the CIB (for the alerts section) attrd_config_read = mainloop_add_trigger(G_PRIORITY_HIGH, attrd_read_options, NULL); // Always read the CIB at start-up mainloop_set_trigger(attrd_config_read); } static qb_ipcs_service_t *ipcs = NULL; static int32_t attrd_ipc_dispatch(qb_ipcs_connection_t * c, void *data, size_t size) { uint32_t id = 0; uint32_t flags = 0; pcmk__client_t *client = pcmk__find_client(c); xmlNode *xml = NULL; const char *op; // Sanity-check, and parse XML from IPC data CRM_CHECK((c != NULL) && (client != NULL), return 0); if (data == NULL) { crm_debug("No IPC data from PID %d", pcmk__client_pid(c)); return 0; } xml = pcmk__client_data2xml(client, data, &id, &flags); if (xml == NULL) { crm_debug("Unrecognizable IPC data from PID %d", pcmk__client_pid(c)); return 0; } #if ENABLE_ACL CRM_ASSERT(client->user != NULL); crm_acl_get_set_user(xml, F_ATTRD_USER, client->user); #endif op = crm_element_value(xml, F_ATTRD_TASK); if (client->name == NULL) { const char *value = crm_element_value(xml, F_ORIG); client->name = crm_strdup_printf("%s.%d", value?value:"unknown", client->pid); } if (safe_str_eq(op, ATTRD_OP_PEER_REMOVE)) { attrd_send_ack(client, id, flags); attrd_client_peer_remove(client->name, xml); } else if (safe_str_eq(op, ATTRD_OP_CLEAR_FAILURE)) { attrd_send_ack(client, id, flags); attrd_client_clear_failure(xml); } else if (safe_str_eq(op, ATTRD_OP_UPDATE)) { attrd_send_ack(client, id, flags); attrd_client_update(xml); } else if (safe_str_eq(op, ATTRD_OP_UPDATE_BOTH)) { attrd_send_ack(client, id, flags); attrd_client_update(xml); } else if (safe_str_eq(op, ATTRD_OP_UPDATE_DELAY)) { attrd_send_ack(client, id, flags); attrd_client_update(xml); } else if (safe_str_eq(op, ATTRD_OP_REFRESH)) { attrd_send_ack(client, id, flags); attrd_client_refresh(); } else if (safe_str_eq(op, ATTRD_OP_QUERY)) { /* queries will get reply, so no ack is necessary */ attrd_client_query(client, id, flags, xml); } else { crm_info("Ignoring request from client %s with unknown operation %s", client->name, op); } free_xml(xml); return 0; } void attrd_ipc_fini() { if (ipcs != NULL) { pcmk__drop_all_clients(ipcs); qb_ipcs_destroy(ipcs); ipcs = NULL; } } static int attrd_cluster_connect() { attrd_cluster = calloc(1, sizeof(crm_cluster_t)); attrd_cluster->destroy = attrd_cpg_destroy; attrd_cluster->cpg.cpg_deliver_fn = attrd_cpg_dispatch; attrd_cluster->cpg.cpg_confchg_fn = pcmk_cpg_membership; crm_set_status_callback(&attrd_peer_change_cb); if (crm_cluster_connect(attrd_cluster) == FALSE) { crm_err("Cluster connection failed"); return -ENOTCONN; } return pcmk_ok; } -/* *INDENT-OFF* */ -static struct crm_option long_options[] = { - /* Top-level Options */ - {"help", 0, 0, '?', "\tThis text"}, - {"verbose", 0, 0, 'V', "\tIncrease debug output"}, - - {0, 0, 0, 0} +static pcmk__cli_option_t long_options[] = { + // long option, argument type, storage, short option, description, flags + { + "help", no_argument, NULL, '?', + "\tThis text", pcmk__option_default + }, + { + "verbose", no_argument, NULL, 'V', + "\tIncrease debug output", pcmk__option_default + }, + { 0, 0, 0, 0 } }; -/* *INDENT-ON* */ int main(int argc, char **argv) { int flag = 0; int index = 0; int argerr = 0; crm_ipc_t *old_instance = NULL; attrd_init_mainloop(); crm_log_preinit(NULL, argc, argv); - crm_set_options(NULL, "[options]", long_options, - "Daemon for aggregating and atomically storing node attribute updates into the CIB"); + pcmk__set_cli_options(NULL, "[options]", long_options, + "daemon for managing Pacemaker node attributes"); mainloop_add_signal(SIGTERM, attrd_shutdown); while (1) { - flag = crm_get_option(argc, argv, &index); + flag = pcmk__next_cli_option(argc, argv, &index, NULL); if (flag == -1) break; switch (flag) { case 'V': crm_bump_log_level(argc, argv); break; case 'h': /* Help message */ - crm_help(flag, CRM_EX_OK); + pcmk__cli_help(flag, CRM_EX_OK); break; default: ++argerr; break; } } if (optind > argc) { ++argerr; } if (argerr) { - crm_help('?', CRM_EX_USAGE); + pcmk__cli_help('?', CRM_EX_USAGE); } crm_log_init(T_ATTRD, LOG_INFO, TRUE, FALSE, argc, argv, FALSE); crm_notice("Starting Pacemaker node attribute manager"); old_instance = crm_ipc_new(T_ATTRD, 0); if (crm_ipc_connect(old_instance)) { /* IPC end-point already up */ crm_ipc_close(old_instance); crm_ipc_destroy(old_instance); crm_err("pacemaker-attrd is already active, aborting startup"); crm_exit(CRM_EX_OK); } else { /* not up or not authentic, we'll proceed either way */ crm_ipc_destroy(old_instance); old_instance = NULL; } attributes = g_hash_table_new_full(crm_str_hash, g_str_equal, NULL, free_attribute); /* Connect to the CIB before connecting to the cluster or listening for IPC. * This allows us to assume the CIB is connected whenever we process a * cluster or IPC message (which also avoids start-up race conditions). */ if (attrd_cib_connect(10) != pcmk_ok) { attrd_exit_status = CRM_EX_FATAL; goto done; } crm_info("CIB connection active"); if (attrd_cluster_connect() != pcmk_ok) { attrd_exit_status = CRM_EX_FATAL; goto done; } crm_info("Cluster connection active"); // Initialization that requires the cluster to be connected attrd_election_init(); attrd_cib_init(); /* Set a private attribute for ourselves with the protocol version we * support. This lets all nodes determine the minimum supported version * across all nodes. It also ensures that the writer learns our node name, * so it can send our attributes to the CIB. */ attrd_broadcast_protocol(); attrd_init_ipc(&ipcs, attrd_ipc_dispatch); crm_notice("Pacemaker node attribute manager successfully started and accepting connections"); attrd_run_mainloop(); done: crm_info("Shutting down attribute manager"); attrd_election_fini(); attrd_ipc_fini(); attrd_lrmd_disconnect(); attrd_cib_disconnect(); g_hash_table_destroy(attributes); crm_exit(attrd_exit_status); } diff --git a/daemons/based/pacemaker-based.c b/daemons/based/pacemaker-based.c index 124c511353..c3acee2cc4 100644 --- a/daemons/based/pacemaker-based.c +++ b/daemons/based/pacemaker-based.c @@ -1,366 +1,378 @@ /* * Copyright 2004-2020 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 extern int init_remote_listener(int port, gboolean encrypted); gboolean cib_shutdown_flag = FALSE; int cib_status = pcmk_ok; crm_cluster_t crm_cluster; GMainLoop *mainloop = NULL; const char *cib_root = NULL; char *cib_our_uname = NULL; static gboolean preserve_status = FALSE; gboolean cib_writes_enabled = TRUE; int remote_fd = 0; int remote_tls_fd = 0; GHashTable *config_hash = NULL; GHashTable *local_notify_queue = NULL; static void cib_init(void); void cib_shutdown(int nsig); static bool startCib(const char *filename); extern int write_cib_contents(gpointer p); void cib_cleanup(void); static void cib_enable_writes(int nsig) { crm_info("(Re)enabling disk writes"); cib_writes_enabled = TRUE; } -/* *INDENT-OFF* */ -static struct crm_option long_options[] = { - /* Top-level Options */ - {"help", 0, 0, '?', "\tThis text"}, - {"verbose", 0, 0, 'V', "\tIncrease debug output"}, - - {"stand-alone", 0, 0, 's', "\tAdvanced use only"}, - {"disk-writes", 0, 0, 'w', "\tAdvanced use only"}, - {"cib-root", 1, 0, 'r', "\tAdvanced use only"}, - - {0, 0, 0, 0} +static pcmk__cli_option_t long_options[] = { + // long option, argument type, storage, short option, description, flags + { + "help", no_argument, 0, '?', + "\tThis text", pcmk__option_default + }, + { + "verbose", no_argument, NULL, 'V', + "\tIncrease debug output", pcmk__option_default + }, + { + "stand-alone", no_argument, NULL, 's', + "\tAdvanced use only", pcmk__option_default + }, + { + "disk-writes", no_argument, NULL, 'w', + "\tAdvanced use only", pcmk__option_default + }, + { + "cib-root", required_argument, NULL, 'r', + "\tAdvanced use only", pcmk__option_default + }, + { 0, 0, 0, 0 } }; -/* *INDENT-ON* */ int main(int argc, char **argv) { int flag; int rc = 0; int index = 0; int argerr = 0; struct passwd *pwentry = NULL; crm_ipc_t *old_instance = NULL; crm_log_preinit(NULL, argc, argv); - crm_set_options(NULL, "[options]", - long_options, "Daemon for storing and replicating the cluster configuration"); + pcmk__set_cli_options(NULL, "[options]", long_options, + "daemon for managing the configuration " + "of a Pacemaker cluster"); mainloop_add_signal(SIGTERM, cib_shutdown); mainloop_add_signal(SIGPIPE, cib_enable_writes); cib_writer = mainloop_add_trigger(G_PRIORITY_LOW, write_cib_contents, NULL); while (1) { - flag = crm_get_option(argc, argv, &index); + flag = pcmk__next_cli_option(argc, argv, &index, NULL); if (flag == -1) break; switch (flag) { case 'V': crm_bump_log_level(argc, argv); break; case 's': stand_alone = TRUE; preserve_status = TRUE; cib_writes_enabled = FALSE; pwentry = getpwnam(CRM_DAEMON_USER); CRM_CHECK(pwentry != NULL, crm_perror(LOG_ERR, "Invalid uid (%s) specified", CRM_DAEMON_USER); return CRM_EX_FATAL); rc = setgid(pwentry->pw_gid); if (rc < 0) { crm_perror(LOG_ERR, "Could not set group to %d", pwentry->pw_gid); return CRM_EX_FATAL; } rc = initgroups(CRM_DAEMON_USER, pwentry->pw_gid); if (rc < 0) { crm_perror(LOG_ERR, "Could not setup groups for user %d", pwentry->pw_uid); return CRM_EX_FATAL; } rc = setuid(pwentry->pw_uid); if (rc < 0) { crm_perror(LOG_ERR, "Could not set user to %d", pwentry->pw_uid); return CRM_EX_FATAL; } break; case '?': /* Help message */ - crm_help(flag, CRM_EX_OK); + pcmk__cli_help(flag, CRM_EX_OK); break; case 'w': cib_writes_enabled = TRUE; break; case 'r': cib_root = optarg; break; case 'm': cib_metadata(); return CRM_EX_OK; default: ++argerr; break; } } if (argc - optind == 1 && safe_str_eq("metadata", argv[optind])) { cib_metadata(); return CRM_EX_OK; } if (optind > argc) { ++argerr; } if (argerr) { - crm_help('?', CRM_EX_USAGE); + pcmk__cli_help('?', CRM_EX_USAGE); } crm_log_init(NULL, LOG_INFO, TRUE, FALSE, argc, argv, FALSE); crm_notice("Starting Pacemaker CIB manager"); old_instance = crm_ipc_new(CIB_CHANNEL_RO, 0); if (crm_ipc_connect(old_instance)) { /* IPC end-point already up */ crm_ipc_close(old_instance); crm_ipc_destroy(old_instance); crm_err("pacemaker-based is already active, aborting startup"); crm_exit(CRM_EX_OK); } else { /* not up or not authentic, we'll proceed either way */ crm_ipc_destroy(old_instance); old_instance = NULL; } if (cib_root == NULL) { cib_root = CRM_CONFIG_DIR; } else { crm_notice("Using custom config location: %s", cib_root); } if (pcmk__daemon_can_write(cib_root, NULL) == FALSE) { crm_err("Terminating due to bad permissions on %s", cib_root); fprintf(stderr, "ERROR: Bad permissions on %s (see logs for details)\n", cib_root); fflush(stderr); return CRM_EX_FATAL; } crm_peer_init(); // Read initial CIB, connect to cluster, and start IPC servers cib_init(); // Run the main loop mainloop = g_main_loop_new(NULL, FALSE); crm_notice("Pacemaker CIB manager successfully started and accepting connections"); g_main_loop_run(mainloop); /* If main loop returned, clean up and exit. We disconnect in case * terminate_cib() was called with fast=-1. */ crm_cluster_disconnect(&crm_cluster); cib_ipc_servers_destroy(ipcs_ro, ipcs_rw, ipcs_shm); return CRM_EX_OK; } void cib_cleanup(void) { crm_peer_destroy(); if (local_notify_queue) { g_hash_table_destroy(local_notify_queue); } pcmk__client_cleanup(); g_hash_table_destroy(config_hash); free(cib_our_uname); } #if SUPPORT_COROSYNC static void cib_cs_dispatch(cpg_handle_t handle, const struct cpg_name *groupName, uint32_t nodeid, uint32_t pid, void *msg, size_t msg_len) { uint32_t kind = 0; xmlNode *xml = NULL; const char *from = NULL; char *data = pcmk_message_common_cs(handle, nodeid, pid, msg, &kind, &from); if(data == NULL) { return; } if (kind == crm_class_cluster) { xml = string2xml(data); if (xml == NULL) { crm_err("Invalid XML: '%.120s'", data); free(data); return; } crm_xml_add(xml, F_ORIG, from); /* crm_xml_add_int(xml, F_SEQ, wrapper->id); */ cib_peer_callback(xml, NULL); } free_xml(xml); free(data); } static void cib_cs_destroy(gpointer user_data) { if (cib_shutdown_flag) { crm_info("Corosync disconnection complete"); } else { crm_crit("Lost connection to cluster layer, shutting down"); terminate_cib(__FUNCTION__, CRM_EX_DISCONNECT); } } #endif static void cib_peer_update_callback(enum crm_status_type type, crm_node_t * node, const void *data) { switch (type) { case crm_status_processes: if (cib_legacy_mode() && is_not_set(node->processes, crm_get_cluster_proc())) { uint32_t old = data? *(const uint32_t *)data : 0; if ((node->processes ^ old) & crm_proc_cpg) { crm_info("Attempting to disable legacy mode after %s left the cluster", node->uname); legacy_mode = FALSE; } } break; case crm_status_uname: case crm_status_nstate: if (cib_shutdown_flag && (crm_active_peers() < 2) && (pcmk__ipc_client_count() == 0)) { crm_info("No more peers"); terminate_cib(__FUNCTION__, -1); } break; } } static void cib_init(void) { if (is_corosync_cluster()) { #if SUPPORT_COROSYNC crm_cluster.destroy = cib_cs_destroy; crm_cluster.cpg.cpg_deliver_fn = cib_cs_dispatch; crm_cluster.cpg.cpg_confchg_fn = pcmk_cpg_membership; #endif } config_hash = crm_str_table_new(); if (startCib("cib.xml") == FALSE) { crm_crit("Cannot start CIB... terminating"); crm_exit(CRM_EX_NOINPUT); } if (stand_alone == FALSE) { if (is_corosync_cluster()) { crm_set_status_callback(&cib_peer_update_callback); } if (crm_cluster_connect(&crm_cluster) == FALSE) { crm_crit("Cannot sign in to the cluster... terminating"); crm_exit(CRM_EX_FATAL); } cib_our_uname = crm_cluster.uname; } else { cib_our_uname = strdup("localhost"); } cib_ipc_servers_init(&ipcs_ro, &ipcs_rw, &ipcs_shm, &ipc_ro_callbacks, &ipc_rw_callbacks); if (stand_alone) { cib_is_master = TRUE; } } static bool startCib(const char *filename) { gboolean active = FALSE; xmlNode *cib = readCibXmlFile(cib_root, filename, !preserve_status); if (activateCibXml(cib, TRUE, "start") == 0) { int port = 0; const char *port_s = NULL; active = TRUE; cib_read_config(config_hash, cib); port_s = crm_element_value(cib, "remote-tls-port"); if (port_s) { port = crm_parse_int(port_s, "0"); remote_tls_fd = init_remote_listener(port, TRUE); } port_s = crm_element_value(cib, "remote-clear-port"); if (port_s) { port = crm_parse_int(port_s, "0"); remote_fd = init_remote_listener(port, FALSE); } } return active; } diff --git a/daemons/controld/controld_control.c b/daemons/controld/controld_control.c index 32cd6c9cde..b4c1c0a0dd 100644 --- a/daemons/controld/controld_control.c +++ b/daemons/controld/controld_control.c @@ -1,772 +1,820 @@ /* * Copyright 2004-2020 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 qb_ipcs_service_t *ipcs = NULL; #if SUPPORT_COROSYNC extern gboolean crm_connect_corosync(crm_cluster_t * cluster); #endif void crm_shutdown(int nsig); gboolean crm_read_options(gpointer user_data); gboolean fsa_has_quorum = FALSE; crm_trigger_t *fsa_source = NULL; crm_trigger_t *config_read = NULL; bool no_quorum_suicide_escalation = FALSE; bool controld_shutdown_lock_enabled = false; /* A_HA_CONNECT */ void do_ha_control(long long action, enum crmd_fsa_cause cause, enum crmd_fsa_state cur_state, enum crmd_fsa_input current_input, fsa_data_t * msg_data) { gboolean registered = FALSE; static crm_cluster_t *cluster = NULL; if (cluster == NULL) { cluster = calloc(1, sizeof(crm_cluster_t)); } if (action & A_HA_DISCONNECT) { crm_cluster_disconnect(cluster); crm_info("Disconnected from the cluster"); set_bit(fsa_input_register, R_HA_DISCONNECTED); } if (action & A_HA_CONNECT) { crm_set_status_callback(&peer_update_callback); crm_set_autoreap(FALSE); if (is_corosync_cluster()) { #if SUPPORT_COROSYNC registered = crm_connect_corosync(cluster); #endif } if (registered == TRUE) { controld_election_init(cluster->uname); fsa_our_uname = cluster->uname; fsa_our_uuid = cluster->uuid; if(cluster->uuid == NULL) { crm_err("Could not obtain local uuid"); registered = FALSE; } } if (registered == FALSE) { set_bit(fsa_input_register, R_HA_DISCONNECTED); register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL); return; } populate_cib_nodes(node_update_none, __FUNCTION__); clear_bit(fsa_input_register, R_HA_DISCONNECTED); crm_info("Connected to the cluster"); } if (action & ~(A_HA_CONNECT | A_HA_DISCONNECT)) { crm_err("Unexpected action %s in %s", fsa_action2string(action), __FUNCTION__); } } /* A_SHUTDOWN */ void do_shutdown(long long action, enum crmd_fsa_cause cause, enum crmd_fsa_state cur_state, enum crmd_fsa_input current_input, fsa_data_t * msg_data) { /* just in case */ set_bit(fsa_input_register, R_SHUTDOWN); controld_disconnect_fencer(FALSE); } /* A_SHUTDOWN_REQ */ void do_shutdown_req(long long action, enum crmd_fsa_cause cause, enum crmd_fsa_state cur_state, enum crmd_fsa_input current_input, fsa_data_t * msg_data) { xmlNode *msg = NULL; set_bit(fsa_input_register, R_SHUTDOWN); crm_info("Sending shutdown request to all peers (DC is %s)", (fsa_our_dc? fsa_our_dc : "not set")); msg = create_request(CRM_OP_SHUTDOWN_REQ, NULL, NULL, CRM_SYSTEM_CRMD, CRM_SYSTEM_CRMD, NULL); /* set_bit(fsa_input_register, R_STAYDOWN); */ if (send_cluster_message(NULL, crm_msg_crmd, msg, TRUE) == FALSE) { register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL); } free_xml(msg); } extern char *max_generation_from; extern xmlNode *max_generation_xml; extern GHashTable *resource_history; extern GHashTable *voted; void crmd_fast_exit(crm_exit_t exit_code) { if (is_set(fsa_input_register, R_STAYDOWN)) { crm_warn("Inhibiting respawn "CRM_XS" remapping exit code %d to %d", exit_code, CRM_EX_FATAL); exit_code = CRM_EX_FATAL; } else if ((exit_code == CRM_EX_OK) && is_set(fsa_input_register, R_IN_RECOVERY)) { crm_err("Could not recover from internal error"); exit_code = CRM_EX_ERROR; } crm_exit(exit_code); } crm_exit_t crmd_exit(crm_exit_t exit_code) { GListPtr gIter = NULL; GMainLoop *mloop = crmd_mainloop; static bool in_progress = FALSE; if (in_progress && (exit_code == CRM_EX_OK)) { crm_debug("Exit is already in progress"); return exit_code; } else if(in_progress) { crm_notice("Error during shutdown process, exiting now with status %d (%s)", exit_code, crm_exit_str(exit_code)); crm_write_blackbox(SIGTRAP, NULL); crmd_fast_exit(exit_code); } in_progress = TRUE; crm_trace("Preparing to exit with status %d (%s)", exit_code, crm_exit_str(exit_code)); /* Suppress secondary errors resulting from us disconnecting everything */ set_bit(fsa_input_register, R_HA_DISCONNECTED); /* Close all IPC servers and clients to ensure any and all shared memory files are cleaned up */ if(ipcs) { crm_trace("Closing IPC server"); mainloop_del_ipc_server(ipcs); ipcs = NULL; } controld_close_attrd_ipc(); pe_subsystem_free(); controld_disconnect_fencer(TRUE); if ((exit_code == CRM_EX_OK) && (crmd_mainloop == NULL)) { crm_debug("No mainloop detected"); exit_code = CRM_EX_ERROR; } /* On an error, just get out. * * Otherwise, make the effort to have mainloop exit gracefully so * that it (mostly) cleans up after itself and valgrind has less * to report on - allowing real errors stand out */ if (exit_code != CRM_EX_OK) { crm_notice("Forcing immediate exit with status %d (%s)", exit_code, crm_exit_str(exit_code)); crm_write_blackbox(SIGTRAP, NULL); crmd_fast_exit(exit_code); } /* Clean up as much memory as possible for valgrind */ for (gIter = fsa_message_queue; gIter != NULL; gIter = gIter->next) { fsa_data_t *fsa_data = gIter->data; crm_info("Dropping %s: [ state=%s cause=%s origin=%s ]", fsa_input2string(fsa_data->fsa_input), fsa_state2string(fsa_state), fsa_cause2string(fsa_data->fsa_cause), fsa_data->origin); delete_fsa_input(fsa_data); } clear_bit(fsa_input_register, R_MEMBERSHIP); g_list_free(fsa_message_queue); fsa_message_queue = NULL; metadata_cache_fini(); controld_election_fini(); /* Tear down the CIB manager connection, but don't free it yet -- it could * be used when we drain the mainloop later. */ cib_free_callbacks(fsa_cib_conn); fsa_cib_conn->cmds->signoff(fsa_cib_conn); verify_stopped(fsa_state, LOG_WARNING); clear_bit(fsa_input_register, R_LRM_CONNECTED); lrm_state_destroy_all(); /* This basically will not work, since mainloop has a reference to it */ mainloop_destroy_trigger(fsa_source); fsa_source = NULL; mainloop_destroy_trigger(config_read); config_read = NULL; mainloop_destroy_trigger(transition_trigger); transition_trigger = NULL; pcmk__client_cleanup(); crm_peer_destroy(); controld_free_fsa_timers(); te_cleanup_stonith_history_sync(NULL, TRUE); controld_free_sched_timer(); free(fsa_our_dc_version); fsa_our_dc_version = NULL; free(fsa_our_uname); fsa_our_uname = NULL; free(fsa_our_uuid); fsa_our_uuid = NULL; free(fsa_our_dc); fsa_our_dc = NULL; free(fsa_cluster_name); fsa_cluster_name = NULL; free(te_uuid); te_uuid = NULL; free(failed_stop_offset); failed_stop_offset = NULL; free(failed_start_offset); failed_start_offset = NULL; free(max_generation_from); max_generation_from = NULL; free_xml(max_generation_xml); max_generation_xml = NULL; mainloop_destroy_signal(SIGPIPE); mainloop_destroy_signal(SIGUSR1); mainloop_destroy_signal(SIGTERM); mainloop_destroy_signal(SIGTRAP); /* leave SIGCHLD engaged as we might still want to drain some service-actions */ if (mloop) { GMainContext *ctx = g_main_loop_get_context(crmd_mainloop); /* Don't re-enter this block */ crmd_mainloop = NULL; /* no signals on final draining anymore */ mainloop_destroy_signal(SIGCHLD); crm_trace("Draining mainloop %d %d", g_main_loop_is_running(mloop), g_main_context_pending(ctx)); { int lpc = 0; while((g_main_context_pending(ctx) && lpc < 10)) { lpc++; crm_trace("Iteration %d", lpc); g_main_context_dispatch(ctx); } } crm_trace("Closing mainloop %d %d", g_main_loop_is_running(mloop), g_main_context_pending(ctx)); g_main_loop_quit(mloop); /* Won't do anything yet, since we're inside it now */ g_main_loop_unref(mloop); } else { mainloop_destroy_signal(SIGCHLD); } cib_delete(fsa_cib_conn); fsa_cib_conn = NULL; throttle_fini(); /* Graceful */ crm_trace("Done preparing for exit with status %d (%s)", exit_code, crm_exit_str(exit_code)); return exit_code; } /* A_EXIT_0, A_EXIT_1 */ void do_exit(long long action, enum crmd_fsa_cause cause, enum crmd_fsa_state cur_state, enum crmd_fsa_input current_input, fsa_data_t * msg_data) { crm_exit_t exit_code = CRM_EX_OK; int log_level = LOG_INFO; const char *exit_type = "gracefully"; if (action & A_EXIT_1) { log_level = LOG_ERR; exit_type = "forcefully"; exit_code = CRM_EX_ERROR; } verify_stopped(cur_state, LOG_ERR); do_crm_log(log_level, "Performing %s - %s exiting the controller", fsa_action2string(action), exit_type); crm_info("[%s] stopped (%d)", crm_system_name, exit_code); crmd_exit(exit_code); } static void sigpipe_ignore(int nsig) { return; } /* A_STARTUP */ void do_startup(long long action, enum crmd_fsa_cause cause, enum crmd_fsa_state cur_state, enum crmd_fsa_input current_input, fsa_data_t * msg_data) { crm_debug("Registering Signal Handlers"); mainloop_add_signal(SIGTERM, crm_shutdown); mainloop_add_signal(SIGPIPE, sigpipe_ignore); fsa_source = mainloop_add_trigger(G_PRIORITY_HIGH, crm_fsa_trigger, NULL); config_read = mainloop_add_trigger(G_PRIORITY_HIGH, crm_read_options, NULL); transition_trigger = mainloop_add_trigger(G_PRIORITY_LOW, te_graph_trigger, NULL); crm_debug("Creating CIB manager and executor objects"); fsa_cib_conn = cib_new(); lrm_state_init_local(); if (controld_init_fsa_timers() == FALSE) { register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL); } } // \return libqb error code (0 on success, -errno on error) static int32_t accept_controller_client(qb_ipcs_connection_t *c, uid_t uid, gid_t gid) { crm_trace("Accepting new IPC client connection"); if (pcmk__new_client(c, uid, gid) == NULL) { return -EIO; } return 0; } // \return libqb error code (0 on success, -errno on error) static int32_t dispatch_controller_ipc(qb_ipcs_connection_t * c, void *data, size_t size) { uint32_t id = 0; uint32_t flags = 0; pcmk__client_t *client = pcmk__find_client(c); xmlNode *msg = pcmk__client_data2xml(client, data, &id, &flags); pcmk__ipc_send_ack(client, id, flags, "ack"); if (msg == NULL) { return 0; } #if ENABLE_ACL CRM_ASSERT(client->user != NULL); crm_acl_get_set_user(msg, F_CRM_USER, client->user); #endif crm_xml_add(msg, F_CRM_SYS_FROM, client->id); if (controld_authorize_ipc_message(msg, client, NULL)) { crm_trace("Processing IPC message from %s", pcmk__client_name(client)); route_message(C_IPC_MESSAGE, msg); } trigger_fsa(fsa_source); free_xml(msg); return 0; } static int32_t crmd_ipc_closed(qb_ipcs_connection_t * c) { pcmk__client_t *client = pcmk__find_client(c); if (client) { crm_trace("Disconnecting %sregistered client %s (%p/%p)", (client->userdata? "" : "un"), pcmk__client_name(client), c, client); free(client->userdata); pcmk__free_client(client); trigger_fsa(fsa_source); } return 0; } static void crmd_ipc_destroy(qb_ipcs_connection_t * c) { crm_trace("Connection %p", c); crmd_ipc_closed(c); } /* A_STOP */ void do_stop(long long action, enum crmd_fsa_cause cause, enum crmd_fsa_state cur_state, enum crmd_fsa_input current_input, fsa_data_t * msg_data) { crm_trace("Closing IPC server"); mainloop_del_ipc_server(ipcs); ipcs = NULL; register_fsa_input(C_FSA_INTERNAL, I_TERMINATE, NULL); } /* A_STARTED */ void do_started(long long action, enum crmd_fsa_cause cause, enum crmd_fsa_state cur_state, enum crmd_fsa_input current_input, fsa_data_t * msg_data) { static struct qb_ipcs_service_handlers crmd_callbacks = { .connection_accept = accept_controller_client, .connection_created = NULL, .msg_process = dispatch_controller_ipc, .connection_closed = crmd_ipc_closed, .connection_destroyed = crmd_ipc_destroy }; if (cur_state != S_STARTING) { crm_err("Start cancelled... %s", fsa_state2string(cur_state)); return; } else if (is_set(fsa_input_register, R_MEMBERSHIP) == FALSE) { crm_info("Delaying start, no membership data (%.16llx)", R_MEMBERSHIP); crmd_fsa_stall(TRUE); return; } else if (is_set(fsa_input_register, R_LRM_CONNECTED) == FALSE) { crm_info("Delaying start, not connected to executor (%.16llx)", R_LRM_CONNECTED); crmd_fsa_stall(TRUE); return; } else if (is_set(fsa_input_register, R_CIB_CONNECTED) == FALSE) { crm_info("Delaying start, CIB not connected (%.16llx)", R_CIB_CONNECTED); crmd_fsa_stall(TRUE); return; } else if (is_set(fsa_input_register, R_READ_CONFIG) == FALSE) { crm_info("Delaying start, Config not read (%.16llx)", R_READ_CONFIG); crmd_fsa_stall(TRUE); return; } else if (is_set(fsa_input_register, R_PEER_DATA) == FALSE) { crm_info("Delaying start, No peer data (%.16llx)", R_PEER_DATA); crmd_fsa_stall(TRUE); return; } crm_debug("Init server comms"); ipcs = crmd_ipc_server_init(&crmd_callbacks); if (ipcs == NULL) { crm_err("Failed to create IPC server: shutting down and inhibiting respawn"); register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL); } else { crm_notice("Pacemaker controller successfully started and accepting connections"); } controld_trigger_fencer_connect(); clear_bit(fsa_input_register, R_STARTING); register_fsa_input(msg_data->fsa_cause, I_PENDING, NULL); } /* A_RECOVER */ void do_recover(long long action, enum crmd_fsa_cause cause, enum crmd_fsa_state cur_state, enum crmd_fsa_input current_input, fsa_data_t * msg_data) { set_bit(fsa_input_register, R_IN_RECOVERY); crm_warn("Fast-tracking shutdown in response to errors"); register_fsa_input(C_FSA_INTERNAL, I_TERMINATE, NULL); } -/* *INDENT-OFF* */ -static pe_cluster_option crmd_opts[] = { - /* name, old-name, validate, values, default, short description, long description */ - { "dc-version", NULL, "string", NULL, "none", NULL, - "Version of Pacemaker on the cluster's DC.", - "Includes the hash which identifies the exact changeset it was built from. Used for diagnostic purposes." - }, - { "cluster-infrastructure", NULL, "string", NULL, "corosync", NULL, - "The messaging stack on which Pacemaker is currently running.", - "Used for informational and diagnostic purposes." }, - { "cluster-name", NULL, "string", NULL, NULL, NULL, +static pcmk__cluster_option_t crmd_opts[] = { + /* name, old name, type, allowed values, + * default value, validator, + * short description, + * long description + */ + { + "dc-version", NULL, "string", NULL, "none", NULL, + "Pacemaker version on cluster node elected Designated Controller (DC)", + "Includes a hash which identifies the exact changeset the code was " + "built from. Used for diagnostic purposes." + }, + { + "cluster-infrastructure", NULL, "string", NULL, "corosync", NULL, + "The messaging stack on which Pacemaker is currently running", + "Used for informational and diagnostic purposes." + }, + { + "cluster-name", NULL, "string", NULL, NULL, NULL, "An arbitrary name for the cluster", "This optional value is mostly for users' convenience as desired " - "in administration, but may also be used in Pacemaker configuration " - "rules via the #cluster-name node attribute, and by higher-level tools " - "and resource agents." + "in administration, but may also be used in Pacemaker " + "configuration rules via the #cluster-name node attribute, and " + "by higher-level tools and resource agents." + }, + { + XML_CONFIG_ATTR_DC_DEADTIME, NULL, "time", + NULL, "20s", pcmk__valid_interval_spec, + "How long to wait for a response from other nodes during start-up", + "The optimal value will depend on the speed and load of your network " + "and the type of switches used." + }, + { + XML_CONFIG_ATTR_RECHECK, NULL, "time", + "Zero disables polling, while positive values are an interval in seconds" + "(unless other units are specified, for example \"5min\")", + "15min", pcmk__valid_interval_spec, + "Polling interval to recheck cluster state and evalute rules " + "with date specifications", + "Pacemaker is primarily event-driven, and looks ahead to know when to " + "recheck cluster state for failure timeouts and most time-based " + "rules. However, it will also recheck the cluster after this " + "amount of inactivity, to evaluate rules with date specifications " + "and serve as a fail-safe for certain types of scheduler bugs." + }, + { + "load-threshold", NULL, "percentage", NULL, + "80%", pcmk__valid_utilization, + "Maximum amount of system load that should be used by cluster nodes", + "The cluster will slow down its recovery process when the amount of " + "system resources used (currently CPU) approaches this limit", + }, + { + "node-action-limit", NULL, "integer", NULL, + "0", pcmk__valid_number, + "Maximum number of jobs that can be scheduled per node " + "(defaults to 2x cores)" }, - { XML_CONFIG_ATTR_DC_DEADTIME, NULL, "time", NULL, "20s", &check_time, - "How long to wait for a response from other nodes during startup.", - "The \"correct\" value will depend on the speed/load of your network and the type of switches used." - }, - { XML_CONFIG_ATTR_RECHECK, NULL, "time", - "Zero disables polling. Positive values are an interval in seconds (unless other SI units are specified. eg. 5min)", - "15min", &check_timer, - "Polling interval for time based changes to options, resource parameters and constraints.", - "The Cluster is primarily event driven, however the configuration can have elements that change based on time." - " To ensure these changes take effect, we can optionally poll the cluster's status for changes." - }, - - { "load-threshold", NULL, "percentage", NULL, "80%", &check_utilization, - "The maximum amount of system resources that should be used by nodes in the cluster", - "The cluster will slow down its recovery process when the amount of system resources used" - " (currently CPU) approaches this limit", - }, - { "node-action-limit", NULL, "integer", NULL, "0", &check_number, - "The maximum number of jobs that can be scheduled per node. Defaults to 2x cores"}, { XML_CONFIG_ATTR_FENCE_REACTION, NULL, "string", NULL, "stop", NULL, "How a cluster node should react if notified of its own fencing", "A cluster node may receive notification of its own fencing if fencing " "is misconfigured, or if fabric fencing is in use that doesn't cut " "cluster communication. Allowed values are \"stop\" to attempt to " "immediately stop pacemaker and stay stopped, or \"panic\" to attempt " "to immediately reboot the local node, falling back to stop on failure." }, - { XML_CONFIG_ATTR_ELECTION_FAIL, NULL, "time", NULL, "2min", &check_timer, - "*** Advanced Use Only ***.", "If need to adjust this value, it probably indicates the presence of a bug." - }, - { XML_CONFIG_ATTR_FORCE_QUIT, NULL, "time", NULL, "20min", &check_timer, - "*** Advanced Use Only ***.", "If need to adjust this value, it probably indicates the presence of a bug." - }, - { - "join-integration-timeout", "crmd-integration-timeout", - "time", NULL, "3min", &check_timer, + { + XML_CONFIG_ATTR_ELECTION_FAIL, NULL, "time", NULL, + "2min", pcmk__valid_interval_spec, "*** Advanced Use Only ***", - "If need to adjust this value, it probably indicates the presence of a bug" + "Declare an election failed if it is not decided within this much " + "time. If you need to adjust this value, it probably indicates " + "the presence of a bug." }, - { - "join-finalization-timeout", "crmd-finalization-timeout", - "time", NULL, "30min", &check_timer, + { + XML_CONFIG_ATTR_FORCE_QUIT, NULL, "time", NULL, + "20min", pcmk__valid_interval_spec, "*** Advanced Use Only ***", - "If you need to adjust this value, it probably indicates the presence of a bug" + "Exit immediately if shutdown does not complete within this much " + "time. If you need to adjust this value, it probably indicates " + "the presence of a bug." }, - { - "transition-delay", "crmd-transition-delay", - "time", NULL, "0s", &check_timer, - "*** Advanced Use Only *** Enabling this option will slow down cluster recovery under all conditions", - "Delay cluster recovery for the configured interval to allow for additional/related events to occur.\n" - "Useful if your configuration is sensitive to the order in which ping updates arrive." + { + "join-integration-timeout", "crmd-integration-timeout", "time", NULL, + "3min", pcmk__valid_interval_spec, + "*** Advanced Use Only ***", + "If you need to adjust this value, it probably indicates " + "the presence of a bug." + }, + { + "join-finalization-timeout", "crmd-finalization-timeout", "time", NULL, + "30min", pcmk__valid_interval_spec, + "*** Advanced Use Only ***", + "If you need to adjust this value, it probably indicates " + "the presence of a bug." + }, + { + "transition-delay", "crmd-transition-delay", "time", NULL, + "0s", pcmk__valid_interval_spec, + "*** Advanced Use Only *** Enabling this option will slow down " + "cluster recovery under all conditions", + "Delay cluster recovery for this much time to allow for additional " + "events to occur. Useful if your configuration is sensitive to " + "the order in which ping updates arrive." + }, + { + "stonith-watchdog-timeout", NULL, "time", NULL, + NULL, pcmk__valid_sbd_timeout, + "How long to wait before we can assume nodes are safely down " + "when sbd is in use", + NULL + }, + { + "stonith-max-attempts", NULL, "integer", NULL, + "10", pcmk__valid_positive_number, + "How many times fencing can fail before it will no longer be " + "immediately re-attempted on a target" }, - { "stonith-watchdog-timeout", NULL, "time", NULL, NULL, &check_sbd_timeout, - "How long to wait before we can assume nodes are safely down", NULL - }, - { "stonith-max-attempts",NULL,"integer",NULL,"10",&check_positive_number, - "How many times stonith can fail before it will no longer be attempted on a target" - }, // Already documented in libpe_status (other values must be kept identical) - { "no-quorum-policy", NULL, "enum", "stop, freeze, ignore, suicide", "stop", &check_quorum, NULL, NULL }, - { XML_CONFIG_ATTR_SHUTDOWN_LOCK, NULL, "boolean", NULL, "false", &check_boolean, NULL, NULL }, + { + "no-quorum-policy", NULL, "enum", "stop, freeze, ignore, suicide", + "stop", pcmk__valid_quorum, NULL, NULL + }, + { + XML_CONFIG_ATTR_SHUTDOWN_LOCK, NULL, "boolean", NULL, + "false", pcmk__valid_boolean, NULL, NULL + }, }; -/* *INDENT-ON* */ void crmd_metadata(void) { - config_metadata("pacemaker-controld", "1.0", - "controller properties", - "Cluster properties used by Pacemaker's controller," - " formerly known as crmd", - crmd_opts, DIMOF(crmd_opts)); + pcmk__print_option_metadata("pacemaker-controld", "1.0", + "Pacemaker controller options", + "Cluster options used by Pacemaker's " + "controller (formerly called crmd)", + crmd_opts, DIMOF(crmd_opts)); } static void verify_crmd_options(GHashTable * options) { - verify_all_options(options, crmd_opts, DIMOF(crmd_opts)); + pcmk__validate_cluster_options(options, crmd_opts, DIMOF(crmd_opts)); } static const char * crmd_pref(GHashTable * options, const char *name) { - return get_cluster_pref(options, crmd_opts, DIMOF(crmd_opts), name); + return pcmk__cluster_option(options, crmd_opts, DIMOF(crmd_opts), name); } static void config_query_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data) { const char *value = NULL; GHashTable *config_hash = NULL; crm_time_t *now = crm_time_new(NULL); xmlNode *crmconfig = NULL; xmlNode *alerts = NULL; if (rc != pcmk_ok) { fsa_data_t *msg_data = NULL; crm_err("Local CIB query resulted in an error: %s", pcmk_strerror(rc)); register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL); if (rc == -EACCES || rc == -pcmk_err_schema_validation) { crm_err("The cluster is mis-configured - shutting down and staying down"); set_bit(fsa_input_register, R_STAYDOWN); } goto bail; } crmconfig = output; if ((crmconfig) && (crm_element_name(crmconfig)) && (strcmp(crm_element_name(crmconfig), XML_CIB_TAG_CRMCONFIG) != 0)) { crmconfig = first_named_child(crmconfig, XML_CIB_TAG_CRMCONFIG); } if (!crmconfig) { fsa_data_t *msg_data = NULL; crm_err("Local CIB query for " XML_CIB_TAG_CRMCONFIG " section failed"); register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL); goto bail; } crm_debug("Call %d : Parsing CIB options", call_id); config_hash = crm_str_table_new(); pe_unpack_nvpairs(crmconfig, crmconfig, XML_CIB_TAG_PROPSET, NULL, config_hash, CIB_OPTIONS_FIRST, FALSE, now, NULL); verify_crmd_options(config_hash); value = crmd_pref(config_hash, XML_CONFIG_ATTR_DC_DEADTIME); election_trigger->period_ms = crm_parse_interval_spec(value); value = crmd_pref(config_hash, "node-action-limit"); /* Also checks migration-limit */ throttle_update_job_max(value); value = crmd_pref(config_hash, "load-threshold"); if(value) { throttle_set_load_target(strtof(value, NULL) / 100.0); } value = crmd_pref(config_hash, "no-quorum-policy"); if (safe_str_eq(value, "suicide") && pcmk_locate_sbd()) { no_quorum_suicide_escalation = TRUE; } set_fence_reaction(crmd_pref(config_hash, XML_CONFIG_ATTR_FENCE_REACTION)); value = crmd_pref(config_hash,"stonith-max-attempts"); update_stonith_max_attempts(value); value = crmd_pref(config_hash, XML_CONFIG_ATTR_FORCE_QUIT); shutdown_escalation_timer->period_ms = crm_parse_interval_spec(value); crm_debug("Shutdown escalation occurs if DC has not responded to request in %ums", shutdown_escalation_timer->period_ms); value = crmd_pref(config_hash, XML_CONFIG_ATTR_ELECTION_FAIL); controld_set_election_period(value); value = crmd_pref(config_hash, XML_CONFIG_ATTR_RECHECK); recheck_interval_ms = crm_parse_interval_spec(value); crm_debug("Re-run scheduler after %dms of inactivity", recheck_interval_ms); value = crmd_pref(config_hash, "transition-delay"); transition_timer->period_ms = crm_parse_interval_spec(value); value = crmd_pref(config_hash, "join-integration-timeout"); integration_timer->period_ms = crm_parse_interval_spec(value); value = crmd_pref(config_hash, "join-finalization-timeout"); finalization_timer->period_ms = crm_parse_interval_spec(value); value = crmd_pref(config_hash, XML_CONFIG_ATTR_SHUTDOWN_LOCK); controld_shutdown_lock_enabled = crm_is_true(value); free(fsa_cluster_name); fsa_cluster_name = NULL; value = g_hash_table_lookup(config_hash, "cluster-name"); if (value) { fsa_cluster_name = strdup(value); } alerts = first_named_child(output, XML_CIB_TAG_ALERTS); crmd_unpack_alerts(alerts); set_bit(fsa_input_register, R_READ_CONFIG); crm_trace("Triggering FSA: %s", __FUNCTION__); mainloop_set_trigger(fsa_source); g_hash_table_destroy(config_hash); bail: crm_time_free(now); } gboolean crm_read_options(gpointer user_data) { int call_id = fsa_cib_conn->cmds->query(fsa_cib_conn, "//" XML_CIB_TAG_CRMCONFIG " | //" XML_CIB_TAG_ALERTS, NULL, cib_xpath | cib_scope_local); fsa_register_cib_callback(call_id, FALSE, NULL, config_query_callback); crm_trace("Querying the CIB... call %d", call_id); return TRUE; } /* A_READCONFIG */ void do_read_config(long long action, enum crmd_fsa_cause cause, enum crmd_fsa_state cur_state, enum crmd_fsa_input current_input, fsa_data_t * msg_data) { throttle_init(); mainloop_set_trigger(config_read); } void crm_shutdown(int nsig) { if (crmd_mainloop != NULL && g_main_loop_is_running(crmd_mainloop)) { if (is_set(fsa_input_register, R_SHUTDOWN)) { crm_err("Escalating the shutdown"); register_fsa_input_before(C_SHUTDOWN, I_ERROR, NULL); } else { set_bit(fsa_input_register, R_SHUTDOWN); register_fsa_input(C_SHUTDOWN, I_SHUTDOWN, NULL); if (shutdown_escalation_timer->period_ms == 0) { const char *value = crmd_pref(NULL, XML_CONFIG_ATTR_FORCE_QUIT); shutdown_escalation_timer->period_ms = crm_parse_interval_spec(value); } /* can't rely on this... */ crm_notice("Shutting down cluster resource manager " CRM_XS " limit=%ums", shutdown_escalation_timer->period_ms); controld_start_timer(shutdown_escalation_timer); } } else { crm_info("exit from shutdown"); crmd_exit(CRM_EX_OK); } } diff --git a/daemons/controld/pacemaker-controld.c b/daemons/controld/pacemaker-controld.c index 9092cced92..24e5f9bc69 100644 --- a/daemons/controld/pacemaker-controld.c +++ b/daemons/controld/pacemaker-controld.c @@ -1,172 +1,176 @@ /* - * Copyright 2004-2019 the Pacemaker project contributors + * Copyright 2004-2020 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 #define OPTARGS "hV" void usage(const char *cmd, int exit_status); _Noreturn void crmd_init(void); void crmd_hamsg_callback(const xmlNode * msg, void *private_data); extern void init_dotfile(void); GMainLoop *crmd_mainloop = NULL; -/* *INDENT-OFF* */ -static struct crm_option long_options[] = { - /* Top-level Options */ - {"help", 0, 0, '?', "\tThis text"}, - {"verbose", 0, 0, 'V', "\tIncrease debug output"}, - - {0, 0, 0, 0} +static pcmk__cli_option_t long_options[] = { + // long option, argument type, storage, short option, description, flags + { + "help", no_argument, NULL, '?', + "\tThis text", pcmk__option_default + }, + { + "verbose", no_argument, NULL, 'V', + "\tIncrease debug output", pcmk__option_default + }, + { 0, 0, 0, 0 } }; -/* *INDENT-ON* */ int main(int argc, char **argv) { int flag; int index = 0; int argerr = 0; crm_ipc_t *old_instance = NULL; crmd_mainloop = g_main_loop_new(NULL, FALSE); crm_log_preinit(NULL, argc, argv); - crm_set_options(NULL, "[options]", long_options, - "Daemon for aggregating resource and node failures as well as co-ordinating the cluster's response"); + pcmk__set_cli_options(NULL, "[options]", long_options, + "daemon for coordinating a Pacemaker cluster's " + "response to events"); while (1) { - flag = crm_get_option(argc, argv, &index); + flag = pcmk__next_cli_option(argc, argv, &index, NULL); if (flag == -1) break; switch (flag) { case 'V': crm_bump_log_level(argc, argv); break; case 'h': /* Help message */ - crm_help(flag, CRM_EX_OK); + pcmk__cli_help(flag, CRM_EX_OK); break; default: ++argerr; break; } } if (argc - optind == 1 && safe_str_eq("metadata", argv[optind])) { crmd_metadata(); return CRM_EX_OK; } else if (argc - optind == 1 && safe_str_eq("version", argv[optind])) { fprintf(stdout, "CRM Version: %s (%s)\n", PACEMAKER_VERSION, BUILD_VERSION); return CRM_EX_OK; } crm_log_init(NULL, LOG_INFO, TRUE, FALSE, argc, argv, FALSE); if (optind > argc) { ++argerr; } if (argerr) { - crm_help('?', CRM_EX_USAGE); + pcmk__cli_help('?', CRM_EX_USAGE); } crm_notice("Starting Pacemaker controller"); old_instance = crm_ipc_new(CRM_SYSTEM_CRMD, 0); if (crm_ipc_connect(old_instance)) { /* IPC end-point already up */ crm_ipc_close(old_instance); crm_ipc_destroy(old_instance); crm_err("pacemaker-controld is already active, aborting startup"); crm_exit(CRM_EX_OK); } else { /* not up or not authentic, we'll proceed either way */ crm_ipc_destroy(old_instance); old_instance = NULL; } if (pcmk__daemon_can_write(PE_STATE_DIR, NULL) == FALSE) { crm_err("Terminating due to bad permissions on " PE_STATE_DIR); fprintf(stderr, "ERROR: Bad permissions on " PE_STATE_DIR " (see logs for details)\n"); fflush(stderr); return CRM_EX_FATAL; } else if (pcmk__daemon_can_write(CRM_CONFIG_DIR, NULL) == FALSE) { crm_err("Terminating due to bad permissions on " CRM_CONFIG_DIR); fprintf(stderr, "ERROR: Bad permissions on " CRM_CONFIG_DIR " (see logs for details)\n"); fflush(stderr); return CRM_EX_FATAL; } crmd_init(); return 0; // not reachable } static void log_deprecation_warnings() { // Add deprecations here as needed } void crmd_init(void) { crm_exit_t exit_code = CRM_EX_OK; enum crmd_fsa_state state; log_deprecation_warnings(); fsa_state = S_STARTING; fsa_input_register = 0; /* zero out the regester */ init_dotfile(); register_fsa_input(C_STARTUP, I_STARTUP, NULL); crm_peer_init(); state = s_crmd_fsa(C_STARTUP); if (state == S_PENDING || state == S_STARTING) { /* Create the mainloop and run it... */ crm_trace("Starting %s's mainloop", crm_system_name); g_main_loop_run(crmd_mainloop); if (is_set(fsa_input_register, R_STAYDOWN)) { crm_info("Inhibiting automated respawn"); exit_code = CRM_EX_FATAL; } } else { crm_err("Startup of %s failed. Current state: %s", crm_system_name, fsa_state2string(state)); exit_code = CRM_EX_ERROR; } crm_info("%s[%lu] exiting with status %d (%s)", crm_system_name, (unsigned long) getpid(), exit_code, crm_exit_str(exit_code)); crmd_fast_exit(exit_code); } diff --git a/daemons/execd/cts-exec-helper.c b/daemons/execd/cts-exec-helper.c index ec8eb60e5a..1ff2a11c05 100644 --- a/daemons/execd/cts-exec-helper.c +++ b/daemons/execd/cts-exec-helper.c @@ -1,624 +1,695 @@ /* - * Copyright 2012-2019 the Pacemaker project contributors + * Copyright 2012-2020 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 -/* *INDENT-OFF* */ -static struct crm_option long_options[] = { - {"help", 0, 0, '?'}, - {"verbose", 0, 0, 'V', "\t\tPrint out logs and events to screen"}, - {"quiet", 0, 0, 'Q', "\t\tSuppress all output to screen"}, - {"tls", 0, 0, 'S', "\t\tUse tls backend for local connection"}, - {"listen", 1, 0, 'l', "\tListen for a specific event string"}, - {"api-call", 1, 0, 'c', "\tDirectly relates to executor API functions"}, - {"no-wait", 0, 0, 'w', "\tMake api call and do not wait for result."}, - {"is-running", 0, 0, 'R', "\tDetermine if a resource is registered and running."}, - {"notify-orig", 0, 0, 'n', "\tOnly notify this client the results of an api action."}, - {"notify-changes", 0, 0, 'o', "\tOnly notify client changes to recurring operations."}, - {"-spacer-", 1, 0, '-', "\nParameters for api-call option"}, - {"action", 1, 0, 'a'}, - {"rsc-id", 1, 0, 'r'}, - {"cancel-call-id", 1, 0, 'x'}, - {"provider", 1, 0, 'P'}, - {"class", 1, 0, 'C'}, - {"type", 1, 0, 'T'}, - {"interval", 1, 0, 'i'}, - {"timeout", 1, 0, 't'}, - {"start-delay", 1, 0, 's'}, - {"param-key", 1, 0, 'k'}, - {"param-val", 1, 0, 'v'}, - - {"-spacer-", 1, 0, '-'}, - {0, 0, 0, 0} +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 } }; -/* *INDENT-ON* */ 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), \ services_lrm_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 (safe_str_eq(options.listen, event_buf_v0)) { 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, services_lrm_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, "pacemaker-execd", 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 (safe_str_eq(options.api_call, "exec")) { 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 (safe_str_eq(options.api_call, "register_rsc")) { rc = lrmd_conn->cmds->register_rsc(lrmd_conn, options.rsc_id, options.class, options.provider, options.type, 0); } else if (safe_str_eq(options.api_call, "get_rsc_info")) { 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 (safe_str_eq(options.api_call, "unregister_rsc")) { rc = lrmd_conn->cmds->unregister_rsc(lrmd_conn, options.rsc_id, 0); } else if (safe_str_eq(options.api_call, "cancel")) { rc = lrmd_conn->cmds->cancel(lrmd_conn, options.rsc_id, options.action, options.interval_ms); } else if (safe_str_eq(options.api_call, "metadata")) { 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 (safe_str_eq(options.api_call, "list_agents")) { 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 (safe_str_eq(options.api_call, "list_ocf_providers")) { 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 (safe_str_eq(options.api_call, "list_standards")) { 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 (safe_str_eq(options.api_call, "get_recurring_ops")) { 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; 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; } set_bit(data_set->flags, pe_flag_no_counts); set_bit(data_set->flags, pe_flag_no_compat); cib_conn = cib_new(); rc = cib_conn->cmds->signon(cib_conn, "cts-exec-helper", 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_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); 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 = crm_str_table_new(); meta = crm_str_table_new(); get_rsc_attributes(params, rsc, NULL, data_set); get_meta_attributes(meta, rsc, NULL, data_set); if (params) { 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); } g_hash_table_destroy(params); } 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; crm_log_cli_init("cts-exec-helper"); - crm_set_options(NULL, "mode [options]", long_options, - "Inject commands into the executor, and watch for events\n"); + pcmk__set_cli_options(NULL, " [options]", long_options, + "inject commands into the Pacemaker executor, " + "and watch for events"); while (1) { - flag = crm_get_option(argc, argv, &option_index); + flag = pcmk__next_cli_option(argc, argv, &option_index, NULL); if (flag == -1) break; switch (flag) { case '?': - crm_help(flag, CRM_EX_OK); + 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) { - crm_help('?', CRM_EX_USAGE); + pcmk__cli_help('?', CRM_EX_USAGE); } if (optind > argc) { ++argerr; } if (!options.listen && (safe_str_eq(options.api_call, "metadata") || safe_str_eq(options.api_call, "list_agents") || safe_str_eq(options.api_call, "list_standards") || safe_str_eq(options.api_call, "list_ocf_providers"))) { 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/daemons/execd/execd_commands.c b/daemons/execd/execd_commands.c index 6058ec77de..0550583dd3 100644 --- a/daemons/execd/execd_commands.c +++ b/daemons/execd/execd_commands.c @@ -1,1867 +1,1867 @@ /* * Copyright 2012-2020 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 #undef PCMK__TIME_USE_CGT #if HAVE_DECL_CLOCK_MONOTONIC && defined(CLOCK_MONOTONIC) \ && !defined(PCMK_TIME_EMERGENCY_CGT) # define PCMK__TIME_USE_CGT # include /* clock_gettime */ #endif #include #include #include #include #include #include #include #include "pacemaker-execd.h" #define EXIT_REASON_MAX_LEN 128 GHashTable *rsc_list = NULL; typedef struct lrmd_cmd_s { int timeout; guint interval_ms; int start_delay; int timeout_orig; int call_id; int exec_rc; int lrmd_op_status; int call_opts; /* Timer ids, must be removed on cmd destruction. */ int delay_id; int stonith_recurring_id; int rsc_deleted; int service_flags; char *client_id; char *origin; char *rsc_id; char *action; char *real_action; char *exit_reason; char *output; char *userdata_str; #ifdef PCMK__TIME_USE_CGT /* Recurring and systemd operations may involve more than one executor * command per operation, so they need info about the original and the most * recent. */ struct timespec t_first_run; /* Timestamp of when op first ran */ struct timespec t_run; /* Timestamp of when op most recently ran */ struct timespec t_first_queue; /* Timestamp of when op first was queued */ struct timespec t_queue; /* Timestamp of when op most recently was queued */ struct timespec t_rcchange; /* Timestamp of last rc change */ #endif int first_notify_sent; int last_notify_rc; int last_notify_op_status; int last_pid; GHashTable *params; } lrmd_cmd_t; static void cmd_finalize(lrmd_cmd_t * cmd, lrmd_rsc_t * rsc); static gboolean lrmd_rsc_dispatch(gpointer user_data); static void cancel_all_recurring(lrmd_rsc_t * rsc, const char *client_id); #ifdef PCMK__TIME_USE_CGT /*! * \internal * \brief Check whether a struct timespec has been set * * \param[in] timespec Time to check * * \return true if timespec has been set (i.e. is nonzero), false otherwise */ static inline bool time_is_set(struct timespec *timespec) { return (timespec != NULL) && ((timespec->tv_sec != 0) || (timespec->tv_nsec != 0)); } /*! * \internal * \brief Return difference between two times in milliseconds * * \param[in] now More recent time (or NULL to use current time) * \param[in] old Earlier time * * \return milliseconds difference (or 0 if old is NULL or has tv_sec zero) * * \note Can overflow on 32bit machines when the differences is around * 24 days or more. */ static int time_diff_ms(struct timespec *now, struct timespec *old) { int diff_ms = 0; if (time_is_set(old)) { struct timespec local_now = { 0, }; if (now == NULL) { clock_gettime(CLOCK_MONOTONIC, &local_now); now = &local_now; } diff_ms = (now->tv_sec - old->tv_sec) * 1000 + (now->tv_nsec - old->tv_nsec) / 1000000; } return diff_ms; } /*! * \internal * \brief Reset a command's operation times to their original values. * * Reset a command's run and queued timestamps to the timestamps of the original * command, so we report the entire time since then and not just the time since * the most recent command (for recurring and systemd operations). * * /param[in] cmd Executor command object to reset * * /note It's not obvious what the queued time should be for a systemd * start/stop operation, which might go like this: * initial command queued 5ms, runs 3s * monitor command queued 10ms, runs 10s * monitor command queued 10ms, runs 10s * Is the queued time for that operation 5ms, 10ms or 25ms? The current * implementation will report 5ms. If it's 25ms, then we need to * subtract 20ms from the total exec time so as not to count it twice. * We can implement that later if it matters to anyone ... */ static void cmd_original_times(lrmd_cmd_t * cmd) { cmd->t_run = cmd->t_first_run; cmd->t_queue = cmd->t_first_queue; } #endif static void log_finished(lrmd_cmd_t * cmd, int exec_time, int queue_time) { char pid_str[32] = { 0, }; int log_level = LOG_INFO; if (cmd->last_pid) { snprintf(pid_str, 32, "%d", cmd->last_pid); } if (safe_str_eq(cmd->action, "monitor")) { log_level = LOG_DEBUG; } do_crm_log(log_level, "%s %s (call %d%s%s) exited with status %d" #ifdef PCMK__TIME_USE_CGT " (execution time %dms, queue time %dms)", #endif cmd->rsc_id, cmd->action, cmd->call_id, (cmd->last_pid? ", PID " : ""), pid_str, cmd->exec_rc #ifdef PCMK__TIME_USE_CGT , exec_time, queue_time #endif ); } static void log_execute(lrmd_cmd_t * cmd) { int log_level = LOG_INFO; if (safe_str_eq(cmd->action, "monitor")) { log_level = LOG_DEBUG; } do_crm_log(log_level, "executing - rsc:%s action:%s call_id:%d", cmd->rsc_id, cmd->action, cmd->call_id); } static const char * normalize_action_name(lrmd_rsc_t * rsc, const char *action) { if (safe_str_eq(action, "monitor") && is_set(pcmk_get_ra_caps(rsc->class), pcmk_ra_cap_status)) { return "status"; } return action; } static lrmd_rsc_t * build_rsc_from_xml(xmlNode * msg) { xmlNode *rsc_xml = get_xpath_object("//" F_LRMD_RSC, msg, LOG_ERR); lrmd_rsc_t *rsc = NULL; rsc = calloc(1, sizeof(lrmd_rsc_t)); crm_element_value_int(msg, F_LRMD_CALLOPTS, &rsc->call_opts); rsc->rsc_id = crm_element_value_copy(rsc_xml, F_LRMD_RSC_ID); rsc->class = crm_element_value_copy(rsc_xml, F_LRMD_CLASS); rsc->provider = crm_element_value_copy(rsc_xml, F_LRMD_PROVIDER); rsc->type = crm_element_value_copy(rsc_xml, F_LRMD_TYPE); rsc->work = mainloop_add_trigger(G_PRIORITY_HIGH, lrmd_rsc_dispatch, rsc); rsc->st_probe_rc = -ENODEV; // if stonith, initialize to "not running" return rsc; } static lrmd_cmd_t * create_lrmd_cmd(xmlNode *msg, pcmk__client_t *client) { int call_options = 0; xmlNode *rsc_xml = get_xpath_object("//" F_LRMD_RSC, msg, LOG_ERR); lrmd_cmd_t *cmd = NULL; cmd = calloc(1, sizeof(lrmd_cmd_t)); crm_element_value_int(msg, F_LRMD_CALLOPTS, &call_options); cmd->call_opts = call_options; cmd->client_id = strdup(client->id); crm_element_value_int(msg, F_LRMD_CALLID, &cmd->call_id); crm_element_value_ms(rsc_xml, F_LRMD_RSC_INTERVAL, &cmd->interval_ms); crm_element_value_int(rsc_xml, F_LRMD_TIMEOUT, &cmd->timeout); crm_element_value_int(rsc_xml, F_LRMD_RSC_START_DELAY, &cmd->start_delay); cmd->timeout_orig = cmd->timeout; cmd->origin = crm_element_value_copy(rsc_xml, F_LRMD_ORIGIN); cmd->action = crm_element_value_copy(rsc_xml, F_LRMD_RSC_ACTION); cmd->userdata_str = crm_element_value_copy(rsc_xml, F_LRMD_RSC_USERDATA_STR); cmd->rsc_id = crm_element_value_copy(rsc_xml, F_LRMD_RSC_ID); cmd->params = xml2list(rsc_xml); if (safe_str_eq(g_hash_table_lookup(cmd->params, "CRM_meta_on_fail"), "block")) { crm_debug("Setting flag to leave pid group on timeout and only kill action pid for " CRM_OP_FMT, cmd->rsc_id, cmd->action, cmd->interval_ms); cmd->service_flags |= SVC_ACTION_LEAVE_GROUP; } return cmd; } static void stop_recurring_timer(lrmd_cmd_t *cmd) { if (cmd) { if (cmd->stonith_recurring_id) { g_source_remove(cmd->stonith_recurring_id); } cmd->stonith_recurring_id = 0; } } static void free_lrmd_cmd(lrmd_cmd_t * cmd) { stop_recurring_timer(cmd); if (cmd->delay_id) { g_source_remove(cmd->delay_id); } if (cmd->params) { g_hash_table_destroy(cmd->params); } free(cmd->origin); free(cmd->action); free(cmd->real_action); free(cmd->userdata_str); free(cmd->rsc_id); free(cmd->output); free(cmd->exit_reason); free(cmd->client_id); free(cmd); } static gboolean stonith_recurring_op_helper(gpointer data) { lrmd_cmd_t *cmd = data; lrmd_rsc_t *rsc; cmd->stonith_recurring_id = 0; if (!cmd->rsc_id) { return FALSE; } rsc = g_hash_table_lookup(rsc_list, cmd->rsc_id); CRM_ASSERT(rsc != NULL); /* take it out of recurring_ops list, and put it in the pending ops * to be executed */ rsc->recurring_ops = g_list_remove(rsc->recurring_ops, cmd); rsc->pending_ops = g_list_append(rsc->pending_ops, cmd); #ifdef PCMK__TIME_USE_CGT clock_gettime(CLOCK_MONOTONIC, &(cmd->t_queue)); if (!time_is_set(&(cmd->t_first_queue))) { cmd->t_first_queue = cmd->t_queue; } #endif mainloop_set_trigger(rsc->work); return FALSE; } static inline void start_recurring_timer(lrmd_cmd_t *cmd) { if (cmd && (cmd->interval_ms > 0)) { cmd->stonith_recurring_id = g_timeout_add(cmd->interval_ms, stonith_recurring_op_helper, cmd); } } static gboolean start_delay_helper(gpointer data) { lrmd_cmd_t *cmd = data; lrmd_rsc_t *rsc = NULL; cmd->delay_id = 0; rsc = cmd->rsc_id ? g_hash_table_lookup(rsc_list, cmd->rsc_id) : NULL; if (rsc) { mainloop_set_trigger(rsc->work); } return FALSE; } static gboolean merge_recurring_duplicate(lrmd_rsc_t * rsc, lrmd_cmd_t * cmd) { GListPtr gIter = NULL; lrmd_cmd_t * dup = NULL; gboolean dup_pending = FALSE; if (cmd->interval_ms == 0) { return 0; } for (gIter = rsc->pending_ops; gIter != NULL; gIter = gIter->next) { dup = gIter->data; if (safe_str_eq(cmd->action, dup->action) && (cmd->interval_ms == dup->interval_ms)) { dup_pending = TRUE; goto merge_dup; } } /* if dup is in recurring_ops list, that means it has already executed * and is in the interval loop. we can't just remove it in this case. */ for (gIter = rsc->recurring_ops; gIter != NULL; gIter = gIter->next) { dup = gIter->data; if (safe_str_eq(cmd->action, dup->action) && (cmd->interval_ms == dup->interval_ms)) { goto merge_dup; } } return FALSE; merge_dup: /* This should not occur. If it does, we need to investigate how something * like this is possible in the controller. */ crm_warn("Duplicate recurring op entry detected (" CRM_OP_FMT "), merging with previous op entry", rsc->rsc_id, normalize_action_name(rsc, dup->action), dup->interval_ms); /* merge */ dup->first_notify_sent = 0; free(dup->userdata_str); dup->userdata_str = cmd->userdata_str; cmd->userdata_str = NULL; dup->call_id = cmd->call_id; if (safe_str_eq(rsc->class, PCMK_RESOURCE_CLASS_STONITH)) { /* if we are waiting for the next interval, kick it off now */ if (dup_pending == TRUE) { stop_recurring_timer(cmd); stonith_recurring_op_helper(cmd); } } else if (dup_pending == FALSE) { /* if we've already handed this to the service lib, kick off an early execution */ services_action_kick(rsc->rsc_id, normalize_action_name(rsc, dup->action), dup->interval_ms); } free_lrmd_cmd(cmd); return TRUE; } static void schedule_lrmd_cmd(lrmd_rsc_t * rsc, lrmd_cmd_t * cmd) { gboolean dup_processed = FALSE; CRM_CHECK(cmd != NULL, return); CRM_CHECK(rsc != NULL, return); crm_trace("Scheduling %s on %s", cmd->action, rsc->rsc_id); dup_processed = merge_recurring_duplicate(rsc, cmd); if (dup_processed) { /* duplicate recurring cmd found, cmds merged */ return; } /* The controller expects the executor to automatically cancel * recurring operations before a resource stops. */ if (safe_str_eq(cmd->action, "stop")) { cancel_all_recurring(rsc, NULL); } rsc->pending_ops = g_list_append(rsc->pending_ops, cmd); #ifdef PCMK__TIME_USE_CGT clock_gettime(CLOCK_MONOTONIC, &(cmd->t_queue)); if (!time_is_set(&(cmd->t_first_queue))) { cmd->t_first_queue = cmd->t_queue; } #endif mainloop_set_trigger(rsc->work); if (cmd->start_delay) { cmd->delay_id = g_timeout_add(cmd->start_delay, start_delay_helper, cmd); } } static xmlNode * create_lrmd_reply(const char *origin, int rc, int call_id) { xmlNode *reply = create_xml_node(NULL, T_LRMD_REPLY); crm_xml_add(reply, F_LRMD_ORIGIN, origin); crm_xml_add_int(reply, F_LRMD_RC, rc); crm_xml_add_int(reply, F_LRMD_CALLID, call_id); return reply; } static void send_client_notify(gpointer key, gpointer value, gpointer user_data) { xmlNode *update_msg = user_data; pcmk__client_t *client = value; int rc; int log_level = LOG_WARNING; const char *msg = NULL; CRM_CHECK(client != NULL, return); if (client->name == NULL) { crm_trace("Skipping notification to client without name"); return; } rc = lrmd_server_send_notify(client, update_msg); if (rc == pcmk_rc_ok) { return; } switch (rc) { case ENOTCONN: case EPIPE: // Client exited without waiting for notification log_level = LOG_INFO; msg = "Disconnected"; break; default: msg = pcmk_rc_str(rc); break; } do_crm_log(log_level, "Could not notify client %s/%s: %s " CRM_XS " rc=%d", client->name, client->id, msg, rc); } static void send_cmd_complete_notify(lrmd_cmd_t * cmd) { xmlNode *notify = NULL; #ifdef PCMK__TIME_USE_CGT int exec_time = time_diff_ms(NULL, &(cmd->t_run)); int queue_time = time_diff_ms(&cmd->t_run, &(cmd->t_queue)); log_finished(cmd, exec_time, queue_time); #else log_finished(cmd, 0, 0); #endif /* if the first notify result for a cmd has already been sent earlier, and the * the option to only send notifies on result changes is set. Check to see * if the last result is the same as the new one. If so, suppress this update */ if (cmd->first_notify_sent && (cmd->call_opts & lrmd_opt_notify_changes_only)) { if (cmd->last_notify_rc == cmd->exec_rc && cmd->last_notify_op_status == cmd->lrmd_op_status) { /* only send changes */ return; } } cmd->first_notify_sent = 1; cmd->last_notify_rc = cmd->exec_rc; cmd->last_notify_op_status = cmd->lrmd_op_status; notify = create_xml_node(NULL, T_LRMD_NOTIFY); crm_xml_add(notify, F_LRMD_ORIGIN, __FUNCTION__); crm_xml_add_int(notify, F_LRMD_TIMEOUT, cmd->timeout); crm_xml_add_ms(notify, F_LRMD_RSC_INTERVAL, cmd->interval_ms); crm_xml_add_int(notify, F_LRMD_RSC_START_DELAY, cmd->start_delay); crm_xml_add_int(notify, F_LRMD_EXEC_RC, cmd->exec_rc); crm_xml_add_int(notify, F_LRMD_OP_STATUS, cmd->lrmd_op_status); crm_xml_add_int(notify, F_LRMD_CALLID, cmd->call_id); crm_xml_add_int(notify, F_LRMD_RSC_DELETED, cmd->rsc_deleted); #ifdef PCMK__TIME_USE_CGT crm_xml_add_ll(notify, F_LRMD_RSC_RUN_TIME, (long long) cmd->t_run.tv_sec); crm_xml_add_ll(notify, F_LRMD_RSC_RCCHANGE_TIME, (long long) cmd->t_rcchange.tv_sec); crm_xml_add_int(notify, F_LRMD_RSC_EXEC_TIME, exec_time); crm_xml_add_int(notify, F_LRMD_RSC_QUEUE_TIME, queue_time); #endif crm_xml_add(notify, F_LRMD_OPERATION, LRMD_OP_RSC_EXEC); crm_xml_add(notify, F_LRMD_RSC_ID, cmd->rsc_id); if(cmd->real_action) { crm_xml_add(notify, F_LRMD_RSC_ACTION, cmd->real_action); } else { crm_xml_add(notify, F_LRMD_RSC_ACTION, cmd->action); } crm_xml_add(notify, F_LRMD_RSC_USERDATA_STR, cmd->userdata_str); crm_xml_add(notify, F_LRMD_RSC_OUTPUT, cmd->output); crm_xml_add(notify, F_LRMD_RSC_EXIT_REASON, cmd->exit_reason); if (cmd->params) { char *key = NULL; char *value = NULL; GHashTableIter iter; xmlNode *args = create_xml_node(notify, XML_TAG_ATTRS); g_hash_table_iter_init(&iter, cmd->params); while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) { hash2smartfield((gpointer) key, (gpointer) value, args); } } if (cmd->client_id && (cmd->call_opts & lrmd_opt_notify_orig_only)) { pcmk__client_t *client = pcmk__find_client_by_id(cmd->client_id); if (client) { send_client_notify(client->id, client, notify); } } else { pcmk__foreach_ipc_client(send_client_notify, notify); } free_xml(notify); } static void send_generic_notify(int rc, xmlNode * request) { if (pcmk__ipc_client_count() != 0) { int call_id = 0; xmlNode *notify = NULL; xmlNode *rsc_xml = get_xpath_object("//" F_LRMD_RSC, request, LOG_ERR); const char *rsc_id = crm_element_value(rsc_xml, F_LRMD_RSC_ID); const char *op = crm_element_value(request, F_LRMD_OPERATION); crm_element_value_int(request, F_LRMD_CALLID, &call_id); notify = create_xml_node(NULL, T_LRMD_NOTIFY); crm_xml_add(notify, F_LRMD_ORIGIN, __FUNCTION__); crm_xml_add_int(notify, F_LRMD_RC, rc); crm_xml_add_int(notify, F_LRMD_CALLID, call_id); crm_xml_add(notify, F_LRMD_OPERATION, op); crm_xml_add(notify, F_LRMD_RSC_ID, rsc_id); pcmk__foreach_ipc_client(send_client_notify, notify); free_xml(notify); } } static void cmd_reset(lrmd_cmd_t * cmd) { cmd->lrmd_op_status = 0; cmd->last_pid = 0; #ifdef PCMK__TIME_USE_CGT memset(&cmd->t_run, 0, sizeof(cmd->t_run)); memset(&cmd->t_queue, 0, sizeof(cmd->t_queue)); #endif free(cmd->exit_reason); cmd->exit_reason = NULL; free(cmd->output); cmd->output = NULL; } static void cmd_finalize(lrmd_cmd_t * cmd, lrmd_rsc_t * rsc) { crm_trace("Resource operation rsc:%s action:%s completed (%p %p)", cmd->rsc_id, cmd->action, rsc ? rsc->active : NULL, cmd); if (rsc && (rsc->active == cmd)) { rsc->active = NULL; mainloop_set_trigger(rsc->work); } if (!rsc) { cmd->rsc_deleted = 1; } /* reset original timeout so client notification has correct information */ cmd->timeout = cmd->timeout_orig; send_cmd_complete_notify(cmd); if (cmd->interval_ms && (cmd->lrmd_op_status == PCMK_LRM_OP_CANCELLED)) { if (rsc) { rsc->recurring_ops = g_list_remove(rsc->recurring_ops, cmd); rsc->pending_ops = g_list_remove(rsc->pending_ops, cmd); } free_lrmd_cmd(cmd); } else if (cmd->interval_ms == 0) { if (rsc) { rsc->pending_ops = g_list_remove(rsc->pending_ops, cmd); } free_lrmd_cmd(cmd); } else { /* Clear all the values pertaining just to the last iteration of a recurring op. */ cmd_reset(cmd); } } static int ocf2uniform_rc(int rc) { if (rc < 0 || rc > PCMK_OCF_FAILED_MASTER) { return PCMK_OCF_UNKNOWN_ERROR; } return rc; } static int stonith2uniform_rc(const char *action, int rc) { switch (rc) { case pcmk_ok: rc = PCMK_OCF_OK; break; case -ENODEV: /* This should be possible only for probes in practice, but * interpret for all actions to be safe. */ if (safe_str_eq(action, "monitor")) { rc = PCMK_OCF_NOT_RUNNING; } else if (safe_str_eq(action, "stop")) { rc = PCMK_OCF_OK; } else { rc = PCMK_OCF_NOT_INSTALLED; } break; case -EOPNOTSUPP: rc = PCMK_OCF_UNIMPLEMENT_FEATURE; break; case -ETIME: case -ETIMEDOUT: rc = PCMK_OCF_TIMEOUT; break; default: rc = PCMK_OCF_UNKNOWN_ERROR; break; } return rc; } #if SUPPORT_NAGIOS static int nagios2uniform_rc(const char *action, int rc) { if (rc < 0) { return PCMK_OCF_UNKNOWN_ERROR; } switch (rc) { case NAGIOS_STATE_OK: return PCMK_OCF_OK; case NAGIOS_INSUFFICIENT_PRIV: return PCMK_OCF_INSUFFICIENT_PRIV; case NAGIOS_NOT_INSTALLED: return PCMK_OCF_NOT_INSTALLED; case NAGIOS_STATE_WARNING: case NAGIOS_STATE_CRITICAL: case NAGIOS_STATE_UNKNOWN: case NAGIOS_STATE_DEPENDENT: default: return PCMK_OCF_UNKNOWN_ERROR; } return PCMK_OCF_UNKNOWN_ERROR; } #endif static int get_uniform_rc(const char *standard, const char *action, int rc) { if (safe_str_eq(standard, PCMK_RESOURCE_CLASS_OCF)) { return ocf2uniform_rc(rc); } else if (safe_str_eq(standard, PCMK_RESOURCE_CLASS_STONITH)) { return stonith2uniform_rc(action, rc); } else if (safe_str_eq(standard, PCMK_RESOURCE_CLASS_SYSTEMD)) { return rc; } else if (safe_str_eq(standard, PCMK_RESOURCE_CLASS_UPSTART)) { return rc; #if SUPPORT_NAGIOS } else if (safe_str_eq(standard, PCMK_RESOURCE_CLASS_NAGIOS)) { return nagios2uniform_rc(action, rc); #endif } else { return services_get_ocf_exitcode(action, rc); } } static int action_get_uniform_rc(svc_action_t * action) { lrmd_cmd_t *cmd = action->cb_data; return get_uniform_rc(action->standard, cmd->action, action->rc); } struct notify_new_client_data { xmlNode *notify; pcmk__client_t *new_client; }; static void notify_one_client(gpointer key, gpointer value, gpointer user_data) { pcmk__client_t *client = value; struct notify_new_client_data *data = user_data; if (safe_str_neq(client->id, data->new_client->id)) { send_client_notify(key, (gpointer) client, (gpointer) data->notify); } } void notify_of_new_client(pcmk__client_t *new_client) { struct notify_new_client_data data; data.new_client = new_client; data.notify = create_xml_node(NULL, T_LRMD_NOTIFY); crm_xml_add(data.notify, F_LRMD_ORIGIN, __FUNCTION__); crm_xml_add(data.notify, F_LRMD_OPERATION, LRMD_OP_NEW_CLIENT); pcmk__foreach_ipc_client(notify_one_client, &data); free_xml(data.notify); } static char * parse_exit_reason(const char *output) { const char *cur = NULL; const char *last = NULL; static int cookie_len = 0; char *eol = NULL; size_t reason_len = EXIT_REASON_MAX_LEN; if (output == NULL) { return NULL; } if (!cookie_len) { cookie_len = strlen(PCMK_OCF_REASON_PREFIX); } cur = strstr(output, PCMK_OCF_REASON_PREFIX); for (; cur != NULL; cur = strstr(cur, PCMK_OCF_REASON_PREFIX)) { /* skip over the cookie delimiter string */ cur += cookie_len; last = cur; } if (last == NULL) { return NULL; } // Truncate everything after a new line, and limit reason string size eol = strchr(last, '\n'); if (eol) { reason_len = QB_MIN(reason_len, eol - last); } return strndup(last, reason_len); } void client_disconnect_cleanup(const char *client_id) { GHashTableIter iter; lrmd_rsc_t *rsc = NULL; char *key = NULL; g_hash_table_iter_init(&iter, rsc_list); while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & rsc)) { if (rsc->call_opts & lrmd_opt_drop_recurring) { /* This client is disconnecting, drop any recurring operations * it may have initiated on the resource */ cancel_all_recurring(rsc, client_id); } } } static void action_complete(svc_action_t * action) { lrmd_rsc_t *rsc; lrmd_cmd_t *cmd = action->cb_data; const char *rclass = NULL; #ifdef PCMK__TIME_USE_CGT bool goagain = false; #endif if (!cmd) { crm_err("Completed executor action (%s) does not match any known operations", action->id); return; } #ifdef PCMK__TIME_USE_CGT if (cmd->exec_rc != action->rc) { clock_gettime(CLOCK_MONOTONIC, &(cmd->t_rcchange)); } #endif cmd->last_pid = action->pid; cmd->exec_rc = action_get_uniform_rc(action); cmd->lrmd_op_status = action->status; rsc = cmd->rsc_id ? g_hash_table_lookup(rsc_list, cmd->rsc_id) : NULL; if (rsc && safe_str_eq(rsc->class, PCMK_RESOURCE_CLASS_SERVICE)) { rclass = resources_find_service_class(rsc->type); } else if(rsc) { rclass = rsc->class; } #ifdef PCMK__TIME_USE_CGT if (safe_str_eq(rclass, PCMK_RESOURCE_CLASS_SYSTEMD)) { if ((cmd->exec_rc == PCMK_OCF_OK) && (safe_str_eq(cmd->action, "start") || safe_str_eq(cmd->action, "stop"))) { /* systemd returns from start and stop actions after the action * begins, not after it completes. We have to jump through a few * hoops so that we don't report 'complete' to the rest of pacemaker * until it's actually done. */ goagain = true; cmd->real_action = cmd->action; cmd->action = strdup("monitor"); } else if (cmd->real_action != NULL) { // This is follow-up monitor to check whether start/stop completed if ((cmd->lrmd_op_status == PCMK_LRM_OP_DONE) && (cmd->exec_rc == PCMK_OCF_PENDING)) { goagain = true; } else if ((cmd->exec_rc == PCMK_OCF_OK) && safe_str_eq(cmd->real_action, "stop")) { goagain = true; } else { int time_sum = time_diff_ms(NULL, &(cmd->t_first_run)); int timeout_left = cmd->timeout_orig - time_sum; crm_debug("%s systemd %s is now complete (elapsed=%dms, " "remaining=%dms): %s (%d)", cmd->rsc_id, cmd->real_action, time_sum, timeout_left, services_ocf_exitcode_str(cmd->exec_rc), cmd->exec_rc); cmd_original_times(cmd); // Monitors may return "not running", but start/stop shouldn't if ((cmd->lrmd_op_status == PCMK_LRM_OP_DONE) && (cmd->exec_rc == PCMK_OCF_NOT_RUNNING)) { if (safe_str_eq(cmd->real_action, "start")) { cmd->exec_rc = PCMK_OCF_UNKNOWN_ERROR; } else if (safe_str_eq(cmd->real_action, "stop")) { cmd->exec_rc = PCMK_OCF_OK; } } } } } #endif #if SUPPORT_NAGIOS if (rsc && safe_str_eq(rsc->class, PCMK_RESOURCE_CLASS_NAGIOS)) { if (safe_str_eq(cmd->action, "monitor") && (cmd->interval_ms == 0) && cmd->exec_rc == PCMK_OCF_OK) { /* Successfully executed --version for the nagios plugin */ cmd->exec_rc = PCMK_OCF_NOT_RUNNING; } else if (safe_str_eq(cmd->action, "start") && cmd->exec_rc != PCMK_OCF_OK) { #ifdef PCMK__TIME_USE_CGT goagain = true; #endif } } #endif #ifdef PCMK__TIME_USE_CGT if (goagain) { int time_sum = time_diff_ms(NULL, &(cmd->t_first_run)); int timeout_left = cmd->timeout_orig - time_sum; int delay = cmd->timeout_orig / 10; if(delay >= timeout_left && timeout_left > 20) { delay = timeout_left/2; } delay = QB_MIN(2000, delay); if (delay < timeout_left) { cmd->start_delay = delay; cmd->timeout = timeout_left; if(cmd->exec_rc == PCMK_OCF_OK) { crm_debug("%s %s may still be in progress: re-scheduling (elapsed=%dms, remaining=%dms, start_delay=%dms)", cmd->rsc_id, cmd->real_action, time_sum, timeout_left, delay); } else if(cmd->exec_rc == PCMK_OCF_PENDING) { crm_info("%s %s is still in progress: re-scheduling (elapsed=%dms, remaining=%dms, start_delay=%dms)", cmd->rsc_id, cmd->action, time_sum, timeout_left, delay); } else { crm_notice("%s %s failed '%s' (%d): re-scheduling (elapsed=%dms, remaining=%dms, start_delay=%dms)", cmd->rsc_id, cmd->action, services_ocf_exitcode_str(cmd->exec_rc), cmd->exec_rc, time_sum, timeout_left, delay); } cmd_reset(cmd); if(rsc) { rsc->active = NULL; } schedule_lrmd_cmd(rsc, cmd); /* Don't finalize cmd, we're not done with it yet */ return; } else { crm_notice("Giving up on %s %s (rc=%d): timeout (elapsed=%dms, remaining=%dms)", cmd->rsc_id, cmd->real_action?cmd->real_action:cmd->action, cmd->exec_rc, time_sum, timeout_left); cmd->lrmd_op_status = PCMK_LRM_OP_TIMEOUT; cmd->exec_rc = PCMK_OCF_TIMEOUT; cmd_original_times(cmd); } } #endif if (action->stderr_data) { cmd->output = strdup(action->stderr_data); cmd->exit_reason = parse_exit_reason(action->stderr_data); } else if (action->stdout_data) { cmd->output = strdup(action->stdout_data); } cmd_finalize(cmd, rsc); } /*! * \internal * \brief Determine operation status of a stonith operation * * Non-stonith resource operations get their operation status directly from the * service library, but the fencer does not have an equivalent, so we must infer * an operation status from the fencer API's return code. * * \param[in] action Name of action performed on stonith resource * \param[in] interval_ms Action interval * \param[in] rc Action result from fencer * * \return Operation status corresponding to fencer API return code */ static int stonith_rc2status(const char *action, guint interval_ms, int rc) { int status = PCMK_LRM_OP_DONE; switch (rc) { case pcmk_ok: break; case -EOPNOTSUPP: case -EPROTONOSUPPORT: status = PCMK_LRM_OP_NOTSUPPORTED; break; case -ETIME: case -ETIMEDOUT: status = PCMK_LRM_OP_TIMEOUT; break; case -ENOTCONN: case -ECOMM: // Couldn't talk to fencer status = PCMK_LRM_OP_ERROR; break; case -ENODEV: // The device is not registered with the fencer status = PCMK_LRM_OP_ERROR; break; default: break; } return status; } static void stonith_action_complete(lrmd_cmd_t * cmd, int rc) { // This can be NULL if resource was removed before command completed lrmd_rsc_t *rsc = g_hash_table_lookup(rsc_list, cmd->rsc_id); cmd->exec_rc = stonith2uniform_rc(cmd->action, rc); /* This function may be called with status already set to cancelled, if a * pending action was aborted. Otherwise, we need to determine status from * the fencer return code. */ if (cmd->lrmd_op_status != PCMK_LRM_OP_CANCELLED) { cmd->lrmd_op_status = stonith_rc2status(cmd->action, cmd->interval_ms, rc); // Certain successful actions change the known state of the resource if (rsc && (cmd->exec_rc == PCMK_OCF_OK)) { if (safe_str_eq(cmd->action, "start")) { rsc->st_probe_rc = pcmk_ok; // maps to PCMK_OCF_OK } else if (safe_str_eq(cmd->action, "stop")) { rsc->st_probe_rc = -ENODEV; // maps to PCMK_OCF_NOT_RUNNING } } } /* The recurring timer should not be running at this point in any case, but * as a failsafe, stop it if it is. */ stop_recurring_timer(cmd); /* Reschedule this command if appropriate. If a recurring command is *not* * rescheduled, its status must be PCMK_LRM_OP_CANCELLED, otherwise it will * not be removed from recurring_ops by cmd_finalize(). */ if (rsc && (cmd->interval_ms > 0) && (cmd->lrmd_op_status != PCMK_LRM_OP_CANCELLED)) { start_recurring_timer(cmd); } cmd_finalize(cmd, rsc); } static void lrmd_stonith_callback(stonith_t * stonith, stonith_callback_data_t * data) { stonith_action_complete(data->userdata, data->rc); } void stonith_connection_failed(void) { GHashTableIter iter; GList *cmd_list = NULL; GList *cmd_iter = NULL; lrmd_rsc_t *rsc = NULL; char *key = NULL; g_hash_table_iter_init(&iter, rsc_list); while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & rsc)) { if (safe_str_eq(rsc->class, PCMK_RESOURCE_CLASS_STONITH)) { /* If we registered this fence device, we don't know whether the * fencer still has the registration or not. Cause future probes to * return PCMK_OCF_UNKNOWN_ERROR until the resource is stopped or * started successfully. This is especially important if the * controller also went away (possibly due to a cluster layer * restart) and won't receive our client notification of any * monitors finalized below. */ if (rsc->st_probe_rc == pcmk_ok) { rsc->st_probe_rc = pcmk_err_generic; } if (rsc->active) { cmd_list = g_list_append(cmd_list, rsc->active); } if (rsc->recurring_ops) { cmd_list = g_list_concat(cmd_list, rsc->recurring_ops); } if (rsc->pending_ops) { cmd_list = g_list_concat(cmd_list, rsc->pending_ops); } rsc->pending_ops = rsc->recurring_ops = NULL; } } if (!cmd_list) { return; } crm_err("Connection to fencer failed, finalizing %d pending operations", g_list_length(cmd_list)); for (cmd_iter = cmd_list; cmd_iter; cmd_iter = cmd_iter->next) { stonith_action_complete(cmd_iter->data, -ENOTCONN); } g_list_free(cmd_list); } /*! * \internal * \brief Execute a stonith resource "start" action * * Start a stonith resource by registering it with the fencer. * (Stonith agents don't have a start command.) * * \param[in] stonith_api Connection to fencer * \param[in] rsc Stonith resource to start * \param[in] cmd Start command to execute * * \return pcmk_ok on success, -errno otherwise */ static int execd_stonith_start(stonith_t *stonith_api, lrmd_rsc_t *rsc, lrmd_cmd_t *cmd) { char *key = NULL; char *value = NULL; stonith_key_value_t *device_params = NULL; int rc = pcmk_ok; // Convert command parameters to stonith API key/values if (cmd->params) { GHashTableIter iter; g_hash_table_iter_init(&iter, cmd->params); while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) { device_params = stonith_key_value_add(device_params, key, value); } } /* The fencer will automatically register devices via CIB notifications * when the CIB changes, but to avoid a possible race condition between * the fencer receiving the notification and the executor requesting that * resource, the executor registers the device as well. The fencer knows how * to handle duplicate registrations. */ rc = stonith_api->cmds->register_device(stonith_api, st_opt_sync_call, cmd->rsc_id, rsc->provider, rsc->type, device_params); stonith_key_value_freeall(device_params, 1, 1); return rc; } /*! * \internal * \brief Execute a stonith resource "stop" action * * Stop a stonith resource by unregistering it with the fencer. * (Stonith agents don't have a stop command.) * * \param[in] stonith_api Connection to fencer * \param[in] rsc Stonith resource to stop * * \return pcmk_ok on success, -errno otherwise */ static inline int execd_stonith_stop(stonith_t *stonith_api, const lrmd_rsc_t *rsc) { /* @TODO Failure would indicate a problem communicating with fencer; * perhaps we should try reconnecting and retrying a few times? */ return stonith_api->cmds->remove_device(stonith_api, st_opt_sync_call, rsc->rsc_id); } /*! * \internal * \brief Initiate a stonith resource agent recurring "monitor" action * * \param[in] stonith_api Connection to fencer * \param[in] rsc Stonith resource to monitor * \param[in] cmd Monitor command being executed * * \return pcmk_ok if monitor was successfully initiated, -errno otherwise */ static inline int execd_stonith_monitor(stonith_t *stonith_api, lrmd_rsc_t *rsc, lrmd_cmd_t *cmd) { int rc = stonith_api->cmds->monitor(stonith_api, 0, cmd->rsc_id, cmd->timeout / 1000); rc = stonith_api->cmds->register_callback(stonith_api, rc, 0, 0, cmd, "lrmd_stonith_callback", lrmd_stonith_callback); if (rc == TRUE) { rsc->active = cmd; rc = pcmk_ok; } else { rc = -pcmk_err_generic; } return rc; } static void lrmd_rsc_execute_stonith(lrmd_rsc_t * rsc, lrmd_cmd_t * cmd) { int rc = 0; bool do_monitor = FALSE; stonith_t *stonith_api = get_stonith_connection(); if (!stonith_api) { rc = -ENOTCONN; } else if (safe_str_eq(cmd->action, "start")) { rc = execd_stonith_start(stonith_api, rsc, cmd); if (rc == 0) { do_monitor = TRUE; } } else if (safe_str_eq(cmd->action, "stop")) { rc = execd_stonith_stop(stonith_api, rsc); } else if (safe_str_eq(cmd->action, "monitor")) { if (cmd->interval_ms > 0) { do_monitor = TRUE; } else { rc = rsc->st_probe_rc; } } if (do_monitor) { rc = execd_stonith_monitor(stonith_api, rsc, cmd); if (rc == pcmk_ok) { // Don't clean up yet, we will find out result of the monitor later return; } } stonith_action_complete(cmd, rc); } static int lrmd_rsc_execute_service_lib(lrmd_rsc_t * rsc, lrmd_cmd_t * cmd) { svc_action_t *action = NULL; GHashTable *params_copy = NULL; CRM_ASSERT(rsc); CRM_ASSERT(cmd); crm_trace("Creating action, resource:%s action:%s class:%s provider:%s agent:%s", rsc->rsc_id, cmd->action, rsc->class, rsc->provider, rsc->type); #if SUPPORT_NAGIOS /* Recurring operations are cancelled anyway for a stop operation */ if (safe_str_eq(rsc->class, PCMK_RESOURCE_CLASS_NAGIOS) && safe_str_eq(cmd->action, "stop")) { cmd->exec_rc = PCMK_OCF_OK; goto exec_done; } #endif params_copy = crm_str_table_dup(cmd->params); action = resources_action_create(rsc->rsc_id, rsc->class, rsc->provider, rsc->type, normalize_action_name(rsc, cmd->action), cmd->interval_ms, cmd->timeout, params_copy, cmd->service_flags); if (!action) { crm_err("Failed to create action, action:%s on resource %s", cmd->action, rsc->rsc_id); cmd->lrmd_op_status = PCMK_LRM_OP_ERROR; goto exec_done; } action->cb_data = cmd; /* 'cmd' may not be valid after this point if * services_action_async() returned TRUE * * Upstart and systemd both synchronously determine monitor/status * results and call action_complete (which may free 'cmd') if necessary. */ if (services_action_async(action, action_complete)) { return TRUE; } cmd->exec_rc = action->rc; if(action->status != PCMK_LRM_OP_DONE) { cmd->lrmd_op_status = action->status; } else { cmd->lrmd_op_status = PCMK_LRM_OP_ERROR; } services_action_free(action); action = NULL; exec_done: cmd_finalize(cmd, rsc); return TRUE; } static gboolean lrmd_rsc_execute(lrmd_rsc_t * rsc) { lrmd_cmd_t *cmd = NULL; CRM_CHECK(rsc != NULL, return FALSE); if (rsc->active) { crm_trace("%s is still active", rsc->rsc_id); return TRUE; } if (rsc->pending_ops) { GList *first = rsc->pending_ops; cmd = first->data; if (cmd->delay_id) { crm_trace ("Command %s %s was asked to run too early, waiting for start_delay timeout of %dms", cmd->rsc_id, cmd->action, cmd->start_delay); return TRUE; } rsc->pending_ops = g_list_remove_link(rsc->pending_ops, first); g_list_free_1(first); #ifdef PCMK__TIME_USE_CGT if (cmd->t_first_run.tv_sec == 0) { clock_gettime(CLOCK_MONOTONIC, &cmd->t_first_run); } clock_gettime(CLOCK_MONOTONIC, &cmd->t_run); #endif } if (!cmd) { crm_trace("Nothing further to do for %s", rsc->rsc_id); return TRUE; } rsc->active = cmd; /* only one op at a time for a rsc */ if (cmd->interval_ms) { rsc->recurring_ops = g_list_append(rsc->recurring_ops, cmd); } log_execute(cmd); if (safe_str_eq(rsc->class, PCMK_RESOURCE_CLASS_STONITH)) { lrmd_rsc_execute_stonith(rsc, cmd); } else { lrmd_rsc_execute_service_lib(rsc, cmd); } return TRUE; } static gboolean lrmd_rsc_dispatch(gpointer user_data) { return lrmd_rsc_execute(user_data); } void free_rsc(gpointer data) { GListPtr gIter = NULL; lrmd_rsc_t *rsc = data; int is_stonith = safe_str_eq(rsc->class, PCMK_RESOURCE_CLASS_STONITH); gIter = rsc->pending_ops; while (gIter != NULL) { GListPtr next = gIter->next; lrmd_cmd_t *cmd = gIter->data; /* command was never executed */ cmd->lrmd_op_status = PCMK_LRM_OP_CANCELLED; cmd_finalize(cmd, NULL); gIter = next; } /* frees list, but not list elements. */ g_list_free(rsc->pending_ops); gIter = rsc->recurring_ops; while (gIter != NULL) { GListPtr next = gIter->next; lrmd_cmd_t *cmd = gIter->data; if (is_stonith) { cmd->lrmd_op_status = PCMK_LRM_OP_CANCELLED; /* If a stonith command is in-flight, just mark it as cancelled; * it is not safe to finalize/free the cmd until the stonith api * says it has either completed or timed out. */ if (rsc->active != cmd) { cmd_finalize(cmd, NULL); } } else { /* This command is already handed off to service library, * let service library cancel it and tell us via the callback * when it is cancelled. The rsc can be safely destroyed * even if we are waiting for the cancel result */ services_action_cancel(rsc->rsc_id, normalize_action_name(rsc, cmd->action), cmd->interval_ms); } gIter = next; } /* frees list, but not list elements. */ g_list_free(rsc->recurring_ops); free(rsc->rsc_id); free(rsc->class); free(rsc->provider); free(rsc->type); mainloop_destroy_trigger(rsc->work); free(rsc); } static xmlNode * process_lrmd_signon(pcmk__client_t *client, xmlNode *request, int call_id) { xmlNode *reply = NULL; int rc = pcmk_ok; const char *is_ipc_provider = crm_element_value(request, F_LRMD_IS_IPC_PROVIDER); const char *protocol_version = crm_element_value(request, F_LRMD_PROTOCOL_VERSION); if (compare_version(protocol_version, LRMD_MIN_PROTOCOL_VERSION) < 0) { crm_err("Cluster API version must be greater than or equal to %s, not %s", LRMD_MIN_PROTOCOL_VERSION, protocol_version); rc = -EPROTO; } reply = create_lrmd_reply(__FUNCTION__, rc, call_id); crm_xml_add(reply, F_LRMD_OPERATION, CRM_OP_REGISTER); crm_xml_add(reply, F_LRMD_CLIENTID, client->id); crm_xml_add(reply, F_LRMD_PROTOCOL_VERSION, LRMD_PROTOCOL_VERSION); if (crm_is_true(is_ipc_provider)) { // This is a remote connection from a cluster node's controller #ifdef SUPPORT_REMOTE ipc_proxy_add_provider(client); #endif } return reply; } static int process_lrmd_rsc_register(pcmk__client_t *client, uint32_t id, xmlNode *request) { int rc = pcmk_ok; lrmd_rsc_t *rsc = build_rsc_from_xml(request); lrmd_rsc_t *dup = g_hash_table_lookup(rsc_list, rsc->rsc_id); if (dup && safe_str_eq(rsc->class, dup->class) && safe_str_eq(rsc->provider, dup->provider) && safe_str_eq(rsc->type, dup->type)) { crm_notice("Ignoring duplicate registration of '%s'", rsc->rsc_id); free_rsc(rsc); return rc; } g_hash_table_replace(rsc_list, rsc->rsc_id, rsc); crm_info("Cached agent information for '%s'", rsc->rsc_id); return rc; } static xmlNode * process_lrmd_get_rsc_info(xmlNode *request, int call_id) { int rc = pcmk_ok; xmlNode *rsc_xml = get_xpath_object("//" F_LRMD_RSC, request, LOG_ERR); const char *rsc_id = crm_element_value(rsc_xml, F_LRMD_RSC_ID); xmlNode *reply = NULL; lrmd_rsc_t *rsc = NULL; if (rsc_id == NULL) { rc = -ENODEV; } else { rsc = g_hash_table_lookup(rsc_list, rsc_id); if (rsc == NULL) { crm_info("Agent information for '%s' not in cache", rsc_id); rc = -ENODEV; } } reply = create_lrmd_reply(__FUNCTION__, rc, call_id); if (rsc) { crm_xml_add(reply, F_LRMD_RSC_ID, rsc->rsc_id); crm_xml_add(reply, F_LRMD_CLASS, rsc->class); crm_xml_add(reply, F_LRMD_PROVIDER, rsc->provider); crm_xml_add(reply, F_LRMD_TYPE, rsc->type); } return reply; } static int process_lrmd_rsc_unregister(pcmk__client_t *client, uint32_t id, xmlNode *request) { int rc = pcmk_ok; lrmd_rsc_t *rsc = NULL; xmlNode *rsc_xml = get_xpath_object("//" F_LRMD_RSC, request, LOG_ERR); const char *rsc_id = crm_element_value(rsc_xml, F_LRMD_RSC_ID); if (!rsc_id) { return -ENODEV; } rsc = g_hash_table_lookup(rsc_list, rsc_id); if (rsc == NULL) { crm_info("Ignoring unregistration of resource '%s', which is not registered", rsc_id); return pcmk_ok; } if (rsc->active) { /* let the caller know there are still active ops on this rsc to watch for */ crm_trace("Operation (0x%p) still in progress for unregistered resource %s", rsc->active, rsc_id); rc = -EINPROGRESS; } g_hash_table_remove(rsc_list, rsc_id); return rc; } static int process_lrmd_rsc_exec(pcmk__client_t *client, uint32_t id, xmlNode *request) { lrmd_rsc_t *rsc = NULL; lrmd_cmd_t *cmd = NULL; xmlNode *rsc_xml = get_xpath_object("//" F_LRMD_RSC, request, LOG_ERR); const char *rsc_id = crm_element_value(rsc_xml, F_LRMD_RSC_ID); int call_id; if (!rsc_id) { return -EINVAL; } if (!(rsc = g_hash_table_lookup(rsc_list, rsc_id))) { crm_info("Resource '%s' not found (%d active resources)", rsc_id, g_hash_table_size(rsc_list)); return -ENODEV; } cmd = create_lrmd_cmd(request, client); call_id = cmd->call_id; /* Don't reference cmd after handing it off to be scheduled. * The cmd could get merged and freed. */ schedule_lrmd_cmd(rsc, cmd); return call_id; } static int cancel_op(const char *rsc_id, const char *action, guint interval_ms) { GListPtr gIter = NULL; lrmd_rsc_t *rsc = g_hash_table_lookup(rsc_list, rsc_id); /* How to cancel an action. * 1. Check pending ops list, if it hasn't been handed off * to the service library or stonith recurring list remove * it there and that will stop it. * 2. If it isn't in the pending ops list, then it's either a * recurring op in the stonith recurring list, or the service * library's recurring list. Stop it there * 3. If not found in any lists, then this operation has either * been executed already and is not a recurring operation, or * never existed. */ if (!rsc) { return -ENODEV; } for (gIter = rsc->pending_ops; gIter != NULL; gIter = gIter->next) { lrmd_cmd_t *cmd = gIter->data; if (safe_str_eq(cmd->action, action) && (cmd->interval_ms == interval_ms)) { cmd->lrmd_op_status = PCMK_LRM_OP_CANCELLED; cmd_finalize(cmd, rsc); return pcmk_ok; } } if (safe_str_eq(rsc->class, PCMK_RESOURCE_CLASS_STONITH)) { /* The service library does not handle stonith operations. * We have to handle recurring stonith operations ourselves. */ for (gIter = rsc->recurring_ops; gIter != NULL; gIter = gIter->next) { lrmd_cmd_t *cmd = gIter->data; if (safe_str_eq(cmd->action, action) && (cmd->interval_ms == interval_ms)) { cmd->lrmd_op_status = PCMK_LRM_OP_CANCELLED; if (rsc->active != cmd) { cmd_finalize(cmd, rsc); } return pcmk_ok; } } } else if (services_action_cancel(rsc_id, normalize_action_name(rsc, action), interval_ms) == TRUE) { /* The service library will tell the action_complete callback function * this action was cancelled, which will destroy the cmd and remove * it from the recurring_op list. Do not do that in this function * if the service library says it cancelled it. */ return pcmk_ok; } return -EOPNOTSUPP; } static void cancel_all_recurring(lrmd_rsc_t * rsc, const char *client_id) { GList *cmd_list = NULL; GList *cmd_iter = NULL; /* Notice a copy of each list is created when concat is called. * This prevents odd behavior from occurring when the cmd_list * is iterated through later on. It is possible the cancel_op * function may end up modifying the recurring_ops and pending_ops * lists. If we did not copy those lists, our cmd_list iteration * could get messed up.*/ if (rsc->recurring_ops) { cmd_list = g_list_concat(cmd_list, g_list_copy(rsc->recurring_ops)); } if (rsc->pending_ops) { cmd_list = g_list_concat(cmd_list, g_list_copy(rsc->pending_ops)); } if (!cmd_list) { return; } for (cmd_iter = cmd_list; cmd_iter; cmd_iter = cmd_iter->next) { lrmd_cmd_t *cmd = cmd_iter->data; if (cmd->interval_ms == 0) { continue; } if (client_id && safe_str_neq(cmd->client_id, client_id)) { continue; } cancel_op(rsc->rsc_id, cmd->action, cmd->interval_ms); } /* frees only the copied list data, not the cmds */ g_list_free(cmd_list); } static int process_lrmd_rsc_cancel(pcmk__client_t *client, uint32_t id, xmlNode *request) { xmlNode *rsc_xml = get_xpath_object("//" F_LRMD_RSC, request, LOG_ERR); const char *rsc_id = crm_element_value(rsc_xml, F_LRMD_RSC_ID); const char *action = crm_element_value(rsc_xml, F_LRMD_RSC_ACTION); guint interval_ms = 0; crm_element_value_ms(rsc_xml, F_LRMD_RSC_INTERVAL, &interval_ms); if (!rsc_id || !action) { return -EINVAL; } return cancel_op(rsc_id, action, interval_ms); } static void add_recurring_op_xml(xmlNode *reply, lrmd_rsc_t *rsc) { xmlNode *rsc_xml = create_xml_node(reply, F_LRMD_RSC); crm_xml_add(rsc_xml, F_LRMD_RSC_ID, rsc->rsc_id); for (GList *item = rsc->recurring_ops; item != NULL; item = item->next) { lrmd_cmd_t *cmd = item->data; xmlNode *op_xml = create_xml_node(rsc_xml, T_LRMD_RSC_OP); crm_xml_add(op_xml, F_LRMD_RSC_ACTION, (cmd->real_action? cmd->real_action : cmd->action)); crm_xml_add_ms(op_xml, F_LRMD_RSC_INTERVAL, cmd->interval_ms); crm_xml_add_int(op_xml, F_LRMD_TIMEOUT, cmd->timeout_orig); } } static xmlNode * process_lrmd_get_recurring(xmlNode *request, int call_id) { int rc = pcmk_ok; const char *rsc_id = NULL; lrmd_rsc_t *rsc = NULL; xmlNode *reply = NULL; xmlNode *rsc_xml = NULL; // Resource ID is optional rsc_xml = first_named_child(request, F_LRMD_CALLDATA); if (rsc_xml) { rsc_xml = first_named_child(rsc_xml, F_LRMD_RSC); } if (rsc_xml) { rsc_id = crm_element_value(rsc_xml, F_LRMD_RSC_ID); } // If resource ID is specified, resource must exist if (rsc_id != NULL) { rsc = g_hash_table_lookup(rsc_list, rsc_id); if (rsc == NULL) { crm_info("Resource '%s' not found (%d active resources)", rsc_id, g_hash_table_size(rsc_list)); rc = -ENODEV; } } reply = create_lrmd_reply(__FUNCTION__, rc, call_id); // If resource ID is not specified, check all resources if (rsc_id == NULL) { GHashTableIter iter; char *key = NULL; g_hash_table_iter_init(&iter, rsc_list); while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &rsc)) { add_recurring_op_xml(reply, rsc); } } else if (rsc) { add_recurring_op_xml(reply, rsc); } return reply; } void process_lrmd_message(pcmk__client_t *client, uint32_t id, xmlNode *request) { int rc = pcmk_ok; int call_id = 0; const char *op = crm_element_value(request, F_LRMD_OPERATION); int do_reply = 0; int do_notify = 0; xmlNode *reply = NULL; crm_trace("Processing %s operation from %s", op, client->id); crm_element_value_int(request, F_LRMD_CALLID, &call_id); if (crm_str_eq(op, CRM_OP_IPC_FWD, TRUE)) { #ifdef SUPPORT_REMOTE ipc_proxy_forward_client(client, request); #endif do_reply = 1; } else if (crm_str_eq(op, CRM_OP_REGISTER, TRUE)) { reply = process_lrmd_signon(client, request, call_id); do_reply = 1; } else if (crm_str_eq(op, LRMD_OP_RSC_REG, TRUE)) { rc = process_lrmd_rsc_register(client, id, request); do_notify = 1; do_reply = 1; } else if (crm_str_eq(op, LRMD_OP_RSC_INFO, TRUE)) { reply = process_lrmd_get_rsc_info(request, call_id); do_reply = 1; } else if (crm_str_eq(op, LRMD_OP_RSC_UNREG, TRUE)) { rc = process_lrmd_rsc_unregister(client, id, request); /* don't notify anyone about failed un-registers */ if (rc == pcmk_ok || rc == -EINPROGRESS) { do_notify = 1; } do_reply = 1; } else if (crm_str_eq(op, LRMD_OP_RSC_EXEC, TRUE)) { rc = process_lrmd_rsc_exec(client, id, request); do_reply = 1; } else if (crm_str_eq(op, LRMD_OP_RSC_CANCEL, TRUE)) { rc = process_lrmd_rsc_cancel(client, id, request); do_reply = 1; } else if (crm_str_eq(op, LRMD_OP_POKE, TRUE)) { do_notify = 1; do_reply = 1; } else if (crm_str_eq(op, LRMD_OP_CHECK, TRUE)) { xmlNode *data = get_message_xml(request, F_LRMD_CALLDATA); const char *timeout = crm_element_value(data, F_LRMD_WATCHDOG); CRM_LOG_ASSERT(data != NULL); - check_sbd_timeout(timeout); + pcmk__valid_sbd_timeout(timeout); } else if (crm_str_eq(op, LRMD_OP_ALERT_EXEC, TRUE)) { rc = process_lrmd_alert_exec(client, id, request); do_reply = 1; } else if (crm_str_eq(op, LRMD_OP_GET_RECURRING, TRUE)) { reply = process_lrmd_get_recurring(request, call_id); do_reply = 1; } else { rc = -EOPNOTSUPP; do_reply = 1; crm_err("Unknown IPC request '%s' from %s", op, client->name); } crm_debug("Processed %s operation from %s: rc=%d, reply=%d, notify=%d", op, client->id, rc, do_reply, do_notify); if (do_reply) { int send_rc = pcmk_rc_ok; if (reply == NULL) { reply = create_lrmd_reply(__FUNCTION__, rc, call_id); } send_rc = lrmd_server_send_reply(client, id, reply); free_xml(reply); if (send_rc != pcmk_rc_ok) { crm_warn("Reply to client %s failed: %s " CRM_XS " %d", client->name, pcmk_rc_str(send_rc), send_rc); } } if (do_notify) { send_generic_notify(rc, request); } } diff --git a/daemons/execd/pacemaker-execd.c b/daemons/execd/pacemaker-execd.c index e74777a66d..df27d1a328 100644 --- a/daemons/execd/pacemaker-execd.c +++ b/daemons/execd/pacemaker-execd.c @@ -1,508 +1,518 @@ /* * Copyright 2012-2020 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 "pacemaker-execd.h" #if defined(HAVE_GNUTLS_GNUTLS_H) && defined(SUPPORT_REMOTE) # define ENABLE_PCMK_REMOTE #endif static GMainLoop *mainloop = NULL; static qb_ipcs_service_t *ipcs = NULL; static stonith_t *stonith_api = NULL; int lrmd_call_id = 0; #ifdef ENABLE_PCMK_REMOTE /* whether shutdown request has been sent */ static sig_atomic_t shutting_down = FALSE; /* timer for waiting for acknowledgment of shutdown request */ static guint shutdown_ack_timer = 0; static gboolean lrmd_exit(gpointer data); #endif static void stonith_connection_destroy_cb(stonith_t * st, stonith_event_t * e) { stonith_api->state = stonith_disconnected; crm_err("Connection to fencer lost"); stonith_connection_failed(); } stonith_t * get_stonith_connection(void) { if (stonith_api && stonith_api->state == stonith_disconnected) { stonith_api_delete(stonith_api); stonith_api = NULL; } if (stonith_api == NULL) { int rc = pcmk_ok; stonith_api = stonith_api_new(); if (stonith_api == NULL) { crm_err("Could not connect to fencer: API memory allocation failed"); return NULL; } rc = stonith_api_connect_retry(stonith_api, crm_system_name, 10); if (rc != pcmk_ok) { crm_err("Could not connect to fencer in 10 attempts: %s " CRM_XS " rc=%d", pcmk_strerror(rc), rc); stonith_api_delete(stonith_api); stonith_api = NULL; } else { stonith_api->cmds->register_notification(stonith_api, T_STONITH_NOTIFY_DISCONNECT, stonith_connection_destroy_cb); } } return stonith_api; } static int32_t lrmd_ipc_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) { crm_trace("Connection %p", c); if (pcmk__new_client(c, uid, gid) == NULL) { return -EIO; } return 0; } static void lrmd_ipc_created(qb_ipcs_connection_t * c) { pcmk__client_t *new_client = pcmk__find_client(c); crm_trace("Connection %p", c); CRM_ASSERT(new_client != NULL); /* Now that the connection is offically established, alert * the other clients a new connection exists. */ notify_of_new_client(new_client); } static int32_t lrmd_ipc_dispatch(qb_ipcs_connection_t * c, void *data, size_t size) { uint32_t id = 0; uint32_t flags = 0; pcmk__client_t *client = pcmk__find_client(c); xmlNode *request = pcmk__client_data2xml(client, data, &id, &flags); CRM_CHECK(client != NULL, crm_err("Invalid client"); return FALSE); CRM_CHECK(client->id != NULL, crm_err("Invalid client: %p", client); return FALSE); CRM_CHECK(flags & crm_ipc_client_response, crm_err("Invalid client request: %p", client); return FALSE); if (!request) { return 0; } if (!client->name) { const char *value = crm_element_value(request, F_LRMD_CLIENTNAME); if (value == NULL) { client->name = crm_itoa(pcmk__client_pid(c)); } else { client->name = strdup(value); } } lrmd_call_id++; if (lrmd_call_id < 1) { lrmd_call_id = 1; } crm_xml_add(request, F_LRMD_CLIENTID, client->id); crm_xml_add(request, F_LRMD_CLIENTNAME, client->name); crm_xml_add_int(request, F_LRMD_CALLID, lrmd_call_id); process_lrmd_message(client, id, request); free_xml(request); return 0; } /*! * \internal * \brief Free a client connection, and exit if appropriate * * \param[in] client Client connection to free */ void lrmd_client_destroy(pcmk__client_t *client) { pcmk__free_client(client); #ifdef ENABLE_PCMK_REMOTE /* If we were waiting to shut down, we can now safely do so * if there are no more proxied IPC providers */ if (shutting_down && (ipc_proxy_get_provider() == NULL)) { lrmd_exit(NULL); } #endif } static int32_t lrmd_ipc_closed(qb_ipcs_connection_t * c) { pcmk__client_t *client = pcmk__find_client(c); if (client == NULL) { return 0; } crm_trace("Connection %p", c); client_disconnect_cleanup(client->id); #ifdef ENABLE_PCMK_REMOTE ipc_proxy_remove_provider(client); #endif lrmd_client_destroy(client); return 0; } static void lrmd_ipc_destroy(qb_ipcs_connection_t * c) { lrmd_ipc_closed(c); crm_trace("Connection %p", c); } static struct qb_ipcs_service_handlers lrmd_ipc_callbacks = { .connection_accept = lrmd_ipc_accept, .connection_created = lrmd_ipc_created, .msg_process = lrmd_ipc_dispatch, .connection_closed = lrmd_ipc_closed, .connection_destroyed = lrmd_ipc_destroy }; // \return Standard Pacemaker return code int lrmd_server_send_reply(pcmk__client_t *client, uint32_t id, xmlNode *reply) { crm_trace("Sending reply (%d) to client (%s)", id, client->id); switch (client->kind) { case PCMK__CLIENT_IPC: return pcmk__ipc_send_xml(client, id, reply, FALSE); #ifdef ENABLE_PCMK_REMOTE case PCMK__CLIENT_TLS: return lrmd_tls_send_msg(client->remote, reply, id, "reply"); #endif default: crm_err("Could not send reply: unknown client type %d", client->kind); } return ENOTCONN; } // \return Standard Pacemaker return code int lrmd_server_send_notify(pcmk__client_t *client, xmlNode *msg) { crm_trace("Sending notification to client (%s)", client->id); switch (client->kind) { case PCMK__CLIENT_IPC: if (client->ipcs == NULL) { crm_trace("Could not notify local client: disconnected"); return ENOTCONN; } return pcmk__ipc_send_xml(client, 0, msg, crm_ipc_server_event); #ifdef ENABLE_PCMK_REMOTE case PCMK__CLIENT_TLS: if (client->remote == NULL) { crm_trace("Could not notify remote client: disconnected"); return ENOTCONN; } else { return lrmd_tls_send_msg(client->remote, msg, 0, "notify"); } #endif default: crm_err("Could not notify client: unknown type %d", client->kind); } return ENOTCONN; } /*! * \internal * \brief Clean up and exit immediately * * \param[in] data Ignored * * \return Doesn't return * \note This can be used as a timer callback. */ static gboolean lrmd_exit(gpointer data) { crm_info("Terminating with %d clients", pcmk__ipc_client_count()); if (stonith_api) { stonith_api->cmds->remove_notification(stonith_api, T_STONITH_NOTIFY_DISCONNECT); stonith_api->cmds->disconnect(stonith_api); stonith_api_delete(stonith_api); } if (ipcs) { mainloop_del_ipc_server(ipcs); } #ifdef ENABLE_PCMK_REMOTE lrmd_tls_server_destroy(); ipc_proxy_cleanup(); #endif pcmk__client_cleanup(); g_hash_table_destroy(rsc_list); if (mainloop) { lrmd_drain_alerts(mainloop); } crm_exit(CRM_EX_OK); return FALSE; } /*! * \internal * \brief Request cluster shutdown if appropriate, otherwise exit immediately * * \param[in] nsig Signal that caused invocation (ignored) */ static void lrmd_shutdown(int nsig) { #ifdef ENABLE_PCMK_REMOTE pcmk__client_t *ipc_proxy = ipc_proxy_get_provider(); /* If there are active proxied IPC providers, then we may be running * resources, so notify the cluster that we wish to shut down. */ if (ipc_proxy) { if (shutting_down) { crm_notice("Waiting for cluster to stop resources before exiting"); return; } crm_info("Sending shutdown request to cluster"); if (ipc_proxy_shutdown_req(ipc_proxy) < 0) { crm_crit("Shutdown request failed, exiting immediately"); } else { /* We requested a shutdown. Now, we need to wait for an * acknowledgement from the proxy host (which ensures the proxy host * supports shutdown requests), then wait for all proxy hosts to * disconnect (which ensures that all resources have been stopped). */ shutting_down = TRUE; /* Stop accepting new proxy connections */ lrmd_tls_server_destroy(); /* Older controller versions will never acknowledge our request, so * set a fairly short timeout to exit quickly in that case. If we * get the ack, we'll defuse this timer. */ shutdown_ack_timer = g_timeout_add_seconds(20, lrmd_exit, NULL); /* Currently, we let the OS kill us if the clients don't disconnect * in a reasonable time. We could instead set a long timer here * (shorter than what the OS is likely to use) and exit immediately * if it pops. */ return; } } #endif lrmd_exit(NULL); } /*! * \internal * \brief Defuse short exit timer if shutting down */ void handle_shutdown_ack() { #ifdef ENABLE_PCMK_REMOTE if (shutting_down) { crm_info("Received shutdown ack"); if (shutdown_ack_timer > 0) { g_source_remove(shutdown_ack_timer); shutdown_ack_timer = 0; } return; } #endif crm_debug("Ignoring unexpected shutdown ack"); } /*! * \internal * \brief Make short exit timer fire immediately */ void handle_shutdown_nack() { #ifdef ENABLE_PCMK_REMOTE if (shutting_down) { crm_info("Received shutdown nack"); if (shutdown_ack_timer > 0) { g_source_remove(shutdown_ack_timer); shutdown_ack_timer = g_timeout_add(0, lrmd_exit, NULL); } return; } #endif crm_debug("Ignoring unexpected shutdown nack"); } -/* *INDENT-OFF* */ -static struct crm_option long_options[] = { - /* Top-level Options */ - {"help", 0, 0, '?', "\tThis text"}, - {"version", 0, 0, '$', "\tVersion information" }, - {"verbose", 0, 0, 'V', "\tIncrease debug output"}, - - {"logfile", 1, 0, 'l', "\tSend logs to the additional named logfile"}, +static pcmk__cli_option_t long_options[] = { + // long option, argument type, storage, short option, description, flags + { + "help", no_argument, NULL, '?', + "\tThis text", pcmk__option_default + }, + { + "version", no_argument, NULL, '$', + "\tVersion information", pcmk__option_default + }, + { + "verbose", no_argument, NULL, 'V', + "\tIncrease debug output", pcmk__option_default + }, + { + "logfile", required_argument, NULL, 'l', + "\tSend logs to the additional named logfile", pcmk__option_default + }, #ifdef ENABLE_PCMK_REMOTE - {"port", 1, 0, 'p', "\tPort to listen on"}, + { + "port", required_argument, NULL, 'p', + "\tPort to listen on", pcmk__option_default + }, #endif - - {0, 0, 0, 0} + { 0, 0, 0, 0 } }; -/* *INDENT-ON* */ #ifdef ENABLE_PCMK_REMOTE # define EXECD_TYPE "remote" +# define EXECD_NAME "pacemaker-remoted" +# define EXECD_DESC "resource agent executor daemon for Pacemaker Remote nodes" #else # define EXECD_TYPE "local" +# define EXECD_NAME "pacemaker-execd" +# define EXECD_DESC "resource agent executor daemon for Pacemaker cluster nodes" #endif int main(int argc, char **argv, char **envp) { int flag = 0; int index = 0; int bump_log_num = 0; const char *option = NULL; -#ifndef ENABLE_PCMK_REMOTE - crm_log_preinit("pacemaker-execd", argc, argv); - crm_set_options(NULL, "[options]", long_options, - "Resource agent executor daemon for cluster nodes"); -#else +#ifdef ENABLE_PCMK_REMOTE // If necessary, create PID 1 now before any file descriptors are opened remoted_spawn_pidone(argc, argv, envp); - - crm_log_preinit("pacemaker-remoted", argc, argv); - crm_set_options(NULL, "[options]", long_options, - "Resource agent executor daemon for Pacemaker Remote nodes"); #endif + crm_log_preinit(EXECD_NAME, argc, argv); + pcmk__set_cli_options(NULL, "[options]", long_options, EXECD_DESC); + while (1) { - flag = crm_get_option(argc, argv, &index); + flag = pcmk__next_cli_option(argc, argv, &index, NULL); if (flag == -1) { break; } switch (flag) { case 'l': crm_add_logfile(optarg); break; case 'p': setenv("PCMK_remote_port", optarg, 1); break; case 'V': bump_log_num++; break; case '?': case '$': - crm_help(flag, CRM_EX_OK); + pcmk__cli_help(flag, CRM_EX_OK); break; default: - crm_help('?', CRM_EX_USAGE); + pcmk__cli_help('?', CRM_EX_USAGE); break; } } crm_log_init(NULL, LOG_INFO, TRUE, FALSE, argc, argv, FALSE); while (bump_log_num > 0) { crm_bump_log_level(argc, argv); bump_log_num--; } option = pcmk__env_option("logfacility"); if (option && safe_str_neq(option, "none") && safe_str_neq(option, "/dev/null")) { setenv("HA_LOGFACILITY", option, 1); /* Used by the ocf_log/ha_log OCF macro */ } option = pcmk__env_option("logfile"); if(option && safe_str_neq(option, "none")) { setenv("HA_LOGFILE", option, 1); /* Used by the ocf_log/ha_log OCF macro */ if (pcmk__env_option_enabled(crm_system_name, "debug")) { setenv("HA_DEBUGLOG", option, 1); /* Used by the ocf_log/ha_debug OCF macro */ } } crm_notice("Starting Pacemaker " EXECD_TYPE " executor"); /* The presence of this variable allegedly controls whether child * processes like httpd will try and use Systemd's sd_notify * API */ unsetenv("NOTIFY_SOCKET"); /* Used by RAs - Leave owned by root */ crm_build_path(CRM_RSCTMP_DIR, 0755); rsc_list = g_hash_table_new_full(crm_str_hash, g_str_equal, NULL, free_rsc); ipcs = mainloop_add_ipc_server(CRM_SYSTEM_LRMD, QB_IPC_SHM, &lrmd_ipc_callbacks); if (ipcs == NULL) { crm_err("Failed to create IPC server: shutting down and inhibiting respawn"); crm_exit(CRM_EX_FATAL); } #ifdef ENABLE_PCMK_REMOTE if (lrmd_init_remote_tls_server() < 0) { crm_err("Failed to create TLS listener: shutting down and staying down"); crm_exit(CRM_EX_FATAL); } ipc_proxy_init(); #endif mainloop_add_signal(SIGTERM, lrmd_shutdown); mainloop = g_main_loop_new(NULL, FALSE); crm_notice("Pacemaker " EXECD_TYPE " executor successfully started and accepting connections"); g_main_loop_run(mainloop); /* should never get here */ lrmd_exit(NULL); return CRM_EX_OK; } diff --git a/daemons/fenced/cts-fence-helper.c b/daemons/fenced/cts-fence-helper.c index 8fd27c80c5..af8191f854 100644 --- a/daemons/fenced/cts-fence-helper.c +++ b/daemons/fenced/cts-fence-helper.c @@ -1,657 +1,673 @@ /* - * Copyright 2009-2019 the Pacemaker project contributors + * Copyright 2009-2020 the Pacemaker project contributors * * 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 static GMainLoop *mainloop = NULL; static crm_trigger_t *trig = NULL; static int mainloop_iter = 0; static int callback_rc = 0; typedef void (*mainloop_test_iteration_cb) (int check_event); #define MAINLOOP_DEFAULT_TIMEOUT 2 #define mainloop_test_done(pass) \ if (pass) { \ crm_info("SUCCESS - %s", __FUNCTION__); \ mainloop_iter++; \ mainloop_set_trigger(trig); \ } else { \ crm_err("FAILURE = %s async_callback %d", __FUNCTION__, callback_rc); \ crm_exit(CRM_EX_ERROR); \ } \ callback_rc = 0; \ -/* *INDENT-OFF* */ enum test_modes { test_standard = 0, // test using a specific developer environment test_passive, // watch notifications only test_api_sanity, // sanity-test stonith client API using fence_dummy test_api_mainloop, // sanity-test mainloop code with async responses }; -static struct crm_option long_options[] = { - {"verbose", 0, 0, 'V'}, - {"version", 0, 0, '$'}, - {"help", 0, 0, '?'}, - {"passive", 0, 0, 'p'}, - {"api_test", 0, 0, 't'}, - {"mainloop_api_test", 0, 0, 'm'}, - - {0, 0, 0, 0} +static pcmk__cli_option_t long_options[] = { + // long option, argument type, storage, short option, description, flags + { + "verbose", no_argument, NULL, 'V', + NULL, pcmk__option_default + }, + { + "version", no_argument, NULL, '$', + NULL, pcmk__option_default + }, + { + "help", no_argument, NULL, '?', + NULL, pcmk__option_default + }, + { + "passive", no_argument, NULL, 'p', + NULL, pcmk__option_default + }, + { + "api_test", no_argument, NULL, 't', + NULL, pcmk__option_default + }, + { + "mainloop_api_test", no_argument, NULL, 'm', + NULL, pcmk__option_default + }, + { 0, 0, 0, 0 } }; -/* *INDENT-ON* */ static stonith_t *st = NULL; static struct pollfd pollfd; static int st_opts = st_opt_sync_call; static int expected_notifications = 0; static int verbose = 0; static void dispatch_helper(int timeout) { int rc; crm_debug("Looking for notification"); pollfd.events = POLLIN; while (true) { rc = poll(&pollfd, 1, timeout); /* wait 10 minutes, -1 forever */ if (rc > 0) { if (!stonith_dispatch(st)) { break; } } else { break; } } } static void st_callback(stonith_t * st, stonith_event_t * e) { if (st->state == stonith_disconnected) { crm_exit(CRM_EX_DISCONNECT); } crm_notice("Operation %s requested by %s %s for peer %s. %s reported: %s (ref=%s)", e->operation, e->origin, e->result == pcmk_ok ? "completed" : "failed", e->target, e->executioner ? e->executioner : "", pcmk_strerror(e->result), e->id); if (expected_notifications) { expected_notifications--; } } static void st_global_callback(stonith_t * stonith, stonith_callback_data_t * data) { crm_notice("Call id %d completed with rc %d", data->call_id, data->rc); } static void passive_test(void) { int rc = 0; rc = st->cmds->connect(st, crm_system_name, &pollfd.fd); if (rc != pcmk_ok) { stonith_api_delete(st); crm_exit(CRM_EX_DISCONNECT); } st->cmds->register_notification(st, T_STONITH_NOTIFY_DISCONNECT, st_callback); st->cmds->register_notification(st, T_STONITH_NOTIFY_FENCE, st_callback); st->cmds->register_notification(st, STONITH_OP_DEVICE_ADD, st_callback); st->cmds->register_notification(st, STONITH_OP_DEVICE_DEL, st_callback); st->cmds->register_callback(st, 0, 120, st_opt_timeout_updates, NULL, "st_global_callback", st_global_callback); dispatch_helper(600 * 1000); } #define single_test(cmd, str, num_notifications, expected_rc) \ { \ int rc = 0; \ rc = cmd; \ expected_notifications = 0; \ if (num_notifications) { \ expected_notifications = num_notifications; \ dispatch_helper(500); \ } \ if (rc != expected_rc) { \ crm_err("FAILURE - expected rc %d != %d(%s) for cmd - %s", expected_rc, rc, pcmk_strerror(rc), str); \ crm_exit(CRM_EX_ERROR); \ } else if (expected_notifications) { \ crm_err("FAILURE - expected %d notifications, got only %d for cmd - %s", \ num_notifications, num_notifications - expected_notifications, str); \ crm_exit(CRM_EX_ERROR); \ } else { \ if (verbose) { \ crm_info("SUCCESS - %s: %d", str, rc); \ } else { \ crm_debug("SUCCESS - %s: %d", str, rc); \ } \ } \ }\ static void run_fence_failure_test(void) { stonith_key_value_t *params = NULL; params = stonith_key_value_add(params, "pcmk_host_map", "false_1_node1=1,2 false_1_node2=3,4"); params = stonith_key_value_add(params, "mode", "fail"); single_test(st-> cmds->register_device(st, st_opts, "test-id1", "stonith-ng", "fence_dummy", params), "Register device1 for failure test", 1, 0); single_test(st->cmds->fence(st, st_opts, "false_1_node2", "off", 3, 0), "Fence failure results off", 1, -pcmk_err_generic); single_test(st->cmds->fence(st, st_opts, "false_1_node2", "reboot", 3, 0), "Fence failure results reboot", 1, -pcmk_err_generic); single_test(st->cmds->remove_device(st, st_opts, "test-id1"), "Remove device1 for failure test", 1, 0); stonith_key_value_freeall(params, 1, 1); } static void run_fence_failure_rollover_test(void) { stonith_key_value_t *params = NULL; params = stonith_key_value_add(params, "pcmk_host_map", "false_1_node1=1,2 false_1_node2=3,4"); params = stonith_key_value_add(params, "mode", "fail"); single_test(st-> cmds->register_device(st, st_opts, "test-id1", "stonith-ng", "fence_dummy", params), "Register device1 for rollover test", 1, 0); stonith_key_value_freeall(params, 1, 1); params = NULL; params = stonith_key_value_add(params, "pcmk_host_map", "false_1_node1=1,2 false_1_node2=3,4"); params = stonith_key_value_add(params, "mode", "pass"); single_test(st-> cmds->register_device(st, st_opts, "test-id2", "stonith-ng", "fence_dummy", params), "Register device2 for rollover test", 1, 0); single_test(st->cmds->fence(st, st_opts, "false_1_node2", "off", 3, 0), "Fence rollover results off", 1, 0); /* Expect -ENODEV because fence_dummy requires 'on' to be executed on target */ single_test(st->cmds->fence(st, st_opts, "false_1_node2", "on", 3, 0), "Fence rollover results on", 1, -ENODEV); single_test(st->cmds->remove_device(st, st_opts, "test-id1"), "Remove device1 for rollover tests", 1, 0); single_test(st->cmds->remove_device(st, st_opts, "test-id2"), "Remove device2 for rollover tests", 1, 0); stonith_key_value_freeall(params, 1, 1); } static void run_standard_test(void) { stonith_key_value_t *params = NULL; params = stonith_key_value_add(params, "pcmk_host_map", "false_1_node1=1,2 false_1_node2=3,4"); params = stonith_key_value_add(params, "mode", "pass"); params = stonith_key_value_add(params, "mock_dynamic_hosts", "false_1_node1 false_1_node2"); single_test(st-> cmds->register_device(st, st_opts, "test-id", "stonith-ng", "fence_dummy", params), "Register", 1, 0); stonith_key_value_freeall(params, 1, 1); params = NULL; single_test(st->cmds->list(st, st_opts, "test-id", NULL, 1), "list", 1, 0); single_test(st->cmds->monitor(st, st_opts, "test-id", 1), "Monitor", 1, 0); single_test(st->cmds->status(st, st_opts, "test-id", "false_1_node2", 1), "Status false_1_node2", 1, 0); single_test(st->cmds->status(st, st_opts, "test-id", "false_1_node1", 1), "Status false_1_node1", 1, 0); single_test(st->cmds->fence(st, st_opts, "unknown-host", "off", 1, 0), "Fence unknown-host (expected failure)", 0, -ENODEV); single_test(st->cmds->fence(st, st_opts, "false_1_node1", "off", 1, 0), "Fence false_1_node1", 1, 0); /* Expect -ENODEV because fence_dummy requires 'on' to be executed on target */ single_test(st->cmds->fence(st, st_opts, "false_1_node1", "on", 1, 0), "Unfence false_1_node1", 1, -ENODEV); /* Confirm that an invalid level index is rejected */ single_test(st->cmds->register_level(st, st_opts, "node1", 999, params), "Attempt to register an invalid level index", 0, -EINVAL); single_test(st->cmds->remove_device(st, st_opts, "test-id"), "Remove test-id", 1, 0); stonith_key_value_freeall(params, 1, 1); } static void sanity_tests(void) { int rc = 0; rc = st->cmds->connect(st, crm_system_name, &pollfd.fd); if (rc != pcmk_ok) { stonith_api_delete(st); crm_exit(CRM_EX_DISCONNECT); } st->cmds->register_notification(st, T_STONITH_NOTIFY_DISCONNECT, st_callback); st->cmds->register_notification(st, T_STONITH_NOTIFY_FENCE, st_callback); st->cmds->register_notification(st, STONITH_OP_DEVICE_ADD, st_callback); st->cmds->register_notification(st, STONITH_OP_DEVICE_DEL, st_callback); st->cmds->register_callback(st, 0, 120, st_opt_timeout_updates, NULL, "st_global_callback", st_global_callback); crm_info("Starting API Sanity Tests"); run_standard_test(); run_fence_failure_test(); run_fence_failure_rollover_test(); crm_info("Sanity Tests Passed"); } static void standard_dev_test(void) { int rc = 0; char *tmp = NULL; stonith_key_value_t *params = NULL; rc = st->cmds->connect(st, crm_system_name, &pollfd.fd); if (rc != pcmk_ok) { stonith_api_delete(st); crm_exit(CRM_EX_DISCONNECT); } params = stonith_key_value_add(params, "pcmk_host_map", "some-host=pcmk-7 true_1_node1=3,4"); rc = st->cmds->register_device(st, st_opts, "test-id", "stonith-ng", "fence_xvm", params); crm_debug("Register: %d", rc); rc = st->cmds->list(st, st_opts, "test-id", &tmp, 10); crm_debug("List: %d output: %s", rc, tmp ? tmp : ""); rc = st->cmds->monitor(st, st_opts, "test-id", 10); crm_debug("Monitor: %d", rc); rc = st->cmds->status(st, st_opts, "test-id", "false_1_node2", 10); crm_debug("Status false_1_node2: %d", rc); rc = st->cmds->status(st, st_opts, "test-id", "false_1_node1", 10); crm_debug("Status false_1_node1: %d", rc); rc = st->cmds->fence(st, st_opts, "unknown-host", "off", 60, 0); crm_debug("Fence unknown-host: %d", rc); rc = st->cmds->status(st, st_opts, "test-id", "false_1_node1", 10); crm_debug("Status false_1_node1: %d", rc); rc = st->cmds->fence(st, st_opts, "false_1_node1", "off", 60, 0); crm_debug("Fence false_1_node1: %d", rc); rc = st->cmds->status(st, st_opts, "test-id", "false_1_node1", 10); crm_debug("Status false_1_node1: %d", rc); rc = st->cmds->fence(st, st_opts, "false_1_node1", "on", 10, 0); crm_debug("Unfence false_1_node1: %d", rc); rc = st->cmds->status(st, st_opts, "test-id", "false_1_node1", 10); crm_debug("Status false_1_node1: %d", rc); rc = st->cmds->fence(st, st_opts, "some-host", "off", 10, 0); crm_debug("Fence alias: %d", rc); rc = st->cmds->status(st, st_opts, "test-id", "some-host", 10); crm_debug("Status alias: %d", rc); rc = st->cmds->fence(st, st_opts, "false_1_node1", "on", 10, 0); crm_debug("Unfence false_1_node1: %d", rc); rc = st->cmds->remove_device(st, st_opts, "test-id"); crm_debug("Remove test-id: %d", rc); stonith_key_value_freeall(params, 1, 1); } static void iterate_mainloop_tests(gboolean event_ready); static void mainloop_callback(stonith_t * stonith, stonith_callback_data_t * data) { callback_rc = data->rc; iterate_mainloop_tests(TRUE); } static int register_callback_helper(int callid) { return st->cmds->register_callback(st, callid, MAINLOOP_DEFAULT_TIMEOUT, st_opt_timeout_updates, NULL, "callback", mainloop_callback); } static void test_async_fence_pass(int check_event) { int rc = 0; if (check_event) { if (callback_rc != 0) { mainloop_test_done(FALSE); } else { mainloop_test_done(TRUE); } return; } rc = st->cmds->fence(st, 0, "true_1_node1", "off", MAINLOOP_DEFAULT_TIMEOUT, 0); if (rc < 0) { crm_err("fence failed with rc %d", rc); mainloop_test_done(FALSE); } register_callback_helper(rc); /* wait for event */ } #define CUSTOM_TIMEOUT_ADDITION 10 static void test_async_fence_custom_timeout(int check_event) { int rc = 0; static time_t begin = 0; if (check_event) { uint32_t diff = (time(NULL) - begin); if (callback_rc != -ETIME) { mainloop_test_done(FALSE); } else if (diff < CUSTOM_TIMEOUT_ADDITION + MAINLOOP_DEFAULT_TIMEOUT) { crm_err ("Custom timeout test failed, callback expiration should be updated to %d, actual timeout was %d", CUSTOM_TIMEOUT_ADDITION + MAINLOOP_DEFAULT_TIMEOUT, diff); mainloop_test_done(FALSE); } else { mainloop_test_done(TRUE); } return; } begin = time(NULL); rc = st->cmds->fence(st, 0, "custom_timeout_node1", "off", MAINLOOP_DEFAULT_TIMEOUT, 0); if (rc < 0) { crm_err("fence failed with rc %d", rc); mainloop_test_done(FALSE); } register_callback_helper(rc); /* wait for event */ } static void test_async_fence_timeout(int check_event) { int rc = 0; if (check_event) { if (callback_rc != -ENODEV) { mainloop_test_done(FALSE); } else { mainloop_test_done(TRUE); } return; } rc = st->cmds->fence(st, 0, "false_1_node2", "off", MAINLOOP_DEFAULT_TIMEOUT, 0); if (rc < 0) { crm_err("fence failed with rc %d", rc); mainloop_test_done(FALSE); } register_callback_helper(rc); /* wait for event */ } static void test_async_monitor(int check_event) { int rc = 0; if (check_event) { if (callback_rc) { mainloop_test_done(FALSE); } else { mainloop_test_done(TRUE); } return; } rc = st->cmds->monitor(st, 0, "false_1", MAINLOOP_DEFAULT_TIMEOUT); if (rc < 0) { crm_err("monitor failed with rc %d", rc); mainloop_test_done(FALSE); } register_callback_helper(rc); /* wait for event */ } static void test_register_async_devices(int check_event) { char buf[16] = { 0, }; stonith_key_value_t *params = NULL; params = stonith_key_value_add(params, "pcmk_host_map", "false_1_node1=1,2"); params = stonith_key_value_add(params, "mode", "fail"); st->cmds->register_device(st, st_opts, "false_1", "stonith-ng", "fence_dummy", params); stonith_key_value_freeall(params, 1, 1); params = NULL; params = stonith_key_value_add(params, "pcmk_host_map", "true_1_node1=1,2"); params = stonith_key_value_add(params, "mode", "pass"); st->cmds->register_device(st, st_opts, "true_1", "stonith-ng", "fence_dummy", params); stonith_key_value_freeall(params, 1, 1); params = NULL; params = stonith_key_value_add(params, "pcmk_host_map", "custom_timeout_node1=1,2"); params = stonith_key_value_add(params, "mode", "fail"); params = stonith_key_value_add(params, "delay", "1000"); snprintf(buf, sizeof(buf) - 1, "%d", MAINLOOP_DEFAULT_TIMEOUT + CUSTOM_TIMEOUT_ADDITION); params = stonith_key_value_add(params, "pcmk_off_timeout", buf); st->cmds->register_device(st, st_opts, "false_custom_timeout", "stonith-ng", "fence_dummy", params); stonith_key_value_freeall(params, 1, 1); mainloop_test_done(TRUE); } static void try_mainloop_connect(int check_event) { int rc = stonith_api_connect_retry(st, crm_system_name, 10); if (rc == pcmk_ok) { mainloop_test_done(TRUE); return; } crm_err("API CONNECTION FAILURE"); mainloop_test_done(FALSE); } static void iterate_mainloop_tests(gboolean event_ready) { static mainloop_test_iteration_cb callbacks[] = { try_mainloop_connect, test_register_async_devices, test_async_monitor, test_async_fence_pass, test_async_fence_timeout, test_async_fence_custom_timeout, }; if (mainloop_iter == (sizeof(callbacks) / sizeof(mainloop_test_iteration_cb))) { /* all tests ran, everything passed */ crm_info("ALL MAINLOOP TESTS PASSED!"); crm_exit(CRM_EX_OK); } callbacks[mainloop_iter] (event_ready); } static gboolean trigger_iterate_mainloop_tests(gpointer user_data) { iterate_mainloop_tests(FALSE); return TRUE; } static void test_shutdown(int nsig) { int rc = 0; if (st) { rc = st->cmds->disconnect(st); crm_info("Disconnect: %d", rc); crm_debug("Destroy"); stonith_api_delete(st); } if (rc) { crm_exit(CRM_EX_ERROR); } } static void mainloop_tests(void) { trig = mainloop_add_trigger(G_PRIORITY_HIGH, trigger_iterate_mainloop_tests, 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); } int main(int argc, char **argv) { int argerr = 0; int flag; int option_index = 0; enum test_modes mode = test_standard; crm_log_cli_init("cts-fence-helper"); - crm_set_options(NULL, "mode [options]", long_options, - "Provides a summary of cluster's current state." - "\n\nOutputs varying levels of detail in a number of different formats.\n"); + pcmk__set_cli_options(NULL, " [options]", long_options, + "inject commands into the Pacemaker fencer, " + "and watch for events"); while (1) { - flag = crm_get_option(argc, argv, &option_index); + flag = pcmk__next_cli_option(argc, argv, &option_index, NULL); if (flag == -1) { break; } switch (flag) { case 'V': verbose = 1; break; case '$': case '?': - crm_help(flag, CRM_EX_OK); + pcmk__cli_help(flag, CRM_EX_OK); break; case 'p': mode = test_passive; break; case 't': mode = test_api_sanity; break; case 'm': mode = test_api_mainloop; break; default: ++argerr; break; } } crm_log_init(NULL, LOG_INFO, TRUE, (verbose? TRUE : FALSE), argc, argv, FALSE); if (optind > argc) { ++argerr; } if (argerr) { - crm_help('?', CRM_EX_USAGE); + pcmk__cli_help('?', CRM_EX_USAGE); } st = stonith_api_new(); if (st == NULL) { crm_err("Could not connect to fencer: API memory allocation failed"); crm_exit(CRM_EX_DISCONNECT); } switch (mode) { case test_standard: standard_dev_test(); break; case test_passive: passive_test(); break; case test_api_sanity: sanity_tests(); break; case test_api_mainloop: mainloop_tests(); break; } test_shutdown(0); return CRM_EX_OK; } diff --git a/daemons/fenced/pacemaker-fenced.c b/daemons/fenced/pacemaker-fenced.c index 1d461fc5d9..4d03d9d204 100644 --- a/daemons/fenced/pacemaker-fenced.c +++ b/daemons/fenced/pacemaker-fenced.c @@ -1,1522 +1,1538 @@ /* * Copyright 2009-2020 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 /* U32T ~ PRIu32, X32T ~ PRIx32 */ #include #include #include #include #include #include #include #include #include #include #include #include #include char *stonith_our_uname = NULL; char *stonith_our_uuid = NULL; long stonith_watchdog_timeout_ms = 0; static GMainLoop *mainloop = NULL; gboolean stand_alone = FALSE; static gboolean no_cib_connect = FALSE; static gboolean stonith_shutdown_flag = FALSE; static qb_ipcs_service_t *ipcs = NULL; static xmlNode *local_cib = NULL; static pe_working_set_t *fenced_data_set = NULL; static cib_t *cib_api = NULL; static void *cib_library = NULL; static void stonith_shutdown(int nsig); static void stonith_cleanup(void); static int32_t st_ipc_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) { if (stonith_shutdown_flag) { crm_info("Ignoring new client [%d] during shutdown", pcmk__client_pid(c)); return -EPERM; } if (pcmk__new_client(c, uid, gid) == NULL) { return -EIO; } return 0; } /* Exit code means? */ static int32_t st_ipc_dispatch(qb_ipcs_connection_t * qbc, void *data, size_t size) { uint32_t id = 0; uint32_t flags = 0; int call_options = 0; xmlNode *request = NULL; pcmk__client_t *c = pcmk__find_client(qbc); const char *op = NULL; if (c == NULL) { crm_info("Invalid client: %p", qbc); return 0; } request = pcmk__client_data2xml(c, data, &id, &flags); if (request == NULL) { pcmk__ipc_send_ack(c, id, flags, "nack"); return 0; } op = crm_element_value(request, F_CRM_TASK); if(safe_str_eq(op, CRM_OP_RM_NODE_CACHE)) { crm_xml_add(request, F_TYPE, T_STONITH_NG); crm_xml_add(request, F_STONITH_OPERATION, op); crm_xml_add(request, F_STONITH_CLIENTID, c->id); crm_xml_add(request, F_STONITH_CLIENTNAME, pcmk__client_name(c)); crm_xml_add(request, F_STONITH_CLIENTNODE, stonith_our_uname); send_cluster_message(NULL, crm_msg_stonith_ng, request, FALSE); free_xml(request); return 0; } if (c->name == NULL) { const char *value = crm_element_value(request, F_STONITH_CLIENTNAME); if (value == NULL) { value = "unknown"; } c->name = crm_strdup_printf("%s.%u", value, c->pid); } crm_element_value_int(request, F_STONITH_CALLOPTS, &call_options); crm_trace("Flags %" X32T "/%u for command %" U32T " from %s", flags, call_options, id, pcmk__client_name(c)); if (is_set(call_options, st_opt_sync_call)) { CRM_ASSERT(flags & crm_ipc_client_response); CRM_LOG_ASSERT(c->request_id == 0); /* This means the client has two synchronous events in-flight */ c->request_id = id; /* Reply only to the last one */ } crm_xml_add(request, F_STONITH_CLIENTID, c->id); crm_xml_add(request, F_STONITH_CLIENTNAME, pcmk__client_name(c)); crm_xml_add(request, F_STONITH_CLIENTNODE, stonith_our_uname); stonith_command(c, id, flags, request, NULL); free_xml(request); return 0; } /* Error code means? */ static int32_t st_ipc_closed(qb_ipcs_connection_t * c) { pcmk__client_t *client = pcmk__find_client(c); if (client == NULL) { return 0; } crm_trace("Connection %p closed", c); pcmk__free_client(client); /* 0 means: yes, go ahead and destroy the connection */ return 0; } static void st_ipc_destroy(qb_ipcs_connection_t * c) { crm_trace("Connection %p destroyed", c); st_ipc_closed(c); } static void stonith_peer_callback(xmlNode * msg, void *private_data) { const char *remote_peer = crm_element_value(msg, F_ORIG); const char *op = crm_element_value(msg, F_STONITH_OPERATION); if (crm_str_eq(op, "poke", TRUE)) { return; } crm_log_xml_trace(msg, "Peer[inbound]"); stonith_command(NULL, 0, 0, msg, remote_peer); } #if SUPPORT_COROSYNC static void stonith_peer_ais_callback(cpg_handle_t handle, const struct cpg_name *groupName, uint32_t nodeid, uint32_t pid, void *msg, size_t msg_len) { uint32_t kind = 0; xmlNode *xml = NULL; const char *from = NULL; char *data = pcmk_message_common_cs(handle, nodeid, pid, msg, &kind, &from); if(data == NULL) { return; } if (kind == crm_class_cluster) { xml = string2xml(data); if (xml == NULL) { crm_err("Invalid XML: '%.120s'", data); free(data); return; } crm_xml_add(xml, F_ORIG, from); /* crm_xml_add_int(xml, F_SEQ, wrapper->id); */ stonith_peer_callback(xml, NULL); } free_xml(xml); free(data); return; } static void stonith_peer_cs_destroy(gpointer user_data) { crm_crit("Lost connection to cluster layer, shutting down"); stonith_shutdown(0); } #endif void do_local_reply(xmlNode * notify_src, const char *client_id, gboolean sync_reply, gboolean from_peer) { /* send callback to originating child */ pcmk__client_t *client_obj = NULL; int local_rc = pcmk_rc_ok; crm_trace("Sending response"); client_obj = pcmk__find_client_by_id(client_id); crm_trace("Sending callback to request originator"); if (client_obj == NULL) { local_rc = EPROTO; crm_trace("No client to sent the response to. F_STONITH_CLIENTID not set."); } else { int rid = 0; if (sync_reply) { CRM_LOG_ASSERT(client_obj->request_id); rid = client_obj->request_id; client_obj->request_id = 0; crm_trace("Sending response %d to %s %s", rid, client_obj->name, from_peer ? "(originator of delegated request)" : ""); } else { crm_trace("Sending an event to %s %s", client_obj->name, from_peer ? "(originator of delegated request)" : ""); } local_rc = pcmk__ipc_send_xml(client_obj, rid, notify_src, (sync_reply? crm_ipc_flags_none : crm_ipc_server_event)); } if ((local_rc != pcmk_rc_ok) && (client_obj != NULL)) { crm_warn("%s reply to %s failed: %s", (sync_reply? "Synchronous" : "Asynchronous"), (client_obj? client_obj->name : "unknown client"), pcmk_rc_str(local_rc)); } } long long get_stonith_flag(const char *name) { if (safe_str_eq(name, T_STONITH_NOTIFY_FENCE)) { return st_callback_notify_fence; } else if (safe_str_eq(name, STONITH_OP_DEVICE_ADD)) { return st_callback_device_add; } else if (safe_str_eq(name, STONITH_OP_DEVICE_DEL)) { return st_callback_device_del; } else if (safe_str_eq(name, T_STONITH_NOTIFY_HISTORY)) { return st_callback_notify_history; } else if (safe_str_eq(name, T_STONITH_NOTIFY_HISTORY_SYNCED)) { return st_callback_notify_history_synced; } return st_callback_unknown; } static void stonith_notify_client(gpointer key, gpointer value, gpointer user_data) { xmlNode *update_msg = user_data; pcmk__client_t *client = value; const char *type = NULL; CRM_CHECK(client != NULL, return); CRM_CHECK(update_msg != NULL, return); type = crm_element_value(update_msg, F_SUBTYPE); CRM_CHECK(type != NULL, crm_log_xml_err(update_msg, "notify"); return); if (client->ipcs == NULL) { crm_trace("Skipping client with NULL channel"); return; } if (client->options & get_stonith_flag(type)) { int rc = pcmk__ipc_send_xml(client, 0, update_msg, crm_ipc_server_event|crm_ipc_server_error); if (rc != pcmk_rc_ok) { crm_warn("%s notification of client %s failed: %s " CRM_XS " id=%.8s rc=%d", type, pcmk__client_name(client), pcmk_rc_str(rc), client->id, rc); } else { crm_trace("Sent %s notification to client %s.%.6s", type, pcmk__client_name(client), client->id); } } } void do_stonith_async_timeout_update(const char *client_id, const char *call_id, int timeout) { pcmk__client_t *client = NULL; xmlNode *notify_data = NULL; if (!timeout || !call_id || !client_id) { return; } client = pcmk__find_client_by_id(client_id); if (!client) { return; } notify_data = create_xml_node(NULL, T_STONITH_TIMEOUT_VALUE); crm_xml_add(notify_data, F_TYPE, T_STONITH_TIMEOUT_VALUE); crm_xml_add(notify_data, F_STONITH_CALLID, call_id); crm_xml_add_int(notify_data, F_STONITH_TIMEOUT, timeout); crm_trace("timeout update is %d for client %s and call id %s", timeout, client_id, call_id); if (client) { pcmk__ipc_send_xml(client, 0, notify_data, crm_ipc_server_event); } free_xml(notify_data); } void do_stonith_notify(int options, const char *type, int result, xmlNode * data) { /* TODO: Standardize the contents of data */ xmlNode *update_msg = create_xml_node(NULL, "notify"); CRM_CHECK(type != NULL,;); crm_xml_add(update_msg, F_TYPE, T_STONITH_NOTIFY); crm_xml_add(update_msg, F_SUBTYPE, type); crm_xml_add(update_msg, F_STONITH_OPERATION, type); crm_xml_add_int(update_msg, F_STONITH_RC, result); if (data != NULL) { add_message_xml(update_msg, F_STONITH_CALLDATA, data); } crm_trace("Notifying clients"); pcmk__foreach_ipc_client(stonith_notify_client, update_msg); free_xml(update_msg); crm_trace("Notify complete"); } static void do_stonith_notify_config(int options, const char *op, int rc, const char *desc, int active) { xmlNode *notify_data = create_xml_node(NULL, op); CRM_CHECK(notify_data != NULL, return); crm_xml_add(notify_data, F_STONITH_DEVICE, desc); crm_xml_add_int(notify_data, F_STONITH_ACTIVE, active); do_stonith_notify(options, op, rc, notify_data); free_xml(notify_data); } void do_stonith_notify_device(int options, const char *op, int rc, const char *desc) { do_stonith_notify_config(options, op, rc, desc, g_hash_table_size(device_list)); } void do_stonith_notify_level(int options, const char *op, int rc, const char *desc) { do_stonith_notify_config(options, op, rc, desc, g_hash_table_size(topology)); } static void topology_remove_helper(const char *node, int level) { int rc; char *desc = NULL; xmlNode *data = create_xml_node(NULL, XML_TAG_FENCING_LEVEL); crm_xml_add(data, F_STONITH_ORIGIN, __FUNCTION__); crm_xml_add_int(data, XML_ATTR_STONITH_INDEX, level); crm_xml_add(data, XML_ATTR_STONITH_TARGET, node); rc = stonith_level_remove(data, &desc); do_stonith_notify_level(0, STONITH_OP_LEVEL_DEL, rc, desc); free_xml(data); free(desc); } static void remove_cib_device(xmlXPathObjectPtr xpathObj) { int max = numXpathResults(xpathObj), lpc = 0; for (lpc = 0; lpc < max; lpc++) { const char *rsc_id = NULL; const char *standard = NULL; xmlNode *match = getXpathResult(xpathObj, lpc); CRM_LOG_ASSERT(match != NULL); if(match != NULL) { standard = crm_element_value(match, XML_AGENT_ATTR_CLASS); } if (safe_str_neq(standard, PCMK_RESOURCE_CLASS_STONITH)) { continue; } rsc_id = crm_element_value(match, XML_ATTR_ID); stonith_device_remove(rsc_id, TRUE); } } static void handle_topology_change(xmlNode *match, bool remove) { int rc; char *desc = NULL; CRM_CHECK(match != NULL, return); crm_trace("Updating %s", ID(match)); if(remove) { int index = 0; char *key = stonith_level_key(match, -1); crm_element_value_int(match, XML_ATTR_STONITH_INDEX, &index); topology_remove_helper(key, index); free(key); } rc = stonith_level_register(match, &desc); do_stonith_notify_level(0, STONITH_OP_LEVEL_ADD, rc, desc); free(desc); } static void remove_fencing_topology(xmlXPathObjectPtr xpathObj) { int max = numXpathResults(xpathObj), lpc = 0; for (lpc = 0; lpc < max; lpc++) { xmlNode *match = getXpathResult(xpathObj, lpc); CRM_LOG_ASSERT(match != NULL); if (match && crm_element_value(match, XML_DIFF_MARKER)) { /* Deletion */ int index = 0; char *target = stonith_level_key(match, -1); crm_element_value_int(match, XML_ATTR_STONITH_INDEX, &index); if (target == NULL) { crm_err("Invalid fencing target in element %s", ID(match)); } else if (index <= 0) { crm_err("Invalid level for %s in element %s", target, ID(match)); } else { topology_remove_helper(target, index); } /* } else { Deal with modifications during the 'addition' stage */ } } } static void register_fencing_topology(xmlXPathObjectPtr xpathObj) { int max = numXpathResults(xpathObj), lpc = 0; for (lpc = 0; lpc < max; lpc++) { xmlNode *match = getXpathResult(xpathObj, lpc); handle_topology_change(match, TRUE); } } /* Fencing */ static void fencing_topology_init() { xmlXPathObjectPtr xpathObj = NULL; const char *xpath = "//" XML_TAG_FENCING_LEVEL; crm_trace("Full topology refresh"); free_topology_list(); init_topology_list(); /* Grab everything */ xpathObj = xpath_search(local_cib, xpath); register_fencing_topology(xpathObj); freeXpathObject(xpathObj); } #define rsc_name(x) x->clone_name?x->clone_name:x->id /*! * \internal * \brief Check whether our uname is in a resource's allowed node list * * \param[in] rsc Resource to check * * \return Pointer to node object if found, NULL otherwise */ static node_t * our_node_allowed_for(resource_t *rsc) { GHashTableIter iter; node_t *node = NULL; if (rsc && stonith_our_uname) { g_hash_table_iter_init(&iter, rsc->allowed_nodes); while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) { if (node && strcmp(node->details->uname, stonith_our_uname) == 0) { break; } node = NULL; } } return node; } /*! * \internal * \brief If a resource or any of its children are STONITH devices, update their * definitions given a cluster working set. * * \param[in] rsc Resource to check * \param[in] data_set Cluster working set with device information */ static void cib_device_update(resource_t *rsc, pe_working_set_t *data_set) { node_t *node = NULL; const char *value = NULL; const char *rclass = NULL; node_t *parent = NULL; gboolean remove = TRUE; /* If this is a complex resource, check children rather than this resource itself. * TODO: Mark each installed device and remove if untouched when this process finishes. */ if(rsc->children) { GListPtr gIter = NULL; for (gIter = rsc->children; gIter != NULL; gIter = gIter->next) { cib_device_update(gIter->data, data_set); if(pe_rsc_is_clone(rsc)) { crm_trace("Only processing one copy of the clone %s", rsc->id); break; } } return; } /* We only care about STONITH resources. */ rclass = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); if (safe_str_neq(rclass, PCMK_RESOURCE_CLASS_STONITH)) { return; } /* If this STONITH resource is disabled, just remove it. */ if (pe__resource_is_disabled(rsc)) { crm_info("Device %s has been disabled", rsc->id); goto update_done; } /* Check whether our node is allowed for this resource (and its parent if in a group) */ node = our_node_allowed_for(rsc); if (rsc->parent && (rsc->parent->variant == pe_group)) { parent = our_node_allowed_for(rsc->parent); } if(node == NULL) { /* Our node is disallowed, so remove the device */ GHashTableIter iter; crm_info("Device %s has been disabled on %s: unknown", rsc->id, stonith_our_uname); g_hash_table_iter_init(&iter, rsc->allowed_nodes); while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) { crm_trace("Available: %s = %d", node->details->uname, node->weight); } goto update_done; } else if(node->weight < 0 || (parent && parent->weight < 0)) { /* Our node (or its group) is disallowed by score, so remove the device */ char *score = score2char((node->weight < 0) ? node->weight : parent->weight); crm_info("Device %s has been disabled on %s: score=%s", rsc->id, stonith_our_uname, score); free(score); goto update_done; } else { /* Our node is allowed, so update the device information */ int rc; xmlNode *data; GHashTableIter gIter; stonith_key_value_t *params = NULL; const char *name = NULL; const char *agent = crm_element_value(rsc->xml, XML_EXPR_ATTR_TYPE); const char *rsc_provides = NULL; crm_debug("Device %s is allowed on %s: score=%d", rsc->id, stonith_our_uname, node->weight); get_rsc_attributes(rsc->parameters, rsc, node, data_set); get_meta_attributes(rsc->meta, rsc, node, data_set); rsc_provides = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_PROVIDES); g_hash_table_iter_init(&gIter, rsc->parameters); while (g_hash_table_iter_next(&gIter, (gpointer *) & name, (gpointer *) & value)) { if (!name || !value) { continue; } params = stonith_key_value_add(params, name, value); crm_trace(" %s=%s", name, value); } remove = FALSE; data = create_device_registration_xml(rsc_name(rsc), st_namespace_any, agent, params, rsc_provides); stonith_key_value_freeall(params, 1, 1); rc = stonith_device_register(data, NULL, TRUE); CRM_ASSERT(rc == pcmk_ok); free_xml(data); } update_done: if(remove && g_hash_table_lookup(device_list, rsc_name(rsc))) { stonith_device_remove(rsc_name(rsc), TRUE); } } /*! * \internal * \brief Update all STONITH device definitions based on current CIB */ static void cib_devices_update(void) { GListPtr gIter = NULL; crm_info("Updating devices to version %s.%s.%s", crm_element_value(local_cib, XML_ATTR_GENERATION_ADMIN), crm_element_value(local_cib, XML_ATTR_GENERATION), crm_element_value(local_cib, XML_ATTR_NUMUPDATES)); CRM_ASSERT(fenced_data_set != NULL); fenced_data_set->input = local_cib; fenced_data_set->now = crm_time_new(NULL); fenced_data_set->flags |= pe_flag_quick_location; fenced_data_set->localhost = stonith_our_uname; cluster_status(fenced_data_set); pcmk__schedule_actions(fenced_data_set, NULL, NULL); for (gIter = fenced_data_set->resources; gIter != NULL; gIter = gIter->next) { cib_device_update(gIter->data, fenced_data_set); } fenced_data_set->input = NULL; // Wasn't a copy, so don't let API free it pe_reset_working_set(fenced_data_set); } static void update_cib_stonith_devices_v2(const char *event, xmlNode * msg) { xmlNode *change = NULL; char *reason = NULL; bool needs_update = FALSE; xmlNode *patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT); for (change = __xml_first_child(patchset); change != NULL; change = __xml_next(change)) { const char *op = crm_element_value(change, XML_DIFF_OP); const char *xpath = crm_element_value(change, XML_DIFF_PATH); const char *shortpath = NULL; if ((op == NULL) || (strcmp(op, "move") == 0) || strstr(xpath, "/"XML_CIB_TAG_STATUS)) { continue; } else if (safe_str_eq(op, "delete") && strstr(xpath, "/"XML_CIB_TAG_RESOURCE)) { const char *rsc_id = NULL; char *search = NULL; char *mutable = NULL; if (strstr(xpath, XML_TAG_ATTR_SETS) || strstr(xpath, XML_TAG_META_SETS)) { needs_update = TRUE; reason = strdup("(meta) attribute deleted from resource"); break; } mutable = strdup(xpath); rsc_id = strstr(mutable, "primitive[@id=\'"); if (rsc_id != NULL) { rsc_id += strlen("primitive[@id=\'"); search = strchr(rsc_id, '\''); } if (search != NULL) { *search = 0; stonith_device_remove(rsc_id, TRUE); } else { crm_warn("Ignoring malformed CIB update (resource deletion)"); } free(mutable); } else if (strstr(xpath, "/"XML_CIB_TAG_RESOURCES) || strstr(xpath, "/"XML_CIB_TAG_CONSTRAINTS) || strstr(xpath, "/"XML_CIB_TAG_RSCCONFIG)) { shortpath = strrchr(xpath, '/'); CRM_ASSERT(shortpath); reason = crm_strdup_printf("%s %s", op, shortpath+1); needs_update = TRUE; break; } } if(needs_update) { crm_info("Updating device list from the cib: %s", reason); cib_devices_update(); } else { crm_trace("No updates for device list found in cib"); } free(reason); } static void update_cib_stonith_devices_v1(const char *event, xmlNode * msg) { const char *reason = "none"; gboolean needs_update = FALSE; xmlXPathObjectPtr xpath_obj = NULL; /* process new constraints */ xpath_obj = xpath_search(msg, "//" F_CIB_UPDATE_RESULT "//" XML_CONS_TAG_RSC_LOCATION); if (numXpathResults(xpath_obj) > 0) { int max = numXpathResults(xpath_obj), lpc = 0; /* Safest and simplest to always recompute */ needs_update = TRUE; reason = "new location constraint"; for (lpc = 0; lpc < max; lpc++) { xmlNode *match = getXpathResult(xpath_obj, lpc); crm_log_xml_trace(match, "new constraint"); } } freeXpathObject(xpath_obj); /* process deletions */ xpath_obj = xpath_search(msg, "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_REMOVED "//" XML_CIB_TAG_RESOURCE); if (numXpathResults(xpath_obj) > 0) { remove_cib_device(xpath_obj); } freeXpathObject(xpath_obj); /* process additions */ xpath_obj = xpath_search(msg, "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED "//" XML_CIB_TAG_RESOURCE); if (numXpathResults(xpath_obj) > 0) { int max = numXpathResults(xpath_obj), lpc = 0; for (lpc = 0; lpc < max; lpc++) { const char *rsc_id = NULL; const char *standard = NULL; xmlNode *match = getXpathResult(xpath_obj, lpc); rsc_id = crm_element_value(match, XML_ATTR_ID); standard = crm_element_value(match, XML_AGENT_ATTR_CLASS); if (safe_str_neq(standard, PCMK_RESOURCE_CLASS_STONITH)) { continue; } crm_trace("Fencing resource %s was added or modified", rsc_id); reason = "new resource"; needs_update = TRUE; } } freeXpathObject(xpath_obj); if(needs_update) { crm_info("Updating device list from the cib: %s", reason); cib_devices_update(); } } static void update_cib_stonith_devices(const char *event, xmlNode * msg) { int format = 1; xmlNode *patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT); CRM_ASSERT(patchset); crm_element_value_int(patchset, "format", &format); switch(format) { case 1: update_cib_stonith_devices_v1(event, msg); break; case 2: update_cib_stonith_devices_v2(event, msg); break; default: crm_warn("Unknown patch format: %d", format); } } /* Needs to hold node name + attribute name + attribute value + 75 */ #define XPATH_MAX 512 /*! * \internal * \brief Check whether a node has a specific attribute name/value * * \param[in] node Name of node to check * \param[in] name Name of an attribute to look for * \param[in] value The value the named attribute needs to be set to in order to be considered a match * * \return TRUE if the locally cached CIB has the specified node attribute */ gboolean node_has_attr(const char *node, const char *name, const char *value) { char xpath[XPATH_MAX]; xmlNode *match; int n; CRM_CHECK(local_cib != NULL, return FALSE); /* Search for the node's attributes in the CIB. While the schema allows * multiple sets of instance attributes, and allows instance attributes to * use id-ref to reference values elsewhere, that is intended for resources, * so we ignore that here. */ n = snprintf(xpath, XPATH_MAX, "//" XML_CIB_TAG_NODES "/" XML_CIB_TAG_NODE "[@uname='%s']/" XML_TAG_ATTR_SETS "/" XML_CIB_TAG_NVPAIR "[@name='%s' and @value='%s']", node, name, value); match = get_xpath_object(xpath, local_cib, LOG_NEVER); CRM_CHECK(n < XPATH_MAX, return FALSE); return (match != NULL); } static void update_fencing_topology(const char *event, xmlNode * msg) { int format = 1; const char *xpath; xmlXPathObjectPtr xpathObj = NULL; xmlNode *patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT); CRM_ASSERT(patchset); crm_element_value_int(patchset, "format", &format); if(format == 1) { /* Process deletions (only) */ xpath = "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_REMOVED "//" XML_TAG_FENCING_LEVEL; xpathObj = xpath_search(msg, xpath); remove_fencing_topology(xpathObj); freeXpathObject(xpathObj); /* Process additions and changes */ xpath = "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED "//" XML_TAG_FENCING_LEVEL; xpathObj = xpath_search(msg, xpath); register_fencing_topology(xpathObj); freeXpathObject(xpathObj); } else if(format == 2) { xmlNode *change = NULL; int add[] = { 0, 0, 0 }; int del[] = { 0, 0, 0 }; xml_patch_versions(patchset, add, del); for (change = __xml_first_child(patchset); change != NULL; change = __xml_next(change)) { const char *op = crm_element_value(change, XML_DIFF_OP); const char *xpath = crm_element_value(change, XML_DIFF_PATH); if(op == NULL) { continue; } else if(strstr(xpath, "/" XML_TAG_FENCING_LEVEL) != NULL) { /* Change to a specific entry */ crm_trace("Handling %s operation %d.%d.%d for %s", op, add[0], add[1], add[2], xpath); if(strcmp(op, "move") == 0) { continue; } else if(strcmp(op, "create") == 0) { handle_topology_change(change->children, FALSE); } else if(strcmp(op, "modify") == 0) { xmlNode *match = first_named_child(change, XML_DIFF_RESULT); if(match) { handle_topology_change(match->children, TRUE); } } else if(strcmp(op, "delete") == 0) { /* Nuclear option, all we have is the path and an id... not enough to remove a specific entry */ crm_info("Re-initializing fencing topology after %s operation %d.%d.%d for %s", op, add[0], add[1], add[2], xpath); fencing_topology_init(); return; } } else if (strstr(xpath, "/" XML_TAG_FENCING_TOPOLOGY) != NULL) { /* Change to the topology in general */ crm_info("Re-initializing fencing topology after top-level %s operation %d.%d.%d for %s", op, add[0], add[1], add[2], xpath); fencing_topology_init(); return; } else if (strstr(xpath, "/" XML_CIB_TAG_CONFIGURATION)) { /* Changes to the whole config section, possibly including the topology as a whild */ if(first_named_child(change, XML_TAG_FENCING_TOPOLOGY) == NULL) { crm_trace("Nothing for us in %s operation %d.%d.%d for %s.", op, add[0], add[1], add[2], xpath); } else if(strcmp(op, "delete") == 0 || strcmp(op, "create") == 0) { crm_info("Re-initializing fencing topology after top-level %s operation %d.%d.%d for %s.", op, add[0], add[1], add[2], xpath); fencing_topology_init(); return; } } else { crm_trace("Nothing for us in %s operation %d.%d.%d for %s", op, add[0], add[1], add[2], xpath); } } } else { crm_warn("Unknown patch format: %d", format); } } static bool have_cib_devices = FALSE; static void update_cib_cache_cb(const char *event, xmlNode * msg) { int rc = pcmk_ok; xmlNode *stonith_enabled_xml = NULL; xmlNode *stonith_watchdog_xml = NULL; const char *stonith_enabled_s = NULL; static gboolean stonith_enabled_saved = TRUE; if(!have_cib_devices) { crm_trace("Skipping updates until we get a full dump"); return; } else if(msg == NULL) { crm_trace("Missing %s update", event); return; } /* Maintain a local copy of the CIB so that we have full access * to device definitions, location constraints, and node attributes */ if (local_cib != NULL) { int rc = pcmk_ok; xmlNode *patchset = NULL; crm_element_value_int(msg, F_CIB_RC, &rc); if (rc != pcmk_ok) { return; } patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT); xml_log_patchset(LOG_TRACE, "Config update", patchset); rc = xml_apply_patchset(local_cib, patchset, TRUE); switch (rc) { case pcmk_ok: case -pcmk_err_old_data: break; case -pcmk_err_diff_resync: case -pcmk_err_diff_failed: crm_notice("[%s] Patch aborted: %s (%d)", event, pcmk_strerror(rc), rc); free_xml(local_cib); local_cib = NULL; break; default: crm_warn("[%s] ABORTED: %s (%d)", event, pcmk_strerror(rc), rc); free_xml(local_cib); local_cib = NULL; } } if (local_cib == NULL) { crm_trace("Re-requesting the full cib"); rc = cib_api->cmds->query(cib_api, NULL, &local_cib, cib_scope_local | cib_sync_call); if(rc != pcmk_ok) { crm_err("Couldn't retrieve the CIB: %s (%d)", pcmk_strerror(rc), rc); return; } CRM_ASSERT(local_cib != NULL); stonith_enabled_saved = FALSE; /* Trigger a full refresh below */ } crm_peer_caches_refresh(local_cib); stonith_enabled_xml = get_xpath_object("//nvpair[@name='stonith-enabled']", local_cib, LOG_NEVER); if (stonith_enabled_xml) { stonith_enabled_s = crm_element_value(stonith_enabled_xml, XML_NVPAIR_ATTR_VALUE); } if (stonith_enabled_s == NULL || crm_is_true(stonith_enabled_s)) { long timeout_ms = 0; const char *value = NULL; stonith_watchdog_xml = get_xpath_object("//nvpair[@name='stonith-watchdog-timeout']", local_cib, LOG_NEVER); if (stonith_watchdog_xml) { value = crm_element_value(stonith_watchdog_xml, XML_NVPAIR_ATTR_VALUE); } if(value) { timeout_ms = crm_get_msec(value); } if (timeout_ms < 0) { - timeout_ms = crm_auto_watchdog_timeout(); + timeout_ms = pcmk__auto_watchdog_timeout(); } if(timeout_ms != stonith_watchdog_timeout_ms) { crm_notice("New watchdog timeout %lds (was %lds)", timeout_ms/1000, stonith_watchdog_timeout_ms/1000); stonith_watchdog_timeout_ms = timeout_ms; } } else { stonith_watchdog_timeout_ms = 0; } if (stonith_enabled_s && crm_is_true(stonith_enabled_s) == FALSE) { crm_trace("Ignoring cib updates while stonith is disabled"); stonith_enabled_saved = FALSE; return; } else if (stonith_enabled_saved == FALSE) { crm_info("Updating stonith device and topology lists now that stonith is enabled"); stonith_enabled_saved = TRUE; fencing_topology_init(); cib_devices_update(); } else { update_fencing_topology(event, msg); update_cib_stonith_devices(event, msg); } } static void init_cib_cache_cb(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data) { crm_info("Updating device list from the cib: init"); have_cib_devices = TRUE; local_cib = copy_xml(output); crm_peer_caches_refresh(local_cib); fencing_topology_init(); cib_devices_update(); } static void stonith_shutdown(int nsig) { crm_info("Terminating with %d clients", pcmk__ipc_client_count()); stonith_shutdown_flag = TRUE; if (mainloop != NULL && g_main_loop_is_running(mainloop)) { g_main_loop_quit(mainloop); } else { stonith_cleanup(); crm_exit(CRM_EX_OK); } } static void cib_connection_destroy(gpointer user_data) { if (stonith_shutdown_flag) { crm_info("Connection to the CIB manager closed"); return; } else { crm_crit("Lost connection to the CIB manager, shutting down"); } if (cib_api) { cib_api->cmds->signoff(cib_api); } stonith_shutdown(0); } static void stonith_cleanup(void) { if (cib_api) { cib_api->cmds->signoff(cib_api); } if (ipcs) { qb_ipcs_destroy(ipcs); } crm_peer_destroy(); pcmk__client_cleanup(); free_stonith_remote_op_list(); free_topology_list(); free_device_list(); free_metadata_cache(); free(stonith_our_uname); stonith_our_uname = NULL; free_xml(local_cib); local_cib = NULL; } -/* *INDENT-OFF* */ -static struct crm_option long_options[] = { - {"stand-alone", 0, 0, 's'}, - {"stand-alone-w-cpg", 0, 0, 'c'}, - {"logfile", 1, 0, 'l'}, - {"verbose", 0, 0, 'V'}, - {"version", 0, 0, '$'}, - {"help", 0, 0, '?'}, - - {0, 0, 0, 0} +static pcmk__cli_option_t long_options[] = { + // long option, argument type, storage, short option, description, flags + { + "stand-alone", no_argument, 0, 's', + NULL, pcmk__option_default + }, + { + "stand-alone-w-cpg", no_argument, 0, 'c', + NULL, pcmk__option_default + }, + { + "logfile", required_argument, 0, 'l', + NULL, pcmk__option_default + }, + { + "verbose", no_argument, 0, 'V', + NULL, pcmk__option_default + }, + { + "version", no_argument, 0, '$', + NULL, pcmk__option_default + }, + { + "help", no_argument, 0, '?', + NULL, pcmk__option_default + }, + { 0, 0, 0, 0 } }; -/* *INDENT-ON* */ static void setup_cib(void) { int rc, retries = 0; static cib_t *(*cib_new_fn) (void) = NULL; if (cib_new_fn == NULL) { cib_new_fn = find_library_function(&cib_library, CIB_LIBRARY, "cib_new", TRUE); } if (cib_new_fn != NULL) { cib_api = (*cib_new_fn) (); } if (cib_api == NULL) { crm_err("No connection to the CIB manager"); return; } do { sleep(retries); rc = cib_api->cmds->signon(cib_api, CRM_SYSTEM_STONITHD, cib_command); } while (rc == -ENOTCONN && ++retries < 5); if (rc != pcmk_ok) { crm_err("Could not connect to the CIB manager: %s (%d)", pcmk_strerror(rc), rc); } else if (pcmk_ok != cib_api->cmds->add_notify_callback(cib_api, T_CIB_DIFF_NOTIFY, update_cib_cache_cb)) { crm_err("Could not set CIB notification callback"); } else { rc = cib_api->cmds->query(cib_api, NULL, NULL, cib_scope_local); cib_api->cmds->register_callback(cib_api, rc, 120, FALSE, NULL, "init_cib_cache_cb", init_cib_cache_cb); cib_api->cmds->set_connection_dnotify(cib_api, cib_connection_destroy); crm_info("Watching for stonith topology changes"); } } struct qb_ipcs_service_handlers ipc_callbacks = { .connection_accept = st_ipc_accept, .connection_created = NULL, .msg_process = st_ipc_dispatch, .connection_closed = st_ipc_closed, .connection_destroyed = st_ipc_destroy }; /*! * \internal * \brief Callback for peer status changes * * \param[in] type What changed * \param[in] node What peer had the change * \param[in] data Previous value of what changed */ static void st_peer_update_callback(enum crm_status_type type, crm_node_t * node, const void *data) { if ((type != crm_status_processes) && !is_set(node->flags, crm_remote_node)) { /* * This is a hack until we can send to a nodeid and/or we fix node name lookups * These messages are ignored in stonith_peer_callback() */ xmlNode *query = create_xml_node(NULL, "stonith_command"); crm_xml_add(query, F_XML_TAGNAME, "stonith_command"); crm_xml_add(query, F_TYPE, T_STONITH_NG); crm_xml_add(query, F_STONITH_OPERATION, "poke"); crm_debug("Broadcasting our uname because of node %u", node->id); send_cluster_message(NULL, crm_msg_stonith_ng, query, FALSE); free_xml(query); } } int main(int argc, char **argv) { int flag; int lpc = 0; int argerr = 0; int option_index = 0; crm_cluster_t cluster; const char *actions[] = { "reboot", "off", "on", "list", "monitor", "status" }; crm_ipc_t *old_instance = NULL; crm_log_preinit(NULL, argc, argv); - crm_set_options(NULL, "mode [options]", long_options, - "Provides a summary of cluster's current state." - "\n\nOutputs varying levels of detail in a number of different formats.\n"); + pcmk__set_cli_options(NULL, "[options]", long_options, + "daemon for executing fencing devices in a " + "Pacemaker cluster"); while (1) { - flag = crm_get_option(argc, argv, &option_index); + flag = pcmk__next_cli_option(argc, argv, &option_index, NULL); if (flag == -1) { break; } switch (flag) { case 'V': crm_bump_log_level(argc, argv); break; case 'l': crm_add_logfile(optarg); break; case 's': stand_alone = TRUE; break; case 'c': stand_alone = FALSE; no_cib_connect = TRUE; break; case '$': case '?': - crm_help(flag, CRM_EX_OK); + pcmk__cli_help(flag, CRM_EX_OK); break; default: ++argerr; break; } } if (argc - optind == 1 && safe_str_eq("metadata", argv[optind])) { printf("\n"); printf("\n"); printf(" 1.0\n"); printf(" Instance attributes available for all \"stonith\"-class resources" " and used by Pacemaker's fence daemon, formerly known as stonithd\n"); printf(" Instance attributes available for all \"stonith\"-class resources\n"); printf(" \n"); #if 0 // priority is not implemented yet printf(" \n"); printf(" Devices that are not in a topology " "are tried in order of highest to lowest integer priority\n"); printf(" \n"); printf(" \n"); #endif printf(" \n", STONITH_ATTR_HOSTARG); printf (" Advanced use only: An alternate parameter to supply instead of 'port'\n"); printf (" Some devices do not support the standard 'port' parameter or may provide additional ones.\n" "Use this to specify an alternate, device-specific, parameter that should indicate the machine to be fenced.\n" "A value of 'none' can be used to tell the cluster not to supply any additional parameters.\n" " \n"); printf(" \n"); printf(" \n"); printf(" \n", STONITH_ATTR_HOSTMAP); printf (" A mapping of host names to ports numbers for devices that do not support host names.\n"); printf (" Eg. node1:1;node2:2,3 would tell the cluster to use port 1 for node1 and ports 2 and 3 for node2\n"); printf(" \n"); printf(" \n"); printf(" \n", STONITH_ATTR_HOSTLIST); printf (" A list of machines controlled by this device (Optional unless %s=static-list).\n", STONITH_ATTR_HOSTCHECK); printf(" \n"); printf(" \n"); printf(" \n", STONITH_ATTR_HOSTCHECK); printf (" How to determine which machines are controlled by the device.\n"); printf(" Allowed values: dynamic-list " "(query the device via the 'list' command), static-list " "(check the " STONITH_ATTR_HOSTLIST " attribute), status " "(query the device via the 'status' command), none (assume " "every device can fence every machine)\n"); printf(" \n"); printf(" \n"); printf(" \n", STONITH_ATTR_DELAY_MAX); printf (" Enable a random delay for stonith actions and specify the maximum of random delay.\n"); printf (" This prevents double fencing when using slow devices such as sbd.\n" "Use this to enable a random delay for stonith actions.\n" "The overall delay is derived from this random delay value adding a static delay so that the sum is kept below the maximum delay.\n"); printf(" \n"); printf(" \n"); printf(" \n", STONITH_ATTR_DELAY_BASE); printf (" Enable a base delay for stonith actions and specify base delay value.\n"); printf (" This prevents double fencing when different delays are configured on the nodes.\n" "Use this to enable a static delay for stonith actions.\n" "The overall delay is derived from a random delay value adding this static delay so that the sum is kept below the maximum delay.\n"); printf(" \n"); printf(" \n"); printf(" \n", STONITH_ATTR_ACTION_LIMIT); printf (" The maximum number of actions can be performed in parallel on this device\n"); printf (" Cluster property concurrent-fencing=true needs to be configured first.\n" "Then use this to specify the maximum number of actions can be performed in parallel on this device. -1 is unlimited.\n"); printf(" \n"); printf(" \n"); for (lpc = 0; lpc < DIMOF(actions); lpc++) { printf(" \n", actions[lpc]); printf (" Advanced use only: An alternate command to run instead of '%s'\n", actions[lpc]); printf (" Some devices do not support the standard commands or may provide additional ones.\n" "Use this to specify an alternate, device-specific, command that implements the '%s' action.\n", actions[lpc]); printf(" \n", actions[lpc]); printf(" \n"); printf(" \n", actions[lpc]); printf (" Advanced use only: Specify an alternate timeout to use for %s actions instead of stonith-timeout\n", actions[lpc]); printf (" Some devices need much more/less time to complete than normal.\n" "Use this to specify an alternate, device-specific, timeout for '%s' actions.\n", actions[lpc]); printf(" \n"); printf(" \n"); printf(" \n", actions[lpc]); printf (" Advanced use only: The maximum number of times to retry the '%s' command within the timeout period\n", actions[lpc]); printf(" Some devices do not support multiple connections." " Operations may 'fail' if the device is busy with another task so Pacemaker will automatically retry the operation, if there is time remaining." " Use this option to alter the number of times Pacemaker retries '%s' actions before giving up." "\n", actions[lpc]); printf(" \n"); printf(" \n"); } printf(" \n"); printf("\n"); return CRM_EX_OK; } if (optind != argc) { ++argerr; } if (argerr) { - crm_help('?', CRM_EX_USAGE); + pcmk__cli_help('?', CRM_EX_USAGE); } crm_log_init(NULL, LOG_INFO, TRUE, FALSE, argc, argv, FALSE); crm_notice("Starting Pacemaker fencer"); old_instance = crm_ipc_new("stonith-ng", 0); if (crm_ipc_connect(old_instance)) { /* IPC end-point already up */ crm_ipc_close(old_instance); crm_ipc_destroy(old_instance); crm_err("pacemaker-fenced is already active, aborting startup"); crm_exit(CRM_EX_OK); } else { /* not up or not authentic, we'll proceed either way */ crm_ipc_destroy(old_instance); old_instance = NULL; } mainloop_add_signal(SIGTERM, stonith_shutdown); crm_peer_init(); fenced_data_set = pe_new_working_set(); CRM_ASSERT(fenced_data_set != NULL); set_bit(fenced_data_set->flags, pe_flag_no_counts); set_bit(fenced_data_set->flags, pe_flag_no_compat); if (stand_alone == FALSE) { if (is_corosync_cluster()) { #if SUPPORT_COROSYNC cluster.destroy = stonith_peer_cs_destroy; cluster.cpg.cpg_deliver_fn = stonith_peer_ais_callback; cluster.cpg.cpg_confchg_fn = pcmk_cpg_membership; #endif } crm_set_status_callback(&st_peer_update_callback); if (crm_cluster_connect(&cluster) == FALSE) { crm_crit("Cannot sign in to the cluster... terminating"); crm_exit(CRM_EX_FATAL); } stonith_our_uname = cluster.uname; stonith_our_uuid = cluster.uuid; if (no_cib_connect == FALSE) { setup_cib(); } } else { stonith_our_uname = strdup("localhost"); } init_device_list(); init_topology_list(); if(stonith_watchdog_timeout_ms > 0) { int rc; xmlNode *xml; stonith_key_value_t *params = NULL; params = stonith_key_value_add(params, STONITH_ATTR_HOSTLIST, stonith_our_uname); xml = create_device_registration_xml("watchdog", st_namespace_internal, STONITH_WATCHDOG_AGENT, params, NULL); stonith_key_value_freeall(params, 1, 1); rc = stonith_device_register(xml, NULL, FALSE); free_xml(xml); if (rc != pcmk_ok) { crm_crit("Cannot register watchdog pseudo fence agent"); crm_exit(CRM_EX_FATAL); } } stonith_ipc_server_init(&ipcs, &ipc_callbacks); /* Create the mainloop and run it... */ mainloop = g_main_loop_new(NULL, FALSE); crm_notice("Pacemaker fencer successfully started and accepting connections"); g_main_loop_run(mainloop); stonith_cleanup(); pe_free_working_set(fenced_data_set); crm_exit(CRM_EX_OK); } diff --git a/daemons/pacemakerd/pacemakerd.c b/daemons/pacemakerd/pacemakerd.c index 2c1e44982b..16a959c510 100644 --- a/daemons/pacemakerd/pacemakerd.c +++ b/daemons/pacemakerd/pacemakerd.c @@ -1,1439 +1,1466 @@ /* * Copyright 2010-2020 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 "pacemakerd.h" #include #include #include #include #include #include #include #include #include #include #include /* indirectly: CRM_EX_* */ #include /* cib_channel_ro */ #include #include #include #include #include #include /* PCMK__SPECIAL_PID*, ... */ #ifdef SUPPORT_COROSYNC #include #endif #include #include static gboolean pcmk_quorate = FALSE; static gboolean fatal_error = FALSE; static GMainLoop *mainloop = NULL; static bool global_keep_tracking = false; #define PCMK_PROCESS_CHECK_INTERVAL 5 static const char *local_name = NULL; static uint32_t local_nodeid = 0; static crm_trigger_t *shutdown_trigger = NULL; static const char *pid_file = PCMK_RUN_DIR "/pacemaker.pid"; typedef struct pcmk_child_s { pid_t pid; long flag; int start_seq; int respawn_count; gboolean respawn; const char *name; const char *uid; const char *command; const char *endpoint; /* IPC server name */ gboolean active_before_startup; } pcmk_child_t; /* Index into the array below */ #define PCMK_CHILD_CONTROLD 3 static pcmk_child_t pcmk_children[] = { { 0, crm_proc_none, 0, 0, FALSE, "none", NULL, NULL }, { 0, crm_proc_execd, 3, 0, TRUE, "pacemaker-execd", NULL, CRM_DAEMON_DIR "/pacemaker-execd", CRM_SYSTEM_LRMD }, { 0, crm_proc_based, 1, 0, TRUE, "pacemaker-based", CRM_DAEMON_USER, CRM_DAEMON_DIR "/pacemaker-based", CIB_CHANNEL_RO }, { 0, crm_proc_controld, 6, 0, TRUE, "pacemaker-controld", CRM_DAEMON_USER, CRM_DAEMON_DIR "/pacemaker-controld", CRM_SYSTEM_CRMD }, { 0, crm_proc_attrd, 4, 0, TRUE, "pacemaker-attrd", CRM_DAEMON_USER, CRM_DAEMON_DIR "/pacemaker-attrd", T_ATTRD }, { 0, crm_proc_schedulerd, 5, 0, TRUE, "pacemaker-schedulerd", CRM_DAEMON_USER, CRM_DAEMON_DIR "/pacemaker-schedulerd", CRM_SYSTEM_PENGINE }, { 0, crm_proc_fenced, 2, 0, TRUE, "pacemaker-fenced", NULL, CRM_DAEMON_DIR "/pacemaker-fenced", "stonith-ng" }, }; static gboolean check_active_before_startup_processes(gpointer user_data); static int child_liveness(pcmk_child_t *child); static gboolean start_child(pcmk_child_t * child); static gboolean update_node_processes(uint32_t id, const char *uname, uint32_t procs); void update_process_clients(pcmk__client_t *client); static uint32_t get_process_list(void) { int lpc = 0; uint32_t procs = crm_get_cluster_proc(); for (lpc = 0; lpc < SIZEOF(pcmk_children); lpc++) { if (pcmk_children[lpc].pid != 0) { procs |= pcmk_children[lpc].flag; } } return procs; } static void pcmk_process_exit(pcmk_child_t * child) { child->pid = 0; child->active_before_startup = FALSE; /* Broadcast the fact that one of our processes died ASAP * * Try to get some logging of the cause out first though * because we're probably about to get fenced * * Potentially do this only if respawn_count > N * to allow for local recovery */ update_node_processes(local_nodeid, NULL, get_process_list()); child->respawn_count += 1; if (child->respawn_count > MAX_RESPAWN) { crm_err("Child respawn count exceeded by %s", child->name); child->respawn = FALSE; } if (shutdown_trigger) { /* resume step-wise shutdown (returned TRUE yields no parallelizing) */ mainloop_set_trigger(shutdown_trigger); /* intended to speed up propagating expected lay-off of the daemons? */ update_node_processes(local_nodeid, NULL, get_process_list()); } else if (!child->respawn) { /* nothing to do */ } else if (crm_is_true(getenv("PCMK_fail_fast"))) { crm_err("Rebooting system because of %s", child->name); pcmk_panic(__FUNCTION__); } else if (child_liveness(child) == pcmk_rc_ok) { crm_warn("One-off suppressing strict respawning of a child process %s," " appears alright per %s IPC end-point", child->name, child->endpoint); /* need to monitor how it evolves, and start new process if badly */ child->active_before_startup = TRUE; if (!global_keep_tracking) { global_keep_tracking = true; g_timeout_add_seconds(PCMK_PROCESS_CHECK_INTERVAL, check_active_before_startup_processes, NULL); } } else { crm_notice("Respawning failed child process: %s", child->name); start_child(child); } } static void pcmk_exit_with_cluster(int exitcode) { #ifdef SUPPORT_COROSYNC corosync_cfg_handle_t cfg_handle; cs_error_t err; if (exitcode == CRM_EX_FATAL) { crm_info("Asking Corosync to shut down"); err = corosync_cfg_initialize(&cfg_handle, NULL); if (err != CS_OK) { crm_warn("Unable to open handle to corosync to close it down. err=%d", err); } err = corosync_cfg_try_shutdown(cfg_handle, COROSYNC_CFG_SHUTDOWN_FLAG_IMMEDIATE); if (err != CS_OK) { crm_warn("Corosync shutdown failed. err=%d", err); } corosync_cfg_finalize(cfg_handle); } #endif crm_exit(exitcode); } static void pcmk_child_exit(mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode) { pcmk_child_t *child = mainloop_child_userdata(p); const char *name = mainloop_child_name(p); if (signo) { do_crm_log(((signo == SIGKILL)? LOG_WARNING : LOG_ERR), "%s[%d] terminated with signal %d (core=%d)", name, pid, signo, core); } else { switch(exitcode) { case CRM_EX_OK: crm_info("%s[%d] exited with status %d (%s)", name, pid, exitcode, crm_exit_str(exitcode)); break; case CRM_EX_FATAL: crm_warn("Shutting cluster down because %s[%d] had fatal failure", name, pid); child->respawn = FALSE; fatal_error = TRUE; pcmk_shutdown(SIGTERM); break; case CRM_EX_PANIC: do_crm_log_always(LOG_EMERG, "%s[%d] instructed the machine to reset", name, pid); child->respawn = FALSE; fatal_error = TRUE; pcmk_panic(__FUNCTION__); pcmk_shutdown(SIGTERM); break; default: crm_err("%s[%d] exited with status %d (%s)", name, pid, exitcode, crm_exit_str(exitcode)); break; } } pcmk_process_exit(child); } static gboolean stop_child(pcmk_child_t * child, int signal) { if (signal == 0) { signal = SIGTERM; } /* why to skip PID of 1? - FreeBSD ~ how untrackable process behind IPC is masqueraded as - elsewhere: how "init" task is designated; in particular, in systemd arrangement of socket-based activation, this is pretty real */ if (child->command == NULL || child->pid == PCMK__SPECIAL_PID) { crm_debug("Nothing to do for child \"%s\" (process %lld)", child->name, (long long) PCMK__SPECIAL_PID_AS_0(child->pid)); return TRUE; } if (child->pid <= 0) { crm_trace("Client %s not running", child->name); return TRUE; } errno = 0; if (kill(child->pid, signal) == 0) { crm_notice("Stopping %s "CRM_XS" sent signal %d to process %lld", child->name, signal, (long long) child->pid); } else { crm_err("Could not stop %s (process %lld) with signal %d: %s", child->name, (long long) child->pid, signal, strerror(errno)); } return TRUE; } static char *opts_default[] = { NULL, NULL }; static char *opts_vgrind[] = { NULL, NULL, NULL, NULL, NULL }; /* TODO once libqb is taught to juggle with IPC end-points carried over as bare file descriptor (https://github.com/ClusterLabs/libqb/issues/325) it shall hand over these descriptors here if/once they are successfully pre-opened in (presumably) child_liveness(), to avoid any remaining room for races */ static gboolean start_child(pcmk_child_t * child) { uid_t uid = 0; gid_t gid = 0; gboolean use_valgrind = FALSE; gboolean use_callgrind = FALSE; const char *devnull = "/dev/null"; const char *env_valgrind = getenv("PCMK_valgrind_enabled"); const char *env_callgrind = getenv("PCMK_callgrind_enabled"); child->active_before_startup = FALSE; if (child->command == NULL) { crm_info("Nothing to do for child \"%s\"", child->name); return TRUE; } if (env_callgrind != NULL && crm_is_true(env_callgrind)) { use_callgrind = TRUE; use_valgrind = TRUE; } else if (env_callgrind != NULL && strstr(env_callgrind, child->name)) { use_callgrind = TRUE; use_valgrind = TRUE; } else if (env_valgrind != NULL && crm_is_true(env_valgrind)) { use_valgrind = TRUE; } else if (env_valgrind != NULL && strstr(env_valgrind, child->name)) { use_valgrind = TRUE; } if (use_valgrind && strlen(VALGRIND_BIN) == 0) { crm_warn("Cannot enable valgrind for %s:" " The location of the valgrind binary is unknown", child->name); use_valgrind = FALSE; } if (child->uid) { if (crm_user_lookup(child->uid, &uid, &gid) < 0) { crm_err("Invalid user (%s) for %s: not found", child->uid, child->name); return FALSE; } crm_info("Using uid=%u and group=%u for process %s", uid, gid, child->name); } child->pid = fork(); CRM_ASSERT(child->pid != -1); if (child->pid > 0) { /* parent */ mainloop_child_add(child->pid, 0, child->name, child, pcmk_child_exit); crm_info("Forked child %lld for process %s%s", (long long) child->pid, child->name, use_valgrind ? " (valgrind enabled: " VALGRIND_BIN ")" : ""); update_node_processes(local_nodeid, NULL, get_process_list()); return TRUE; } else { /* Start a new session */ (void)setsid(); /* Setup the two alternate arg arrays */ opts_vgrind[0] = strdup(VALGRIND_BIN); if (use_callgrind) { opts_vgrind[1] = strdup("--tool=callgrind"); opts_vgrind[2] = strdup("--callgrind-out-file=" CRM_STATE_DIR "/callgrind.out.%p"); opts_vgrind[3] = strdup(child->command); opts_vgrind[4] = NULL; } else { opts_vgrind[1] = strdup(child->command); opts_vgrind[2] = NULL; opts_vgrind[3] = NULL; opts_vgrind[4] = NULL; } opts_default[0] = strdup(child->command); if(gid) { // Whether we need root group access to talk to cluster layer bool need_root_group = TRUE; if (is_corosync_cluster()) { /* Corosync clusters can drop root group access, because we set * uidgid.gid.${gid}=1 via CMAP, which allows these processes to * connect to corosync. */ need_root_group = FALSE; } // Drop root group access if not needed if (!need_root_group && (setgid(gid) < 0)) { crm_perror(LOG_ERR, "Could not set group to %d", gid); } /* Initialize supplementary groups to only those always granted to * the user, plus haclient (so we can access IPC). */ if (initgroups(child->uid, gid) < 0) { crm_err("Cannot initialize groups for %s: %s (%d)", child->uid, pcmk_strerror(errno), errno); } } if (uid && setuid(uid) < 0) { crm_perror(LOG_ERR, "Could not set user to %d (%s)", uid, child->uid); } pcmk__close_fds_in_child(true); (void)open(devnull, O_RDONLY); /* Stdin: fd 0 */ (void)open(devnull, O_WRONLY); /* Stdout: fd 1 */ (void)open(devnull, O_WRONLY); /* Stderr: fd 2 */ if (use_valgrind) { (void)execvp(VALGRIND_BIN, opts_vgrind); } else { (void)execvp(child->command, opts_default); } crm_perror(LOG_ERR, "FATAL: Cannot exec %s", child->command); crm_exit(CRM_EX_FATAL); } return TRUE; /* never reached */ } static gboolean escalate_shutdown(gpointer data) { pcmk_child_t *child = data; if (child->pid == PCMK__SPECIAL_PID) { pcmk_process_exit(child); } else if (child->pid != 0) { /* Use SIGSEGV instead of SIGKILL to create a core so we can see what it was up to */ crm_err("Child %s not terminating in a timely manner, forcing", child->name); stop_child(child, SIGSEGV); } return FALSE; } #define SHUTDOWN_ESCALATION_PERIOD 180000 /* 3m */ static gboolean pcmk_shutdown_worker(gpointer user_data) { static int phase = 0; static time_t next_log = 0; static int max = SIZEOF(pcmk_children); int lpc = 0; if (phase == 0) { crm_notice("Shutting down Pacemaker"); phase = max; } for (; phase > 0; phase--) { /* Don't stop anything with start_seq < 1 */ for (lpc = max - 1; lpc >= 0; lpc--) { pcmk_child_t *child = &(pcmk_children[lpc]); if (phase != child->start_seq) { continue; } if (child->pid != 0) { time_t now = time(NULL); if (child->respawn) { if (child->pid == PCMK__SPECIAL_PID) { crm_warn("The process behind %s IPC cannot be" " terminated, so either wait the graceful" " period of %ld s for its native termination" " if it vitally depends on some other daemons" " going down in a controlled way already," " or locate and kill the correct %s process" " on your own; set PCMK_fail_fast=1 to avoid" " this altogether next time around", child->name, (long) SHUTDOWN_ESCALATION_PERIOD, child->command); } next_log = now + 30; child->respawn = FALSE; stop_child(child, SIGTERM); if (phase < pcmk_children[PCMK_CHILD_CONTROLD].start_seq) { g_timeout_add(SHUTDOWN_ESCALATION_PERIOD, escalate_shutdown, child); } } else if (now >= next_log) { next_log = now + 30; crm_notice("Still waiting for %s to terminate " CRM_XS " pid=%lld seq=%d", child->name, (long long) child->pid, child->start_seq); } return TRUE; } /* cleanup */ crm_debug("%s confirmed stopped", child->name); child->pid = 0; } } /* send_cluster_id(); */ crm_notice("Shutdown complete"); { const char *delay = pcmk__env_option("shutdown_delay"); if(delay) { sync(); sleep(crm_get_msec(delay) / 1000); } } g_main_loop_quit(mainloop); if (fatal_error) { crm_notice("Shutting down and staying down after fatal error"); pcmk_exit_with_cluster(CRM_EX_FATAL); } return TRUE; } static void pcmk_ignore(int nsig) { crm_info("Ignoring signal %s (%d)", strsignal(nsig), nsig); } static void pcmk_sigquit(int nsig) { pcmk_panic(__FUNCTION__); } void pcmk_shutdown(int nsig) { if (shutdown_trigger == NULL) { shutdown_trigger = mainloop_add_trigger(G_PRIORITY_HIGH, pcmk_shutdown_worker, NULL); } mainloop_set_trigger(shutdown_trigger); } static int32_t pcmk_ipc_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) { crm_trace("Connection %p", c); if (pcmk__new_client(c, uid, gid) == NULL) { return -EIO; } return 0; } /* Exit code means? */ static int32_t pcmk_ipc_dispatch(qb_ipcs_connection_t * qbc, void *data, size_t size) { uint32_t id = 0; uint32_t flags = 0; const char *task = NULL; pcmk__client_t *c = pcmk__find_client(qbc); xmlNode *msg = pcmk__client_data2xml(c, data, &id, &flags); pcmk__ipc_send_ack(c, id, flags, "ack"); if (msg == NULL) { return 0; } task = crm_element_value(msg, F_CRM_TASK); if (crm_str_eq(task, CRM_OP_QUIT, TRUE)) { /* Time to quit */ crm_notice("Shutting down in response to ticket %s (%s)", crm_element_value(msg, F_CRM_REFERENCE), crm_element_value(msg, F_CRM_ORIGIN)); pcmk_shutdown(15); } else if (crm_str_eq(task, CRM_OP_RM_NODE_CACHE, TRUE)) { /* Send to everyone */ struct iovec *iov; int id = 0; const char *name = NULL; crm_element_value_int(msg, XML_ATTR_ID, &id); name = crm_element_value(msg, XML_ATTR_UNAME); crm_notice("Instructing peers to remove references to node %s/%u", name, id); iov = calloc(1, sizeof(struct iovec)); iov->iov_base = dump_xml_unformatted(msg); iov->iov_len = 1 + strlen(iov->iov_base); send_cpg_iov(iov); } else { update_process_clients(c); } free_xml(msg); return 0; } /* Error code means? */ static int32_t pcmk_ipc_closed(qb_ipcs_connection_t * c) { pcmk__client_t *client = pcmk__find_client(c); if (client == NULL) { return 0; } crm_trace("Connection %p", c); pcmk__free_client(client); return 0; } static void pcmk_ipc_destroy(qb_ipcs_connection_t * c) { crm_trace("Connection %p", c); pcmk_ipc_closed(c); } struct qb_ipcs_service_handlers mcp_ipc_callbacks = { .connection_accept = pcmk_ipc_accept, .connection_created = NULL, .msg_process = pcmk_ipc_dispatch, .connection_closed = pcmk_ipc_closed, .connection_destroyed = pcmk_ipc_destroy }; static void send_xml_to_client(gpointer key, gpointer value, gpointer user_data) { pcmk__ipc_send_xml((pcmk__client_t *) value, 0, (xmlNode *) user_data, crm_ipc_server_event); } /*! * \internal * \brief Send an XML message with process list of all known peers to client(s) * * \param[in] client Send message to this client, or all clients if NULL */ void update_process_clients(pcmk__client_t *client) { GHashTableIter iter; crm_node_t *node = NULL; xmlNode *update = create_xml_node(NULL, "nodes"); if (is_corosync_cluster()) { crm_xml_add_int(update, "quorate", pcmk_quorate); } g_hash_table_iter_init(&iter, crm_peer_cache); while (g_hash_table_iter_next(&iter, NULL, (gpointer *) & node)) { xmlNode *xml = create_xml_node(update, "node"); crm_xml_add_int(xml, "id", node->id); crm_xml_add(xml, "uname", node->uname); crm_xml_add(xml, "state", node->state); crm_xml_add_int(xml, "processes", node->processes); } if(client) { crm_trace("Sending process list to client %s", client->id); send_xml_to_client(NULL, client, update); } else { crm_trace("Sending process list to %d clients", pcmk__ipc_client_count()); pcmk__foreach_ipc_client(send_xml_to_client, update); } free_xml(update); } /*! * \internal * \brief Send a CPG message with local node's process list to all peers */ static void update_process_peers(void) { /* Do nothing for corosync-2 based clusters */ struct iovec *iov = calloc(1, sizeof(struct iovec)); CRM_ASSERT(iov); if (local_name) { iov->iov_base = crm_strdup_printf("", local_name, get_process_list()); } else { iov->iov_base = crm_strdup_printf("", get_process_list()); } iov->iov_len = strlen(iov->iov_base) + 1; crm_trace("Sending %s", (char*) iov->iov_base); send_cpg_iov(iov); } /*! * \internal * \brief Update a node's process list, notifying clients and peers if needed * * \param[in] id Node ID of affected node * \param[in] uname Uname of affected node * \param[in] procs Affected node's process list mask * * \return TRUE if the process list changed, FALSE otherwise */ static gboolean update_node_processes(uint32_t id, const char *uname, uint32_t procs) { gboolean changed = FALSE; crm_node_t *node = crm_get_peer(id, uname); if (procs != 0) { if (procs != node->processes) { crm_debug("Node %s now has process list: %.32x (was %.32x)", node->uname, procs, node->processes); node->processes = procs; changed = TRUE; /* If local node's processes have changed, notify clients/peers */ if (id == local_nodeid) { update_process_clients(NULL); update_process_peers(); } } else { crm_trace("Node %s still has process list: %.32x", node->uname, procs); } } return changed; } -/* *INDENT-OFF* */ -static struct crm_option long_options[] = { - /* Top-level Options */ - {"help", 0, 0, '?', "\tThis text"}, - {"version", 0, 0, '$', "\tVersion information" }, - {"verbose", 0, 0, 'V', "\tIncrease debug output"}, - {"shutdown", 0, 0, 'S', "\tInstruct Pacemaker to shutdown on this machine"}, - {"features", 0, 0, 'F', "\tDisplay the full version and list of features Pacemaker was built with"}, - - {"-spacer-", 1, 0, '-', "\nAdditional Options:"}, - {"foreground", 0, 0, 'f', "\t(Ignored) Pacemaker always runs in the foreground"}, - {"pid-file", 1, 0, 'p', "\t(Ignored) Daemon pid file location"}, - {"standby", 0, 0, 's', "\tStart node in standby state"}, - - {NULL, 0, 0, 0} +static pcmk__cli_option_t long_options[] = { + // long option, argument type, storage, short option, description, flags + { + "help", no_argument, NULL, '?', + "\tThis text", pcmk__option_default + }, + { + "version", no_argument, NULL, '$', + "\tVersion information", pcmk__option_default + }, + { + "verbose", no_argument, NULL, 'V', + "\tIncrease debug output", pcmk__option_default + }, + { + "shutdown", no_argument, NULL, 'S', + "\tInstruct Pacemaker to shutdown on this machine", pcmk__option_default + }, + { + "features", no_argument, NULL, 'F', + "\tDisplay full version and list of features Pacemaker was built with", + pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nAdditional Options:", pcmk__option_default + }, + { + "foreground", no_argument, NULL, 'f', + "\t(Ignored) Pacemaker always runs in the foreground", + pcmk__option_default + }, + { + "pid-file", required_argument, NULL, 'p', + "\t(Ignored) Daemon pid file location", pcmk__option_default + }, + { + "standby", no_argument, NULL, 's', + "\tStart node in standby state", pcmk__option_default + }, + { 0, 0, 0, 0 } }; -/* *INDENT-ON* */ static void mcp_chown(const char *path, uid_t uid, gid_t gid) { int rc = chown(path, uid, gid); if (rc < 0) { crm_warn("Cannot change the ownership of %s to user %s and gid %d: %s", path, CRM_DAEMON_USER, gid, pcmk_strerror(errno)); } } /*! * \internal * \brief Check the liveness of the child based on IPC name and PID if tracked * * \param[inout] child Child tracked data * * \return Standard Pacemaker return code * * \note Return codes of particular interest include pcmk_rc_ipc_unresponsive * indicating that no trace of IPC liveness was detected, * pcmk_rc_ipc_unauthorized indicating that the IPC endpoint is blocked by * an unauthorized process, and pcmk_rc_ipc_pid_only indicating that * the child is up by PID but not IPC end-point (possibly starting). * \note This function doesn't modify any of \p child members but \c pid, * and is not actively toying with processes as such but invoking * \c stop_child in one particular case (there's for some reason * a different authentic holder of the IPC end-point). */ static int child_liveness(pcmk_child_t *child) { uid_t cl_uid = 0; gid_t cl_gid = 0; const uid_t root_uid = 0; const gid_t root_gid = 0; const uid_t *ref_uid; const gid_t *ref_gid; int rc = pcmk_rc_ipc_unresponsive; pid_t ipc_pid = 0; if (child->endpoint == NULL && (child->pid <= 0 || child->pid == PCMK__SPECIAL_PID)) { crm_err("Cannot track child %s for missing both API end-point and PID", child->name); rc = EINVAL; // Misuse of function when child is not trackable } else if (child->endpoint != NULL) { int legacy_rc = pcmk_ok; if (child->uid == NULL) { ref_uid = &root_uid; ref_gid = &root_gid; } else { ref_uid = &cl_uid; ref_gid = &cl_gid; legacy_rc = pcmk_daemon_user(&cl_uid, &cl_gid); } if (legacy_rc < 0) { rc = pcmk_legacy2rc(legacy_rc); crm_err("Could not find user and group IDs for user %s: %s " CRM_XS " rc=%d", CRM_DAEMON_USER, pcmk_rc_str(rc), rc); } else { rc = pcmk__ipc_is_authentic_process_active(child->endpoint, *ref_uid, *ref_gid, &ipc_pid); if ((rc == pcmk_rc_ok) || (rc == pcmk_rc_ipc_unresponsive)) { if (child->pid <= 0) { /* If rc is pcmk_rc_ok, ipc_pid is nonzero and this * initializes a new child. If rc is * pcmk_rc_ipc_unresponsive, ipc_pid is zero, and we will * investigate further. */ child->pid = ipc_pid; } else if ((ipc_pid != 0) && (child->pid != ipc_pid)) { /* An unexpected (but authorized) process is responding to * IPC. Investigate further. */ rc = pcmk_rc_ipc_unresponsive; } } } } if (rc == pcmk_rc_ipc_unresponsive) { /* If we get here, a child without IPC is being tracked, no IPC liveness * has been detected, or IPC liveness has been detected with an * unexpected (but authorized) process. This is safe on FreeBSD since * the only change possible from a proper child's PID into "special" PID * of 1 behind more loosely related process. */ int ret = pcmk__pid_active(child->pid, child->name); if (ipc_pid && ((ret != pcmk_rc_ok) || ipc_pid == PCMK__SPECIAL_PID || (pcmk__pid_active(ipc_pid, child->name) == pcmk_rc_ok))) { /* An unexpected (but authorized) process was detected at the IPC * endpoint, and either it is active, or the child we're tracking is * not. */ if (ret == pcmk_rc_ok) { /* The child we're tracking is active. Kill it, and adopt the * detected process. This assumes that our children don't fork * (thus getting a different PID owning the IPC), but rather the * tracking got out of sync because of some means external to * Pacemaker, and adopting the detected process is better than * killing it and possibly having to spawn a new child. */ /* not possessing IPC, afterall (what about corosync CPG?) */ stop_child(child, SIGKILL); } rc = pcmk_rc_ok; child->pid = ipc_pid; } else if (ret == pcmk_rc_ok) { // Our tracked child's PID was found active, but not its IPC rc = pcmk_rc_ipc_pid_only; } else if ((child->pid == 0) && (ret == EINVAL)) { // FreeBSD can return EINVAL rc = pcmk_rc_ipc_unresponsive; } else { switch (ret) { case EACCES: rc = pcmk_rc_ipc_unauthorized; break; case ESRCH: rc = pcmk_rc_ipc_unresponsive; break; default: rc = ret; break; } } } return rc; } static gboolean check_active_before_startup_processes(gpointer user_data) { int start_seq = 1, lpc = 0; static int max = SIZEOF(pcmk_children); gboolean keep_tracking = FALSE; for (start_seq = 1; start_seq < max; start_seq++) { for (lpc = 0; lpc < max; lpc++) { if (pcmk_children[lpc].active_before_startup == FALSE) { /* we are already tracking it as a child process. */ continue; } else if (start_seq != pcmk_children[lpc].start_seq) { continue; } else { int rc = child_liveness(&pcmk_children[lpc]); switch (rc) { case pcmk_rc_ok: break; case pcmk_rc_ipc_unresponsive: case pcmk_rc_ipc_pid_only: // This case: it was previously OK if (pcmk_children[lpc].respawn == TRUE) { crm_err("%s[%lld] terminated%s", pcmk_children[lpc].name, (long long) PCMK__SPECIAL_PID_AS_0(pcmk_children[lpc].pid), (rc == pcmk_rc_ipc_pid_only)? " as IPC server" : ""); } else { /* orderly shutdown */ crm_notice("%s[%lld] terminated%s", pcmk_children[lpc].name, (long long) PCMK__SPECIAL_PID_AS_0(pcmk_children[lpc].pid), (rc == pcmk_rc_ipc_pid_only)? " as IPC server" : ""); } pcmk_process_exit(&(pcmk_children[lpc])); continue; default: crm_exit(CRM_EX_FATAL); break; /* static analysis/noreturn */ } } /* at least one of the processes found at startup * is still going, so keep this recurring timer around */ keep_tracking = TRUE; } } global_keep_tracking = keep_tracking; return keep_tracking; } /*! * \internal * \brief Initial one-off check of the pre-existing "child" processes * * With "child" process, we mean the subdaemon that defines an API end-point * (all of them do as of the comment) -- the possible complement is skipped * as it is deemed it has no such shared resources to cause conflicts about, * hence it can presumably be started anew without hesitation. * If that won't hold true in the future, the concept of a shared resource * will have to be generalized beyond the API end-point. * * For boundary cases that the "child" is still starting (IPC end-point is yet * to be witnessed), or more rarely (practically FreeBSD only), when there's * a pre-existing "untrackable" authentic process, we give the situation some * time to possibly unfold in the right direction, meaning that said socket * will appear or the unattainable process will disappear per the observable * IPC, respectively. * * \return Standard Pacemaker return code * * \note Since this gets run at the very start, \c respawn_count fields * for particular children get temporarily overloaded with "rounds * of waiting" tracking, restored once we are about to finish with * success (i.e. returning value >=0) and will remain unrestored * otherwise. One way to suppress liveness detection logic for * particular child is to set the said value to a negative number. */ #define WAIT_TRIES 4 /* together with interleaved sleeps, worst case ~ 1s */ static int find_and_track_existing_processes(void) { bool tracking = false; bool wait_in_progress; int rc; size_t i, rounds; for (rounds = 1; rounds <= WAIT_TRIES; rounds++) { wait_in_progress = false; for (i = 0; i < SIZEOF(pcmk_children); i++) { if ((pcmk_children[i].endpoint == NULL) || (pcmk_children[i].respawn_count < 0)) { continue; } rc = child_liveness(&pcmk_children[i]); if (rc == pcmk_rc_ipc_unresponsive) { /* As a speculation, don't give up if there are more rounds to * come for other reasons, but don't artificially wait just * because of this, since we would preferably start ASAP. */ continue; } pcmk_children[i].respawn_count = rounds; switch (rc) { case pcmk_rc_ok: if (pcmk_children[i].pid == PCMK__SPECIAL_PID) { if (crm_is_true(getenv("PCMK_fail_fast"))) { crm_crit("Cannot reliably track pre-existing" " authentic process behind %s IPC on this" " platform and PCMK_fail_fast requested", pcmk_children[i].endpoint); return EOPNOTSUPP; } else if (pcmk_children[i].respawn_count == WAIT_TRIES) { crm_notice("Assuming pre-existing authentic, though" " on this platform untrackable, process" " behind %s IPC is stable (was in %d" " previous samples) so rather than" " bailing out (PCMK_fail_fast not" " requested), we just switch to a less" " optimal IPC liveness monitoring" " (not very suitable for heavy load)", pcmk_children[i].name, WAIT_TRIES - 1); crm_warn("The process behind %s IPC cannot be" " terminated, so the overall shutdown" " will get delayed implicitly (%ld s)," " which serves as a graceful period for" " its native termination if it vitally" " depends on some other daemons going" " down in a controlled way already", pcmk_children[i].name, (long) SHUTDOWN_ESCALATION_PERIOD); } else { wait_in_progress = true; crm_warn("Cannot reliably track pre-existing" " authentic process behind %s IPC on this" " platform, can still disappear in %d" " attempt(s)", pcmk_children[i].endpoint, WAIT_TRIES - pcmk_children[i].respawn_count); continue; } } crm_notice("Tracking existing %s process (pid=%lld)", pcmk_children[i].name, (long long) PCMK__SPECIAL_PID_AS_0( pcmk_children[i].pid)); pcmk_children[i].respawn_count = -1; /* 0~keep watching */ pcmk_children[i].active_before_startup = TRUE; tracking = true; break; case pcmk_rc_ipc_pid_only: if (pcmk_children[i].respawn_count == WAIT_TRIES) { crm_crit("%s IPC end-point for existing authentic" " process %lld did not (re)appear", pcmk_children[i].endpoint, (long long) PCMK__SPECIAL_PID_AS_0( pcmk_children[i].pid)); return rc; } wait_in_progress = true; crm_warn("Cannot find %s IPC end-point for existing" " authentic process %lld, can still (re)appear" " in %d attempts (?)", pcmk_children[i].endpoint, (long long) PCMK__SPECIAL_PID_AS_0( pcmk_children[i].pid), WAIT_TRIES - pcmk_children[i].respawn_count); continue; default: crm_crit("Checked liveness of %s: %s " CRM_XS " rc=%d", pcmk_children[i].name, pcmk_rc_str(rc), rc); return rc; } } if (!wait_in_progress) { break; } (void) poll(NULL, 0, 250); /* a bit for changes to possibly happen */ } for (i = 0; i < SIZEOF(pcmk_children); i++) { pcmk_children[i].respawn_count = 0; /* restore pristine state */ } if (tracking) { g_timeout_add_seconds(PCMK_PROCESS_CHECK_INTERVAL, check_active_before_startup_processes, NULL); } return pcmk_rc_ok; } static void init_children_processes(void) { int start_seq = 1, lpc = 0; static int max = SIZEOF(pcmk_children); /* start any children that have not been detected */ for (start_seq = 1; start_seq < max; start_seq++) { /* don't start anything with start_seq < 1 */ for (lpc = 0; lpc < max; lpc++) { if (pcmk_children[lpc].pid != 0) { /* we are already tracking it */ continue; } if (start_seq == pcmk_children[lpc].start_seq) { start_child(&(pcmk_children[lpc])); } } } /* From this point on, any daemons being started will be due to * respawning rather than node start. * * This may be useful for the daemons to know */ setenv("PCMK_respawned", "true", 1); } static void mcp_cpg_destroy(gpointer user_data) { crm_crit("Lost connection to cluster layer, shutting down"); crm_exit(CRM_EX_DISCONNECT); } /*! * \internal * \brief Process a CPG message (process list or manual peer cache removal) * * \param[in] handle CPG connection (ignored) * \param[in] groupName CPG group name (ignored) * \param[in] nodeid ID of affected node * \param[in] pid Process ID (ignored) * \param[in] msg CPG XML message * \param[in] msg_len Length of msg in bytes (ignored) */ static void mcp_cpg_deliver(cpg_handle_t handle, const struct cpg_name *groupName, uint32_t nodeid, uint32_t pid, void *msg, size_t msg_len) { xmlNode *xml = string2xml(msg); const char *task = crm_element_value(xml, F_CRM_TASK); crm_trace("Received CPG message (%s): %.200s", (task? task : "process list"), (char*)msg); if (task == NULL) { if (nodeid == local_nodeid) { crm_debug("Ignoring message with local node's process list"); } else { uint32_t procs = 0; const char *uname = crm_element_value(xml, "uname"); crm_element_value_int(xml, "proclist", (int *)&procs); if (update_node_processes(nodeid, uname, procs)) { update_process_clients(NULL); } } } else if (crm_str_eq(task, CRM_OP_RM_NODE_CACHE, TRUE)) { int id = 0; const char *name = NULL; crm_element_value_int(xml, XML_ATTR_ID, &id); name = crm_element_value(xml, XML_ATTR_UNAME); reap_crm_member(id, name); } if (xml != NULL) { free_xml(xml); } } static void mcp_cpg_membership(cpg_handle_t handle, const struct cpg_name *groupName, const struct cpg_address *member_list, size_t member_list_entries, const struct cpg_address *left_list, size_t left_list_entries, const struct cpg_address *joined_list, size_t joined_list_entries) { /* Update peer cache if needed */ pcmk_cpg_membership(handle, groupName, member_list, member_list_entries, left_list, left_list_entries, joined_list, joined_list_entries); /* Always broadcast our own presence after any membership change */ update_process_peers(); } static gboolean mcp_quorum_callback(unsigned long long seq, gboolean quorate) { pcmk_quorate = quorate; return TRUE; } static void mcp_quorum_destroy(gpointer user_data) { crm_info("connection lost"); } int main(int argc, char **argv) { int rc; int flag; int argerr = 0; int option_index = 0; gboolean shutdown = FALSE; uid_t pcmk_uid = 0; gid_t pcmk_gid = 0; struct rlimit cores; crm_ipc_t *old_instance = NULL; qb_ipcs_service_t *ipcs = NULL; static crm_cluster_t cluster; crm_log_preinit(NULL, argc, argv); - crm_set_options(NULL, "mode [options]", long_options, "Start/Stop Pacemaker\n"); + pcmk__set_cli_options(NULL, "[options]", long_options, + "primary Pacemaker daemon that launches and " + "monitors all subsidiary Pacemaker daemons"); mainloop_add_signal(SIGHUP, pcmk_ignore); mainloop_add_signal(SIGQUIT, pcmk_sigquit); while (1) { - flag = crm_get_option(argc, argv, &option_index); + flag = pcmk__next_cli_option(argc, argv, &option_index, NULL); if (flag == -1) break; switch (flag) { case 'V': crm_bump_log_level(argc, argv); break; case 'f': /* Legacy */ break; case 'p': pid_file = optarg; break; case 's': pcmk__set_env_option("node_start_state", "standby"); break; case '$': case '?': - crm_help(flag, CRM_EX_OK); + pcmk__cli_help(flag, CRM_EX_OK); break; case 'S': shutdown = TRUE; break; case 'F': printf("Pacemaker %s (Build: %s)\n Supporting v%s: %s\n", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURE_SET, CRM_FEATURES); crm_exit(CRM_EX_OK); default: printf("Argument code 0%o (%c) is not (?yet?) supported\n", flag, flag); ++argerr; break; } } if (optind < argc) { printf("non-option ARGV-elements: "); while (optind < argc) printf("%s ", argv[optind++]); printf("\n"); } if (argerr) { - crm_help('?', CRM_EX_USAGE); + pcmk__cli_help('?', CRM_EX_USAGE); } setenv("LC_ALL", "C", 1); pcmk__set_env_option("mcp", "true"); crm_log_init(NULL, LOG_INFO, TRUE, FALSE, argc, argv, FALSE); crm_debug("Checking for existing Pacemaker instance"); old_instance = crm_ipc_new(CRM_SYSTEM_MCP, 0); (void) crm_ipc_connect(old_instance); if (shutdown) { crm_debug("Shutting down existing Pacemaker instance by request"); while (crm_ipc_connected(old_instance)) { xmlNode *cmd = create_request(CRM_OP_QUIT, NULL, NULL, CRM_SYSTEM_MCP, CRM_SYSTEM_MCP, NULL); crm_debug("."); crm_ipc_send(old_instance, cmd, 0, 0, NULL); free_xml(cmd); sleep(2); } crm_ipc_close(old_instance); crm_ipc_destroy(old_instance); crm_exit(CRM_EX_OK); } else if (crm_ipc_connected(old_instance)) { crm_ipc_close(old_instance); crm_ipc_destroy(old_instance); crm_err("Aborting start-up because active Pacemaker instance found"); crm_exit(CRM_EX_FATAL); } crm_ipc_close(old_instance); crm_ipc_destroy(old_instance); if (mcp_read_config() == FALSE) { crm_notice("Could not obtain corosync config data, exiting"); crm_exit(CRM_EX_UNAVAILABLE); } // OCF shell functions and cluster-glue need facility under different name { const char *facility = pcmk__env_option("logfacility"); if (facility && safe_str_neq(facility, "none")) { setenv("HA_LOGFACILITY", facility, 1); } } crm_notice("Starting Pacemaker %s "CRM_XS" build=%s features:%s", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES); mainloop = g_main_loop_new(NULL, FALSE); rc = getrlimit(RLIMIT_CORE, &cores); if (rc < 0) { crm_perror(LOG_ERR, "Cannot determine current maximum core size."); } else { if (cores.rlim_max == 0 && geteuid() == 0) { cores.rlim_max = RLIM_INFINITY; } else { crm_info("Maximum core file size is: %lu", (unsigned long)cores.rlim_max); } cores.rlim_cur = cores.rlim_max; rc = setrlimit(RLIMIT_CORE, &cores); if (rc < 0) { crm_perror(LOG_ERR, "Core file generation will remain disabled." " Core files are an important diagnostic tool, so" " please consider enabling them by default."); } } if (pcmk_daemon_user(&pcmk_uid, &pcmk_gid) < 0) { crm_err("Cluster user %s does not exist, aborting Pacemaker startup", CRM_DAEMON_USER); crm_exit(CRM_EX_NOUSER); } // Used by some resource agents if ((mkdir(CRM_STATE_DIR, 0750) < 0) && (errno != EEXIST)) { crm_warn("Could not create " CRM_STATE_DIR ": %s", pcmk_strerror(errno)); } else { mcp_chown(CRM_STATE_DIR, pcmk_uid, pcmk_gid); } /* Used to store core/blackbox/scheduler/cib files in */ crm_build_path(CRM_PACEMAKER_DIR, 0750); mcp_chown(CRM_PACEMAKER_DIR, pcmk_uid, pcmk_gid); /* Used to store core files in */ crm_build_path(CRM_CORE_DIR, 0750); mcp_chown(CRM_CORE_DIR, pcmk_uid, pcmk_gid); /* Used to store blackbox dumps in */ crm_build_path(CRM_BLACKBOX_DIR, 0750); mcp_chown(CRM_BLACKBOX_DIR, pcmk_uid, pcmk_gid); // Used to store scheduler inputs in crm_build_path(PE_STATE_DIR, 0750); mcp_chown(PE_STATE_DIR, pcmk_uid, pcmk_gid); /* Used to store the cluster configuration */ crm_build_path(CRM_CONFIG_DIR, 0750); mcp_chown(CRM_CONFIG_DIR, pcmk_uid, pcmk_gid); // Don't build CRM_RSCTMP_DIR, pacemaker-execd will do it ipcs = mainloop_add_ipc_server(CRM_SYSTEM_MCP, QB_IPC_NATIVE, &mcp_ipc_callbacks); if (ipcs == NULL) { crm_err("Couldn't start IPC server"); crm_exit(CRM_EX_OSERR); } /* Allows us to block shutdown */ if (cluster_connect_cfg(&local_nodeid) == FALSE) { crm_err("Couldn't connect to Corosync's CFG service"); crm_exit(CRM_EX_PROTOCOL); } if(pcmk_locate_sbd() > 0) { setenv("PCMK_watchdog", "true", 1); } else { setenv("PCMK_watchdog", "false", 1); } switch (find_and_track_existing_processes()) { case pcmk_rc_ok: break; case pcmk_rc_ipc_unauthorized: crm_exit(CRM_EX_CANTCREAT); default: crm_exit(CRM_EX_FATAL); }; cluster.destroy = mcp_cpg_destroy; cluster.cpg.cpg_deliver_fn = mcp_cpg_deliver; cluster.cpg.cpg_confchg_fn = mcp_cpg_membership; crm_set_autoreap(FALSE); rc = pcmk_ok; if (cluster_connect_cpg(&cluster) == FALSE) { crm_err("Couldn't connect to Corosync's CPG service"); rc = -ENOPROTOOPT; } else if (cluster_connect_quorum(mcp_quorum_callback, mcp_quorum_destroy) == FALSE) { rc = -ENOTCONN; } else { local_name = get_local_node_name(); update_node_processes(local_nodeid, local_name, get_process_list()); mainloop_add_signal(SIGTERM, pcmk_shutdown); mainloop_add_signal(SIGINT, pcmk_shutdown); init_children_processes(); crm_notice("Pacemaker daemon successfully started and accepting connections"); g_main_loop_run(mainloop); } if (ipcs) { crm_trace("Closing IPC server"); mainloop_del_ipc_server(ipcs); ipcs = NULL; } g_main_loop_unref(mainloop); cluster_disconnect_cpg(&cluster); cluster_disconnect_cfg(); crm_exit(crm_errno2exit(rc)); } diff --git a/daemons/schedulerd/pacemaker-schedulerd.c b/daemons/schedulerd/pacemaker-schedulerd.c index 92f5eaa0b5..851ade04e8 100644 --- a/daemons/schedulerd/pacemaker-schedulerd.c +++ b/daemons/schedulerd/pacemaker-schedulerd.c @@ -1,354 +1,358 @@ /* * Copyright 2004-2020 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 #define OPTARGS "hVc" static GMainLoop *mainloop = NULL; static qb_ipcs_service_t *ipcs = NULL; static pe_working_set_t *sched_data_set = NULL; #define get_series() was_processing_error?1:was_processing_warning?2:3 typedef struct series_s { const char *name; const char *param; int wrap; } series_t; series_t series[] = { {"pe-unknown", "_do_not_match_anything_", -1}, {"pe-error", "pe-error-series-max", -1}, {"pe-warn", "pe-warn-series-max", 200}, {"pe-input", "pe-input-series-max", 400}, }; void pengine_shutdown(int nsig); static gboolean process_pe_message(xmlNode *msg, xmlNode *xml_data, pcmk__client_t *sender) { static char *last_digest = NULL; static char *filename = NULL; const char *sys_to = crm_element_value(msg, F_CRM_SYS_TO); const char *op = crm_element_value(msg, F_CRM_TASK); const char *ref = crm_element_value(msg, F_CRM_REFERENCE); crm_trace("Processing %s op (ref=%s)...", op, ref); if (op == NULL) { /* error */ } else if (strcasecmp(op, CRM_OP_HELLO) == 0) { /* ignore */ } else if (safe_str_eq(crm_element_value(msg, F_CRM_MSG_TYPE), XML_ATTR_RESPONSE)) { /* ignore */ } else if (sys_to == NULL || strcasecmp(sys_to, CRM_SYSTEM_PENGINE) != 0) { crm_trace("Bad sys-to %s", crm_str(sys_to)); return FALSE; } else if (strcasecmp(op, CRM_OP_PECALC) == 0) { unsigned int seq; int series_id = 0; int series_wrap = 0; char *digest = NULL; const char *value = NULL; time_t execution_date = time(NULL); xmlNode *converted = NULL; xmlNode *reply = NULL; gboolean is_repoke = FALSE; gboolean process = TRUE; crm_config_error = FALSE; crm_config_warning = FALSE; was_processing_error = FALSE; was_processing_warning = FALSE; if (sched_data_set == NULL) { sched_data_set = pe_new_working_set(); CRM_ASSERT(sched_data_set != NULL); set_bit(sched_data_set->flags, pe_flag_no_counts); set_bit(sched_data_set->flags, pe_flag_no_compat); } digest = calculate_xml_versioned_digest(xml_data, FALSE, FALSE, CRM_FEATURE_SET); converted = copy_xml(xml_data); if (cli_config_update(&converted, NULL, TRUE) == FALSE) { sched_data_set->graph = create_xml_node(NULL, XML_TAG_GRAPH); crm_xml_add_int(sched_data_set->graph, "transition_id", 0); crm_xml_add_int(sched_data_set->graph, "cluster-delay", 0); process = FALSE; free(digest); } else if (safe_str_eq(digest, last_digest)) { crm_info("Input has not changed since last time, not saving to disk"); is_repoke = TRUE; free(digest); } else { free(last_digest); last_digest = digest; } if (process) { pcmk__schedule_actions(sched_data_set, converted, NULL); } series_id = get_series(); series_wrap = series[series_id].wrap; value = pe_pref(sched_data_set->config_hash, series[series_id].param); if (value != NULL) { series_wrap = (int) crm_parse_ll(value, NULL); if (errno != 0) { series_wrap = series[series_id].wrap; } } else { crm_config_warn("No value specified for cluster" " preference: %s", series[series_id].param); } if (pcmk__read_series_sequence(PE_STATE_DIR, series[series_id].name, &seq) != pcmk_rc_ok) { // @TODO maybe handle errors better ... seq = 0; } crm_trace("Series %s: wrap=%d, seq=%u, pref=%s", series[series_id].name, series_wrap, seq, value); sched_data_set->input = NULL; reply = create_reply(msg, sched_data_set->graph); CRM_ASSERT(reply != NULL); if (is_repoke == FALSE) { free(filename); filename = pcmk__series_filename(PE_STATE_DIR, series[series_id].name, seq, true); } crm_xml_add(reply, F_CRM_TGRAPH_INPUT, filename); crm_xml_add_int(reply, "graph-errors", was_processing_error); crm_xml_add_int(reply, "graph-warnings", was_processing_warning); crm_xml_add_int(reply, "config-errors", crm_config_error); crm_xml_add_int(reply, "config-warnings", crm_config_warning); if (pcmk__ipc_send_xml(sender, 0, reply, crm_ipc_server_event) != pcmk_rc_ok) { int graph_file_fd = 0; char *graph_file = NULL; umask(S_IWGRP | S_IWOTH | S_IROTH); graph_file = crm_strdup_printf("%s/pengine.graph.XXXXXX", PE_STATE_DIR); graph_file_fd = mkstemp(graph_file); crm_err("Couldn't send transition graph to peer, writing to %s instead", graph_file); crm_xml_add(reply, F_CRM_TGRAPH, graph_file); write_xml_fd(sched_data_set->graph, graph_file, graph_file_fd, FALSE); free(graph_file); free_xml(first_named_child(reply, F_CRM_DATA)); CRM_ASSERT(pcmk__ipc_send_xml(sender, 0, reply, crm_ipc_server_event) == pcmk_rc_ok); } free_xml(reply); pe_reset_working_set(sched_data_set); pcmk__log_transition_summary(filename); if (is_repoke == FALSE && series_wrap != 0) { unlink(filename); crm_xml_add_ll(xml_data, "execution-date", (long long) execution_date); write_xml_file(xml_data, filename, TRUE); pcmk__write_series_sequence(PE_STATE_DIR, series[series_id].name, ++seq, series_wrap); } else { crm_trace("Not writing out %s: %d & %d", filename, is_repoke, series_wrap); } free_xml(converted); } return TRUE; } static int32_t pe_ipc_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) { crm_trace("Connection %p", c); if (pcmk__new_client(c, uid, gid) == NULL) { return -EIO; } return 0; } gboolean process_pe_message(xmlNode *msg, xmlNode *xml_data, pcmk__client_t *sender); static int32_t pe_ipc_dispatch(qb_ipcs_connection_t * qbc, void *data, size_t size) { uint32_t id = 0; uint32_t flags = 0; pcmk__client_t *c = pcmk__find_client(qbc); xmlNode *msg = pcmk__client_data2xml(c, data, &id, &flags); pcmk__ipc_send_ack(c, id, flags, "ack"); if (msg != NULL) { xmlNode *data_xml = get_message_xml(msg, F_CRM_DATA); process_pe_message(msg, data_xml, c); free_xml(msg); } return 0; } /* Error code means? */ static int32_t pe_ipc_closed(qb_ipcs_connection_t * c) { pcmk__client_t *client = pcmk__find_client(c); if (client == NULL) { return 0; } crm_trace("Connection %p", c); pcmk__free_client(client); return 0; } static void pe_ipc_destroy(qb_ipcs_connection_t * c) { crm_trace("Connection %p", c); pe_ipc_closed(c); } struct qb_ipcs_service_handlers ipc_callbacks = { .connection_accept = pe_ipc_accept, .connection_created = NULL, .msg_process = pe_ipc_dispatch, .connection_closed = pe_ipc_closed, .connection_destroyed = pe_ipc_destroy }; -/* *INDENT-OFF* */ -static struct crm_option long_options[] = { - /* Top-level Options */ - {"help", 0, 0, '?', "\tThis text"}, - {"verbose", 0, 0, 'V', "\tIncrease debug output"}, - - {0, 0, 0, 0} +static pcmk__cli_option_t long_options[] = { + // long option, argument type, storage, short option, description, flags + { + "help", no_argument, NULL, '?', + "\tThis text", pcmk__option_default + }, + { + "verbose", no_argument, NULL, 'V', + "\tIncrease debug output", pcmk__option_default + }, + { 0, 0, 0, 0 } }; -/* *INDENT-ON* */ int main(int argc, char **argv) { int flag; int index = 0; int argerr = 0; crm_log_preinit(NULL, argc, argv); - crm_set_options(NULL, "[options]", - long_options, "Daemon for calculating the cluster's response to events"); + pcmk__set_cli_options(NULL, "[options]", long_options, + "daemon for calculating a Pacemaker cluster's " + "response to events"); mainloop_add_signal(SIGTERM, pengine_shutdown); while (1) { - flag = crm_get_option(argc, argv, &index); + flag = pcmk__next_cli_option(argc, argv, &index, NULL); if (flag == -1) break; switch (flag) { case 'V': crm_bump_log_level(argc, argv); break; case 'h': /* Help message */ - crm_help('?', CRM_EX_OK); + pcmk__cli_help('?', CRM_EX_OK); break; default: ++argerr; break; } } if (argc - optind == 1 && safe_str_eq("metadata", argv[optind])) { pe_metadata(); return CRM_EX_OK; } if (optind > argc) { ++argerr; } if (argerr) { - crm_help('?', CRM_EX_USAGE); + pcmk__cli_help('?', CRM_EX_USAGE); } crm_log_init(NULL, LOG_INFO, TRUE, FALSE, argc, argv, FALSE); crm_notice("Starting Pacemaker scheduler"); if (pcmk__daemon_can_write(PE_STATE_DIR, NULL) == FALSE) { crm_err("Terminating due to bad permissions on " PE_STATE_DIR); fprintf(stderr, "ERROR: Bad permissions on " PE_STATE_DIR " (see logs for details)\n"); fflush(stderr); return CRM_EX_FATAL; } ipcs = mainloop_add_ipc_server(CRM_SYSTEM_PENGINE, QB_IPC_SHM, &ipc_callbacks); if (ipcs == NULL) { crm_err("Failed to create IPC server: shutting down and inhibiting respawn"); crm_exit(CRM_EX_FATAL); } /* Create the mainloop and run it... */ mainloop = g_main_loop_new(NULL, FALSE); crm_notice("Pacemaker scheduler successfully started and accepting connections"); g_main_loop_run(mainloop); pe_free_working_set(sched_data_set); crm_info("Exiting %s", crm_system_name); crm_exit(CRM_EX_OK); } void pengine_shutdown(int nsig) { mainloop_del_ipc_server(ipcs); pe_free_working_set(sched_data_set); crm_exit(CRM_EX_OK); } diff --git a/include/crm/common/Makefile.am b/include/crm/common/Makefile.am index 6198cae63c..652c290748 100644 --- a/include/crm/common/Makefile.am +++ b/include/crm/common/Makefile.am @@ -1,19 +1,19 @@ # # Copyright 2004-2020 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. # MAINTAINERCLEANFILES = Makefile.in headerdir=$(pkgincludedir)/crm/common header_HEADERS = xml.h ipc.h util.h iso8601.h mainloop.h logging.h results.h \ nvpair.h noinst_HEADERS = ipcs_internal.h internal.h alerts_internal.h \ iso8601_internal.h remote_internal.h xml_internal.h \ ipc_internal.h output.h cmdline_internal.h curses_internal.h \ - attrd_internal.h + attrd_internal.h options_internal.h diff --git a/include/crm/common/options_internal.h b/include/crm/common/options_internal.h new file mode 100644 index 0000000000..db54da4ce1 --- /dev/null +++ b/include/crm/common/options_internal.h @@ -0,0 +1,117 @@ +/* + * Copyright 2006-2020 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 OPTIONS_INTERNAL__H +# define OPTIONS_INTERNAL__H + +# include // HAVE_GETOPT, _Noreturn +# include // GHashTable +# include // bool + +/* + * Command-line option handling + * + * This will all eventually go away as everything is converted to use GOption + */ + +# ifdef HAVE_GETOPT_H +# include +# else +# define no_argument 0 +# define required_argument 1 +# endif + +enum pcmk__cli_option_flags { + pcmk__option_default = (1 << 0), + pcmk__option_hidden = (1 << 1), + pcmk__option_paragraph = (1 << 2), + pcmk__option_example = (1 << 3), +}; + +typedef struct pcmk__cli_option_s { + /* Fields from 'struct option' in getopt.h */ + /* name of long option */ + const char *name; + /* + * one of no_argument, required_argument, and optional_argument: + * whether option takes an argument + */ + int has_arg; + /* if not NULL, set *flag to val when option found */ + int *flag; + /* if flag not NULL, value to set *flag to; else return value */ + int val; + + /* Custom fields */ + const char *desc; + long flags; +} pcmk__cli_option_t; + +void pcmk__set_cli_options(const char *short_options, const char *usage, + pcmk__cli_option_t *long_options, + const char *app_desc); +int pcmk__next_cli_option(int argc, char **argv, int *index, + const char **longname); +_Noreturn void pcmk__cli_help(char cmd, crm_exit_t exit_code); +void pcmk__cli_option_cleanup(void); + + +/* + * Environment variable option handling + */ + +const char *pcmk__env_option(const char *option); +void pcmk__set_env_option(const char *option, const char *value); +bool pcmk__env_option_enabled(const char *daemon, const char *option); + + +/* + * Cluster option handling + */ + +typedef struct pcmk__cluster_option_s { + const char *name; + const char *alt_name; + const char *type; + const char *values; + const char *default_value; + + bool (*is_valid)(const char *); + + const char *description_short; + const char *description_long; + +} pcmk__cluster_option_t; + +const char *pcmk__cluster_option(GHashTable *options, + pcmk__cluster_option_t *option_list, int len, + const char *name); + +void pcmk__print_option_metadata(const char *name, const char *version, + const char *desc_short, const char *desc_long, + pcmk__cluster_option_t *option_list, int len); + +void pcmk__validate_cluster_options(GHashTable *options, + pcmk__cluster_option_t *option_list, + int len); + +bool pcmk__valid_interval_spec(const char *value); +bool pcmk__valid_boolean(const char *value); +bool pcmk__valid_number(const char *value); +bool pcmk__valid_positive_number(const char *value); +bool pcmk__valid_quorum(const char *value); +bool pcmk__valid_script(const char *value); +bool pcmk__valid_utilization(const char *value); + +// from watchdog.c +long pcmk__get_sbd_timeout(void); +long pcmk__auto_watchdog_timeout(void); +bool pcmk__valid_sbd_timeout(const char *value); + +#endif // OPTIONS_INTERNAL__H diff --git a/include/crm_internal.h b/include/crm_internal.h index 7eaff8c78a..b91bebb2a0 100644 --- a/include/crm_internal.h +++ b/include/crm_internal.h @@ -1,313 +1,235 @@ /* * Copyright 2006-2020 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 CRM_INTERNAL__H # define CRM_INTERNAL__H # include # include # include # include # include # include # include # include +# include # include /* This symbol allows us to deprecate public API and prevent internal code from * using it while still keeping it for backward compatibility. */ #define PCMK__NO_COMPAT /* Dynamic loading of libraries */ void *find_library_function(void **handle, const char *lib, const char *fn, int fatal); /* For ACLs */ char *pcmk__uid2username(uid_t uid); const char *crm_acl_get_set_user(xmlNode * request, const char *field, const char *peer_user); # if ENABLE_ACL # include static inline gboolean is_privileged(const char *user) { if (user == NULL) { return FALSE; } else if (strcmp(user, CRM_DAEMON_USER) == 0) { return TRUE; } else if (strcmp(user, "root") == 0) { return TRUE; } return FALSE; } # endif -/* CLI option processing*/ -# ifdef HAVE_GETOPT_H -# include -# else -# define no_argument 0 -# define required_argument 1 -# endif - -# define pcmk_option_default 0x00000 -# define pcmk_option_hidden 0x00001 -# define pcmk_option_paragraph 0x00002 -# define pcmk_option_example 0x00004 - -struct crm_option { - /* Fields from 'struct option' in getopt.h */ - /* name of long option */ - const char *name; - /* - * one of no_argument, required_argument, and optional_argument: - * whether option takes an argument - */ - int has_arg; - /* if not NULL, set *flag to val when option found */ - int *flag; - /* if flag not NULL, value to set *flag to; else return value */ - int val; - - /* Custom fields */ - const char *desc; - long flags; -}; - -void crm_set_options(const char *short_options, const char *usage, struct crm_option *long_options, - const char *app_desc); -int crm_get_option(int argc, char **argv, int *index); -int crm_get_option_long(int argc, char **argv, int *index, const char **longname); -_Noreturn void crm_help(char cmd, crm_exit_t exit_code); - -/* Cluster Option Processing */ -typedef struct pe_cluster_option_s { - const char *name; - const char *alt_name; - const char *type; - const char *values; - const char *default_value; - - gboolean(*is_valid) (const char *); - - const char *description_short; - const char *description_long; - -} pe_cluster_option; - -const char *cluster_option(GHashTable * options, gboolean(*validate) (const char *), - const char *name, const char *old_name, const char *def_value); - -const char *get_cluster_pref(GHashTable * options, pe_cluster_option * option_list, int len, - const char *name); - -void config_metadata(const char *name, const char *version, const char *desc_short, - const char *desc_long, pe_cluster_option * option_list, int len); - -void verify_all_options(GHashTable * options, pe_cluster_option * option_list, int len); -gboolean check_time(const char *value); -gboolean check_timer(const char *value); -gboolean check_boolean(const char *value); -gboolean check_number(const char *value); -gboolean check_positive_number(const char *value); -gboolean check_quorum(const char *value); -gboolean check_script(const char *value); -gboolean check_utilization(const char *value); -long crm_get_sbd_timeout(void); -long crm_auto_watchdog_timeout(void); -gboolean check_sbd_timeout(const char *value); -void crm_args_fini(void); - /* char2score */ extern int node_score_red; extern int node_score_green; extern int node_score_yellow; /* Assorted convenience functions */ void crm_make_daemon(const char *name, gboolean daemonize, const char *pidfile); // printf-style format to create operation ID from resource, action, interval #define CRM_OP_FMT "%s_%s_%u" static inline long long crm_clear_bit(const char *function, int line, const char *target, long long word, long long bit) { long long rc = (word & ~bit); if (rc == word) { /* Unchanged */ } else if (target) { crm_trace("Bit 0x%.8llx for %s cleared by %s:%d", bit, target, function, line); } else { crm_trace("Bit 0x%.8llx cleared by %s:%d", bit, function, line); } return rc; } static inline long long crm_set_bit(const char *function, int line, const char *target, long long word, long long bit) { long long rc = (word | bit); if (rc == word) { /* Unchanged */ } else if (target) { crm_trace("Bit 0x%.8llx for %s set by %s:%d", bit, target, function, line); } else { crm_trace("Bit 0x%.8llx set by %s:%d", bit, function, line); } return rc; } # define set_bit(word, bit) word = crm_set_bit(__FUNCTION__, __LINE__, NULL, word, bit) # define clear_bit(word, bit) word = crm_clear_bit(__FUNCTION__, __LINE__, NULL, word, bit) char *generate_hash_key(const char *crm_msg_reference, const char *sys); -const char *pcmk__env_option(const char *option); -void pcmk__set_env_option(const char *option, const char *value); -bool pcmk__env_option_enabled(const char *daemon, const char *option); void strip_text_nodes(xmlNode * xml); void pcmk_panic(const char *origin); pid_t pcmk_locate_sbd(void); # define crm_config_err(fmt...) { crm_config_error = TRUE; crm_err(fmt); } # define crm_config_warn(fmt...) { crm_config_warning = TRUE; crm_warn(fmt); } # define F_ATTRD_KEY "attr_key" # define F_ATTRD_ATTRIBUTE "attr_name" # define F_ATTRD_REGEX "attr_regex" # define F_ATTRD_TASK "task" # define F_ATTRD_VALUE "attr_value" # define F_ATTRD_SET "attr_set" # define F_ATTRD_IS_REMOTE "attr_is_remote" # define F_ATTRD_IS_PRIVATE "attr_is_private" # define F_ATTRD_SECTION "attr_section" # define F_ATTRD_DAMPEN "attr_dampening" # define F_ATTRD_HOST "attr_host" # define F_ATTRD_HOST_ID "attr_host_id" # define F_ATTRD_USER "attr_user" # define F_ATTRD_WRITER "attr_writer" # define F_ATTRD_VERSION "attr_version" # define F_ATTRD_RESOURCE "attr_resource" # define F_ATTRD_OPERATION "attr_clear_operation" # define F_ATTRD_INTERVAL "attr_clear_interval" # define F_ATTRD_IS_FORCE_WRITE "attrd_is_force_write" /* attrd operations */ # define ATTRD_OP_PEER_REMOVE "peer-remove" # define ATTRD_OP_UPDATE "update" # define ATTRD_OP_UPDATE_BOTH "update-both" # define ATTRD_OP_UPDATE_DELAY "update-delay" # define ATTRD_OP_QUERY "query" # define ATTRD_OP_REFRESH "refresh" # define ATTRD_OP_FLUSH "flush" # define ATTRD_OP_SYNC "sync" # define ATTRD_OP_SYNC_RESPONSE "sync-response" # define ATTRD_OP_CLEAR_FAILURE "clear-failure" # define PCMK__XA_MODE "mode" # define PCMK_ENV_PHYSICAL_HOST "physical_host" # if SUPPORT_COROSYNC # include # include typedef struct qb_ipc_request_header cs_ipc_header_request_t; typedef struct qb_ipc_response_header cs_ipc_header_response_t; # else typedef struct { int size __attribute__ ((aligned(8))); int id __attribute__ ((aligned(8))); } __attribute__ ((aligned(8))) cs_ipc_header_request_t; typedef struct { int size __attribute__ ((aligned(8))); int id __attribute__ ((aligned(8))); int error __attribute__ ((aligned(8))); } __attribute__ ((aligned(8))) cs_ipc_header_response_t; # endif void attrd_ipc_server_init(qb_ipcs_service_t **ipcs, struct qb_ipcs_service_handlers *cb); void stonith_ipc_server_init(qb_ipcs_service_t **ipcs, struct qb_ipcs_service_handlers *cb); qb_ipcs_service_t * crmd_ipc_server_init(struct qb_ipcs_service_handlers *cb); void cib_ipc_servers_init(qb_ipcs_service_t **ipcs_ro, qb_ipcs_service_t **ipcs_rw, qb_ipcs_service_t **ipcs_shm, struct qb_ipcs_service_handlers *ro_cb, struct qb_ipcs_service_handlers *rw_cb); void cib_ipc_servers_destroy(qb_ipcs_service_t *ipcs_ro, qb_ipcs_service_t *ipcs_rw, qb_ipcs_service_t *ipcs_shm); static inline void * realloc_safe(void *ptr, size_t size) { void *new_ptr; // realloc(p, 0) can replace free(p) but this wrapper can't CRM_ASSERT(size > 0); new_ptr = realloc(ptr, size); if (new_ptr == NULL) { free(ptr); abort(); } return new_ptr; } const char *crm_xml_add_last_written(xmlNode *xml_node); void crm_xml_dump(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth); void crm_buffer_add_char(char **buffer, int *offset, int *max, char c); bool pcmk__verify_digest(xmlNode *input, const char *expected); /* IPC Proxy Backend Shared Functions */ typedef struct remote_proxy_s { char *node_name; char *session_id; gboolean is_local; crm_ipc_t *ipc; mainloop_io_t *source; uint32_t last_request_id; lrmd_t *lrm; } remote_proxy_t; remote_proxy_t *remote_proxy_new( lrmd_t *lrmd, struct ipc_client_callbacks *proxy_callbacks, const char *node_name, const char *session_id, const char *channel); int remote_proxy_check(lrmd_t *lrmd, GHashTable *hash); void remote_proxy_cb(lrmd_t *lrmd, const char *node_name, xmlNode *msg); void remote_proxy_ack_shutdown(lrmd_t *lrmd); void remote_proxy_nack_shutdown(lrmd_t *lrmd); int remote_proxy_dispatch(const char *buffer, ssize_t length, gpointer userdata); void remote_proxy_disconnected(gpointer data); void remote_proxy_free(gpointer data); void remote_proxy_relay_event(remote_proxy_t *proxy, xmlNode *msg); void remote_proxy_relay_response(remote_proxy_t *proxy, xmlNode *msg, int msg_id); #endif /* CRM_INTERNAL__H */ diff --git a/lib/cib/cib_utils.c b/lib/cib/cib_utils.c index 231602e910..98a2f1e231 100644 --- a/lib/cib/cib_utils.c +++ b/lib/cib/cib_utils.c @@ -1,762 +1,762 @@ /* * Original copyright 2004 International Business Machines - * Later changes copyright 2008-2019 the Pacemaker project contributors + * Later changes copyright 2008-2020 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 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* */ 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_DOMAINS, "/cib/configuration", "//cib/configuration/domains" }, { 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_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 = DIMOF(known_paths); for (; lpc < max; lpc++) { if ((object_type == NULL && known_paths[lpc].name == NULL) || safe_str_eq(object_type, known_paths[lpc].name)) { return known_paths[lpc].path; } } return NULL; } const char * get_object_parent(const char *object_type) { int lpc = 0; int max = DIMOF(known_paths); for (; lpc < max; lpc++) { if (safe_str_eq(object_type, known_paths[lpc].name)) { 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); } /* * It is the callers responsibility to free both the new CIB (output) * and the new CIB (input) */ xmlNode * createEmptyCib(int admin_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, admin_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); return cib_root; } static bool cib_acl_enabled(xmlNode *xml, const char *user) { bool rc = FALSE; #if ENABLE_ACL if(pcmk_acl_required(user)) { const char *value = NULL; GHashTable *options = crm_str_table_new(); 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"); #endif 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", 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 (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"); strip_text_nodes(scratch); fix_plus_plus_recursive(scratch); if (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, __FUNCTION__, 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, __FUNCTION__, local_diff); crm_log_xml_trace(local_diff, "raw patch"); } if (is_not_set(call_options, cib_zero_copy) /* The 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 (safe_str_eq(section, XML_CIB_TAG_STATUS)) { /* 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 && is_not_set(call_options, cib_no_mtime)) { const char *schema = crm_element_value(scratch, XML_ATTR_VALIDATION); crm_xml_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)); #if ENABLE_ACL crm_xml_replace(scratch, XML_ATTR_UPDATE_USER, crm_element_value(req, F_CIB_USER)); #endif } } } crm_trace("Perform validation: %s", (check_schema? "true" : "false")); 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 ENABLE_ACL 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); } } #endif 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 ENABLE_ACL if (user_name) { crm_xml_add(op_msg, F_CIB_USER, user_name); } #endif 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 = g_hash_table_lookup(cib_op_callback_table, GINT_TO_POINTER(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 (safe_str_neq(entry->event, event)) { 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 pe_cluster_option cib_opts[] = { - /* - * name, legacy name, - * type, allowed values, default, validator, +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", &check_boolean, - "Enable CIB ACL", + "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", &check_positive_number, + "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)" + " multiplied by the number of nodes)." }, }; void cib_metadata(void) { - config_metadata("Cluster Information Base", "1.0", - "Cluster Information Base Options", - "This is a fake resource that details the options that can be configured for the Cluster Information Base.", - cib_opts, DIMOF(cib_opts)); + 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, DIMOF(cib_opts)); } void verify_cib_options(GHashTable * options) { - verify_all_options(options, cib_opts, DIMOF(cib_opts)); + pcmk__validate_cluster_options(options, cib_opts, DIMOF(cib_opts)); } const char * cib_pref(GHashTable * options, const char *name) { - return get_cluster_pref(options, cib_opts, DIMOF(cib_opts), name); + return pcmk__cluster_option(options, cib_opts, DIMOF(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; } 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; } /* 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 ENABLE_ACL if(user_name == NULL) { user_name = getenv("CIB_user"); } #endif return delegate(cib, op, host, section, data, output_data, call_options, user_name); } diff --git a/lib/common/Makefile.am b/lib/common/Makefile.am index 144a506518..5e72eaddd0 100644 --- a/lib/common/Makefile.am +++ b/lib/common/Makefile.am @@ -1,82 +1,83 @@ # -# Copyright 2004-2019 the Pacemaker project contributors +# Copyright 2004-2020 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 $(top_srcdir)/mk/common.mk AM_CPPFLAGS += -I$(top_builddir)/lib/gnu -I$(top_srcdir)/lib/gnu -DPCMK_SCHEMAS_EMERGENCY_XSLT=0 MOSTLYCLEANFILES = md5.c ## libraries lib_LTLIBRARIES = libcrmcommon.la # Disable -Wcast-qual if used, because we do some hacky casting, # and because libxml2 has some signatures that should be const but aren't # for backward compatibility reasons. # s390 needs -fPIC # s390-suse-linux/bin/ld: .libs/ipc.o: relocation R_390_PC32DBL against `__stack_chk_fail@@GLIBC_2.4' can not be used when making a shared object; recompile with -fPIC CFLAGS = $(CFLAGS_COPY:-Wcast-qual=) -fPIC SUBDIRS = tests noinst_HEADERS = crmcommon_private.h libcrmcommon_la_LDFLAGS = -version-info 37:0:3 libcrmcommon_la_CFLAGS = $(CFLAGS_HARDENED_LIB) libcrmcommon_la_LDFLAGS += $(LDFLAGS_HARDENED_LIB) libcrmcommon_la_LIBADD = @LIBADD_DL@ # Use += rather than backlashed continuation lines for parsing by bumplibs.sh libcrmcommon_la_SOURCES = libcrmcommon_la_SOURCES += acl.c libcrmcommon_la_SOURCES += agents.c libcrmcommon_la_SOURCES += alerts.c libcrmcommon_la_SOURCES += attrd_client.c if BUILD_CIBSECRETS libcrmcommon_la_SOURCES += cib_secrets.c endif libcrmcommon_la_SOURCES += cmdline.c libcrmcommon_la_SOURCES += digest.c libcrmcommon_la_SOURCES += io.c libcrmcommon_la_SOURCES += ipc.c libcrmcommon_la_SOURCES += iso8601.c libcrmcommon_la_SOURCES += logging.c libcrmcommon_la_SOURCES += mainloop.c libcrmcommon_la_SOURCES += nvpair.c libcrmcommon_la_SOURCES += operations.c +libcrmcommon_la_SOURCES += options.c libcrmcommon_la_SOURCES += output.c libcrmcommon_la_SOURCES += output_html.c libcrmcommon_la_SOURCES += output_log.c libcrmcommon_la_SOURCES += output_none.c libcrmcommon_la_SOURCES += output_text.c libcrmcommon_la_SOURCES += output_xml.c libcrmcommon_la_SOURCES += pid.c libcrmcommon_la_SOURCES += procfs.c libcrmcommon_la_SOURCES += remote.c libcrmcommon_la_SOURCES += results.c libcrmcommon_la_SOURCES += schemas.c libcrmcommon_la_SOURCES += strings.c libcrmcommon_la_SOURCES += utils.c libcrmcommon_la_SOURCES += watchdog.c libcrmcommon_la_SOURCES += xml.c libcrmcommon_la_SOURCES += xpath.c # It's possible to build the library adding ../gnu/md5.c directly to SOURCES, # but distclean chokes on that because it tries to include the source's .Plo # file, which may have already been cleaned. nodist_libcrmcommon_la_SOURCES = md5.c md5.c: ../gnu/md5.c cp "$<" "$@" clean-generic: rm -f *.log *.debug *.xml *~ diff --git a/lib/common/logging.c b/lib/common/logging.c index 4496b752e7..3fe0a7a333 100644 --- a/lib/common/logging.c +++ b/lib/common/logging.c @@ -1,1078 +1,989 @@ /* * Copyright 2004-2020 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 #include #include #include #include #include #include unsigned int crm_log_priority = LOG_NOTICE; unsigned int crm_log_level = LOG_INFO; static gboolean crm_tracing_enabled(void); unsigned int crm_trace_nonlog = 0; bool crm_is_daemon = 0; GLogFunc glib_log_default; static void crm_glib_handler(const gchar * log_domain, GLogLevelFlags flags, const gchar * message, gpointer user_data) { int log_level = LOG_WARNING; GLogLevelFlags msg_level = (flags & G_LOG_LEVEL_MASK); static struct qb_log_callsite *glib_cs = NULL; if (glib_cs == NULL) { glib_cs = qb_log_callsite_get(__FUNCTION__, __FILE__, "glib-handler", LOG_DEBUG, __LINE__, crm_trace_nonlog); } switch (msg_level) { case G_LOG_LEVEL_CRITICAL: log_level = LOG_CRIT; if (crm_is_callsite_active(glib_cs, LOG_DEBUG, 0) == FALSE) { /* log and record how we got here */ crm_abort(__FILE__, __FUNCTION__, __LINE__, message, TRUE, TRUE); } break; case G_LOG_LEVEL_ERROR: log_level = LOG_ERR; break; case G_LOG_LEVEL_MESSAGE: log_level = LOG_NOTICE; break; case G_LOG_LEVEL_INFO: log_level = LOG_INFO; break; case G_LOG_LEVEL_DEBUG: log_level = LOG_DEBUG; break; case G_LOG_LEVEL_WARNING: case G_LOG_FLAG_RECURSION: case G_LOG_FLAG_FATAL: case G_LOG_LEVEL_MASK: log_level = LOG_WARNING; break; } do_crm_log(log_level, "%s: %s", log_domain, message); } #ifndef NAME_MAX # define NAME_MAX 256 #endif /*! * \internal * \brief Write out a blackbox (enabling blackboxes if needed) * * \param[in] nsig Signal number that was received * * \note This is a true signal handler, and so must be async-safe. */ static void crm_trigger_blackbox(int nsig) { if(nsig == SIGTRAP) { /* Turn it on if it wasn't already */ crm_enable_blackbox(nsig); } crm_write_blackbox(nsig, NULL); } -/*! - * \internal - * \brief Get the value of a Pacemaker environment variable option - * - * If an environment variable option is set, with either a PCMK_ or (for - * backward compatibility) HA_ prefix, log and return the value. - * - * \param[in] option Environment variable name (without prefix) - * - * \return Value of environment variable option - */ -const char * -pcmk__env_option(const char *option) -{ - char env_name[NAME_MAX]; - const char *value = NULL; - - snprintf(env_name, NAME_MAX, "PCMK_%s", option); - value = getenv(env_name); - if (value != NULL) { - crm_trace("Found %s = %s", env_name, value); - return value; - } - - snprintf(env_name, NAME_MAX, "HA_%s", option); - value = getenv(env_name); - if (value != NULL) { - crm_trace("Found %s = %s", env_name, value); - return value; - } - - crm_trace("Nothing found for %s", option); - return NULL; -} - -/*! - * \brief Set or unset a Pacemaker environment variable option - * - * Set an environment variable option with both a PCMK_ and (for - * backward compatibility) HA_ prefix. - * - * \param[in] option Environment variable name (without prefix) - * \param[in] value New value (or NULL to unset) - */ -void -pcmk__set_env_option(const char *option, const char *value) -{ - char env_name[NAME_MAX]; - - snprintf(env_name, NAME_MAX, "PCMK_%s", option); - if (value) { - crm_trace("Setting %s to %s", env_name, value); - setenv(env_name, value, 1); - } else { - crm_trace("Unsetting %s", env_name); - unsetenv(env_name); - } - - snprintf(env_name, NAME_MAX, "HA_%s", option); - if (value) { - crm_trace("Setting %s to %s", env_name, value); - setenv(env_name, value, 1); - } else { - crm_trace("Unsetting %s", env_name); - unsetenv(env_name); - } -} - -/*! - * \internal - * \brief Check whether Pacemaker environment variable option is enabled - * - * Given a Pacemaker environment variable option that can either be boolean - * or a list of daemon names, return true if the option is enabled for a given - * daemon. - * - * \param[in] daemon Daemon name - * \param[in] option Pacemaker environment variable name - * - * \return true if variable is enabled for daemon, otherwise false - */ -bool -pcmk__env_option_enabled(const char *daemon, const char *option) -{ - const char *value = pcmk__env_option(option); - - return (value != NULL) && (crm_is_true(value) || strstr(value, daemon)); -} - void crm_log_deinit(void) { g_log_set_default_handler(glib_log_default, NULL); } #define FMT_MAX 256 static void set_format_string(int method, const char *daemon) { if (method == QB_LOG_SYSLOG) { // The system log gets a simplified, user-friendly format crm_extended_logging(method, QB_FALSE); qb_log_format_set(method, "%g %p: %b"); } else { // Everything else gets more detail, for advanced troubleshooting int offset = 0; char fmt[FMT_MAX]; if (method > QB_LOG_STDERR) { struct utsname res; const char *nodename = "localhost"; if (uname(&res) == 0) { nodename = res.nodename; } // If logging to file, prefix with timestamp, node name, daemon ID offset += snprintf(fmt + offset, FMT_MAX - offset, "%%t %s %-20s[%lu] ", nodename, daemon, (unsigned long) getpid()); } // Add function name (in parentheses) offset += snprintf(fmt + offset, FMT_MAX - offset, "(%%n"); if (crm_tracing_enabled()) { // When tracing, add file and line number offset += snprintf(fmt + offset, FMT_MAX - offset, "@%%f:%%l"); } offset += snprintf(fmt + offset, FMT_MAX - offset, ")"); // Add tag (if any), severity, and actual message offset += snprintf(fmt + offset, FMT_MAX - offset, " %%g\t%%p: %%b"); CRM_LOG_ASSERT(offset > 0); qb_log_format_set(method, fmt); } } gboolean crm_add_logfile(const char *filename) { bool is_default = false; static int default_fd = -1; static gboolean have_logfile = FALSE; const char *default_logfile = CRM_LOG_DIR "/pacemaker.log"; struct stat parent; int fd = 0, rc = 0; FILE *logfile = NULL; char *parent_dir = NULL; char *filename_cp; if (filename == NULL && have_logfile == FALSE) { filename = default_logfile; } if (filename == NULL) { return FALSE; /* Nothing to do */ } else if(safe_str_eq(filename, "none")) { return FALSE; /* Nothing to do */ } else if(safe_str_eq(filename, "/dev/null")) { return FALSE; /* Nothing to do */ } else if(safe_str_eq(filename, default_logfile)) { is_default = TRUE; } if(is_default && default_fd >= 0) { return TRUE; /* Nothing to do */ } /* Check the parent directory */ filename_cp = strdup(filename); parent_dir = dirname(filename_cp); rc = stat(parent_dir, &parent); if (rc != 0) { crm_err("Directory '%s' does not exist: logging to '%s' is disabled", parent_dir, filename); free(filename_cp); return FALSE; } free(filename_cp); errno = 0; logfile = fopen(filename, "a"); if(logfile == NULL) { crm_err("%s (%d): Logging to '%s' as uid=%u, gid=%u is disabled", pcmk_strerror(errno), errno, filename, geteuid(), getegid()); return FALSE; } /* Check/Set permissions if we're root */ if (geteuid() == 0) { struct stat st; uid_t pcmk_uid = 0; gid_t pcmk_gid = 0; gboolean fix = FALSE; int logfd = fileno(logfile); rc = fstat(logfd, &st); if (rc < 0) { crm_perror(LOG_WARNING, "Cannot stat %s", filename); fclose(logfile); return FALSE; } if (pcmk_daemon_user(&pcmk_uid, &pcmk_gid) == 0) { if (st.st_gid != pcmk_gid) { /* Wrong group */ fix = TRUE; } else if ((st.st_mode & S_IRWXG) != (S_IRGRP | S_IWGRP)) { /* Not read/writable by the correct group */ fix = TRUE; } } if (fix) { rc = fchown(logfd, pcmk_uid, pcmk_gid); if (rc < 0) { crm_warn("Cannot change the ownership of %s to user %s and gid %d", filename, CRM_DAEMON_USER, pcmk_gid); } rc = fchmod(logfd, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); if (rc < 0) { crm_warn("Cannot change the mode of %s to rw-rw----", filename); } fprintf(logfile, "Set r/w permissions for uid=%d, gid=%d on %s\n", pcmk_uid, pcmk_gid, filename); if (fflush(logfile) < 0 || fsync(logfd) < 0) { crm_err("Couldn't write out logfile: %s", filename); } } } /* Close and reopen with libqb */ fclose(logfile); fd = qb_log_file_open(filename); if (fd < 0) { crm_perror(LOG_WARNING, "Couldn't send additional logging to %s", filename); return FALSE; } if(is_default) { default_fd = fd; // Some resource agents will log only if environment variable is set if (pcmk__env_option("logfile") == NULL) { pcmk__set_env_option("logfile", filename); } } else if(default_fd >= 0) { crm_notice("Switching to %s", filename); qb_log_ctl(default_fd, QB_LOG_CONF_ENABLED, QB_FALSE); } crm_notice("Additional logging available in %s", filename); qb_log_ctl(fd, QB_LOG_CONF_ENABLED, QB_TRUE); /* qb_log_ctl(fd, QB_LOG_CONF_FILE_SYNC, 1); Turn on synchronous writes */ #ifdef HAVE_qb_log_conf_QB_LOG_CONF_MAX_LINE_LEN // Longer than default, for logging long XML lines qb_log_ctl(fd, QB_LOG_CONF_MAX_LINE_LEN, 800); #endif /* Enable callsites */ crm_update_callsites(); have_logfile = TRUE; return TRUE; } static int blackbox_trigger = 0; static volatile char *blackbox_file_prefix = NULL; #ifdef QB_FEATURE_LOG_HIRES_TIMESTAMPS typedef struct timespec *log_time_t; #else typedef time_t log_time_t; #endif static void blackbox_logger(int32_t t, struct qb_log_callsite *cs, log_time_t timestamp, const char *msg) { if(cs && cs->priority < LOG_ERR) { crm_write_blackbox(SIGTRAP, cs); /* Bypass the over-dumping logic */ } else { crm_write_blackbox(0, cs); } } static void crm_control_blackbox(int nsig, bool enable) { int lpc = 0; if (blackbox_file_prefix == NULL) { pid_t pid = getpid(); blackbox_file_prefix = crm_strdup_printf("%s/%s-%lu", CRM_BLACKBOX_DIR, crm_system_name, (unsigned long) pid); } if (enable && qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) { qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_SIZE, 5 * 1024 * 1024); /* Any size change drops existing entries */ qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE); /* Setting the size seems to disable it */ /* Enable synchronous logging */ for (lpc = QB_LOG_BLACKBOX; lpc < QB_LOG_TARGET_MAX; lpc++) { qb_log_ctl(lpc, QB_LOG_CONF_FILE_SYNC, QB_TRUE); } crm_notice("Initiated blackbox recorder: %s", blackbox_file_prefix); /* Save to disk on abnormal termination */ crm_signal_handler(SIGSEGV, crm_trigger_blackbox); crm_signal_handler(SIGABRT, crm_trigger_blackbox); crm_signal_handler(SIGILL, crm_trigger_blackbox); crm_signal_handler(SIGBUS, crm_trigger_blackbox); crm_signal_handler(SIGFPE, crm_trigger_blackbox); crm_update_callsites(); blackbox_trigger = qb_log_custom_open(blackbox_logger, NULL, NULL, NULL); qb_log_ctl(blackbox_trigger, QB_LOG_CONF_ENABLED, QB_TRUE); crm_trace("Trigger: %d is %d %d", blackbox_trigger, qb_log_ctl(blackbox_trigger, QB_LOG_CONF_STATE_GET, 0), QB_LOG_STATE_ENABLED); crm_update_callsites(); } else if (!enable && qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0) == QB_LOG_STATE_ENABLED) { qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE); /* Disable synchronous logging again when the blackbox is disabled */ for (lpc = QB_LOG_BLACKBOX; lpc < QB_LOG_TARGET_MAX; lpc++) { qb_log_ctl(lpc, QB_LOG_CONF_FILE_SYNC, QB_FALSE); } } } void crm_enable_blackbox(int nsig) { crm_control_blackbox(nsig, TRUE); } void crm_disable_blackbox(int nsig) { crm_control_blackbox(nsig, FALSE); } /*! * \internal * \brief Write out a blackbox, if blackboxes are enabled * * \param[in] nsig Signal that was received * \param[in] cs libqb callsite * * \note This may be called via a true signal handler and so must be async-safe. * @TODO actually make this async-safe */ void crm_write_blackbox(int nsig, struct qb_log_callsite *cs) { static volatile int counter = 1; static volatile time_t last = 0; char buffer[NAME_MAX]; time_t now = time(NULL); if (blackbox_file_prefix == NULL) { return; } switch (nsig) { case 0: case SIGTRAP: /* The graceful case - such as assertion failure or user request */ if (nsig == 0 && now == last) { /* Prevent over-dumping */ return; } snprintf(buffer, NAME_MAX, "%s.%d", blackbox_file_prefix, counter++); if (nsig == SIGTRAP) { crm_notice("Blackbox dump requested, please see %s for contents", buffer); } else if (cs) { syslog(LOG_NOTICE, "Problem detected at %s:%d (%s), please see %s for additional details", cs->function, cs->lineno, cs->filename, buffer); } else { crm_notice("Problem detected, please see %s for additional details", buffer); } last = now; qb_log_blackbox_write_to_file(buffer); /* Flush the existing contents * A size change would also work */ qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE); qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE); break; default: /* Do as little as possible, just try to get what we have out * We logged the filename when the blackbox was enabled */ crm_signal_handler(nsig, SIG_DFL); qb_log_blackbox_write_to_file((const char *)blackbox_file_prefix); qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE); raise(nsig); break; } } gboolean crm_log_cli_init(const char *entity) { return crm_log_init(entity, LOG_ERR, FALSE, FALSE, 0, NULL, TRUE); } static const char * crm_quark_to_string(uint32_t tag) { const char *text = g_quark_to_string(tag); if (text) { return text; } return ""; } static void crm_log_filter_source(int source, const char *trace_files, const char *trace_fns, const char *trace_fmts, const char *trace_tags, const char *trace_blackbox, struct qb_log_callsite *cs) { if (qb_log_ctl(source, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) { return; } else if (cs->tags != crm_trace_nonlog && source == QB_LOG_BLACKBOX) { /* Blackbox gets everything if enabled */ qb_bit_set(cs->targets, source); } else if (source == blackbox_trigger && blackbox_trigger > 0) { /* Should this log message result in the blackbox being dumped */ if (cs->priority <= LOG_ERR) { qb_bit_set(cs->targets, source); } else if (trace_blackbox) { char *key = crm_strdup_printf("%s:%d", cs->function, cs->lineno); if (strstr(trace_blackbox, key) != NULL) { qb_bit_set(cs->targets, source); } free(key); } } else if (source == QB_LOG_SYSLOG) { /* No tracing to syslog */ if (cs->priority <= crm_log_priority && cs->priority <= crm_log_level) { qb_bit_set(cs->targets, source); } /* Log file tracing options... */ } else if (cs->priority <= crm_log_level) { qb_bit_set(cs->targets, source); } else if (trace_files && strstr(trace_files, cs->filename) != NULL) { qb_bit_set(cs->targets, source); } else if (trace_fns && strstr(trace_fns, cs->function) != NULL) { qb_bit_set(cs->targets, source); } else if (trace_fmts && strstr(trace_fmts, cs->format) != NULL) { qb_bit_set(cs->targets, source); } else if (trace_tags && cs->tags != 0 && cs->tags != crm_trace_nonlog && g_quark_to_string(cs->tags) != NULL) { qb_bit_set(cs->targets, source); } } static void crm_log_filter(struct qb_log_callsite *cs) { int lpc = 0; static int need_init = 1; static const char *trace_fns = NULL; static const char *trace_tags = NULL; static const char *trace_fmts = NULL; static const char *trace_files = NULL; static const char *trace_blackbox = NULL; if (need_init) { need_init = 0; trace_fns = getenv("PCMK_trace_functions"); trace_fmts = getenv("PCMK_trace_formats"); trace_tags = getenv("PCMK_trace_tags"); trace_files = getenv("PCMK_trace_files"); trace_blackbox = getenv("PCMK_trace_blackbox"); if (trace_tags != NULL) { uint32_t tag; char token[500]; const char *offset = NULL; const char *next = trace_tags; do { offset = next; next = strchrnul(offset, ','); snprintf(token, sizeof(token), "%.*s", (int)(next - offset), offset); tag = g_quark_from_string(token); crm_info("Created GQuark %u from token '%s' in '%s'", tag, token, trace_tags); if (next[0] != 0) { next++; } } while (next != NULL && next[0] != 0); } } cs->targets = 0; /* Reset then find targets to enable */ for (lpc = QB_LOG_SYSLOG; lpc < QB_LOG_TARGET_MAX; lpc++) { crm_log_filter_source(lpc, trace_files, trace_fns, trace_fmts, trace_tags, trace_blackbox, cs); } } gboolean crm_is_callsite_active(struct qb_log_callsite *cs, uint8_t level, uint32_t tags) { gboolean refilter = FALSE; if (cs == NULL) { return FALSE; } if (cs->priority != level) { cs->priority = level; refilter = TRUE; } if (cs->tags != tags) { cs->tags = tags; refilter = TRUE; } if (refilter) { crm_log_filter(cs); } if (cs->targets == 0) { return FALSE; } return TRUE; } void crm_update_callsites(void) { static gboolean log = TRUE; if (log) { log = FALSE; crm_debug ("Enabling callsites based on priority=%d, files=%s, functions=%s, formats=%s, tags=%s", crm_log_level, getenv("PCMK_trace_files"), getenv("PCMK_trace_functions"), getenv("PCMK_trace_formats"), getenv("PCMK_trace_tags")); } qb_log_filter_fn_set(crm_log_filter); } static gboolean crm_tracing_enabled(void) { if (crm_log_level == LOG_TRACE) { return TRUE; } else if (getenv("PCMK_trace_files") || getenv("PCMK_trace_functions") || getenv("PCMK_trace_formats") || getenv("PCMK_trace_tags")) { return TRUE; } return FALSE; } static int crm_priority2int(const char *name) { struct syslog_names { const char *name; int priority; }; static struct syslog_names p_names[] = { {"emerg", LOG_EMERG}, {"alert", LOG_ALERT}, {"crit", LOG_CRIT}, {"error", LOG_ERR}, {"warning", LOG_WARNING}, {"notice", LOG_NOTICE}, {"info", LOG_INFO}, {"debug", LOG_DEBUG}, {NULL, -1} }; int lpc; for (lpc = 0; name != NULL && p_names[lpc].name != NULL; lpc++) { if (crm_str_eq(p_names[lpc].name, name, TRUE)) { return p_names[lpc].priority; } } return crm_log_priority; } static void crm_identity(const char *entity, int argc, char **argv) { if(crm_system_name != NULL) { /* Nothing to do */ } else if (entity) { free(crm_system_name); crm_system_name = strdup(entity); } else if (argc > 0 && argv != NULL) { char *mutable = strdup(argv[0]); char *modified = basename(mutable); if (strstr(modified, "lt-") == modified) { modified += 3; } free(crm_system_name); crm_system_name = strdup(modified); free(mutable); } else if (crm_system_name == NULL) { crm_system_name = strdup("Unknown"); } setenv("PCMK_service", crm_system_name, 1); } void crm_log_preinit(const char *entity, int argc, char **argv) { /* Configure libqb logging with nothing turned on */ int lpc = 0; int32_t qb_facility = 0; static bool have_logging = FALSE; if(have_logging == FALSE) { have_logging = TRUE; crm_xml_init(); /* Sets buffer allocation strategy */ if (crm_trace_nonlog == 0) { crm_trace_nonlog = g_quark_from_static_string("Pacemaker non-logging tracepoint"); } umask(S_IWGRP | S_IWOTH | S_IROTH); /* Redirect messages from glib functions to our handler */ glib_log_default = g_log_set_default_handler(crm_glib_handler, NULL); /* and for good measure... - this enum is a bit field (!) */ g_log_set_always_fatal((GLogLevelFlags) 0); /*value out of range */ /* Who do we log as */ crm_identity(entity, argc, argv); qb_facility = qb_log_facility2int("local0"); qb_log_init(crm_system_name, qb_facility, LOG_ERR); crm_log_level = LOG_CRIT; /* Nuke any syslog activity until it's asked for */ qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_FALSE); #ifdef HAVE_qb_log_conf_QB_LOG_CONF_MAX_LINE_LEN // Shorter than default, generous for what we *should* send to syslog qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_MAX_LINE_LEN, 256); #endif /* Set format strings and disable threading * Pacemaker and threads do not mix well (due to the amount of forking) */ qb_log_tags_stringify_fn_set(crm_quark_to_string); for (lpc = QB_LOG_SYSLOG; lpc < QB_LOG_TARGET_MAX; lpc++) { qb_log_ctl(lpc, QB_LOG_CONF_THREADED, QB_FALSE); #ifdef HAVE_qb_log_conf_QB_LOG_CONF_ELLIPSIS // End truncated lines with '...' qb_log_ctl(lpc, QB_LOG_CONF_ELLIPSIS, QB_TRUE); #endif set_format_string(lpc, crm_system_name); } } } gboolean crm_log_init(const char *entity, uint8_t level, gboolean daemon, gboolean to_stderr, int argc, char **argv, gboolean quiet) { const char *syslog_priority = NULL; const char *logfile = pcmk__env_option("logfile"); const char *facility = pcmk__env_option("logfacility"); const char *f_copy = facility; crm_is_daemon = daemon; crm_log_preinit(entity, argc, argv); if (level > LOG_TRACE) { level = LOG_TRACE; } if(level > crm_log_level) { crm_log_level = level; } /* Should we log to syslog */ if (facility == NULL) { if(crm_is_daemon) { facility = "daemon"; } else { facility = "none"; } pcmk__set_env_option("logfacility", facility); } if (safe_str_eq(facility, "none")) { quiet = TRUE; } else { qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_FACILITY, qb_log_facility2int(facility)); } if (pcmk__env_option_enabled(crm_system_name, "debug")) { /* Override the default setting */ crm_log_level = LOG_DEBUG; } /* What lower threshold do we have for sending to syslog */ syslog_priority = pcmk__env_option("logpriority"); if(syslog_priority) { int priority = crm_priority2int(syslog_priority); crm_log_priority = priority; qb_log_filter_ctl(QB_LOG_SYSLOG, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*", priority); } else { qb_log_filter_ctl(QB_LOG_SYSLOG, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*", LOG_NOTICE); } // Log to syslog unless requested to be quiet if (!quiet) { qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_TRUE); } /* Should we log to stderr */ if (pcmk__env_option_enabled(crm_system_name, "stderr")) { /* Override the default setting */ to_stderr = TRUE; } crm_enable_stderr(to_stderr); /* Should we log to a file */ if (safe_str_eq("none", logfile)) { /* No soup^Hlogs for you! */ } else if(crm_is_daemon) { // Daemons always get a log file, unless explicitly set to "none" crm_add_logfile(logfile); } else if(logfile) { crm_add_logfile(logfile); } if (crm_is_daemon && pcmk__env_option_enabled(crm_system_name, "blackbox")) { crm_enable_blackbox(0); } /* Summary */ crm_trace("Quiet: %d, facility %s", quiet, f_copy); pcmk__env_option("logfile"); pcmk__env_option("logfacility"); crm_update_callsites(); /* Ok, now we can start logging... */ if (quiet == FALSE && crm_is_daemon == FALSE) { crm_log_args(argc, argv); } if (crm_is_daemon) { const char *user = getenv("USER"); if (user != NULL && safe_str_neq(user, "root") && safe_str_neq(user, CRM_DAEMON_USER)) { crm_trace("Not switching to corefile directory for %s", user); crm_is_daemon = FALSE; } } if (crm_is_daemon) { int user = getuid(); const char *base = CRM_CORE_DIR; struct passwd *pwent = getpwuid(user); if (pwent == NULL) { crm_perror(LOG_ERR, "Cannot get name for uid: %d", user); } else if (safe_str_neq(pwent->pw_name, "root") && safe_str_neq(pwent->pw_name, CRM_DAEMON_USER)) { crm_trace("Don't change active directory for regular user: %s", pwent->pw_name); } else if (chdir(base) < 0) { crm_perror(LOG_INFO, "Cannot change active directory to %s", base); } else { crm_info("Changed active directory to %s", base); #if 0 { char path[512]; snprintf(path, 512, "%s-%lu", crm_system_name, (unsigned long) getpid()); mkdir(path, 0750); chdir(path); crm_info("Changed active directory to %s/%s/%s", base, pwent->pw_name, path); } #endif } /* Original meanings from signal(7) * * Signal Value Action Comment * SIGTRAP 5 Core Trace/breakpoint trap * SIGUSR1 30,10,16 Term User-defined signal 1 * SIGUSR2 31,12,17 Term User-defined signal 2 * * Our usage is as similar as possible */ mainloop_add_signal(SIGUSR1, crm_enable_blackbox); mainloop_add_signal(SIGUSR2, crm_disable_blackbox); mainloop_add_signal(SIGTRAP, crm_trigger_blackbox); } return TRUE; } /* returns the old value */ unsigned int set_crm_log_level(unsigned int level) { unsigned int old = crm_log_level; if (level > LOG_TRACE) { level = LOG_TRACE; } crm_log_level = level; crm_update_callsites(); crm_trace("New log level: %d", level); return old; } void crm_enable_stderr(int enable) { if (enable && qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) { qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_TRUE); crm_update_callsites(); } else if (enable == FALSE) { qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_FALSE); } } void crm_bump_log_level(int argc, char **argv) { static int args = TRUE; int level = crm_log_level; if (args && argc > 1) { crm_log_args(argc, argv); } if (qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_STATE_GET, 0) == QB_LOG_STATE_ENABLED) { set_crm_log_level(level + 1); } /* Enable after potentially logging the argstring, not before */ crm_enable_stderr(TRUE); } unsigned int get_crm_log_level(void) { return crm_log_level; } #define ARGS_FMT "Invoked: %s" void crm_log_args(int argc, char **argv) { int lpc = 0; int len = 0; int existing_len = 0; int line = __LINE__; static int logged = 0; char *arg_string = NULL; if (argc == 0 || argv == NULL || logged) { return; } logged = 1; for (; lpc < argc; lpc++) { if (argv[lpc] == NULL) { break; } len = 2 + strlen(argv[lpc]); /* +1 space, +1 EOS */ arg_string = realloc_safe(arg_string, len + existing_len); existing_len += sprintf(arg_string + existing_len, "%s ", argv[lpc]); } qb_log_from_external_source(__func__, __FILE__, ARGS_FMT, LOG_NOTICE, line, 0, arg_string); free(arg_string); } void crm_log_output_fn(const char *file, const char *function, int line, int level, const char *prefix, const char *output) { const char *next = NULL; const char *offset = NULL; if (level == LOG_NEVER) { return; } if (output == NULL) { if (level != LOG_STDOUT) { level = LOG_TRACE; } output = "-- empty --"; } next = output; do { offset = next; next = strchrnul(offset, '\n'); do_crm_log_alias(level, file, function, line, "%s [ %.*s ]", prefix, (int)(next - offset), offset); if (next[0] != 0) { next++; } } while (next != NULL && next[0] != 0); } diff --git a/lib/common/options.c b/lib/common/options.c new file mode 100644 index 0000000000..09a3e90510 --- /dev/null +++ b/lib/common/options.c @@ -0,0 +1,602 @@ +/* + * Copyright 2004-2020 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif + +#include + +#include +#include +#include +#include +#include + +#ifdef HAVE_GETOPT_H +# include +#endif + +#include + + +/* + * Command-line option handling + */ + +static char *crm_short_options = NULL; +static pcmk__cli_option_t *crm_long_options = NULL; +static const char *crm_app_description = NULL; +static const char *crm_app_usage = NULL; + +void +pcmk__cli_option_cleanup() +{ + free(crm_short_options); + crm_short_options = NULL; +} + +static struct option * +create_long_opts(pcmk__cli_option_t *long_options) +{ + struct option *long_opts = NULL; + +#ifdef HAVE_GETOPT_H + int index = 0, lpc = 0; + + /* + * A previous, possibly poor, choice of '?' as the short form of --help + * means that getopt_long() returns '?' for both --help and for "unknown option" + * + * This dummy entry allows us to differentiate between the two in + * pcmk__next_cli_option() and exit with the correct error code. + */ + long_opts = realloc_safe(long_opts, (index + 1) * sizeof(struct option)); + long_opts[index].name = "__dummmy__"; + long_opts[index].has_arg = 0; + long_opts[index].flag = 0; + long_opts[index].val = '_'; + index++; + + for (lpc = 0; long_options[lpc].name != NULL; lpc++) { + if (long_options[lpc].name[0] == '-') { + continue; + } + + long_opts = realloc_safe(long_opts, (index + 1) * sizeof(struct option)); + /*fprintf(stderr, "Creating %d %s = %c\n", index, + * long_options[lpc].name, long_options[lpc].val); */ + long_opts[index].name = long_options[lpc].name; + long_opts[index].has_arg = long_options[lpc].has_arg; + long_opts[index].flag = long_options[lpc].flag; + long_opts[index].val = long_options[lpc].val; + index++; + } + + /* Now create the list terminator */ + long_opts = realloc_safe(long_opts, (index + 1) * sizeof(struct option)); + long_opts[index].name = NULL; + long_opts[index].has_arg = 0; + long_opts[index].flag = 0; + long_opts[index].val = 0; +#endif + + return long_opts; +} + +/*! + * \internal + * \brief Define the command-line options a daemon or tool accepts + * + * \param[in] short_options getopt(3)-style short option list + * \param[in] app_usage summary of how command is invoked (for help) + * \param[in] long_options definition of options accepted + * \param[in] app_desc brief command description (for help) + */ +void +pcmk__set_cli_options(const char *short_options, const char *app_usage, + pcmk__cli_option_t *long_options, const char *app_desc) +{ + if (short_options) { + crm_short_options = strdup(short_options); + + } else if (long_options) { + int lpc = 0; + int opt_string_len = 0; + char *local_short_options = NULL; + + for (lpc = 0; long_options[lpc].name != NULL; lpc++) { + if (long_options[lpc].val && long_options[lpc].val != '-' && long_options[lpc].val < UCHAR_MAX) { + local_short_options = realloc_safe(local_short_options, opt_string_len + 4); + local_short_options[opt_string_len++] = long_options[lpc].val; + /* getopt(3) says: Two colons mean an option takes an optional arg; */ + if (long_options[lpc].has_arg == optional_argument) { + local_short_options[opt_string_len++] = ':'; + } + if (long_options[lpc].has_arg >= required_argument) { + local_short_options[opt_string_len++] = ':'; + } + local_short_options[opt_string_len] = 0; + } + } + crm_short_options = local_short_options; + crm_trace("Generated short option string: '%s'", local_short_options); + } + + if (long_options) { + crm_long_options = long_options; + } + if (app_desc) { + crm_app_description = app_desc; + } + if (app_usage) { + crm_app_usage = app_usage; + } +} + +int +pcmk__next_cli_option(int argc, char **argv, int *index, const char **longname) +{ +#ifdef HAVE_GETOPT_H + static struct option *long_opts = NULL; + + if (long_opts == NULL && crm_long_options) { + long_opts = create_long_opts(crm_long_options); + } + + *index = 0; + if (long_opts) { + int flag = getopt_long(argc, argv, crm_short_options, long_opts, index); + + switch (flag) { + case 0: + if (long_opts[*index].val) { + return long_opts[*index].val; + } else if (longname) { + *longname = long_opts[*index].name; + } else { + crm_notice("Unhandled option --%s", long_opts[*index].name); + return flag; + } + case -1: /* End of option processing */ + break; + case ':': + crm_trace("Missing argument"); + pcmk__cli_help('?', CRM_EX_USAGE); + break; + case '?': + pcmk__cli_help('?', (*index? CRM_EX_OK : CRM_EX_USAGE)); + break; + } + return flag; + } +#endif + + if (crm_short_options) { + return getopt(argc, argv, crm_short_options); + } + + return -1; +} + +void +pcmk__cli_help(char cmd, crm_exit_t exit_code) +{ + int i = 0; + FILE *stream = (exit_code ? stderr : stdout); + + if (cmd == 'v' || cmd == '$') { + fprintf(stream, "Pacemaker %s\n", PACEMAKER_VERSION); + fprintf(stream, "Written by Andrew Beekhof\n"); + goto out; + } + + if (cmd == '!') { + fprintf(stream, "Pacemaker %s (Build: %s): %s\n", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES); + goto out; + } + + fprintf(stream, "%s - %s\n", crm_system_name, crm_app_description); + + if (crm_app_usage) { + fprintf(stream, "Usage: %s %s\n", crm_system_name, crm_app_usage); + } + + if (crm_long_options) { + fprintf(stream, "Options:\n"); + for (i = 0; crm_long_options[i].name != NULL; i++) { + if (crm_long_options[i].flags & pcmk__option_hidden) { + + } else if (crm_long_options[i].flags & pcmk__option_paragraph) { + fprintf(stream, "%s\n\n", crm_long_options[i].desc); + + } else if (crm_long_options[i].flags & pcmk__option_example) { + fprintf(stream, "\t#%s\n\n", crm_long_options[i].desc); + + } else if (crm_long_options[i].val == '-' && crm_long_options[i].desc) { + fprintf(stream, "%s\n", crm_long_options[i].desc); + + } else { + /* is val printable as char ? */ + if (crm_long_options[i].val && crm_long_options[i].val <= UCHAR_MAX) { + fprintf(stream, " -%c,", crm_long_options[i].val); + } else { + fputs(" ", stream); + } + fprintf(stream, " --%s%s\t%s\n", crm_long_options[i].name, + crm_long_options[i].has_arg == optional_argument ? "[=value]" : + crm_long_options[i].has_arg == required_argument ? "=value" : "", + crm_long_options[i].desc ? crm_long_options[i].desc : ""); + } + } + + } else if (crm_short_options) { + fprintf(stream, "Usage: %s - %s\n", crm_system_name, crm_app_description); + for (i = 0; crm_short_options[i] != 0; i++) { + int has_arg = no_argument /* 0 */; + + if (crm_short_options[i + 1] == ':') { + if (crm_short_options[i + 2] == ':') + has_arg = optional_argument /* 2 */; + else + has_arg = required_argument /* 1 */; + } + + fprintf(stream, " -%c %s\n", crm_short_options[i], + has_arg == optional_argument ? "[value]" : + has_arg == required_argument ? "{value}" : ""); + i += has_arg; + } + } + + fprintf(stream, "\nReport bugs to %s\n", PACKAGE_BUGREPORT); + + out: + crm_exit(exit_code); + while(1); // above does not return +} + + +/* + * Environment variable option handling + */ + +/*! + * \internal + * \brief Get the value of a Pacemaker environment variable option + * + * If an environment variable option is set, with either a PCMK_ or (for + * backward compatibility) HA_ prefix, log and return the value. + * + * \param[in] option Environment variable name (without prefix) + * + * \return Value of environment variable option + */ +const char * +pcmk__env_option(const char *option) +{ + char env_name[NAME_MAX]; + const char *value = NULL; + + snprintf(env_name, NAME_MAX, "PCMK_%s", option); + value = getenv(env_name); + if (value != NULL) { + crm_trace("Found %s = %s", env_name, value); + return value; + } + + snprintf(env_name, NAME_MAX, "HA_%s", option); + value = getenv(env_name); + if (value != NULL) { + crm_trace("Found %s = %s", env_name, value); + return value; + } + + crm_trace("Nothing found for %s", option); + return NULL; +} + +/*! + * \brief Set or unset a Pacemaker environment variable option + * + * Set an environment variable option with both a PCMK_ and (for + * backward compatibility) HA_ prefix. + * + * \param[in] option Environment variable name (without prefix) + * \param[in] value New value (or NULL to unset) + */ +void +pcmk__set_env_option(const char *option, const char *value) +{ + char env_name[NAME_MAX]; + + snprintf(env_name, NAME_MAX, "PCMK_%s", option); + if (value) { + crm_trace("Setting %s to %s", env_name, value); + setenv(env_name, value, 1); + } else { + crm_trace("Unsetting %s", env_name); + unsetenv(env_name); + } + + snprintf(env_name, NAME_MAX, "HA_%s", option); + if (value) { + crm_trace("Setting %s to %s", env_name, value); + setenv(env_name, value, 1); + } else { + crm_trace("Unsetting %s", env_name); + unsetenv(env_name); + } +} + +/*! + * \internal + * \brief Check whether Pacemaker environment variable option is enabled + * + * Given a Pacemaker environment variable option that can either be boolean + * or a list of daemon names, return true if the option is enabled for a given + * daemon. + * + * \param[in] daemon Daemon name + * \param[in] option Pacemaker environment variable name + * + * \return true if variable is enabled for daemon, otherwise false + */ +bool +pcmk__env_option_enabled(const char *daemon, const char *option) +{ + const char *value = pcmk__env_option(option); + + return (value != NULL) && (crm_is_true(value) || strstr(value, daemon)); +} + + +/* + * Cluster option handling + */ + +bool +pcmk__valid_interval_spec(const char *value) +{ + (void) crm_parse_interval_spec(value); + return errno == 0; +} + +bool +pcmk__valid_boolean(const char *value) +{ + int tmp; + + return crm_str_to_boolean(value, &tmp) == 1; +} + +bool +pcmk__valid_number(const char *value) +{ + if (value == NULL) { + return false; + + } else if (safe_str_eq(value, CRM_MINUS_INFINITY_S) + || safe_str_eq(value, CRM_INFINITY_S)) { + return true; + } + + errno = 0; + crm_parse_ll(value, NULL); + return errno == 0; +} + +bool +pcmk__valid_positive_number(const char *value) +{ + return safe_str_eq(value, CRM_INFINITY_S) + || (crm_parse_ll(value, NULL) > 0); +} + +bool +pcmk__valid_quorum(const char *value) +{ + return safe_str_eq(value, "stop") + || safe_str_eq(value, "freeze") + || safe_str_eq(value, "ignore") + || safe_str_eq(value, "suicide"); +} + +bool +pcmk__valid_script(const char *value) +{ + struct stat st; + + if (safe_str_eq(value, "/dev/null")) { + return true; + } + + if (stat(value, &st) != 0) { + crm_err("Script %s does not exist", value); + return false; + } + + if (S_ISREG(st.st_mode) == 0) { + crm_err("Script %s is not a regular file", value); + return false; + } + + if ((st.st_mode & (S_IXUSR | S_IXGRP)) == 0) { + crm_err("Script %s is not executable", value); + return false; + } + + return true; +} + +bool +pcmk__valid_utilization(const char *value) +{ + char *end = NULL; + long number = strtol(value, &end, 10); + + if (end && (end[0] != '%')) { + return false; + } + return number >= 0; +} + +/*! + * \internal + * \brief Check a table of configured options for a particular option + * + * \param[in] options Name/value pairs for configured options + * \param[in] validate If not NULL, validator function for option value + * \param[in] name Option name to look for + * \param[in] old_name Alternative option name to look for + * \param[in] def_value Default to use if option not configured + * + * \return Option value (from supplied options table or default value) + */ +static const char * +cluster_option_value(GHashTable *options, bool (*validate)(const char *), + const char *name, const char *old_name, + const char *def_value) +{ + const char *value = NULL; + char *new_value = NULL; + + CRM_ASSERT(name != NULL); + + if (options) { + value = g_hash_table_lookup(options, name); + + if ((value == NULL) && old_name) { + value = g_hash_table_lookup(options, old_name); + if (value != NULL) { + crm_config_warn("Support for legacy name '%s' for cluster option '%s'" + " is deprecated and will be removed in a future release", + old_name, name); + + // Inserting copy with current name ensures we only warn once + new_value = strdup(value); + g_hash_table_insert(options, strdup(name), new_value); + value = new_value; + } + } + + if (value && validate && (validate(value) == FALSE)) { + crm_config_err("Resetting cluster option '%s' to default: value '%s' is invalid", + name, value); + value = NULL; + } + + if (value) { + return value; + } + } + + // No value found, use default + value = def_value; + + if (value == NULL) { + crm_trace("No value or default provided for cluster option '%s'", + name); + return NULL; + } + + if (validate) { + CRM_CHECK(validate(value) != FALSE, + crm_err("Bug: default value for cluster option '%s' is invalid", name); + return NULL); + } + + crm_trace("Using default value '%s' for cluster option '%s'", + value, name); + if (options) { + new_value = strdup(value); + g_hash_table_insert(options, strdup(name), new_value); + value = new_value; + } + return value; +} + +/*! + * \internal + * \brief Get the value of a cluster option + * + * \param[in] options Name/value pairs for configured options + * \param[in] option_list Possible cluster options + * \param[in] name (Primary) option name to look for + * + * \return Option value + */ +const char * +pcmk__cluster_option(GHashTable *options, pcmk__cluster_option_t *option_list, + int len, const char *name) +{ + const char *value = NULL; + + for (int lpc = 0; lpc < len; lpc++) { + if (safe_str_eq(name, option_list[lpc].name)) { + value = cluster_option_value(options, option_list[lpc].is_valid, + option_list[lpc].name, + option_list[lpc].alt_name, + option_list[lpc].default_value); + return value; + } + } + CRM_CHECK(FALSE, crm_err("Bug: looking for unknown option '%s'", name)); + return NULL; +} + +void +pcmk__print_option_metadata(const char *name, const char *version, + const char *desc_short, const char *desc_long, + pcmk__cluster_option_t *option_list, int len) +{ + int lpc = 0; + + fprintf(stdout, "" + "\n" + "\n" + " %s\n" + " %s\n" + " %s\n" + " \n", name, version, desc_long, desc_short); + + for (lpc = 0; lpc < len; lpc++) { + if ((option_list[lpc].description_long == NULL) + && (option_list[lpc].description_short == NULL)) { + continue; + } + fprintf(stdout, " \n" + " %s\n" + " \n" + " %s%s%s\n" + " \n", + option_list[lpc].name, + option_list[lpc].description_short, + option_list[lpc].type, + option_list[lpc].default_value, + option_list[lpc].description_long? + option_list[lpc].description_long : + option_list[lpc].description_short, + (option_list[lpc].values? " Allowed values: " : ""), + (option_list[lpc].values? option_list[lpc].values : "")); + } + fprintf(stdout, " \n\n"); +} + +void +pcmk__validate_cluster_options(GHashTable *options, + pcmk__cluster_option_t *option_list, int len) +{ + for (int lpc = 0; lpc < len; lpc++) { + cluster_option_value(options, option_list[lpc].is_valid, + option_list[lpc].name, + option_list[lpc].alt_name, + option_list[lpc].default_value); + } +} diff --git a/lib/common/remote.c b/lib/common/remote.c index 4194657fec..94e06ddf93 100644 --- a/lib/common/remote.c +++ b/lib/common/remote.c @@ -1,1280 +1,1280 @@ /* * Copyright 2008-2020 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 #include /* X32T ~ PRIx32 */ #include #include #include #include #include #include #ifdef HAVE_GNUTLS_GNUTLS_H # include #endif /* Swab macros from linux/swab.h */ #ifdef HAVE_LINUX_SWAB_H # include #else /* * casts are necessary for constants, because we never know how for sure * how U/UL/ULL map to __u16, __u32, __u64. At least not in a portable way. */ #define __swab16(x) ((uint16_t)( \ (((uint16_t)(x) & (uint16_t)0x00ffU) << 8) | \ (((uint16_t)(x) & (uint16_t)0xff00U) >> 8))) #define __swab32(x) ((uint32_t)( \ (((uint32_t)(x) & (uint32_t)0x000000ffUL) << 24) | \ (((uint32_t)(x) & (uint32_t)0x0000ff00UL) << 8) | \ (((uint32_t)(x) & (uint32_t)0x00ff0000UL) >> 8) | \ (((uint32_t)(x) & (uint32_t)0xff000000UL) >> 24))) #define __swab64(x) ((uint64_t)( \ (((uint64_t)(x) & (uint64_t)0x00000000000000ffULL) << 56) | \ (((uint64_t)(x) & (uint64_t)0x000000000000ff00ULL) << 40) | \ (((uint64_t)(x) & (uint64_t)0x0000000000ff0000ULL) << 24) | \ (((uint64_t)(x) & (uint64_t)0x00000000ff000000ULL) << 8) | \ (((uint64_t)(x) & (uint64_t)0x000000ff00000000ULL) >> 8) | \ (((uint64_t)(x) & (uint64_t)0x0000ff0000000000ULL) >> 24) | \ (((uint64_t)(x) & (uint64_t)0x00ff000000000000ULL) >> 40) | \ (((uint64_t)(x) & (uint64_t)0xff00000000000000ULL) >> 56))) #endif #define REMOTE_MSG_VERSION 1 #define ENDIAN_LOCAL 0xBADADBBD struct remote_header_v0 { uint32_t endian; /* Detect messages from hosts with different endian-ness */ uint32_t version; uint64_t id; uint64_t flags; uint32_t size_total; uint32_t payload_offset; uint32_t payload_compressed; uint32_t payload_uncompressed; /* New fields get added here */ } __attribute__ ((packed)); /*! * \internal * \brief Retrieve remote message header, in local endianness * * Return a pointer to the header portion of a remote connection's message * buffer, converting the header to local endianness if needed. * * \param[in,out] remote Remote connection with new message * * \return Pointer to message header, localized if necessary */ static struct remote_header_v0 * localized_remote_header(pcmk__remote_t *remote) { struct remote_header_v0 *header = (struct remote_header_v0 *)remote->buffer; if(remote->buffer_offset < sizeof(struct remote_header_v0)) { return NULL; } else if(header->endian != ENDIAN_LOCAL) { uint32_t endian = __swab32(header->endian); CRM_LOG_ASSERT(endian == ENDIAN_LOCAL); if(endian != ENDIAN_LOCAL) { crm_err("Invalid message detected, endian mismatch: %" X32T " is neither %" X32T " nor the swab'd %" X32T, ENDIAN_LOCAL, header->endian, endian); return NULL; } header->id = __swab64(header->id); header->flags = __swab64(header->flags); header->endian = __swab32(header->endian); header->version = __swab32(header->version); header->size_total = __swab32(header->size_total); header->payload_offset = __swab32(header->payload_offset); header->payload_compressed = __swab32(header->payload_compressed); header->payload_uncompressed = __swab32(header->payload_uncompressed); } return header; } #ifdef HAVE_GNUTLS_GNUTLS_H int pcmk__tls_client_handshake(pcmk__remote_t *remote, int timeout_ms) { int rc = 0; int pollrc = 0; time_t time_limit = time(NULL) + timeout_ms / 1000; do { rc = gnutls_handshake(*remote->tls_session); if ((rc == GNUTLS_E_INTERRUPTED) || (rc == GNUTLS_E_AGAIN)) { pollrc = pcmk__remote_ready(remote, 1000); if ((pollrc != pcmk_rc_ok) && (pollrc != ETIME)) { /* poll returned error, there is no hope */ crm_trace("TLS handshake poll failed: %s (%d)", pcmk_strerror(pollrc), pollrc); return pcmk_legacy2rc(pollrc); } } else if (rc < 0) { crm_trace("TLS handshake failed: %s (%d)", gnutls_strerror(rc), rc); return EPROTO; } else { return pcmk_rc_ok; } } while (time(NULL) < time_limit); return ETIME; } /*! * \internal * \brief Set minimum prime size required by TLS client * * \param[in] session TLS session to affect */ static void set_minimum_dh_bits(gnutls_session_t *session) { const char *dh_min_bits_s = getenv("PCMK_dh_min_bits"); if (dh_min_bits_s) { int dh_min_bits = crm_parse_int(dh_min_bits_s, "0"); /* This function is deprecated since GnuTLS 3.1.7, in favor of letting * the priority string imply the DH requirements, but this is the only * way to give the user control over compatibility with older servers. */ if (dh_min_bits > 0) { crm_info("Requiring server use a Diffie-Hellman prime of at least %d bits", dh_min_bits); gnutls_dh_set_prime_bits(*session, dh_min_bits); } } } static unsigned int get_bound_dh_bits(unsigned int dh_bits) { const char *dh_min_bits_s = getenv("PCMK_dh_min_bits"); const char *dh_max_bits_s = getenv("PCMK_dh_max_bits"); int dh_min_bits = 0; int dh_max_bits = 0; if (dh_min_bits_s) { dh_min_bits = crm_parse_int(dh_min_bits_s, "0"); } if (dh_max_bits_s) { dh_max_bits = crm_parse_int(dh_max_bits_s, "0"); if ((dh_min_bits > 0) && (dh_max_bits > 0) && (dh_max_bits < dh_min_bits)) { crm_warn("Ignoring PCMK_dh_max_bits because it is less than PCMK_dh_min_bits"); dh_max_bits = 0; } } if ((dh_min_bits > 0) && (dh_bits < dh_min_bits)) { return dh_min_bits; } if ((dh_max_bits > 0) && (dh_bits > dh_max_bits)) { return dh_max_bits; } return dh_bits; } /*! * \internal * \brief Initialize a new TLS session * * \param[in] csock Connected socket for TLS session * \param[in] conn_type GNUTLS_SERVER or GNUTLS_CLIENT * \param[in] cred_type GNUTLS_CRD_ANON or GNUTLS_CRD_PSK * \param[in] credentials TLS session credentials * * \return Pointer to newly created session object, or NULL on error */ gnutls_session_t * pcmk__new_tls_session(int csock, unsigned int conn_type, gnutls_credentials_type_t cred_type, void *credentials) { int rc = GNUTLS_E_SUCCESS; const char *prio_base = NULL; char *prio = NULL; gnutls_session_t *session = NULL; /* Determine list of acceptable ciphers, etc. Pacemaker always adds the * values required for its functionality. * * For an example of anonymous authentication, see: * http://www.manpagez.com/info/gnutls/gnutls-2.10.4/gnutls_81.php#Echo-Server-with-anonymous-authentication */ prio_base = getenv("PCMK_tls_priorities"); if (prio_base == NULL) { prio_base = PCMK_GNUTLS_PRIORITIES; } prio = crm_strdup_printf("%s:%s", prio_base, (cred_type == GNUTLS_CRD_ANON)? "+ANON-DH" : "+DHE-PSK:+PSK"); session = gnutls_malloc(sizeof(gnutls_session_t)); if (session == NULL) { rc = GNUTLS_E_MEMORY_ERROR; goto error; } rc = gnutls_init(session, conn_type); if (rc != GNUTLS_E_SUCCESS) { goto error; } /* @TODO On the server side, it would be more efficient to cache the * priority with gnutls_priority_init2() and set it with * gnutls_priority_set() for all sessions. */ rc = gnutls_priority_set_direct(*session, prio, NULL); if (rc != GNUTLS_E_SUCCESS) { goto error; } if (conn_type == GNUTLS_CLIENT) { set_minimum_dh_bits(session); } gnutls_transport_set_ptr(*session, (gnutls_transport_ptr_t) GINT_TO_POINTER(csock)); rc = gnutls_credentials_set(*session, cred_type, credentials); if (rc != GNUTLS_E_SUCCESS) { goto error; } free(prio); return session; error: crm_err("Could not initialize %s TLS %s session: %s " CRM_XS " rc=%d priority='%s'", (cred_type == GNUTLS_CRD_ANON)? "anonymous" : "PSK", (conn_type == GNUTLS_SERVER)? "server" : "client", gnutls_strerror(rc), rc, prio); free(prio); if (session != NULL) { gnutls_free(session); } return NULL; } /*! * \internal * \brief Initialize Diffie-Hellman parameters for a TLS server * * \param[out] dh_params Parameter object to initialize * * \return Standard Pacemaker return code * \todo The current best practice is to allow the client and server to * negotiate the Diffie-Hellman parameters via a TLS extension (RFC 7919). * However, we have to support both older versions of GnuTLS (<3.6) that * don't support the extension on our side, and older Pacemaker versions * that don't support the extension on the other side. The next best * practice would be to use a known good prime (see RFC 5114 section 2.2), * possibly stored in a file distributed with Pacemaker. */ int pcmk__init_tls_dh(gnutls_dh_params_t *dh_params) { int rc = GNUTLS_E_SUCCESS; unsigned int dh_bits = 0; rc = gnutls_dh_params_init(dh_params); if (rc != GNUTLS_E_SUCCESS) { goto error; } #ifdef HAVE_GNUTLS_SEC_PARAM_TO_PK_BITS dh_bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_NORMAL); if (dh_bits == 0) { rc = GNUTLS_E_DH_PRIME_UNACCEPTABLE; goto error; } #else dh_bits = 1024; #endif dh_bits = get_bound_dh_bits(dh_bits); crm_info("Generating Diffie-Hellman parameters with %u-bit prime for TLS", dh_bits); rc = gnutls_dh_params_generate2(*dh_params, dh_bits); if (rc != GNUTLS_E_SUCCESS) { goto error; } return pcmk_rc_ok; error: crm_err("Could not initialize Diffie-Hellman parameters for TLS: %s " CRM_XS " rc=%d", gnutls_strerror(rc), rc); return EPROTO; } /*! * \internal * \brief Process handshake data from TLS client * * Read as much TLS handshake data as is available. * * \param[in] client Client connection * * \return Standard Pacemaker return code (of particular interest, EAGAIN * if some data was successfully read but more data is needed) */ int pcmk__read_handshake_data(pcmk__client_t *client) { int rc = 0; CRM_ASSERT(client && client->remote && client->remote->tls_session); do { rc = gnutls_handshake(*client->remote->tls_session); } while (rc == GNUTLS_E_INTERRUPTED); if (rc == GNUTLS_E_AGAIN) { /* No more data is available at the moment. This function should be * invoked again once the client sends more. */ return EAGAIN; } else if (rc != GNUTLS_E_SUCCESS) { crm_err("TLS handshake with remote client failed: %s " CRM_XS " rc=%d", gnutls_strerror(rc), rc); return EPROTO; } return pcmk_rc_ok; } // \return Standard Pacemaker return code static int send_tls(gnutls_session_t *session, struct iovec *iov) { const char *unsent = iov->iov_base; size_t unsent_len = iov->iov_len; ssize_t gnutls_rc; if (unsent == NULL) { return EINVAL; } crm_debug("Sending TLS message of %llu bytes", (unsigned long long) unsent_len); while (true) { gnutls_rc = gnutls_record_send(*session, unsent, unsent_len); if (gnutls_rc == GNUTLS_E_INTERRUPTED || gnutls_rc == GNUTLS_E_AGAIN) { crm_trace("Retrying to send %llu bytes remaining", (unsigned long long) unsent_len); } else if (gnutls_rc < 0) { // Caller can log as error if necessary crm_info("TLS connection terminated: %s " CRM_XS " rc=%lld", gnutls_strerror((int) gnutls_rc), (long long) gnutls_rc); return ECONNABORTED; } else if (gnutls_rc < unsent_len) { crm_trace("Sent %lld of %llu bytes remaining", (long long) gnutls_rc, (unsigned long long) unsent_len); unsent_len -= gnutls_rc; unsent += gnutls_rc; } else { crm_trace("Sent all %lld bytes remaining", (long long) gnutls_rc); break; } } return pcmk_rc_ok; } #endif // \return Standard Pacemaker return code static int send_plaintext(int sock, struct iovec *iov) { const char *unsent = iov->iov_base; size_t unsent_len = iov->iov_len; ssize_t write_rc; if (unsent == NULL) { return EINVAL; } crm_debug("Sending plaintext message of %llu bytes to socket %d", (unsigned long long) unsent_len, sock); while (true) { write_rc = write(sock, unsent, unsent_len); if (write_rc < 0) { int rc = errno; if ((errno == EINTR) || (errno == EAGAIN)) { crm_trace("Retrying to send %llu bytes remaining to socket %d", (unsigned long long) unsent_len, sock); continue; } // Caller can log as error if necessary crm_info("Could not send message: %s " CRM_XS " rc=%d socket=%d", pcmk_rc_str(rc), rc, sock); return rc; } else if (write_rc < unsent_len) { crm_trace("Sent %lld of %llu bytes remaining", (long long) write_rc, (unsigned long long) unsent_len); unsent += write_rc; unsent_len -= write_rc; continue; } else { crm_trace("Sent all %lld bytes remaining: %.100s", (long long) write_rc, (char *) (iov->iov_base)); break; } } return pcmk_rc_ok; } // \return Standard Pacemaker return code static int remote_send_iovs(pcmk__remote_t *remote, struct iovec *iov, int iovs) { int rc = pcmk_rc_ok; for (int lpc = 0; (lpc < iovs) && (rc == pcmk_rc_ok); lpc++) { #ifdef HAVE_GNUTLS_GNUTLS_H if (remote->tls_session) { rc = send_tls(remote->tls_session, &(iov[lpc])); continue; } #endif if (remote->tcp_socket) { rc = send_plaintext(remote->tcp_socket, &(iov[lpc])); } else { rc = ESOCKTNOSUPPORT; } } return rc; } /*! * \internal * \brief Send an XML message over a Pacemaker Remote connection * * \param[in] remote Pacemaker Remote connection to use * \param[in] msg XML to send * * \return Standard Pacemaker return code */ int pcmk__remote_send_xml(pcmk__remote_t *remote, xmlNode *msg) { int rc = pcmk_rc_ok; static uint64_t id = 0; char *xml_text = NULL; struct iovec iov[2]; struct remote_header_v0 *header; CRM_CHECK((remote != NULL) && (msg != NULL), return EINVAL); xml_text = dump_xml_unformatted(msg); CRM_CHECK(xml_text != NULL, return EINVAL); header = calloc(1, sizeof(struct remote_header_v0)); CRM_ASSERT(header != NULL); iov[0].iov_base = header; iov[0].iov_len = sizeof(struct remote_header_v0); iov[1].iov_base = xml_text; iov[1].iov_len = 1 + strlen(xml_text); id++; header->id = id; header->endian = ENDIAN_LOCAL; header->version = REMOTE_MSG_VERSION; header->payload_offset = iov[0].iov_len; header->payload_uncompressed = iov[1].iov_len; header->size_total = iov[0].iov_len + iov[1].iov_len; rc = remote_send_iovs(remote, iov, 2); if (rc != pcmk_rc_ok) { crm_err("Could not send remote message: %s " CRM_XS " rc=%d", pcmk_rc_str(rc), rc); } free(iov[0].iov_base); free(iov[1].iov_base); return rc; } /*! * \internal * \brief Obtain the XML from the currently buffered remote connection message * * \param[in] remote Remote connection possibly with message available * * \return Newly allocated XML object corresponding to message data, or NULL * \note This effectively removes the message from the connection buffer. */ xmlNode * pcmk__remote_message_xml(pcmk__remote_t *remote) { xmlNode *xml = NULL; struct remote_header_v0 *header = localized_remote_header(remote); if (header == NULL) { return NULL; } /* Support compression on the receiving end now, in case we ever want to add it later */ if (header->payload_compressed) { int rc = 0; unsigned int size_u = 1 + header->payload_uncompressed; char *uncompressed = calloc(1, header->payload_offset + size_u); crm_trace("Decompressing message data %d bytes into %d bytes", header->payload_compressed, size_u); rc = BZ2_bzBuffToBuffDecompress(uncompressed + header->payload_offset, &size_u, remote->buffer + header->payload_offset, header->payload_compressed, 1, 0); if (rc != BZ_OK && header->version > REMOTE_MSG_VERSION) { crm_warn("Couldn't decompress v%d message, we only understand v%d", header->version, REMOTE_MSG_VERSION); free(uncompressed); return NULL; } else if (rc != BZ_OK) { crm_err("Decompression failed: %s " CRM_XS " bzerror=%d", bz2_strerror(rc), rc); free(uncompressed); return NULL; } CRM_ASSERT(size_u == header->payload_uncompressed); memcpy(uncompressed, remote->buffer, header->payload_offset); /* Preserve the header */ remote->buffer_size = header->payload_offset + size_u; free(remote->buffer); remote->buffer = uncompressed; header = localized_remote_header(remote); } /* take ownership of the buffer */ remote->buffer_offset = 0; CRM_LOG_ASSERT(remote->buffer[sizeof(struct remote_header_v0) + header->payload_uncompressed - 1] == 0); xml = string2xml(remote->buffer + header->payload_offset); if (xml == NULL && header->version > REMOTE_MSG_VERSION) { crm_warn("Couldn't parse v%d message, we only understand v%d", header->version, REMOTE_MSG_VERSION); } else if (xml == NULL) { crm_err("Couldn't parse: '%.120s'", remote->buffer + header->payload_offset); } return xml; } static int get_remote_socket(pcmk__remote_t *remote) { #ifdef HAVE_GNUTLS_GNUTLS_H if (remote->tls_session) { void *sock_ptr = gnutls_transport_get_ptr(*remote->tls_session); return GPOINTER_TO_INT(sock_ptr); } #endif if (remote->tcp_socket) { return remote->tcp_socket; } crm_err("Remote connection type undetermined (bug?)"); return -1; } /*! * \internal * \brief Wait for a remote session to have data to read * * \param[in] remote Connection to check * \param[in] timeout_ms Maximum time (in ms) to wait * * \return Standard Pacemaker return code (of particular interest, pcmk_rc_ok if * there is data ready to be read, and ETIME if there is no data within * the specified timeout) */ int pcmk__remote_ready(pcmk__remote_t *remote, int timeout_ms) { struct pollfd fds = { 0, }; int sock = 0; int rc = 0; time_t start; int timeout = timeout_ms; sock = get_remote_socket(remote); if (sock <= 0) { crm_trace("No longer connected"); return ENOTCONN; } start = time(NULL); errno = 0; do { fds.fd = sock; fds.events = POLLIN; /* If we got an EINTR while polling, and we have a * specific timeout we are trying to honor, attempt * to adjust the timeout to the closest second. */ if (errno == EINTR && (timeout > 0)) { timeout = timeout_ms - ((time(NULL) - start) * 1000); if (timeout < 1000) { timeout = 1000; } } rc = poll(&fds, 1, timeout); } while (rc < 0 && errno == EINTR); if (rc < 0) { return errno; } return (rc == 0)? ETIME : pcmk_rc_ok; } /*! * \internal * \brief Read bytes from non-blocking remote connection * * \param[in] remote Remote connection to read * * \return Standard Pacemaker return code (of particular interest, pcmk_rc_ok if * a full message has been received, or EAGAIN for a partial message) * \note Use only with non-blocking sockets after polling the socket. * \note This function will return when the socket read buffer is empty or an * error is encountered. */ static int read_available_remote_data(pcmk__remote_t *remote) { int rc = pcmk_rc_ok; size_t read_len = sizeof(struct remote_header_v0); struct remote_header_v0 *header = localized_remote_header(remote); bool received = false; ssize_t read_rc; if(header) { /* Stop at the end of the current message */ read_len = header->size_total; } /* automatically grow the buffer when needed */ if(remote->buffer_size < read_len) { remote->buffer_size = 2 * read_len; crm_trace("Expanding buffer to %llu bytes", (unsigned long long) remote->buffer_size); remote->buffer = realloc_safe(remote->buffer, remote->buffer_size + 1); } #ifdef HAVE_GNUTLS_GNUTLS_H if (!received && remote->tls_session) { read_rc = gnutls_record_recv(*(remote->tls_session), remote->buffer + remote->buffer_offset, remote->buffer_size - remote->buffer_offset); if (read_rc == GNUTLS_E_INTERRUPTED) { rc = EINTR; } else if (read_rc == GNUTLS_E_AGAIN) { rc = EAGAIN; } else if (read_rc < 0) { crm_debug("TLS receive failed: %s (%lld)", gnutls_strerror(read_rc), (long long) read_rc); rc = EIO; } received = true; } #endif if (!received && remote->tcp_socket) { read_rc = read(remote->tcp_socket, remote->buffer + remote->buffer_offset, remote->buffer_size - remote->buffer_offset); if (read_rc < 0) { rc = errno; } received = true; } if (!received) { crm_err("Remote connection type undetermined (bug?)"); return ESOCKTNOSUPPORT; } /* process any errors. */ if (read_rc > 0) { remote->buffer_offset += read_rc; /* always null terminate buffer, the +1 to alloc always allows for this. */ remote->buffer[remote->buffer_offset] = '\0'; crm_trace("Received %lld more bytes (%llu total)", (long long) read_rc, (unsigned long long) remote->buffer_offset); } else if ((rc == EINTR) || (rc == EAGAIN)) { crm_trace("No data available for non-blocking remote read: %s (%d)", pcmk_rc_str(rc), rc); } else if (read_rc == 0) { crm_debug("End of remote data encountered after %llu bytes", (unsigned long long) remote->buffer_offset); return ENOTCONN; } else { crm_debug("Error receiving remote data after %llu bytes: %s (%d)", (unsigned long long) remote->buffer_offset, pcmk_rc_str(rc), rc); return ENOTCONN; } header = localized_remote_header(remote); if(header) { if(remote->buffer_offset < header->size_total) { crm_trace("Read partial remote message (%llu of %u bytes)", (unsigned long long) remote->buffer_offset, header->size_total); } else { crm_trace("Read full remote message of %llu bytes", (unsigned long long) remote->buffer_offset); return pcmk_rc_ok; } } return EAGAIN; } /*! * \internal * \brief Read one message from a remote connection * * \param[in] remote Remote connection to read * \param[in] timeout_ms Fail if message not read in this many milliseconds * (10s will be used if 0, and 60s if negative) * * \return Standard Pacemaker return code */ int pcmk__read_remote_message(pcmk__remote_t *remote, int timeout_ms) { int rc = pcmk_rc_ok; time_t start = time(NULL); int remaining_timeout = 0; if (timeout_ms == 0) { timeout_ms = 10000; } else if (timeout_ms < 0) { timeout_ms = 60000; } remaining_timeout = timeout_ms; while (remaining_timeout > 0) { crm_trace("Waiting for remote data (%d ms of %d ms timeout remaining)", remaining_timeout, timeout_ms); rc = pcmk__remote_ready(remote, remaining_timeout); if (rc == ETIME) { crm_err("Timed out (%d ms) while waiting for remote data", remaining_timeout); return rc; } else if (rc != pcmk_rc_ok) { crm_debug("Wait for remote data aborted (will retry): %s " CRM_XS " rc=%d", pcmk_rc_str(rc), rc); } else { rc = read_available_remote_data(remote); if (rc == pcmk_rc_ok) { return rc; } else if (rc == EAGAIN) { crm_trace("Waiting for more remote data"); } else { crm_debug("Could not receive remote data: %s " CRM_XS " rc=%d", pcmk_rc_str(rc), rc); } } // Don't waste time retrying after fatal errors if ((rc == ENOTCONN) || (rc == ESOCKTNOSUPPORT)) { return rc; } remaining_timeout = timeout_ms - ((time(NULL) - start) * 1000); } return ETIME; } struct tcp_async_cb_data { int sock; int timeout_ms; time_t start; void *userdata; void (*callback) (void *userdata, int rc, int sock); }; // \return TRUE if timer should be rescheduled, FALSE otherwise static gboolean check_connect_finished(gpointer userdata) { struct tcp_async_cb_data *cb_data = userdata; int rc; fd_set rset, wset; struct timeval ts = { 0, }; if (cb_data->start == 0) { // Last connect() returned success immediately rc = pcmk_rc_ok; goto dispatch_done; } // If the socket is ready for reading or writing, the connect succeeded FD_ZERO(&rset); FD_SET(cb_data->sock, &rset); wset = rset; rc = select(cb_data->sock + 1, &rset, &wset, NULL, &ts); if (rc < 0) { // select() error rc = errno; if ((rc == EINPROGRESS) || (rc == EAGAIN)) { if ((time(NULL) - cb_data->start) < (cb_data->timeout_ms / 1000)) { return TRUE; // There is time left, so reschedule timer } else { rc = ETIMEDOUT; } } crm_trace("Could not check socket %d for connection success: %s (%d)", cb_data->sock, pcmk_rc_str(rc), rc); } else if (rc == 0) { // select() timeout if ((time(NULL) - cb_data->start) < (cb_data->timeout_ms / 1000)) { return TRUE; // There is time left, so reschedule timer } crm_debug("Timed out while waiting for socket %d connection success", cb_data->sock); rc = ETIMEDOUT; // select() returned number of file descriptors that are ready } else if (FD_ISSET(cb_data->sock, &rset) || FD_ISSET(cb_data->sock, &wset)) { // The socket is ready; check it for connection errors int error = 0; socklen_t len = sizeof(error); if (getsockopt(cb_data->sock, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { rc = errno; crm_trace("Couldn't check socket %d for connection errors: %s (%d)", cb_data->sock, pcmk_rc_str(rc), rc); } else if (error != 0) { rc = error; crm_trace("Socket %d connected with error: %s (%d)", cb_data->sock, pcmk_rc_str(rc), rc); } else { rc = pcmk_rc_ok; } } else { // Should not be possible crm_trace("select() succeeded, but socket %d not in resulting " "read/write sets", cb_data->sock); rc = EAGAIN; } dispatch_done: if (rc == pcmk_rc_ok) { crm_trace("Socket %d is connected", cb_data->sock); } else { close(cb_data->sock); cb_data->sock = -1; } if (cb_data->callback) { cb_data->callback(cb_data->userdata, rc, cb_data->sock); } free(cb_data); return FALSE; // Do not reschedule timer } /*! * \internal * \brief Attempt to connect socket, calling callback when done * * Set a given socket non-blocking, then attempt to connect to it, * retrying periodically until success or a timeout is reached. * Call a caller-supplied callback function when completed. * * \param[in] sock Newly created socket * \param[in] addr Socket address information for connect * \param[in] addrlen Size of socket address information in bytes * \param[in] timeout_ms Fail if not connected within this much time * \param[out] timer_id If not NULL, store retry timer ID here * \param[in] userdata User data to pass to callback * \param[in] callback Function to call when connection attempt completes * * \return Standard Pacemaker return code */ static int connect_socket_retry(int sock, const struct sockaddr *addr, socklen_t addrlen, int timeout_ms, int *timer_id, void *userdata, void (*callback) (void *userdata, int rc, int sock)) { int rc = 0; int interval = 500; int timer; struct tcp_async_cb_data *cb_data = NULL; rc = pcmk__set_nonblocking(sock); if (rc != pcmk_rc_ok) { crm_warn("Could not set socket non-blocking: %s " CRM_XS " rc=%d", pcmk_rc_str(rc), rc); return rc; } rc = connect(sock, addr, addrlen); if (rc < 0 && (errno != EINPROGRESS) && (errno != EAGAIN)) { rc = errno; crm_warn("Could not connect socket: %s " CRM_XS " rc=%d", pcmk_rc_str(rc), rc); return rc; } cb_data = calloc(1, sizeof(struct tcp_async_cb_data)); cb_data->userdata = userdata; cb_data->callback = callback; cb_data->sock = sock; cb_data->timeout_ms = timeout_ms; if (rc == 0) { /* The connect was successful immediately, we still return to mainloop * and let this callback get called later. This avoids the user of this api * to have to account for the fact the callback could be invoked within this * function before returning. */ cb_data->start = 0; interval = 1; } else { cb_data->start = time(NULL); } /* This timer function does a non-blocking poll on the socket to see if we * can use it. Once we can, the connect has completed. This method allows us * to connect without blocking the mainloop. * * @TODO Use a mainloop fd callback for this instead of polling. Something * about the way mainloop is currently polling prevents this from * working at the moment though. (See connect(2) regarding EINPROGRESS * for possible new handling needed.) */ crm_trace("Scheduling check in %dms for whether connect to fd %d finished", interval, sock); timer = g_timeout_add(interval, check_connect_finished, cb_data); if (timer_id) { *timer_id = timer; } return pcmk_rc_ok; } /*! * \internal * \brief Attempt once to connect socket and set it non-blocking * * \param[in] sock Newly created socket * \param[in] addr Socket address information for connect * \param[in] addrlen Size of socket address information in bytes * * \return Standard Pacemaker return code */ static int connect_socket_once(int sock, const struct sockaddr *addr, socklen_t addrlen) { int rc = connect(sock, addr, addrlen); if (rc < 0) { rc = errno; crm_warn("Could not connect socket: %s " CRM_XS " rc=%d", pcmk_rc_str(rc), rc); return rc; } rc = pcmk__set_nonblocking(sock); if (rc != pcmk_rc_ok) { crm_warn("Could not set socket non-blocking: %s " CRM_XS " rc=%d", pcmk_rc_str(rc), rc); return rc; } return pcmk_ok; } /*! * \internal * \brief Connect to server at specified TCP port * * \param[in] host Name of server to connect to * \param[in] port Server port to connect to * \param[in] timeout_ms If asynchronous, fail if not connected in this time * \param[out] timer_id If asynchronous and this is non-NULL, retry timer ID * will be put here (for ease of cancelling by caller) * \param[out] sock_fd Where to store socket file descriptor * \param[in] userdata If asynchronous, data to pass to callback * \param[in] callback If NULL, attempt a single synchronous connection, * otherwise retry asynchronously then call this * * \return Standard Pacemaker return code */ int pcmk__connect_remote(const char *host, int port, int timeout, int *timer_id, int *sock_fd, void *userdata, void (*callback) (void *userdata, int rc, int sock)) { char buffer[INET6_ADDRSTRLEN]; struct addrinfo *res = NULL; struct addrinfo *rp = NULL; struct addrinfo hints; const char *server = host; int rc; int sock = -1; CRM_CHECK((host != NULL) && (sock_fd != NULL), return EINVAL); // Get host's IP address(es) memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_CANONNAME; rc = getaddrinfo(server, NULL, &hints, &res); if (rc != 0) { crm_err("Unable to get IP address info for %s: %s", server, gai_strerror(rc)); rc = ENOTCONN; goto async_cleanup; } if (!res || !res->ai_addr) { crm_err("Unable to get IP address info for %s: no result", server); rc = ENOTCONN; goto async_cleanup; } // getaddrinfo() returns a list of host's addresses, try them in order for (rp = res; rp != NULL; rp = rp->ai_next) { struct sockaddr *addr = rp->ai_addr; if (!addr) { continue; } if (rp->ai_canonname) { server = res->ai_canonname; } crm_debug("Got canonical name %s for %s", server, host); sock = socket(rp->ai_family, SOCK_STREAM, IPPROTO_TCP); if (sock == -1) { rc = errno; crm_warn("Could not create socket for remote connection to %s:%d: " "%s " CRM_XS " rc=%d", server, port, pcmk_rc_str(rc), rc); continue; } /* Set port appropriately for address family */ /* (void*) casts avoid false-positive compiler alignment warnings */ if (addr->sa_family == AF_INET6) { ((struct sockaddr_in6 *)(void*)addr)->sin6_port = htons(port); } else { ((struct sockaddr_in *)(void*)addr)->sin_port = htons(port); } memset(buffer, 0, DIMOF(buffer)); pcmk__sockaddr2str(addr, buffer); crm_info("Attempting remote connection to %s:%d", buffer, port); if (callback) { if (connect_socket_retry(sock, rp->ai_addr, rp->ai_addrlen, timeout, timer_id, userdata, callback) == pcmk_rc_ok) { goto async_cleanup; /* Success for now, we'll hear back later in the callback */ } } else if (connect_socket_once(sock, rp->ai_addr, rp->ai_addrlen) == pcmk_rc_ok) { break; /* Success */ } // Connect failed close(sock); sock = -1; rc = ENOTCONN; } async_cleanup: if (res) { freeaddrinfo(res); } *sock_fd = sock; return rc; } /*! * \internal * \brief Convert an IP address (IPv4 or IPv6) to a string for logging * * \param[in] sa Socket address for IP * \param[out] s Storage for at least INET6_ADDRSTRLEN bytes * * \note sa The socket address can be a pointer to struct sockaddr_in (IPv4), * struct sockaddr_in6 (IPv6) or struct sockaddr_storage (either), * as long as its sa_family member is set correctly. */ void pcmk__sockaddr2str(void *sa, char *s) { switch (((struct sockaddr*)sa)->sa_family) { case AF_INET: inet_ntop(AF_INET, &(((struct sockaddr_in *)sa)->sin_addr), s, INET6_ADDRSTRLEN); break; case AF_INET6: inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)sa)->sin6_addr), s, INET6_ADDRSTRLEN); break; default: strcpy(s, ""); } } /*! * \internal * \brief Accept a client connection on a remote server socket * * \param[in] ssock Server socket file descriptor being listened on * \param[out] csock Where to put new client socket's file descriptor * * \return Standard Pacemaker return code */ int pcmk__accept_remote_connection(int ssock, int *csock) { int rc; struct sockaddr_storage addr; socklen_t laddr = sizeof(addr); char addr_str[INET6_ADDRSTRLEN]; /* accept the connection */ memset(&addr, 0, sizeof(addr)); *csock = accept(ssock, (struct sockaddr *)&addr, &laddr); if (*csock == -1) { rc = errno; crm_err("Could not accept remote client connection: %s " CRM_XS " rc=%d", pcmk_rc_str(rc), rc); return rc; } pcmk__sockaddr2str(&addr, addr_str); crm_info("Accepted new remote client connection from %s", addr_str); rc = pcmk__set_nonblocking(*csock); if (rc != pcmk_rc_ok) { crm_err("Could not set socket non-blocking: %s " CRM_XS " rc=%d", pcmk_rc_str(rc), rc); close(*csock); *csock = -1; return rc; } #ifdef TCP_USER_TIMEOUT - if (crm_get_sbd_timeout() > 0) { + if (pcmk__get_sbd_timeout() > 0) { // Time to fail and retry before watchdog - unsigned int optval = (unsigned int) crm_get_sbd_timeout() / 2; + unsigned int optval = (unsigned int) pcmk__get_sbd_timeout() / 2; rc = setsockopt(*csock, SOL_TCP, TCP_USER_TIMEOUT, &optval, sizeof(optval)); if (rc < 0) { rc = errno; crm_err("Could not set TCP timeout to %d ms on remote connection: " "%s " CRM_XS " rc=%d", optval, pcmk_rc_str(rc), rc); close(*csock); *csock = -1; return rc; } } #endif return rc; } /*! * \brief Get the default remote connection TCP port on this host * * \return Remote connection TCP port number */ int crm_default_remote_port() { static int port = 0; if (port == 0) { const char *env = getenv("PCMK_remote_port"); if (env) { errno = 0; port = strtol(env, NULL, 10); if (errno || (port < 1) || (port > 65535)) { crm_warn("Environment variable PCMK_remote_port has invalid value '%s', using %d instead", env, DEFAULT_REMOTE_PORT); port = DEFAULT_REMOTE_PORT; } } else { port = DEFAULT_REMOTE_PORT; } } return port; } diff --git a/lib/common/results.c b/lib/common/results.c index 132047b4be..6e618555f3 100644 --- a/lib/common/results.c +++ b/lib/common/results.c @@ -1,721 +1,721 @@ /* * Copyright 2004-2020 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 #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include #include #include // @COMPAT Legacy function return codes //! \deprecated Use standard return codes and pcmk_rc_name() instead const char * pcmk_errorname(int rc) { rc = abs(rc); switch (rc) { case pcmk_err_generic: return "pcmk_err_generic"; case pcmk_err_no_quorum: return "pcmk_err_no_quorum"; case pcmk_err_schema_validation: return "pcmk_err_schema_validation"; case pcmk_err_transform_failed: return "pcmk_err_transform_failed"; case pcmk_err_old_data: return "pcmk_err_old_data"; case pcmk_err_diff_failed: return "pcmk_err_diff_failed"; case pcmk_err_diff_resync: return "pcmk_err_diff_resync"; case pcmk_err_cib_modified: return "pcmk_err_cib_modified"; case pcmk_err_cib_backup: return "pcmk_err_cib_backup"; case pcmk_err_cib_save: return "pcmk_err_cib_save"; case pcmk_err_cib_corrupt: return "pcmk_err_cib_corrupt"; case pcmk_err_multiple: return "pcmk_err_multiple"; case pcmk_err_node_unknown: return "pcmk_err_node_unknown"; case pcmk_err_already: return "pcmk_err_already"; case pcmk_err_bad_nvpair: return "pcmk_err_bad_nvpair"; case pcmk_err_unknown_format: return "pcmk_err_unknown_format"; default: return pcmk_rc_name(rc); // system errno } } //! \deprecated Use standard return codes and pcmk_rc_str() instead const char * pcmk_strerror(int rc) { if (rc == 0) { return "OK"; } rc = abs(rc); // Of course rc > 0 ... unless someone passed INT_MIN as rc if ((rc > 0) && (rc < PCMK_ERROR_OFFSET)) { return strerror(rc); } switch (rc) { case pcmk_err_generic: return "Generic Pacemaker error"; case pcmk_err_no_quorum: return "Operation requires quorum"; case pcmk_err_schema_validation: return "Update does not conform to the configured schema"; case pcmk_err_transform_failed: return "Schema transform failed"; case pcmk_err_old_data: return "Update was older than existing configuration"; case pcmk_err_diff_failed: return "Application of an update diff failed"; case pcmk_err_diff_resync: return "Application of an update diff failed, requesting a full refresh"; case pcmk_err_cib_modified: return "The on-disk configuration was manually modified"; case pcmk_err_cib_backup: return "Could not archive the previous configuration"; case pcmk_err_cib_save: return "Could not save the new configuration to disk"; case pcmk_err_cib_corrupt: return "Could not parse on-disk configuration"; case pcmk_err_multiple: return "Resource active on multiple nodes"; case pcmk_err_node_unknown: return "Node not found"; case pcmk_err_already: return "Situation already as requested"; case pcmk_err_bad_nvpair: return "Bad name/value pair given"; case pcmk_err_schema_unchanged: return "Schema is already the latest available"; case pcmk_err_unknown_format: return "Unknown output format"; /* The following cases will only be hit on systems for which they are non-standard */ /* coverity[dead_error_condition] False positive on non-Linux */ case ENOTUNIQ: return "Name not unique on network"; /* coverity[dead_error_condition] False positive on non-Linux */ case ECOMM: return "Communication error on send"; /* coverity[dead_error_condition] False positive on non-Linux */ case ELIBACC: return "Can not access a needed shared library"; /* coverity[dead_error_condition] False positive on non-Linux */ case EREMOTEIO: return "Remote I/O error"; /* coverity[dead_error_condition] False positive on non-Linux */ case EUNATCH: return "Protocol driver not attached"; /* coverity[dead_error_condition] False positive on non-Linux */ case ENOKEY: return "Required key not available"; } crm_err("Unknown error code: %d", rc); return "Unknown error"; } // Standard Pacemaker API return codes /* This array is used only for nonzero values of pcmk_rc_e. Its values must be * kept in the exact reverse order of the enum value numbering (i.e. add new * values to the end of the array). */ static struct pcmk__rc_info { const char *name; const char *desc; int legacy_rc; } pcmk__rcs[] = { { "pcmk_rc_error", "Error", -pcmk_err_generic, }, { "pcmk_rc_unknown_format", "Unknown output format", -pcmk_err_unknown_format, }, { "pcmk_rc_bad_nvpair", "Bad name/value pair given", -pcmk_err_bad_nvpair, }, { "pcmk_rc_already", "Already in requested state", -pcmk_err_already, }, { "pcmk_rc_node_unknown", "Node not found", -pcmk_err_node_unknown, }, { "pcmk_rc_multiple", "Resource active on multiple nodes", -pcmk_err_multiple, }, { "pcmk_rc_cib_corrupt", "Could not parse on-disk configuration", -pcmk_err_cib_corrupt, }, { "pcmk_rc_cib_save", "Could not save new configuration to disk", -pcmk_err_cib_save, }, { "pcmk_rc_cib_backup", "Could not archive previous configuration", -pcmk_err_cib_backup, }, { "pcmk_rc_cib_modified", "On-disk configuration was manually modified", -pcmk_err_cib_modified, }, { "pcmk_rc_diff_resync", "Application of update diff failed, requesting full refresh", -pcmk_err_diff_resync, }, { "pcmk_rc_diff_failed", "Application of update diff failed", -pcmk_err_diff_failed, }, { "pcmk_rc_old_data", "Update was older than existing configuration", -pcmk_err_old_data, }, { "pcmk_rc_transform_failed", "Schema transform failed", -pcmk_err_transform_failed, }, { "pcmk_rc_schema_unchanged", "Schema is already the latest available", -pcmk_err_schema_unchanged, }, { "pcmk_rc_schema_validation", "Update does not conform to the configured schema", -pcmk_err_schema_validation, }, { "pcmk_rc_no_quorum", "Operation requires quorum", -pcmk_err_no_quorum, }, { "pcmk_rc_ipc_pid_only", "IPC server process is active but not accepting connections", -pcmk_err_generic, }, { "pcmk_rc_ipc_unresponsive", "IPC server is unresponsive", -pcmk_err_generic, }, { "pcmk_rc_ipc_unauthorized", "IPC server is blocked by unauthorized process", -pcmk_err_generic, }, }; #define PCMK__N_RC (sizeof(pcmk__rcs) / sizeof(struct pcmk__rc_info)) /*! * \brief Get a return code constant name as a string * * \param[in] rc Integer return code to convert * * \return String of constant name corresponding to rc */ const char * pcmk_rc_name(int rc) { if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < PCMK__N_RC)) { return pcmk__rcs[pcmk_rc_error - rc].name; } switch (rc) { case pcmk_rc_ok: return "pcmk_rc_ok"; case E2BIG: return "E2BIG"; case EACCES: return "EACCES"; case EADDRINUSE: return "EADDRINUSE"; case EADDRNOTAVAIL: return "EADDRNOTAVAIL"; case EAFNOSUPPORT: return "EAFNOSUPPORT"; case EAGAIN: return "EAGAIN"; case EALREADY: return "EALREADY"; case EBADF: return "EBADF"; case EBADMSG: return "EBADMSG"; case EBUSY: return "EBUSY"; case ECANCELED: return "ECANCELED"; case ECHILD: return "ECHILD"; case ECOMM: return "ECOMM"; case ECONNABORTED: return "ECONNABORTED"; case ECONNREFUSED: return "ECONNREFUSED"; case ECONNRESET: return "ECONNRESET"; /* case EDEADLK: return "EDEADLK"; */ case EDESTADDRREQ: return "EDESTADDRREQ"; case EDOM: return "EDOM"; case EDQUOT: return "EDQUOT"; case EEXIST: return "EEXIST"; case EFAULT: return "EFAULT"; case EFBIG: return "EFBIG"; case EHOSTDOWN: return "EHOSTDOWN"; case EHOSTUNREACH: return "EHOSTUNREACH"; case EIDRM: return "EIDRM"; case EILSEQ: return "EILSEQ"; case EINPROGRESS: return "EINPROGRESS"; case EINTR: return "EINTR"; case EINVAL: return "EINVAL"; case EIO: return "EIO"; case EISCONN: return "EISCONN"; case EISDIR: return "EISDIR"; case ELIBACC: return "ELIBACC"; case ELOOP: return "ELOOP"; case EMFILE: return "EMFILE"; case EMLINK: return "EMLINK"; case EMSGSIZE: return "EMSGSIZE"; #ifdef EMULTIHOP // Not available on OpenBSD case EMULTIHOP: return "EMULTIHOP"; #endif case ENAMETOOLONG: return "ENAMETOOLONG"; case ENETDOWN: return "ENETDOWN"; case ENETRESET: return "ENETRESET"; case ENETUNREACH: return "ENETUNREACH"; case ENFILE: return "ENFILE"; case ENOBUFS: return "ENOBUFS"; case ENODATA: return "ENODATA"; case ENODEV: return "ENODEV"; case ENOENT: return "ENOENT"; case ENOEXEC: return "ENOEXEC"; case ENOKEY: return "ENOKEY"; case ENOLCK: return "ENOLCK"; #ifdef ENOLINK // Not available on OpenBSD case ENOLINK: return "ENOLINK"; #endif case ENOMEM: return "ENOMEM"; case ENOMSG: return "ENOMSG"; case ENOPROTOOPT: return "ENOPROTOOPT"; case ENOSPC: return "ENOSPC"; case ENOSR: return "ENOSR"; case ENOSTR: return "ENOSTR"; case ENOSYS: return "ENOSYS"; case ENOTBLK: return "ENOTBLK"; case ENOTCONN: return "ENOTCONN"; case ENOTDIR: return "ENOTDIR"; case ENOTEMPTY: return "ENOTEMPTY"; case ENOTSOCK: return "ENOTSOCK"; #if ENOTSUP != EOPNOTSUPP case ENOTSUP: return "ENOTSUP"; #endif case ENOTTY: return "ENOTTY"; case ENOTUNIQ: return "ENOTUNIQ"; case ENXIO: return "ENXIO"; case EOPNOTSUPP: return "EOPNOTSUPP"; case EOVERFLOW: return "EOVERFLOW"; case EPERM: return "EPERM"; case EPFNOSUPPORT: return "EPFNOSUPPORT"; case EPIPE: return "EPIPE"; case EPROTO: return "EPROTO"; case EPROTONOSUPPORT: return "EPROTONOSUPPORT"; case EPROTOTYPE: return "EPROTOTYPE"; case ERANGE: return "ERANGE"; case EREMOTE: return "EREMOTE"; case EREMOTEIO: return "EREMOTEIO"; case EROFS: return "EROFS"; case ESHUTDOWN: return "ESHUTDOWN"; case ESPIPE: return "ESPIPE"; case ESOCKTNOSUPPORT: return "ESOCKTNOSUPPORT"; case ESRCH: return "ESRCH"; case ESTALE: return "ESTALE"; case ETIME: return "ETIME"; case ETIMEDOUT: return "ETIMEDOUT"; case ETXTBSY: return "ETXTBSY"; case EUNATCH: return "EUNATCH"; case EUSERS: return "EUSERS"; /* case EWOULDBLOCK: return "EWOULDBLOCK"; */ case EXDEV: return "EXDEV"; #ifdef EBADE // Not available on OS X case EBADE: return "EBADE"; case EBADFD: return "EBADFD"; case EBADSLT: return "EBADSLT"; case EDEADLOCK: return "EDEADLOCK"; case EBADR: return "EBADR"; case EBADRQC: return "EBADRQC"; case ECHRNG: return "ECHRNG"; #ifdef EISNAM // Not available on OS X, Illumos, Solaris case EISNAM: return "EISNAM"; case EKEYEXPIRED: return "EKEYEXPIRED"; case EKEYREJECTED: return "EKEYREJECTED"; case EKEYREVOKED: return "EKEYREVOKED"; #endif case EL2HLT: return "EL2HLT"; case EL2NSYNC: return "EL2NSYNC"; case EL3HLT: return "EL3HLT"; case EL3RST: return "EL3RST"; case ELIBBAD: return "ELIBBAD"; case ELIBMAX: return "ELIBMAX"; case ELIBSCN: return "ELIBSCN"; case ELIBEXEC: return "ELIBEXEC"; #ifdef ENOMEDIUM // Not available on OS X, Illumos, Solaris case ENOMEDIUM: return "ENOMEDIUM"; case EMEDIUMTYPE: return "EMEDIUMTYPE"; #endif case ENONET: return "ENONET"; case ENOPKG: return "ENOPKG"; case EREMCHG: return "EREMCHG"; case ERESTART: return "ERESTART"; case ESTRPIPE: return "ESTRPIPE"; #ifdef EUCLEAN // Not available on OS X, Illumos, Solaris case EUCLEAN: return "EUCLEAN"; #endif case EXFULL: return "EXFULL"; #endif // EBADE default: return "Unknown"; } } /*! * \brief Get a user-friendly description of a return code * * \param[in] rc Integer return code to convert * * \return String description of rc */ const char * pcmk_rc_str(int rc) { if (rc == pcmk_rc_ok) { return "OK"; } if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < PCMK__N_RC)) { return pcmk__rcs[pcmk_rc_error - rc].desc; } if (rc < 0) { return "Unknown error"; } return strerror(rc); } // This returns negative values for errors //! \deprecated Use standard return codes instead int pcmk_rc2legacy(int rc) { if (rc >= 0) { return -rc; // OK or system errno } if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < PCMK__N_RC)) { return pcmk__rcs[pcmk_rc_error - rc].legacy_rc; } return -pcmk_err_generic; } //! \deprecated Use standard return codes instead int pcmk_legacy2rc(int legacy_rc) { legacy_rc = abs(legacy_rc); switch (legacy_rc) { case pcmk_err_no_quorum: return pcmk_rc_no_quorum; case pcmk_err_schema_validation: return pcmk_rc_schema_validation; case pcmk_err_schema_unchanged: return pcmk_rc_schema_unchanged; case pcmk_err_transform_failed: return pcmk_rc_transform_failed; case pcmk_err_old_data: return pcmk_rc_old_data; case pcmk_err_diff_failed: return pcmk_rc_diff_failed; case pcmk_err_diff_resync: return pcmk_rc_diff_resync; case pcmk_err_cib_modified: return pcmk_rc_cib_modified; case pcmk_err_cib_backup: return pcmk_rc_cib_backup; case pcmk_err_cib_save: return pcmk_rc_cib_save; case pcmk_err_cib_corrupt: return pcmk_rc_cib_corrupt; case pcmk_err_multiple: return pcmk_rc_multiple; case pcmk_err_node_unknown: return pcmk_rc_node_unknown; case pcmk_err_already: return pcmk_rc_already; case pcmk_err_bad_nvpair: return pcmk_rc_bad_nvpair; case pcmk_err_unknown_format: return pcmk_rc_unknown_format; case pcmk_err_generic: return pcmk_rc_error; case pcmk_ok: return pcmk_rc_ok; default: return legacy_rc; // system errno } } // Exit status codes const char * crm_exit_name(crm_exit_t exit_code) { switch (exit_code) { case CRM_EX_OK: return "CRM_EX_OK"; case CRM_EX_ERROR: return "CRM_EX_ERROR"; case CRM_EX_INVALID_PARAM: return "CRM_EX_INVALID_PARAM"; case CRM_EX_UNIMPLEMENT_FEATURE: return "CRM_EX_UNIMPLEMENT_FEATURE"; case CRM_EX_INSUFFICIENT_PRIV: return "CRM_EX_INSUFFICIENT_PRIV"; case CRM_EX_NOT_INSTALLED: return "CRM_EX_NOT_INSTALLED"; case CRM_EX_NOT_CONFIGURED: return "CRM_EX_NOT_CONFIGURED"; case CRM_EX_NOT_RUNNING: return "CRM_EX_NOT_RUNNING"; case CRM_EX_USAGE: return "CRM_EX_USAGE"; case CRM_EX_DATAERR: return "CRM_EX_DATAERR"; case CRM_EX_NOINPUT: return "CRM_EX_NOINPUT"; case CRM_EX_NOUSER: return "CRM_EX_NOUSER"; case CRM_EX_NOHOST: return "CRM_EX_NOHOST"; case CRM_EX_UNAVAILABLE: return "CRM_EX_UNAVAILABLE"; case CRM_EX_SOFTWARE: return "CRM_EX_SOFTWARE"; case CRM_EX_OSERR: return "CRM_EX_OSERR"; case CRM_EX_OSFILE: return "CRM_EX_OSFILE"; case CRM_EX_CANTCREAT: return "CRM_EX_CANTCREAT"; case CRM_EX_IOERR: return "CRM_EX_IOERR"; case CRM_EX_TEMPFAIL: return "CRM_EX_TEMPFAIL"; case CRM_EX_PROTOCOL: return "CRM_EX_PROTOCOL"; case CRM_EX_NOPERM: return "CRM_EX_NOPERM"; case CRM_EX_CONFIG: return "CRM_EX_CONFIG"; case CRM_EX_FATAL: return "CRM_EX_FATAL"; case CRM_EX_PANIC: return "CRM_EX_PANIC"; case CRM_EX_DISCONNECT: return "CRM_EX_DISCONNECT"; case CRM_EX_DIGEST: return "CRM_EX_DIGEST"; case CRM_EX_NOSUCH: return "CRM_EX_NOSUCH"; case CRM_EX_QUORUM: return "CRM_EX_QUORUM"; case CRM_EX_UNSAFE: return "CRM_EX_UNSAFE"; case CRM_EX_EXISTS: return "CRM_EX_EXISTS"; case CRM_EX_MULTIPLE: return "CRM_EX_MULTIPLE"; case CRM_EX_EXPIRED: return "CRM_EX_EXPIRED"; case CRM_EX_NOT_YET_IN_EFFECT: return "CRM_EX_NOT_YET_IN_EFFECT"; case CRM_EX_INDETERMINATE: return "CRM_EX_INDETERMINATE"; case CRM_EX_OLD: return "CRM_EX_OLD"; case CRM_EX_TIMEOUT: return "CRM_EX_TIMEOUT"; case CRM_EX_MAX: return "CRM_EX_UNKNOWN"; } return "CRM_EX_UNKNOWN"; } const char * crm_exit_str(crm_exit_t exit_code) { switch (exit_code) { case CRM_EX_OK: return "OK"; case CRM_EX_ERROR: return "Error occurred"; case CRM_EX_INVALID_PARAM: return "Invalid parameter"; case CRM_EX_UNIMPLEMENT_FEATURE: return "Unimplemented"; case CRM_EX_INSUFFICIENT_PRIV: return "Insufficient privileges"; case CRM_EX_NOT_INSTALLED: return "Not installed"; case CRM_EX_NOT_CONFIGURED: return "Not configured"; case CRM_EX_NOT_RUNNING: return "Not running"; case CRM_EX_USAGE: return "Incorrect usage"; case CRM_EX_DATAERR: return "Invalid data given"; case CRM_EX_NOINPUT: return "Input file not available"; case CRM_EX_NOUSER: return "User does not exist"; case CRM_EX_NOHOST: return "Host does not exist"; case CRM_EX_UNAVAILABLE: return "Necessary service unavailable"; case CRM_EX_SOFTWARE: return "Internal software bug"; case CRM_EX_OSERR: return "Operating system error occurred"; case CRM_EX_OSFILE: return "System file not available"; case CRM_EX_CANTCREAT: return "Cannot create output file"; case CRM_EX_IOERR: return "I/O error occurred"; case CRM_EX_TEMPFAIL: return "Temporary failure, try again"; case CRM_EX_PROTOCOL: return "Protocol violated"; case CRM_EX_NOPERM: return "Insufficient privileges"; case CRM_EX_CONFIG: return "Invalid configuration"; case CRM_EX_FATAL: return "Fatal error occurred, will not respawn"; case CRM_EX_PANIC: return "System panic required"; case CRM_EX_DISCONNECT: return "Not connected"; case CRM_EX_DIGEST: return "Digest mismatch"; case CRM_EX_NOSUCH: return "No such object"; case CRM_EX_QUORUM: return "Quorum required"; case CRM_EX_UNSAFE: return "Operation not safe"; case CRM_EX_EXISTS: return "Requested item already exists"; case CRM_EX_MULTIPLE: return "Multiple items match request"; case CRM_EX_EXPIRED: return "Requested item has expired"; case CRM_EX_NOT_YET_IN_EFFECT: return "Requested item is not yet in effect"; case CRM_EX_INDETERMINATE: return "Could not determine status"; case CRM_EX_OLD: return "Update was older than existing configuration"; case CRM_EX_TIMEOUT: return "Timeout occurred"; case CRM_EX_MAX: return "Error occurred"; } if ((exit_code > 128) && (exit_code < CRM_EX_MAX)) { return "Interrupted by signal"; } return "Unknown exit status"; } //! \deprecated Use standard return codes and pcmk_rc2exitc() instead crm_exit_t crm_errno2exit(int rc) { rc = abs(rc); // Convenience for functions that return -errno switch (rc) { case pcmk_ok: return CRM_EX_OK; case pcmk_err_no_quorum: return CRM_EX_QUORUM; case pcmk_err_old_data: return CRM_EX_OLD; case pcmk_err_schema_validation: case pcmk_err_transform_failed: return CRM_EX_CONFIG; case pcmk_err_bad_nvpair: return CRM_EX_INVALID_PARAM; case pcmk_err_already: return CRM_EX_EXISTS; case pcmk_err_multiple: return CRM_EX_MULTIPLE; case pcmk_err_node_unknown: case pcmk_err_unknown_format: return CRM_EX_NOSUCH; default: return pcmk_rc2exitc(rc); // system errno } } /*! * \brief Map a function return code to the most similar exit code * * \param[in] rc Function return code * * \return Most similar exit code */ crm_exit_t pcmk_rc2exitc(int rc) { switch (rc) { case pcmk_rc_ok: return CRM_EX_OK; case pcmk_rc_no_quorum: return CRM_EX_QUORUM; case pcmk_rc_old_data: return CRM_EX_OLD; case pcmk_rc_schema_validation: case pcmk_rc_transform_failed: return CRM_EX_CONFIG; case pcmk_rc_bad_nvpair: return CRM_EX_INVALID_PARAM; case EACCES: return CRM_EX_INSUFFICIENT_PRIV; case EBADF: case EINVAL: case EFAULT: case ENOSYS: case EOVERFLOW: return CRM_EX_SOFTWARE; case EBADMSG: case EMSGSIZE: case ENOMSG: case ENOPROTOOPT: case EPROTO: case EPROTONOSUPPORT: case EPROTOTYPE: return CRM_EX_PROTOCOL; case ECOMM: case ENOMEM: return CRM_EX_OSERR; case ECONNABORTED: case ECONNREFUSED: case ECONNRESET: case ENOTCONN: return CRM_EX_DISCONNECT; case EEXIST: case pcmk_rc_already: return CRM_EX_EXISTS; case EIO: return CRM_EX_IOERR; case ENOTSUP: #if EOPNOTSUPP != ENOTSUP case EOPNOTSUPP: #endif return CRM_EX_UNIMPLEMENT_FEATURE; case ENOTUNIQ: case pcmk_rc_multiple: return CRM_EX_MULTIPLE; case ENXIO: case pcmk_rc_node_unknown: case pcmk_rc_unknown_format: return CRM_EX_NOSUCH; case ETIME: case ETIMEDOUT: return CRM_EX_TIMEOUT; default: return CRM_EX_ERROR; } } // Other functions const char * bz2_strerror(int rc) { // See ftp://sources.redhat.com/pub/bzip2/docs/manual_3.html#SEC17 switch (rc) { case BZ_OK: case BZ_RUN_OK: case BZ_FLUSH_OK: case BZ_FINISH_OK: case BZ_STREAM_END: return "Ok"; case BZ_CONFIG_ERROR: return "libbz2 has been improperly compiled on your platform"; case BZ_SEQUENCE_ERROR: return "library functions called in the wrong order"; case BZ_PARAM_ERROR: return "parameter is out of range or otherwise incorrect"; case BZ_MEM_ERROR: return "memory allocation failed"; case BZ_DATA_ERROR: return "data integrity error is detected during decompression"; case BZ_DATA_ERROR_MAGIC: return "the compressed stream does not start with the correct magic bytes"; case BZ_IO_ERROR: return "error reading or writing in the compressed file"; case BZ_UNEXPECTED_EOF: return "compressed file finishes before the logical end of stream is detected"; case BZ_OUTBUFF_FULL: return "output data will not fit into the buffer provided"; } return "Unknown error"; } crm_exit_t crm_exit(crm_exit_t rc) { /* A compiler could theoretically use any type for crm_exit_t, but an int * should always hold it, so cast to int to keep static analysis happy. */ if ((((int) rc) < 0) || (((int) rc) > CRM_EX_MAX)) { rc = CRM_EX_ERROR; } mainloop_cleanup(); crm_xml_cleanup(); qb_log_fini(); - crm_args_fini(); + pcmk__cli_option_cleanup(); if (crm_system_name) { crm_info("Exiting %s " CRM_XS " with status %d", crm_system_name, rc); free(crm_system_name); } else { crm_trace("Exiting with status %d", rc); } exit(rc); } diff --git a/lib/common/utils.c b/lib/common/utils.c index 56bf641be0..ac1671176c 100644 --- a/lib/common/utils.c +++ b/lib/common/utils.c @@ -1,1179 +1,710 @@ /* * Copyright 2004-2020 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 #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef MAXLINE # define MAXLINE 512 #endif -#ifdef HAVE_GETOPT_H -# include -#endif - #ifndef PW_BUFFER_LEN # define PW_BUFFER_LEN 500 #endif CRM_TRACE_INIT_DATA(common); gboolean crm_config_error = FALSE; gboolean crm_config_warning = FALSE; char *crm_system_name = NULL; int node_score_red = 0; int node_score_green = 0; int node_score_yellow = 0; -static struct crm_option *crm_long_options = NULL; -static const char *crm_app_description = NULL; -static char *crm_short_options = NULL; -static const char *crm_app_usage = NULL; - -gboolean -check_time(const char *value) -{ - if (crm_get_msec(value) < 5000) { - return FALSE; - } - return TRUE; -} - -gboolean -check_timer(const char *value) -{ - if (crm_get_msec(value) < 0) { - return FALSE; - } - return TRUE; -} - -gboolean -check_boolean(const char *value) -{ - int tmp = FALSE; - - if (crm_str_to_boolean(value, &tmp) != 1) { - return FALSE; - } - return TRUE; -} - -gboolean -check_number(const char *value) -{ - errno = 0; - if (value == NULL) { - return FALSE; - - } else if (safe_str_eq(value, CRM_MINUS_INFINITY_S)) { - - } else if (safe_str_eq(value, CRM_INFINITY_S)) { - - } else { - crm_parse_ll(value, NULL); - } - - if (errno != 0) { - return FALSE; - } - return TRUE; -} - -gboolean -check_positive_number(const char *value) -{ - return safe_str_eq(value, CRM_INFINITY_S) - || (crm_parse_ll(value, NULL) > 0); -} - -gboolean -check_quorum(const char *value) -{ - if (safe_str_eq(value, "stop")) { - return TRUE; - - } else if (safe_str_eq(value, "freeze")) { - return TRUE; - - } else if (safe_str_eq(value, "ignore")) { - return TRUE; - - } else if (safe_str_eq(value, "suicide")) { - return TRUE; - } - return FALSE; -} - -gboolean -check_script(const char *value) -{ - struct stat st; - - if(safe_str_eq(value, "/dev/null")) { - return TRUE; - } - - if(stat(value, &st) != 0) { - crm_err("Script %s does not exist", value); - return FALSE; - } - - if(S_ISREG(st.st_mode) == 0) { - crm_err("Script %s is not a regular file", value); - return FALSE; - } - - if( (st.st_mode & (S_IXUSR | S_IXGRP )) == 0) { - crm_err("Script %s is not executable", value); - return FALSE; - } - - return TRUE; -} - -gboolean -check_utilization(const char *value) -{ - char *end = NULL; - long number = strtol(value, &end, 10); - - if(end && end[0] != '%') { - return FALSE; - } else if(number < 0) { - return FALSE; - } - - return TRUE; -} - -void -crm_args_fini() -{ - free(crm_short_options); - crm_short_options = NULL; -} - int char2score(const char *score) { int score_f = 0; if (score == NULL) { } else if (safe_str_eq(score, CRM_MINUS_INFINITY_S)) { score_f = -CRM_SCORE_INFINITY; } else if (safe_str_eq(score, CRM_INFINITY_S)) { score_f = CRM_SCORE_INFINITY; } else if (safe_str_eq(score, CRM_PLUS_INFINITY_S)) { score_f = CRM_SCORE_INFINITY; } else if (safe_str_eq(score, "red")) { score_f = node_score_red; } else if (safe_str_eq(score, "yellow")) { score_f = node_score_yellow; } else if (safe_str_eq(score, "green")) { score_f = node_score_green; } else { score_f = crm_parse_int(score, NULL); if (score_f > 0 && score_f > CRM_SCORE_INFINITY) { score_f = CRM_SCORE_INFINITY; } else if (score_f < 0 && score_f < -CRM_SCORE_INFINITY) { score_f = -CRM_SCORE_INFINITY; } } return score_f; } char * score2char_stack(int score, char *buf, size_t len) { if (score >= CRM_SCORE_INFINITY) { strncpy(buf, CRM_INFINITY_S, 9); } else if (score <= -CRM_SCORE_INFINITY) { strncpy(buf, CRM_MINUS_INFINITY_S , 10); } else { return crm_itoa_stack(score, buf, len); } return buf; } char * score2char(int score) { if (score >= CRM_SCORE_INFINITY) { return strdup(CRM_INFINITY_S); } else if (score <= -CRM_SCORE_INFINITY) { return strdup(CRM_MINUS_INFINITY_S); } return crm_itoa(score); } -const char * -cluster_option(GHashTable * options, gboolean(*validate) (const char *), - const char *name, const char *old_name, const char *def_value) -{ - const char *value = NULL; - char *new_value = NULL; - - CRM_ASSERT(name != NULL); - - if (options) { - value = g_hash_table_lookup(options, name); - - if ((value == NULL) && old_name) { - value = g_hash_table_lookup(options, old_name); - if (value != NULL) { - crm_config_warn("Support for legacy name '%s' for cluster option '%s'" - " is deprecated and will be removed in a future release", - old_name, name); - - // Inserting copy with current name ensures we only warn once - new_value = strdup(value); - g_hash_table_insert(options, strdup(name), new_value); - value = new_value; - } - } - - if (value && validate && (validate(value) == FALSE)) { - crm_config_err("Resetting cluster option '%s' to default: value '%s' is invalid", - name, value); - value = NULL; - } - - if (value) { - return value; - } - } - - // No value found, use default - value = def_value; - - if (value == NULL) { - crm_trace("No value or default provided for cluster option '%s'", - name); - return NULL; - } - - if (validate) { - CRM_CHECK(validate(value) != FALSE, - crm_err("Bug: default value for cluster option '%s' is invalid", name); - return NULL); - } - - crm_trace("Using default value '%s' for cluster option '%s'", - value, name); - if (options) { - new_value = strdup(value); - g_hash_table_insert(options, strdup(name), new_value); - value = new_value; - } - return value; -} - -const char * -get_cluster_pref(GHashTable * options, pe_cluster_option * option_list, int len, const char *name) -{ - const char *value = NULL; - - for (int lpc = 0; lpc < len; lpc++) { - if (safe_str_eq(name, option_list[lpc].name)) { - value = cluster_option(options, - option_list[lpc].is_valid, - option_list[lpc].name, - option_list[lpc].alt_name, - option_list[lpc].default_value); - return value; - } - } - CRM_CHECK(FALSE, crm_err("Bug: looking for unknown option '%s'", name)); - return NULL; -} - -void -config_metadata(const char *name, const char *version, const char *desc_short, - const char *desc_long, pe_cluster_option * option_list, int len) -{ - int lpc = 0; - - fprintf(stdout, "" - "\n" - "\n" - " %s\n" - " %s\n" - " %s\n" - " \n", name, version, desc_long, desc_short); - - for (lpc = 0; lpc < len; lpc++) { - if (option_list[lpc].description_long == NULL && option_list[lpc].description_short == NULL) { - continue; - } - fprintf(stdout, " \n" - " %s\n" - " \n" - " %s%s%s\n" - " \n", - option_list[lpc].name, - option_list[lpc].description_short, - option_list[lpc].type, - option_list[lpc].default_value, - option_list[lpc].description_long ? option_list[lpc]. - description_long : option_list[lpc].description_short, - option_list[lpc].values ? " Allowed values: " : "", - option_list[lpc].values ? option_list[lpc].values : ""); - } - fprintf(stdout, " \n\n"); -} - -void -verify_all_options(GHashTable * options, pe_cluster_option * option_list, int len) -{ - int lpc = 0; - - for (lpc = 0; lpc < len; lpc++) { - cluster_option(options, - option_list[lpc].is_valid, - option_list[lpc].name, - option_list[lpc].alt_name, option_list[lpc].default_value); - } -} - char * generate_hash_key(const char *crm_msg_reference, const char *sys) { char *hash_key = crm_concat(sys ? sys : "none", crm_msg_reference, '_'); crm_trace("created hash key: (%s)", hash_key); return hash_key; } int crm_user_lookup(const char *name, uid_t * uid, gid_t * gid) { int rc = pcmk_ok; char *buffer = NULL; struct passwd pwd; struct passwd *pwentry = NULL; buffer = calloc(1, PW_BUFFER_LEN); if (buffer == NULL) { return -ENOMEM; } rc = getpwnam_r(name, &pwd, buffer, PW_BUFFER_LEN, &pwentry); if (pwentry) { if (uid) { *uid = pwentry->pw_uid; } if (gid) { *gid = pwentry->pw_gid; } crm_trace("User %s has uid=%d gid=%d", name, pwentry->pw_uid, pwentry->pw_gid); } else { rc = rc? -rc : -EINVAL; crm_info("User %s lookup: %s", name, pcmk_strerror(rc)); } free(buffer); return rc; } /*! * \brief Get user and group IDs of pacemaker daemon user * * \param[out] uid If non-NULL, where to store daemon user ID * \param[out] gid If non-NULL, where to store daemon group ID * * \return pcmk_ok on success, -errno otherwise */ int pcmk_daemon_user(uid_t *uid, gid_t *gid) { static uid_t daemon_uid; static gid_t daemon_gid; static bool found = false; int rc = pcmk_err_generic; if (!found) { rc = crm_user_lookup(CRM_DAEMON_USER, &daemon_uid, &daemon_gid); if (rc == pcmk_ok) { found = true; } } if (found) { if (uid) { *uid = daemon_uid; } if (gid) { *gid = daemon_gid; } } return rc; } static int crm_version_helper(const char *text, const char **end_text) { int atoi_result = -1; CRM_ASSERT(end_text != NULL); errno = 0; if (text != NULL && text[0] != 0) { /* seemingly sacrificing const-correctness -- because while strtol doesn't modify the input, it doesn't want to artificially taint the "end_text" pointer-to-pointer-to-first-char-in-string with constness in case the input wasn't actually constant -- by semantic definition not a single character will get modified so it shall be perfectly safe to make compiler happy with dropping "const" qualifier here */ atoi_result = (int) strtol(text, (char **) end_text, 10); if (errno == EINVAL) { crm_err("Conversion of '%s' %c failed", text, text[0]); atoi_result = -1; } } return atoi_result; } /* * version1 < version2 : -1 * version1 = version2 : 0 * version1 > version2 : 1 */ int compare_version(const char *version1, const char *version2) { int rc = 0; int lpc = 0; const char *ver1_iter, *ver2_iter; if (version1 == NULL && version2 == NULL) { return 0; } else if (version1 == NULL) { return -1; } else if (version2 == NULL) { return 1; } ver1_iter = version1; ver2_iter = version2; while (1) { int digit1 = 0; int digit2 = 0; lpc++; if (ver1_iter == ver2_iter) { break; } if (ver1_iter != NULL) { digit1 = crm_version_helper(ver1_iter, &ver1_iter); } if (ver2_iter != NULL) { digit2 = crm_version_helper(ver2_iter, &ver2_iter); } if (digit1 < digit2) { rc = -1; break; } else if (digit1 > digit2) { rc = 1; break; } if (ver1_iter != NULL && *ver1_iter == '.') { ver1_iter++; } if (ver1_iter != NULL && *ver1_iter == '\0') { ver1_iter = NULL; } if (ver2_iter != NULL && *ver2_iter == '.') { ver2_iter++; } if (ver2_iter != NULL && *ver2_iter == 0) { ver2_iter = NULL; } } if (rc == 0) { crm_trace("%s == %s (%d)", version1, version2, lpc); } else if (rc < 0) { crm_trace("%s < %s (%d)", version1, version2, lpc); } else if (rc > 0) { crm_trace("%s > %s (%d)", version1, version2, lpc); } return rc; } +/*! + * \brief Parse milliseconds from a Pacemaker interval specification + * + * \param[in] input Pacemaker time interval specification (a bare number of + * seconds, a number with a unit optionally with whitespace + * before and/or after the number, or an ISO 8601 duration) + * + * \return Milliseconds equivalent of given specification on success (limited + * to the range of an unsigned integer), 0 if input is NULL, + * or 0 (and set errno to EINVAL) on error + */ guint crm_parse_interval_spec(const char *input) { - long long msec = 0; + long long msec = -1; + errno = 0; if (input == NULL) { return 0; - } else if (input[0] != 'P') { - long long tmp = crm_get_msec(input); - - if(tmp > 0) { - msec = tmp; - } - - } else { + } else if (input[0] == 'P') { crm_time_t *period_s = crm_time_parse_duration(input); if (period_s) { msec = 1000 * crm_time_get_seconds(period_s); crm_time_free(period_s); - } else { - // Reason why not valid has already been logged - crm_warn("Using 0 instead of '%s'", input); } + + } else { + msec = crm_get_msec(input); } - return (msec <= 0)? 0 : ((msec >= G_MAXUINT)? G_MAXUINT : (guint) msec); + if (msec < 0) { + crm_warn("Using 0 instead of '%s'", input); + errno = EINVAL; + return 0; + } + return (msec >= G_MAXUINT)? G_MAXUINT : (guint) msec; } extern bool crm_is_daemon; /* coverity[+kill] */ void crm_abort(const char *file, const char *function, int line, const char *assert_condition, gboolean do_core, gboolean do_fork) { int rc = 0; int pid = 0; int status = 0; /* Implied by the parent's error logging below */ /* crm_write_blackbox(0); */ if(crm_is_daemon == FALSE) { /* This is a command line tool - do not fork */ /* crm_add_logfile(NULL); * Record it to a file? */ crm_enable_stderr(TRUE); /* Make sure stderr is enabled so we can tell the caller */ do_fork = FALSE; /* Just crash if needed */ } if (do_core == FALSE) { crm_err("%s: Triggered assert at %s:%d : %s", function, file, line, assert_condition); return; } else if (do_fork) { pid = fork(); } else { crm_err("%s: Triggered fatal assert at %s:%d : %s", function, file, line, assert_condition); } if (pid == -1) { crm_crit("%s: Cannot create core for non-fatal assert at %s:%d : %s", function, file, line, assert_condition); return; } else if(pid == 0) { /* Child process */ abort(); return; } /* Parent process */ crm_err("%s: Forked child %d to record non-fatal assert at %s:%d : %s", function, pid, file, line, assert_condition); crm_write_blackbox(SIGTRAP, NULL); do { rc = waitpid(pid, &status, 0); if(rc == pid) { return; /* Job done */ } } while(errno == EINTR); if (errno == ECHILD) { /* crm_mon does this */ crm_trace("Cannot wait on forked child %d - SIGCHLD is probably set to SIG_IGN", pid); return; } crm_perror(LOG_ERR, "Cannot wait on forked child %d", pid); } void crm_make_daemon(const char *name, gboolean daemonize, const char *pidfile) { int rc; pid_t pid; const char *devnull = "/dev/null"; if (daemonize == FALSE) { return; } /* Check before we even try... */ rc = pcmk__pidfile_matches(pidfile, 1, name, &pid); if ((rc != pcmk_rc_ok) && (rc != ENOENT)) { crm_err("%s: already running [pid %lld in %s]", name, (long long) pid, pidfile); printf("%s: already running [pid %lld in %s]\n", name, (long long) pid, pidfile); crm_exit(CRM_EX_ERROR); } pid = fork(); if (pid < 0) { fprintf(stderr, "%s: could not start daemon\n", name); crm_perror(LOG_ERR, "fork"); crm_exit(CRM_EX_OSERR); } else if (pid > 0) { crm_exit(CRM_EX_OK); } rc = pcmk__lock_pidfile(pidfile, name); if (rc != pcmk_rc_ok) { crm_err("Could not lock '%s' for %s: %s " CRM_XS " rc=%d", pidfile, name, pcmk_rc_str(rc), rc); printf("Could not lock '%s' for %s: %s (%d)\n", pidfile, name, pcmk_rc_str(rc), rc); crm_exit(CRM_EX_ERROR); } umask(S_IWGRP | S_IWOTH | S_IROTH); close(STDIN_FILENO); (void)open(devnull, O_RDONLY); /* Stdin: fd 0 */ close(STDOUT_FILENO); (void)open(devnull, O_WRONLY); /* Stdout: fd 1 */ close(STDERR_FILENO); (void)open(devnull, O_WRONLY); /* Stderr: fd 2 */ } char * crm_meta_name(const char *field) { int lpc = 0; int max = 0; char *crm_name = NULL; CRM_CHECK(field != NULL, return NULL); crm_name = crm_concat(CRM_META, field, '_'); /* Massage the names so they can be used as shell variables */ max = strlen(crm_name); for (; lpc < max; lpc++) { switch (crm_name[lpc]) { case '-': crm_name[lpc] = '_'; break; } } return crm_name; } const char * crm_meta_value(GHashTable * hash, const char *field) { char *key = NULL; const char *value = NULL; key = crm_meta_name(field); if (key) { value = g_hash_table_lookup(hash, key); free(key); } return value; } -static struct option * -crm_create_long_opts(struct crm_option *long_options) -{ - struct option *long_opts = NULL; - -#ifdef HAVE_GETOPT_H - int index = 0, lpc = 0; - - /* - * A previous, possibly poor, choice of '?' as the short form of --help - * means that getopt_long() returns '?' for both --help and for "unknown option" - * - * This dummy entry allows us to differentiate between the two in crm_get_option() - * and exit with the correct error code - */ - long_opts = realloc_safe(long_opts, (index + 1) * sizeof(struct option)); - long_opts[index].name = "__dummmy__"; - long_opts[index].has_arg = 0; - long_opts[index].flag = 0; - long_opts[index].val = '_'; - index++; - - for (lpc = 0; long_options[lpc].name != NULL; lpc++) { - if (long_options[lpc].name[0] == '-') { - continue; - } - - long_opts = realloc_safe(long_opts, (index + 1) * sizeof(struct option)); - /*fprintf(stderr, "Creating %d %s = %c\n", index, - * long_options[lpc].name, long_options[lpc].val); */ - long_opts[index].name = long_options[lpc].name; - long_opts[index].has_arg = long_options[lpc].has_arg; - long_opts[index].flag = long_options[lpc].flag; - long_opts[index].val = long_options[lpc].val; - index++; - } - - /* Now create the list terminator */ - long_opts = realloc_safe(long_opts, (index + 1) * sizeof(struct option)); - long_opts[index].name = NULL; - long_opts[index].has_arg = 0; - long_opts[index].flag = 0; - long_opts[index].val = 0; -#endif - - return long_opts; -} - -void -crm_set_options(const char *short_options, const char *app_usage, struct crm_option *long_options, - const char *app_desc) -{ - if (short_options) { - crm_short_options = strdup(short_options); - - } else if (long_options) { - int lpc = 0; - int opt_string_len = 0; - char *local_short_options = NULL; - - for (lpc = 0; long_options[lpc].name != NULL; lpc++) { - if (long_options[lpc].val && long_options[lpc].val != '-' && long_options[lpc].val < UCHAR_MAX) { - local_short_options = realloc_safe(local_short_options, opt_string_len + 4); - local_short_options[opt_string_len++] = long_options[lpc].val; - /* getopt(3) says: Two colons mean an option takes an optional arg; */ - if (long_options[lpc].has_arg == optional_argument) { - local_short_options[opt_string_len++] = ':'; - } - if (long_options[lpc].has_arg >= required_argument) { - local_short_options[opt_string_len++] = ':'; - } - local_short_options[opt_string_len] = 0; - } - } - crm_short_options = local_short_options; - crm_trace("Generated short option string: '%s'", local_short_options); - } - - if (long_options) { - crm_long_options = long_options; - } - if (app_desc) { - crm_app_description = app_desc; - } - if (app_usage) { - crm_app_usage = app_usage; - } -} - -int -crm_get_option(int argc, char **argv, int *index) -{ - return crm_get_option_long(argc, argv, index, NULL); -} - -int -crm_get_option_long(int argc, char **argv, int *index, const char **longname) -{ -#ifdef HAVE_GETOPT_H - static struct option *long_opts = NULL; - - if (long_opts == NULL && crm_long_options) { - long_opts = crm_create_long_opts(crm_long_options); - } - - *index = 0; - if (long_opts) { - int flag = getopt_long(argc, argv, crm_short_options, long_opts, index); - - switch (flag) { - case 0: - if (long_opts[*index].val) { - return long_opts[*index].val; - } else if (longname) { - *longname = long_opts[*index].name; - } else { - crm_notice("Unhandled option --%s", long_opts[*index].name); - return flag; - } - case -1: /* End of option processing */ - break; - case ':': - crm_trace("Missing argument"); - crm_help('?', CRM_EX_USAGE); - break; - case '?': - crm_help('?', (*index? CRM_EX_OK : CRM_EX_USAGE)); - break; - } - return flag; - } -#endif - - if (crm_short_options) { - return getopt(argc, argv, crm_short_options); - } - - return -1; -} - -void -crm_help(char cmd, crm_exit_t exit_code) -{ - int i = 0; - FILE *stream = (exit_code ? stderr : stdout); - - if (cmd == 'v' || cmd == '$') { - fprintf(stream, "Pacemaker %s\n", PACEMAKER_VERSION); - fprintf(stream, "Written by Andrew Beekhof\n"); - goto out; - } - - if (cmd == '!') { - fprintf(stream, "Pacemaker %s (Build: %s): %s\n", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES); - goto out; - } - - fprintf(stream, "%s - %s\n", crm_system_name, crm_app_description); - - if (crm_app_usage) { - fprintf(stream, "Usage: %s %s\n", crm_system_name, crm_app_usage); - } - - if (crm_long_options) { - fprintf(stream, "Options:\n"); - for (i = 0; crm_long_options[i].name != NULL; i++) { - if (crm_long_options[i].flags & pcmk_option_hidden) { - - } else if (crm_long_options[i].flags & pcmk_option_paragraph) { - fprintf(stream, "%s\n\n", crm_long_options[i].desc); - - } else if (crm_long_options[i].flags & pcmk_option_example) { - fprintf(stream, "\t#%s\n\n", crm_long_options[i].desc); - - } else if (crm_long_options[i].val == '-' && crm_long_options[i].desc) { - fprintf(stream, "%s\n", crm_long_options[i].desc); - - } else { - /* is val printable as char ? */ - if (crm_long_options[i].val && crm_long_options[i].val <= UCHAR_MAX) { - fprintf(stream, " -%c,", crm_long_options[i].val); - } else { - fputs(" ", stream); - } - fprintf(stream, " --%s%s\t%s\n", crm_long_options[i].name, - crm_long_options[i].has_arg == optional_argument ? "[=value]" : - crm_long_options[i].has_arg == required_argument ? "=value" : "", - crm_long_options[i].desc ? crm_long_options[i].desc : ""); - } - } - - } else if (crm_short_options) { - fprintf(stream, "Usage: %s - %s\n", crm_system_name, crm_app_description); - for (i = 0; crm_short_options[i] != 0; i++) { - int has_arg = no_argument /* 0 */; - - if (crm_short_options[i + 1] == ':') { - if (crm_short_options[i + 2] == ':') - has_arg = optional_argument /* 2 */; - else - has_arg = required_argument /* 1 */; - } - - fprintf(stream, " -%c %s\n", crm_short_options[i], - has_arg == optional_argument ? "[value]" : - has_arg == required_argument ? "{value}" : ""); - i += has_arg; - } - } - - fprintf(stream, "\nReport bugs to %s\n", PACKAGE_BUGREPORT); - - out: - crm_exit(exit_code); - while(1); // above does not return -} - void cib_ipc_servers_init(qb_ipcs_service_t **ipcs_ro, qb_ipcs_service_t **ipcs_rw, qb_ipcs_service_t **ipcs_shm, struct qb_ipcs_service_handlers *ro_cb, struct qb_ipcs_service_handlers *rw_cb) { *ipcs_ro = mainloop_add_ipc_server(CIB_CHANNEL_RO, QB_IPC_NATIVE, ro_cb); *ipcs_rw = mainloop_add_ipc_server(CIB_CHANNEL_RW, QB_IPC_NATIVE, rw_cb); *ipcs_shm = mainloop_add_ipc_server(CIB_CHANNEL_SHM, QB_IPC_SHM, rw_cb); if (*ipcs_ro == NULL || *ipcs_rw == NULL || *ipcs_shm == NULL) { crm_err("Failed to create the CIB manager: exiting and inhibiting respawn"); crm_warn("Verify pacemaker and pacemaker_remote are not both enabled"); crm_exit(CRM_EX_FATAL); } } void cib_ipc_servers_destroy(qb_ipcs_service_t *ipcs_ro, qb_ipcs_service_t *ipcs_rw, qb_ipcs_service_t *ipcs_shm) { qb_ipcs_destroy(ipcs_ro); qb_ipcs_destroy(ipcs_rw); qb_ipcs_destroy(ipcs_shm); } qb_ipcs_service_t * crmd_ipc_server_init(struct qb_ipcs_service_handlers *cb) { return mainloop_add_ipc_server(CRM_SYSTEM_CRMD, QB_IPC_NATIVE, cb); } void attrd_ipc_server_init(qb_ipcs_service_t **ipcs, struct qb_ipcs_service_handlers *cb) { *ipcs = mainloop_add_ipc_server(T_ATTRD, QB_IPC_NATIVE, cb); if (*ipcs == NULL) { crm_err("Failed to create pacemaker-attrd server: exiting and inhibiting respawn"); crm_warn("Verify pacemaker and pacemaker_remote are not both enabled."); crm_exit(CRM_EX_FATAL); } } void stonith_ipc_server_init(qb_ipcs_service_t **ipcs, struct qb_ipcs_service_handlers *cb) { *ipcs = mainloop_add_ipc_server_with_prio("stonith-ng", QB_IPC_NATIVE, cb, QB_LOOP_HIGH); if (*ipcs == NULL) { crm_err("Failed to create fencer: exiting and inhibiting respawn."); crm_warn("Verify pacemaker and pacemaker_remote are not both enabled."); crm_exit(CRM_EX_FATAL); } } void * find_library_function(void **handle, const char *lib, const char *fn, gboolean fatal) { char *error; void *a_function; if (*handle == NULL) { *handle = dlopen(lib, RTLD_LAZY); } if (!(*handle)) { crm_err("%sCould not open %s: %s", fatal ? "Fatal: " : "", lib, dlerror()); if (fatal) { crm_exit(CRM_EX_FATAL); } return NULL; } a_function = dlsym(*handle, fn); if (a_function == NULL) { error = dlerror(); crm_err("%sCould not find %s in %s: %s", fatal ? "Fatal: " : "", fn, lib, error); if (fatal) { crm_exit(CRM_EX_FATAL); } } return a_function; } #ifdef HAVE_UUID_UUID_H # include #endif char * crm_generate_uuid(void) { unsigned char uuid[16]; char *buffer = malloc(37); /* Including NUL byte */ uuid_generate(uuid); uuid_unparse(uuid, buffer); return buffer; } /*! * \brief Get name to be used as identifier for cluster messages * * \param[in] name Actual system name to check * * \return Non-NULL cluster message identifier corresponding to name * * \note The Pacemaker daemons were renamed in version 2.0.0, but the old names * must continue to be used as the identifier for cluster messages, so * that mixed-version clusters are possible during a rolling upgrade. */ const char * pcmk_message_name(const char *name) { if (name == NULL) { return "unknown"; } else if (!strcmp(name, "pacemaker-attrd")) { return "attrd"; } else if (!strcmp(name, "pacemaker-based")) { return CRM_SYSTEM_CIB; } else if (!strcmp(name, "pacemaker-controld")) { return CRM_SYSTEM_CRMD; } else if (!strcmp(name, "pacemaker-execd")) { return CRM_SYSTEM_LRMD; } else if (!strcmp(name, "pacemaker-fenced")) { return "stonith-ng"; } else if (!strcmp(name, "pacemaker-schedulerd")) { return CRM_SYSTEM_PENGINE; } else { return name; } } /*! * \brief Check whether a string represents a cluster daemon name * * \param[in] name String to check * * \return TRUE if name is standard client name used by daemons, FALSE otherwise */ bool crm_is_daemon_name(const char *name) { name = pcmk_message_name(name); return (!strcmp(name, CRM_SYSTEM_CRMD) || !strcmp(name, CRM_SYSTEM_STONITHD) || !strcmp(name, "stonith-ng") || !strcmp(name, "attrd") || !strcmp(name, CRM_SYSTEM_CIB) || !strcmp(name, CRM_SYSTEM_MCP) || !strcmp(name, CRM_SYSTEM_DC) || !strcmp(name, CRM_SYSTEM_TENGINE) || !strcmp(name, CRM_SYSTEM_LRMD)); } #include char * crm_md5sum(const char *buffer) { int lpc = 0, len = 0; char *digest = NULL; unsigned char raw_digest[MD5_DIGEST_SIZE]; if (buffer == NULL) { buffer = ""; } len = strlen(buffer); crm_trace("Beginning digest of %d bytes", len); digest = malloc(2 * MD5_DIGEST_SIZE + 1); if(digest) { md5_buffer(buffer, len, raw_digest); for (lpc = 0; lpc < MD5_DIGEST_SIZE; lpc++) { sprintf(digest + (2 * lpc), "%02x", raw_digest[lpc]); } digest[(2 * MD5_DIGEST_SIZE)] = 0; crm_trace("Digest %s.", digest); } else { crm_err("Could not create digest"); } return digest; } #ifdef HAVE_GNUTLS_GNUTLS_H void crm_gnutls_global_init(void) { signal(SIGPIPE, SIG_IGN); gnutls_global_init(); } #endif /*! * \brief Get the local hostname * * \return Newly allocated string with name, or NULL (and set errno) on error */ char * pcmk_hostname() { struct utsname hostinfo; return (uname(&hostinfo) < 0)? NULL : strdup(hostinfo.nodename); } diff --git a/lib/common/watchdog.c b/lib/common/watchdog.c index 848236cebc..8dadfa13e8 100644 --- a/lib/common/watchdog.c +++ b/lib/common/watchdog.c @@ -1,278 +1,278 @@ /* * Copyright 2013-2020 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 #ifdef _POSIX_MEMLOCK # include #endif static pid_t sbd_pid = 0; enum pcmk_panic_flags { pcmk_panic_none = 0x00, pcmk_panic_delay = 0x01, pcmk_panic_kdump = 0x02, pcmk_panic_shutdown = 0x04, }; static void sysrq_trigger(char t) { #if SUPPORT_PROCFS FILE *procf; // Root can always write here, regardless of kernel.sysrq value procf = fopen("/proc/sysrq-trigger", "a"); if (!procf) { crm_perror(LOG_WARNING, "Opening sysrq-trigger failed"); return; } crm_info("sysrq-trigger: %c", t); fprintf(procf, "%c\n", t); fclose(procf); #endif // SUPPORT_PROCFS return; } static void pcmk_panic_local(void) { int rc = pcmk_ok; uid_t uid = geteuid(); pid_t ppid = getppid(); if(uid != 0 && ppid > 1) { /* We're a non-root pacemaker daemon (pacemaker-based, * pacemaker-controld, pacemaker-schedulerd, pacemaker-attrd, etc.) with * the original pacemakerd parent. * * Of these, only the controller is likely to be initiating resets. */ do_crm_log_always(LOG_EMERG, "Signaling parent %lld to panic", (long long) ppid); crm_exit(CRM_EX_PANIC); return; } else if (uid != 0) { #if SUPPORT_PROCFS /* * No permissions, and no pacemakerd parent to escalate to. * Track down the new pacemakerd process and send a signal instead. */ union sigval signal_value; memset(&signal_value, 0, sizeof(signal_value)); ppid = pcmk__procfs_pid_of("pacemakerd"); do_crm_log_always(LOG_EMERG, "Signaling pacemakerd[%lld] to panic", (long long) ppid); if(ppid > 1 && sigqueue(ppid, SIGQUIT, signal_value) < 0) { crm_perror(LOG_EMERG, "Cannot signal pacemakerd[%lld] to panic", (long long) ppid); } #endif // SUPPORT_PROCFS /* The best we can do now is die */ crm_exit(CRM_EX_PANIC); return; } /* We're either pacemakerd, or a pacemaker daemon running as root */ if (safe_str_eq("crash", getenv("PCMK_panic_action"))) { sysrq_trigger('c'); } else { sysrq_trigger('b'); } /* reboot(RB_HALT_SYSTEM); rc = errno; */ reboot(RB_AUTOBOOT); rc = errno; do_crm_log_always(LOG_EMERG, "Reboot failed, escalating to parent %lld: %s " CRM_XS " rc=%d", (long long) ppid, pcmk_rc_str(rc), rc); if(ppid > 1) { /* child daemon */ exit(CRM_EX_PANIC); } else { /* pacemakerd or orphan child */ exit(CRM_EX_FATAL); } } static void pcmk_panic_sbd(void) { union sigval signal_value; pid_t ppid = getppid(); do_crm_log_always(LOG_EMERG, "Signaling sbd[%lld] to panic", (long long) sbd_pid); memset(&signal_value, 0, sizeof(signal_value)); /* TODO: Arrange for a slightly less brutal option? */ if(sigqueue(sbd_pid, SIGKILL, signal_value) < 0) { crm_perror(LOG_EMERG, "Cannot signal sbd[%lld] to terminate", (long long) sbd_pid); pcmk_panic_local(); } if(ppid > 1) { /* child daemon */ exit(CRM_EX_PANIC); } else { /* pacemakerd or orphan child */ exit(CRM_EX_FATAL); } } void pcmk_panic(const char *origin) { static struct qb_log_callsite *panic_cs = NULL; if (panic_cs == NULL) { panic_cs = qb_log_callsite_get(__func__, __FILE__, "panic-delay", LOG_TRACE, __LINE__, crm_trace_nonlog); } /* Ensure sbd_pid is set */ (void)pcmk_locate_sbd(); if (panic_cs && panic_cs->targets) { /* getppid() == 1 means our original parent no longer exists */ do_crm_log_always(LOG_EMERG, "Shutting down instead of panicking the node " CRM_XS " origin=%s sbd=%lld parent=%d", origin, (long long) sbd_pid, getppid()); crm_exit(CRM_EX_FATAL); return; } if(sbd_pid > 1) { do_crm_log_always(LOG_EMERG, "Signaling sbd[%lld] to panic the system: %s", (long long) sbd_pid, origin); pcmk_panic_sbd(); } else { do_crm_log_always(LOG_EMERG, "Panicking the system directly: %s", origin); pcmk_panic_local(); } } pid_t pcmk_locate_sbd(void) { char *pidfile = NULL; char *sbd_path = NULL; int rc; if(sbd_pid > 1) { return sbd_pid; } /* Look for the pid file */ pidfile = crm_strdup_printf(PCMK_RUN_DIR "/sbd.pid"); sbd_path = crm_strdup_printf("%s/sbd", SBIN_DIR); /* Read the pid file */ rc = pcmk__pidfile_matches(pidfile, 0, sbd_path, &sbd_pid); if (rc == pcmk_rc_ok) { crm_trace("SBD detected at pid %lld (via PID file %s)", (long long) sbd_pid, pidfile); #if SUPPORT_PROCFS } else { /* Fall back to /proc for systems that support it */ sbd_pid = pcmk__procfs_pid_of("sbd"); crm_trace("SBD detected at pid %lld (via procfs)", (long long) sbd_pid); #endif // SUPPORT_PROCFS } if(sbd_pid < 0) { sbd_pid = 0; crm_trace("SBD not detected"); } free(pidfile); free(sbd_path); return sbd_pid; } long -crm_get_sbd_timeout(void) +pcmk__get_sbd_timeout(void) { static long sbd_timeout = -2; if (sbd_timeout == -2) { sbd_timeout = crm_get_msec(getenv("SBD_WATCHDOG_TIMEOUT")); } return sbd_timeout; } long -crm_auto_watchdog_timeout() +pcmk__auto_watchdog_timeout() { - long sbd_timeout = crm_get_sbd_timeout(); + long sbd_timeout = pcmk__get_sbd_timeout(); return (sbd_timeout <= 0)? 0 : (2 * sbd_timeout); } -gboolean -check_sbd_timeout(const char *value) +bool +pcmk__valid_sbd_timeout(const char *value) { long st_timeout = value? crm_get_msec(value) : 0; if (st_timeout < 0) { - st_timeout = crm_auto_watchdog_timeout(); + st_timeout = pcmk__auto_watchdog_timeout(); crm_debug("Using calculated value %ld for stonith-watchdog-timeout (%s)", st_timeout, value); } if (st_timeout == 0) { crm_debug("Watchdog may be enabled but stonith-watchdog-timeout is disabled (%s)", value? value : "default"); } else if (pcmk_locate_sbd() == 0) { do_crm_log_always(LOG_EMERG, "Shutting down: stonith-watchdog-timeout configured (%s) but SBD not active", (value? value : "auto")); crm_exit(CRM_EX_FATAL); - return FALSE; + return false; } else { - long sbd_timeout = crm_get_sbd_timeout(); + long sbd_timeout = pcmk__get_sbd_timeout(); if (st_timeout < sbd_timeout) { do_crm_log_always(LOG_EMERG, "Shutting down: stonith-watchdog-timeout (%s) too short (must be >%ldms)", value, sbd_timeout); crm_exit(CRM_EX_FATAL); - return FALSE; + return false; } crm_info("Watchdog configured with stonith-watchdog-timeout %s and SBD timeout %ldms", value, sbd_timeout); } - return TRUE; + return true; } diff --git a/lib/pengine/bundle.c b/lib/pengine/bundle.c index 562ec97332..cbc9ba5c49 100644 --- a/lib/pengine/bundle.c +++ b/lib/pengine/bundle.c @@ -1,1940 +1,1940 @@ /* * Copyright 2004-2020 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 #define PE__VARIANT_BUNDLE 1 #include "./variant.h" static char * next_ip(const char *last_ip) { unsigned int oct1 = 0; unsigned int oct2 = 0; unsigned int oct3 = 0; unsigned int oct4 = 0; int rc = sscanf(last_ip, "%u.%u.%u.%u", &oct1, &oct2, &oct3, &oct4); if (rc != 4) { /*@ TODO check for IPv6 */ return NULL; } else if (oct3 > 253) { return NULL; } else if (oct4 > 253) { ++oct3; oct4 = 1; } else { ++oct4; } return crm_strdup_printf("%u.%u.%u.%u", oct1, oct2, oct3, oct4); } static int allocate_ip(pe__bundle_variant_data_t *data, pe__bundle_replica_t *replica, char *buffer, int max) { if(data->ip_range_start == NULL) { return 0; } else if(data->ip_last) { replica->ipaddr = next_ip(data->ip_last); } else { replica->ipaddr = strdup(data->ip_range_start); } data->ip_last = replica->ipaddr; switch (data->agent_type) { case PE__CONTAINER_AGENT_DOCKER: case PE__CONTAINER_AGENT_PODMAN: if (data->add_host) { return snprintf(buffer, max, " --add-host=%s-%d:%s", data->prefix, replica->offset, replica->ipaddr); } case PE__CONTAINER_AGENT_RKT: return snprintf(buffer, max, " --hosts-entry=%s=%s-%d", replica->ipaddr, data->prefix, replica->offset); default: // PE__CONTAINER_AGENT_UNKNOWN break; } return 0; } static xmlNode * create_resource(const char *name, const char *provider, const char *kind) { xmlNode *rsc = create_xml_node(NULL, XML_CIB_TAG_RESOURCE); crm_xml_add(rsc, XML_ATTR_ID, name); crm_xml_add(rsc, XML_AGENT_ATTR_CLASS, PCMK_RESOURCE_CLASS_OCF); crm_xml_add(rsc, XML_AGENT_ATTR_PROVIDER, provider); crm_xml_add(rsc, XML_ATTR_TYPE, kind); return rsc; } /*! * \internal * \brief Check whether cluster can manage resource inside container * * \param[in] data Container variant data * * \return TRUE if networking configuration is acceptable, FALSE otherwise * * \note The resource is manageable if an IP range or control port has been * specified. If a control port is used without an IP range, replicas per * host must be 1. */ static bool valid_network(pe__bundle_variant_data_t *data) { if(data->ip_range_start) { return TRUE; } if(data->control_port) { if(data->nreplicas_per_host > 1) { pe_err("Specifying the 'control-port' for %s requires 'replicas-per-host=1'", data->prefix); data->nreplicas_per_host = 1; /* @TODO to be sure: clear_bit(rsc->flags, pe_rsc_unique); */ } return TRUE; } return FALSE; } static bool create_ip_resource(pe_resource_t *parent, pe__bundle_variant_data_t *data, pe__bundle_replica_t *replica, pe_working_set_t *data_set) { if(data->ip_range_start) { char *id = NULL; xmlNode *xml_ip = NULL; xmlNode *xml_obj = NULL; id = crm_strdup_printf("%s-ip-%s", data->prefix, replica->ipaddr); crm_xml_sanitize_id(id); xml_ip = create_resource(id, "heartbeat", "IPaddr2"); free(id); xml_obj = create_xml_node(xml_ip, XML_TAG_ATTR_SETS); crm_xml_set_id(xml_obj, "%s-attributes-%d", data->prefix, replica->offset); crm_create_nvpair_xml(xml_obj, NULL, "ip", replica->ipaddr); if(data->host_network) { crm_create_nvpair_xml(xml_obj, NULL, "nic", data->host_network); } if(data->host_netmask) { crm_create_nvpair_xml(xml_obj, NULL, "cidr_netmask", data->host_netmask); } else { crm_create_nvpair_xml(xml_obj, NULL, "cidr_netmask", "32"); } xml_obj = create_xml_node(xml_ip, "operations"); crm_create_op_xml(xml_obj, ID(xml_ip), "monitor", "60s", NULL); // TODO: Other ops? Timeouts and intervals from underlying resource? if (!common_unpack(xml_ip, &replica->ip, parent, data_set)) { return FALSE; } parent->children = g_list_append(parent->children, replica->ip); } return TRUE; } static bool create_docker_resource(pe_resource_t *parent, pe__bundle_variant_data_t *data, pe__bundle_replica_t *replica, pe_working_set_t *data_set) { int offset = 0, max = 4096; char *buffer = calloc(1, max+1); int doffset = 0, dmax = 1024; char *dbuffer = calloc(1, dmax+1); char *id = NULL; xmlNode *xml_container = NULL; xmlNode *xml_obj = NULL; id = crm_strdup_printf("%s-docker-%d", data->prefix, replica->offset); crm_xml_sanitize_id(id); xml_container = create_resource(id, "heartbeat", PE__CONTAINER_AGENT_DOCKER_S); free(id); xml_obj = create_xml_node(xml_container, XML_TAG_ATTR_SETS); crm_xml_set_id(xml_obj, "%s-attributes-%d", data->prefix, replica->offset); crm_create_nvpair_xml(xml_obj, NULL, "image", data->image); crm_create_nvpair_xml(xml_obj, NULL, "allow_pull", XML_BOOLEAN_TRUE); crm_create_nvpair_xml(xml_obj, NULL, "force_kill", XML_BOOLEAN_FALSE); crm_create_nvpair_xml(xml_obj, NULL, "reuse", XML_BOOLEAN_FALSE); offset += snprintf(buffer+offset, max-offset, " --restart=no"); /* Set a container hostname only if we have an IP to map it to. * The user can set -h or --uts=host themselves if they want a nicer * name for logs, but this makes applications happy who need their * hostname to match the IP they bind to. */ if (data->ip_range_start != NULL) { offset += snprintf(buffer+offset, max-offset, " -h %s-%d", data->prefix, replica->offset); } offset += snprintf(buffer+offset, max-offset, " -e PCMK_stderr=1"); if (data->container_network) { #if 0 offset += snprintf(buffer+offset, max-offset, " --link-local-ip=%s", replica->ipaddr); #endif offset += snprintf(buffer+offset, max-offset, " --net=%s", data->container_network); } if(data->control_port) { offset += snprintf(buffer+offset, max-offset, " -e PCMK_remote_port=%s", data->control_port); } else { offset += snprintf(buffer+offset, max-offset, " -e PCMK_remote_port=%d", DEFAULT_REMOTE_PORT); } for(GListPtr pIter = data->mounts; pIter != NULL; pIter = pIter->next) { pe__bundle_mount_t *mount = pIter->data; if (is_set(mount->flags, pe__bundle_mount_subdir)) { char *source = crm_strdup_printf( "%s/%s-%d", mount->source, data->prefix, replica->offset); if(doffset > 0) { doffset += snprintf(dbuffer+doffset, dmax-doffset, ","); } doffset += snprintf(dbuffer+doffset, dmax-doffset, "%s", source); offset += snprintf(buffer+offset, max-offset, " -v %s:%s", source, mount->target); free(source); } else { offset += snprintf(buffer+offset, max-offset, " -v %s:%s", mount->source, mount->target); } if(mount->options) { offset += snprintf(buffer+offset, max-offset, ":%s", mount->options); } } for(GListPtr pIter = data->ports; pIter != NULL; pIter = pIter->next) { pe__bundle_port_t *port = pIter->data; if (replica->ipaddr) { offset += snprintf(buffer+offset, max-offset, " -p %s:%s:%s", replica->ipaddr, port->source, port->target); } else if(safe_str_neq(data->container_network, "host")) { // No need to do port mapping if net=host offset += snprintf(buffer+offset, max-offset, " -p %s:%s", port->source, port->target); } } if (data->launcher_options) { offset += snprintf(buffer+offset, max-offset, " %s", data->launcher_options); } if (data->container_host_options) { offset += snprintf(buffer + offset, max - offset, " %s", data->container_host_options); } crm_create_nvpair_xml(xml_obj, NULL, "run_opts", buffer); free(buffer); crm_create_nvpair_xml(xml_obj, NULL, "mount_points", dbuffer); free(dbuffer); if (replica->child) { if (data->container_command) { crm_create_nvpair_xml(xml_obj, NULL, "run_cmd", data->container_command); } else { crm_create_nvpair_xml(xml_obj, NULL, "run_cmd", SBIN_DIR "/pacemaker-remoted"); } /* TODO: Allow users to specify their own? * * We just want to know if the container is alive, we'll * monitor the child independently */ crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", "/bin/true"); /* } else if(child && data->untrusted) { * Support this use-case? * * The ability to have resources started/stopped by us, but * unable to set attributes, etc. * * Arguably better to control API access this with ACLs like * "normal" remote nodes * * crm_create_nvpair_xml(xml_obj, NULL, * "run_cmd", * "/usr/libexec/pacemaker/pacemaker-execd"); * crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", * "/usr/libexec/pacemaker/lrmd_internal_ctl -c poke"); */ } else { if (data->container_command) { crm_create_nvpair_xml(xml_obj, NULL, "run_cmd", data->container_command); } /* TODO: Allow users to specify their own? * * We don't know what's in the container, so we just want * to know if it is alive */ crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", "/bin/true"); } xml_obj = create_xml_node(xml_container, "operations"); crm_create_op_xml(xml_obj, ID(xml_container), "monitor", "60s", NULL); // TODO: Other ops? Timeouts and intervals from underlying resource? if (!common_unpack(xml_container, &replica->container, parent, data_set)) { return FALSE; } parent->children = g_list_append(parent->children, replica->container); return TRUE; } static bool create_podman_resource(pe_resource_t *parent, pe__bundle_variant_data_t *data, pe__bundle_replica_t *replica, pe_working_set_t *data_set) { int offset = 0, max = 4096; char *buffer = calloc(1, max+1); int doffset = 0, dmax = 1024; char *dbuffer = calloc(1, dmax+1); char *id = NULL; xmlNode *xml_container = NULL; xmlNode *xml_obj = NULL; id = crm_strdup_printf("%s-podman-%d", data->prefix, replica->offset); crm_xml_sanitize_id(id); xml_container = create_resource(id, "heartbeat", PE__CONTAINER_AGENT_PODMAN_S); free(id); xml_obj = create_xml_node(xml_container, XML_TAG_ATTR_SETS); crm_xml_set_id(xml_obj, "%s-attributes-%d", data->prefix, replica->offset); crm_create_nvpair_xml(xml_obj, NULL, "image", data->image); crm_create_nvpair_xml(xml_obj, NULL, "allow_pull", XML_BOOLEAN_TRUE); crm_create_nvpair_xml(xml_obj, NULL, "force_kill", XML_BOOLEAN_FALSE); crm_create_nvpair_xml(xml_obj, NULL, "reuse", XML_BOOLEAN_FALSE); // FIXME: (bandini 2018-08) podman has no restart policies //offset += snprintf(buffer+offset, max-offset, " --restart=no"); /* Set a container hostname only if we have an IP to map it to. * The user can set -h or --uts=host themselves if they want a nicer * name for logs, but this makes applications happy who need their * hostname to match the IP they bind to. */ if (data->ip_range_start != NULL) { offset += snprintf(buffer+offset, max-offset, " -h %s-%d", data->prefix, replica->offset); } offset += snprintf(buffer+offset, max-offset, " -e PCMK_stderr=1"); if (data->container_network) { #if 0 // podman has no support for --link-local-ip offset += snprintf(buffer+offset, max-offset, " --link-local-ip=%s", replica->ipaddr); #endif offset += snprintf(buffer+offset, max-offset, " --net=%s", data->container_network); } if(data->control_port) { offset += snprintf(buffer+offset, max-offset, " -e PCMK_remote_port=%s", data->control_port); } else { offset += snprintf(buffer+offset, max-offset, " -e PCMK_remote_port=%d", DEFAULT_REMOTE_PORT); } for(GListPtr pIter = data->mounts; pIter != NULL; pIter = pIter->next) { pe__bundle_mount_t *mount = pIter->data; if (is_set(mount->flags, pe__bundle_mount_subdir)) { char *source = crm_strdup_printf( "%s/%s-%d", mount->source, data->prefix, replica->offset); if(doffset > 0) { doffset += snprintf(dbuffer+doffset, dmax-doffset, ","); } doffset += snprintf(dbuffer+doffset, dmax-doffset, "%s", source); offset += snprintf(buffer+offset, max-offset, " -v %s:%s", source, mount->target); free(source); } else { offset += snprintf(buffer+offset, max-offset, " -v %s:%s", mount->source, mount->target); } if(mount->options) { offset += snprintf(buffer+offset, max-offset, ":%s", mount->options); } } for(GListPtr pIter = data->ports; pIter != NULL; pIter = pIter->next) { pe__bundle_port_t *port = pIter->data; if (replica->ipaddr) { offset += snprintf(buffer+offset, max-offset, " -p %s:%s:%s", replica->ipaddr, port->source, port->target); } else if(safe_str_neq(data->container_network, "host")) { // No need to do port mapping if net=host offset += snprintf(buffer+offset, max-offset, " -p %s:%s", port->source, port->target); } } if (data->launcher_options) { offset += snprintf(buffer+offset, max-offset, " %s", data->launcher_options); } if (data->container_host_options) { offset += snprintf(buffer + offset, max - offset, " %s", data->container_host_options); } crm_create_nvpair_xml(xml_obj, NULL, "run_opts", buffer); free(buffer); crm_create_nvpair_xml(xml_obj, NULL, "mount_points", dbuffer); free(dbuffer); if (replica->child) { if (data->container_command) { crm_create_nvpair_xml(xml_obj, NULL, "run_cmd", data->container_command); } else { crm_create_nvpair_xml(xml_obj, NULL, "run_cmd", SBIN_DIR "/pacemaker-remoted"); } /* TODO: Allow users to specify their own? * * We just want to know if the container is alive, we'll * monitor the child independently */ crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", "/bin/true"); /* } else if(child && data->untrusted) { * Support this use-case? * * The ability to have resources started/stopped by us, but * unable to set attributes, etc. * * Arguably better to control API access this with ACLs like * "normal" remote nodes * * crm_create_nvpair_xml(xml_obj, NULL, * "run_cmd", * "/usr/libexec/pacemaker/pacemaker-execd"); * crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", * "/usr/libexec/pacemaker/lrmd_internal_ctl -c poke"); */ } else { if (data->container_command) { crm_create_nvpair_xml(xml_obj, NULL, "run_cmd", data->container_command); } /* TODO: Allow users to specify their own? * * We don't know what's in the container, so we just want * to know if it is alive */ crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", "/bin/true"); } xml_obj = create_xml_node(xml_container, "operations"); crm_create_op_xml(xml_obj, ID(xml_container), "monitor", "60s", NULL); // TODO: Other ops? Timeouts and intervals from underlying resource? if (!common_unpack(xml_container, &replica->container, parent, data_set)) { return FALSE; } parent->children = g_list_append(parent->children, replica->container); return TRUE; } static bool create_rkt_resource(pe_resource_t *parent, pe__bundle_variant_data_t *data, pe__bundle_replica_t *replica, pe_working_set_t *data_set) { int offset = 0, max = 4096; char *buffer = calloc(1, max+1); int doffset = 0, dmax = 1024; char *dbuffer = calloc(1, dmax+1); char *id = NULL; xmlNode *xml_container = NULL; xmlNode *xml_obj = NULL; int volid = 0; id = crm_strdup_printf("%s-rkt-%d", data->prefix, replica->offset); crm_xml_sanitize_id(id); xml_container = create_resource(id, "heartbeat", PE__CONTAINER_AGENT_RKT_S); free(id); xml_obj = create_xml_node(xml_container, XML_TAG_ATTR_SETS); crm_xml_set_id(xml_obj, "%s-attributes-%d", data->prefix, replica->offset); crm_create_nvpair_xml(xml_obj, NULL, "image", data->image); crm_create_nvpair_xml(xml_obj, NULL, "allow_pull", "true"); crm_create_nvpair_xml(xml_obj, NULL, "force_kill", "false"); crm_create_nvpair_xml(xml_obj, NULL, "reuse", "false"); /* Set a container hostname only if we have an IP to map it to. * The user can set -h or --uts=host themselves if they want a nicer * name for logs, but this makes applications happy who need their * hostname to match the IP they bind to. */ if (data->ip_range_start != NULL) { offset += snprintf(buffer+offset, max-offset, " --hostname=%s-%d", data->prefix, replica->offset); } offset += snprintf(buffer+offset, max-offset, " --environment=PCMK_stderr=1"); if (data->container_network) { #if 0 offset += snprintf(buffer+offset, max-offset, " --link-local-ip=%s", replica->ipaddr); #endif offset += snprintf(buffer+offset, max-offset, " --net=%s", data->container_network); } if(data->control_port) { offset += snprintf(buffer+offset, max-offset, " --environment=PCMK_remote_port=%s", data->control_port); } else { offset += snprintf(buffer+offset, max-offset, " --environment=PCMK_remote_port=%d", DEFAULT_REMOTE_PORT); } for(GListPtr pIter = data->mounts; pIter != NULL; pIter = pIter->next) { pe__bundle_mount_t *mount = pIter->data; if (is_set(mount->flags, pe__bundle_mount_subdir)) { char *source = crm_strdup_printf( "%s/%s-%d", mount->source, data->prefix, replica->offset); if(doffset > 0) { doffset += snprintf(dbuffer+doffset, dmax-doffset, ","); } doffset += snprintf(dbuffer+doffset, dmax-doffset, "%s", source); offset += snprintf(buffer+offset, max-offset, " --volume vol%d,kind=host,source=%s", volid, source); if(mount->options) { offset += snprintf(buffer+offset, max-offset, ",%s", mount->options); } offset += snprintf(buffer+offset, max-offset, " --mount volume=vol%d,target=%s", volid, mount->target); free(source); } else { offset += snprintf(buffer+offset, max-offset, " --volume vol%d,kind=host,source=%s", volid, mount->source); if(mount->options) { offset += snprintf(buffer+offset, max-offset, ",%s", mount->options); } offset += snprintf(buffer+offset, max-offset, " --mount volume=vol%d,target=%s", volid, mount->target); } volid++; } for(GListPtr pIter = data->ports; pIter != NULL; pIter = pIter->next) { pe__bundle_port_t *port = pIter->data; if (replica->ipaddr) { offset += snprintf(buffer+offset, max-offset, " --port=%s:%s:%s", port->target, replica->ipaddr, port->source); } else { offset += snprintf(buffer+offset, max-offset, " --port=%s:%s", port->target, port->source); } } if (data->launcher_options) { offset += snprintf(buffer+offset, max-offset, " %s", data->launcher_options); } if (data->container_host_options) { offset += snprintf(buffer + offset, max - offset, " %s", data->container_host_options); } crm_create_nvpair_xml(xml_obj, NULL, "run_opts", buffer); free(buffer); crm_create_nvpair_xml(xml_obj, NULL, "mount_points", dbuffer); free(dbuffer); if (replica->child) { if (data->container_command) { crm_create_nvpair_xml(xml_obj, NULL, "run_cmd", data->container_command); } else { crm_create_nvpair_xml(xml_obj, NULL, "run_cmd", SBIN_DIR "/pacemaker-remoted"); } /* TODO: Allow users to specify their own? * * We just want to know if the container is alive, we'll * monitor the child independently */ crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", "/bin/true"); /* } else if(child && data->untrusted) { * Support this use-case? * * The ability to have resources started/stopped by us, but * unable to set attributes, etc. * * Arguably better to control API access this with ACLs like * "normal" remote nodes * * crm_create_nvpair_xml(xml_obj, NULL, * "run_cmd", * "/usr/libexec/pacemaker/pacemaker-execd"); * crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", * "/usr/libexec/pacemaker/lrmd_internal_ctl -c poke"); */ } else { if (data->container_command) { crm_create_nvpair_xml(xml_obj, NULL, "run_cmd", data->container_command); } /* TODO: Allow users to specify their own? * * We don't know what's in the container, so we just want * to know if it is alive */ crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", "/bin/true"); } xml_obj = create_xml_node(xml_container, "operations"); crm_create_op_xml(xml_obj, ID(xml_container), "monitor", "60s", NULL); // TODO: Other ops? Timeouts and intervals from underlying resource? if (!common_unpack(xml_container, &replica->container, parent, data_set)) { return FALSE; } parent->children = g_list_append(parent->children, replica->container); return TRUE; } /*! * \brief Ban a node from a resource's (and its children's) allowed nodes list * * \param[in,out] rsc Resource to modify * \param[in] uname Name of node to ban */ static void disallow_node(resource_t *rsc, const char *uname) { gpointer match = g_hash_table_lookup(rsc->allowed_nodes, uname); if (match) { ((pe_node_t *) match)->weight = -INFINITY; ((pe_node_t *) match)->rsc_discover_mode = pe_discover_never; } if (rsc->children) { GListPtr child; for (child = rsc->children; child != NULL; child = child->next) { disallow_node((resource_t *) (child->data), uname); } } } static bool create_remote_resource(pe_resource_t *parent, pe__bundle_variant_data_t *data, pe__bundle_replica_t *replica, pe_working_set_t *data_set) { if (replica->child && valid_network(data)) { GHashTableIter gIter; GListPtr rsc_iter = NULL; node_t *node = NULL; xmlNode *xml_remote = NULL; char *id = crm_strdup_printf("%s-%d", data->prefix, replica->offset); char *port_s = NULL; const char *uname = NULL; const char *connect_name = NULL; if (pe_find_resource(data_set->resources, id) != NULL) { free(id); // The biggest hammer we have id = crm_strdup_printf("pcmk-internal-%s-remote-%d", replica->child->id, replica->offset); //@TODO return false instead of asserting? CRM_ASSERT(pe_find_resource(data_set->resources, id) == NULL); } /* REMOTE_CONTAINER_HACK: Using "#uname" as the server name when the * connection does not have its own IP is a magic string that we use to * support nested remotes (i.e. a bundle running on a remote node). */ connect_name = (replica->ipaddr? replica->ipaddr : "#uname"); if (data->control_port == NULL) { port_s = crm_itoa(DEFAULT_REMOTE_PORT); } /* This sets replica->container as replica->remote's container, which is * similar to what happens with guest nodes. This is how the scheduler * knows that the bundle node is fenced by recovering the container, and * that remote should be ordered relative to the container. */ xml_remote = pe_create_remote_xml(NULL, id, replica->container->id, NULL, NULL, NULL, connect_name, (data->control_port? data->control_port : port_s)); free(port_s); /* Abandon our created ID, and pull the copy from the XML, because we * need something that will get freed during data set cleanup to use as * the node ID and uname. */ free(id); id = NULL; uname = ID(xml_remote); /* Ensure a node has been created for the guest (it may have already * been, if it has a permanent node attribute), and ensure its weight is * -INFINITY so no other resources can run on it. */ node = pe_find_node(data_set->nodes, uname); if (node == NULL) { node = pe_create_node(uname, uname, "remote", "-INFINITY", data_set); } else { node->weight = -INFINITY; } node->rsc_discover_mode = pe_discover_never; /* unpack_remote_nodes() ensures that each remote node and guest node * has a pe_node_t entry. Ideally, it would do the same for bundle nodes. * Unfortunately, a bundle has to be mostly unpacked before it's obvious * what nodes will be needed, so we do it just above. * * Worse, that means that the node may have been utilized while * unpacking other resources, without our weight correction. The most * likely place for this to happen is when common_unpack() calls * resource_location() to set a default score in symmetric clusters. * This adds a node *copy* to each resource's allowed nodes, and these * copies will have the wrong weight. * * As a hacky workaround, fix those copies here. * * @TODO Possible alternative: ensure bundles are unpacked before other * resources, so the weight is correct before any copies are made. */ for (rsc_iter = data_set->resources; rsc_iter; rsc_iter = rsc_iter->next) { disallow_node((resource_t *) (rsc_iter->data), uname); } replica->node = node_copy(node); replica->node->weight = 500; replica->node->rsc_discover_mode = pe_discover_exclusive; /* Ensure the node shows up as allowed and with the correct discovery set */ if (replica->child->allowed_nodes != NULL) { g_hash_table_destroy(replica->child->allowed_nodes); } replica->child->allowed_nodes = g_hash_table_new_full(crm_str_hash, g_str_equal, NULL, free); g_hash_table_insert(replica->child->allowed_nodes, (gpointer) replica->node->details->id, node_copy(replica->node)); { node_t *copy = node_copy(replica->node); copy->weight = -INFINITY; g_hash_table_insert(replica->child->parent->allowed_nodes, (gpointer) replica->node->details->id, copy); } if (!common_unpack(xml_remote, &replica->remote, parent, data_set)) { return FALSE; } g_hash_table_iter_init(&gIter, replica->remote->allowed_nodes); while (g_hash_table_iter_next(&gIter, NULL, (void **)&node)) { if (pe__is_guest_or_remote_node(node)) { /* Remote resources can only run on 'normal' cluster node */ node->weight = -INFINITY; } } replica->node->details->remote_rsc = replica->remote; // Ensure pe__is_guest_node() functions correctly immediately replica->remote->container = replica->container; /* A bundle's #kind is closer to "container" (guest node) than the * "remote" set by pe_create_node(). */ g_hash_table_insert(replica->node->details->attrs, strdup(CRM_ATTR_KIND), strdup("container")); /* One effect of this is that setup_container() will add * replica->remote to replica->container's fillers, which will make * pe__resource_contains_guest_node() true for replica->container. * * replica->child does NOT get added to replica->container's fillers. * The only noticeable effect if it did would be for its fail count to * be taken into account when checking replica->container's migration * threshold. */ parent->children = g_list_append(parent->children, replica->remote); } return TRUE; } static bool create_container(pe_resource_t *parent, pe__bundle_variant_data_t *data, pe__bundle_replica_t *replica, pe_working_set_t *data_set) { switch (data->agent_type) { case PE__CONTAINER_AGENT_DOCKER: if (!create_docker_resource(parent, data, replica, data_set)) { return FALSE; } break; case PE__CONTAINER_AGENT_PODMAN: if (!create_podman_resource(parent, data, replica, data_set)) { return FALSE; } break; case PE__CONTAINER_AGENT_RKT: if (!create_rkt_resource(parent, data, replica, data_set)) { return FALSE; } break; default: // PE__CONTAINER_AGENT_UNKNOWN return FALSE; } if (create_ip_resource(parent, data, replica, data_set) == FALSE) { return FALSE; } if(create_remote_resource(parent, data, replica, data_set) == FALSE) { return FALSE; } if (replica->child && replica->ipaddr) { add_hash_param(replica->child->meta, "external-ip", replica->ipaddr); } if (replica->remote) { /* * Allow the remote connection resource to be allocated to a * different node than the one on which the container is active. * * This makes it possible to have Pacemaker Remote nodes running * containers with pacemaker-remoted inside in order to start * services inside those containers. */ set_bit(replica->remote->flags, pe_rsc_allow_remote_remotes); } return TRUE; } static void mount_add(pe__bundle_variant_data_t *bundle_data, const char *source, const char *target, const char *options, uint32_t flags) { pe__bundle_mount_t *mount = calloc(1, sizeof(pe__bundle_mount_t)); mount->source = strdup(source); mount->target = strdup(target); if (options) { mount->options = strdup(options); } mount->flags = flags; bundle_data->mounts = g_list_append(bundle_data->mounts, mount); } static void mount_free(pe__bundle_mount_t *mount) { free(mount->source); free(mount->target); free(mount->options); free(mount); } static void port_free(pe__bundle_port_t *port) { free(port->source); free(port->target); free(port); } static pe__bundle_replica_t * replica_for_remote(pe_resource_t *remote) { resource_t *top = remote; pe__bundle_variant_data_t *bundle_data = NULL; if (top == NULL) { return NULL; } while (top->parent != NULL) { top = top->parent; } get_bundle_variant_data(bundle_data, top); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; if (replica->remote == remote) { return replica; } } CRM_LOG_ASSERT(FALSE); return NULL; } bool pe__bundle_needs_remote_name(pe_resource_t *rsc) { const char *value; if (rsc == NULL) { return FALSE; } value = g_hash_table_lookup(rsc->parameters, XML_RSC_ATTR_REMOTE_RA_ADDR); if (safe_str_eq(value, "#uname") == FALSE) { return FALSE; } else { const char *match[3][2] = { { XML_ATTR_TYPE, "remote" }, { XML_AGENT_ATTR_CLASS, PCMK_RESOURCE_CLASS_OCF }, { XML_AGENT_ATTR_PROVIDER, "pacemaker" }, }; for (int m = 0; m < 3; m++) { value = crm_element_value(rsc->xml, match[m][0]); if (safe_str_neq(value, match[m][1])) { return FALSE; } } } return TRUE; } const char * pe__add_bundle_remote_name(pe_resource_t *rsc, xmlNode *xml, const char *field) { // REMOTE_CONTAINER_HACK: Allow remote nodes that start containers with pacemaker remote inside pe_node_t *node = NULL; pe__bundle_replica_t *replica = NULL; if (!pe__bundle_needs_remote_name(rsc)) { return NULL; } replica = replica_for_remote(rsc); if (replica == NULL) { return NULL; } node = replica->container->allocated_to; if (node == NULL) { /* If it won't be running anywhere after the * transition, go with where it's running now. */ node = pe__current_node(replica->container); } if(node == NULL) { crm_trace("Cannot determine address for bundle connection %s", rsc->id); return NULL; } crm_trace("Setting address for bundle connection %s to bundle host %s", rsc->id, node->details->uname); if(xml != NULL && field != NULL) { crm_xml_add(xml, field, node->details->uname); } return node->details->uname; } gboolean pe__unpack_bundle(pe_resource_t *rsc, pe_working_set_t *data_set) { const char *value = NULL; xmlNode *xml_obj = NULL; xmlNode *xml_resource = NULL; pe__bundle_variant_data_t *bundle_data = NULL; bool need_log_mount = TRUE; CRM_ASSERT(rsc != NULL); pe_rsc_trace(rsc, "Processing resource %s...", rsc->id); bundle_data = calloc(1, sizeof(pe__bundle_variant_data_t)); rsc->variant_opaque = bundle_data; bundle_data->prefix = strdup(rsc->id); xml_obj = first_named_child(rsc->xml, PE__CONTAINER_AGENT_DOCKER_S); if (xml_obj != NULL) { bundle_data->agent_type = PE__CONTAINER_AGENT_DOCKER; } else { xml_obj = first_named_child(rsc->xml, PE__CONTAINER_AGENT_RKT_S); if (xml_obj != NULL) { bundle_data->agent_type = PE__CONTAINER_AGENT_RKT; } else { xml_obj = first_named_child(rsc->xml, PE__CONTAINER_AGENT_PODMAN_S); if (xml_obj != NULL) { bundle_data->agent_type = PE__CONTAINER_AGENT_PODMAN; } else { return FALSE; } } } value = crm_element_value(xml_obj, XML_RSC_ATTR_PROMOTED_MAX); if (value == NULL) { // @COMPAT deprecated since 2.0.0 value = crm_element_value(xml_obj, "masters"); } bundle_data->promoted_max = crm_parse_int(value, "0"); if (bundle_data->promoted_max < 0) { pe_err("%s for %s must be nonnegative integer, using 0", XML_RSC_ATTR_PROMOTED_MAX, rsc->id); bundle_data->promoted_max = 0; } value = crm_element_value(xml_obj, "replicas"); if ((value == NULL) && bundle_data->promoted_max) { bundle_data->nreplicas = bundle_data->promoted_max; } else { bundle_data->nreplicas = crm_parse_int(value, "1"); } if (bundle_data->nreplicas < 1) { pe_err("'replicas' for %s must be positive integer, using 1", rsc->id); bundle_data->nreplicas = 1; } /* * Communication between containers on the same host via the * floating IPs only works if the container is started with: * --userland-proxy=false --ip-masq=false */ value = crm_element_value(xml_obj, "replicas-per-host"); bundle_data->nreplicas_per_host = crm_parse_int(value, "1"); if (bundle_data->nreplicas_per_host < 1) { pe_err("'replicas-per-host' for %s must be positive integer, using 1", rsc->id); bundle_data->nreplicas_per_host = 1; } if (bundle_data->nreplicas_per_host == 1) { clear_bit(rsc->flags, pe_rsc_unique); } bundle_data->container_command = crm_element_value_copy(xml_obj, "run-command"); bundle_data->launcher_options = crm_element_value_copy(xml_obj, "options"); bundle_data->image = crm_element_value_copy(xml_obj, "image"); bundle_data->container_network = crm_element_value_copy(xml_obj, "network"); xml_obj = first_named_child(rsc->xml, "network"); if(xml_obj) { bundle_data->ip_range_start = crm_element_value_copy(xml_obj, "ip-range-start"); bundle_data->host_netmask = crm_element_value_copy(xml_obj, "host-netmask"); bundle_data->host_network = crm_element_value_copy(xml_obj, "host-interface"); bundle_data->control_port = crm_element_value_copy(xml_obj, "control-port"); value = crm_element_value(xml_obj, "add-host"); - if (check_boolean(value) == FALSE) { - bundle_data->add_host = TRUE; - } else { + if (pcmk__valid_boolean(value)) { crm_str_to_boolean(value, &bundle_data->add_host); + } else { + bundle_data->add_host = TRUE; } for (xmlNode *xml_child = __xml_first_child_element(xml_obj); xml_child != NULL; xml_child = __xml_next_element(xml_child)) { pe__bundle_port_t *port = calloc(1, sizeof(pe__bundle_port_t)); port->source = crm_element_value_copy(xml_child, "port"); if(port->source == NULL) { port->source = crm_element_value_copy(xml_child, "range"); } else { port->target = crm_element_value_copy(xml_child, "internal-port"); } if(port->source != NULL && strlen(port->source) > 0) { if(port->target == NULL) { port->target = strdup(port->source); } bundle_data->ports = g_list_append(bundle_data->ports, port); } else { pe_err("Invalid port directive %s", ID(xml_child)); port_free(port); } } } xml_obj = first_named_child(rsc->xml, "storage"); for (xmlNode *xml_child = __xml_first_child_element(xml_obj); xml_child != NULL; xml_child = __xml_next_element(xml_child)) { const char *source = crm_element_value(xml_child, "source-dir"); const char *target = crm_element_value(xml_child, "target-dir"); const char *options = crm_element_value(xml_child, "options"); int flags = pe__bundle_mount_none; if (source == NULL) { source = crm_element_value(xml_child, "source-dir-root"); set_bit(flags, pe__bundle_mount_subdir); } if (source && target) { mount_add(bundle_data, source, target, options, flags); if (strcmp(target, "/var/log") == 0) { need_log_mount = FALSE; } } else { pe_err("Invalid mount directive %s", ID(xml_child)); } } xml_obj = first_named_child(rsc->xml, "primitive"); if (xml_obj && valid_network(bundle_data)) { char *value = NULL; xmlNode *xml_set = NULL; xml_resource = create_xml_node(NULL, XML_CIB_TAG_INCARNATION); /* @COMPAT We no longer use the tag, but we need to keep it as * part of the resource name, so that bundles don't restart in a rolling * upgrade. (It also avoids needing to change regression tests.) */ crm_xml_set_id(xml_resource, "%s-%s", bundle_data->prefix, (bundle_data->promoted_max? "master" : (const char *)xml_resource->name)); xml_set = create_xml_node(xml_resource, XML_TAG_META_SETS); crm_xml_set_id(xml_set, "%s-%s-meta", bundle_data->prefix, xml_resource->name); crm_create_nvpair_xml(xml_set, NULL, XML_RSC_ATTR_ORDERED, XML_BOOLEAN_TRUE); value = crm_itoa(bundle_data->nreplicas); crm_create_nvpair_xml(xml_set, NULL, XML_RSC_ATTR_INCARNATION_MAX, value); free(value); value = crm_itoa(bundle_data->nreplicas_per_host); crm_create_nvpair_xml(xml_set, NULL, XML_RSC_ATTR_INCARNATION_NODEMAX, value); free(value); crm_create_nvpair_xml(xml_set, NULL, XML_RSC_ATTR_UNIQUE, (bundle_data->nreplicas_per_host > 1)? XML_BOOLEAN_TRUE : XML_BOOLEAN_FALSE); if (bundle_data->promoted_max) { crm_create_nvpair_xml(xml_set, NULL, XML_RSC_ATTR_PROMOTABLE, XML_BOOLEAN_TRUE); value = crm_itoa(bundle_data->promoted_max); crm_create_nvpair_xml(xml_set, NULL, XML_RSC_ATTR_PROMOTED_MAX, value); free(value); } //crm_xml_add(xml_obj, XML_ATTR_ID, bundle_data->prefix); add_node_copy(xml_resource, xml_obj); } else if(xml_obj) { pe_err("Cannot control %s inside %s without either ip-range-start or control-port", rsc->id, ID(xml_obj)); return FALSE; } if(xml_resource) { int lpc = 0; GListPtr childIter = NULL; resource_t *new_rsc = NULL; pe__bundle_port_t *port = NULL; int offset = 0, max = 1024; char *buffer = NULL; if (common_unpack(xml_resource, &new_rsc, rsc, data_set) == FALSE) { pe_err("Failed unpacking resource %s", ID(rsc->xml)); if (new_rsc != NULL && new_rsc->fns != NULL) { new_rsc->fns->free(new_rsc); } return FALSE; } bundle_data->child = new_rsc; /* Currently, we always map the default authentication key location * into the same location inside the container. * * Ideally, we would respect the host's PCMK_authkey_location, but: * - it may be different on different nodes; * - the actual connection will do extra checking to make sure the key * file exists and is readable, that we can't do here on the DC * - tools such as crm_resource and crm_simulate may not have the same * environment variables as the cluster, causing operation digests to * differ * * Always using the default location inside the container is fine, * because we control the pacemaker_remote environment, and it avoids * having to pass another environment variable to the container. * * @TODO A better solution may be to have only pacemaker_remote use the * environment variable, and have the cluster nodes use a new * cluster option for key location. This would introduce the limitation * of the location being the same on all cluster nodes, but that's * reasonable. */ mount_add(bundle_data, DEFAULT_REMOTE_KEY_LOCATION, DEFAULT_REMOTE_KEY_LOCATION, NULL, pe__bundle_mount_none); if (need_log_mount) { mount_add(bundle_data, CRM_BUNDLE_DIR, "/var/log", NULL, pe__bundle_mount_subdir); } port = calloc(1, sizeof(pe__bundle_port_t)); if(bundle_data->control_port) { port->source = strdup(bundle_data->control_port); } else { /* If we wanted to respect PCMK_remote_port, we could use * crm_default_remote_port() here and elsewhere in this file instead * of DEFAULT_REMOTE_PORT. * * However, it gains nothing, since we control both the container * environment and the connection resource parameters, and the user * can use a different port if desired by setting control-port. */ port->source = crm_itoa(DEFAULT_REMOTE_PORT); } port->target = strdup(port->source); bundle_data->ports = g_list_append(bundle_data->ports, port); buffer = calloc(1, max+1); for (childIter = bundle_data->child->children; childIter != NULL; childIter = childIter->next) { pe__bundle_replica_t *replica = calloc(1, sizeof(pe__bundle_replica_t)); replica->child = childIter->data; replica->child->exclusive_discover = TRUE; replica->offset = lpc++; // Ensure the child's notify gets set based on the underlying primitive's value if (is_set(replica->child->flags, pe_rsc_notify)) { set_bit(bundle_data->child->flags, pe_rsc_notify); } offset += allocate_ip(bundle_data, replica, buffer+offset, max-offset); bundle_data->replicas = g_list_append(bundle_data->replicas, replica); bundle_data->attribute_target = g_hash_table_lookup(replica->child->meta, XML_RSC_ATTR_TARGET); } bundle_data->container_host_options = buffer; if (bundle_data->attribute_target) { g_hash_table_replace(rsc->meta, strdup(XML_RSC_ATTR_TARGET), strdup(bundle_data->attribute_target)); g_hash_table_replace(bundle_data->child->meta, strdup(XML_RSC_ATTR_TARGET), strdup(bundle_data->attribute_target)); } } else { // Just a naked container, no pacemaker-remote int offset = 0, max = 1024; char *buffer = calloc(1, max+1); for (int lpc = 0; lpc < bundle_data->nreplicas; lpc++) { pe__bundle_replica_t *replica = calloc(1, sizeof(pe__bundle_replica_t)); replica->offset = lpc; offset += allocate_ip(bundle_data, replica, buffer+offset, max-offset); bundle_data->replicas = g_list_append(bundle_data->replicas, replica); } bundle_data->container_host_options = buffer; } for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; if (!create_container(rsc, bundle_data, replica, data_set)) { pe_err("Failed unpacking resource %s", rsc->id); rsc->fns->free(rsc); return FALSE; } } if (bundle_data->child) { rsc->children = g_list_append(rsc->children, bundle_data->child); } return TRUE; } static int replica_resource_active(pe_resource_t *rsc, gboolean all) { if (rsc) { gboolean child_active = rsc->fns->active(rsc, all); if (child_active && !all) { return TRUE; } else if (!child_active && all) { return FALSE; } } return -1; } gboolean pe__bundle_active(pe_resource_t *rsc, gboolean all) { pe__bundle_variant_data_t *bundle_data = NULL; GListPtr iter = NULL; get_bundle_variant_data(bundle_data, rsc); for (iter = bundle_data->replicas; iter != NULL; iter = iter->next) { pe__bundle_replica_t *replica = iter->data; int rsc_active; rsc_active = replica_resource_active(replica->ip, all); if (rsc_active >= 0) { return (gboolean) rsc_active; } rsc_active = replica_resource_active(replica->child, all); if (rsc_active >= 0) { return (gboolean) rsc_active; } rsc_active = replica_resource_active(replica->container, all); if (rsc_active >= 0) { return (gboolean) rsc_active; } rsc_active = replica_resource_active(replica->remote, all); if (rsc_active >= 0) { return (gboolean) rsc_active; } } /* If "all" is TRUE, we've already checked that no resources were inactive, * so return TRUE; if "all" is FALSE, we didn't find any active resources, * so return FALSE. */ return all; } /*! * \internal * \brief Find the bundle replica corresponding to a given node * * \param[in] bundle Top-level bundle resource * \param[in] node Node to search for * * \return Bundle replica if found, NULL otherwise */ pe_resource_t * pe__find_bundle_replica(const pe_resource_t *bundle, const pe_node_t *node) { pe__bundle_variant_data_t *bundle_data = NULL; CRM_ASSERT(bundle && node); get_bundle_variant_data(bundle_data, bundle); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; CRM_ASSERT(replica && replica->node); if (replica->node->details == node->details) { return replica->child; } } return NULL; } static void print_rsc_in_list(resource_t *rsc, const char *pre_text, long options, void *print_data) { if (rsc != NULL) { if (options & pe_print_html) { status_print("
  • "); } rsc->fns->print(rsc, pre_text, options, print_data); if (options & pe_print_html) { status_print("
  • \n"); } } } static const char* container_agent_str(enum pe__container_agent t) { switch (t) { case PE__CONTAINER_AGENT_DOCKER: return PE__CONTAINER_AGENT_DOCKER_S; case PE__CONTAINER_AGENT_RKT: return PE__CONTAINER_AGENT_RKT_S; case PE__CONTAINER_AGENT_PODMAN: return PE__CONTAINER_AGENT_PODMAN_S; default: // PE__CONTAINER_AGENT_UNKNOWN break; } return PE__CONTAINER_AGENT_UNKNOWN_S; } static void bundle_print_xml(pe_resource_t *rsc, const char *pre_text, long options, void *print_data) { pe__bundle_variant_data_t *bundle_data = NULL; char *child_text = NULL; CRM_CHECK(rsc != NULL, return); if (pre_text == NULL) { pre_text = ""; } child_text = crm_concat(pre_text, " ", ' '); get_bundle_variant_data(bundle_data, rsc); status_print("%sid); status_print("type=\"%s\" ", container_agent_str(bundle_data->agent_type)); status_print("image=\"%s\" ", bundle_data->image); status_print("unique=\"%s\" ", is_set(rsc->flags, pe_rsc_unique)? "true" : "false"); status_print("managed=\"%s\" ", is_set(rsc->flags, pe_rsc_managed) ? "true" : "false"); status_print("failed=\"%s\" ", is_set(rsc->flags, pe_rsc_failed) ? "true" : "false"); status_print(">\n"); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; CRM_ASSERT(replica); status_print("%s \n", pre_text, replica->offset); print_rsc_in_list(replica->ip, child_text, options, print_data); print_rsc_in_list(replica->child, child_text, options, print_data); print_rsc_in_list(replica->container, child_text, options, print_data); print_rsc_in_list(replica->remote, child_text, options, print_data); status_print("%s \n", pre_text); } status_print("%s\n", pre_text); free(child_text); } int pe__bundle_xml(pcmk__output_t *out, va_list args) { unsigned int options = va_arg(args, unsigned int); pe_resource_t *rsc = va_arg(args, pe_resource_t *); pe__bundle_variant_data_t *bundle_data = NULL; int rc = 0; CRM_ASSERT(rsc != NULL); get_bundle_variant_data(bundle_data, rsc); rc = pe__name_and_nvpairs_xml(out, true, "bundle", 6 , "id", rsc->id , "type", container_agent_str(bundle_data->agent_type) , "image", bundle_data->image , "unique", BOOL2STR(is_set(rsc->flags, pe_rsc_unique)) , "managed", BOOL2STR(is_set(rsc->flags, pe_rsc_managed)) , "failed", BOOL2STR(is_set(rsc->flags, pe_rsc_failed))); CRM_ASSERT(rc == 0); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; char *id = crm_itoa(replica->offset); CRM_ASSERT(replica); rc = pe__name_and_nvpairs_xml(out, true, "replica", 1, "id", id); free(id); CRM_ASSERT(rc == 0); if (replica->ip != NULL) { out->message(out, crm_map_element_name(replica->ip->xml), options, replica->ip); } if (replica->child != NULL) { out->message(out, crm_map_element_name(replica->child->xml), options, replica->child); } out->message(out, crm_map_element_name(replica->container->xml), options, replica->container); if (replica->remote != NULL) { out->message(out, crm_map_element_name(replica->remote->xml), options, replica->remote); } pcmk__output_xml_pop_parent(out); // replica } pcmk__output_xml_pop_parent(out); // bundle return rc; } static void pe__bundle_replica_output_html(pcmk__output_t *out, pe__bundle_replica_t *replica, long options) { node_t *node = NULL; pe_resource_t *rsc = replica->child; int offset = 0; char buffer[LINE_MAX]; if(rsc == NULL) { rsc = replica->container; } if (replica->remote) { offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", rsc_printable_id(replica->remote)); } else { offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", rsc_printable_id(replica->container)); } if (replica->ipaddr) { offset += snprintf(buffer + offset, LINE_MAX - offset, " (%s)", replica->ipaddr); } node = pe__current_node(replica->container); pe__common_output_html(out, rsc, buffer, node, options); } int pe__bundle_html(pcmk__output_t *out, va_list args) { unsigned int options = va_arg(args, unsigned int); pe_resource_t *rsc = va_arg(args, pe_resource_t *); pe__bundle_variant_data_t *bundle_data = NULL; char buffer[LINE_MAX]; CRM_ASSERT(rsc != NULL); get_bundle_variant_data(bundle_data, rsc); pcmk__output_create_xml_node(out, "br"); out->begin_list(out, NULL, NULL, "Container bundle%s: %s [%s]%s%s", (bundle_data->nreplicas > 1)? " set" : "", rsc->id, bundle_data->image, is_set(rsc->flags, pe_rsc_unique) ? " (unique)" : "", is_set(rsc->flags, pe_rsc_managed) ? "" : " (unmanaged)"); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; CRM_ASSERT(replica); pcmk__output_xml_create_parent(out, "li"); if (is_set(options, pe_print_implicit)) { if (pcmk__list_of_multiple(bundle_data->replicas)) { snprintf(buffer, LINE_MAX, " Replica[%d]", replica->offset); xmlNodeSetContent(pcmk__output_xml_peek_parent(out), (pcmkXmlStr) buffer); } pcmk__output_create_xml_node(out, "br"); out->begin_list(out, NULL, NULL, NULL); if (replica->ip != NULL) { out->message(out, crm_map_element_name(replica->ip->xml), options, replica->ip); } if (replica->child != NULL) { out->message(out, crm_map_element_name(replica->child->xml), options, replica->child); } out->message(out, crm_map_element_name(replica->container->xml), options, replica->container); if (replica->remote != NULL) { out->message(out, crm_map_element_name(replica->remote->xml), options, replica->remote); } out->end_list(out); } else { pe__bundle_replica_output_html(out, replica, options); } pcmk__output_xml_pop_parent(out); } out->end_list(out); return 0; } static void pe__bundle_replica_output_text(pcmk__output_t *out, pe__bundle_replica_t *replica, long options) { node_t *node = NULL; pe_resource_t *rsc = replica->child; int offset = 0; char buffer[LINE_MAX]; if(rsc == NULL) { rsc = replica->container; } if (replica->remote) { offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", rsc_printable_id(replica->remote)); } else { offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", rsc_printable_id(replica->container)); } if (replica->ipaddr) { offset += snprintf(buffer + offset, LINE_MAX - offset, " (%s)", replica->ipaddr); } node = pe__current_node(replica->container); pe__common_output_text(out, rsc, buffer, node, options); } int pe__bundle_text(pcmk__output_t *out, va_list args) { unsigned int options = va_arg(args, unsigned int); pe_resource_t *rsc = va_arg(args, pe_resource_t *); pe__bundle_variant_data_t *bundle_data = NULL; CRM_ASSERT(rsc != NULL); get_bundle_variant_data(bundle_data, rsc); out->begin_list(out, NULL, NULL, "Container bundle%s: %s [%s]%s%s", (bundle_data->nreplicas > 1)? " set" : "", rsc->id, bundle_data->image, is_set(rsc->flags, pe_rsc_unique) ? " (unique)" : "", is_set(rsc->flags, pe_rsc_managed) ? "" : " (unmanaged)"); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; CRM_ASSERT(replica); if (is_set(options, pe_print_implicit)) { if (pcmk__list_of_multiple(bundle_data->replicas)) { out->list_item(out, NULL, "Replica[%d]", replica->offset); } out->begin_list(out, NULL, NULL, NULL); if (replica->ip != NULL) { out->message(out, crm_map_element_name(replica->ip->xml), options, replica->ip); } if (replica->child != NULL) { out->message(out, crm_map_element_name(replica->child->xml), options, replica->child); } out->message(out, crm_map_element_name(replica->container->xml), options, replica->container); if (replica->remote != NULL) { out->message(out, crm_map_element_name(replica->remote->xml), options, replica->remote); } out->end_list(out); } else { pe__bundle_replica_output_text(out, replica, options); } } out->end_list(out); return 0; } static void print_bundle_replica(pe__bundle_replica_t *replica, const char *pre_text, long options, void *print_data) { node_t *node = NULL; pe_resource_t *rsc = replica->child; int offset = 0; char buffer[LINE_MAX]; if(rsc == NULL) { rsc = replica->container; } if (replica->remote) { offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", rsc_printable_id(replica->remote)); } else { offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", rsc_printable_id(replica->container)); } if (replica->ipaddr) { offset += snprintf(buffer + offset, LINE_MAX - offset, " (%s)", replica->ipaddr); } node = pe__current_node(replica->container); common_print(rsc, pre_text, buffer, node, options, print_data); } void pe__print_bundle(pe_resource_t *rsc, const char *pre_text, long options, void *print_data) { pe__bundle_variant_data_t *bundle_data = NULL; char *child_text = NULL; CRM_CHECK(rsc != NULL, return); if (options & pe_print_xml) { bundle_print_xml(rsc, pre_text, options, print_data); return; } get_bundle_variant_data(bundle_data, rsc); if (pre_text == NULL) { pre_text = " "; } status_print("%sContainer bundle%s: %s [%s]%s%s\n", pre_text, ((bundle_data->nreplicas > 1)? " set" : ""), rsc->id, bundle_data->image, is_set(rsc->flags, pe_rsc_unique) ? " (unique)" : "", is_set(rsc->flags, pe_rsc_managed) ? "" : " (unmanaged)"); if (options & pe_print_html) { status_print("
    \n
      \n"); } for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; CRM_ASSERT(replica); if (options & pe_print_html) { status_print("
    • "); } if (is_set(options, pe_print_implicit)) { child_text = crm_strdup_printf(" %s", pre_text); if (pcmk__list_of_multiple(bundle_data->replicas)) { status_print(" %sReplica[%d]\n", pre_text, replica->offset); } if (options & pe_print_html) { status_print("
      \n
        \n"); } print_rsc_in_list(replica->ip, child_text, options, print_data); print_rsc_in_list(replica->container, child_text, options, print_data); print_rsc_in_list(replica->remote, child_text, options, print_data); print_rsc_in_list(replica->child, child_text, options, print_data); if (options & pe_print_html) { status_print("
      \n"); } } else { child_text = crm_strdup_printf("%s ", pre_text); print_bundle_replica(replica, child_text, options, print_data); } free(child_text); if (options & pe_print_html) { status_print("
    • \n"); } } if (options & pe_print_html) { status_print("
    \n"); } } static void free_bundle_replica(pe__bundle_replica_t *replica) { if (replica == NULL) { return; } if (replica->node) { free(replica->node); replica->node = NULL; } if (replica->ip) { free_xml(replica->ip->xml); replica->ip->xml = NULL; replica->ip->fns->free(replica->ip); replica->ip = NULL; } if (replica->container) { free_xml(replica->container->xml); replica->container->xml = NULL; replica->container->fns->free(replica->container); replica->container = NULL; } if (replica->remote) { free_xml(replica->remote->xml); replica->remote->xml = NULL; replica->remote->fns->free(replica->remote); replica->remote = NULL; } free(replica->ipaddr); free(replica); } void pe__free_bundle(pe_resource_t *rsc) { pe__bundle_variant_data_t *bundle_data = NULL; CRM_CHECK(rsc != NULL, return); get_bundle_variant_data(bundle_data, rsc); pe_rsc_trace(rsc, "Freeing %s", rsc->id); free(bundle_data->prefix); free(bundle_data->image); free(bundle_data->control_port); free(bundle_data->host_network); free(bundle_data->host_netmask); free(bundle_data->ip_range_start); free(bundle_data->container_network); free(bundle_data->launcher_options); free(bundle_data->container_command); free(bundle_data->container_host_options); g_list_free_full(bundle_data->replicas, (GDestroyNotify) free_bundle_replica); g_list_free_full(bundle_data->mounts, (GDestroyNotify)mount_free); g_list_free_full(bundle_data->ports, (GDestroyNotify)port_free); g_list_free(rsc->children); if(bundle_data->child) { free_xml(bundle_data->child->xml); bundle_data->child->xml = NULL; bundle_data->child->fns->free(bundle_data->child); } common_free(rsc); } enum rsc_role_e pe__bundle_resource_state(const pe_resource_t *rsc, gboolean current) { enum rsc_role_e container_role = RSC_ROLE_UNKNOWN; return container_role; } /*! * \brief Get the number of configured replicas in a bundle * * \param[in] rsc Bundle resource * * \return Number of configured replicas, or 0 on error */ int pe_bundle_replicas(const resource_t *rsc) { if ((rsc == NULL) || (rsc->variant != pe_container)) { return 0; } else { pe__bundle_variant_data_t *bundle_data = NULL; get_bundle_variant_data(bundle_data, rsc); return bundle_data->nreplicas; } } void pe__count_bundle(pe_resource_t *rsc) { pe__bundle_variant_data_t *bundle_data = NULL; get_bundle_variant_data(bundle_data, rsc); for (GList *item = bundle_data->replicas; item != NULL; item = item->next) { pe__bundle_replica_t *replica = item->data; if (replica->ip) { replica->ip->fns->count(replica->ip); } if (replica->child) { replica->child->fns->count(replica->child); } if (replica->container) { replica->container->fns->count(replica->container); } if (replica->remote) { replica->remote->fns->count(replica->remote); } } } diff --git a/lib/pengine/common.c b/lib/pengine/common.c index e72a033dd7..b2b0f77076 100644 --- a/lib/pengine/common.c +++ b/lib/pengine/common.c @@ -1,496 +1,585 @@ /* * Copyright 2004-2020 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 gboolean was_processing_error = FALSE; gboolean was_processing_warning = FALSE; -static gboolean +static bool check_health(const char *value) { - if (safe_str_eq(value, "none")) { - return TRUE; - - } else if (safe_str_eq(value, "custom")) { - return TRUE; - - } else if (safe_str_eq(value, "only-green")) { - return TRUE; - - } else if (safe_str_eq(value, "progressive")) { - return TRUE; - - } else if (safe_str_eq(value, "migrate-on-red")) { - return TRUE; - } - return FALSE; + return safe_str_eq(value, "none") + || safe_str_eq(value, "custom") + || safe_str_eq(value, "only-green") + || safe_str_eq(value, "progressive") + || safe_str_eq(value, "migrate-on-red"); } -static gboolean +static bool check_stonith_action(const char *value) { - if (safe_str_eq(value, "reboot")) { - return TRUE; - - } else if (safe_str_eq(value, "poweroff")) { - return TRUE; - - } else if (safe_str_eq(value, "off")) { - return TRUE; - } - return FALSE; + return safe_str_eq(value, "reboot") + || safe_str_eq(value, "poweroff") + || safe_str_eq(value, "off"); } -static gboolean +static bool check_placement_strategy(const char *value) { - if (safe_str_eq(value, "default")) { - return TRUE; - - } else if (safe_str_eq(value, "utilization")) { - return TRUE; - - } else if (safe_str_eq(value, "minimal")) { - return TRUE; - - } else if (safe_str_eq(value, "balanced")) { - return TRUE; - } - return FALSE; + return safe_str_eq(value, "default") + || safe_str_eq(value, "utilization") + || safe_str_eq(value, "minimal") + || safe_str_eq(value, "balanced"); } -/* *INDENT-OFF* */ -static pe_cluster_option pe_opts[] = { - /* name, old-name, validate, default, description */ - { "no-quorum-policy", NULL, "enum", "stop, freeze, ignore, suicide", "stop", &check_quorum, - "What to do when the cluster does not have quorum", NULL }, - { "symmetric-cluster", NULL, "boolean", NULL, "true", &check_boolean, - "All resources can run anywhere by default", NULL }, - { "maintenance-mode", NULL, "boolean", NULL, "false", &check_boolean, - "Should the cluster monitor resources and start/stop them as required", NULL }, - { "start-failure-is-fatal", NULL, "boolean", NULL, "true", &check_boolean, "Always treat start failures as fatal", - "When set to TRUE, the cluster will immediately ban a resource from a node if it fails to start there. When FALSE, the cluster will instead check the resource's fail count against its migration-threshold." }, - { "enable-startup-probes", NULL, "boolean", NULL, "true", &check_boolean, - "Should the cluster check for active resources during startup", NULL }, +static pcmk__cluster_option_t pe_opts[] = { + /* name, old name, type, allowed values, + * default value, validator, + * short description, + * long description + */ + { + "no-quorum-policy", NULL, "enum", "stop, freeze, ignore, suicide", + "stop", pcmk__valid_quorum, + "What to do when the cluster does not have quorum", + NULL + }, + { + "symmetric-cluster", NULL, "boolean", NULL, + "true", pcmk__valid_boolean, + "Whether resources can run on any node by default", + NULL + }, { - XML_CONFIG_ATTR_SHUTDOWN_LOCK, - NULL, "boolean", NULL, "false", &check_boolean, + "maintenance-mode", NULL, "boolean", NULL, + "false", pcmk__valid_boolean, + "Whether the cluster should refrain from monitoring, starting, " + "and stopping resources", + NULL + }, + { + "start-failure-is-fatal", NULL, "boolean", NULL, + "true", pcmk__valid_boolean, + "Whether a start failure should prevent a resource from being " + "recovered on the same node", + "When true, the cluster will immediately ban a resource from a node " + "if it fails to start there. When false, the cluster will instead " + "check the resource's fail count against its migration-threshold." + }, + { + "enable-startup-probes", NULL, "boolean", NULL, + "true", pcmk__valid_boolean, + "Whether the cluster should check for active resources during start-up", + NULL + }, + { + XML_CONFIG_ATTR_SHUTDOWN_LOCK, NULL, "boolean", NULL, + "false", pcmk__valid_boolean, "Whether to lock resources to a cleanly shut down node", "When true, resources active on a node when it is cleanly shut down " "are kept \"locked\" to that node (not allowed to run elsewhere) " "until they start again on that node after it rejoins (or for at " "most shutdown-lock-limit, if set). Stonith resources and " "Pacemaker Remote connections are never locked. Clone and bundle " "instances and the master role of promotable clones are currently " "never locked, though support could be added in a future release." }, { - XML_CONFIG_ATTR_SHUTDOWN_LOCK_LIMIT, - NULL, "time", NULL, "0", &check_timer, + XML_CONFIG_ATTR_SHUTDOWN_LOCK_LIMIT, NULL, "time", NULL, + "0", pcmk__valid_interval_spec, "Do not lock resources to a cleanly shut down node longer than this", "If shutdown-lock is true and this is set to a nonzero time duration, " "shutdown locks will expire after this much time has passed since " "the shutdown was initiated, even if the node has not rejoined." }, - /* Stonith Options */ - { "stonith-enabled", NULL, "boolean", NULL, "true", &check_boolean, - "Failed nodes are STONITH'd", NULL }, - { "stonith-action", NULL, "enum", "reboot, off, poweroff", "reboot", &check_stonith_action, - "Action to send to STONITH device ('poweroff' is a deprecated alias for 'off')", NULL }, - { "stonith-timeout", NULL, "time", NULL, "60s", &check_timer, - "How long to wait for the STONITH action (reboot,on,off) to complete", NULL }, - { XML_ATTR_HAVE_WATCHDOG, NULL, "boolean", NULL, "false", &check_boolean, - "Enable watchdog integration", "Set automatically by the cluster if SBD is detected. User configured values are ignored." }, - { "concurrent-fencing", NULL, "boolean", NULL, + // Fencing-related options + { + "stonith-enabled", NULL, "boolean", NULL, + "true", pcmk__valid_boolean, + "*** Advanced Use Only *** " + "Whether nodes may be fenced as part of recovery", + "If false, unresponsive nodes are immediately assumed to be harmless, " + "and resources that were active on them may be recovered " + "elsewhere. This can result in a \"split-brain\" situation, " + "potentially leading to data loss and/or service unavailability." + }, + { + "stonith-action", NULL, "enum", "reboot, off, poweroff", + "reboot", check_stonith_action, + "Action to send to fence device when a node needs to be fenced " + "(\"poweroff\" is a deprecated alias for \"off\")", + NULL + }, + { + "stonith-timeout", NULL, "time", NULL, + "60s", pcmk__valid_interval_spec, + "*** Advanced Use Only *** Unused by Pacemaker", + "This value is not used by Pacemaker, but is kept for backward " + "compatibility, and certain legacy fence agents might use it." + }, + { + XML_ATTR_HAVE_WATCHDOG, NULL, "boolean", NULL, + "false", pcmk__valid_boolean, + "Whether watchdog integration is enabled", + "This is set automatically by the cluster according to whether SBD " + "is detected to be in use. User-configured values are ignored." + }, + { + "concurrent-fencing", NULL, "boolean", NULL, #ifdef DEFAULT_CONCURRENT_FENCING_TRUE - "true", + "true", #else - "false", + "false", #endif - &check_boolean, - "Allow performing fencing operations in parallel", NULL }, - { "startup-fencing", NULL, "boolean", NULL, "true", &check_boolean, - "STONITH unseen nodes", "Advanced Use Only! Not using the default is very unsafe!" }, - - /* Timeouts etc */ - { "cluster-delay", NULL, "time", NULL, "60s", &check_time, - "Round trip delay over the network (excluding action execution)", - "The \"correct\" value will depend on the speed and load of your network and cluster nodes." }, - { "batch-limit", NULL, "integer", NULL, "0", &check_number, - "The number of jobs that the TE is allowed to execute in parallel", - "The \"correct\" value will depend on the speed and load of your network and cluster nodes." }, - { "migration-limit", NULL, "integer", NULL, "-1", &check_number, - "The number of migration jobs that the TE is allowed to execute in parallel on a node"}, - - /* Orphans and stopping */ - { "stop-all-resources", NULL, "boolean", NULL, "false", &check_boolean, - "Should the cluster stop all active resources", NULL }, - { "stop-orphan-resources", NULL, "boolean", NULL, "true", &check_boolean, - "Should deleted resources be stopped", NULL }, - { "stop-orphan-actions", NULL, "boolean", NULL, "true", &check_boolean, - "Should deleted actions be cancelled", NULL }, - { "remove-after-stop", NULL, "boolean", NULL, "false", &check_boolean, - "Remove resources from the executor after they are stopped", - "Always set this to false. Other values are, at best, poorly tested and potentially dangerous." }, -/* { "", "", , "0", "", NULL }, */ - - /* Storing inputs */ - { - "pe-error-series-max", NULL, "integer", NULL, "-1", &check_number, - "The number of scheduler inputs resulting in ERRORs to save", - "Zero to disable, -1 to store unlimited" + pcmk__valid_boolean, + "Allow performing fencing operations in parallel", + NULL + }, + { + "startup-fencing", NULL, "boolean", NULL, + "true", pcmk__valid_boolean, + "*** Advanced Use Only *** Whether to fence unseen nodes at start-up", + "Setting this to false may lead to a \"split-brain\" situation," + "potentially leading to data loss and/or service unavailability." }, - { - "pe-warn-series-max", NULL, "integer", NULL, "5000", &check_number, - "The number of scheduler inputs resulting in WARNINGs to save", - "Zero to disable, -1 to store unlimited" + + { + "cluster-delay", NULL, "time", NULL, + "60s", pcmk__valid_interval_spec, + "Maximum time for node-to-node communication", + "The node elected Designated Controller (DC) will consider an action " + "failed if it does not get a response from the node executing the " + "action within this time (after considering the action's own " + "timeout). The \"correct\" value will depend on the speed and " + "load of your network and cluster nodes." + }, + { + "batch-limit", NULL, "integer", NULL, + "0", pcmk__valid_number, + "Maximum number of jobs that the cluster may execute in parallel " + "across all nodes", + "The \"correct\" value will depend on the speed and load of your " + "network and cluster nodes. If set to 0, the cluster will " + "impose a dynamically calculated limit when any node has a " + "high load." + }, + { + "migration-limit", NULL, "integer", NULL, + "-1", pcmk__valid_number, + "The number of live migration actions that the cluster is allowed " + "to execute in parallel on a node (-1 means no limit)" + }, + + /* Orphans and stopping */ + { + "stop-all-resources", NULL, "boolean", NULL, + "false", pcmk__valid_boolean, + "Whether the cluster should stop all active resources", + NULL + }, + { + "stop-orphan-resources", NULL, "boolean", NULL, + "true", pcmk__valid_boolean, + "Whether to stop resources that were removed from the configuration", + NULL + }, + { + "stop-orphan-actions", NULL, "boolean", NULL, + "true", pcmk__valid_boolean, + "Whether to cancel recurring actions removed from the configuration", + NULL + }, + { + "remove-after-stop", NULL, "boolean", NULL, + "false", pcmk__valid_boolean, + "*** Advanced Use Only *** Whether to remove stopped resources from " + "the executor", + "Values other than default are poorly tested and potentially dangerous." + }, + + /* Storing inputs */ + { + "pe-error-series-max", NULL, "integer", NULL, + "-1", pcmk__valid_number, + "The number of scheduler inputs resulting in errors to save", + "Zero to disable, -1 to store unlimited." }, - { - "pe-input-series-max", NULL, "integer", NULL, "4000", &check_number, - "The number of other scheduler inputs to save", - "Zero to disable, -1 to store unlimited" + { + "pe-warn-series-max", NULL, "integer", NULL, + "5000", pcmk__valid_number, + "The number of scheduler inputs resulting in warnings to save", + "Zero to disable, -1 to store unlimited." + }, + { + "pe-input-series-max", NULL, "integer", NULL, + "4000", pcmk__valid_number, + "The number of scheduler inputs without errors or warnings to save", + "Zero to disable, -1 to store unlimited." }, - /* Node health */ - { "node-health-strategy", NULL, "enum", "none, migrate-on-red, only-green, progressive, custom", "none", &check_health, - "The strategy combining node attributes to determine overall node health.", - "Requires external entities to create node attributes (named with the prefix '#health') with values: 'red', 'yellow' or 'green'."}, - { "node-health-base", NULL, "integer", NULL, "0", &check_number, - "The base score assigned to a node", - "Only used when node-health-strategy is set to progressive." }, - { "node-health-green", NULL, "integer", NULL, "0", &check_number, - "The score 'green' translates to in rsc_location constraints", - "Only used when node-health-strategy is set to custom or progressive." }, - { "node-health-yellow", NULL, "integer", NULL, "0", &check_number, - "The score 'yellow' translates to in rsc_location constraints", - "Only used when node-health-strategy is set to custom or progressive." }, - { "node-health-red", NULL, "integer", NULL, "-INFINITY", &check_number, - "The score 'red' translates to in rsc_location constraints", - "Only used when node-health-strategy is set to custom or progressive." }, - - /*Placement Strategy*/ - { "placement-strategy", NULL, "enum", "default, utilization, minimal, balanced", "default", &check_placement_strategy, - "The strategy to determine resource placement", NULL}, + /* Node health */ + { + "node-health-strategy", NULL, "enum", + "none, migrate-on-red, only-green, progressive, custom", + "none", check_health, + "How cluster should react to node health attributes", + "Requires external entities to create node attributes (named with " + "the prefix \"#health\") with values \"red\", \"yellow\" or " + "\"green\"." + }, + { + "node-health-base", NULL, "integer", NULL, + "0", pcmk__valid_number, + "Base health score assigned to a node", + "Only used when node-health-strategy is set to progressive." + }, + { + "node-health-green", NULL, "integer", NULL, + "0", pcmk__valid_number, + "The score to use for a node health attribute whose value is \"green\"", + "Only used when node-health-strategy is set to custom or progressive." + }, + { + "node-health-yellow", NULL, "integer", NULL, + "0", pcmk__valid_number, + "The score to use for a node health attribute whose value is \"yellow\"", + "Only used when node-health-strategy is set to custom or progressive." + }, + { + "node-health-red", NULL, "integer", NULL, + "-INFINITY", pcmk__valid_number, + "The score to use for a node health attribute whose value is \"red\"", + "Only used when node-health-strategy is set to custom or progressive." + }, + + /*Placement Strategy*/ + { + "placement-strategy", NULL, "enum", + "default, utilization, minimal, balanced", + "default", check_placement_strategy, + "How the cluster should allocate resources to nodes", + NULL + }, }; -/* *INDENT-ON* */ void pe_metadata(void) { - config_metadata("pacemaker-schedulerd", "1.0", "scheduler properties", - "Cluster properties used by Pacemaker's scheduler," - " formerly known as pengine", - pe_opts, DIMOF(pe_opts)); + pcmk__print_option_metadata("pacemaker-schedulerd", "1.0", + "Pacemaker scheduler options", + "Cluster options used by Pacemaker's scheduler" + " (formerly called pengine)", + pe_opts, DIMOF(pe_opts)); } void verify_pe_options(GHashTable * options) { - verify_all_options(options, pe_opts, DIMOF(pe_opts)); + pcmk__validate_cluster_options(options, pe_opts, DIMOF(pe_opts)); } const char * pe_pref(GHashTable * options, const char *name) { - return get_cluster_pref(options, pe_opts, DIMOF(pe_opts), name); + return pcmk__cluster_option(options, pe_opts, DIMOF(pe_opts), name); } const char * fail2text(enum action_fail_response fail) { const char *result = ""; switch (fail) { case action_fail_ignore: result = "ignore"; break; case action_fail_block: result = "block"; break; case action_fail_recover: result = "recover"; break; case action_fail_migrate: result = "migrate"; break; case action_fail_stop: result = "stop"; break; case action_fail_fence: result = "fence"; break; case action_fail_standby: result = "standby"; break; case action_fail_restart_container: result = "restart-container"; break; case action_fail_reset_remote: result = "reset-remote"; break; } return result; } enum action_tasks text2task(const char *task) { if (safe_str_eq(task, CRMD_ACTION_STOP)) { return stop_rsc; } else if (safe_str_eq(task, CRMD_ACTION_STOPPED)) { return stopped_rsc; } else if (safe_str_eq(task, CRMD_ACTION_START)) { return start_rsc; } else if (safe_str_eq(task, CRMD_ACTION_STARTED)) { return started_rsc; } else if (safe_str_eq(task, CRM_OP_SHUTDOWN)) { return shutdown_crm; } else if (safe_str_eq(task, CRM_OP_FENCE)) { return stonith_node; } else if (safe_str_eq(task, CRMD_ACTION_STATUS)) { return monitor_rsc; } else if (safe_str_eq(task, CRMD_ACTION_NOTIFY)) { return action_notify; } else if (safe_str_eq(task, CRMD_ACTION_NOTIFIED)) { return action_notified; } else if (safe_str_eq(task, CRMD_ACTION_PROMOTE)) { return action_promote; } else if (safe_str_eq(task, CRMD_ACTION_DEMOTE)) { return action_demote; } else if (safe_str_eq(task, CRMD_ACTION_PROMOTED)) { return action_promoted; } else if (safe_str_eq(task, CRMD_ACTION_DEMOTED)) { return action_demoted; } #if SUPPORT_TRACING if (safe_str_eq(task, CRMD_ACTION_CANCEL)) { return no_action; } else if (safe_str_eq(task, CRMD_ACTION_DELETE)) { return no_action; } else if (safe_str_eq(task, CRMD_ACTION_STATUS)) { return no_action; } else if (safe_str_eq(task, CRM_OP_PROBED)) { return no_action; } else if (safe_str_eq(task, CRM_OP_LRM_REFRESH)) { return no_action; } else if (safe_str_eq(task, CRMD_ACTION_MIGRATE)) { return no_action; } else if (safe_str_eq(task, CRMD_ACTION_MIGRATED)) { return no_action; } crm_trace("Unsupported action: %s", task); #endif return no_action; } const char * task2text(enum action_tasks task) { const char *result = ""; switch (task) { case no_action: result = "no_action"; break; case stop_rsc: result = CRMD_ACTION_STOP; break; case stopped_rsc: result = CRMD_ACTION_STOPPED; break; case start_rsc: result = CRMD_ACTION_START; break; case started_rsc: result = CRMD_ACTION_STARTED; break; case shutdown_crm: result = CRM_OP_SHUTDOWN; break; case stonith_node: result = CRM_OP_FENCE; break; case monitor_rsc: result = CRMD_ACTION_STATUS; break; case action_notify: result = CRMD_ACTION_NOTIFY; break; case action_notified: result = CRMD_ACTION_NOTIFIED; break; case action_promote: result = CRMD_ACTION_PROMOTE; break; case action_promoted: result = CRMD_ACTION_PROMOTED; break; case action_demote: result = CRMD_ACTION_DEMOTE; break; case action_demoted: result = CRMD_ACTION_DEMOTED; break; } return result; } const char * role2text(enum rsc_role_e role) { switch (role) { case RSC_ROLE_UNKNOWN: return RSC_ROLE_UNKNOWN_S; case RSC_ROLE_STOPPED: return RSC_ROLE_STOPPED_S; case RSC_ROLE_STARTED: return RSC_ROLE_STARTED_S; case RSC_ROLE_SLAVE: return RSC_ROLE_SLAVE_S; case RSC_ROLE_MASTER: return RSC_ROLE_MASTER_S; } CRM_CHECK(role >= RSC_ROLE_UNKNOWN, return RSC_ROLE_UNKNOWN_S); CRM_CHECK(role < RSC_ROLE_MAX, return RSC_ROLE_UNKNOWN_S); // coverity[dead_error_line] return RSC_ROLE_UNKNOWN_S; } enum rsc_role_e text2role(const char *role) { CRM_ASSERT(role != NULL); if (safe_str_eq(role, RSC_ROLE_STOPPED_S)) { return RSC_ROLE_STOPPED; } else if (safe_str_eq(role, RSC_ROLE_STARTED_S)) { return RSC_ROLE_STARTED; } else if (safe_str_eq(role, RSC_ROLE_SLAVE_S)) { return RSC_ROLE_SLAVE; } else if (safe_str_eq(role, RSC_ROLE_MASTER_S)) { return RSC_ROLE_MASTER; } else if (safe_str_eq(role, RSC_ROLE_UNKNOWN_S)) { return RSC_ROLE_UNKNOWN; } crm_err("Unknown role: %s", role); return RSC_ROLE_UNKNOWN; } int merge_weights(int w1, int w2) { int result = w1 + w2; if (w1 <= -INFINITY || w2 <= -INFINITY) { if (w1 >= INFINITY || w2 >= INFINITY) { crm_trace("-INFINITY + INFINITY == -INFINITY"); } return -INFINITY; } else if (w1 >= INFINITY || w2 >= INFINITY) { return INFINITY; } /* detect wrap-around */ if (result > 0) { if (w1 <= 0 && w2 < 0) { result = -INFINITY; } } else if (w1 > 0 && w2 > 0) { result = INFINITY; } /* detect +/- INFINITY */ if (result >= INFINITY) { result = INFINITY; } else if (result <= -INFINITY) { result = -INFINITY; } crm_trace("%d + %d = %d", w1, w2, result); return result; } void add_hash_param(GHashTable * hash, const char *name, const char *value) { CRM_CHECK(hash != NULL, return); crm_trace("adding: name=%s value=%s", crm_str(name), crm_str(value)); if (name == NULL || value == NULL) { return; } else if (safe_str_eq(value, "#default")) { return; } else if (g_hash_table_lookup(hash, name) == NULL) { g_hash_table_insert(hash, strdup(name), strdup(value)); } } const char * pe_node_attribute_calculated(const pe_node_t *node, const char *name, const resource_t *rsc) { const char *source; if(node == NULL) { return NULL; } else if(rsc == NULL) { return g_hash_table_lookup(node->details->attrs, name); } source = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET); if(source == NULL || safe_str_eq("host", source) == FALSE) { return g_hash_table_lookup(node->details->attrs, name); } /* Use attributes set for the containers location * instead of for the container itself * * Useful when the container is using the host's local * storage */ CRM_ASSERT(node->details->remote_rsc); CRM_ASSERT(node->details->remote_rsc->container); if(node->details->remote_rsc->container->running_on) { pe_node_t *host = node->details->remote_rsc->container->running_on->data; pe_rsc_trace(rsc, "%s: Looking for %s on the container host %s", rsc->id, name, host->details->uname); return g_hash_table_lookup(host->details->attrs, name); } pe_rsc_trace(rsc, "%s: Not looking for %s on the container host: %s is inactive", rsc->id, name, node->details->remote_rsc->container->id); return NULL; } const char * pe_node_attribute_raw(pe_node_t *node, const char *name) { if(node == NULL) { return NULL; } return g_hash_table_lookup(node->details->attrs, name); } diff --git a/tools/attrd_updater.c b/tools/attrd_updater.c index ebba945404..9a469633dc 100644 --- a/tools/attrd_updater.c +++ b/tools/attrd_updater.c @@ -1,380 +1,466 @@ /* * Copyright 2004-2020 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 -/* *INDENT-OFF* */ -static struct crm_option long_options[] = { - /* Top-level Options */ - {"help", 0, 0, '?', "\tThis text"}, - {"version", 0, 0, '$', "\tVersion information" }, - {"verbose", 0, 0, 'V', "\tIncrease debug output\n"}, - - {"name", 1, 0, 'n', "The attribute's name"}, - - {"-spacer-",1, 0, '-', "\nCommands:"}, - {"update", 1, 0, 'U', "Update the attribute's value in pacemaker-attrd. If this causes the value to change, it will also be updated in the cluster configuration"}, - {"update-both", 1, 0, 'B', "Update the attribute's value and time to wait (dampening) in pacemaker-attrd. If this causes the value or dampening to change, the attribute will also be written to the cluster configuration, so be aware that repeatedly changing the dampening reduces its effectiveness."}, - {"update-delay", 0, 0, 'Y', "Update the attribute's dampening in pacemaker-attrd (requires -d/--delay). If this causes the dampening to change, the attribute will also be written to the cluster configuration, so be aware that repeatedly changing the dampening reduces its effectiveness."}, - {"query", 0, 0, 'Q', "\tQuery the attribute's value from pacemaker-attrd"}, - {"delete", 0, 0, 'D', "\tDelete the attribute in pacemaker-attrd. If a value was previously set, it will also be removed from the cluster configuration"}, - {"refresh", 0, 0, 'R', "\t(Advanced) Force the pacemaker-attrd daemon to resend all current values to the CIB\n"}, - - {"-spacer-",1, 0, '-', "\nAdditional options:"}, - {"delay", 1, 0, 'd', "The time to wait (dampening) in seconds for further changes before writing"}, - {"set", 1, 0, 's', "(Advanced) The attribute set in which to place the value"}, - {"node", 1, 0, 'N', "Set the attribute for the named node (instead of the local one)"}, - {"all", 0, 0, 'A', "Show values of the attribute for all nodes (query only)"}, - /* lifetime could be implemented if there is sufficient user demand */ - {"lifetime",1, 0, 'l', "(Deprecated) Lifetime of the node attribute (silently ignored by cluster)"}, - {"private", 0, 0, 'p', "\tIf this creates a new attribute, never write the attribute to the CIB"}, +static pcmk__cli_option_t long_options[] = { + // long option, argument type, storage, short option, description, flags + { + "help", no_argument, NULL, '?', + "\tThis text", pcmk__option_default + }, + { + "version", no_argument, NULL, '$', + "\tVersion information", pcmk__option_default + }, + { + "verbose", no_argument, NULL, 'V', + "\tIncrease debug output\n", pcmk__option_default + }, + { + "name", required_argument, NULL, 'n', + "The attribute's name", pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nCommands:", pcmk__option_default + }, + { + "update", required_argument, NULL, 'U', + "Update attribute's value in pacemaker-attrd. If this causes the value " + "to change, it will also be updated in the cluster configuration.", + pcmk__option_default + }, + { + "update-both", required_argument, NULL, 'B', + "Update attribute's value and time to wait (dampening) in " + "pacemaker-attrd. If this causes the value or dampening to change, " + "the attribute will also be written to the cluster configuration, " + "so be aware that repeatedly changing the dampening reduces its " + "effectiveness.", + pcmk__option_default + }, + { + "update-delay", no_argument, NULL, 'Y', + "Update attribute's dampening in pacemaker-attrd (requires " + "-d/--delay). If this causes the dampening to change, the " + "attribute will also be written to the cluster configuration, so " + "be aware that repeatedly changing the dampening reduces its " + "effectiveness.", + pcmk__option_default + }, + { + "query", no_argument, NULL, 'Q', + "\tQuery the attribute's value from pacemaker-attrd", + pcmk__option_default + }, + { + "delete", no_argument, NULL, 'D', + "\tDelete attribute from pacemaker-attrd. If a value was previously " + "set, it will also be removed from the cluster configuration", + pcmk__option_default + }, + { + "refresh", no_argument, NULL, 'R', + "\t(Advanced) Force the pacemaker-attrd daemon to resend all current " + "values to the CIB", + pcmk__option_default + }, + + { + "-spacer-", no_argument, NULL, '-', + "\nAdditional options:", pcmk__option_default + }, + { + "delay", required_argument, NULL, 'd', + "The time to wait (dampening) in seconds for further changes " + "before writing", + pcmk__option_default + }, + { + "set", required_argument, NULL, 's', + "(Advanced) The attribute set in which to place the value", + pcmk__option_default + }, + { + "node", required_argument, NULL, 'N', + "Set the attribute for the named node (instead of the local one)", + pcmk__option_default + }, + { + "all", no_argument, NULL, 'A', + "Show values of the attribute for all nodes (query only)", + pcmk__option_default + }, + + // @TODO Implement --lifetime + { + "lifetime", required_argument, NULL, 'l', + "(Not yet implemented) Lifetime of the node attribute (silently " + "ignored by cluster)", + pcmk__option_default + }, + { + "private", no_argument, NULL, 'p', + "\tIf this creates a new attribute, never write the attribute to CIB", + pcmk__option_default + }, /* Legacy options */ - {"quiet", 0, 0, 'q', NULL, pcmk_option_hidden}, - {"update", 1, 0, 'v', NULL, pcmk_option_hidden}, - {"section", 1, 0, 'S', NULL, pcmk_option_hidden}, - {0, 0, 0, 0} + { + "quiet", no_argument, NULL, 'q', + NULL, pcmk__option_hidden + }, + { + "update", required_argument, NULL, 'v', + NULL, pcmk__option_hidden + }, + { + "section", required_argument, NULL, 'S', + NULL, pcmk__option_hidden + }, + { 0, 0, 0, 0 } }; -/* *INDENT-ON* */ static int do_query(const char *attr_name, const char *attr_node, gboolean query_all); static int do_update(char command, const char *attr_node, const char *attr_name, const char *attr_value, const char *attr_section, const char *attr_set, const char *attr_dampen, int attr_options); // Free memory at exit to make analyzers happy #define cleanup_memory() \ free(attr_dampen); \ free(attr_name); \ free(attr_node); \ free(attr_section); \ free(attr_set); #define set_option(option_var) \ if (option_var) { \ free(option_var); \ } \ option_var = strdup(optarg); int main(int argc, char **argv) { int index = 0; int argerr = 0; int attr_options = pcmk__node_attr_none; int flag; crm_exit_t exit_code = CRM_EX_OK; char *attr_node = NULL; char *attr_name = NULL; char *attr_set = NULL; char *attr_section = NULL; char *attr_dampen = NULL; const char *attr_value = NULL; char command = 'Q'; gboolean query_all = FALSE; crm_log_cli_init("attrd_updater"); - crm_set_options(NULL, "command -n attribute [options]", long_options, - "Tool for updating cluster node attributes"); + pcmk__set_cli_options(NULL, "-n [options]", + long_options, + "query and update Pacemaker node attributes"); if (argc < 2) { - crm_help('?', CRM_EX_USAGE); + pcmk__cli_help('?', CRM_EX_USAGE); } while (1) { - flag = crm_get_option(argc, argv, &index); + flag = pcmk__next_cli_option(argc, argv, &index, NULL); if (flag == -1) break; switch (flag) { case 'V': crm_bump_log_level(argc, argv); break; case '?': case '$': cleanup_memory(); - crm_help(flag, CRM_EX_OK); + pcmk__cli_help(flag, CRM_EX_OK); break; case 'n': set_option(attr_name); break; case 's': set_option(attr_set); break; case 'd': set_option(attr_dampen); break; case 'l': case 'S': set_option(attr_section); break; case 'N': set_option(attr_node); break; case 'A': query_all = TRUE; break; case 'p': set_bit(attr_options, pcmk__node_attr_private); break; case 'q': break; case 'Y': command = flag; crm_log_args(argc, argv); /* Too much? */ break; case 'Q': case 'B': case 'R': case 'D': case 'U': case 'v': command = flag; attr_value = optarg; crm_log_args(argc, argv); /* Too much? */ break; default: ++argerr; break; } } if (optind > argc) { ++argerr; } if (command != 'R' && attr_name == NULL) { ++argerr; } if (argerr) { cleanup_memory(); - crm_help('?', CRM_EX_USAGE); + pcmk__cli_help('?', CRM_EX_USAGE); } if (command == 'Q') { exit_code = crm_errno2exit(do_query(attr_name, attr_node, query_all)); } else { /* @TODO We don't know whether the specified node is a Pacemaker Remote * node or not, so we can't set pcmk__node_attr_remote when appropriate. * However, it's not a big problem, because pacemaker-attrd will learn * and remember a node's "remoteness". */ exit_code = pcmk_rc2exitc(do_update(command, pcmk__node_attr_target(attr_node), attr_name, attr_value, attr_section, attr_set, attr_dampen, attr_options)); } cleanup_memory(); crm_exit(exit_code); } /*! * \internal * \brief Submit a query request to pacemaker-attrd and wait for reply * * \param[in] name Name of attribute to query * \param[in] host Query applies to this host only (or all hosts if NULL) * \param[out] reply On success, will be set to new XML tree with reply * * \return pcmk_ok on success, -errno on error * \note On success, caller is responsible for freeing result via free_xml(*reply) */ static int send_attrd_query(const char *name, const char *host, xmlNode **reply) { int rc; crm_ipc_t *ipc; xmlNode *query; /* Build the query XML */ query = create_xml_node(NULL, __FUNCTION__); if (query == NULL) { return -ENOMEM; } crm_xml_add(query, F_TYPE, T_ATTRD); crm_xml_add(query, F_ORIG, crm_system_name); crm_xml_add(query, F_ATTRD_HOST, host); crm_xml_add(query, F_ATTRD_TASK, ATTRD_OP_QUERY); crm_xml_add(query, F_ATTRD_ATTRIBUTE, name); /* Connect to pacemaker-attrd, send query XML and get reply */ crm_debug("Sending query for value of %s on %s", name, (host? host : "all nodes")); ipc = crm_ipc_new(T_ATTRD, 0); if (crm_ipc_connect(ipc) == FALSE) { crm_perror(LOG_ERR, "Connection to cluster attribute manager failed"); rc = -ENOTCONN; } else { rc = crm_ipc_send(ipc, query, crm_ipc_flags_none|crm_ipc_client_response, 0, reply); if (rc > 0) { rc = pcmk_ok; } crm_ipc_close(ipc); } free_xml(query); return(rc); } /*! * \brief Validate pacemaker-attrd's XML reply to an query * * param[in] reply Root of reply XML tree to validate * param[in] attr_name Name of attribute that was queried * * \return pcmk_ok on success, * -errno on error (-ENXIO = requested attribute does not exist) */ static int validate_attrd_reply(xmlNode *reply, const char *attr_name) { const char *reply_attr; if (reply == NULL) { fprintf(stderr, "Could not query value of %s: reply did not contain valid XML\n", attr_name); return -pcmk_err_schema_validation; } crm_log_xml_trace(reply, "Reply"); reply_attr = crm_element_value(reply, F_ATTRD_ATTRIBUTE); if (reply_attr == NULL) { fprintf(stderr, "Could not query value of %s: attribute does not exist\n", attr_name); return -ENXIO; } if (safe_str_neq(crm_element_value(reply, F_TYPE), T_ATTRD) || (crm_element_value(reply, F_ATTRD_VERSION) == NULL) || strcmp(reply_attr, attr_name)) { fprintf(stderr, "Could not query value of %s: reply did not contain expected identification\n", attr_name); return -pcmk_err_schema_validation; } return pcmk_ok; } /*! * \brief Print the attribute values in a pacemaker-attrd XML query reply * * \param[in] reply Root of XML tree with query reply * \param[in] attr_name Name of attribute that was queried * * \return TRUE if any values were printed */ static gboolean print_attrd_values(xmlNode *reply, const char *attr_name) { xmlNode *child; const char *reply_host, *reply_value; gboolean have_values = FALSE; /* Iterate through reply's XML tags (a node tag for each host-value pair) */ for (child = __xml_first_child(reply); child != NULL; child = __xml_next(child)) { if (safe_str_neq((const char*)child->name, XML_CIB_TAG_NODE)) { crm_warn("Ignoring unexpected %s tag in query reply", child->name); } else { reply_host = crm_element_value(child, F_ATTRD_HOST); reply_value = crm_element_value(child, F_ATTRD_VALUE); if (reply_host == NULL) { crm_warn("Ignoring %s tag without %s attribute in query reply", XML_CIB_TAG_NODE, F_ATTRD_HOST); } else { printf("name=\"%s\" host=\"%s\" value=\"%s\"\n", attr_name, reply_host, (reply_value? reply_value : "")); have_values = TRUE; } } } return have_values; } /*! * \brief Submit a query to pacemaker-attrd and print reply * * \param[in] attr_name Name of attribute to be affected by request * \param[in] attr_node Name of host to query for (or NULL for localhost) * \param[in] query_all If TRUE, ignore attr_node and query all nodes instead * * \return pcmk_ok on success, -errno on error */ static int do_query(const char *attr_name, const char *attr_node, gboolean query_all) { xmlNode *reply = NULL; int rc; /* Decide which node(s) to query */ if (query_all == TRUE) { attr_node = NULL; } else { attr_node = pcmk__node_attr_target(attr_node); } /* Build and send pacemaker-attrd request, and get XML reply */ rc = send_attrd_query(attr_name, attr_node, &reply); if (rc != pcmk_ok) { fprintf(stderr, "Could not query value of %s: %s (%d)\n", attr_name, pcmk_strerror(rc), rc); return rc; } /* Validate the XML reply */ rc = validate_attrd_reply(reply, attr_name); if (rc != pcmk_ok) { if (reply != NULL) { free_xml(reply); } return rc; } /* Print the values from the reply */ if (print_attrd_values(reply, attr_name) == FALSE) { fprintf(stderr, "Could not query value of %s: reply had attribute name but no host values\n", attr_name); free_xml(reply); return -pcmk_err_schema_validation; } return pcmk_ok; } static int do_update(char command, const char *attr_node, const char *attr_name, const char *attr_value, const char *attr_section, const char *attr_set, const char *attr_dampen, int attr_options) { int rc = pcmk__node_attr_request(NULL, command, attr_node, attr_name, attr_value, attr_section, attr_set, attr_dampen, NULL, attr_options); if (rc != pcmk_rc_ok) { fprintf(stderr, "Could not update %s=%s: %s (%d)\n", attr_name, attr_value, pcmk_rc_str(rc), rc); } return rc; } diff --git a/tools/cibadmin.c b/tools/cibadmin.c index 9661320c94..6deec24f90 100644 --- a/tools/cibadmin.c +++ b/tools/cibadmin.c @@ -1,552 +1,772 @@ /* - * Copyright 2004-2019 the Pacemaker project contributors + * Copyright 2004-2020 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 static int message_timeout_ms = 30; static int command_options = 0; static int request_id = 0; static int bump_log_num = 0; static const char *host = NULL; static const char *cib_user = NULL; static const char *cib_action = NULL; static const char *obj_type = NULL; static cib_t *the_cib = NULL; static GMainLoop *mainloop = NULL; static gboolean force_flag = FALSE; static crm_exit_t exit_code = CRM_EX_OK; int do_init(void); int do_work(xmlNode *input, int command_options, xmlNode **output); void cibadmin_op_callback(xmlNode *msg, int call_id, int rc, xmlNode *output, void *user_data); -/* *INDENT-OFF* */ -static struct crm_option long_options[] = { - {"help", 0, 0, '?', "\tThis text"}, - {"version", 0, 0, '$', "\tVersion information" }, - {"verbose", 0, 0, 'V', "\tIncrease debug output\n"}, - - {"-spacer-", 0, 0, '-', "Commands:"}, - {"upgrade", 0, 0, 'u', "\tUpgrade the configuration to the latest syntax"}, - {"query", 0, 0, 'Q', "\tQuery the contents of the CIB"}, - {"erase", 0, 0, 'E', "\tErase the contents of the whole CIB"}, - {"bump", 0, 0, 'B', "\tIncrease the CIB's epoch value by 1"}, - {"create", 0, 0, 'C', "\tCreate an object in the CIB. Will fail if the object already exists."}, - {"modify", 0, 0, 'M', "\tFind the object somewhere in the CIB's XML tree and update it. Fails if the object does not exist unless -c is specified"}, - {"patch", 0, 0, 'P', "\tSupply an update in the form of an xml diff (See also: crm_diff)"}, - {"replace", 0, 0, 'R', "\tRecursively replace an object in the CIB"}, - {"delete", 0, 0, 'D', "\tDelete the first object matching the supplied criteria, Eg. "}, - {"-spacer-", 0, 0, '-', "\n\tThe tagname and all attributes must match in order for the element to be deleted\n"}, - {"delete-all", 0, 0, 'd', "When used with --xpath, remove all matching objects in the configuration instead of just the first one"}, - {"empty", 0, 0, 'a', "\tOutput an empty CIB"}, - {"md5-sum", 0, 0, '5', "\tCalculate the on-disk CIB digest"}, - {"md5-sum-versioned", 0, 0, '6', "Calculate an on-the-wire versioned CIB digest"}, - {"blank", 0, 0, '-', NULL, 1}, - - {"-spacer-",1, 0, '-', "\nAdditional options:"}, - {"force", 0, 0, 'f'}, - {"timeout", 1, 0, 't', "Time (in seconds) to wait before declaring the operation failed"}, - {"user", 1, 0, 'U', "Run the command with permissions of the named user (valid only for the root and "CRM_DAEMON_USER" accounts)"}, - {"sync-call", 0, 0, 's', "Wait for call to complete before returning"}, - {"local", 0, 0, 'l', "\tCommand takes effect locally. Should only be used for queries"}, - {"allow-create",0, 0, 'c', "(Advanced) Allow the target of a --modify,-M operation to be created if they do not exist"}, - {"no-children", 0, 0, 'n', "(Advanced) When querying an object, do not return include its children in the result\n"}, - {"no-bcast", 0, 0, 'b', NULL, 1}, - - {"-spacer-", 0, 0, '-', "Data:"}, - {"xml-text", 1, 0, 'X', "Retrieve XML from the supplied string"}, - {"xml-file", 1, 0, 'x', "Retrieve XML from the named file"}, - {"xml-pipe", 0, 0, 'p', "Retrieve XML from stdin\n"}, - - {"scope", 1, 0, 'o', "Limit the scope of the operation to a specific section of the CIB."}, - {"-spacer-", 0, 0, '-', "\tValid values are: nodes, resources, constraints, crm_config, rsc_defaults, op_defaults, status"}, - - {"xpath", 1, 0, 'A', "A valid XPath to use instead of --scope,-o"}, - {"node-path", 0, 0, 'e', "When performing XPath queries, return the address of any matches found."}, - {"-spacer-", 0, 0, '-', " Eg: /cib/configuration/resources/clone[@id='ms_RH1_SCS']/primitive[@id='prm_RH1_SCS']", pcmk_option_paragraph}, - {"node", 1, 0, 'N', "(Advanced) Send command to the specified host\n"}, - {"-space-", 0, 0, '!', NULL, 1}, - - {"-spacer-", 0, 0, '-', "\nExamples:\n"}, - {"-spacer-", 0, 0, '-', "Query the configuration from the local node:", pcmk_option_paragraph}, - {"-spacer-", 0, 0, '-', " cibadmin --query --local", pcmk_option_example}, - - {"-spacer-", 0, 0, '-', "Query just the cluster options configuration:", pcmk_option_paragraph}, - {"-spacer-", 0, 0, '-', " cibadmin --query --scope crm_config", pcmk_option_example}, - - {"-spacer-", 0, 0, '-', "Query all 'target-role' settings:", pcmk_option_paragraph}, - {"-spacer-", 0, 0, '-', " cibadmin --query --xpath \"//nvpair[@name='target-role']\"", pcmk_option_example}, - - {"-spacer-", 0, 0, '-', "Remove all 'is-managed' settings:", pcmk_option_paragraph}, - {"-spacer-", 0, 0, '-', " cibadmin --delete-all --xpath \"//nvpair[@name='is-managed']\"", pcmk_option_example}, - - {"-spacer-", 0, 0, '-', "Remove the resource named 'old':", pcmk_option_paragraph}, - {"-spacer-", 0, 0, '-', " cibadmin --delete --xml-text ''", pcmk_option_example}, - - {"-spacer-", 0, 0, '-', "Remove all resources from the configuration:", pcmk_option_paragraph}, - {"-spacer-", 0, 0, '-', " cibadmin --replace --scope resources --xml-text ''", pcmk_option_example}, - - {"-spacer-", 0, 0, '-', "Replace the complete configuration with the contents of $HOME/pacemaker.xml:", pcmk_option_paragraph}, - {"-spacer-", 0, 0, '-', " cibadmin --replace --xml-file $HOME/pacemaker.xml", pcmk_option_example}, - - {"-spacer-", 0, 0, '-', "Replace the constraints section of the configuration with the contents of $HOME/constraints.xml:", pcmk_option_paragraph}, - {"-spacer-", 0, 0, '-', " cibadmin --replace --scope constraints --xml-file $HOME/constraints.xml", pcmk_option_example}, - - {"-spacer-", 0, 0, '-', "Increase the configuration version to prevent old configurations from being loaded accidentally:", pcmk_option_paragraph}, - {"-spacer-", 0, 0, '-', " cibadmin --modify --xml-text ''", pcmk_option_example}, - - {"-spacer-", 0, 0, '-', "Edit the configuration with your favorite $EDITOR:", pcmk_option_paragraph}, - {"-spacer-", 0, 0, '-', " cibadmin --query > $HOME/local.xml", pcmk_option_example}, - {"-spacer-", 0, 0, '-', " $EDITOR $HOME/local.xml", pcmk_option_example}, - {"-spacer-", 0, 0, '-', " cibadmin --replace --xml-file $HOME/local.xml", pcmk_option_example}, - - {"-spacer-", 0, 0, '-', "SEE ALSO:"}, - {"-spacer-", 0, 0, '-', " crm(8), pcs(8), crm_shadow(8), crm_diff(8)"}, - - /* Legacy options */ - {"host", 1, 0, 'h', NULL, 1}, - - {0, 0, 0, 0} +static pcmk__cli_option_t long_options[] = { + // long option, argument type, storage, short option, description, flags + { + "help", no_argument, NULL, '?', + "\tThis text", pcmk__option_default + }, + { + "version", no_argument, NULL, '$', + "\tVersion information", pcmk__option_default + }, + { + "verbose", no_argument, NULL, 'V', + "\tIncrease debug output\n", pcmk__option_default + }, + + { + "-spacer-", no_argument, NULL, '-', + "Commands:", pcmk__option_default + }, + { + "upgrade", no_argument, NULL, 'u', + "\tUpgrade the configuration to the latest syntax", pcmk__option_default + }, + { + "query", no_argument, NULL, 'Q', + "\tQuery the contents of the CIB", pcmk__option_default + }, + { + "erase", no_argument, NULL, 'E', + "\tErase the contents of the whole CIB", pcmk__option_default + }, + { + "bump", no_argument, NULL, 'B', + "\tIncrease the CIB's epoch value by 1", pcmk__option_default + }, + { + "create", no_argument, NULL, 'C', + "\tCreate an object in the CIB (will fail if object already exists)", + pcmk__option_default + }, + { + "modify", no_argument, NULL, 'M', + "\tFind object somewhere in CIB's XML tree and update it " + "(fails if object does not exist unless -c is also specified)", + pcmk__option_default + }, + { + "patch", no_argument, NULL, 'P', + "\tSupply an update in the form of an XML diff (see crm_diff(8))", + pcmk__option_default + }, + { + "replace", no_argument, NULL, 'R', + "\tRecursively replace an object in the CIB", pcmk__option_default + }, + { + "delete", no_argument, NULL, 'D', + "\tDelete first object matching supplied criteria " + "(for example, )", + pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\n\tThe XML element name and all attributes must match " + "in order for the element to be deleted.\n", + pcmk__option_default + }, + { + "delete-all", no_argument, NULL, 'd', + "When used with --xpath, remove all matching objects in the " + "configuration instead of just the first one", + pcmk__option_default + }, + { + "empty", no_argument, NULL, 'a', + "\tOutput an empty CIB", pcmk__option_default + }, + { + "md5-sum", no_argument, NULL, '5', + "\tCalculate the on-disk CIB digest", pcmk__option_default + }, + { + "md5-sum-versioned", no_argument, NULL, '6', + "Calculate an on-the-wire versioned CIB digest", pcmk__option_default + }, + { + "blank", no_argument, NULL, '-', + NULL, pcmk__option_hidden + }, + + { + "-spacer-", required_argument, NULL, '-', + "\nAdditional options:", pcmk__option_default + }, + { + "force", no_argument, NULL, 'f', + NULL, pcmk__option_default + }, + { + "timeout", required_argument, NULL, 't', + "Time (in seconds) to wait before declaring the operation failed", + pcmk__option_default + }, + { + "user", required_argument, NULL, 'U', + "Run the command with permissions of the named user (valid only for " + "the root and " CRM_DAEMON_USER " accounts)", + pcmk__option_default + }, + { + "sync-call", no_argument, NULL, 's', + "Wait for call to complete before returning", pcmk__option_default + }, + { + "local", no_argument, NULL, 'l', + "\tCommand takes effect locally (should be used only for queries)", + pcmk__option_default + }, + { + "allow-create", no_argument, NULL, 'c', + "(Advanced) Allow target of --modify/-M to be created " + "if it does not exist", + pcmk__option_default + }, + { + "no-children", no_argument, NULL, 'n', + "(Advanced) When querying an object, do not include its children " + "in the result", + pcmk__option_default + }, + { + "no-bcast", no_argument, NULL, 'b', + NULL, pcmk__option_hidden + }, + + { + "-spacer-", no_argument, NULL, '-', + "\nData:", pcmk__option_default + }, + { + "xml-text", required_argument, NULL, 'X', + "Retrieve XML from the supplied string", pcmk__option_default + }, + { + "xml-file", required_argument, NULL, 'x', + "Retrieve XML from the named file", pcmk__option_default + }, + { + "xml-pipe", no_argument, NULL, 'p', + "Retrieve XML from stdin\n", pcmk__option_default + }, + + { + "scope", required_argument, NULL, 'o', + "Limit scope of operation to specific section of CIB", + pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\tValid values: nodes, resources, constraints, crm_config, " + "rsc_defaults, op_defaults, status", + pcmk__option_default + }, + + { + "xpath", required_argument, NULL, 'A', + "A valid XPath to use instead of --scope/-o", pcmk__option_default + }, + { + "node-path", no_argument, NULL, 'e', + "When performing XPath queries, return path of any matches found", + pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "(for example, \"/cib/configuration/resources/clone[@id='ms_RH1_SCS']" + "/primitive[@id='prm_RH1_SCS']\")", + pcmk__option_paragraph + }, + { + "node", required_argument, NULL, 'N', + "(Advanced) Send command to the specified host", pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '!', + NULL, pcmk__option_hidden + }, + { + "-spacer-", no_argument, NULL, '-', + "\n\nExamples:\n", pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "Query the configuration from the local node:", pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " cibadmin --query --local", pcmk__option_example + }, + + { + "-spacer-", no_argument, NULL, '-', + "Query just the cluster options configuration:", pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " cibadmin --query --scope crm_config", pcmk__option_example + }, + + { + "-spacer-", no_argument, NULL, '-', + "Query all 'target-role' settings:", pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " cibadmin --query --xpath \"//nvpair[@name='target-role']\"", + pcmk__option_example + }, + + { + "-spacer-", no_argument, NULL, '-', + "Remove all 'is-managed' settings:", pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " cibadmin --delete-all --xpath \"//nvpair[@name='is-managed']\"", + pcmk__option_example + }, + + { + "-spacer-", no_argument, NULL, '-', + "Remove the resource named 'old':", pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " cibadmin --delete --xml-text ''", + pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Remove all resources from the configuration:", pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " cibadmin --replace --scope resources --xml-text ''", + pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Replace complete configuration with contents of $HOME/pacemaker.xml:", + pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " cibadmin --replace --xml-file $HOME/pacemaker.xml", + pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Replace constraints section of configuration with contents of " + "$HOME/constraints.xml:", + pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " cibadmin --replace --scope constraints --xml-file " + "$HOME/constraints.xml", + pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Increase configuration version to prevent old configurations from " + "being loaded accidentally:", + pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " cibadmin --modify --xml-text ''", + pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Edit the configuration with your favorite $EDITOR:", + pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " cibadmin --query > $HOME/local.xml", pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + " $EDITOR $HOME/local.xml", pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + " cibadmin --replace --xml-file $HOME/local.xml", pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "SEE ALSO:", pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + " crm(8), pcs(8), crm_shadow(8), crm_diff(8)", pcmk__option_default + }, + { + "host", required_argument, NULL, 'h', + "deprecated", pcmk__option_hidden + }, + { 0, 0, 0, 0 } }; -/* *INDENT-ON* */ static void print_xml_output(xmlNode * xml) { char *buffer; if (!xml) { return; } else if (xml->type != XML_ELEMENT_NODE) { return; } if (command_options & cib_xpath_address) { const char *id = crm_element_value(xml, XML_ATTR_ID); if (safe_str_eq((const char *)xml->name, "xpath-query")) { xmlNode *child = NULL; for (child = xml->children; child; child = child->next) { print_xml_output(child); } } else if (id) { printf("%s\n", id); } } else { buffer = dump_xml_formatted(xml); fprintf(stdout, "%s", crm_str(buffer)); free(buffer); } } // Upgrade requested but already at latest schema static void report_schema_unchanged() { const char *err = pcmk_strerror(pcmk_err_schema_unchanged); crm_info("Upgrade unnecessary: %s\n", err); printf("Upgrade unnecessary: %s\n", err); exit_code = CRM_EX_OK; } int main(int argc, char **argv) { int argerr = 0; int rc = pcmk_ok; int flag; const char *source = NULL; const char *admin_input_xml = NULL; const char *admin_input_file = NULL; gboolean dangerous_cmd = FALSE; gboolean admin_input_stdin = FALSE; xmlNode *output = NULL; xmlNode *input = NULL; int option_index = 0; crm_log_cli_init("cibadmin"); set_crm_log_level(LOG_CRIT); - crm_set_options(NULL, "command [options] [data]", long_options, - "Provides direct access to the cluster configuration." - "\n\nAllows the configuration, or sections of it, to be queried, modified, replaced and deleted." - "\n\nWhere necessary, XML data will be obtained using the -X, -x, or -p options.\n"); + pcmk__set_cli_options(NULL, " [options]", long_options, + "query and edit the Pacemaker configuration"); if (argc < 2) { - crm_help('?', CRM_EX_USAGE); + pcmk__cli_help('?', CRM_EX_USAGE); } while (1) { - flag = crm_get_option(argc, argv, &option_index); + flag = pcmk__next_cli_option(argc, argv, &option_index, NULL); if (flag == -1) break; switch (flag) { case 't': message_timeout_ms = atoi(optarg); if (message_timeout_ms < 1) { message_timeout_ms = 30; } break; case 'A': obj_type = optarg; command_options |= cib_xpath; break; case 'e': command_options |= cib_xpath_address; break; case 'u': cib_action = CIB_OP_UPGRADE; dangerous_cmd = TRUE; break; case 'E': cib_action = CIB_OP_ERASE; dangerous_cmd = TRUE; break; case 'Q': cib_action = CIB_OP_QUERY; break; case 'P': cib_action = CIB_OP_APPLY_DIFF; break; case 'U': cib_user = optarg; break; case 'M': cib_action = CIB_OP_MODIFY; break; case 'R': cib_action = CIB_OP_REPLACE; break; case 'C': cib_action = CIB_OP_CREATE; break; case 'D': cib_action = CIB_OP_DELETE; break; case '5': cib_action = "md5-sum"; break; case '6': cib_action = "md5-sum-versioned"; break; case 'c': command_options |= cib_can_create; break; case 'n': command_options |= cib_no_children; break; case 'B': cib_action = CIB_OP_BUMP; crm_log_args(argc, argv); break; case 'V': command_options = command_options | cib_verbose; bump_log_num++; break; case '?': case '$': case '!': - crm_help(flag, CRM_EX_OK); + pcmk__cli_help(flag, CRM_EX_OK); break; case 'o': crm_trace("Option %c => %s", flag, optarg); obj_type = optarg; break; case 'X': crm_trace("Option %c => %s", flag, optarg); admin_input_xml = optarg; crm_log_args(argc, argv); break; case 'x': crm_trace("Option %c => %s", flag, optarg); admin_input_file = optarg; crm_log_args(argc, argv); break; case 'p': admin_input_stdin = TRUE; crm_log_args(argc, argv); break; case 'N': case 'h': host = strdup(optarg); break; case 'l': command_options |= cib_scope_local; break; case 'd': cib_action = CIB_OP_DELETE; command_options |= cib_multiple; dangerous_cmd = TRUE; break; case 'b': dangerous_cmd = TRUE; command_options |= cib_inhibit_bcast; command_options |= cib_scope_local; break; case 's': command_options |= cib_sync_call; break; case 'f': force_flag = TRUE; command_options |= cib_quorum_override; crm_log_args(argc, argv); break; case 'a': output = createEmptyCib(1); if (optind < argc) { crm_xml_add(output, XML_ATTR_VALIDATION, argv[optind]); } admin_input_xml = dump_xml_formatted(output); fprintf(stdout, "%s\n", crm_str(admin_input_xml)); crm_exit(CRM_EX_OK); break; default: printf("Argument code 0%o (%c)" " is not (?yet?) supported\n", flag, flag); ++argerr; break; } } while (bump_log_num > 0) { crm_bump_log_level(argc, argv); bump_log_num--; } if (optind < argc) { printf("non-option ARGV-elements: "); while (optind < argc) printf("%s ", argv[optind++]); printf("\n"); - crm_help('?', CRM_EX_USAGE); + pcmk__cli_help('?', CRM_EX_USAGE); } if (optind > argc || cib_action == NULL) { ++argerr; } if (argerr) { - crm_help('?', CRM_EX_USAGE); + pcmk__cli_help('?', CRM_EX_USAGE); } if (dangerous_cmd && force_flag == FALSE) { fprintf(stderr, "The supplied command is considered dangerous." " To prevent accidental destruction of the cluster," " the --force flag is required in order to proceed.\n"); fflush(stderr); crm_exit(CRM_EX_UNSAFE); } if (admin_input_file != NULL) { input = filename2xml(admin_input_file); source = admin_input_file; } else if (admin_input_xml != NULL) { source = "input string"; input = string2xml(admin_input_xml); } else if (admin_input_stdin) { source = "STDIN"; input = stdin2xml(); } if (input != NULL) { crm_log_xml_debug(input, "[admin input]"); } else if (source) { fprintf(stderr, "Couldn't parse input from %s.\n", source); crm_exit(CRM_EX_CONFIG); } if (safe_str_eq(cib_action, "md5-sum")) { char *digest = NULL; if (input == NULL) { fprintf(stderr, "Please supply XML to process with -X, -x or -p\n"); crm_exit(CRM_EX_USAGE); } digest = calculate_on_disk_digest(input); fprintf(stderr, "Digest: "); fprintf(stdout, "%s\n", crm_str(digest)); free(digest); free_xml(input); crm_exit(CRM_EX_OK); } else if (safe_str_eq(cib_action, "md5-sum-versioned")) { char *digest = NULL; const char *version = NULL; if (input == NULL) { fprintf(stderr, "Please supply XML to process with -X, -x or -p\n"); crm_exit(CRM_EX_USAGE); } version = crm_element_value(input, XML_ATTR_CRM_VERSION); digest = calculate_xml_versioned_digest(input, FALSE, TRUE, version); fprintf(stderr, "Versioned (%s) digest: ", version); fprintf(stdout, "%s\n", crm_str(digest)); free(digest); free_xml(input); crm_exit(CRM_EX_OK); } rc = do_init(); if (rc != pcmk_ok) { crm_err("Init failed, could not perform requested operations"); fprintf(stderr, "Init failed, could not perform requested operations\n"); free_xml(input); crm_exit(crm_errno2exit(rc)); } rc = do_work(input, command_options, &output); if (rc > 0) { /* wait for the reply by creating a mainloop and running it until * the callbacks are invoked... */ request_id = rc; the_cib->cmds->register_callback(the_cib, request_id, message_timeout_ms, FALSE, NULL, "cibadmin_op_callback", cibadmin_op_callback); mainloop = g_main_loop_new(NULL, FALSE); crm_trace("%s waiting for reply from the local CIB", crm_system_name); crm_info("Starting mainloop"); g_main_loop_run(mainloop); } else if ((rc == -pcmk_err_schema_unchanged) && crm_str_eq(cib_action, CIB_OP_UPGRADE, TRUE)) { report_schema_unchanged(); } else if (rc < 0) { crm_err("Call failed: %s", pcmk_strerror(rc)); fprintf(stderr, "Call failed: %s\n", pcmk_strerror(rc)); if (rc == -pcmk_err_schema_validation) { if (crm_str_eq(cib_action, CIB_OP_UPGRADE, TRUE)) { xmlNode *obj = NULL; int version = 0, rc = 0; rc = the_cib->cmds->query(the_cib, NULL, &obj, command_options); if (rc == pcmk_ok) { update_validation(&obj, &version, 0, TRUE, FALSE); } } else if (output) { validate_xml_verbose(output); } } exit_code = crm_errno2exit(rc); } if (output != NULL) { print_xml_output(output); free_xml(output); } crm_trace("%s exiting normally", crm_system_name); free_xml(input); rc = the_cib->cmds->signoff(the_cib); if (exit_code == CRM_EX_OK) { exit_code = crm_errno2exit(rc); } cib_delete(the_cib); crm_exit(exit_code); } int do_work(xmlNode * input, int call_options, xmlNode ** output) { /* construct the request */ the_cib->call_timeout = message_timeout_ms; if (strcasecmp(CIB_OP_REPLACE, cib_action) == 0 && safe_str_eq(crm_element_name(input), XML_TAG_CIB)) { xmlNode *status = get_object_root(XML_CIB_TAG_STATUS, input); if (status == NULL) { create_xml_node(input, XML_CIB_TAG_STATUS); } } if (cib_action != NULL) { crm_trace("Passing \"%s\" to variant_op...", cib_action); return cib_internal_op(the_cib, cib_action, host, obj_type, input, output, call_options, cib_user); } else { crm_err("You must specify an operation"); } return -EINVAL; } int do_init(void) { int rc = pcmk_ok; the_cib = cib_new(); rc = the_cib->cmds->signon(the_cib, crm_system_name, cib_command); if (rc != pcmk_ok) { crm_err("Connection to the CIB manager failed: %s", pcmk_strerror(rc)); fprintf(stderr, "Connection to the CIB manager failed: %s\n", pcmk_strerror(rc)); } return rc; } void cibadmin_op_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data) { exit_code = crm_errno2exit(rc); if (rc == -pcmk_err_schema_unchanged) { report_schema_unchanged(); } else if (rc != pcmk_ok) { crm_warn("Call %s failed (%d): %s", cib_action, rc, pcmk_strerror(rc)); fprintf(stderr, "Call %s failed (%d): %s\n", cib_action, rc, pcmk_strerror(rc)); print_xml_output(output); } else if (safe_str_eq(cib_action, CIB_OP_QUERY) && output == NULL) { crm_err("Query returned no output"); crm_log_xml_err(msg, "no output"); } else if (output == NULL) { crm_info("Call passed"); } else { crm_info("Call passed"); print_xml_output(output); } if (call_id == request_id) { g_main_loop_quit(mainloop); } else { crm_info("Message was not the response we were looking for (%d vs. %d)", call_id, request_id); } } diff --git a/tools/crm_attribute.c b/tools/crm_attribute.c index 37274caebc..6445d11620 100644 --- a/tools/crm_attribute.c +++ b/tools/crm_attribute.c @@ -1,354 +1,494 @@ /* * Copyright 2004-2020 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 gboolean BE_QUIET = FALSE; char command = 'G'; const char *dest_uname = NULL; char *dest_node = NULL; char *set_name = NULL; char *attr_id = NULL; char *attr_name = NULL; char *attr_pattern = NULL; const char *type = NULL; const char *rsc_id = NULL; const char *attr_value = NULL; const char *attr_default = NULL; const char *set_type = NULL; -/* *INDENT-OFF* */ -static struct crm_option long_options[] = { - /* Top-level Options */ - {"help", 0, 0, '?', "\tThis text"}, - {"version", 0, 0, '$', "\tVersion information" }, - {"verbose", 0, 0, 'V', "\tIncrease debug output"}, - {"quiet", 0, 0, 'q', "\tPrint only the value on stdout\n"}, - - {"name", 1, 0, 'n', "Name of the attribute/option to operate on"}, - {"pattern", 1, 0, 'P', "Pattern matching names of attributes (only with -v/-D and -l reboot)"}, - - {"-spacer-", 0, 0, '-', "\nCommands:"}, - {"query", 0, 0, 'G', "\tQuery the current value of the attribute/option"}, - {"update", 1, 0, 'v', "Update the value of the attribute/option"}, - {"delete", 0, 0, 'D', "\tDelete the attribute/option"}, - - {"-spacer-", 0, 0, '-', "\nAdditional Options:"}, - {"node", 1, 0, 'N', "Set an attribute for the named node (instead of a cluster option). See also: -l"}, - {"type", 1, 0, 't', "Which part of the configuration to update/delete/query the option in"}, - {"-spacer-", 0, 0, '-', "\t\t\tValid values: crm_config, rsc_defaults, op_defaults, tickets"}, - {"lifetime", 1, 0, 'l', "Lifetime of the node attribute"}, - {"-spacer-", 0, 0, '-', "\t\t\tValid values: reboot, forever"}, - {"utilization", 0, 0, 'z', "Set an utilization attribute for the node."}, - {"set-name", 1, 0, 's', "(Advanced) The attribute set in which to place the value"}, - {"id", 1, 0, 'i', "\t(Advanced) The ID used to identify the attribute"}, - {"default", 1, 0, 'd', "(Advanced) The default value to display if none is found in the configuration"}, - - {"inhibit-policy-engine", 0, 0, '!', NULL, 1}, +static pcmk__cli_option_t long_options[] = { + // long option, argument type, storage, short option, description, flags + { + "help", no_argument, NULL, '?', + "\tThis text", pcmk__option_default + }, + { + "version", no_argument, NULL, '$', + "\tVersion information", pcmk__option_default + }, + { + "verbose", no_argument, NULL, 'V', + "\tIncrease debug output", pcmk__option_default + }, + { + "quiet", no_argument, NULL, 'q', + "\tPrint only the value on stdout\n", pcmk__option_default + }, + { + "name", required_argument, NULL, 'n', + "Name of the attribute/option to operate on", pcmk__option_default + }, + { + "pattern", required_argument, NULL, 'P', + "Pattern matching names of attributes (only with -v/-D and -l reboot)", + pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nCommands:", pcmk__option_default + }, + { + "query", no_argument, NULL, 'G', + "\tQuery the current value of the attribute/option", + pcmk__option_default + }, + { + "update", required_argument, NULL, 'v', + "Update the value of the attribute/option", pcmk__option_default + }, + { + "delete", no_argument, NULL, 'D', + "\tDelete the attribute/option", pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nAdditional Options:", pcmk__option_default + }, + { + "node", required_argument, NULL, 'N', + "Set a node attribute for named node (instead of a cluster option). " + "See also: -l", + pcmk__option_default + }, + { + "type", required_argument, NULL, 't', + "Which part of the configuration to update/delete/query the option in", + pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\t\t\tValid values: crm_config, rsc_defaults, op_defaults, tickets", + pcmk__option_default + }, + { + "lifetime", required_argument, NULL, 'l', + "Lifetime of the node attribute", pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\t\t\tValid values: reboot, forever", pcmk__option_default + }, + { + "utilization", no_argument, NULL, 'z', + "Set an utilization attribute for the node.", pcmk__option_default + }, + { + "set-name", required_argument, NULL, 's', + "(Advanced) The attribute set in which to place the value", + pcmk__option_default + }, + { + "id", required_argument, NULL, 'i', + "\t(Advanced) The ID used to identify the attribute", + pcmk__option_default + }, + { + "default", required_argument, NULL, 'd', + "(Advanced) Default value to display if none is found in configuration", + pcmk__option_default + }, + { + "inhibit-policy-engine", no_argument, NULL, '!', + NULL, pcmk__option_hidden + }, /* legacy */ - {"quiet", 0, 0, 'Q', NULL, 1}, - {"node-uname", 1, 0, 'U', NULL, 1}, - {"get-value", 0, 0, 'G', NULL, 1}, - {"delete-attr", 0, 0, 'D', NULL, 1}, - {"attr-value", 1, 0, 'v', NULL, 1}, - {"attr-name", 1, 0, 'n', NULL, 1}, - {"attr-id", 1, 0, 'i', NULL, 1}, - - {"-spacer-", 1, 0, '-', "\nExamples:", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', "Add a new node attribute called 'location' with the value of 'office' for host 'myhost':", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', " crm_attribute --node myhost --name location --update office", pcmk_option_example}, - {"-spacer-", 1, 0, '-', "Query the value of the 'location' node attribute for host 'myhost':", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', " crm_attribute --node myhost --name location --query", pcmk_option_example}, - {"-spacer-", 1, 0, '-', "Change the value of the 'location' node attribute for host 'myhost':", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', " crm_attribute --node myhost --name location --update backoffice", pcmk_option_example}, - {"-spacer-", 1, 0, '-', "Delete the 'location' node attribute for host 'myhost':", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', " crm_attribute --node myhost --name location --delete", pcmk_option_example}, - {"-spacer-", 1, 0, '-', "Query the value of the cluster-delay cluster option:", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', " crm_attribute --type crm_config --name cluster-delay --query", pcmk_option_example}, - {"-spacer-", 1, 0, '-', "Query the value of the cluster-delay cluster option. Only print the value:", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', " crm_attribute --type crm_config --name cluster-delay --query --quiet", pcmk_option_example}, - - {0, 0, 0, 0} + { + "quiet", no_argument, NULL, 'Q', + NULL, pcmk__option_hidden + }, + { + "node-uname", required_argument, NULL, 'U', + NULL, pcmk__option_hidden + }, + { + "get-value", no_argument, NULL, 'G', + NULL, pcmk__option_hidden + }, + { + "delete-attr", no_argument, NULL, 'D', + NULL, pcmk__option_hidden + }, + { + "attr-value", required_argument, NULL, 'v', + NULL, pcmk__option_hidden + }, + { + "attr-name", required_argument, NULL, 'n', + NULL, pcmk__option_hidden + }, + { + "attr-id", required_argument, NULL, 'i', + NULL, pcmk__option_hidden + }, + + { + "-spacer-", no_argument, NULL, '-', + "\nExamples:", pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + "Add new node attribute called 'location' with the value of 'office' " + "for host 'myhost':", + pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_attribute --node myhost --name location --update office", + pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Query the value of the 'location' node attribute for host 'myhost':", + pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_attribute --node myhost --name location --query", + pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Change the value of the 'location' node attribute for host 'myhost':", + pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_attribute --node myhost --name location --update backoffice", + pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Delete the 'location' node attribute for host 'myhost':", + pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_attribute --node myhost --name location --delete", + pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Query the value of the cluster-delay cluster option:", + pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_attribute --type crm_config --name cluster-delay --query", + pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Query value of the \"cluster-delay\" cluster option and print only " + "the value:", + pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_attribute --type crm_config --name cluster-delay --query --quiet", + pcmk__option_example + }, + { 0, 0, 0, 0 } }; -/* *INDENT-ON* */ int main(int argc, char **argv) { cib_t *the_cib = NULL; int rc = pcmk_ok; int cib_opts = cib_sync_call; int argerr = 0; int flag; int option_index = 0; int is_remote_node = 0; bool try_attrd = true; int attrd_opts = pcmk__node_attr_none; crm_log_cli_init("crm_attribute"); - crm_set_options(NULL, " -n [options]", long_options, - "Manage node's attributes and cluster options." - "\n\nAllows node attributes and cluster options to be queried, modified and deleted.\n"); + pcmk__set_cli_options(NULL, "-n [options]", + long_options, + "query and update Pacemaker cluster options " + "and node attributes"); if (argc < 2) { - crm_help('?', CRM_EX_USAGE); + pcmk__cli_help('?', CRM_EX_USAGE); } while (1) { - flag = crm_get_option(argc, argv, &option_index); + flag = pcmk__next_cli_option(argc, argv, &option_index, NULL); if (flag == -1) break; switch (flag) { case 'V': crm_bump_log_level(argc, argv); break; case '$': case '?': - crm_help(flag, CRM_EX_OK); + pcmk__cli_help(flag, CRM_EX_OK); break; case 'G': command = flag; attr_value = optarg; break; case 'D': case 'v': command = flag; attr_value = optarg; crm_log_args(argc, argv); break; case 'q': case 'Q': BE_QUIET = TRUE; break; case 'U': case 'N': dest_uname = strdup(optarg); break; case 's': set_name = strdup(optarg); break; case 'l': case 't': type = optarg; break; case 'z': type = XML_CIB_TAG_NODES; set_type = XML_TAG_UTILIZATION; break; case 'n': attr_name = strdup(optarg); break; case 'P': attr_pattern = strdup(optarg); break; case 'i': attr_id = strdup(optarg); break; case 'r': rsc_id = optarg; break; case 'd': attr_default = optarg; break; case '!': crm_warn("Inhibiting notifications for this update"); cib_opts |= cib_inhibit_notify; break; default: printf("Argument code 0%o (%c) is not (?yet?) supported\n", flag, flag); ++argerr; break; } } if (optind < argc) { printf("non-option ARGV-elements: "); while (optind < argc) printf("%s ", argv[optind++]); printf("\n"); } if (optind > argc) { ++argerr; } if (argerr) { - crm_help('?', CRM_EX_USAGE); + pcmk__cli_help('?', CRM_EX_USAGE); } the_cib = cib_new(); rc = the_cib->cmds->signon(the_cib, crm_system_name, cib_command); if (rc != pcmk_ok) { fprintf(stderr, "Error connecting to the CIB manager: %s\n", pcmk_strerror(rc)); crm_exit(crm_errno2exit(rc)); } if (type == NULL && dest_uname != NULL) { type = "forever"; } if (safe_str_eq(type, "reboot")) { type = XML_CIB_TAG_STATUS; } else if (safe_str_eq(type, "forever")) { type = XML_CIB_TAG_NODES; } if (type == NULL && dest_uname == NULL) { /* we're updating cluster options - don't populate dest_node */ type = XML_CIB_TAG_CRMCONFIG; } else if (safe_str_eq(type, XML_CIB_TAG_CRMCONFIG)) { } else if (safe_str_neq(type, XML_CIB_TAG_TICKETS)) { /* If we are being called from a resource agent via the cluster, * the correct local node name will be passed as an environment * variable. Otherwise, we have to ask the cluster. */ dest_uname = pcmk__node_attr_target(dest_uname); if (dest_uname == NULL) { dest_uname = get_local_node_name(); } rc = query_node_uuid(the_cib, dest_uname, &dest_node, &is_remote_node); if (pcmk_ok != rc) { fprintf(stderr, "Could not map name=%s to a UUID\n", dest_uname); the_cib->cmds->signoff(the_cib); cib_delete(the_cib); crm_exit(crm_errno2exit(rc)); } } if ((command == 'D') && (attr_name == NULL) && (attr_pattern == NULL)) { fprintf(stderr, "Error: must specify attribute name or pattern to delete\n"); crm_exit(CRM_EX_USAGE); } if (attr_pattern) { if (((command != 'v') && (command != 'D')) || safe_str_neq(type, XML_CIB_TAG_STATUS)) { fprintf(stderr, "Error: pattern can only be used with till-reboot update or delete\n"); crm_exit(CRM_EX_USAGE); } command = 'u'; free(attr_name); attr_name = attr_pattern; } // Only go through attribute manager for transient attributes try_attrd = safe_str_eq(type, XML_CIB_TAG_STATUS); // Don't try to contact attribute manager if we're using a file as CIB if (getenv("CIB_file") || getenv("CIB_shadow")) { try_attrd = FALSE; } if (is_remote_node) { attrd_opts = pcmk__node_attr_remote; } if (((command == 'v') || (command == 'D') || (command == 'u')) && try_attrd && (pcmk__node_attr_request(NULL, command, dest_uname, attr_name, attr_value, type, set_name, NULL, NULL, attrd_opts) == pcmk_rc_ok)) { crm_info("Update %s=%s sent via pacemaker-attrd", attr_name, ((command == 'D')? "" : attr_value)); } else if (command == 'D') { rc = delete_attr_delegate(the_cib, cib_opts, type, dest_node, set_type, set_name, attr_id, attr_name, attr_value, TRUE, NULL); if (rc == -ENXIO) { /* Nothing to delete... * which means it's not there... * which is what the admin wanted */ rc = pcmk_ok; } } else if (command == 'v') { CRM_LOG_ASSERT(type != NULL); CRM_LOG_ASSERT(attr_name != NULL); CRM_LOG_ASSERT(attr_value != NULL); rc = update_attr_delegate(the_cib, cib_opts, type, dest_node, set_type, set_name, attr_id, attr_name, attr_value, TRUE, NULL, is_remote_node ? "remote" : NULL); } else { /* query */ char *read_value = NULL; rc = read_attr_delegate(the_cib, type, dest_node, set_type, set_name, attr_id, attr_name, &read_value, TRUE, NULL); if (rc == -ENXIO && attr_default) { read_value = strdup(attr_default); rc = pcmk_ok; } crm_info("Read %s=%s %s%s", attr_name, crm_str(read_value), set_name ? "in " : "", set_name ? set_name : ""); if (rc == -ENOTUNIQ) { // Multiple matches (already displayed) are not error for queries rc = pcmk_ok; } else if (BE_QUIET == FALSE) { fprintf(stdout, "%s%s %s%s %s%s value=%s\n", type ? "scope=" : "", type ? type : "", attr_id ? "id=" : "", attr_id ? attr_id : "", attr_name ? "name=" : "", attr_name ? attr_name : "", read_value ? read_value : "(null)"); } else if (read_value != NULL) { fprintf(stdout, "%s\n", read_value); } free(read_value); } if (rc == -ENOTUNIQ) { printf("Please choose from one of the matches above and supply the 'id' with --attr-id\n"); } else if (rc != pcmk_ok) { fprintf(stderr, "Error performing operation: %s\n", pcmk_strerror(rc)); } the_cib->cmds->signoff(the_cib); cib_delete(the_cib); crm_exit(crm_errno2exit(rc)); } diff --git a/tools/crm_diff.c b/tools/crm_diff.c index a565bb851c..f57f3d52b3 100644 --- a/tools/crm_diff.c +++ b/tools/crm_diff.c @@ -1,396 +1,396 @@ /* * Copyright 2005-2018 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 #define SUMMARY "Compare two Pacemaker configurations (in XML format) to produce a custom diff-like output, " \ "or apply such an output as a patch" struct { gboolean apply; gboolean as_cib; gboolean no_version; gboolean raw_1; gboolean raw_2; gboolean use_stdin; char *xml_file_1; char *xml_file_2; } options; gboolean new_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean original_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean patch_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); static GOptionEntry original_xml_entries[] = { { "original", 'o', 0, G_OPTION_ARG_STRING, &options.xml_file_1, "XML is contained in the named file", "FILE" }, { "original-string", 'O', 0, G_OPTION_ARG_CALLBACK, original_string_cb, "XML is contained in the supplied string", "STRING" }, { NULL } }; static GOptionEntry operation_entries[] = { { "new", 'n', 0, G_OPTION_ARG_STRING, &options.xml_file_2, "Compare the original XML to the contents of the named file", "FILE" }, { "new-string", 'N', 0, G_OPTION_ARG_CALLBACK, new_string_cb, "Compare the original XML with the contents of the supplied string", "STRING" }, { "patch", 'p', 0, G_OPTION_ARG_CALLBACK, patch_cb, "Patch the original XML with the contents of the named file", "FILE" }, { NULL } }; static GOptionEntry addl_entries[] = { { "cib", 'c', 0, G_OPTION_ARG_NONE, &options.as_cib, "Compare/patch the inputs as a CIB (includes versions details)", NULL }, { "stdin", 's', 0, G_OPTION_ARG_NONE, &options.use_stdin, "", NULL }, { "no-version", 'u', 0, G_OPTION_ARG_NONE, &options.no_version, "Generate the difference without versions details", NULL }, { NULL } }; gboolean new_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.raw_2 = TRUE; if (options.xml_file_2 != NULL) { free(options.xml_file_2); } options.xml_file_2 = strdup(optarg); return TRUE; } gboolean original_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.raw_1 = TRUE; if (options.xml_file_1 != NULL) { free(options.xml_file_1); } options.xml_file_1 = strdup(optarg); return TRUE; } gboolean patch_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.apply = TRUE; if (options.xml_file_2 != NULL) { free(options.xml_file_2); } options.xml_file_2 = strdup(optarg); return TRUE; } static void print_patch(xmlNode *patch) { char *buffer = dump_xml_formatted(patch); printf("%s\n", crm_str(buffer)); free(buffer); fflush(stdout); } static int apply_patch(xmlNode *input, xmlNode *patch, gboolean as_cib) { int rc; xmlNode *output = copy_xml(input); rc = xml_apply_patchset(output, patch, as_cib); if (rc != pcmk_ok) { fprintf(stderr, "Could not apply patch: %s\n", pcmk_strerror(rc)); free_xml(output); return rc; } if (output != NULL) { const char *version; char *buffer; print_patch(output); version = crm_element_value(output, XML_ATTR_CRM_VERSION); buffer = calculate_xml_versioned_digest(output, FALSE, TRUE, version); crm_trace("Digest: %s\n", crm_str(buffer)); free(buffer); free_xml(output); } return pcmk_ok; } static void log_patch_cib_versions(xmlNode *patch) { int add[] = { 0, 0, 0 }; int del[] = { 0, 0, 0 }; const char *fmt = NULL; const char *digest = NULL; xml_patch_versions(patch, add, del); fmt = crm_element_value(patch, "format"); digest = crm_element_value(patch, XML_ATTR_DIGEST); if (add[2] != del[2] || add[1] != del[1] || add[0] != del[0]) { crm_info("Patch: --- %d.%d.%d %s", del[0], del[1], del[2], fmt); crm_info("Patch: +++ %d.%d.%d %s", add[0], add[1], add[2], digest); } } static void strip_patch_cib_version(xmlNode *patch, const char **vfields, size_t nvfields) { int format = 1; crm_element_value_int(patch, "format", &format); if (format == 2) { xmlNode *version_xml = find_xml_node(patch, "version", FALSE); if (version_xml) { free_xml(version_xml); } } else { int i = 0; const char *tags[] = { XML_TAG_DIFF_REMOVED, XML_TAG_DIFF_ADDED, }; for (i = 0; i < DIMOF(tags); i++) { xmlNode *tmp = NULL; int lpc; tmp = find_xml_node(patch, tags[i], FALSE); if (tmp) { for (lpc = 0; lpc < nvfields; lpc++) { xml_remove_prop(tmp, vfields[lpc]); } tmp = find_xml_node(tmp, XML_TAG_CIB, FALSE); if (tmp) { for (lpc = 0; lpc < nvfields; lpc++) { xml_remove_prop(tmp, vfields[lpc]); } } } } } } static int generate_patch(xmlNode *object_1, xmlNode *object_2, const char *xml_file_2, gboolean as_cib, gboolean no_version) { xmlNode *output = NULL; const char *vfields[] = { XML_ATTR_GENERATION_ADMIN, XML_ATTR_GENERATION, XML_ATTR_NUMUPDATES, }; /* If we're ignoring the version, make the version information * identical, so it isn't detected as a change. */ if (no_version) { int lpc; for (lpc = 0; lpc < DIMOF(vfields); lpc++) { crm_copy_xml_element(object_1, object_2, vfields[lpc]); } } xml_track_changes(object_2, NULL, object_2, FALSE); if(as_cib) { xml_calculate_significant_changes(object_1, object_2); } else { xml_calculate_changes(object_1, object_2); } crm_log_xml_debug(object_2, (xml_file_2? xml_file_2: "target")); output = xml_create_patchset(0, object_1, object_2, NULL, FALSE); xml_log_changes(LOG_INFO, __FUNCTION__, object_2); xml_accept_changes(object_2); if (output == NULL) { return pcmk_ok; } patchset_process_digest(output, object_1, object_2, as_cib); if (as_cib) { log_patch_cib_versions(output); } else if (no_version) { strip_patch_cib_version(output, vfields, DIMOF(vfields)); } xml_log_patchset(LOG_NOTICE, __FUNCTION__, output); print_patch(output); free_xml(output); return -pcmk_err_generic; } static GOptionContext * build_arg_context(pcmk__common_args_t *args) { GOptionContext *context = NULL; const char *description = "*Examples*\n\n" "Obtain the two different configuration files by running cibadmin on the two cluster setups to compare:\n\n" "\tcibadmin --query > cib-old.xml\n" "\tcibadmin --query > cib-new.xml\n\n" "Calculate and save the difference between the two files:\n\n" "\tcrm_diff --original cib-old.xml --new cib-new.xml > patch.xml\n\n" "Apply the patch to the original file:\n\n" "\tcrm_diff --original cib-old.xml --patch patch.xml > updated.xml\n\n" "Apply the patch to the running cluster:\n\n" "\tcibadmin --patch patch.xml\n"; context = pcmk__build_arg_context(args, NULL, NULL); g_option_context_set_description(context, description); pcmk__add_arg_group(context, "xml", "Original XML:", "Show original XML options", original_xml_entries); pcmk__add_arg_group(context, "operation", "Operation:", "Show operation options", operation_entries); pcmk__add_arg_group(context, "additional", "Additional Options:", "Show additional options", addl_entries); return context; } int main(int argc, char **argv) { int rc = pcmk_ok; xmlNode *object_1 = NULL; xmlNode *object_2 = NULL; pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); GError *error = NULL; GOptionContext *context = NULL; gchar **processed_args = NULL; context = build_arg_context(args); crm_log_cli_init("crm_diff"); processed_args = pcmk__cmdline_preproc(argv, "nopNO"); if (!g_option_context_parse_strv(context, &processed_args, &error)) { fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message); rc = CRM_EX_USAGE; goto done; } for (int i = 0; i < args->verbosity; i++) { crm_bump_log_level(argc, argv); } if (args->version) { /* FIXME: When crm_diff is converted to use formatted output, this can go. */ - crm_help('v', CRM_EX_USAGE); + pcmk__cli_help('v', CRM_EX_USAGE); } if (optind > argc) { fprintf(stderr, "%s", g_option_context_get_help(context, TRUE, NULL)); rc = CRM_EX_USAGE; goto done; } if (options.apply && options.no_version) { fprintf(stderr, "warning: -u/--no-version ignored with -p/--patch\n"); } else if (options.as_cib && options.no_version) { fprintf(stderr, "error: -u/--no-version incompatible with -c/--cib\n"); rc = CRM_EX_USAGE; goto done; } if (options.raw_1) { object_1 = string2xml(options.xml_file_1); } else if (options.use_stdin) { fprintf(stderr, "Input first XML fragment:"); object_1 = stdin2xml(); } else if (options.xml_file_1 != NULL) { object_1 = filename2xml(options.xml_file_1); } if (options.raw_2) { object_2 = string2xml(options.xml_file_2); } else if (options.use_stdin) { fprintf(stderr, "Input second XML fragment:"); object_2 = stdin2xml(); } else if (options.xml_file_2 != NULL) { object_2 = filename2xml(options.xml_file_2); } if (object_1 == NULL) { fprintf(stderr, "Could not parse the first XML fragment\n"); rc = CRM_EX_DATAERR; goto done; } if (object_2 == NULL) { fprintf(stderr, "Could not parse the second XML fragment\n"); rc = CRM_EX_DATAERR; goto done; } if (options.apply) { rc = apply_patch(object_1, object_2, options.as_cib); } else { rc = generate_patch(object_1, object_2, options.xml_file_2, options.as_cib, options.no_version); } done: g_strfreev(processed_args); g_clear_error(&error); pcmk__free_arg_context(context); free(options.xml_file_1); free(options.xml_file_2); free_xml(object_1); free_xml(object_2); return crm_errno2exit(rc); } diff --git a/tools/crm_error.c b/tools/crm_error.c index 0dcae056dc..9407a4590d 100644 --- a/tools/crm_error.c +++ b/tools/crm_error.c @@ -1,139 +1,159 @@ /* * Copyright 2012-2020 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 -/* *INDENT-OFF* */ -static struct crm_option long_options[] = { - /* Top-level Options */ - {"help", 0, 0, '?', "\tThis text"}, - {"version", 0, 0, '$', "\tVersion information" }, - {"verbose", 0, 0, 'V', "\tIncrease debug output"}, - - {"name", 0, 0, 'n', "\tShow the error's name with its description." - "\n\t\t\tUseful for looking for sources of the error in source code"}, - - {"list", 0, 0, 'l', "\tShow all known errors."}, - {"exit", 0, 0, 'X', "\tInterpret as exit code rather than legacy function return value"}, - {"rc", 0, 0, 'r', "\tInterpret as return code rather than legacy function return value"}, - - {0, 0, 0, 0} +static pcmk__cli_option_t long_options[] = { + // long option, argument type, storage, short option, description, flags + { + "help", no_argument, NULL, '?', + "\tThis text", pcmk__option_default + }, + { + "version", no_argument, NULL, '$', + "\tVersion information", pcmk__option_default + }, + { + "verbose", no_argument, NULL, 'V', + "\tIncrease debug output", pcmk__option_default + }, + { + "name", no_argument, NULL, 'n', + "\tShow error's name with its description (useful for looking for " + "sources of the error in source code)", + pcmk__option_default + }, + { + "list", no_argument, NULL, 'l', + "\tShow all known errors", pcmk__option_default + }, + { + "exit", no_argument, NULL, 'X', + "\tInterpret as exit code rather than legacy function return value", + pcmk__option_default + }, + { + "rc", no_argument, NULL, 'r', + "\tInterpret as return code rather than legacy function return value", + pcmk__option_default + }, + { 0, 0, 0, 0 } }; -/* *INDENT-ON* */ static bool as_exit_code = false; static bool as_rc = false; static void get_strings(int rc, const char **name, const char **str) { if (as_exit_code) { *str = crm_exit_str((crm_exit_t) rc); *name = crm_exit_name(rc); } else if (as_rc) { *str = pcmk_rc_str(rc); *name = pcmk_rc_name(rc); } else { *str = pcmk_strerror(rc); *name = pcmk_errorname(rc); } } int main(int argc, char **argv) { int rc = 0; int lpc = 0; int flag = 0; int option_index = 0; bool do_list = FALSE; bool with_name = FALSE; const char *name = NULL; const char *desc = NULL; crm_log_cli_init("crm_error"); - crm_set_options(NULL, "[options] -- [...]", long_options, - "Tool for displaying the textual name or description of a reported error code"); + pcmk__set_cli_options(NULL, "[options] -- [...]", long_options, + "display name or description of a Pacemaker " + "error code"); while (flag >= 0) { - flag = crm_get_option(argc, argv, &option_index); + flag = pcmk__next_cli_option(argc, argv, &option_index, NULL); switch (flag) { case -1: break; case 'V': crm_bump_log_level(argc, argv); break; case '$': case '?': - crm_help(flag, CRM_EX_OK); + pcmk__cli_help(flag, CRM_EX_OK); break; case 'n': with_name = TRUE; break; case 'l': do_list = TRUE; break; case 'r': as_rc = true; break; case 'X': as_exit_code = TRUE; break; default: - crm_help(flag, CRM_EX_OK); + pcmk__cli_help(flag, CRM_EX_OK); break; } } if(do_list) { int start, end, width; // 256 is a hacky magic number that "should" be enough if (as_rc) { start = pcmk_rc_error - 256; end = PCMK_CUSTOM_OFFSET; width = 4; } else { start = 0; end = 256; width = 3; } for (rc = start; rc < end; rc++) { if (rc == (pcmk_rc_error + 1)) { // Values in between are reserved for callers, no use iterating rc = pcmk_rc_ok; } get_strings(rc, &name, &desc); if (!name || !strcmp(name, "Unknown") || !strcmp(name, "CRM_EX_UNKNOWN")) { // Undefined } else if(with_name) { printf("% .*d: %-26s %s\n", width, rc, name, desc); } else { printf("% .*d: %s\n", width, rc, desc); } } } else { for (lpc = optind; lpc < argc; lpc++) { rc = crm_atoi(argv[lpc], NULL); get_strings(rc, &name, &desc); if (with_name) { printf("%s - %s\n", name, desc); } else { printf("%s\n", desc); } } } return CRM_EX_OK; } diff --git a/tools/crm_node.c b/tools/crm_node.c index 45909170fd..2bc0b4cab9 100644 --- a/tools/crm_node.c +++ b/tools/crm_node.c @@ -1,665 +1,665 @@ /* * Copyright 2004-2019 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 "crm_node - Tool for displaying low-level node information" struct { gboolean corosync; gboolean dangerous_cmd; gboolean force_flag; char command; int nodeid; char *target_uname; } options = { .command = '\0', .force_flag = FALSE }; gboolean command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean name_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean remove_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); static char *pid_s = NULL; static GMainLoop *mainloop = NULL; static crm_exit_t exit_code = CRM_EX_OK; #define INDENT " " static GOptionEntry command_entries[] = { { "cluster-id", 'i', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display this node's cluster id", NULL }, { "list", 'l', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display all known members (past and present) of this cluster", NULL }, { "name", 'n', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display the name used by the cluster for this node", NULL }, { "partition", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display the members of this partition", NULL }, { "quorum", 'q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display a 1 if our partition has quorum, 0 if not", NULL }, { "name-for-id", 'N', 0, G_OPTION_ARG_CALLBACK, name_cb, "Display the name used by the cluster for the node with the specified ID", "ID" }, { "remove", 'R', 0, G_OPTION_ARG_CALLBACK, remove_cb, "(Advanced) Remove the (stopped) node with the specified name from Pacemaker's\n" INDENT "configuration and caches (the node must already have been removed from\n" INDENT "the underlying cluster stack configuration", "NAME" }, { NULL } }; static GOptionEntry addl_entries[] = { { "force", 'f', 0, G_OPTION_ARG_NONE, &options.force_flag, NULL, NULL }, #if SUPPORT_COROSYNC /* Unused and deprecated */ { "corosync", 'C', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.corosync, NULL, NULL }, #endif // @TODO add timeout option for when IPC replies are needed { NULL } }; gboolean command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (safe_str_eq("-i", option_name) || safe_str_eq("--cluster-id", option_name)) { options.command = 'i'; } else if (safe_str_eq("-l", option_name) || safe_str_eq("--list", option_name)) { options.command = 'l'; } else if (safe_str_eq("-n", option_name) || safe_str_eq("--name", option_name)) { options.command = 'n'; } else if (safe_str_eq("-p", option_name) || safe_str_eq("--partition", option_name)) { options.command = 'p'; } else if (safe_str_eq("-q", option_name) || safe_str_eq("--quorum", option_name)) { options.command = 'q'; } else { g_set_error(error, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, "Unknown param passed to command_cb: %s\n", option_name); return FALSE; } return TRUE; } gboolean name_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.command = 'N'; options.nodeid = crm_parse_int(optarg, NULL); return TRUE; } gboolean remove_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (optarg == NULL) { crm_err("-R option requires an argument"); g_set_error(error, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, "-R option requires an argument"); return FALSE; } options.command = 'R'; options.dangerous_cmd = TRUE; options.target_uname = strdup(optarg); return TRUE; } /*! * \internal * \brief Exit crm_node * Clean up memory, and either quit mainloop (if running) or exit * * \param[in] value Exit status */ static void crm_node_exit(crm_exit_t value) { if (pid_s) { free(pid_s); pid_s = NULL; } exit_code = value; if (mainloop && g_main_loop_is_running(mainloop)) { g_main_loop_quit(mainloop); } else { crm_exit(exit_code); } } static void exit_disconnect(gpointer user_data) { fprintf(stderr, "error: Lost connection to cluster\n"); crm_node_exit(CRM_EX_DISCONNECT); } typedef int (*ipc_dispatch_fn) (const char *buffer, ssize_t length, gpointer userdata); static crm_ipc_t * new_mainloop_for_ipc(const char *system, ipc_dispatch_fn dispatch) { mainloop_io_t *source = NULL; crm_ipc_t *ipc = NULL; struct ipc_client_callbacks ipc_callbacks = { .dispatch = dispatch, .destroy = exit_disconnect }; mainloop = g_main_loop_new(NULL, FALSE); source = mainloop_add_ipc_client(system, G_PRIORITY_DEFAULT, 0, NULL, &ipc_callbacks); ipc = mainloop_get_ipc_client(source); if (ipc == NULL) { fprintf(stderr, "error: Could not connect to cluster (is it running?)\n"); crm_node_exit(CRM_EX_DISCONNECT); } return ipc; } static void run_mainloop_and_exit() { g_main_loop_run(mainloop); g_main_loop_unref(mainloop); mainloop = NULL; crm_node_exit(exit_code); } static int send_controller_hello(crm_ipc_t *controller) { xmlNode *hello = NULL; int rc; pid_s = crm_getpid_s(); hello = create_hello_message(pid_s, crm_system_name, "1", "0"); rc = crm_ipc_send(controller, hello, 0, 0, NULL); free_xml(hello); return (rc < 0)? rc : 0; } static int send_node_info_request(crm_ipc_t *controller, uint32_t nodeid) { xmlNode *ping = NULL; int rc; ping = create_request(CRM_OP_NODE_INFO, NULL, NULL, CRM_SYSTEM_CRMD, crm_system_name, pid_s); if (nodeid > 0) { crm_xml_add_int(ping, XML_ATTR_ID, nodeid); } rc = crm_ipc_send(controller, ping, 0, 0, NULL); free_xml(ping); return (rc < 0)? rc : 0; } static int dispatch_controller(const char *buffer, ssize_t length, gpointer userdata) { xmlNode *message = string2xml(buffer); xmlNode *data = NULL; const char *value = NULL; if (message == NULL) { fprintf(stderr, "error: Could not understand reply from controller\n"); crm_node_exit(CRM_EX_PROTOCOL); return 0; } crm_log_xml_trace(message, "controller reply"); exit_code = CRM_EX_PROTOCOL; // Validate reply value = crm_element_value(message, F_CRM_MSG_TYPE); if (safe_str_neq(value, XML_ATTR_RESPONSE)) { fprintf(stderr, "error: Message from controller was not a reply\n"); goto done; } value = crm_element_value(message, XML_ATTR_REFERENCE); if (value == NULL) { fprintf(stderr, "error: Controller reply did not specify original message\n"); goto done; } data = get_message_xml(message, F_CRM_DATA); if (data == NULL) { fprintf(stderr, "error: Controller reply did not contain any data\n"); goto done; } switch (options.command) { case 'i': value = crm_element_value(data, XML_ATTR_ID); if (value == NULL) { fprintf(stderr, "error: Controller reply did not contain node ID\n"); } else { printf("%s\n", value); exit_code = CRM_EX_OK; } break; case 'n': case 'N': value = crm_element_value(data, XML_ATTR_UNAME); if (value == NULL) { fprintf(stderr, "Node is not known to cluster\n"); exit_code = CRM_EX_NOHOST; } else { printf("%s\n", value); exit_code = CRM_EX_OK; } break; case 'q': value = crm_element_value(data, XML_ATTR_HAVE_QUORUM); if (value == NULL) { fprintf(stderr, "error: Controller reply did not contain quorum status\n"); } else { bool quorum = crm_is_true(value); printf("%d\n", quorum); exit_code = quorum? CRM_EX_OK : CRM_EX_QUORUM; } break; default: fprintf(stderr, "internal error: Controller reply not expected\n"); exit_code = CRM_EX_SOFTWARE; break; } done: free_xml(message); crm_node_exit(exit_code); return 0; } static void run_controller_mainloop(uint32_t nodeid) { crm_ipc_t *controller = NULL; int rc; controller = new_mainloop_for_ipc(CRM_SYSTEM_CRMD, dispatch_controller); rc = send_controller_hello(controller); if (rc < 0) { fprintf(stderr, "error: Could not register with controller: %s\n", pcmk_strerror(rc)); crm_node_exit(crm_errno2exit(rc)); } rc = send_node_info_request(controller, nodeid); if (rc < 0) { fprintf(stderr, "error: Could not ping controller: %s\n", pcmk_strerror(rc)); crm_node_exit(crm_errno2exit(rc)); } // Run main loop to get controller reply via dispatch_controller() run_mainloop_and_exit(); } static void print_node_name() { // Check environment first (i.e. when called by resource agent) const char *name = getenv("OCF_RESKEY_" CRM_META "_" XML_LRM_ATTR_TARGET); if (name != NULL) { printf("%s\n", name); crm_node_exit(CRM_EX_OK); } else { // Otherwise ask the controller run_controller_mainloop(0); } } static int cib_remove_node(long id, const char *name) { int rc; cib_t *cib = NULL; xmlNode *node = NULL; xmlNode *node_state = NULL; crm_trace("Removing %s from the CIB", name); if(name == NULL && id == 0) { return -ENOTUNIQ; } node = create_xml_node(NULL, XML_CIB_TAG_NODE); node_state = create_xml_node(NULL, XML_CIB_TAG_STATE); crm_xml_add(node, XML_ATTR_UNAME, name); crm_xml_add(node_state, XML_ATTR_UNAME, name); if (id > 0) { crm_xml_set_id(node, "%ld", id); crm_xml_add(node_state, XML_ATTR_ID, ID(node)); } cib = cib_new(); cib->cmds->signon(cib, crm_system_name, cib_command); rc = cib->cmds->remove(cib, XML_CIB_TAG_NODES, node, cib_sync_call); if (rc != pcmk_ok) { printf("Could not remove %s[%ld] from " XML_CIB_TAG_NODES ": %s", name, id, pcmk_strerror(rc)); } rc = cib->cmds->remove(cib, XML_CIB_TAG_STATUS, node_state, cib_sync_call); if (rc != pcmk_ok) { printf("Could not remove %s[%ld] from " XML_CIB_TAG_STATUS ": %s", name, id, pcmk_strerror(rc)); } cib->cmds->signoff(cib); cib_delete(cib); return rc; } static int tools_remove_node_cache(const char *node_name, long nodeid, const char *target) { int rc = -1; crm_ipc_t *conn = crm_ipc_new(target, 0); xmlNode *cmd = NULL; if (!conn) { return -ENOTCONN; } if (!crm_ipc_connect(conn)) { crm_perror(LOG_ERR, "Connection to %s failed", target); crm_ipc_destroy(conn); return -ENOTCONN; } if(safe_str_eq(target, CRM_SYSTEM_CRMD)) { // The controller requires a hello message before sending a request rc = send_controller_hello(conn); if (rc < 0) { fprintf(stderr, "error: Could not register with controller: %s\n", pcmk_strerror(rc)); return rc; } } crm_trace("Removing %s[%ld] from the %s membership cache", node_name, nodeid, target); if(safe_str_eq(target, T_ATTRD)) { cmd = create_xml_node(NULL, __FUNCTION__); crm_xml_add(cmd, F_TYPE, T_ATTRD); crm_xml_add(cmd, F_ORIG, crm_system_name); crm_xml_add(cmd, F_ATTRD_TASK, ATTRD_OP_PEER_REMOVE); crm_xml_add(cmd, F_ATTRD_HOST, node_name); if (nodeid > 0) { crm_xml_add_int(cmd, F_ATTRD_HOST_ID, (int) nodeid); } } else { cmd = create_request(CRM_OP_RM_NODE_CACHE, NULL, NULL, target, crm_system_name, pid_s); if (nodeid > 0) { crm_xml_set_id(cmd, "%ld", nodeid); } crm_xml_add(cmd, XML_ATTR_UNAME, node_name); } rc = crm_ipc_send(conn, cmd, 0, 0, NULL); crm_debug("%s peer cache cleanup for %s (%ld): %d", target, node_name, nodeid, rc); if (rc > 0) { rc = cib_remove_node(nodeid, node_name); } if (conn) { crm_ipc_close(conn); crm_ipc_destroy(conn); } free_xml(cmd); return rc > 0 ? 0 : rc; } static void remove_node(const char *target_uname) { int d = 0; long nodeid = 0; const char *node_name = NULL; char *endptr = NULL; const char *daemons[] = { CRM_SYSTEM_CRMD, "stonith-ng", T_ATTRD, CRM_SYSTEM_MCP, }; // Check whether node was specified by name or numeric ID errno = 0; nodeid = strtol(target_uname, &endptr, 10); if ((errno != 0) || (endptr == target_uname) || (*endptr != '\0') || (nodeid <= 0)) { // It's not a positive integer, so assume it's a node name nodeid = 0; node_name = target_uname; } for (d = 0; d < DIMOF(daemons); d++) { if (tools_remove_node_cache(node_name, nodeid, daemons[d])) { crm_err("Failed to connect to %s to remove node '%s'", daemons[d], target_uname); crm_node_exit(CRM_EX_ERROR); return; } } crm_node_exit(CRM_EX_OK); } static gint compare_node_xml(gconstpointer a, gconstpointer b) { const char *a_name = crm_element_value((xmlNode*) a, "uname"); const char *b_name = crm_element_value((xmlNode*) b, "uname"); return strcmp((a_name? a_name : ""), (b_name? b_name : "")); } static int node_mcp_dispatch(const char *buffer, ssize_t length, gpointer userdata) { GList *nodes = NULL; xmlNode *node = NULL; xmlNode *msg = string2xml(buffer); const char *uname; const char *state; if (msg == NULL) { fprintf(stderr, "error: Could not understand pacemakerd response\n"); crm_node_exit(CRM_EX_PROTOCOL); return 0; } crm_log_xml_trace(msg, "message"); for (node = __xml_first_child(msg); node != NULL; node = __xml_next(node)) { nodes = g_list_insert_sorted(nodes, node, compare_node_xml); } for (GList *iter = nodes; iter; iter = iter->next) { node = (xmlNode*) iter->data; uname = crm_element_value(node, "uname"); state = crm_element_value(node, "state"); if (options.command == 'l') { int id = 0; crm_element_value_int(node, "id", &id); printf("%d %s %s\n", id, (uname? uname : ""), (state? state : "")); // This is CRM_NODE_MEMBER but we don't want to include cluster header } else if ((options.command == 'p') && safe_str_eq(state, "member")) { printf("%s ", (uname? uname : "")); } } if (options.command == 'p') { fprintf(stdout, "\n"); } free_xml(msg); crm_node_exit(CRM_EX_OK); return 0; } static void run_pacemakerd_mainloop() { crm_ipc_t *ipc = NULL; xmlNode *poke = NULL; ipc = new_mainloop_for_ipc(CRM_SYSTEM_MCP, node_mcp_dispatch); // Sending anything will get us a list of nodes poke = create_xml_node(NULL, "poke"); crm_ipc_send(ipc, poke, 0, 0, NULL); free_xml(poke); // Handle reply via node_mcp_dispatch() run_mainloop_and_exit(); } 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), "Be less descriptive in output.", NULL }, { NULL } }; context = pcmk__build_arg_context(args, NULL, &group); /* Add the -q option, which cannot be part of the globally supported options * because some tools use that flag for something else. */ pcmk__add_main_args(context, extra_prog_entries); pcmk__add_arg_group(context, "commands", "Commands:", "Show command help", command_entries); pcmk__add_arg_group(context, "additional", "Additional Options:", "Show additional options", addl_entries); return context; } int main(int argc, char **argv) { pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); GError *error = NULL; GOptionContext *context = NULL; GOptionGroup *output_group = NULL; gchar **processed_args = NULL; context = build_arg_context(args, output_group); crm_log_cli_init("crm_node"); processed_args = pcmk__cmdline_preproc(argv, "NR"); if (!g_option_context_parse_strv(context, &processed_args, &error)) { fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message); exit_code = CRM_EX_USAGE; goto done; } for (int i = 0; i < args->verbosity; i++) { crm_bump_log_level(argc, argv); } if (args->version) { /* FIXME: When crm_node is converted to use formatted output, this can go. */ - crm_help('v', CRM_EX_USAGE); + pcmk__cli_help('v', CRM_EX_USAGE); } if (optind > argc || options.command == 0) { fprintf(stderr, "%s", g_option_context_get_help(context, TRUE, NULL)); exit_code = CRM_EX_USAGE; goto done; } if (options.dangerous_cmd && options.force_flag == FALSE) { fprintf(stderr, "The supplied command is considered dangerous." " To prevent accidental destruction of the cluster," " the --force flag is required in order to proceed.\n"); exit_code = CRM_EX_USAGE; goto done; } switch (options.command) { case 'n': print_node_name(); break; case 'R': remove_node(options.target_uname); break; case 'i': case 'q': case 'N': run_controller_mainloop(options.nodeid); break; case 'l': case 'p': run_pacemakerd_mainloop(); break; default: break; } done: g_strfreev(processed_args); g_clear_error(&error); pcmk__free_arg_context(context); crm_node_exit(exit_code); return exit_code; } diff --git a/tools/crm_resource.c b/tools/crm_resource.c index 0339faa416..f189cde6b9 100644 --- a/tools/crm_resource.c +++ b/tools/crm_resource.c @@ -1,1403 +1,1547 @@ /* * Copyright 2004-2020 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 bool BE_QUIET = FALSE; bool scope_master = FALSE; int cib_options = cib_sync_call; static GMainLoop *mainloop = NULL; // Things that should be cleaned up on exit static cib_t *cib_conn = NULL; static pcmk_controld_api_t *controld_api = NULL; static pe_working_set_t *data_set = NULL; #define MESSAGE_TIMEOUT_S 60 // Clean up and exit _Noreturn static void bye(crm_exit_t exit_code) { if (cib_conn != NULL) { cib_conn->cmds->signoff(cib_conn); cib_delete(cib_conn); } if (controld_api != NULL) { pcmk_free_controld_api(controld_api); } pe_free_working_set(data_set); crm_exit(exit_code); } static gboolean resource_ipc_timeout(gpointer data) { fprintf(stderr, "Aborting because no messages received in %d seconds\n", MESSAGE_TIMEOUT_S); crm_err("No messages received in %d seconds", MESSAGE_TIMEOUT_S); bye(CRM_EX_TIMEOUT); } static void handle_controller_reply(pcmk_controld_api_t *capi, void *api_data, void *user_data) { fprintf(stderr, "."); if ((capi->replies_expected(capi) == 0) && mainloop && g_main_loop_is_running(mainloop)) { fprintf(stderr, " OK\n"); crm_debug("Got all the replies we expected"); bye(CRM_EX_OK); } } static void handle_controller_drop(pcmk_controld_api_t *capi, void *api_data, void *user_data) { crm_info("Connection to controller was terminated"); bye(CRM_EX_DISCONNECT); } static void start_mainloop(pcmk_controld_api_t *capi) { if (capi->replies_expected(capi) > 0) { unsigned int count = capi->replies_expected(capi); fprintf(stderr, "Waiting for %d %s from the controller", count, pcmk__plural_alt(count, "reply", "replies")); mainloop = g_main_loop_new(NULL, FALSE); g_timeout_add(MESSAGE_TIMEOUT_S * 1000, resource_ipc_timeout, NULL); g_main_loop_run(mainloop); } } static int compare_id(gconstpointer a, gconstpointer b) { return strcmp((const char *)a, (const char *)b); } static GListPtr build_constraint_list(xmlNode *root) { GListPtr retval = NULL; xmlNode *cib_constraints = NULL; xmlXPathObjectPtr xpathObj = NULL; int ndx = 0; cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, root); xpathObj = xpath_search(cib_constraints, "//" XML_CONS_TAG_RSC_LOCATION); for (ndx = 0; ndx < numXpathResults(xpathObj); ndx++) { xmlNode *match = getXpathResult(xpathObj, ndx); retval = g_list_insert_sorted(retval, (gpointer) ID(match), compare_id); } freeXpathObject(xpathObj); return retval; } /* short option letters still available: eEJkKXyYZ */ -/* *INDENT-OFF* */ -static struct crm_option long_options[] = { - /* Top-level Options */ +static pcmk__cli_option_t long_options[] = { + // long option, argument type, storage, short option, description, flags { "help", no_argument, NULL, '?', - "\t\tDisplay this text and exit" + "\t\tDisplay this text and exit", pcmk__option_default }, { "version", no_argument, NULL, '$', - "\t\tDisplay version information and exit" + "\t\tDisplay version information and exit", pcmk__option_default }, { "verbose", no_argument, NULL, 'V', - "\t\tIncrease debug output (may be specified multiple times)" + "\t\tIncrease debug output (may be specified multiple times)", + pcmk__option_default }, { "quiet", no_argument, NULL, 'Q', - "\t\tBe less descriptive in results" + "\t\tBe less descriptive in results", pcmk__option_default }, { "resource", required_argument, NULL, 'r', - "\tResource ID" + "\tResource ID", pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nQueries:", pcmk__option_default }, - - { "-spacer-", no_argument, NULL, '-', "\nQueries:" }, { "list", no_argument, NULL, 'L', - "\t\tList all cluster resources with status"}, + "\t\tList all cluster resources with status", pcmk__option_default + }, { "list-raw", no_argument, NULL, 'l', - "\t\tList IDs of all instantiated resources (individual members rather than groups etc.)" + "\t\tList IDs of all instantiated resources (individual members rather " + "than groups etc.)", + pcmk__option_default }, { "list-cts", no_argument, NULL, 'c', - NULL, pcmk_option_hidden + NULL, pcmk__option_hidden }, { "list-operations", no_argument, NULL, 'O', - "\tList active resource operations, optionally filtered by --resource and/or --node" + "\tList active resource operations, optionally filtered by --resource " + "and/or --node", + pcmk__option_default }, { "list-all-operations", no_argument, NULL, 'o', - "List all resource operations, optionally filtered by --resource and/or --node" + "List all resource operations, optionally filtered by --resource " + "and/or --node", + pcmk__option_default }, { "list-standards", no_argument, NULL, 0, - "\tList supported standards" + "\tList supported standards", pcmk__option_default }, { "list-ocf-providers", no_argument, NULL, 0, - "List all available OCF providers" + "List all available OCF providers", pcmk__option_default }, { "list-agents", required_argument, NULL, 0, - "List all agents available for the named standard and/or provider." + "List all agents available for the named standard and/or provider", + pcmk__option_default }, { "list-ocf-alternatives", required_argument, NULL, 0, - "List all available providers for the named OCF agent" + "List all available providers for the named OCF agent", + pcmk__option_default }, { "show-metadata", required_argument, NULL, 0, - "Show the metadata for the named class:provider:agent" + "Show the metadata for the named class:provider:agent", + pcmk__option_default }, { "query-xml", no_argument, NULL, 'q', - "\tShow XML configuration of resource (after any template expansion)" + "\tShow XML configuration of resource (after any template expansion)", + pcmk__option_default }, { "query-xml-raw", no_argument, NULL, 'w', - "\tShow XML configuration of resource (before any template expansion)" + "\tShow XML configuration of resource (before any template expansion)", + pcmk__option_default }, { "get-parameter", required_argument, NULL, 'g', - "Display named parameter for resource.\n" - "\t\t\t\tUse instance attribute unless --meta or --utilization is specified" + "Display named parameter for resource (use instance attribute unless " + "--meta or --utilization is specified)", + pcmk__option_default }, { "get-property", required_argument, NULL, 'G', - "Display named property of resource ('class', 'type', or 'provider') (requires --resource)", - pcmk_option_hidden + "Display named property of resource ('class', 'type', or 'provider') " + "(requires --resource)", + pcmk__option_hidden }, { "locate", no_argument, NULL, 'W', - "\t\tShow node(s) currently running resource" + "\t\tShow node(s) currently running resource", + pcmk__option_default }, { "stack", no_argument, NULL, 'A', - "\t\tDisplay the prerequisites and dependents of a resource" + "\t\tDisplay the prerequisites and dependents of a resource", + pcmk__option_default }, { "constraints", no_argument, NULL, 'a', - "\tDisplay the (co)location constraints that apply to a resource" + "\tDisplay the (co)location constraints that apply to a resource", + pcmk__option_default }, { "why", no_argument, NULL, 'Y', - "\t\tShow why resources are not running, optionally filtered by --resource and/or --node" + "\t\tShow why resources are not running, optionally filtered by " + "--resource and/or --node", + pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nCommands:", pcmk__option_default }, - - { "-spacer-", no_argument, NULL, '-', "\nCommands:" }, { "validate", no_argument, NULL, 0, - "\t\tValidate resource configuration by calling agent's validate-all action.\n" - "\t\t\t\tThe configuration may be specified either by giving an existing\n" - "\t\t\t\tresource name with -r, or by specifying --class, --agent, and\n" - "\t\t\t\t--provider arguments, along with any number of --option arguments." + "\t\tValidate resource configuration by calling agent's validate-all " + "action. The configuration may be specified either by giving an " + "existing resource name with -r, or by specifying --class, " + "--agent, and --provider arguments, along with any number of " + "--option arguments.", + pcmk__option_default }, { "cleanup", no_argument, NULL, 'C', - "\t\tIf resource has any past failures, clear its history and fail count.\n" - "\t\t\t\tOptionally filtered by --resource, --node, --operation, and --interval (otherwise all).\n" - "\t\t\t\t--operation and --interval apply to fail counts, but entire history is always cleared,\n" - "\t\t\t\tto allow current state to be rechecked. If the named resource is part of a group, or\n" - "\t\t\t\tone numbered instance of a clone or bundled resource, the clean-up applies to the\n" - "\t\t\t\twhole collective resource unless --force is given." + "\t\tIf resource has any past failures, clear its history and " + "fail count. Optionally filtered by --resource, --node, " + "--operation, and --interval (otherwise all). --operation and " + "--interval apply to fail counts, but entire history is always " + "cleared, to allow current state to be rechecked. If the named " + "resource is part of a group, or one numbered instance of a clone " + "or bundled resource, the clean-up applies to the whole collective " + "resource unless --force is given.", + pcmk__option_default }, { "refresh", no_argument, NULL, 'R', - "\t\tDelete resource's history (including failures) so its current state is rechecked.\n" - "\t\t\t\tOptionally filtered by --resource and --node (otherwise all). If the named resource is\n" - "\t\t\t\tpart of a group, or one numbered instance of a clone or bundled resource, the clean-up\n" - "applies to the whole collective resource unless --force is given." + "\t\tDelete resource's history (including failures) so its current " + "state is rechecked. Optionally filtered by --resource and --node " + "(otherwise all). If the named resource is part of a group, or one " + "numbered instance of a clone or bundled resource, the clean-up " + "applies to the whole collective resource unless --force is given.", + pcmk__option_default }, { "set-parameter", required_argument, NULL, 'p', - "Set named parameter for resource (requires -v).\n" - "\t\t\t\tUse instance attribute unless --meta or --utilization is specified." + "Set named parameter for resource (requires -v). Use instance " + "attribute unless --meta or --utilization is specified.", + pcmk__option_default }, { "delete-parameter", required_argument, NULL, 'd', - "Delete named parameter for resource.\n" - "\t\t\t\tUse instance attribute unless --meta or --utilization is specified." + "Delete named parameter for resource. Use instance attribute unless " + "--meta or --utilization is specified.", + pcmk__option_default }, { "set-property", required_argument, NULL, 'S', - "Set named property of resource ('class', 'type', or 'provider') (requires -r, -t, -v)", - pcmk_option_hidden + "Set named property of resource ('class', 'type', or 'provider') " + "(requires -r, -t, -v)", + pcmk__option_hidden + }, + { + "-spacer-", no_argument, NULL, '-', + "\nResource location:", pcmk__option_default }, - - { "-spacer-", no_argument, NULL, '-', "\nResource location:" }, { "move", no_argument, NULL, 'M', - "\t\tCreate a constraint to move resource. If --node is specified, the constraint\n" - "\t\t\t\twill be to move to that node, otherwise it will be to ban the current node.\n" - "\t\t\t\tUnless --force is specified, this will return an error if the resource is\n" - "\t\t\t\talready running on the specified node. If --force is specified, this will\n" - "\t\t\t\talways ban the current node. Optional: --lifetime, --master.\n" - "\t\t\t\tNOTE: This may prevent the resource from running on its previous location\n" - "\t\t\t\tuntil the implicit constraint expires or is removed with --clear." + "\t\tCreate a constraint to move resource. If --node is specified, the " + "constraint will be to move to that node, otherwise it will be to " + "ban the current node. Unless --force is specified, this will " + "return an error if the resource is already running on the " + "specified node. If --force is specified, this will always ban the " + "current node. Optional: --lifetime, --master. NOTE: This may " + "prevent the resource from running on its previous location until " + "the implicit constraint expires or is removed with --clear.", + pcmk__option_default }, { "ban", no_argument, NULL, 'B', - "\t\tCreate a constraint to keep resource off a node. Optional: --node, --lifetime, --master.\n" - "\t\t\t\tNOTE: This will prevent the resource from running on the affected node\n" - "\t\t\t\tuntil the implicit constraint expires or is removed with --clear.\n" - "\t\t\t\tIf --node is not specified, it defaults to the node currently running the resource\n" - "\t\t\t\tfor primitives and groups, or the master for promotable clones with promoted-max=1\n" - "\t\t\t\t(all other situations result in an error as there is no sane default).\n" + "\t\tCreate a constraint to keep resource off a node. Optional: " + "--node, --lifetime, --master. NOTE: This will prevent the " + "resource from running on the affected node until the implicit " + "constraint expires or is removed with --clear. If --node is not " + "specified, it defaults to the node currently running the resource " + "for primitives and groups, or the master for promotable clones " + "with promoted-max=1 (all other situations result in an error as " + "there is no sane default).", + pcmk__option_default }, { "clear", no_argument, NULL, 'U', - "\t\tRemove all constraints created by the --ban and/or --move commands.\n" - "\t\t\t\tRequires: --resource. Optional: --node, --master, --expired.\n" - "\t\t\t\tIf --node is not specified, all constraints created by --ban and --move\n" - "\t\t\t\twill be removed for the named resource. If --node and --force are specified,\n" - "\t\t\t\tany constraint created by --move will be cleared, even if it is not for the specified node.\n" - "\t\t\t\tIf --expired is specified, only those constraints whose lifetimes have expired will\n" - "\t\t\t\tbe removed.\n" + "\t\tRemove all constraints created by the --ban and/or --move " + "commands. Requires: --resource. Optional: --node, --master, " + "--expired. If --node is not specified, all constraints created " + "by --ban and --move will be removed for the named resource. If " + "--node and --force are specified, any constraint created by " + "--move will be cleared, even if it is not for the specified node. " + "If --expired is specified, only those constraints whose lifetimes " + "have expired will be removed.", + pcmk__option_default }, { "expired", no_argument, NULL, 'e', - "\t\tModifies the --clear argument to remove constraints with expired lifetimes.\n" + "\t\tModifies the --clear argument to remove constraints with " + "expired lifetimes.", + pcmk__option_default }, { "lifetime", required_argument, NULL, 'u', - "\tLifespan (as ISO 8601 duration) of created constraints (with -B, -M)\n" - "\t\t\t\t(see https://en.wikipedia.org/wiki/ISO_8601#Durations)" + "\tLifespan (as ISO 8601 duration) of created constraints (with -B, " + "-M) (see https://en.wikipedia.org/wiki/ISO_8601#Durations)", + pcmk__option_default }, { "master", no_argument, NULL, 0, - "\t\tLimit scope of command to the Master role (with -B, -M, -U).\n" - "\t\t\t\tFor -B and -M, the previous master may remain active in the Slave role." + "\t\tLimit scope of command to Master role (with -B, -M, -U). For -B " + "and -M, the previous master may remain active in the Slave role.", + pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nAdvanced Commands:", pcmk__option_default }, - - { "-spacer-", no_argument, NULL, '-', "\nAdvanced Commands:" }, { "delete", no_argument, NULL, 'D', - "\t\t(Advanced) Delete a resource from the CIB. Required: -t" + "\t\t(Advanced) Delete a resource from the CIB. Required: -t", + pcmk__option_default }, { "fail", no_argument, NULL, 'F', - "\t\t(Advanced) Tell the cluster this resource has failed" + "\t\t(Advanced) Tell the cluster this resource has failed", + pcmk__option_default }, { "restart", no_argument, NULL, 0, - "\t\t(Advanced) Tell the cluster to restart this resource and anything that depends on it" + "\t\t(Advanced) Tell the cluster to restart this resource and " + "anything that depends on it", + pcmk__option_default }, { "wait", no_argument, NULL, 0, - "\t\t(Advanced) Wait until the cluster settles into a stable state" + "\t\t(Advanced) Wait until the cluster settles into a stable state", + pcmk__option_default }, { "force-demote", no_argument, NULL, 0, - "\t(Advanced) Bypass the cluster and demote a resource on the local node.\n" - "\t\t\t\tUnless --force is specified, this will refuse to do so if the cluster\n" - "\t\t\t\tbelieves the resource is a clone instance already running on the local node." + "\t(Advanced) Bypass the cluster and demote a resource on the local " + "node. Unless --force is specified, this will refuse to do so if " + "the cluster believes the resource is a clone instance already " + "running on the local node.", + pcmk__option_default }, { "force-stop", no_argument, NULL, 0, - "\t(Advanced) Bypass the cluster and stop a resource on the local node." + "\t(Advanced) Bypass the cluster and stop a resource on the local node", + pcmk__option_default }, { "force-start", no_argument, NULL, 0, - "\t(Advanced) Bypass the cluster and start a resource on the local node.\n" - "\t\t\t\tUnless --force is specified, this will refuse to do so if the cluster\n" - "\t\t\t\tbelieves the resource is a clone instance already running on the local node." + "\t(Advanced) Bypass the cluster and start a resource on the local " + "node. Unless --force is specified, this will refuse to do so if " + "the cluster believes the resource is a clone instance already " + "running on the local node.", + pcmk__option_default }, { "force-promote", no_argument, NULL, 0, - "\t(Advanced) Bypass the cluster and promote a resource on the local node.\n" - "\t\t\t\tUnless --force is specified, this will refuse to do so if the cluster\n" - "\t\t\t\tbelieves the resource is a clone instance already running on the local node." + "\t(Advanced) Bypass the cluster and promote a resource on the local " + "node. Unless --force is specified, this will refuse to do so if " + "the cluster believes the resource is a clone instance already " + "running on the local node.", + pcmk__option_default }, { "force-check", no_argument, NULL, 0, - "\t(Advanced) Bypass the cluster and check the state of a resource on the local node." + "\t(Advanced) Bypass the cluster and check the state of a resource on " + "the local node", + pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nValidate Options:", pcmk__option_default }, - - { "-spacer-", no_argument, NULL, '-', "\nValidate Options:" }, { "class", required_argument, NULL, 0, - "\tThe standard the resource agent confirms to (for example, ocf).\n" - "\t\t\t\tUse with --agent, --provider, --option, and --validate." + "\tThe standard the resource agent confirms to (for example, ocf). " + "Use with --agent, --provider, --option, and --validate.", + pcmk__option_default }, { "agent", required_argument, NULL, 0, - "\tThe agent to use (for example, IPaddr).\n" - "\t\t\t\tUse with --class, --provider, --option, and --validate." + "\tThe agent to use (for example, IPaddr). Use with --class, " + "--provider, --option, and --validate.", + pcmk__option_default }, { "provider", required_argument, NULL, 0, - "\tThe vendor that supplies the resource agent (for example, heartbeat).\n" - "\t\t\t\tuse with --class, --agent, --option, and --validate." + "\tThe vendor that supplies the resource agent (for example, " + "heartbeat). Use with --class, --agent, --option, and --validate.", + pcmk__option_default }, { "option", required_argument, NULL, 0, - "\tSpecify a device configuration parameter as NAME=VALUE\n" - "\t\t\t\t(may be specified multiple times). Use with --validate\n" - "\t\t\t\tand without the -r option." + "\tSpecify a device configuration parameter as NAME=VALUE (may be " + "specified multiple times). Use with --validate and without the " + "-r option.", + pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nAdditional Options:", pcmk__option_default }, - - { "-spacer-", no_argument, NULL, '-', "\nAdditional Options:" }, { "node", required_argument, NULL, 'N', - "\tNode name" + "\tNode name", pcmk__option_default }, { "recursive", no_argument, NULL, 0, - "\tFollow colocation chains when using --set-parameter" + "\tFollow colocation chains when using --set-parameter", + pcmk__option_default }, { "resource-type", required_argument, NULL, 't', - "Resource XML element (primitive, group, etc.) (with -D)" + "Resource XML element (primitive, group, etc.) (with -D)", + pcmk__option_default }, { "parameter-value", required_argument, NULL, 'v', - "Value to use with -p" + "Value to use with -p", pcmk__option_default }, { "meta", no_argument, NULL, 'm', - "\t\tUse resource meta-attribute instead of instance attribute (with -p, -g, -d)" + "\t\tUse resource meta-attribute instead of instance attribute " + "(with -p, -g, -d)", + pcmk__option_default }, { "utilization", no_argument, NULL, 'z', - "\tUse resource utilization attribute instead of instance attribute (with -p, -g, -d)" + "\tUse resource utilization attribute instead of instance attribute " + "(with -p, -g, -d)", + pcmk__option_default }, { "operation", required_argument, NULL, 'n', - "\tOperation to clear instead of all (with -C -r)" + "\tOperation to clear instead of all (with -C -r)", + pcmk__option_default }, { "interval", required_argument, NULL, 'I', - "\tInterval of operation to clear (default 0) (with -C -r -n)" + "\tInterval of operation to clear (default 0) (with -C -r -n)", + pcmk__option_default }, { "set-name", required_argument, NULL, 's', - "\t(Advanced) XML ID of attributes element to use (with -p, -d)" + "\t(Advanced) XML ID of attributes element to use (with -p, -d)", + pcmk__option_default }, { "nvpair", required_argument, NULL, 'i', - "\t(Advanced) XML ID of nvpair element to use (with -p, -d)" + "\t(Advanced) XML ID of nvpair element to use (with -p, -d)", + pcmk__option_default }, { "timeout", required_argument, NULL, 'T', - "\t(Advanced) Abort if command does not finish in this time (with --restart, --wait, --force-*)" + "\t(Advanced) Abort if command does not finish in this time (with " + "--restart, --wait, --force-*)", + pcmk__option_default }, { "force", no_argument, NULL, 'f', - "\t\tIf making CIB changes, do so regardless of quorum.\n" - "\t\t\t\tSee help for individual commands for additional behavior.\n" + "\t\tIf making CIB changes, do so regardless of quorum. See help for " + "individual commands for additional behavior.", + pcmk__option_default }, { "xml-file", required_argument, NULL, 'x', - NULL, pcmk_option_hidden + NULL, pcmk__option_hidden }, /* legacy options */ - {"host-uname", required_argument, NULL, 'H', NULL, pcmk_option_hidden}, - - {"-spacer-", 1, NULL, '-', "\nExamples:", pcmk_option_paragraph}, - {"-spacer-", 1, NULL, '-', "List the available OCF agents:", pcmk_option_paragraph}, - {"-spacer-", 1, NULL, '-', " crm_resource --list-agents ocf", pcmk_option_example}, - {"-spacer-", 1, NULL, '-', "List the available OCF agents from the linux-ha project:", pcmk_option_paragraph}, - {"-spacer-", 1, NULL, '-', " crm_resource --list-agents ocf:heartbeat", pcmk_option_example}, - {"-spacer-", 1, NULL, '-', "Move 'myResource' to a specific node:", pcmk_option_paragraph}, - {"-spacer-", 1, NULL, '-', " crm_resource --resource myResource --move --node altNode", pcmk_option_example}, - {"-spacer-", 1, NULL, '-', "Allow (but not force) 'myResource' to move back to its original location:", pcmk_option_paragraph}, - {"-spacer-", 1, NULL, '-', " crm_resource --resource myResource --clear", pcmk_option_example}, - {"-spacer-", 1, NULL, '-', "Stop 'myResource' (and anything that depends on it):", pcmk_option_paragraph}, - {"-spacer-", 1, NULL, '-', " crm_resource --resource myResource --set-parameter target-role --meta --parameter-value Stopped", pcmk_option_example}, - {"-spacer-", 1, NULL, '-', "Tell the cluster not to manage 'myResource':", pcmk_option_paragraph}, - {"-spacer-", 1, NULL, '-', "The cluster will not attempt to start or stop the resource under any circumstances."}, - {"-spacer-", 1, NULL, '-', "Useful when performing maintenance tasks on a resource.", pcmk_option_paragraph}, - {"-spacer-", 1, NULL, '-', " crm_resource --resource myResource --set-parameter is-managed --meta --parameter-value false", pcmk_option_example}, - {"-spacer-", 1, NULL, '-', "Erase the operation history of 'myResource' on 'aNode':", pcmk_option_paragraph}, - {"-spacer-", 1, NULL, '-', "The cluster will 'forget' the existing resource state (including any errors) and attempt to recover the resource."}, - {"-spacer-", 1, NULL, '-', "Useful when a resource had failed permanently and has been repaired by an administrator.", pcmk_option_paragraph}, - {"-spacer-", 1, NULL, '-', " crm_resource --resource myResource --cleanup --node aNode", pcmk_option_example}, - - {0, 0, 0, 0} + { + "host-uname", required_argument, NULL, 'H', + NULL, pcmk__option_hidden + }, + + { + "-spacer-", 1, NULL, '-', + "\nExamples:", pcmk__option_paragraph + }, + { + "-spacer-", 1, NULL, '-', + "List the available OCF agents:", pcmk__option_paragraph + }, + { + "-spacer-", 1, NULL, '-', + " crm_resource --list-agents ocf", pcmk__option_example + }, + { + "-spacer-", 1, NULL, '-', + "List the available OCF agents from the linux-ha project:", + pcmk__option_paragraph + }, + { + "-spacer-", 1, NULL, '-', + " crm_resource --list-agents ocf:heartbeat", pcmk__option_example + }, + { + "-spacer-", 1, NULL, '-', + "Move 'myResource' to a specific node:", pcmk__option_paragraph + }, + { + "-spacer-", 1, NULL, '-', + " crm_resource --resource myResource --move --node altNode", + pcmk__option_example + }, + { + "-spacer-", 1, NULL, '-', + "Allow (but not force) 'myResource' to move back to its original " + "location:", + pcmk__option_paragraph + }, + { + "-spacer-", 1, NULL, '-', + " crm_resource --resource myResource --clear", pcmk__option_example + }, + { + "-spacer-", 1, NULL, '-', + "Stop 'myResource' (and anything that depends on it):", + pcmk__option_paragraph + }, + { + "-spacer-", 1, NULL, '-', + " crm_resource --resource myResource --set-parameter target-role " + "--meta --parameter-value Stopped", + pcmk__option_example + }, + { + "-spacer-", 1, NULL, '-', + "Tell the cluster not to manage 'myResource' (the cluster will not " + "attempt to start or stop the resource under any circumstances; " + "useful when performing maintenance tasks on a resource):", + pcmk__option_paragraph + }, + { + "-spacer-", 1, NULL, '-', + " crm_resource --resource myResource --set-parameter is-managed " + "--meta --parameter-value false", + pcmk__option_example + }, + { + "-spacer-", 1, NULL, '-', + "Erase the operation history of 'myResource' on 'aNode' (the cluster " + "will 'forget' the existing resource state, including any " + "errors, and attempt to recover the resource; useful when a " + "resource had failed permanently and has been repaired " + "by an administrator):", + pcmk__option_paragraph + }, + { + "-spacer-", 1, NULL, '-', + " crm_resource --resource myResource --cleanup --node aNode", + pcmk__option_example + }, + { 0, 0, 0, 0 } }; -/* *INDENT-ON* */ int main(int argc, char **argv) { char rsc_cmd = 'L'; const char *v_class = NULL; const char *v_agent = NULL; const char *v_provider = NULL; char *name = NULL; char *value = NULL; GHashTable *validate_options = NULL; const char *rsc_id = NULL; const char *host_uname = NULL; const char *prop_name = NULL; const char *prop_value = NULL; const char *rsc_type = NULL; const char *prop_id = NULL; const char *prop_set = NULL; const char *rsc_long_cmd = NULL; const char *longname = NULL; const char *operation = NULL; const char *interval_spec = NULL; const char *cib_file = getenv("CIB_file"); GHashTable *override_params = NULL; char *xml_file = NULL; xmlNode *cib_xml_copy = NULL; resource_t *rsc = NULL; bool recursive = FALSE; bool validate_cmdline = FALSE; /* whether we are just validating based on command line options */ bool require_resource = TRUE; /* whether command requires that resource be specified */ bool require_dataset = TRUE; /* whether command requires populated dataset instance */ bool require_crmd = FALSE; // whether command requires controller connection bool clear_expired = FALSE; int rc = pcmk_ok; int is_ocf_rc = 0; int option_index = 0; int timeout_ms = 0; int argerr = 0; int flag; int find_flags = 0; // Flags to use when searching for resource crm_exit_t exit_code = CRM_EX_OK; crm_log_cli_init("crm_resource"); - crm_set_options(NULL, "(query|command) [options]", long_options, - "Perform tasks related to cluster resources.\nAllows resources to be queried (definition and location), modified, and moved around the cluster.\n"); + pcmk__set_cli_options(NULL, "| [options]", long_options, + "perform tasks related to Pacemaker " + "cluster resources"); validate_options = crm_str_table_new(); while (1) { - flag = crm_get_option_long(argc, argv, &option_index, &longname); + flag = pcmk__next_cli_option(argc, argv, &option_index, &longname); if (flag == -1) break; switch (flag) { case 0: /* long options with no short equivalent */ if (safe_str_eq("master", longname)) { scope_master = TRUE; } else if(safe_str_eq(longname, "recursive")) { recursive = TRUE; } else if (safe_str_eq("wait", longname)) { rsc_cmd = flag; rsc_long_cmd = longname; require_resource = FALSE; require_dataset = FALSE; } else if ( safe_str_eq("validate", longname) || safe_str_eq("restart", longname) || safe_str_eq("force-demote", longname) || safe_str_eq("force-stop", longname) || safe_str_eq("force-start", longname) || safe_str_eq("force-promote", longname) || safe_str_eq("force-check", longname)) { rsc_cmd = flag; rsc_long_cmd = longname; find_flags = pe_find_renamed|pe_find_anon; crm_log_args(argc, argv); } else if (safe_str_eq("list-ocf-providers", longname) || safe_str_eq("list-ocf-alternatives", longname) || safe_str_eq("list-standards", longname)) { const char *text = NULL; lrmd_list_t *list = NULL; lrmd_list_t *iter = NULL; lrmd_t *lrmd_conn = lrmd_api_new(); if (safe_str_eq("list-ocf-providers", longname) || safe_str_eq("list-ocf-alternatives", longname)) { rc = lrmd_conn->cmds->list_ocf_providers(lrmd_conn, optarg, &list); text = "OCF providers"; } else if (safe_str_eq("list-standards", longname)) { rc = lrmd_conn->cmds->list_standards(lrmd_conn, &list); text = "standards"; } if (rc > 0) { for (iter = list; iter != NULL; iter = iter->next) { printf("%s\n", iter->val); } lrmd_list_freeall(list); } else if (optarg) { fprintf(stderr, "No %s found for %s\n", text, optarg); exit_code = CRM_EX_NOSUCH; } else { fprintf(stderr, "No %s found\n", text); exit_code = CRM_EX_NOSUCH; } lrmd_api_delete(lrmd_conn); bye(exit_code); } else if (safe_str_eq("show-metadata", longname)) { char *standard = NULL; char *provider = NULL; char *type = NULL; char *metadata = NULL; lrmd_t *lrmd_conn = lrmd_api_new(); rc = crm_parse_agent_spec(optarg, &standard, &provider, &type); if (rc == pcmk_ok) { rc = lrmd_conn->cmds->get_metadata(lrmd_conn, standard, provider, type, &metadata, 0); } else { fprintf(stderr, "'%s' is not a valid agent specification\n", optarg); rc = -ENXIO; } if (metadata) { printf("%s\n", metadata); } else { fprintf(stderr, "Metadata query for %s failed: %s\n", optarg, pcmk_strerror(rc)); exit_code = crm_errno2exit(rc); } lrmd_api_delete(lrmd_conn); bye(exit_code); } else if (safe_str_eq("list-agents", longname)) { lrmd_list_t *list = NULL; lrmd_list_t *iter = NULL; char *provider = strchr (optarg, ':'); lrmd_t *lrmd_conn = lrmd_api_new(); if (provider) { *provider++ = 0; } rc = lrmd_conn->cmds->list_agents(lrmd_conn, &list, optarg, provider); if (rc > 0) { for (iter = list; iter != NULL; iter = iter->next) { printf("%s\n", iter->val); } lrmd_list_freeall(list); } else { fprintf(stderr, "No agents found for standard=%s, provider=%s\n", optarg, (provider? provider : "*")); exit_code = CRM_EX_NOSUCH; } lrmd_api_delete(lrmd_conn); bye(exit_code); } else if (safe_str_eq("class", longname)) { if (!(pcmk_get_ra_caps(optarg) & pcmk_ra_cap_params)) { if (BE_QUIET == FALSE) { fprintf(stdout, "Standard %s does not support parameters\n", optarg); } bye(exit_code); } else { v_class = optarg; } validate_cmdline = TRUE; require_resource = FALSE; } else if (safe_str_eq("agent", longname)) { validate_cmdline = TRUE; require_resource = FALSE; v_agent = optarg; } else if (safe_str_eq("provider", longname)) { validate_cmdline = TRUE; require_resource = FALSE; v_provider = optarg; } else if (safe_str_eq("option", longname)) { crm_info("Scanning: --option %s", optarg); rc = pcmk_scan_nvpair(optarg, &name, &value); if (rc != 2) { fprintf(stderr, "Invalid option: --option %s: %s", optarg, pcmk_strerror(rc)); argerr++; } else { crm_info("Got: '%s'='%s'", name, value); } g_hash_table_replace(validate_options, name, value); } else { crm_err("Unhandled long option: %s", longname); } break; case 'V': resource_verbose++; crm_bump_log_level(argc, argv); break; case '$': case '?': - crm_help(flag, CRM_EX_OK); + pcmk__cli_help(flag, CRM_EX_OK); break; case 'x': xml_file = strdup(optarg); break; case 'Q': BE_QUIET = TRUE; break; case 'm': attr_set_type = XML_TAG_META_SETS; break; case 'z': attr_set_type = XML_TAG_UTILIZATION; break; case 'u': move_lifetime = strdup(optarg); break; case 'f': do_force = TRUE; crm_log_args(argc, argv); break; case 'i': prop_id = optarg; break; case 's': prop_set = optarg; break; case 'r': rsc_id = optarg; break; case 'v': prop_value = optarg; break; case 't': rsc_type = optarg; break; case 'T': timeout_ms = crm_get_msec(optarg); break; case 'e': clear_expired = TRUE; require_resource = FALSE; break; case 'C': case 'R': crm_log_args(argc, argv); require_resource = FALSE; if (cib_file == NULL) { require_crmd = TRUE; } rsc_cmd = flag; find_flags = pe_find_renamed|pe_find_anon; break; case 'n': operation = optarg; break; case 'I': interval_spec = optarg; break; case 'D': require_dataset = FALSE; crm_log_args(argc, argv); rsc_cmd = flag; find_flags = pe_find_renamed|pe_find_any; break; case 'F': require_crmd = TRUE; crm_log_args(argc, argv); rsc_cmd = flag; break; case 'U': case 'B': case 'M': crm_log_args(argc, argv); rsc_cmd = flag; find_flags = pe_find_renamed|pe_find_anon; break; case 'c': case 'L': case 'l': case 'O': case 'o': require_resource = FALSE; rsc_cmd = flag; break; case 'Y': require_resource = FALSE; rsc_cmd = flag; find_flags = pe_find_renamed|pe_find_anon; break; case 'q': case 'w': rsc_cmd = flag; find_flags = pe_find_renamed|pe_find_any; break; case 'W': case 'A': case 'a': rsc_cmd = flag; find_flags = pe_find_renamed|pe_find_anon; break; case 'S': require_dataset = FALSE; crm_log_args(argc, argv); prop_name = optarg; rsc_cmd = flag; find_flags = pe_find_renamed|pe_find_any; break; case 'p': case 'd': crm_log_args(argc, argv); prop_name = optarg; rsc_cmd = flag; find_flags = pe_find_renamed|pe_find_any; break; case 'G': case 'g': prop_name = optarg; rsc_cmd = flag; find_flags = pe_find_renamed|pe_find_any; break; case 'H': case 'N': crm_trace("Option %c => %s", flag, optarg); host_uname = optarg; break; default: CMD_ERR("Argument code 0%o (%c) is not (?yet?) supported", flag, flag); ++argerr; break; } } // Catch the case where the user didn't specify a command if (rsc_cmd == 'L') { require_resource = FALSE; } // --expired without --clear/-U doesn't make sense if (clear_expired == TRUE && rsc_cmd != 'U') { CMD_ERR("--expired requires --clear or -U"); argerr++; } if (optind < argc && argv[optind] != NULL && rsc_cmd == 0 && rsc_long_cmd) { override_params = crm_str_table_new(); while (optind < argc && argv[optind] != NULL) { char *name = calloc(1, strlen(argv[optind])); char *value = calloc(1, strlen(argv[optind])); int rc = sscanf(argv[optind], "%[^=]=%s", name, value); if(rc == 2) { g_hash_table_replace(override_params, name, value); } else { CMD_ERR("Error parsing '%s' as a name=value pair for --%s", argv[optind], rsc_long_cmd); free(value); free(name); argerr++; } optind++; } } else if (optind < argc && argv[optind] != NULL && rsc_cmd == 0) { CMD_ERR("non-option ARGV-elements: "); while (optind < argc && argv[optind] != NULL) { CMD_ERR("[%d of %d] %s ", optind, argc, argv[optind]); optind++; argerr++; } } if (optind > argc) { ++argerr; } // Sanity check validating from command line parameters. If everything checks out, // go ahead and run the validation. This way we don't need a CIB connection. if (validate_cmdline == TRUE) { // -r cannot be used with any of --class, --agent, or --provider if (rsc_id != NULL) { CMD_ERR("--resource cannot be used with --class, --agent, and --provider"); argerr++; // If --class, --agent, or --provider are given, --validate must also be given. } else if (!safe_str_eq(rsc_long_cmd, "validate")) { CMD_ERR("--class, --agent, and --provider require --validate"); argerr++; // Not all of --class, --agent, and --provider need to be given. Not all // classes support the concept of a provider. Check that what we were given // is valid. } else if (crm_str_eq(v_class, "stonith", TRUE)) { if (v_provider != NULL) { CMD_ERR("stonith does not support providers"); argerr++; } else if (stonith_agent_exists(v_agent, 0) == FALSE) { CMD_ERR("%s is not a known stonith agent", v_agent ? v_agent : ""); argerr++; } } else if (resources_agent_exists(v_class, v_provider, v_agent) == FALSE) { CMD_ERR("%s:%s:%s is not a known resource", v_class ? v_class : "", v_provider ? v_provider : "", v_agent ? v_agent : ""); argerr++; } if (argerr == 0) { rc = cli_resource_execute_from_params("test", v_class, v_provider, v_agent, "validate-all", validate_options, override_params, timeout_ms); exit_code = crm_errno2exit(rc); bye(exit_code); } } if (argerr) { CMD_ERR("Invalid option(s) supplied, use --help for valid usage"); bye(CRM_EX_USAGE); } if (do_force) { crm_debug("Forcing..."); cib_options |= cib_quorum_override; } if (require_resource && !rsc_id) { CMD_ERR("Must supply a resource id with -r"); rc = -ENXIO; goto bail; } if (find_flags && rsc_id) { require_dataset = TRUE; } /* Establish a connection to the CIB manager */ cib_conn = cib_new(); rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command); if (rc != pcmk_ok) { CMD_ERR("Error connecting to the CIB manager: %s", pcmk_strerror(rc)); goto bail; } /* Populate working set from XML file if specified or CIB query otherwise */ if (require_dataset) { if (xml_file != NULL) { cib_xml_copy = filename2xml(xml_file); } else { rc = cib_conn->cmds->query(cib_conn, NULL, &cib_xml_copy, cib_scope_local | cib_sync_call); } if(rc != pcmk_ok) { goto bail; } /* Populate the working set instance */ data_set = pe_new_working_set(); if (data_set == NULL) { rc = -ENOMEM; goto bail; } set_bit(data_set->flags, pe_flag_no_counts); set_bit(data_set->flags, pe_flag_no_compat); rc = update_working_set_xml(data_set, &cib_xml_copy); if (rc != pcmk_ok) { goto bail; } cluster_status(data_set); } // If command requires that resource exist if specified, find it if (find_flags && rsc_id) { rsc = pe_find_resource_with_flags(data_set->resources, rsc_id, find_flags); if (rsc == NULL) { CMD_ERR("Resource '%s' not found", rsc_id); rc = -ENXIO; goto bail; } } // Establish a connection to the controller if needed if (require_crmd) { char *client_uuid; pcmk_controld_api_cb_t dispatch_cb = { handle_controller_reply, NULL }; pcmk_controld_api_cb_t destroy_cb = { handle_controller_drop, NULL }; client_uuid = crm_getpid_s(); controld_api = pcmk_new_controld_api(crm_system_name, client_uuid); free(client_uuid); rc = controld_api->connect(controld_api, true, &dispatch_cb, &destroy_cb); if (rc != pcmk_rc_ok) { CMD_ERR("Error connecting to the controller: %s", pcmk_rc_str(rc)); rc = pcmk_rc2legacy(rc); goto bail; } } /* Handle rsc_cmd appropriately */ if (rsc_cmd == 'L') { rc = pcmk_ok; cli_resource_print_list(data_set, FALSE); } else if (rsc_cmd == 'l') { int found = 0; GListPtr lpc = NULL; rc = pcmk_ok; for (lpc = data_set->resources; lpc != NULL; lpc = lpc->next) { rsc = (resource_t *) lpc->data; found++; cli_resource_print_raw(rsc); } if (found == 0) { printf("NO resources configured\n"); rc = -ENXIO; } } else if (rsc_cmd == 0 && rsc_long_cmd && safe_str_eq(rsc_long_cmd, "restart")) { /* We don't pass data_set because rsc needs to stay valid for the entire * lifetime of cli_resource_restart(), but it will reset and update the * working set multiple times, so it needs to use its own copy. */ rc = cli_resource_restart(rsc, host_uname, timeout_ms, cib_conn); } else if (rsc_cmd == 0 && rsc_long_cmd && safe_str_eq(rsc_long_cmd, "wait")) { rc = wait_till_stable(timeout_ms, cib_conn); } else if (rsc_cmd == 0 && rsc_long_cmd) { // validate, force-(stop|start|demote|promote|check) rc = cli_resource_execute(rsc, rsc_id, rsc_long_cmd, override_params, timeout_ms, cib_conn, data_set); if (rc >= 0) { is_ocf_rc = 1; } } else if (rsc_cmd == 'A' || rsc_cmd == 'a') { GListPtr lpc = NULL; xmlNode *cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, data_set->input); unpack_constraints(cib_constraints, data_set); // Constraints apply to group/clone, not member/instance rsc = uber_parent(rsc); for (lpc = data_set->resources; lpc != NULL; lpc = lpc->next) { resource_t *r = (resource_t *) lpc->data; clear_bit(r->flags, pe_rsc_allocating); } cli_resource_print_colocation(rsc, TRUE, rsc_cmd == 'A', 1); fprintf(stdout, "* %s\n", rsc->id); cli_resource_print_location(rsc, NULL); for (lpc = data_set->resources; lpc != NULL; lpc = lpc->next) { resource_t *r = (resource_t *) lpc->data; clear_bit(r->flags, pe_rsc_allocating); } cli_resource_print_colocation(rsc, FALSE, rsc_cmd == 'A', 1); } else if (rsc_cmd == 'c') { GListPtr lpc = NULL; rc = pcmk_ok; for (lpc = data_set->resources; lpc != NULL; lpc = lpc->next) { rsc = (resource_t *) lpc->data; cli_resource_print_cts(rsc); } cli_resource_print_cts_constraints(data_set); } else if (rsc_cmd == 'F') { rc = cli_resource_fail(controld_api, host_uname, rsc_id, data_set); if (rc == pcmk_rc_ok) { start_mainloop(controld_api); } rc = pcmk_rc2legacy(rc); } else if (rsc_cmd == 'O') { rc = cli_resource_print_operations(rsc_id, host_uname, TRUE, data_set); } else if (rsc_cmd == 'o') { rc = cli_resource_print_operations(rsc_id, host_uname, FALSE, data_set); } else if (rsc_cmd == 'W') { rc = cli_resource_search(rsc, rsc_id, data_set); if (rc >= 0) { rc = pcmk_ok; } } else if (rsc_cmd == 'q') { rc = cli_resource_print(rsc, data_set, TRUE); } else if (rsc_cmd == 'w') { rc = cli_resource_print(rsc, data_set, FALSE); } else if (rsc_cmd == 'Y') { node_t *dest = NULL; if (host_uname) { dest = pe_find_node(data_set->nodes, host_uname); if (dest == NULL) { rc = -pcmk_err_node_unknown; goto bail; } } cli_resource_why(cib_conn, data_set->resources, rsc, dest); rc = pcmk_ok; } else if (rsc_cmd == 'U') { GListPtr before = NULL; GListPtr after = NULL; GListPtr remaining = NULL; GListPtr ele = NULL; node_t *dest = NULL; if (BE_QUIET == FALSE) { before = build_constraint_list(data_set->input); } if (clear_expired == TRUE) { rc = cli_resource_clear_all_expired(data_set->input, cib_conn, rsc_id, host_uname, scope_master); } else if (host_uname) { dest = pe_find_node(data_set->nodes, host_uname); if (dest == NULL) { rc = -pcmk_err_node_unknown; if (BE_QUIET == FALSE) { g_list_free(before); } goto bail; } rc = cli_resource_clear(rsc_id, dest->details->uname, NULL, cib_conn, TRUE); } else { rc = cli_resource_clear(rsc_id, NULL, data_set->nodes, cib_conn, TRUE); } if (BE_QUIET == FALSE) { rc = cib_conn->cmds->query(cib_conn, NULL, &cib_xml_copy, cib_scope_local | cib_sync_call); if (rc != pcmk_ok) { CMD_ERR("Could not get modified CIB: %s\n", pcmk_strerror(rc)); g_list_free(before); goto bail; } data_set->input = cib_xml_copy; cluster_status(data_set); after = build_constraint_list(data_set->input); remaining = subtract_lists(before, after, (GCompareFunc) strcmp); for (ele = remaining; ele != NULL; ele = ele->next) { printf("Removing constraint: %s\n", (char *) ele->data); } g_list_free(before); g_list_free(after); g_list_free(remaining); } } else if (rsc_cmd == 'M' && host_uname) { rc = cli_resource_move(rsc, rsc_id, host_uname, cib_conn, data_set); } else if (rsc_cmd == 'B' && host_uname) { node_t *dest = pe_find_node(data_set->nodes, host_uname); if (dest == NULL) { rc = -pcmk_err_node_unknown; goto bail; } rc = cli_resource_ban(rsc_id, dest->details->uname, NULL, cib_conn); } else if (rsc_cmd == 'B' || rsc_cmd == 'M') { pe_node_t *current = NULL; unsigned int nactive = 0; current = pe__find_active_requires(rsc, &nactive); if (nactive == 1) { rc = cli_resource_ban(rsc_id, current->details->uname, NULL, cib_conn); } else if (is_set(rsc->flags, pe_rsc_promotable)) { int count = 0; GListPtr iter = NULL; current = NULL; for(iter = rsc->children; iter; iter = iter->next) { resource_t *child = (resource_t *)iter->data; enum rsc_role_e child_role = child->fns->state(child, TRUE); if(child_role == RSC_ROLE_MASTER) { count++; current = pe__current_node(child); } } if(count == 1 && current) { rc = cli_resource_ban(rsc_id, current->details->uname, NULL, cib_conn); } else { rc = -EINVAL; exit_code = CRM_EX_USAGE; CMD_ERR("Resource '%s' not moved: active in %d locations (promoted in %d).", rsc_id, nactive, count); CMD_ERR("To prevent '%s' from running on a specific location, " "specify a node.", rsc_id); CMD_ERR("To prevent '%s' from being promoted at a specific " "location, specify a node and the master option.", rsc_id); } } else { rc = -EINVAL; exit_code = CRM_EX_USAGE; CMD_ERR("Resource '%s' not moved: active in %d locations.", rsc_id, nactive); CMD_ERR("To prevent '%s' from running on a specific location, " "specify a node.", rsc_id); } } else if (rsc_cmd == 'G') { rc = cli_resource_print_property(rsc, prop_name, data_set); } else if (rsc_cmd == 'S') { xmlNode *msg_data = NULL; if ((rsc_type == NULL) || !strlen(rsc_type)) { CMD_ERR("Must specify -t with resource type"); rc = -ENXIO; goto bail; } else if ((prop_value == NULL) || !strlen(prop_value)) { CMD_ERR("Must supply -v with new value"); rc = -EINVAL; goto bail; } CRM_LOG_ASSERT(prop_name != NULL); msg_data = create_xml_node(NULL, rsc_type); crm_xml_add(msg_data, XML_ATTR_ID, rsc_id); crm_xml_add(msg_data, prop_name, prop_value); rc = cib_conn->cmds->modify(cib_conn, XML_CIB_TAG_RESOURCES, msg_data, cib_options); free_xml(msg_data); } else if (rsc_cmd == 'g') { rc = cli_resource_print_attribute(rsc, prop_name, data_set); } else if (rsc_cmd == 'p') { if (prop_value == NULL || strlen(prop_value) == 0) { CMD_ERR("You need to supply a value with the -v option"); rc = -EINVAL; goto bail; } /* coverity[var_deref_model] False positive */ rc = cli_resource_update_attribute(rsc, rsc_id, prop_set, prop_id, prop_name, prop_value, recursive, cib_conn, data_set); } else if (rsc_cmd == 'd') { /* coverity[var_deref_model] False positive */ rc = cli_resource_delete_attribute(rsc, rsc_id, prop_set, prop_id, prop_name, cib_conn, data_set); } else if ((rsc_cmd == 'C') && rsc) { if (do_force == FALSE) { rsc = uber_parent(rsc); } crm_debug("Erasing failures of %s (%s requested) on %s", rsc->id, rsc_id, (host_uname? host_uname: "all nodes")); rc = cli_resource_delete(controld_api, host_uname, rsc, operation, interval_spec, TRUE, data_set); if ((rc == pcmk_ok) && !BE_QUIET) { // Show any reasons why resource might stay stopped cli_resource_check(cib_conn, rsc); } if (rc == pcmk_ok) { start_mainloop(controld_api); } } else if (rsc_cmd == 'C') { rc = cli_cleanup_all(controld_api, host_uname, operation, interval_spec, data_set); if (rc == pcmk_ok) { start_mainloop(controld_api); } } else if ((rsc_cmd == 'R') && rsc) { if (do_force == FALSE) { rsc = uber_parent(rsc); } crm_debug("Re-checking the state of %s (%s requested) on %s", rsc->id, rsc_id, (host_uname? host_uname: "all nodes")); rc = cli_resource_delete(controld_api, host_uname, rsc, NULL, 0, FALSE, data_set); if ((rc == pcmk_ok) && !BE_QUIET) { // Show any reasons why resource might stay stopped cli_resource_check(cib_conn, rsc); } if (rc == pcmk_ok) { start_mainloop(controld_api); } } else if (rsc_cmd == 'R') { const char *router_node = host_uname; int attr_options = pcmk__node_attr_none; if (host_uname) { node_t *node = pe_find_node(data_set->nodes, host_uname); if (pe__is_guest_or_remote_node(node)) { node = pe__current_node(node->details->remote_rsc); if (node == NULL) { CMD_ERR("No cluster connection to Pacemaker Remote node %s detected", host_uname); rc = -ENXIO; goto bail; } router_node = node->details->uname; attr_options |= pcmk__node_attr_remote; } } if (controld_api == NULL) { printf("Dry run: skipping clean-up of %s due to CIB_file\n", host_uname? host_uname : "all nodes"); rc = pcmk_ok; goto bail; } crm_debug("Re-checking the state of all resources on %s", host_uname?host_uname:"all nodes"); rc = pcmk_rc2legacy(pcmk__node_attr_request_clear(NULL, host_uname, NULL, NULL, NULL, NULL, attr_options)); if (controld_api->reprobe(controld_api, host_uname, router_node) == pcmk_rc_ok) { start_mainloop(controld_api); } } else if (rsc_cmd == 'D') { xmlNode *msg_data = NULL; if (rsc_type == NULL) { CMD_ERR("You need to specify a resource type with -t"); rc = -ENXIO; goto bail; } msg_data = create_xml_node(NULL, rsc_type); crm_xml_add(msg_data, XML_ATTR_ID, rsc_id); rc = cib_conn->cmds->remove(cib_conn, XML_CIB_TAG_RESOURCES, msg_data, cib_options); free_xml(msg_data); } else { CMD_ERR("Unknown command: %c", rsc_cmd); } bail: if (is_ocf_rc) { exit_code = rc; } else if (rc != pcmk_ok) { CMD_ERR("Error performing operation: %s", pcmk_strerror(rc)); if (rc == -pcmk_err_no_quorum) { CMD_ERR("To ignore quorum, use the force option"); } if (exit_code == CRM_EX_OK) { exit_code = crm_errno2exit(rc); } } bye(exit_code); } diff --git a/tools/crm_rule.c b/tools/crm_rule.c index 51b022ad79..19a8c3badc 100644 --- a/tools/crm_rule.c +++ b/tools/crm_rule.c @@ -1,282 +1,314 @@ /* - * Copyright 2019 the Pacemaker project contributors + * Copyright 2019-2020 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 enum crm_rule_mode { crm_rule_mode_none, crm_rule_mode_check } rule_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 struct crm_option long_options[] = { - /* Top-level Options */ - {"help", no_argument, NULL, '?', "\tThis text"}, - {"version", no_argument, NULL, '$', "\tVersion information" }, - {"verbose", no_argument, NULL, 'V', "\tIncrease debug output"}, - - {"-spacer-", required_argument, NULL, '-', "\nModes (mutually exclusive):" }, - {"check", no_argument, NULL, 'c', "\tCheck whether a rule is in effect" }, - - {"-spacer-", required_argument, NULL, '-', "\nAdditional options:" }, - {"date", required_argument, NULL, 'd', "Whether the rule is in effect on a given date" }, - {"rule", required_argument, NULL, 'r', "The ID of the rule to check" }, - - {"-spacer-", no_argument, NULL, '-', "\nData:"}, - {"xml-text", required_argument, NULL, 'X', "Use argument for XML (or stdin if '-')"}, - - {"-spacer-", required_argument, NULL, '-', "\n\nThis tool is currently experimental.", - pcmk_option_paragraph}, - {"-spacer-", required_argument, NULL, '-', "The interface, behavior, and output may " - "change with any version of pacemaker.", pcmk_option_paragraph}, - - {0, 0, 0, 0} +static pcmk__cli_option_t long_options[] = { + // long option, argument type, storage, short option, description, flags + { + "help", no_argument, NULL, '?', + "\tThis text", pcmk__option_default + }, + { + "version", no_argument, NULL, '$', + "\tVersion information", pcmk__option_default + }, + { + "verbose", no_argument, NULL, 'V', + "\tIncrease debug output", pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nModes (mutually exclusive):", pcmk__option_default + }, + { + "check", no_argument, NULL, 'c', + "\tCheck whether a rule is in effect", pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nAdditional options:", pcmk__option_default + }, + { + "date", required_argument, NULL, 'd', + "Whether the rule is in effect on a given date", pcmk__option_default + }, + { + "rule", required_argument, NULL, 'r', + "The ID of the rule to check", pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nData:", pcmk__option_default + }, + { + "xml-text", required_argument, NULL, 'X', + "Use argument for XML (or stdin if '-')", pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\n\nThis tool is currently experimental.", + pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + "The interface, behavior, and output may change with any version of " + "pacemaker.", + pcmk__option_paragraph + }, + { 0, 0, 0, 0 } }; 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; pe_eval_date_result_t result; char *xpath = NULL; int rc = pcmk_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 also have only a single date_expression * child whose operation is not 'date_spec'. This is fairly limited, but it's hard * to check expressions more complicated than that. */ 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) { CMD_ERR("No rule found with ID=%s containing a date_expression", rule_id); rc = -ENXIO; goto bail; } else if (max > 1) { CMD_ERR("More than one date_expression in %s is not supported", rule_id); rc = -EOPNOTSUPP; goto bail; } 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); result = pe_eval_date_expression(match, effective_date, NULL); if (result == pe_date_within_range) { printf("Rule %s is still in effect\n", rule_id); rc = 0; } else if (result == pe_date_after_range) { printf("Rule %s is expired\n", rule_id); rc = 1; } else if (result == pe_date_before_range) { printf("Rule %s has not yet taken effect\n", rule_id); rc = 2; } else { printf("Could not determine whether rule %s is expired\n", rule_id); rc = 3; } bail: free(xpath); freeXpathObject(xpathObj); return rc; } int main(int argc, char **argv) { cib_t *cib_conn = NULL; pe_working_set_t *data_set = NULL; int flag = 0; int option_index = 0; char *rule_id = NULL; crm_time_t *rule_date = NULL; xmlNode *input = NULL; char *input_xml = NULL; int rc = pcmk_ok; crm_exit_t exit_code = CRM_EX_OK; crm_log_cli_init("crm_rule"); - crm_set_options(NULL, "[options]", long_options, - "Tool for querying the state of rules"); + pcmk__set_cli_options(NULL, "[options]", long_options, + "evaluate rules from the Pacemaker configuration"); while (flag >= 0) { - flag = crm_get_option(argc, argv, &option_index); + flag = pcmk__next_cli_option(argc, argv, &option_index, NULL); switch (flag) { case -1: break; case 'V': crm_bump_log_level(argc, argv); break; case '$': case '?': - crm_help(flag, CRM_EX_OK); + pcmk__cli_help(flag, CRM_EX_OK); break; case 'c': rule_mode = crm_rule_mode_check; break; case 'd': rule_date = crm_time_new(optarg); if (rule_date == NULL) { exit_code = CRM_EX_DATAERR; goto bail; } break; case 'X': input_xml = optarg; break; case 'r': rule_id = strdup(optarg); break; default: - crm_help(flag, CRM_EX_OK); + pcmk__cli_help(flag, CRM_EX_OK); break; } } /* Check command line arguments before opening a connection to * the CIB manager or doing anything else important. */ if (rule_mode == crm_rule_mode_check) { if (rule_id == NULL) { CMD_ERR("--check requires use of --rule=\n"); - crm_help(flag, CRM_EX_USAGE); + pcmk__cli_help(flag, CRM_EX_USAGE); } } /* Set up some defaults. */ if (rule_date == NULL) { rule_date = crm_time_new(NULL); } /* 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 (safe_str_eq(input_xml, "-")) { input = stdin2xml(); if (input == NULL) { fprintf(stderr, "Couldn't parse input from STDIN\n"); exit_code = CRM_EX_DATAERR; goto bail; } } else if (input_xml != NULL) { input = string2xml(input_xml); if (input == NULL) { fprintf(stderr, "Couldn't parse input string: %s\n", input_xml); exit_code = CRM_EX_DATAERR; goto bail; } } else { /* Establish a connection to the CIB manager */ cib_conn = cib_new(); rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command); if (rc != pcmk_ok) { CMD_ERR("Error connecting to the CIB manager: %s", pcmk_strerror(rc)); exit_code = crm_errno2exit(rc); goto bail; } } /* 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); goto bail; } } /* Populate the working set instance */ data_set = pe_new_working_set(); if (data_set == NULL) { exit_code = crm_errno2exit(ENOMEM); goto bail; } set_bit(data_set->flags, pe_flag_no_counts); set_bit(data_set->flags, 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(rule_mode) { case crm_rule_mode_check: rc = crm_rule_check(data_set, rule_id, rule_date); if (rc < 0) { CMD_ERR("Error checking rule: %s", pcmk_strerror(rc)); exit_code = crm_errno2exit(rc); } else if (rc == 1) { exit_code = CRM_EX_EXPIRED; } else if (rc == 2) { exit_code = CRM_EX_NOT_YET_IN_EFFECT; } else if (rc == 3) { exit_code = CRM_EX_INDETERMINATE; } else { exit_code = rc; } break; default: break; } bail: if (cib_conn != NULL) { cib_conn->cmds->signoff(cib_conn); cib_delete(cib_conn); } pe_free_working_set(data_set); crm_exit(exit_code); } diff --git a/tools/crm_shadow.c b/tools/crm_shadow.c index 9171b5e50c..41c368aecf 100644 --- a/tools/crm_shadow.c +++ b/tools/crm_shadow.c @@ -1,449 +1,555 @@ /* - * Copyright 2004-2019 the Pacemaker project contributors + * Copyright 2004-2020 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 static int command_options = cib_sync_call; static cib_t *real_cib = NULL; static int force_flag = 0; static int batch_flag = 0; static char * get_shadow_prompt(const char *name) { return crm_strdup_printf("shadow[%.40s] # ", name); } static void shadow_setup(char *name, gboolean do_switch) { const char *prompt = getenv("PS1"); const char *shell = getenv("SHELL"); char *new_prompt = get_shadow_prompt(name); printf("Setting up shadow instance\n"); if (safe_str_eq(new_prompt, prompt)) { /* nothing to do */ goto done; } else if (batch_flag == FALSE && shell != NULL) { setenv("PS1", new_prompt, 1); setenv("CIB_shadow", name, 1); printf("Type Ctrl-D to exit the crm_shadow shell\n"); if (strstr(shell, "bash")) { execl(shell, shell, "--norc", "--noprofile", NULL); } else { execl(shell, shell, NULL); } } else if (do_switch) { printf("To switch to the named shadow instance, paste the following into your shell:\n"); } else { printf ("A new shadow instance was created. To begin using it paste the following into your shell:\n"); } printf(" CIB_shadow=%s ; export CIB_shadow\n", name); done: free(new_prompt); } static void shadow_teardown(char *name) { const char *prompt = getenv("PS1"); char *our_prompt = get_shadow_prompt(name); if (prompt != NULL && strstr(prompt, our_prompt)) { printf("Now type Ctrl-D to exit the crm_shadow shell\n"); } else { printf ("Please remember to unset the CIB_shadow variable by pasting the following into your shell:\n"); printf(" unset CIB_shadow\n"); } free(our_prompt); } -/* *INDENT-OFF* */ -static struct crm_option long_options[] = { - /* Top-level Options */ - {"help", 0, 0, '?', "\t\tThis text"}, - {"version", 0, 0, '$', "\t\tVersion information" }, - {"verbose", 0, 0, 'V', "\t\tIncrease debug output"}, - - {"-spacer-", 1, 0, '-', "\nQueries:"}, - {"which", no_argument, NULL, 'w', "\t\tIndicate the active shadow copy"}, - {"display", no_argument, NULL, 'p', "\t\tDisplay the contents of the active shadow copy"}, - {"edit", no_argument, NULL, 'E', "\t\tEdit the contents of the active shadow copy with your favorite $EDITOR"}, - {"diff", no_argument, NULL, 'd', "\t\tDisplay the changes in the active shadow copy\n"}, - {"file", no_argument, NULL, 'F', "\t\tDisplay the location of the active shadow copy file\n"}, - - {"-spacer-", 1, 0, '-', "\nCommands:"}, - {"create", required_argument, NULL, 'c', "\tCreate the named shadow copy of the active cluster configuration"}, - {"create-empty", required_argument, NULL, 'e', "Create the named shadow copy with an empty cluster configuration. Optional: --validate-with"}, - {"commit", required_argument, NULL, 'C', "\tUpload the contents of the named shadow copy to the cluster"}, - {"delete", required_argument, NULL, 'D', "\tDelete the contents of the named shadow copy"}, - {"reset", required_argument, NULL, 'r', "\tRecreate the named shadow copy from the active cluster configuration"}, - {"switch", required_argument, NULL, 's', "\t(Advanced) Switch to the named shadow copy"}, - - {"-spacer-", 1, 0, '-', "\nAdditional Options:"}, - {"force", no_argument, NULL, 'f', "\t\t(Advanced) Force the action to be performed"}, - {"batch", no_argument, NULL, 'b', "\t\t(Advanced) Don't spawn a new shell" }, - {"all", no_argument, NULL, 'a', "\t\t(Advanced) Upload the entire CIB, including status, with --commit" }, - {"validate-with", required_argument, NULL, 'v', "(Advanced) Create an older configuration version" }, - - {"-spacer-", 1, 0, '-', "\nExamples:", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', "Create a blank shadow configuration:", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', " crm_shadow --create-empty myShadow", pcmk_option_example}, - {"-spacer-", 1, 0, '-', "Create a shadow configuration from the running cluster:", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', " crm_shadow --create myShadow", pcmk_option_example}, - {"-spacer-", 1, 0, '-', "Display the current shadow configuration:", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', " crm_shadow --display", pcmk_option_example}, - {"-spacer-", 1, 0, '-', "Discard the current shadow configuration (named myShadow):", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', " crm_shadow --delete myShadow --force", pcmk_option_example}, - {"-spacer-", 1, 0, '-', "Upload the current shadow configuration (named myShadow) to the running cluster:", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', " crm_shadow --commit myShadow", pcmk_option_example}, - - {0, 0, 0, 0} +static pcmk__cli_option_t long_options[] = { + // long option, argument type, storage, short option, description, flags + { + "help", no_argument, NULL, '?', + "\t\tThis text", pcmk__option_default + }, + { + "version", no_argument, NULL, '$', + "\t\tVersion information", pcmk__option_default + }, + { + "verbose", no_argument, NULL, 'V', + "\t\tIncrease debug output", pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nQueries:", pcmk__option_default + }, + { + "which", no_argument, NULL, 'w', + "\t\tIndicate the active shadow copy", pcmk__option_default + }, + { + "display", no_argument, NULL, 'p', + "\t\tDisplay the contents of the active shadow copy", + pcmk__option_default + }, + { + "edit", no_argument, NULL, 'E', + "\t\tEdit the contents of the active shadow copy with your " + "favorite $EDITOR", + pcmk__option_default + }, + { + "diff", no_argument, NULL, 'd', + "\t\tDisplay the changes in the active shadow copy\n", + pcmk__option_default + }, + { + "file", no_argument, NULL, 'F', + "\t\tDisplay the location of the active shadow copy file\n", + pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nCommands:", pcmk__option_default + }, + { + "create", required_argument, NULL, 'c', + "\tCreate the named shadow copy of the active cluster configuration", + pcmk__option_default + }, + { + "create-empty", required_argument, NULL, 'e', + "Create the named shadow copy with an empty cluster configuration. " + "Optional: --validate-with", + pcmk__option_default + }, + { + "commit", required_argument, NULL, 'C', + "\tUpload the contents of the named shadow copy to the cluster", + pcmk__option_default + }, + { + "delete", required_argument, NULL, 'D', + "\tDelete the contents of the named shadow copy", pcmk__option_default + }, + { + "reset", required_argument, NULL, 'r', + "\tRecreate named shadow copy from the active cluster configuration", + pcmk__option_default + }, + { + "switch", required_argument, NULL, 's', + "\t(Advanced) Switch to the named shadow copy", pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nAdditional Options:", pcmk__option_default + }, + { + "force", no_argument, NULL, 'f', + "\t\t(Advanced) Force the action to be performed", pcmk__option_default + }, + { + "batch", no_argument, NULL, 'b', + "\t\t(Advanced) Don't spawn a new shell", pcmk__option_default + }, + { + "all", no_argument, NULL, 'a', + "\t\t(Advanced) Upload entire CIB, including status, with --commit", + pcmk__option_default + }, + { + "validate-with", required_argument, NULL, 'v', + "(Advanced) Create an older configuration version", pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nExamples:", pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + "Create a blank shadow configuration:", pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_shadow --create-empty myShadow", pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Create a shadow configuration from the running cluster:", + pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_shadow --create myShadow", pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Display the current shadow configuration:", pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_shadow --display", pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Discard the current shadow configuration (named myShadow):", + pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_shadow --delete myShadow --force", pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Upload current shadow configuration (named myShadow) " + "to running cluster:", + pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_shadow --commit myShadow", pcmk__option_example + }, + { 0, 0, 0, 0 } }; -/* *INDENT-ON* */ int main(int argc, char **argv) { int rc = pcmk_ok; int flag; int argerr = 0; crm_exit_t exit_code = CRM_EX_OK; static int command = '?'; const char *validation = NULL; char *shadow = NULL; char *shadow_file = NULL; gboolean full_upload = FALSE; gboolean dangerous_cmd = FALSE; struct stat buf; int option_index = 0; crm_log_cli_init("crm_shadow"); - crm_set_options(NULL, "(query|command) [modifiers]", long_options, - "Perform configuration changes in a sandbox before updating the live cluster." - "\n\nSets up an environment in which configuration tools (cibadmin, crm_resource, etc) work" - " offline instead of against a live cluster, allowing changes to be previewed and tested" - " for side-effects.\n"); + pcmk__set_cli_options(NULL, "| [options]", long_options, + "perform Pacemaker configuration changes in a sandbox" + "\n\nThis command sets up an environment in which " + "configuration tools (cibadmin,\ncrm_resource, " + "etc.) work offline instead of against a live " + "cluster, allowing\nchanges to be previewed and " + "tested for side-effects.\n"); if (argc < 2) { - crm_help('?', CRM_EX_USAGE); + pcmk__cli_help('?', CRM_EX_USAGE); } while (1) { - flag = crm_get_option(argc, argv, &option_index); + flag = pcmk__next_cli_option(argc, argv, &option_index, NULL); if (flag == -1 || flag == 0) break; switch (flag) { case 'a': full_upload = TRUE; break; case 'd': case 'E': case 'p': case 'w': case 'F': command = flag; free(shadow); shadow = NULL; { const char *env = getenv("CIB_shadow"); if(env) { shadow = strdup(env); } else { fprintf(stderr, "No active shadow configuration defined\n"); crm_exit(CRM_EX_NOSUCH); } } break; case 'v': validation = optarg; break; case 'e': case 'c': case 's': case 'r': command = flag; free(shadow); shadow = strdup(optarg); break; case 'C': case 'D': command = flag; dangerous_cmd = TRUE; free(shadow); shadow = strdup(optarg); break; case 'V': command_options = command_options | cib_verbose; crm_bump_log_level(argc, argv); break; case '$': case '?': - crm_help(flag, CRM_EX_OK); + pcmk__cli_help(flag, CRM_EX_OK); break; case 'f': command_options |= cib_quorum_override; force_flag = 1; break; case 'b': batch_flag = 1; break; default: printf("Argument code 0%o (%c)" " is not (?yet?) supported\n", flag, flag); ++argerr; break; } } if (optind < argc) { printf("non-option ARGV-elements: "); while (optind < argc) printf("%s ", argv[optind++]); printf("\n"); - crm_help('?', CRM_EX_USAGE); + pcmk__cli_help('?', CRM_EX_USAGE); } if (optind > argc) { ++argerr; } if (argerr) { - crm_help('?', CRM_EX_USAGE); + pcmk__cli_help('?', CRM_EX_USAGE); } if (command == 'w') { /* which shadow instance is active? */ const char *local = getenv("CIB_shadow"); if (local == NULL) { fprintf(stderr, "No shadow instance provided\n"); exit_code = CRM_EX_NOSUCH; } else { fprintf(stdout, "%s\n", local); } goto done; } if (shadow == NULL) { fprintf(stderr, "No shadow instance provided\n"); fflush(stderr); exit_code = CRM_EX_NOSUCH; goto done; } else if (command != 's' && command != 'c') { const char *local = getenv("CIB_shadow"); if (local != NULL && safe_str_neq(local, shadow) && force_flag == FALSE) { fprintf(stderr, "The supplied shadow instance (%s) is not the same as the active one (%s).\n" " To prevent accidental destruction of the cluster," " the --force flag is required in order to proceed.\n", shadow, local); fflush(stderr); exit_code = CRM_EX_USAGE; goto done; } } if (dangerous_cmd && force_flag == FALSE) { fprintf(stderr, "The supplied command is considered dangerous." " To prevent accidental destruction of the cluster," " the --force flag is required in order to proceed.\n"); fflush(stderr); exit_code = CRM_EX_USAGE; goto done; } shadow_file = get_shadow_file(shadow); if (command == 'D') { /* delete the file */ if ((unlink(shadow_file) < 0) && (errno != ENOENT)) { exit_code = crm_errno2exit(errno); fprintf(stderr, "Could not remove shadow instance '%s': %s\n", shadow, strerror(errno)); } shadow_teardown(shadow); goto done; } else if (command == 'F') { printf("%s\n", shadow_file); goto done; } if (command == 'd' || command == 'r' || command == 'c' || command == 'C') { real_cib = cib_new_no_shadow(); rc = real_cib->cmds->signon(real_cib, crm_system_name, cib_command); if (rc != pcmk_ok) { fprintf(stderr, "Signon to CIB failed: %s\n", pcmk_strerror(rc)); exit_code = crm_errno2exit(rc); goto done; } } // File existence check rc = stat(shadow_file, &buf); if (command == 'e' || command == 'c') { if (rc == 0 && force_flag == FALSE) { fprintf(stderr, "A shadow instance '%s' already exists.\n" " To prevent accidental destruction of the cluster," " the --force flag is required in order to proceed.\n", shadow); exit_code = CRM_EX_CANTCREAT; goto done; } } else if (rc < 0) { fprintf(stderr, "Could not access shadow instance '%s': %s\n", shadow, strerror(errno)); exit_code = CRM_EX_NOSUCH; goto done; } if (command == 'c' || command == 'e' || command == 'r') { xmlNode *output = NULL; /* create a shadow instance based on the current cluster config */ if (command == 'c' || command == 'r') { rc = real_cib->cmds->query(real_cib, NULL, &output, command_options); if (rc != pcmk_ok) { fprintf(stderr, "Could not connect to the CIB manager: %s\n", pcmk_strerror(rc)); exit_code = crm_errno2exit(rc); goto done; } } else { output = createEmptyCib(0); if(validation) { crm_xml_add(output, XML_ATTR_VALIDATION, validation); } printf("Created new %s configuration\n", crm_element_value(output, XML_ATTR_VALIDATION)); } rc = write_xml_file(output, shadow_file, FALSE); free_xml(output); if (rc < 0) { fprintf(stderr, "Could not %s the shadow instance '%s': %s\n", command == 'r' ? "reset" : "create", shadow, pcmk_strerror(rc)); exit_code = crm_errno2exit(rc); goto done; } shadow_setup(shadow, FALSE); } else if (command == 'E') { char *editor = getenv("EDITOR"); if (editor == NULL) { fprintf(stderr, "No value for EDITOR defined\n"); exit_code = CRM_EX_NOT_CONFIGURED; goto done; } execlp(editor, "--", shadow_file, NULL); fprintf(stderr, "Could not invoke EDITOR (%s %s): %s\n", editor, shadow_file, strerror(errno)); exit_code = CRM_EX_OSFILE; goto done; } else if (command == 's') { shadow_setup(shadow, TRUE); goto done; } else if (command == 'p') { /* display the current contents */ char *output_s = NULL; xmlNode *output = filename2xml(shadow_file); output_s = dump_xml_formatted(output); printf("%s", output_s); free(output_s); free_xml(output); } else if (command == 'd') { /* diff against cluster */ xmlNode *diff = NULL; xmlNode *old_config = NULL; xmlNode *new_config = filename2xml(shadow_file); rc = real_cib->cmds->query(real_cib, NULL, &old_config, command_options); if (rc != pcmk_ok) { fprintf(stderr, "Could not query the CIB: %s\n", pcmk_strerror(rc)); exit_code = crm_errno2exit(rc); goto done; } xml_track_changes(new_config, NULL, new_config, FALSE); xml_calculate_changes(old_config, new_config); diff = xml_create_patchset(0, old_config, new_config, NULL, FALSE); xml_log_changes(LOG_INFO, __FUNCTION__, new_config); xml_accept_changes(new_config); if (diff != NULL) { xml_log_patchset(LOG_STDOUT, " ", diff); exit_code = CRM_EX_ERROR; } goto done; } else if (command == 'C') { /* commit to the cluster */ xmlNode *input = filename2xml(shadow_file); xmlNode *section_xml = input; const char *section = NULL; if (!full_upload) { section = XML_CIB_TAG_CONFIGURATION; section_xml = first_named_child(input, section); } rc = real_cib->cmds->replace(real_cib, section, section_xml, command_options); if (rc != pcmk_ok) { fprintf(stderr, "Could not commit shadow instance '%s' to the CIB: %s\n", shadow, pcmk_strerror(rc)); exit_code = crm_errno2exit(rc); } shadow_teardown(shadow); free_xml(input); } done: free(shadow_file); free(shadow); crm_exit(exit_code); } diff --git a/tools/crm_simulate.c b/tools/crm_simulate.c index 64931bddef..174260c6d6 100644 --- a/tools/crm_simulate.c +++ b/tools/crm_simulate.c @@ -1,942 +1,1109 @@ /* * Copyright 2009-2020 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 cib_t *global_cib = NULL; GListPtr op_fail = NULL; bool action_numbers = FALSE; gboolean quiet = FALSE; gboolean print_pending = TRUE; char *temp_shadow = NULL; extern gboolean bringing_nodes_online; #define quiet_log(fmt, args...) do { \ if(quiet == FALSE) { \ printf(fmt , ##args); \ } \ } while(0) char *use_date = NULL; static void get_date(pe_working_set_t *data_set, bool print_original) { time_t original_date = 0; crm_element_value_epoch(data_set->input, "execution-date", &original_date); if (use_date) { data_set->now = crm_time_new(use_date); quiet_log(" + Setting effective cluster time: %s", use_date); crm_time_log(LOG_NOTICE, "Pretending 'now' is", data_set->now, crm_time_log_date | crm_time_log_timeofday); } else if (original_date) { data_set->now = crm_time_new(NULL); crm_time_set_timet(data_set->now, &original_date); if (print_original) { char *when = crm_time_as_string(data_set->now, crm_time_log_date|crm_time_log_timeofday); printf("Using the original execution date of: %s\n", when); free(when); } } } static void print_cluster_status(pe_working_set_t * data_set, long options) { char *online_nodes = NULL; char *online_remote_nodes = NULL; char *online_guest_nodes = NULL; char *offline_nodes = NULL; char *offline_remote_nodes = NULL; GListPtr gIter = NULL; for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) { node_t *node = (node_t *) gIter->data; const char *node_mode = NULL; char *node_name = NULL; if (pe__is_guest_node(node)) { node_name = crm_strdup_printf("%s:%s", node->details->uname, node->details->remote_rsc->container->id); } else { node_name = crm_strdup_printf("%s", node->details->uname); } if (node->details->unclean) { if (node->details->online && node->details->unclean) { node_mode = "UNCLEAN (online)"; } else if (node->details->pending) { node_mode = "UNCLEAN (pending)"; } else { node_mode = "UNCLEAN (offline)"; } } else if (node->details->pending) { node_mode = "pending"; } else if (node->details->standby_onfail && node->details->online) { node_mode = "standby (on-fail)"; } else if (node->details->standby) { if (node->details->online) { node_mode = "standby"; } else { node_mode = "OFFLINE (standby)"; } } else if (node->details->maintenance) { if (node->details->online) { node_mode = "maintenance"; } else { node_mode = "OFFLINE (maintenance)"; } } else if (node->details->online) { if (pe__is_guest_node(node)) { online_guest_nodes = pcmk__add_word(online_guest_nodes, node_name); } else if (pe__is_remote_node(node)) { online_remote_nodes = pcmk__add_word(online_remote_nodes, node_name); } else { online_nodes = pcmk__add_word(online_nodes, node_name); } free(node_name); continue; } else { if (pe__is_remote_node(node)) { offline_remote_nodes = pcmk__add_word(offline_remote_nodes, node_name); } else if (pe__is_guest_node(node)) { /* ignore offline container nodes */ } else { offline_nodes = pcmk__add_word(offline_nodes, node_name); } free(node_name); continue; } if (pe__is_guest_node(node)) { printf("GuestNode %s: %s\n", node_name, node_mode); } else if (pe__is_remote_node(node)) { printf("RemoteNode %s: %s\n", node_name, node_mode); } else if (safe_str_eq(node->details->uname, node->details->id)) { printf("Node %s: %s\n", node_name, node_mode); } else { printf("Node %s (%s): %s\n", node_name, node->details->id, node_mode); } free(node_name); } if (online_nodes) { printf("Online: [%s ]\n", online_nodes); free(online_nodes); } if (offline_nodes) { printf("OFFLINE: [%s ]\n", offline_nodes); free(offline_nodes); } if (online_remote_nodes) { printf("RemoteOnline: [%s ]\n", online_remote_nodes); free(online_remote_nodes); } if (offline_remote_nodes) { printf("RemoteOFFLINE: [%s ]\n", offline_remote_nodes); free(offline_remote_nodes); } if (online_guest_nodes) { printf("GuestOnline: [%s ]\n", online_guest_nodes); free(online_guest_nodes); } fprintf(stdout, "\n"); for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) { resource_t *rsc = (resource_t *) gIter->data; if (is_set(rsc->flags, pe_rsc_orphan) && rsc->role == RSC_ROLE_STOPPED) { continue; } rsc->fns->print(rsc, NULL, pe_print_printf | options, stdout); } fprintf(stdout, "\n"); } static char * create_action_name(pe_action_t *action) { char *action_name = NULL; const char *prefix = ""; const char *action_host = NULL; const char *clone_name = NULL; const char *task = action->task; if (action->node) { action_host = action->node->details->uname; } else if (is_not_set(action->flags, pe_action_pseudo)) { action_host = ""; } if (safe_str_eq(action->task, RSC_CANCEL)) { prefix = "Cancel "; task = action->cancel_task; } if (action->rsc && action->rsc->clone_name) { clone_name = action->rsc->clone_name; } if (clone_name) { char *key = NULL; guint interval_ms = 0; if (pcmk__guint_from_hash(action->meta, XML_LRM_ATTR_INTERVAL_MS, 0, &interval_ms) != pcmk_rc_ok) { interval_ms = 0; } if (safe_str_eq(action->task, RSC_NOTIFY) || safe_str_eq(action->task, RSC_NOTIFIED)) { const char *n_type = g_hash_table_lookup(action->meta, "notify_key_type"); const char *n_task = g_hash_table_lookup(action->meta, "notify_key_operation"); CRM_ASSERT(n_type != NULL); CRM_ASSERT(n_task != NULL); key = generate_notify_key(clone_name, n_type, n_task); } else { key = pcmk__op_key(clone_name, task, interval_ms); } if (action_host) { action_name = crm_strdup_printf("%s%s %s", prefix, key, action_host); } else { action_name = crm_strdup_printf("%s%s", prefix, key); } free(key); } else if (safe_str_eq(action->task, CRM_OP_FENCE)) { const char *op = g_hash_table_lookup(action->meta, "stonith_action"); action_name = crm_strdup_printf("%s%s '%s' %s", prefix, action->task, op, action_host); } else if (action->rsc && action_host) { action_name = crm_strdup_printf("%s%s %s", prefix, action->uuid, action_host); } else if (action_host) { action_name = crm_strdup_printf("%s%s %s", prefix, action->task, action_host); } else { action_name = crm_strdup_printf("%s", action->uuid); } if (action_numbers) { // i.e. verbose char *with_id = crm_strdup_printf("%s (%d)", action_name, action->id); free(action_name); action_name = with_id; } return action_name; } static void create_dotfile(pe_working_set_t * data_set, const char *dot_file, gboolean all_actions) { GListPtr gIter = NULL; FILE *dot_strm = fopen(dot_file, "w"); if (dot_strm == NULL) { crm_perror(LOG_ERR, "Could not open %s for writing", dot_file); return; } fprintf(dot_strm, " digraph \"g\" {\n"); for (gIter = data_set->actions; gIter != NULL; gIter = gIter->next) { action_t *action = (action_t *) gIter->data; const char *style = "dashed"; const char *font = "black"; const char *color = "black"; char *action_name = create_action_name(action); crm_trace("Action %d: %s %s %p", action->id, action_name, action->uuid, action); if (is_set(action->flags, pe_action_pseudo)) { font = "orange"; } if (is_set(action->flags, pe_action_dumped)) { style = "bold"; color = "green"; } else if (action->rsc != NULL && is_not_set(action->rsc->flags, pe_rsc_managed)) { color = "red"; font = "purple"; if (all_actions == FALSE) { goto do_not_write; } } else if (is_set(action->flags, pe_action_optional)) { color = "blue"; if (all_actions == FALSE) { goto do_not_write; } } else { color = "red"; CRM_CHECK(is_set(action->flags, pe_action_runnable) == FALSE,; ); } set_bit(action->flags, pe_action_dumped); crm_trace("\"%s\" [ style=%s color=\"%s\" fontcolor=\"%s\"]", action_name, style, color, font); fprintf(dot_strm, "\"%s\" [ style=%s color=\"%s\" fontcolor=\"%s\"]\n", action_name, style, color, font); do_not_write: free(action_name); } for (gIter = data_set->actions; gIter != NULL; gIter = gIter->next) { action_t *action = (action_t *) gIter->data; GListPtr gIter2 = NULL; for (gIter2 = action->actions_before; gIter2 != NULL; gIter2 = gIter2->next) { action_wrapper_t *before = (action_wrapper_t *) gIter2->data; char *before_name = NULL; char *after_name = NULL; const char *style = "dashed"; gboolean optional = TRUE; if (before->state == pe_link_dumped) { optional = FALSE; style = "bold"; } else if (is_set(action->flags, pe_action_pseudo) && (before->type & pe_order_stonith_stop)) { continue; } else if (before->type == pe_order_none) { continue; } else if (is_set(before->action->flags, pe_action_dumped) && is_set(action->flags, pe_action_dumped) && before->type != pe_order_load) { optional = FALSE; } if (all_actions || optional == FALSE) { before_name = create_action_name(before->action); after_name = create_action_name(action); crm_trace("\"%s\" -> \"%s\" [ style = %s]", before_name, after_name, style); fprintf(dot_strm, "\"%s\" -> \"%s\" [ style = %s]\n", before_name, after_name, style); free(before_name); free(after_name); } } } fprintf(dot_strm, "}\n"); fflush(dot_strm); fclose(dot_strm); } static void setup_input(const char *input, const char *output) { int rc = pcmk_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); if (rc == pcmk_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; if (rc != pcmk_ok) { fprintf(stderr, "Live CIB query failed: %s (%d)\n", pcmk_strerror(rc), rc); crm_exit(crm_errno2exit(rc)); } else if (cib_object == NULL) { fprintf(stderr, "Live CIB query failed: empty result\n"); crm_exit(CRM_EX_NOINPUT); } } else if (safe_str_eq(input, "-")) { 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); crm_exit(CRM_EX_CONFIG); } if (validate_xml(cib_object, NULL, FALSE) != TRUE) { free_xml(cib_object); crm_exit(CRM_EX_CONFIG); } if (output == NULL) { char *pid = crm_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) { fprintf(stderr, "Could not create '%s': %s\n", output, pcmk_strerror(rc)); crm_exit(CRM_EX_CANTCREAT); } setenv("CIB_file", output, 1); free(local_output); } -/* *INDENT-OFF* */ -static struct crm_option long_options[] = { - /* Top-level Options */ - {"help", 0, 0, '?', "\tThis text"}, - {"version", 0, 0, '$', "\tVersion information" }, - {"quiet", 0, 0, 'Q', "\tDisplay only essentialoutput"}, - {"verbose", 0, 0, 'V', "\tIncrease debug output"}, - - {"-spacer-", 0, 0, '-', "\nOperations:"}, - {"run", 0, 0, 'R', "\tDetermine the cluster's response to the given configuration and status"}, - {"simulate", 0, 0, 'S', "Simulate the transition's execution and display the resulting cluster status"}, - {"in-place", 0, 0, 'X', "Simulate the transition's execution and store the result back to the input file"}, - {"show-scores", 0, 0, 's', "Show allocation scores"}, - {"show-utilization", 0, 0, 'U', "Show utilization information"}, - {"profile", 1, 0, 'P', "Run all tests in the named directory to create profiling data"}, - {"repeat", 1, 0, 'N', "With --profile, repeat each test N times and print timings"}, - {"pending", 0, 0, 'j', "\tDisplay pending state if 'record-pending' is enabled", pcmk_option_hidden}, - - {"-spacer-", 0, 0, '-', "\nSynthetic Cluster Events:"}, - {"node-up", 1, 0, 'u', "\tBring a node online"}, - {"node-down", 1, 0, 'd', "\tTake a node offline"}, - {"node-fail", 1, 0, 'f', "\tMark a node as failed"}, - {"op-inject", 1, 0, 'i', "\tGenerate a failure for the cluster to react to in the simulation"}, - {"-spacer-", 0, 0, '-', "\t\tValue is of the form ${resource}_${task}_${interval_in_ms}@${node}=${rc}."}, - {"-spacer-", 0, 0, '-', "\t\tEg. memcached_monitor_20000@bart.example.com=7"}, - {"-spacer-", 0, 0, '-', "\t\tFor more information on OCF return codes, refer to: https://clusterlabs.org/pacemaker/doc/en-US/Pacemaker/2.0/html/Pacemaker_Administration/s-ocf-return-codes.html"}, - {"op-fail", 1, 0, 'F', "\tIf the specified task occurs during the simulation, have it fail with return code ${rc}"}, - {"-spacer-", 0, 0, '-', "\t\tValue is of the form ${resource}_${task}_${interval_in_ms}@${node}=${rc}."}, - {"-spacer-", 0, 0, '-', "\t\tEg. memcached_stop_0@bart.example.com=1\n"}, - {"-spacer-", 0, 0, '-', "\t\tThe transition will normally stop at the failed action. Save the result with --save-output and re-run with --xml-file"}, +static pcmk__cli_option_t long_options[] = { + // long option, argument type, storage, short option, description, flags + { + "help", no_argument, NULL, '?', + "\tThis text", pcmk__option_default + }, + { + "version", no_argument, NULL, '$', + "\tVersion information", pcmk__option_default + }, + { + "quiet", no_argument, NULL, 'Q', + "\tDisplay only essential output", pcmk__option_default + }, + { + "verbose", no_argument, NULL, 'V', + "\tIncrease debug output", pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nOperations:", pcmk__option_default + }, + { + "run", no_argument, NULL, 'R', + "\tDetermine cluster's response to the given configuration and status", + pcmk__option_default + }, + { + "simulate", no_argument, NULL, 'S', + "Simulate transition's execution and display resulting cluster status", + pcmk__option_default + }, + { + "in-place", no_argument, NULL, 'X', + "Simulate transition's execution and store result back to input file", + pcmk__option_default + }, + { + "show-scores", no_argument, NULL, 's', + "Show allocation scores", pcmk__option_default + }, + { + "show-utilization", no_argument, NULL, 'U', + "Show utilization information", pcmk__option_default + }, + { + "profile", required_argument, NULL, 'P', + "Run all tests in the named directory to create profiling data", + pcmk__option_default + }, + { + "repeat", required_argument, NULL, 'N', + "With --profile, repeat each test N times and print timings", + pcmk__option_default + }, + { + "pending", no_argument, NULL, 'j', + "\tDisplay pending state if 'record-pending' is enabled", + pcmk__option_hidden + }, + { + "-spacer-", no_argument, NULL, '-', + "\nSynthetic Cluster Events:", pcmk__option_default + }, + { + "node-up", required_argument, NULL, 'u', + "\tBring a node online", pcmk__option_default + }, + { + "node-down", required_argument, NULL, 'd', + "\tTake a node offline", pcmk__option_default + }, + { + "node-fail", required_argument, NULL, 'f', + "\tMark a node as failed", pcmk__option_default + }, + { + "op-inject", required_argument, NULL, 'i', + "\tGenerate a failure for the cluster to react to in the simulation", + pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\t\tValue is of the form " + "${resource}_${task}_${interval_in_ms}@${node}=${rc}.", + pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\t\t(for example, memcached_monitor_20000@bart.example.com=7)", + pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\t\tFor more information on OCF return codes, refer to: " + "https://clusterlabs.org/pacemaker/doc/en-US/Pacemaker/" + "2.0/html/Pacemaker_Administration/s-ocf-return-codes.html", + pcmk__option_default + }, + { + "op-fail", required_argument, NULL, 'F', + "\tIf the specified task occurs during the simulation, have it fail " + "with return code ${rc}", + pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\t\tValue is of the form " + "${resource}_${task}_${interval_in_ms}@${node}=${rc}.", + pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\t\t(for example, memcached_stop_0@bart.example.com=1)\n", + pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\t\tThe transition will normally stop at the failed action. Save " + "the result with --save-output and re-run with --xml-file", + pcmk__option_default + }, { "set-datetime", required_argument, NULL, 't', - "Set date/time (ISO 8601 format, see https://en.wikipedia.org/wiki/ISO_8601)" - }, - {"quorum", 1, 0, 'q', "\tSpecify a value for quorum"}, - {"watchdog", 1, 0, 'w', "\tAssume a watchdog device is active"}, - {"ticket-grant", 1, 0, 'g', "Grant a ticket"}, - {"ticket-revoke", 1, 0, 'r', "Revoke a ticket"}, - {"ticket-standby", 1, 0, 'b', "Make a ticket standby"}, - {"ticket-activate", 1, 0, 'e', "Activate a ticket"}, - - {"-spacer-", 0, 0, '-', "\nOutput Options:"}, - - {"save-input", 1, 0, 'I', "\tSave the input configuration to the named file"}, - {"save-output", 1, 0, 'O', "Save the output configuration to the named file"}, - {"save-graph", 1, 0, 'G', "\tSave the transition graph (XML format) to the named file"}, - {"save-dotfile", 1, 0, 'D', "Save the transition graph (DOT format) to the named file"}, - {"all-actions", 0, 0, 'a', "\tDisplay all possible actions in the DOT graph - even ones not part of the transition"}, - - {"-spacer-", 0, 0, '-', "\nData Source:"}, - {"live-check", 0, 0, 'L', "\tConnect to the CIB mamager and use the current CIB contents as input"}, - {"xml-file", 1, 0, 'x', "\tRetrieve XML from the named file"}, - {"xml-pipe", 0, 0, 'p', "\tRetrieve XML from stdin"}, - - {"-spacer-", 0, 0, '-', "\nExamples:\n"}, - {"-spacer-", 0, 0, '-', "Pretend a recurring monitor action found memcached stopped on node fred.example.com and, during recovery, that the memcached stop action failed", pcmk_option_paragraph}, - {"-spacer-", 0, 0, '-', " crm_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", pcmk_option_example}, - {"-spacer-", 0, 0, '-', "Now see what the reaction to the stop failure would be", pcmk_option_paragraph}, - {"-spacer-", 0, 0, '-', " crm_simulate -S --xml-file /tmp/memcached-test.xml", pcmk_option_example}, - - {0, 0, 0, 0} + "Set date/time (ISO 8601 format, see " + "https://en.wikipedia.org/wiki/ISO_8601)", + pcmk__option_default + }, + { + "quorum", required_argument, NULL, 'q', + "\tSpecify a value for quorum", pcmk__option_default + }, + { + "watchdog", required_argument, NULL, 'w', + "\tAssume a watchdog device is active", pcmk__option_default + }, + { + "ticket-grant", required_argument, NULL, 'g', + "Grant a ticket", pcmk__option_default + }, + { + "ticket-revoke", required_argument, NULL, 'r', + "Revoke a ticket", pcmk__option_default + }, + { + "ticket-standby", required_argument, NULL, 'b', + "Make a ticket standby", pcmk__option_default + }, + { + "ticket-activate", required_argument, NULL, 'e', + "Activate a ticket", pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nOutput Options:", pcmk__option_default + }, + { + "save-input", required_argument, NULL, 'I', + "\tSave the input configuration to the named file", pcmk__option_default + }, + { + "save-output", required_argument, NULL, 'O', + "Save the output configuration to the named file", pcmk__option_default + }, + { + "save-graph", required_argument, NULL, 'G', + "\tSave the transition graph (XML format) to the named file", + pcmk__option_default + }, + { + "save-dotfile", required_argument, NULL, 'D', + "Save the transition graph (DOT format) to the named file", + pcmk__option_default + }, + { + "all-actions", no_argument, NULL, 'a', + "\tDisplay all possible actions in DOT graph (even if not part " + "of transition)", + pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nData Source:", pcmk__option_default + }, + { + "live-check", no_argument, NULL, 'L', + "\tConnect to CIB mamager and use the current CIB contents as input", + pcmk__option_default + }, + { + "xml-file", required_argument, NULL, 'x', + "\tRetrieve XML from the named file", pcmk__option_default + }, + { + "xml-pipe", no_argument, NULL, 'p', + "\tRetrieve XML from stdin", pcmk__option_default + }, + + { + "-spacer-", no_argument, NULL, '-', + "\nExamples:\n", pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "Pretend a recurring monitor action found memcached stopped on node " + "fred.example.com and, during recovery, that the memcached stop " + "action failed", + pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_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", + pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Now see what the reaction to the stop failure would be", + pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_simulate -S --xml-file /tmp/memcached-test.xml", + pcmk__option_example + }, + { 0, 0, 0, 0 } }; -/* *INDENT-ON* */ static void profile_one(const char *xml_file, long long repeat, pe_working_set_t *data_set) { xmlNode *cib_object = NULL; clock_t start = 0; printf("* Testing %s ...", xml_file); fflush(stdout); cib_object = filename2xml(xml_file); start = clock(); 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; } if (validate_xml(cib_object, NULL, FALSE) != TRUE) { free_xml(cib_object); return; } for (int i = 0; i < repeat; ++i) { xmlNode *input = (repeat == 1)? cib_object : copy_xml(cib_object); data_set->input = input; get_date(data_set, false); pcmk__schedule_actions(data_set, input, NULL); pe_reset_working_set(data_set); } printf(" %.2f secs\n", (clock() - start) / (float) CLOCKS_PER_SEC); } #ifndef FILENAME_MAX # define FILENAME_MAX 512 #endif static void profile_all(const char *dir, long long repeat, pe_working_set_t *data_set) { struct dirent **namelist; int file_num = scandir(dir, &namelist, 0, alphasort); if (file_num > 0) { struct stat prop; char buffer[FILENAME_MAX]; while (file_num--) { if ('.' == namelist[file_num]->d_name[0]) { free(namelist[file_num]); continue; } else if (!pcmk__ends_with_ext(namelist[file_num]->d_name, ".xml")) { free(namelist[file_num]); continue; } snprintf(buffer, sizeof(buffer), "%s/%s", dir, namelist[file_num]->d_name); if (stat(buffer, &prop) == 0 && S_ISREG(prop.st_mode)) { profile_one(buffer, repeat, data_set); } free(namelist[file_num]); } free(namelist); } } int main(int argc, char **argv) { int rc = pcmk_ok; guint modified = 0; gboolean store = FALSE; gboolean process = FALSE; gboolean simulate = FALSE; gboolean all_actions = FALSE; gboolean have_stdout = FALSE; pe_working_set_t *data_set = NULL; const char *xml_file = "-"; const char *quorum = NULL; const char *watchdog = NULL; const char *test_dir = NULL; const char *dot_file = NULL; const char *graph_file = NULL; const char *input_file = NULL; const char *output_file = NULL; const char *repeat_s = NULL; int flag = 0; int index = 0; int argerr = 0; long long repeat = 1; GListPtr node_up = NULL; GListPtr node_down = NULL; GListPtr node_fail = NULL; GListPtr op_inject = NULL; GListPtr ticket_grant = NULL; GListPtr ticket_revoke = NULL; GListPtr ticket_standby = NULL; GListPtr ticket_activate = NULL; xmlNode *input = NULL; crm_log_cli_init("crm_simulate"); - crm_set_options(NULL, "datasource operation [additional options]", - long_options, "Tool for simulating the cluster's response to events"); + pcmk__set_cli_options(NULL, " [options]", + long_options, + "simulate a Pacemaker cluster's response to events"); if (argc < 2) { - crm_help('?', CRM_EX_USAGE); + pcmk__cli_help('?', CRM_EX_USAGE); } while (1) { - flag = crm_get_option(argc, argv, &index); + flag = pcmk__next_cli_option(argc, argv, &index, NULL); if (flag == -1) break; switch (flag) { case 'V': if (have_stdout == FALSE) { /* Redirect stderr to stdout so we can grep the output */ have_stdout = TRUE; close(STDERR_FILENO); dup2(STDOUT_FILENO, STDERR_FILENO); } crm_bump_log_level(argc, argv); action_numbers = TRUE; break; case '?': case '$': - crm_help(flag, CRM_EX_OK); + pcmk__cli_help(flag, CRM_EX_OK); break; case 'p': xml_file = "-"; break; case 'Q': quiet = TRUE; break; case 'L': xml_file = NULL; break; case 'x': xml_file = optarg; break; case 'u': modified++; bringing_nodes_online = TRUE; node_up = g_list_append(node_up, optarg); break; case 'd': modified++; node_down = g_list_append(node_down, optarg); break; case 'f': modified++; node_fail = g_list_append(node_fail, optarg); break; case 't': use_date = strdup(optarg); break; case 'i': modified++; op_inject = g_list_append(op_inject, optarg); break; case 'F': process = TRUE; simulate = TRUE; op_fail = g_list_append(op_fail, optarg); break; case 'w': modified++; watchdog = optarg; break; case 'q': modified++; quorum = optarg; break; case 'g': modified++; ticket_grant = g_list_append(ticket_grant, optarg); break; case 'r': modified++; ticket_revoke = g_list_append(ticket_revoke, optarg); break; case 'b': modified++; ticket_standby = g_list_append(ticket_standby, optarg); break; case 'e': modified++; ticket_activate = g_list_append(ticket_activate, optarg); break; case 'a': all_actions = TRUE; break; case 's': process = TRUE; show_scores = TRUE; break; case 'U': process = TRUE; show_utilization = TRUE; break; case 'j': print_pending = TRUE; break; case 'S': process = TRUE; simulate = TRUE; break; case 'X': store = TRUE; process = TRUE; simulate = TRUE; break; case 'R': process = TRUE; break; case 'D': process = TRUE; dot_file = optarg; break; case 'G': process = TRUE; graph_file = optarg; break; case 'I': input_file = optarg; break; case 'O': output_file = optarg; break; case 'P': test_dir = optarg; break; case 'N': repeat_s = optarg; break; default: ++argerr; break; } } if (optind > argc) { ++argerr; } if (argerr) { - crm_help('?', CRM_EX_USAGE); + pcmk__cli_help('?', CRM_EX_USAGE); } data_set = pe_new_working_set(); if (data_set == NULL) { crm_perror(LOG_ERR, "Could not allocate working set"); rc = -ENOMEM; goto done; } set_bit(data_set->flags, pe_flag_no_compat); if (test_dir != NULL) { if (repeat_s != NULL) { repeat = crm_parse_ll(repeat_s, NULL); if (errno || (repeat < 1)) { fprintf(stderr, "--repeat must be positive integer, not '%s' -- using 1", repeat_s); repeat = 1; } } profile_all(test_dir, repeat, data_set); return CRM_EX_OK; } setup_input(xml_file, store ? xml_file : output_file); global_cib = cib_new(); rc = global_cib->cmds->signon(global_cib, crm_system_name, cib_command); if (rc != pcmk_ok) { fprintf(stderr, "Could not connect to the CIB manager: %s\n", pcmk_strerror(rc)); goto done; } rc = global_cib->cmds->query(global_cib, NULL, &input, cib_sync_call | cib_scope_local); if (rc != pcmk_ok) { fprintf(stderr, "Could not get local CIB: %s\n", pcmk_strerror(rc)); goto done; } data_set->input = input; get_date(data_set, true); if(xml_file) { set_bit(data_set->flags, pe_flag_sanitized); } set_bit(data_set->flags, pe_flag_stdout); cluster_status(data_set); if (quiet == FALSE) { int options = print_pending ? pe_print_pending : 0; if (is_set(data_set->flags, pe_flag_maintenance_mode)) { quiet_log("\n *** Resource management is DISABLED ***"); quiet_log("\n The cluster will not attempt to start, stop or recover services"); quiet_log("\n"); } if (data_set->disabled_resources || data_set->blocked_resources) { quiet_log("%d of %d resource instances DISABLED and %d BLOCKED " "from further action due to failure\n", data_set->disabled_resources, data_set->ninstances, data_set->blocked_resources); } quiet_log("\nCurrent cluster status:\n"); print_cluster_status(data_set, options); } if (modified) { quiet_log("Performing requested modifications\n"); modify_configuration(data_set, global_cib, quorum, watchdog, node_up, node_down, node_fail, op_inject, ticket_grant, ticket_revoke, ticket_standby, ticket_activate); rc = global_cib->cmds->query(global_cib, NULL, &input, cib_sync_call); if (rc != pcmk_ok) { fprintf(stderr, "Could not get modified CIB: %s\n", pcmk_strerror(rc)); goto done; } cleanup_calculations(data_set); data_set->input = input; get_date(data_set, true); if(xml_file) { set_bit(data_set->flags, pe_flag_sanitized); } set_bit(data_set->flags, pe_flag_stdout); cluster_status(data_set); } if (input_file != NULL) { rc = write_xml_file(input, input_file, FALSE); if (rc < 0) { fprintf(stderr, "Could not create '%s': %s\n", input_file, pcmk_strerror(rc)); goto done; } } if (process || simulate) { crm_time_t *local_date = NULL; if (show_scores && show_utilization) { printf("Allocation scores and utilization information:\n"); } else if (show_scores) { fprintf(stdout, "Allocation scores:\n"); } else if (show_utilization) { printf("Utilization information:\n"); } pcmk__schedule_actions(data_set, input, local_date); input = NULL; /* Don't try and free it twice */ if (graph_file != NULL) { write_xml_file(data_set->graph, graph_file, FALSE); } if (dot_file != NULL) { create_dotfile(data_set, dot_file, all_actions); } if (quiet == FALSE) { GListPtr gIter = NULL; quiet_log("%sTransition Summary:\n", show_scores || show_utilization || modified ? "\n" : ""); fflush(stdout); LogNodeActions(data_set, TRUE); for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) { resource_t *rsc = (resource_t *) gIter->data; LogActions(rsc, data_set, TRUE); } } } rc = pcmk_ok; if (simulate) { if (run_simulation(data_set, global_cib, op_fail, quiet) != pcmk_ok) { rc = pcmk_err_generic; } if(quiet == FALSE) { get_date(data_set, true); quiet_log("\nRevised cluster status:\n"); set_bit(data_set->flags, pe_flag_stdout); cluster_status(data_set); print_cluster_status(data_set, 0); } } done: pe_free_working_set(data_set); global_cib->cmds->signoff(global_cib); cib_delete(global_cib); free(use_date); fflush(stderr); if (temp_shadow) { unlink(temp_shadow); free(temp_shadow); } crm_exit(crm_errno2exit(rc)); } diff --git a/tools/crm_ticket.c b/tools/crm_ticket.c index c4adc715e3..10439d9b10 100644 --- a/tools/crm_ticket.c +++ b/tools/crm_ticket.c @@ -1,906 +1,1074 @@ /* * Copyright 2012-2020 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 gboolean do_force = FALSE; gboolean BE_QUIET = FALSE; const char *ticket_id = NULL; const char *get_attr_name = NULL; const char *attr_name = NULL; const char *attr_value = NULL; const char *attr_id = NULL; const char *set_name = NULL; const char *attr_default = NULL; char ticket_cmd = 'S'; char *xml_file = NULL; int cib_options = cib_sync_call; static ticket_t * find_ticket(const char *ticket_id, pe_working_set_t * data_set) { ticket_t *ticket = NULL; ticket = g_hash_table_lookup(data_set->tickets, ticket_id); return ticket; } static void print_date(time_t time) { int lpc = 0; char date_str[26]; asctime_r(localtime(&time), date_str); for (; lpc < 26; lpc++) { if (date_str[lpc] == '\n') { date_str[lpc] = 0; } } fprintf(stdout, "'%s'", date_str); } static int print_ticket(ticket_t * ticket, gboolean raw, gboolean details) { if (raw) { fprintf(stdout, "%s\n", ticket->id); return pcmk_ok; } fprintf(stdout, "%s\t%s %s", ticket->id, ticket->granted ? "granted" : "revoked", ticket->standby ? "[standby]" : " "); if (details && g_hash_table_size(ticket->state) > 0) { GHashTableIter iter; const char *name = NULL; const char *value = NULL; int lpc = 0; fprintf(stdout, " ("); g_hash_table_iter_init(&iter, ticket->state); while (g_hash_table_iter_next(&iter, (void **)&name, (void **)&value)) { if (lpc > 0) { fprintf(stdout, ", "); } fprintf(stdout, "%s=", name); if (crm_str_eq(name, "last-granted", TRUE) || crm_str_eq(name, "expires", TRUE)) { print_date(crm_parse_int(value, 0)); } else { fprintf(stdout, "%s", value); } lpc++; } fprintf(stdout, ")\n"); } else { if (ticket->last_granted > -1) { fprintf(stdout, " last-granted="); print_date(ticket->last_granted); } fprintf(stdout, "\n"); } return pcmk_ok; } static int print_ticket_list(pe_working_set_t * data_set, gboolean raw, gboolean details) { GHashTableIter iter; ticket_t *ticket = NULL; g_hash_table_iter_init(&iter, data_set->tickets); while (g_hash_table_iter_next(&iter, NULL, (void **)&ticket)) { print_ticket(ticket, raw, details); } return pcmk_ok; } #define XPATH_MAX 1024 static int find_ticket_state(cib_t * the_cib, const char *ticket_id, xmlNode ** ticket_state_xml) { int offset = 0; int rc = pcmk_ok; xmlNode *xml_search = NULL; char *xpath_string = NULL; CRM_ASSERT(ticket_state_xml != NULL); *ticket_state_xml = NULL; xpath_string = calloc(1, XPATH_MAX); offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "%s", "/cib/status/tickets"); if (ticket_id) { offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "/%s[@id=\"%s\"]", XML_CIB_TAG_TICKET_STATE, ticket_id); } CRM_LOG_ASSERT(offset > 0); rc = the_cib->cmds->query(the_cib, xpath_string, &xml_search, cib_sync_call | cib_scope_local | cib_xpath); if (rc != pcmk_ok) { goto bail; } crm_log_xml_debug(xml_search, "Match"); if (xml_has_children(xml_search)) { if (ticket_id) { fprintf(stdout, "Multiple ticket_states match ticket_id=%s\n", ticket_id); } *ticket_state_xml = xml_search; } else { *ticket_state_xml = xml_search; } bail: free(xpath_string); return rc; } static int find_ticket_constraints(cib_t * the_cib, const char *ticket_id, xmlNode ** ticket_cons_xml) { int offset = 0; int rc = pcmk_ok; xmlNode *xml_search = NULL; char *xpath_string = NULL; CRM_ASSERT(ticket_cons_xml != NULL); *ticket_cons_xml = NULL; xpath_string = calloc(1, XPATH_MAX); offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "%s/%s", get_object_path(XML_CIB_TAG_CONSTRAINTS), XML_CONS_TAG_RSC_TICKET); if (ticket_id) { offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "[@ticket=\"%s\"]", ticket_id); } CRM_LOG_ASSERT(offset > 0); rc = the_cib->cmds->query(the_cib, xpath_string, &xml_search, cib_sync_call | cib_scope_local | cib_xpath); if (rc != pcmk_ok) { goto bail; } crm_log_xml_debug(xml_search, "Match"); *ticket_cons_xml = xml_search; bail: free(xpath_string); return rc; } static int dump_ticket_xml(cib_t * the_cib, const char *ticket_id) { int rc = pcmk_ok; xmlNode *state_xml = NULL; rc = find_ticket_state(the_cib, ticket_id, &state_xml); if (state_xml == NULL) { return rc; } fprintf(stdout, "State XML:\n"); if (state_xml) { char *state_xml_str = NULL; state_xml_str = dump_xml_formatted(state_xml); fprintf(stdout, "\n%s\n", crm_str(state_xml_str)); free_xml(state_xml); free(state_xml_str); } return pcmk_ok; } static int dump_constraints(cib_t * the_cib, const char *ticket_id) { int rc = pcmk_ok; xmlNode *cons_xml = NULL; char *cons_xml_str = NULL; rc = find_ticket_constraints(the_cib, ticket_id, &cons_xml); if (cons_xml == NULL) { return rc; } cons_xml_str = dump_xml_formatted(cons_xml); fprintf(stdout, "Constraints XML:\n\n%s\n", crm_str(cons_xml_str)); free_xml(cons_xml); free(cons_xml_str); return pcmk_ok; } static int get_ticket_state_attr(const char *ticket_id, const char *attr_name, const char **attr_value, pe_working_set_t * data_set) { ticket_t *ticket = NULL; CRM_ASSERT(attr_value != NULL); *attr_value = NULL; ticket = g_hash_table_lookup(data_set->tickets, ticket_id); if (ticket == NULL) { return -ENXIO; } *attr_value = g_hash_table_lookup(ticket->state, attr_name); if (*attr_value == NULL) { return -ENXIO; } return pcmk_ok; } static gboolean ticket_warning(const char *ticket_id, const char *action) { gboolean rc = FALSE; int offset = 0; static int text_max = 1024; char *warning = NULL; const char *word = NULL; warning = calloc(1, text_max); if (safe_str_eq(action, "grant")) { offset += snprintf(warning + offset, text_max - offset, "This command cannot help you verify whether '%s' has been already granted elsewhere.\n", ticket_id); word = "to"; } else { offset += snprintf(warning + offset, text_max - offset, "Revoking '%s' can trigger the specified 'loss-policy'(s) relating to '%s'.\n\n", ticket_id, ticket_id); offset += snprintf(warning + offset, text_max - offset, "You can check that with:\ncrm_ticket --ticket %s --constraints\n\n", ticket_id); offset += snprintf(warning + offset, text_max - offset, "Otherwise before revoking '%s', you may want to make '%s' standby with:\ncrm_ticket --ticket %s --standby\n\n", ticket_id, ticket_id, ticket_id); word = "from"; } offset += snprintf(warning + offset, text_max - offset, "If you really want to %s '%s' %s this site now, and you know what you are doing,\n", action, ticket_id, word); offset += snprintf(warning + offset, text_max - offset, "please specify --force."); CRM_LOG_ASSERT(offset > 0); fprintf(stdout, "%s\n", warning); free(warning); return rc; } static gboolean allow_modification(const char *ticket_id, GListPtr attr_delete, GHashTable *attr_set) { const char *value = NULL; GListPtr list_iter = NULL; if (do_force) { return TRUE; } if (g_hash_table_lookup_extended(attr_set, "granted", NULL, (gpointer *) & value)) { if (crm_is_true(value)) { ticket_warning(ticket_id, "grant"); return FALSE; } else { ticket_warning(ticket_id, "revoke"); return FALSE; } } for(list_iter = attr_delete; list_iter; list_iter = list_iter->next) { const char *key = (const char *)list_iter->data; if (safe_str_eq(key, "granted")) { ticket_warning(ticket_id, "revoke"); return FALSE; } } return TRUE; } static int modify_ticket_state(const char * ticket_id, GListPtr attr_delete, GHashTable * attr_set, cib_t * cib, pe_working_set_t * data_set) { int rc = pcmk_ok; xmlNode *xml_top = NULL; xmlNode *ticket_state_xml = NULL; gboolean found = FALSE; GListPtr list_iter = NULL; GHashTableIter hash_iter; char *key = NULL; char *value = NULL; ticket_t *ticket = NULL; rc = find_ticket_state(cib, ticket_id, &ticket_state_xml); if (rc == pcmk_ok) { crm_debug("Found a match state for ticket: id=%s", ticket_id); xml_top = ticket_state_xml; found = TRUE; } else if (rc != -ENXIO) { return rc; } else if (g_hash_table_size(attr_set) == 0){ return pcmk_ok; } else { xmlNode *xml_obj = NULL; xml_top = create_xml_node(NULL, XML_CIB_TAG_STATUS); xml_obj = create_xml_node(xml_top, XML_CIB_TAG_TICKETS); ticket_state_xml = create_xml_node(xml_obj, XML_CIB_TAG_TICKET_STATE); crm_xml_add(ticket_state_xml, XML_ATTR_ID, ticket_id); } for(list_iter = attr_delete; list_iter; list_iter = list_iter->next) { const char *key = (const char *)list_iter->data; xml_remove_prop(ticket_state_xml, key); } ticket = find_ticket(ticket_id, data_set); g_hash_table_iter_init(&hash_iter, attr_set); while (g_hash_table_iter_next(&hash_iter, (gpointer *) & key, (gpointer *) & value)) { crm_xml_add(ticket_state_xml, key, value); if (safe_str_eq(key, "granted") && (ticket == NULL || ticket->granted == FALSE) && crm_is_true(value)) { char *now = crm_ttoa(time(NULL)); crm_xml_add(ticket_state_xml, "last-granted", now); free(now); } } if (found && (attr_delete != NULL)) { crm_log_xml_debug(xml_top, "Replace"); rc = cib->cmds->replace(cib, XML_CIB_TAG_STATUS, ticket_state_xml, cib_options); } else { crm_log_xml_debug(xml_top, "Update"); rc = cib->cmds->modify(cib, XML_CIB_TAG_STATUS, xml_top, cib_options); } free_xml(xml_top); return rc; } static int delete_ticket_state(const char *ticket_id, cib_t * cib) { xmlNode *ticket_state_xml = NULL; int rc = pcmk_ok; rc = find_ticket_state(cib, ticket_id, &ticket_state_xml); if (rc == -ENXIO) { return pcmk_ok; } else if (rc != pcmk_ok) { return rc; } crm_log_xml_debug(ticket_state_xml, "Delete"); rc = cib->cmds->remove(cib, XML_CIB_TAG_STATUS, ticket_state_xml, cib_options); if (rc == pcmk_ok) { fprintf(stdout, "Cleaned up %s\n", ticket_id); } free_xml(ticket_state_xml); return rc; } -/* *INDENT-OFF* */ -static struct crm_option long_options[] = { - /* Top-level Options */ - {"help", 0, 0, '?', "\t\tThis text"}, - {"version", 0, 0, '$', "\t\tVersion information" }, - {"verbose", 0, 0, 'V', "\t\tIncrease debug output"}, - {"quiet", 0, 0, 'Q', "\t\tPrint only the value on stdout\n"}, - - {"ticket", 1, 0, 't', "\tTicket ID" }, - - {"-spacer-", 1, 0, '-', "\nQueries:"}, - {"info", 0, 0, 'l', "\t\tDisplay the information of ticket(s)"}, - {"details", 0, 0, 'L', "\t\tDisplay the details of ticket(s)"}, - {"raw", 0, 0, 'w', "\t\tDisplay the IDs of ticket(s)"}, - {"query-xml", 0, 0, 'q', "\tQuery the XML of ticket(s)"}, - {"constraints",0, 0, 'c', "\tDisplay the rsc_ticket constraints that apply to ticket(s)"}, - - {"-spacer-", 1, 0, '-', "\nCommands:"}, - {"grant", 0, 0, 'g', "\t\tGrant a ticket to this cluster site"}, - {"revoke", 0, 0, 'r', "\t\tRevoke a ticket from this cluster site"}, - {"standby", 0, 0, 's', "\t\tTell this cluster site this ticket is standby"}, - {"activate", 0, 0, 'a', "\tTell this cluster site this ticket is active"}, - - {"-spacer-", 1, 0, '-', "\nAdvanced Commands:"}, - {"get-attr", 1, 0, 'G', "\tDisplay the named attribute for a ticket"}, - {"set-attr", 1, 0, 'S', "\tSet the named attribute for a ticket"}, - {"delete-attr",1, 0, 'D', "\tDelete the named attribute for a ticket"}, - {"cleanup", 0, 0, 'C', "\t\tDelete all state of a ticket at this cluster site"}, - - {"-spacer-", 1, 0, '-', "\nAdditional Options:"}, - {"attr-value", 1, 0, 'v', "\tAttribute value to use with -S"}, - {"default", 1, 0, 'd', "\t(Advanced) The default attribute value to display if none is found. For use with -G"}, - {"force", 0, 0, 'f', "\t\t(Advanced) Force the action to be performed"}, - {"xml-file", 1, 0, 'x', NULL, 1}, +static pcmk__cli_option_t long_options[] = { + // long option, argument type, storage, short option, description, flags + { + "help", no_argument, NULL, '?', + "\t\tThis text", pcmk__option_default + }, + { + "version", no_argument, NULL, '$', + "\t\tVersion information", pcmk__option_default + }, + { + "verbose", no_argument, NULL, 'V', + "\t\tIncrease debug output", pcmk__option_default + }, + { + "quiet", no_argument, NULL, 'Q', + "\t\tPrint only the value on stdout\n", pcmk__option_default + }, + { + "ticket", required_argument, NULL, 't', + "\tTicket ID", pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nQueries:", pcmk__option_default + }, + { + "info", no_argument, NULL, 'l', + "\t\tDisplay the information of ticket(s)", pcmk__option_default + }, + { + "details", no_argument, NULL, 'L', + "\t\tDisplay the details of ticket(s)", pcmk__option_default + }, + { + "raw", no_argument, NULL, 'w', + "\t\tDisplay the IDs of ticket(s)", pcmk__option_default + }, + { + "query-xml", no_argument, NULL, 'q', + "\tQuery the XML of ticket(s)", pcmk__option_default + }, + { + "constraints", no_argument, NULL, 'c', + "\tDisplay the rsc_ticket constraints that apply to ticket(s)", + pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nCommands:", pcmk__option_default + }, + { + "grant", no_argument, NULL, 'g', + "\t\tGrant a ticket to this cluster site", pcmk__option_default + }, + { + "revoke", no_argument, NULL, 'r', + "\t\tRevoke a ticket from this cluster site", pcmk__option_default + }, + { + "standby", no_argument, NULL, 's', + "\t\tTell this cluster site this ticket is standby", + pcmk__option_default + }, + { + "activate", no_argument, NULL, 'a', + "\tTell this cluster site this ticket is active", pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nAdvanced Commands:", pcmk__option_default + }, + { + "get-attr", required_argument, NULL, 'G', + "\tDisplay the named attribute for a ticket", pcmk__option_default + }, + { + "set-attr", required_argument, NULL, 'S', + "\tSet the named attribute for a ticket", pcmk__option_default + }, + { + "delete-attr", required_argument, NULL, 'D', + "\tDelete the named attribute for a ticket", pcmk__option_default + }, + { + "cleanup", no_argument, NULL, 'C', + "\t\tDelete all state of a ticket at this cluster site", + pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nAdditional Options:", pcmk__option_default + }, + { + "attr-value", required_argument, NULL, 'v', + "\tAttribute value to use with -S", pcmk__option_default + }, + { + "default", required_argument, NULL, 'd', + "\t(Advanced) Default attribute value to display if none is found " + "(for use with -G)", + pcmk__option_default + }, + { + "force", no_argument, NULL, 'f', + "\t\t(Advanced) Force the action to be performed", pcmk__option_default + }, + { + "xml-file", required_argument, NULL, 'x', + NULL, pcmk__option_hidden + }, /* legacy options */ - {"set-name", 1, 0, 'n', "\t(Advanced) ID of the instance_attributes object to change"}, - {"nvpair", 1, 0, 'i', "\t(Advanced) ID of the nvpair object to change/delete"}, - - {"-spacer-", 1, 0, '-', "\nExamples:", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', "Display the info of tickets:", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', " crm_ticket --info", pcmk_option_example}, - {"-spacer-", 1, 0, '-', "Display the detailed info of tickets:", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', " crm_ticket --details", pcmk_option_example}, - {"-spacer-", 1, 0, '-', "Display the XML of 'ticketA':", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', " crm_ticket --ticket ticketA --query-xml", pcmk_option_example}, - {"-spacer-", 1, 0, '-', "Display the rsc_ticket constraints that apply to 'ticketA':", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', " crm_ticket --ticket ticketA --constraints", pcmk_option_example}, - - {"-spacer-", 1, 0, '-', "Grant 'ticketA' to this cluster site:", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', " crm_ticket --ticket ticketA --grant", pcmk_option_example}, - {"-spacer-", 1, 0, '-', "Revoke 'ticketA' from this cluster site:", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', " crm_ticket --ticket ticketA --revoke", pcmk_option_example}, - {"-spacer-", 1, 0, '-', "Make 'ticketA' standby:", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', "The cluster site will treat a granted 'ticketA' as 'standby'."}, - {"-spacer-", 1, 0, '-', "The dependent resources will be stopped or demoted gracefully without triggering loss-policies", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', " crm_ticket --ticket ticketA --standby", pcmk_option_example}, - {"-spacer-", 1, 0, '-', "Activate 'ticketA' from being standby:", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', " crm_ticket --ticket ticketA --activate", pcmk_option_example}, - - {"-spacer-", 1, 0, '-', "Get the value of the 'granted' attribute for 'ticketA':", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', " crm_ticket --ticket ticketA --get-attr granted", pcmk_option_example}, - {"-spacer-", 1, 0, '-', "Set the value of the 'standby' attribute for 'ticketA':", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', " crm_ticket --ticket ticketA --set-attr standby --attr-value true", pcmk_option_example}, - {"-spacer-", 1, 0, '-', "Delete the 'granted' attribute for 'ticketA':", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', " crm_ticket --ticket ticketA --delete-attr granted", pcmk_option_example}, - {"-spacer-", 1, 0, '-', "Erase the operation history of 'ticketA' at this cluster site:", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', "The cluster site will 'forget' the existing ticket state.", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', " crm_ticket --ticket ticketA --cleanup", pcmk_option_example}, - - {0, 0, 0, 0} + { + "set-name", required_argument, NULL, 'n', + "\t(Advanced) ID of the instance_attributes object to change", + pcmk__option_default + }, + { + "nvpair", required_argument, NULL, 'i', + "\t(Advanced) ID of the nvpair object to change/delete", + pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nExamples:", pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + "Display the info of tickets:", pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_ticket --info", pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Display the detailed info of tickets:", pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_ticket --details", pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Display the XML of 'ticketA':", pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_ticket --ticket ticketA --query-xml", pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Display the rsc_ticket constraints that apply to 'ticketA':", + pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_ticket --ticket ticketA --constraints", pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Grant 'ticketA' to this cluster site:", pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_ticket --ticket ticketA --grant", pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Revoke 'ticketA' from this cluster site:", pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_ticket --ticket ticketA --revoke", pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Make 'ticketA' standby (the cluster site will treat a granted " + "'ticketA' as 'standby', and the dependent resources will be " + "stopped or demoted gracefully without triggering loss-policies):", + pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_ticket --ticket ticketA --standby", pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Activate 'ticketA' from being standby:", pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_ticket --ticket ticketA --activate", pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Get the value of the 'granted' attribute for 'ticketA':", + pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_ticket --ticket ticketA --get-attr granted", pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Set the value of the 'standby' attribute for 'ticketA':", + pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_ticket --ticket ticketA --set-attr standby --attr-value true", + pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Delete the 'granted' attribute for 'ticketA':", pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_ticket --ticket ticketA --delete-attr granted", + pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Erase the operation history of 'ticketA' at this cluster site:", + pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + "The cluster site will 'forget' the existing ticket state.", + pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_ticket --ticket ticketA --cleanup", pcmk__option_example + }, + { 0, 0, 0, 0 } }; -/* *INDENT-ON* */ int main(int argc, char **argv) { pe_working_set_t *data_set = NULL; xmlNode *cib_xml_copy = NULL; xmlNode *cib_constraints = NULL; cib_t *cib_conn = NULL; crm_exit_t exit_code = CRM_EX_OK; int rc = pcmk_ok; int option_index = 0; int argerr = 0; int flag; guint modified = 0; GListPtr attr_delete = NULL; GHashTable *attr_set = crm_str_table_new(); crm_log_init(NULL, LOG_CRIT, FALSE, FALSE, argc, argv, FALSE); - crm_set_options(NULL, "(query|command) [options]", long_options, - "Perform tasks related to cluster tickets.\nAllows ticket attributes to be queried, modified and deleted.\n"); + pcmk__set_cli_options(NULL, "| [options]", long_options, + "perform tasks related to cluster tickets\n\n" + "Allows ticket attributes to be queried, modified " + "and deleted.\n"); if (argc < 2) { - crm_help('?', CRM_EX_USAGE); + pcmk__cli_help('?', CRM_EX_USAGE); } while (1) { - flag = crm_get_option(argc, argv, &option_index); + flag = pcmk__next_cli_option(argc, argv, &option_index, NULL); if (flag == -1) break; switch (flag) { case 'V': crm_bump_log_level(argc, argv); break; case '$': case '?': - crm_help(flag, CRM_EX_OK); + pcmk__cli_help(flag, CRM_EX_OK); break; case 'Q': BE_QUIET = TRUE; break; case 't': ticket_id = optarg; break; case 'l': case 'L': case 'w': case 'q': case 'c': ticket_cmd = flag; break; case 'g': g_hash_table_insert(attr_set, strdup("granted"), strdup("true")); modified++; break; case 'r': g_hash_table_insert(attr_set, strdup("granted"), strdup("false")); modified++; break; case 's': g_hash_table_insert(attr_set, strdup("standby"), strdup("true")); modified++; break; case 'a': g_hash_table_insert(attr_set, strdup("standby"), strdup("false")); modified++; break; case 'G': get_attr_name = optarg; ticket_cmd = flag; break; case 'S': attr_name = optarg; if (attr_name && attr_value) { g_hash_table_insert(attr_set, strdup(attr_name), strdup(attr_value)); attr_name = NULL; attr_value = NULL; modified++; } break; case 'D': attr_delete = g_list_append(attr_delete, optarg); modified++; break; case 'C': ticket_cmd = flag; break; case 'v': attr_value = optarg; if (attr_name && attr_value) { g_hash_table_insert(attr_set, strdup(attr_name), strdup(attr_value)); attr_name = NULL; attr_value = NULL; modified++; } break; case 'd': attr_default = optarg; break; case 'f': do_force = TRUE; break; case 'x': xml_file = strdup(optarg); break; case 'n': set_name = optarg; break; case 'i': attr_id = optarg; break; default: CMD_ERR("Argument code 0%o (%c) is not (?yet?) supported", flag, flag); ++argerr; break; } } if (optind < argc && argv[optind] != NULL) { CMD_ERR("non-option ARGV-elements:"); while (optind < argc && argv[optind] != NULL) { CMD_ERR("%s", argv[optind++]); ++argerr; } } if (optind > argc) { ++argerr; } if (argerr) { - crm_help('?', CRM_EX_USAGE); + pcmk__cli_help('?', CRM_EX_USAGE); } data_set = pe_new_working_set(); if (data_set == NULL) { crm_perror(LOG_CRIT, "Could not allocate working set"); exit_code = CRM_EX_OSERR; goto bail; } set_bit(data_set->flags, pe_flag_no_counts); set_bit(data_set->flags, pe_flag_no_compat); cib_conn = cib_new(); if (cib_conn == NULL) { CMD_ERR("Could not connect to the CIB manager"); exit_code = CRM_EX_DISCONNECT; goto bail; } rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command); if (rc != pcmk_ok) { CMD_ERR("Could not connect to the CIB manager: %s", pcmk_strerror(rc)); exit_code = crm_errno2exit(rc); goto bail; } if (xml_file != NULL) { cib_xml_copy = filename2xml(xml_file); } else { rc = cib_conn->cmds->query(cib_conn, NULL, &cib_xml_copy, cib_scope_local | cib_sync_call); if (rc != pcmk_ok) { CMD_ERR("Could not get local CIB: %s", pcmk_strerror(rc)); exit_code = crm_errno2exit(rc); goto bail; } } if (cli_config_update(&cib_xml_copy, NULL, FALSE) == FALSE) { CMD_ERR("Could not update local CIB to latest schema version"); exit_code = CRM_EX_CONFIG; goto bail; } data_set->input = cib_xml_copy; data_set->now = crm_time_new(NULL); cluster_status(data_set); /* For recording the tickets that are referenced in rsc_ticket constraints * but have never been granted yet. */ cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, data_set->input); unpack_constraints(cib_constraints, data_set); if (ticket_cmd == 'l' || ticket_cmd == 'L' || ticket_cmd == 'w') { gboolean raw = FALSE; gboolean details = FALSE; if (ticket_cmd == 'L') { details = TRUE; } else if (ticket_cmd == 'w') { raw = TRUE; } if (ticket_id) { ticket_t *ticket = find_ticket(ticket_id, data_set); if (ticket == NULL) { CMD_ERR("No such ticket '%s'", ticket_id); exit_code = CRM_EX_NOSUCH; goto bail; } rc = print_ticket(ticket, raw, details); } else { rc = print_ticket_list(data_set, raw, details); } if (rc != pcmk_ok) { CMD_ERR("Could not print ticket: %s", pcmk_strerror(rc)); } exit_code = crm_errno2exit(rc); } else if (ticket_cmd == 'q') { rc = dump_ticket_xml(cib_conn, ticket_id); if (rc != pcmk_ok) { CMD_ERR("Could not query ticket XML: %s", pcmk_strerror(rc)); } exit_code = crm_errno2exit(rc); } else if (ticket_cmd == 'c') { rc = dump_constraints(cib_conn, ticket_id); if (rc != pcmk_ok) { CMD_ERR("Could not show ticket constraints: %s", pcmk_strerror(rc)); } exit_code = crm_errno2exit(rc); } else if (ticket_cmd == 'G') { const char *value = NULL; if (ticket_id == NULL) { CMD_ERR("Must supply ticket ID with -t"); exit_code = CRM_EX_NOSUCH; goto bail; } rc = get_ticket_state_attr(ticket_id, get_attr_name, &value, data_set); if (rc == pcmk_ok) { fprintf(stdout, "%s\n", value); } else if (rc == -ENXIO && attr_default) { fprintf(stdout, "%s\n", attr_default); rc = pcmk_ok; } exit_code = crm_errno2exit(rc); } else if (ticket_cmd == 'C') { if (ticket_id == NULL) { CMD_ERR("Must supply ticket ID with -t"); exit_code = CRM_EX_USAGE; goto bail; } if (do_force == FALSE) { ticket_t *ticket = NULL; ticket = find_ticket(ticket_id, data_set); if (ticket == NULL) { CMD_ERR("No such ticket '%s'", ticket_id); exit_code = CRM_EX_NOSUCH; goto bail; } if (ticket->granted) { ticket_warning(ticket_id, "revoke"); exit_code = CRM_EX_INSUFFICIENT_PRIV; goto bail; } } rc = delete_ticket_state(ticket_id, cib_conn); if (rc != pcmk_ok) { CMD_ERR("Could not clean up ticket: %s", pcmk_strerror(rc)); } exit_code = crm_errno2exit(rc); } else if (modified) { if (ticket_id == NULL) { CMD_ERR("Must supply ticket ID with -t"); exit_code = CRM_EX_USAGE; goto bail; } if (attr_value && (attr_name == NULL || strlen(attr_name) == 0)) { CMD_ERR("Must supply attribute name with -S for -v %s", attr_value); exit_code = CRM_EX_USAGE; goto bail; } if (attr_name && (attr_value == NULL || strlen(attr_value) == 0)) { CMD_ERR("Must supply attribute value with -v for -S %s", attr_name); exit_code = CRM_EX_USAGE; goto bail; } if (allow_modification(ticket_id, attr_delete, attr_set) == FALSE) { CMD_ERR("Ticket modification not allowed"); exit_code = CRM_EX_INSUFFICIENT_PRIV; goto bail; } rc = modify_ticket_state(ticket_id, attr_delete, attr_set, cib_conn, data_set); if (rc != pcmk_ok) { CMD_ERR("Could not modify ticket: %s", pcmk_strerror(rc)); } exit_code = crm_errno2exit(rc); } else if (ticket_cmd == 'S') { /* Correct usage was handled in the "if (modified)" block above, so * this is just for reporting usage errors */ if (attr_name == NULL || strlen(attr_name) == 0) { // We only get here if ticket_cmd was left as default CMD_ERR("Must supply a command"); exit_code = CRM_EX_USAGE; goto bail; } if (ticket_id == NULL) { CMD_ERR("Must supply ticket ID with -t"); exit_code = CRM_EX_USAGE; goto bail; } if (attr_value == NULL || strlen(attr_value) == 0) { CMD_ERR("Must supply value with -v for -S %s", attr_name); exit_code = CRM_EX_USAGE; goto bail; } } else { CMD_ERR("Unknown command: %c", ticket_cmd); exit_code = CRM_EX_USAGE; } bail: if (attr_set) { g_hash_table_destroy(attr_set); } attr_set = NULL; if (attr_delete) { g_list_free(attr_delete); } attr_delete = NULL; pe_free_working_set(data_set); data_set = NULL; if (cib_conn != NULL) { cib_conn->cmds->signoff(cib_conn); cib_delete(cib_conn); } if (rc == -pcmk_err_no_quorum) { CMD_ERR("Use --force to ignore quorum"); } crm_exit(exit_code); } diff --git a/tools/crm_verify.c b/tools/crm_verify.c index af5422bff8..f03388ff98 100644 --- a/tools/crm_verify.c +++ b/tools/crm_verify.c @@ -1,272 +1,319 @@ /* - * Copyright 2004-2019 the Pacemaker project contributors + * Copyright 2004-2020 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 gboolean USE_LIVE_CIB = FALSE; char *cib_save = NULL; extern gboolean stage0(pe_working_set_t * data_set); -/* *INDENT-OFF* */ -static struct crm_option long_options[] = { - /* Top-level Options */ - {"help", 0, 0, '?', "\tThis text"}, - {"version", 0, 0, '$', "\tVersion information" }, - {"verbose", 0, 0, 'V', "\tSpecify multiple times to increase debug output\n"}, - - {"-spacer-", 1, 0, '-', "\nData sources:"}, - {"live-check", 0, 0, 'L', "Check the configuration used by the running cluster\n"}, - {"xml-file", 1, 0, 'x', "Check the configuration in the named file"}, - {"xml-text", 1, 0, 'X', "Check the configuration in the supplied string"}, - {"xml-pipe", 0, 0, 'p', "Check the configuration piped in via stdin"}, - - {"-spacer-", 1, 0, '-', "\nAdditional Options:"}, - {"save-xml", 1, 0, 'S', "Save the verified XML to the named file. Most useful with -L"}, - - {"-spacer-", 1, 0, '-', "\nExamples:", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', "Check the consistency of the configuration in the running cluster:", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', " crm_verify --live-check", pcmk_option_example}, - {"-spacer-", 1, 0, '-', "Check the consistency of the configuration in a given file and produce verbose output:", pcmk_option_paragraph}, - {"-spacer-", 1, 0, '-', " crm_verify --xml-file file.xml --verbose", pcmk_option_example}, - +static pcmk__cli_option_t long_options[] = { + // long option, argument type, storage, short option, description, flags + { + "help", no_argument, NULL, '?', + "\tThis text", pcmk__option_default + }, + { + "version", no_argument, NULL, '$', + "\tVersion information", pcmk__option_default + }, + { + "verbose", no_argument, NULL, 'V', + "\tSpecify multiple times to increase debug output\n", + pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nData sources:", pcmk__option_default + }, + { + "live-check", no_argument, NULL, 'L', + "Check the configuration used by the running cluster\n", + pcmk__option_default + }, + { + "xml-file", required_argument, NULL, 'x', + "Check the configuration in the named file", pcmk__option_default + }, + { + "xml-text", required_argument, NULL, 'X', + "Check the configuration in the supplied string", pcmk__option_default + }, + { + "xml-pipe", no_argument, NULL, 'p', + "Check the configuration piped in via stdin", pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nAdditional Options:", pcmk__option_default + }, + { + "save-xml", required_argument, 0, 'S', + "Save verified XML to named file (most useful with -L)", + pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nExamples:", pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + "Check the consistency of the configuration in the running cluster:", + pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_verify --live-check", pcmk__option_example + }, + { + "-spacer-", no_argument, NULL, '-', + "Check the consistency of the configuration in a given file and " + "produce verbose output:", + pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + " crm_verify --xml-file file.xml --verbose", pcmk__option_example + }, {0, 0, 0, 0} }; -/* *INDENT-ON* */ int main(int argc, char **argv) { xmlNode *cib_object = NULL; xmlNode *status = NULL; int argerr = 0; int flag; int option_index = 0; pe_working_set_t *data_set = NULL; cib_t *cib_conn = NULL; int rc = pcmk_ok; bool verbose = FALSE; gboolean xml_stdin = FALSE; const char *xml_tag = NULL; const char *xml_file = NULL; const char *xml_string = NULL; crm_log_cli_init("crm_verify"); - crm_set_options(NULL, "[modifiers] data_source", long_options, - "check a Pacemaker configuration for errors" - "\n\nCheck the well-formedness of a complete Pacemaker XML configuration," - "\n\nits conformance to the configured schema, and the presence of common" - "\n\nmisconfigurations. Problems reported as errors must be fixed before the" - "\n\ncluster will work properly. It is left to the administrator to decide" - "\n\nwhether to fix problems reported as warnings."); + pcmk__set_cli_options(NULL, "[options]", long_options, + "check a Pacemaker configuration for errors\n\n" + "Check the well-formedness of a complete Pacemaker " + "XML configuration,\nits conformance to the " + "configured schema, and the presence of common\n" + "misconfigurations. Problems reported as errors " + "must be fixed before the\ncluster will work " + "properly. It is left to the administrator to decide" + "\nwhether to fix problems reported as warnings."); while (1) { - flag = crm_get_option(argc, argv, &option_index); + flag = pcmk__next_cli_option(argc, argv, &option_index, NULL); if (flag == -1) break; switch (flag) { case 'X': crm_trace("Option %c => %s", flag, optarg); xml_string = optarg; break; case 'x': crm_trace("Option %c => %s", flag, optarg); xml_file = optarg; break; case 'p': xml_stdin = TRUE; break; case 'S': cib_save = optarg; break; case 'V': verbose = TRUE; crm_bump_log_level(argc, argv); break; case 'L': USE_LIVE_CIB = TRUE; break; case '$': case '?': - crm_help(flag, CRM_EX_OK); + pcmk__cli_help(flag, CRM_EX_OK); break; default: fprintf(stderr, "Option -%c is not yet supported\n", flag); ++argerr; break; } } if (optind < argc) { printf("non-option ARGV-elements: "); while (optind < argc) { printf("%s ", argv[optind++]); } printf("\n"); } if (optind > argc) { ++argerr; } if (argerr) { crm_err("%d errors in option parsing", argerr); - crm_help(flag, CRM_EX_USAGE); + pcmk__cli_help(flag, CRM_EX_USAGE); } crm_info("=#=#=#=#= Getting XML =#=#=#=#="); if (USE_LIVE_CIB) { cib_conn = cib_new(); rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command); } if (USE_LIVE_CIB) { if (rc == pcmk_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); } if (rc != pcmk_ok) { fprintf(stderr, "Live CIB query failed: %s\n", pcmk_strerror(rc)); goto done; } if (cib_object == NULL) { fprintf(stderr, "Live CIB query failed: empty result\n"); rc = -ENOMSG; goto done; } } else if (xml_file != NULL) { cib_object = filename2xml(xml_file); if (cib_object == NULL) { fprintf(stderr, "Couldn't parse input file: %s\n", xml_file); rc = -ENODATA; goto done; } } else if (xml_string != NULL) { cib_object = string2xml(xml_string); if (cib_object == NULL) { fprintf(stderr, "Couldn't parse input string: %s\n", xml_string); rc = -ENODATA; goto done; } } else if (xml_stdin) { cib_object = stdin2xml(); if (cib_object == NULL) { fprintf(stderr, "Couldn't parse input from STDIN.\n"); rc = -ENODATA; goto done; } } else { fprintf(stderr, "No configuration source specified." " Use --help for usage information.\n"); rc = -ENODATA; goto done; } xml_tag = crm_element_name(cib_object); if (safe_str_neq(xml_tag, XML_TAG_CIB)) { fprintf(stderr, "This tool can only check complete configurations (i.e. those starting with ).\n"); rc = -EBADMSG; goto done; } if (cib_save != NULL) { write_xml_file(cib_object, 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) { crm_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; fprintf(stderr, "The cluster will NOT be able to use this configuration.\n"); fprintf(stderr, "Please manually update the configuration to conform to the %s syntax.\n", 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; } set_bit(data_set->flags, pe_flag_no_counts); set_bit(data_set->flags, pe_flag_no_compat); if (cib_object == NULL) { } else if (status != NULL || 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) { fprintf(stderr, "Errors found during check: config not valid\n"); if (verbose == FALSE) { fprintf(stderr, " -V may provide more details\n"); } rc = -pcmk_err_schema_validation; } else if (crm_config_warning) { fprintf(stderr, "Warnings found during check: config may not be valid\n"); if (verbose == FALSE) { fprintf(stderr, " Use -V -V for more details\n"); } rc = -pcmk_err_schema_validation; } if (USE_LIVE_CIB && cib_conn) { cib_conn->cmds->signoff(cib_conn); cib_delete(cib_conn); } done: crm_exit(crm_errno2exit(rc)); } diff --git a/tools/crmadmin.c b/tools/crmadmin.c index 41bbe24f4b..fe29d79913 100644 --- a/tools/crmadmin.c +++ b/tools/crmadmin.c @@ -1,475 +1,530 @@ /* - * Copyright 2004-2019 the Pacemaker project contributors + * Copyright 2004-2020 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 static int message_timer_id = -1; static int message_timeout_ms = 30 * 1000; static GMainLoop *mainloop = NULL; static crm_ipc_t *crmd_channel = NULL; static char *admin_uuid = NULL; gboolean do_init(void); int do_work(void); void crmadmin_ipc_connection_destroy(gpointer user_data); int admin_msg_callback(const char *buffer, ssize_t length, gpointer userdata); int do_find_node_list(xmlNode * xml_node); gboolean admin_message_timeout(gpointer data); static gboolean BE_VERBOSE = FALSE; static int expected_responses = 1; static gboolean BASH_EXPORT = FALSE; static gboolean DO_HEALTH = FALSE; static gboolean DO_RESET = FALSE; static gboolean DO_RESOURCE = FALSE; static gboolean DO_ELECT_DC = FALSE; static gboolean DO_WHOIS_DC = FALSE; static gboolean DO_NODE_LIST = FALSE; static gboolean BE_SILENT = FALSE; static gboolean DO_RESOURCE_LIST = FALSE; static const char *crmd_operation = NULL; static char *dest_node = NULL; static crm_exit_t exit_code = CRM_EX_OK; static const char *sys_to = NULL; -/* *INDENT-OFF* */ -static struct crm_option long_options[] = { - /* Top-level Options */ - {"help", 0, 0, '?', "\tThis text"}, - {"version", 0, 0, '$', "\tVersion information" }, - {"quiet", 0, 0, 'q', "\tDisplay only the essential query information"}, - {"verbose", 0, 0, 'V', "\tIncrease debug output"}, - - {"-spacer-", 1, 0, '-', "\nCommands:"}, +static pcmk__cli_option_t long_options[] = { + // long option, argument type, storage, short option, description, flags + { + "help", no_argument, NULL, '?', + "\tThis text", pcmk__option_default + }, + { + "version", no_argument, NULL, '$', + "\tVersion information", pcmk__option_default + }, + { + "quiet", no_argument, NULL, 'q', + "\tDisplay only the essential query information", pcmk__option_default + }, + { + "verbose", no_argument, NULL, 'V', + "\tIncrease debug output", pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nCommands:", pcmk__option_default + }, /* daemon options */ - {"status", 1, 0, 'S', "Display the status of the specified node." }, - {"-spacer-", 1, 0, '-', "\n\tResult is the node's internal FSM state which can be useful for debugging\n"}, - {"dc_lookup", 0, 0, 'D', "Display the uname of the node co-ordinating the cluster."}, - {"-spacer-", 1, 0, '-', "\n\tThis is an internal detail and is rarely useful to administrators except when deciding on which node to examine the logs.\n"}, - {"nodes", 0, 0, 'N', "\tDisplay the uname of all member nodes"}, - {"election", 0, 0, 'E', "(Advanced) Start an election for the cluster co-ordinator"}, { - "kill", 1, 0, 'K', - "(Advanced) Stop the controller (not the rest of the cluster stack) on specified node" + "status", required_argument, NULL, 'S', + "Display the status of the specified node.", pcmk__option_default }, - {"health", 0, 0, 'H', NULL, 1}, - - {"-spacer-", 1, 0, '-', "\nAdditional Options:"}, - {XML_ATTR_TIMEOUT, 1, 0, 't', "Time (in milliseconds) to wait before declaring the operation failed"}, - {"bash-export", 0, 0, 'B', "Create Bash export entries of the form 'export uname=uuid'\n"}, - - {"-spacer-", 1, 0, '-', "Notes:"}, - {"-spacer-", 1, 0, '-', " The -K and -E commands are rarely used and may be removed in future versions."}, - - {0, 0, 0, 0} + { + "-spacer-", no_argument, NULL, '-', + "\n\tResult is state of node's internal finite state machine, which " + "can be useful for debugging\n", + pcmk__option_default + }, + { + "dc_lookup", no_argument, NULL, 'D', + "Display the uname of the node co-ordinating the cluster.", + pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\n\tThis is an internal detail rarely useful to administrators " + "except when deciding on which node to examine the logs.\n", + pcmk__option_default + }, + { + "nodes", no_argument, NULL, 'N', + "\tDisplay the uname of all member nodes", pcmk__option_default + }, + { + "election", no_argument, NULL, 'E', + "(Advanced) Start an election for the cluster co-ordinator", + pcmk__option_default + }, + { + "kill", required_argument, NULL, 'K', + "(Advanced) Stop controller (not rest of cluster stack) on " + "specified node", pcmk__option_default + }, + { + "health", no_argument, NULL, 'H', + NULL, pcmk__option_hidden + }, + { + "-spacer-", no_argument, NULL, '-', + "\nAdditional Options:", pcmk__option_default + }, + { + XML_ATTR_TIMEOUT, required_argument, NULL, 't', + "Time (in milliseconds) to wait before declaring the operation failed", + pcmk__option_default + }, + { + "bash-export", no_argument, NULL, 'B', + "Create Bash export entries of the form 'export uname=uuid'\n", + pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "Notes:", pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + " The -K and -E commands are rarely used and may be removed in " + "future versions.", + pcmk__option_default + }, + { 0, 0, 0, 0 } }; -/* *INDENT-ON* */ int main(int argc, char **argv) { int option_index = 0; int argerr = 0; int flag; crm_log_cli_init("crmadmin"); - crm_set_options(NULL, "command [options]", long_options, - "Development tool for performing some controller-specific commands." - "\n Likely to be replaced by crm_node in the future"); + pcmk__set_cli_options(NULL, " [options]", long_options, + "query and manage the Pacemaker controller"); if (argc < 2) { - crm_help('?', CRM_EX_USAGE); + pcmk__cli_help('?', CRM_EX_USAGE); } while (1) { - flag = crm_get_option(argc, argv, &option_index); + flag = pcmk__next_cli_option(argc, argv, &option_index, NULL); if (flag == -1) break; switch (flag) { case 'V': BE_VERBOSE = TRUE; crm_bump_log_level(argc, argv); break; case 't': message_timeout_ms = atoi(optarg); if (message_timeout_ms < 1) { message_timeout_ms = 30 * 1000; } break; case '$': case '?': - crm_help(flag, CRM_EX_OK); + pcmk__cli_help(flag, CRM_EX_OK); break; case 'D': DO_WHOIS_DC = TRUE; break; case 'B': BASH_EXPORT = TRUE; break; case 'K': DO_RESET = TRUE; crm_trace("Option %c => %s", flag, optarg); dest_node = strdup(optarg); crmd_operation = CRM_OP_LOCAL_SHUTDOWN; break; case 'q': BE_SILENT = TRUE; break; case 'S': DO_HEALTH = TRUE; crm_trace("Option %c => %s", flag, optarg); dest_node = strdup(optarg); break; case 'E': DO_ELECT_DC = TRUE; break; case 'N': DO_NODE_LIST = TRUE; break; case 'H': DO_HEALTH = TRUE; break; default: printf("Argument code 0%o (%c) is not (?yet?) supported\n", flag, flag); ++argerr; break; } } if (optind < argc) { printf("non-option ARGV-elements: "); while (optind < argc) printf("%s ", argv[optind++]); printf("\n"); } if (optind > argc) { ++argerr; } if (argerr) { - crm_help('?', CRM_EX_USAGE); + pcmk__cli_help('?', CRM_EX_USAGE); } if (do_init()) { int res = 0; res = do_work(); if (res > 0) { /* wait for the reply by creating a mainloop and running it until * the callbacks are invoked... */ mainloop = g_main_loop_new(NULL, FALSE); crm_trace("Waiting for %d replies from the local CRM", expected_responses); message_timer_id = g_timeout_add(message_timeout_ms, admin_message_timeout, NULL); g_main_loop_run(mainloop); } else if (res < 0) { crm_err("No message to send"); exit_code = CRM_EX_ERROR; } } else { crm_warn("Init failed, could not perform requested operations"); exit_code = CRM_EX_UNAVAILABLE; } crm_trace("%s exiting normally", crm_system_name); return exit_code; } int do_work(void) { int ret = 1; /* construct the request */ xmlNode *msg_data = NULL; gboolean all_is_good = TRUE; if (DO_HEALTH == TRUE) { crm_trace("Querying the system"); sys_to = CRM_SYSTEM_DC; if (dest_node != NULL) { sys_to = CRM_SYSTEM_CRMD; crmd_operation = CRM_OP_PING; if (BE_VERBOSE) { expected_responses = 1; } } else { crm_info("Cluster-wide health not available yet"); all_is_good = FALSE; } } else if (DO_ELECT_DC) { /* tell the local node to initiate an election */ dest_node = NULL; sys_to = CRM_SYSTEM_CRMD; crmd_operation = CRM_OP_VOTE; ret = 0; /* no return message */ } else if (DO_WHOIS_DC) { dest_node = NULL; sys_to = CRM_SYSTEM_DC; crmd_operation = CRM_OP_PING; } else if (DO_NODE_LIST) { cib_t *the_cib = cib_new(); xmlNode *output = NULL; int rc = the_cib->cmds->signon(the_cib, crm_system_name, cib_command); if (rc != pcmk_ok) { return -1; } rc = the_cib->cmds->query(the_cib, NULL, &output, cib_scope_local | cib_sync_call); if(rc == pcmk_ok) { do_find_node_list(output); free_xml(output); } the_cib->cmds->signoff(the_cib); crm_exit(crm_errno2exit(rc)); } else if (DO_RESET) { /* tell dest_node to initiate the shutdown procedure * * if dest_node is NULL, the request will be sent to the * local node */ sys_to = CRM_SYSTEM_CRMD; ret = 0; /* no return message */ } else { crm_err("Unknown options"); all_is_good = FALSE; } if (all_is_good == FALSE) { crm_err("Creation of request failed. No message to send"); return -1; } /* send it */ if (crmd_channel == NULL) { crm_err("The IPC connection is not valid, cannot send anything"); return -1; } if (sys_to == NULL) { if (dest_node != NULL) { sys_to = CRM_SYSTEM_CRMD; } else { sys_to = CRM_SYSTEM_DC; } } { xmlNode *cmd = create_request(crmd_operation, msg_data, dest_node, sys_to, crm_system_name, admin_uuid); crm_ipc_send(crmd_channel, cmd, 0, 0, NULL); free_xml(cmd); } return ret; } void crmadmin_ipc_connection_destroy(gpointer user_data) { crm_err("Connection to controller was terminated"); if (mainloop) { g_main_loop_quit(mainloop); } else { crm_exit(CRM_EX_DISCONNECT); } } struct ipc_client_callbacks crm_callbacks = { .dispatch = admin_msg_callback, .destroy = crmadmin_ipc_connection_destroy }; gboolean do_init(void) { mainloop_io_t *source = mainloop_add_ipc_client(CRM_SYSTEM_CRMD, G_PRIORITY_DEFAULT, 0, NULL, &crm_callbacks); admin_uuid = crm_getpid_s(); crmd_channel = mainloop_get_ipc_client(source); if (DO_RESOURCE || DO_RESOURCE_LIST || DO_NODE_LIST) { return TRUE; } else if (crmd_channel != NULL) { xmlNode *xml = create_hello_message(admin_uuid, crm_system_name, "0", "1"); crm_ipc_send(crmd_channel, xml, 0, 0, NULL); return TRUE; } return FALSE; } static bool validate_crm_message(xmlNode * msg, const char *sys, const char *uuid, const char *msg_type) { const char *type = NULL; const char *crm_msg_reference = NULL; if (msg == NULL) { return FALSE; } type = crm_element_value(msg, F_CRM_MSG_TYPE); crm_msg_reference = crm_element_value(msg, XML_ATTR_REFERENCE); if (type == NULL) { crm_info("No message type defined."); return FALSE; } else if (msg_type != NULL && strcasecmp(msg_type, type) != 0) { crm_info("Expecting a (%s) message but received a (%s).", msg_type, type); return FALSE; } if (crm_msg_reference == NULL) { crm_info("No message crm_msg_reference defined."); return FALSE; } return TRUE; } int admin_msg_callback(const char *buffer, ssize_t length, gpointer userdata) { static int received_responses = 0; xmlNode *xml = string2xml(buffer); received_responses++; g_source_remove(message_timer_id); crm_log_xml_trace(xml, "ipc"); if (xml == NULL) { crm_info("XML in IPC message was not valid... " "discarding."); } else if (validate_crm_message(xml, crm_system_name, admin_uuid, XML_ATTR_RESPONSE) == FALSE) { crm_trace("Message was not a CRM response. Discarding."); } else if (DO_HEALTH) { xmlNode *data = get_message_xml(xml, F_CRM_DATA); const char *state = crm_element_value(data, XML_PING_ATTR_CRMDSTATE); printf("Status of %s@%s: %s (%s)\n", crm_element_value(data, XML_PING_ATTR_SYSFROM), crm_element_value(xml, F_CRM_HOST_FROM), state, crm_element_value(data, XML_PING_ATTR_STATUS)); if (BE_SILENT && state != NULL) { fprintf(stderr, "%s\n", state); } } else if (DO_WHOIS_DC) { const char *dc = crm_element_value(xml, F_CRM_HOST_FROM); printf("Designated Controller is: %s\n", dc); if (BE_SILENT && dc != NULL) { fprintf(stderr, "%s\n", dc); } crm_exit(CRM_EX_OK); } free_xml(xml); if (received_responses >= expected_responses) { crm_trace("Received expected number (%d) of replies, exiting normally", expected_responses); crm_exit(CRM_EX_OK); } message_timer_id = g_timeout_add(message_timeout_ms, admin_message_timeout, NULL); return 0; } gboolean admin_message_timeout(gpointer data) { fprintf(stderr, "No messages received in %d seconds.. aborting\n", (int)message_timeout_ms / 1000); crm_err("No messages received in %d seconds", (int)message_timeout_ms / 1000); exit_code = CRM_EX_TIMEOUT; g_main_loop_quit(mainloop); return FALSE; } int do_find_node_list(xmlNode * xml_node) { int found = 0; xmlNode *node = NULL; xmlNode *nodes = get_object_root(XML_CIB_TAG_NODES, xml_node); for (node = __xml_first_child_element(nodes); node != NULL; node = __xml_next_element(node)) { if (crm_str_eq((const char *)node->name, XML_CIB_TAG_NODE, TRUE)) { if (BASH_EXPORT) { printf("export %s=%s\n", crm_element_value(node, XML_ATTR_UNAME), crm_element_value(node, XML_ATTR_ID)); } else { printf("%s node: %s (%s)\n", crm_element_value(node, XML_ATTR_TYPE), crm_element_value(node, XML_ATTR_UNAME), crm_element_value(node, XML_ATTR_ID)); } found++; } } if (found == 0) { printf("NO nodes configured\n"); } return found; } diff --git a/tools/ipmiservicelogd.c b/tools/ipmiservicelogd.c index d43032dcd0..4613b5c8b0 100644 --- a/tools/ipmiservicelogd.c +++ b/tools/ipmiservicelogd.c @@ -1,609 +1,609 @@ /* * ipmiservicelogd.c * * A program that listens to IPMI events and writes them * out to servicelog. * * Author: International Business Machines, IBM * Mark Hamzy * Author: Intel Corporation * Jeff Zheng * * Original copyright 2009 International Business Machines, IBM * Later changes copyright 2009-2019 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. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* gcc -o ipmiservicelogd -g `pkg-config --cflags --libs OpenIPMI OpenIPMIposix servicelog-1` ipmiservicelogd.c */ /* ./ipmiservicelogd smi 0 */ #include #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define COMPLEX 1 static os_handler_t *os_hnd; char *getStringExecOutput(const char *const args[]); char *getSerialNumber(void); char *getProductName(void); static void con_usage(const char *name, const char *help, void *cb_data); static void usage(const char *progname); void ipmi2servicelog(struct sl_data_bmc *bmc_data); static int sensor_threshold_event_handler(ipmi_sensor_t * sensor, enum ipmi_event_dir_e dir, enum ipmi_thresh_e threshold, enum ipmi_event_value_dir_e high_low, enum ipmi_value_present_e value_present, unsigned int raw_value, double value, void *cb_data, ipmi_event_t * event); static int sensor_discrete_event_handler(ipmi_sensor_t * sensor, enum ipmi_event_dir_e dir, int offset, int severity, int prev_severity, void *cb_data, ipmi_event_t * event); static void sensor_change(enum ipmi_update_e op, ipmi_entity_t * ent, ipmi_sensor_t * sensor, void *cb_data); static void entity_change(enum ipmi_update_e op, ipmi_domain_t * domain, ipmi_entity_t * entity, void *cb_data); void setup_done(ipmi_domain_t * domain, int err, unsigned int conn_num, unsigned int port_num, int still_connected, void *user_data); char * getStringExecOutput(const char *const args[]) { int rc; pid_t pid; int pipefd[2]; rc = pipe2(pipefd, 0); if (rc == -1) { crm_err("Error: pipe errno = %d", errno); return NULL; } pid = fork(); if (0 < pid) { /* Parent */ int childExitStatus; char serialNumber[256]; ssize_t sizeRead; /* close write end of pipe */ rc = close(pipefd[1]); if (rc == -1) { crm_err("Error: parent close (pipefd[1]) = %d", errno); } /* make 0 same as read-from end of pipe */ rc = dup2(pipefd[0], 0); if (rc == -1) { crm_err("Error: parent dup2 (pipefd[0]) = %d", errno); } /* close excess fildes */ rc = close(pipefd[0]); if (rc == -1) { crm_err("Error: parent close (pipefd[0]) = %d", errno); } waitpid(pid, &childExitStatus, 0); if (!WIFEXITED(childExitStatus)) { crm_err("waitpid() exited with an error: status = %d", WEXITSTATUS(childExitStatus)); return NULL; } else if (WIFSIGNALED(childExitStatus)) { crm_err("waitpid() exited due to a signal = %d", WTERMSIG(childExitStatus)); return NULL; } memset(serialNumber, 0, sizeof(serialNumber)); sizeRead = read(0, serialNumber, sizeof(serialNumber) - 1); if (sizeRead > 0) { char *end = serialNumber + strlen(serialNumber) - 1; while (end > serialNumber && (*end == '\n' || *end == '\r' || *end == '\t' || *end == ' ') ) { *end = '\0'; end--; } return strdup(serialNumber); } return NULL; } else if (pid == 0) { /* Child */ /* close read end of pipe */ rc = close(pipefd[0]); if (rc == -1) { crm_err("Error: child close (pipefd[0]) = %d", errno); } /* make 1 same as write-to end of pipe */ rc = dup2(pipefd[1], 1); if (rc == -1) { crm_err("Error: child dup2 (pipefd[1]) = %d", errno); } /* close excess fildes */ rc = close(pipefd[1]); if (rc == -1) { crm_err("Error: child close (pipefd[1]) = %d", errno); } /* execvp() takes (char *const *) for backward compatibility, * but POSIX guarantees that it will not modify the strings, * so the cast is safe */ rc = execvp(args[0], (char *const *) args); if (rc == -1) { crm_err("Error: child execvp = %d", errno); } /* In case of error */ return NULL; } else { /* Error */ crm_err("fork errno = %d", errno); return NULL; } return NULL; } char * getSerialNumber(void) { const char *const dmiArgs[] = { "dmidecode", "--string", "system-serial-number", NULL }; return getStringExecOutput(dmiArgs); } char * getProductName(void) { const char *dmiArgs[] = { "dmidecode", "--string", "system-product-name", NULL }; return getStringExecOutput(dmiArgs); } static void con_usage(const char *name, const char *help, void *cb_data) { printf("%s\n", help); } static void usage(const char *progname) { printf("Usage:\n"); printf(" %s \n", progname); printf(" Where is one of:\n"); ipmi_parse_args_iter_help(con_usage, NULL); } void ipmi2servicelog(struct sl_data_bmc *bmc_data) { servicelog *slog = NULL; struct sl_event sl_event; uint64_t new_id = 0; struct utsname name; char *serial_number = NULL; char *product_name = NULL; int rc; if (uname(&name) == -1) { crm_err("Error: uname failed"); return; } rc = servicelog_open(&slog, 0); /* flags is one of SL_FLAG_xxx */ if (!slog) { crm_err("Error: servicelog_open failed, rc = %d", rc); return; } serial_number = getSerialNumber(); if (serial_number) { if (strlen(serial_number) > 20) { serial_number[20] = '\0'; } } product_name = getProductName(); if (product_name) { if (strlen(product_name) > 20) { product_name[20] = '\0'; } } memset(&sl_event, 0, sizeof(sl_event)); /* *INDENT-OFF* */ sl_event.next = NULL; /* only used if in a linked list */ sl_event.id = 0; /* unique identifier - filled in by API call */ sl_event.time_logged = time (NULL); sl_event.time_event = time (NULL); sl_event.time_last_update = time (NULL); sl_event.type = SL_TYPE_BMC; /* one of SL_TYPE_* */ sl_event.severity = SL_SEV_WARNING; /* one of SL_SEV_* */ sl_event.platform = name.machine; /* ppc64, etc */ sl_event.machine_serial = serial_number; sl_event.machine_model = product_name; /* it may not have the serial # within the first 20 chars */ sl_event.nodename = name.nodename; sl_event.refcode = strdup("ipmi"); sl_event.description = strdup("ipmi event"); sl_event.serviceable = 1; /* 1 or 0 */ sl_event.predictive = 0; /* 1 or 0 */ sl_event.disposition = SL_DISP_RECOVERABLE; /* one of SL_DISP_* */ sl_event.call_home_status = SL_CALLHOME_NONE; /* one of SL_CALLHOME_*, only valid if serviceable */ sl_event.closed = 1; /* 1 or 0, only valid if serviceable */ sl_event.repair = 0; /* id of repairing repair_action */ sl_event.callouts = NULL; sl_event.raw_data_len = 0; sl_event.raw_data = NULL; sl_event.addl_data = &bmc_data; /* pointer to an sl_data_* struct */ /* *INDENT-ON* */ rc = servicelog_event_log(slog, &sl_event, &new_id); if (rc != 0) { crm_err("Error: servicelog_event_log, rc = %d (\"%s\")", rc, servicelog_error(slog)); } else { crm_debug("Sending to servicelog database"); } free(sl_event.refcode); free(sl_event.description); free(serial_number); free(product_name); servicelog_close(slog); } static int sensor_threshold_event_handler(ipmi_sensor_t * sensor, enum ipmi_event_dir_e dir, enum ipmi_thresh_e threshold, enum ipmi_event_value_dir_e high_low, enum ipmi_value_present_e value_present, unsigned int raw_value, double value, void *cb_data, ipmi_event_t * event) { ipmi_entity_t *ent = ipmi_sensor_get_entity(sensor); char name[IPMI_ENTITY_NAME_LEN]; struct sl_data_bmc bmc_data; uint32_t sel_id; uint32_t sel_type; uint16_t generator; uint8_t version; uint8_t sensor_type; int sensor_lun; int sensor_number; uint8_t event_class; uint8_t event_type; int direction; ipmi_sensor_get_id(sensor, name, sizeof(name)); ipmi_sensor_get_num(sensor, &sensor_lun, &sensor_number); sel_id = ipmi_entity_get_entity_id(ent); sel_type = ipmi_entity_get_type(ent); generator = ipmi_entity_get_slave_address(ent) | (sensor_lun << 5); /* LUN (2 bits) | SLAVE ADDRESS (5 bits) */ version = 0x04; sensor_type = ipmi_sensor_get_sensor_type(sensor); event_class = 0; /* @TBD - where does this come from? */ event_type = ipmi_event_get_type(event); direction = dir; memset(&bmc_data, 0, sizeof(bmc_data)); bmc_data.sel_id = sel_id; bmc_data.sel_type = sel_type; bmc_data.generator = generator; bmc_data.version = version; bmc_data.sensor_type = sensor_type; bmc_data.sensor_number = sensor_number; bmc_data.event_class = event_class; bmc_data.event_type = event_type; bmc_data.direction = direction; crm_debug("Writing bmc_data (%08x, %08x, %04x, %02x, %02x, %02x, %02x, %02x, %d)", bmc_data.sel_id, bmc_data.sel_type, bmc_data.generator, bmc_data.version, bmc_data.sensor_type, bmc_data.sensor_number, bmc_data.event_class, bmc_data.event_type, bmc_data.direction); ipmi2servicelog(&bmc_data); /* This passes the event on to the main event handler, which does not exist in this program. */ return IPMI_EVENT_NOT_HANDLED; } static int sensor_discrete_event_handler(ipmi_sensor_t * sensor, enum ipmi_event_dir_e dir, int offset, int severity, int prev_severity, void *cb_data, ipmi_event_t * event) { ipmi_entity_t *ent = ipmi_sensor_get_entity(sensor); char name[IPMI_ENTITY_NAME_LEN]; struct sl_data_bmc bmc_data; uint32_t sel_id; uint32_t sel_type; uint16_t generator; uint8_t version; uint8_t sensor_type; int sensor_lun; int sensor_number; uint8_t event_class; uint8_t event_type; int direction; ipmi_sensor_get_id(sensor, name, sizeof(name)); ipmi_sensor_get_num(sensor, &sensor_lun, &sensor_number); sel_id = ipmi_entity_get_entity_id(ent); sel_type = ipmi_entity_get_type(ent); generator = ipmi_entity_get_slave_address(ent) | (sensor_lun << 5); /* LUN (2 bits) | SLAVE ADDRESS (5 bits) */ version = 0x04; sensor_type = ipmi_sensor_get_sensor_type(sensor); event_class = 0; /* @TBD - where does this come from? */ event_type = ipmi_event_get_type(event); direction = dir; memset(&bmc_data, 0, sizeof(bmc_data)); bmc_data.sel_id = sel_id; bmc_data.sel_type = sel_type; bmc_data.generator = generator; bmc_data.version = version; bmc_data.sensor_type = sensor_type; bmc_data.sensor_number = sensor_number; bmc_data.event_class = event_class; bmc_data.event_type = event_type; bmc_data.direction = direction; crm_debug("Writing bmc_data (%08x, %08x, %04x, %02x, %02x, %02x, %02x, %02x, %d)", bmc_data.sel_id, bmc_data.sel_type, bmc_data.generator, bmc_data.version, bmc_data.sensor_type, bmc_data.sensor_number, bmc_data.event_class, bmc_data.event_type, bmc_data.direction); ipmi2servicelog(&bmc_data); /* This passes the event on to the main event handler, which does not exist in this program. */ return IPMI_EVENT_NOT_HANDLED; } /* Whenever the status of a sensor changes, the function is called We display the information of the sensor if we find a new sensor */ static void sensor_change(enum ipmi_update_e op, ipmi_entity_t * ent, ipmi_sensor_t * sensor, void *cb_data) { int rv; if (op == IPMI_ADDED) { if (ipmi_sensor_get_event_reading_type(sensor) == IPMI_EVENT_READING_TYPE_THRESHOLD) rv = ipmi_sensor_add_threshold_event_handler(sensor, sensor_threshold_event_handler, NULL); else rv = ipmi_sensor_add_discrete_event_handler(sensor, sensor_discrete_event_handler, NULL); if (rv) crm_err("Unable to add the sensor event handler: %x", rv); } } /* Whenever the status of an entity changes, the function is called When a new entity is created, we search all sensors that belong to the entity */ static void entity_change(enum ipmi_update_e op, ipmi_domain_t * domain, ipmi_entity_t * entity, void *cb_data) { int rv; if (op == IPMI_ADDED) { /* Register callback so that when the status of a sensor changes, sensor_change is called */ rv = ipmi_entity_add_sensor_update_handler(entity, sensor_change, entity); if (rv) { crm_err("ipmi_entity_set_sensor_update_handler: 0x%x", rv); crm_exit(CRM_EX_ERROR); } } } /* After we have established connection to domain, this function get called At this time, we can do whatever things we want to do. Herr we want to search all entities in the system */ void setup_done(ipmi_domain_t * domain, int err, unsigned int conn_num, unsigned int port_num, int still_connected, void *user_data) { int rv; /* Register a callback functin entity_change. When a new entities is created, entity_change is called */ rv = ipmi_domain_add_entity_update_handler(domain, entity_change, domain); if (rv) { crm_err("ipmi_domain_add_entity_update_handler return error: %d", rv); return; } } int main(int argc, char *argv[]) { int rv; int curr_arg = 1; ipmi_args_t *args; ipmi_con_t *con; /* OS handler allocated first. */ os_hnd = ipmi_posix_setup_os_handler(); if (!os_hnd) { crm_err("ipmi_smi_setup_con: Unable to allocate os handler"); crm_exit(CRM_EX_ERROR); } /* Initialize the OpenIPMI library. */ ipmi_init(os_hnd); // Check for pacemaker-standard help and version options if (argc > 1) { for (char **arg = &argv[1]; *arg != NULL; ++arg) { if (!strcmp(*arg, "--help") || !strcmp(*arg, "-?")) { usage(argv[0]); return CRM_EX_OK; } else if (!strcmp(*arg, "--version") || !strcmp(*arg, "-$")) { - crm_help('$', CRM_EX_OK); + pcmk__cli_help('$', CRM_EX_OK); } } } #ifdef COMPLEX rv = ipmi_parse_args2(&curr_arg, argc, argv, &args); if (rv) { crm_err("Error parsing command arguments, argument %d: %s", curr_arg, strerror(rv)); usage(argv[0]); crm_exit(CRM_EX_USAGE); } #endif crm_make_daemon("ipmiservicelogd", TRUE, PCMK_RUN_DIR "/ipmiservicelogd.pid0"); crm_log_cli_init("ipmiservicelogd"); // Maybe this should log like a daemon instead? // crm_log_init("ipmiservicelogd", LOG_INFO, TRUE, FALSE, argc, argv, FALSE); #ifdef COMPLEX rv = ipmi_args_setup_con(args, os_hnd, NULL, &con); if (rv) { crm_err("ipmi_ip_setup_con: %s", strerror(rv)); crm_err("Error: Is IPMI configured correctly?"); crm_exit(CRM_EX_ERROR); } #else /* If all you need is an SMI connection, this is all the code you need. */ /* Establish connections to domain through system interface. This function connect domain, selector and OS handler together. When there is response message from domain, the status of file descriptor in selector is changed and predefined callback is called. After the connection is established, setup_done will be called. */ rv = ipmi_smi_setup_con(0, os_hnd, NULL, &con); if (rv) { crm_err("ipmi_smi_setup_con: %s", strerror(rv)); crm_err("Error: Is IPMI configured correctly?"); crm_exit(CRM_EX_ERROR); } #endif rv = ipmi_open_domain("", &con, 1, setup_done, NULL, NULL, NULL, NULL, 0, NULL); if (rv) { crm_err("ipmi_init_domain: %s", strerror(rv)); crm_exit(CRM_EX_ERROR); } /* This is the main loop of the event-driven program. Try to exit the program */ /* Let the selector code run the select loop. */ os_hnd->operation_loop(os_hnd); /* Technically, we can't get here, but this is an example. */ os_hnd->free_os_handler(os_hnd); return CRM_EX_OK; } diff --git a/tools/iso8601.c b/tools/iso8601.c index 5e84faaa8a..d6c4456940 100644 --- a/tools/iso8601.c +++ b/tools/iso8601.c @@ -1,223 +1,268 @@ /* - * Copyright 2005-2019 the Pacemaker project contributors + * Copyright 2005-2020 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 /* CRM_ASSERT */ #include char command = 0; -/* *INDENT-OFF* */ -static struct crm_option long_options[] = { - /* Top-level Options */ - {"help", 0, 0, '?', "\tThis text"}, - {"version", 0, 0, '$', "\tVersion information" }, - {"verbose", 0, 0, 'V', "\tIncrease debug output"}, - - {"-spacer-", 0, 0, '-', "\nCommands:"}, - {"now", 0, 0, 'n', "\tDisplay the current date/time"}, - { "date", 1, 0, 'd', - "Parse an ISO 8601 date/time (for example, '2019-09-24 00:30:00 +01:00' or '2019-040')"}, - { "period", 1, 0, 'p', - "Parse an ISO 8601 period (interval) with start time (for example, '2005-040/2005-043')" - }, - { "duration", 1, 0, 'D', - "Parse an ISO 8601 duration (for example, 'P1M')" - }, - { "expected", 1, 0, 'E', - "Exit with error status if result does not match this text. Requires: -n or -d" - }, - {"-spacer-",0, 0, '-', "\nOutput Modifiers:"}, - {"seconds", 0, 0, 's', "\tShow result as a seconds since 0000-001 00:00:00Z"}, - {"epoch", 0, 0, 'S', "\tShow result as a seconds since EPOCH (1970-001 00:00:00Z)"}, - {"local", 0, 0, 'L', "\tShow result as a 'local' date/time"}, - {"ordinal", 0, 0, 'O', "\tShow result as an 'ordinal' date/time"}, - {"week", 0, 0, 'W', "\tShow result as an 'calendar week' date/time"}, - { "-spacer-",0, 0, '-', - "\nFor more information on the ISO 8601 standard, see https://en.wikipedia.org/wiki/ISO_8601" - }, - - {0, 0, 0, 0} +static pcmk__cli_option_t long_options[] = { + // long option, argument type, storage, short option, description, flags + { + "help", no_argument, NULL, '?', + "\tThis text", pcmk__option_default + }, + { + "version", no_argument, NULL, '$', + "\tVersion information", pcmk__option_default + }, + { + "verbose", no_argument, NULL, 'V', + "\tIncrease debug output", pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nCommands:", pcmk__option_default + }, + { + "now", no_argument, NULL, 'n', + "\tDisplay the current date/time", pcmk__option_default + }, + { + "date", required_argument, NULL, 'd', + "Parse an ISO 8601 date/time (for example, " + "'2019-09-24 00:30:00 +01:00' or '2019-040')", + pcmk__option_default + }, + { + "period", required_argument, NULL, 'p', + "Parse an ISO 8601 period (interval) with start time (for example, " + "'2005-040/2005-043')", + pcmk__option_default + }, + { + "duration", required_argument, NULL, 'D', + "Parse an ISO 8601 duration (for example, 'P1M')", pcmk__option_default + }, + { + "expected", required_argument, NULL, 'E', + "Exit with error status if result does not match this text. " + "Requires: -n or -d", + pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nOutput Modifiers:", pcmk__option_default + }, + { + "seconds", no_argument, NULL, 's', + "\tShow result as a seconds since 0000-001 00:00:00Z", + pcmk__option_default + }, + { + "epoch", no_argument, NULL, 'S', + "\tShow result as a seconds since EPOCH (1970-001 00:00:00Z)", + pcmk__option_default + }, + { + "local", no_argument, NULL, 'L', + "\tShow result as a 'local' date/time", pcmk__option_default + }, + { + "ordinal", no_argument, NULL, 'O', + "\tShow result as an 'ordinal' date/time", pcmk__option_default + }, + { + "week", no_argument, NULL, 'W', + "\tShow result as an 'calendar week' date/time", pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nFor more information on the ISO 8601 standard, see " + "https://en.wikipedia.org/wiki/ISO_8601", + pcmk__option_default + }, + { 0, 0, 0, 0 } }; -/* *INDENT-ON* */ static void log_time_period(int log_level, crm_time_period_t * dtp, int flags) { char *start = crm_time_as_string(dtp->start, flags); char *end = crm_time_as_string(dtp->end, flags); CRM_ASSERT(start != NULL && end != NULL); do_crm_log(log_level, "Period: %s to %s", start, end); free(start); free(end); } int main(int argc, char **argv) { crm_exit_t exit_code = CRM_EX_OK; int argerr = 0; int flag; int index = 0; int print_options = 0; crm_time_t *duration = NULL; crm_time_t *date_time = NULL; const char *period_s = NULL; const char *duration_s = NULL; const char *date_time_s = NULL; const char *expected_s = NULL; crm_log_cli_init("iso8601"); - crm_set_options(NULL, "command [output modifier] ", long_options, - "Display and parse ISO 8601 dates and times"); + pcmk__set_cli_options(NULL, " [options] ", long_options, + "display and parse ISO 8601 dates and times"); if (argc < 2) { argerr++; } while (1) { - flag = crm_get_option(argc, argv, &index); + flag = pcmk__next_cli_option(argc, argv, &index, NULL); if (flag == -1) break; switch (flag) { case 'V': crm_bump_log_level(argc, argv); break; case '?': case '$': - crm_help(flag, CRM_EX_OK); + pcmk__cli_help(flag, CRM_EX_OK); break; case 'n': date_time_s = "now"; break; case 'd': date_time_s = optarg; break; case 'p': period_s = optarg; break; case 'D': duration_s = optarg; break; case 'E': expected_s = optarg; break; case 'S': print_options |= crm_time_epoch; break; case 's': print_options |= crm_time_seconds; break; case 'W': print_options |= crm_time_weeks; break; case 'O': print_options |= crm_time_ordinal; break; case 'L': print_options |= crm_time_log_with_timezone; break; break; } } if (safe_str_eq("now", date_time_s)) { date_time = crm_time_new(NULL); if (date_time == NULL) { fprintf(stderr, "Internal error: couldn't determine 'now'!\n"); crm_exit(CRM_EX_SOFTWARE); } crm_time_log(LOG_TRACE, "Current date/time", date_time, crm_time_ordinal | crm_time_log_date | crm_time_log_timeofday); crm_time_log(LOG_STDOUT, "Current date/time", date_time, print_options | crm_time_log_date | crm_time_log_timeofday); } else if (date_time_s) { date_time = crm_time_new(date_time_s); if (date_time == NULL) { fprintf(stderr, "Invalid date/time specified: %s\n", date_time_s); crm_exit(CRM_EX_INVALID_PARAM); } crm_time_log(LOG_TRACE, "Date", date_time, crm_time_ordinal | crm_time_log_date | crm_time_log_timeofday); crm_time_log(LOG_STDOUT, "Date", date_time, print_options | crm_time_log_date | crm_time_log_timeofday); } if (duration_s) { duration = crm_time_parse_duration(duration_s); if (duration == NULL) { fprintf(stderr, "Invalid duration specified: %s\n", duration_s); crm_exit(CRM_EX_INVALID_PARAM); } crm_time_log(LOG_TRACE, "Duration", duration, crm_time_log_duration); crm_time_log(LOG_STDOUT, "Duration", duration, print_options | crm_time_log_duration); } if (period_s) { crm_time_period_t *period = crm_time_parse_period(period_s); if (period == NULL) { fprintf(stderr, "Invalid interval specified: %s\n", period_s); crm_exit(CRM_EX_INVALID_PARAM); } log_time_period(LOG_TRACE, period, print_options | crm_time_log_date | crm_time_log_timeofday); log_time_period(LOG_STDOUT, period, print_options | crm_time_log_date | crm_time_log_timeofday); crm_time_free_period(period); } if (date_time && duration) { crm_time_t *later = crm_time_add(date_time, duration); if (later == NULL) { fprintf(stderr, "Unable to calculate ending time of %s plus %s", date_time_s, duration_s); crm_exit(CRM_EX_SOFTWARE); } crm_time_log(LOG_TRACE, "Duration ends at", later, crm_time_ordinal | crm_time_log_date | crm_time_log_timeofday); crm_time_log(LOG_STDOUT, "Duration ends at", later, print_options | crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone); if (expected_s) { char *dt_s = crm_time_as_string(later, print_options | crm_time_log_date | crm_time_log_timeofday); if (safe_str_neq(expected_s, dt_s)) { exit_code = CRM_EX_ERROR; } free(dt_s); } crm_time_free(later); } else if (date_time && expected_s) { char *dt_s = crm_time_as_string(date_time, print_options | crm_time_log_date | crm_time_log_timeofday); if (safe_str_neq(expected_s, dt_s)) { exit_code = CRM_EX_ERROR; } free(dt_s); } crm_time_free(date_time); crm_time_free(duration); crm_exit(exit_code); } diff --git a/tools/notifyServicelogEvent.c b/tools/notifyServicelogEvent.c index 44a4c01b4c..b9f4b9a665 100644 --- a/tools/notifyServicelogEvent.c +++ b/tools/notifyServicelogEvent.c @@ -1,193 +1,205 @@ /* * Original copyright 2009 International Business Machines, IBM, Mark Hamzy * Later changes copyright 2009-2020 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. */ /* gcc -o notifyServicelogEvent `pkg-config --cflags servicelog-1` `pkg-config --libs servicelog-1` notifyServicelogEvent.c */ #include #include #include #include #include #include #include #include #include #include /* U64T ~ PRIu64, U64TS ~ SCNu64 */ #include #include #include typedef enum { STATUS_GREEN = 1, STATUS_YELLOW, STATUS_RED } STATUS; const char *status2char(STATUS status); STATUS event2status(struct sl_event *event); const char * status2char(STATUS status) { switch (status) { default: case STATUS_GREEN: return "green"; case STATUS_YELLOW: return "yellow"; case STATUS_RED: return "red"; } } STATUS event2status(struct sl_event * event) { STATUS status = STATUS_GREEN; crm_debug("Severity = %d, Disposition = %d", event->severity, event->disposition); /* @TBD */ if (event->severity == SL_SEV_WARNING) { status = STATUS_YELLOW; } if (event->disposition == SL_DISP_UNRECOVERABLE) { status = STATUS_RED; } return status; } -static struct crm_option long_options[] = { - /* Top-level Options */ - {"help", 0, 0, '?', "\tThis text"}, - {"version", 0, 0, '$', "\tVersion information"}, - {"-spacer-", 0, 0, '-', "\nUsage: notifyServicelogEvent event_id"}, - {"-spacer-", 0, 0, '-', - "\nWhere event_id is unique unsigned event identifier which is then passed into servicelog"}, - - {0, 0, 0, 0} +static pcmk__cli_option_t long_options[] = { + // long option, argument type, storage, short option, description, flags + { + "help", no_argument, NULL, '?', + "\tThis text", pcmk__option_default + }, + { + "version", no_argument, NULL, '$', + "\tVersion information", pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\nUsage: notifyServicelogEvent event_id", pcmk__option_paragraph + }, + { + "-spacer-", no_argument, NULL, '-', + "Where event_id is a unique unsigned event identifier which is " + "then passed into servicelog", + pcmk__option_paragraph + }, + { 0, 0, 0, 0 } }; int main(int argc, char *argv[]) { int argerr = 0; int flag; int index = 0; int rc = 0; servicelog *slog = NULL; struct sl_event *event = NULL; uint64_t event_id = 0; crm_log_cli_init("notifyServicelogEvent"); - crm_set_options(NULL, "event_id ", long_options, - "Gets called upon events written to servicelog database"); + pcmk__set_cli_options(NULL, "", long_options, + "handle events written to servicelog database"); if (argc < 2) { argerr++; } while (1) { - flag = crm_get_option(argc, argv, &index); + flag = pcmk__next_cli_option(argc, argv, &index, NULL); if (flag == -1) break; switch (flag) { case '?': case '$': - crm_help(flag, CRM_EX_OK); + pcmk__cli_help(flag, CRM_EX_OK); break; default: ++argerr; break; } } if (argc - optind != 1) { ++argerr; } if (argerr) { - crm_help('?', CRM_EX_USAGE); + pcmk__cli_help('?', CRM_EX_USAGE); } openlog("notifyServicelogEvent", LOG_NDELAY, LOG_USER); if (sscanf(argv[optind], "%" U64TS, &event_id) != 1) { crm_err("Error: could not read event_id from args!"); rc = 1; goto cleanup; } if (event_id == 0) { crm_err("Error: event_id is 0!"); rc = 1; goto cleanup; } rc = servicelog_open(&slog, 0); /* flags is one of SL_FLAG_xxx */ if (!slog) { crm_err("Error: servicelog_open failed, rc = %d", rc); rc = 1; goto cleanup; } if (slog) { rc = servicelog_event_get(slog, event_id, &event); } if (rc == 0) { STATUS status = STATUS_GREEN; const char *health_component = "#health-ipmi"; const char *health_status = NULL; crm_debug("Event id = %" U64T ", Log timestamp = %s, Event timestamp = %s", event_id, ctime(&(event->time_logged)), ctime(&(event->time_event))); status = event2status(event); health_status = status2char(status); if (health_status) { int attrd_rc; // @TODO pass pcmk__node_attr_remote when appropriate attrd_rc = pcmk__node_attr_request(NULL, 'v', NULL, health_component, health_status, NULL, NULL, NULL, NULL, pcmk__node_attr_none); crm_debug("Updating attribute ('%s', '%s') = %d", health_component, health_status, attrd_rc); } else { crm_err("Error: status2char failed, status = %d", status); rc = 1; } } else { crm_err("Error: servicelog_event_get failed, rc = %d", rc); } cleanup: if (event) { servicelog_event_free(event); } if (slog) { servicelog_close(slog); } closelog(); return rc; }