diff --git a/lib/pacemaker/pcmk_cluster_queries.c b/lib/pacemaker/pcmk_cluster_queries.c index c3ab1f51c0..b001d6508c 100644 --- a/lib/pacemaker/pcmk_cluster_queries.c +++ b/lib/pacemaker/pcmk_cluster_queries.c @@ -1,899 +1,903 @@ /* * Copyright 2020-2024 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 // xmlNode #include #include #include #include #include #include #include #include #include #include #include #include //! Object to store node info from the controller API typedef struct { /* Adapted from pcmk_controld_api_reply_t:data:node_info. * (char **) are convenient here for use within callbacks: we can skip * copying strings unless the caller passes a non-NULL value. */ uint32_t id; char **node_name; char **uuid; char **state; bool have_quorum; bool is_remote; } node_info_t; //! Object to store API results, a timeout, and an output object typedef struct { pcmk__output_t *out; bool show_output; int rc; unsigned int message_timeout_ms; enum pcmk_pacemakerd_state pcmkd_state; node_info_t node_info; } data_t; /*! * \internal * \brief Validate that an IPC API event is a good reply * * \param[in,out] data API results and options * \param[in] api IPC API connection * \param[in] event_type Type of event that occurred * \param[in] status Event status * * \return Standard Pacemaker return code */ static int validate_reply_event(data_t *data, const pcmk_ipc_api_t *api, enum pcmk_ipc_event event_type, crm_exit_t status) { pcmk__output_t *out = data->out; switch (event_type) { case pcmk_ipc_event_reply: break; case pcmk_ipc_event_disconnect: if (data->rc == ECONNRESET) { // Unexpected out->err(out, "error: Lost connection to %s", pcmk_ipc_name(api, true)); } // Nothing bad but not the reply we're looking for return ENOTSUP; default: // Ditto return ENOTSUP; } if (status != CRM_EX_OK) { out->err(out, "error: Bad reply from %s: %s", pcmk_ipc_name(api, true), crm_exit_str(status)); data->rc = EBADMSG; return data->rc; } return pcmk_rc_ok; } /*! * \internal * \brief Validate that a controller API event is a good reply of expected type * * \param[in,out] data API results and options * \param[in] api Controller connection * \param[in] event_type Type of event that occurred * \param[in] status Event status * \param[in] event_data Event-specific data * \param[in] expected_type Expected reply type * * \return Standard Pacemaker return code */ static int validate_controld_reply(data_t *data, const pcmk_ipc_api_t *api, enum pcmk_ipc_event event_type, crm_exit_t status, const void *event_data, enum pcmk_controld_api_reply expected_type) { pcmk__output_t *out = data->out; int rc = pcmk_rc_ok; const pcmk_controld_api_reply_t *reply = NULL; rc = validate_reply_event(data, api, event_type, status); if (rc != pcmk_rc_ok) { return rc; } reply = (const pcmk_controld_api_reply_t *) event_data; if (reply->reply_type != expected_type) { out->err(out, "error: Unexpected reply type '%s' from controller", pcmk__controld_api_reply2str(reply->reply_type)); data->rc = EBADMSG; return data->rc; } return pcmk_rc_ok; } /*! * \internal * \brief Validate that a \p pacemakerd API event is a good reply of expected * type * * \param[in,out] data API results and options * \param[in] api \p pacemakerd connection * \param[in] event_type Type of event that occurred * \param[in] status Event status * \param[in] event_data Event-specific data * \param[in] expected_type Expected reply type * * \return Standard Pacemaker return code */ static int validate_pcmkd_reply(data_t *data, const pcmk_ipc_api_t *api, enum pcmk_ipc_event event_type, crm_exit_t status, const void *event_data, enum pcmk_pacemakerd_api_reply expected_type) { pcmk__output_t *out = data->out; const pcmk_pacemakerd_api_reply_t *reply = NULL; int rc = validate_reply_event(data, api, event_type, status); if (rc != pcmk_rc_ok) { return rc; } reply = (const pcmk_pacemakerd_api_reply_t *) event_data; if (reply->reply_type != expected_type) { out->err(out, "error: Unexpected reply type '%s' from pacemakerd", pcmk__pcmkd_api_reply2str(reply->reply_type)); data->rc = EBADMSG; return data->rc; } return pcmk_rc_ok; } /*! * \internal * \brief Process a controller status IPC event * * \param[in,out] controld_api Controller connection * \param[in] event_type Type of event that occurred * \param[in] status Event status * \param[in,out] event_data \p pcmk_controld_api_reply_t object containing * event-specific data * \param[in,out] user_data \p data_t object for API results and options */ static void controller_status_event_cb(pcmk_ipc_api_t *controld_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { data_t *data = (data_t *) user_data; pcmk__output_t *out = data->out; const pcmk_controld_api_reply_t *reply = NULL; int rc = validate_controld_reply(data, controld_api, event_type, status, event_data, pcmk_controld_reply_ping); if (rc != pcmk_rc_ok) { return; } reply = (const pcmk_controld_api_reply_t *) event_data; out->message(out, "health", reply->data.ping.sys_from, reply->host_from, reply->data.ping.fsa_state, reply->data.ping.result); data->rc = pcmk_rc_ok; } /*! * \internal * \brief Process a designated controller IPC event * * \param[in,out] controld_api Controller connection * \param[in] event_type Type of event that occurred * \param[in] status Event status * \param[in,out] event_data \p pcmk_controld_api_reply_t object containing * event-specific data * \param[in,out] user_data \p data_t object for API results and options */ static void designated_controller_event_cb(pcmk_ipc_api_t *controld_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { data_t *data = (data_t *) user_data; pcmk__output_t *out = data->out; const pcmk_controld_api_reply_t *reply = NULL; int rc = validate_controld_reply(data, controld_api, event_type, status, event_data, pcmk_controld_reply_ping); if (rc != pcmk_rc_ok) { return; } reply = (const pcmk_controld_api_reply_t *) event_data; out->message(out, "dc", reply->host_from); data->rc = pcmk_rc_ok; } /*! * \internal * \brief Process a node info IPC event * * \param[in,out] controld_api Controller connection * \param[in] event_type Type of event that occurred * \param[in] status Event status * \param[in,out] event_data \p pcmk_controld_api_reply_t object containing * event-specific data * \param[in,out] user_data \p data_t object for API results and options */ static void node_info_event_cb(pcmk_ipc_api_t *controld_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { data_t *data = (data_t *) user_data; pcmk__output_t *out = data->out; const pcmk_controld_api_reply_t *reply = NULL; int rc = validate_controld_reply(data, controld_api, event_type, status, event_data, pcmk_controld_reply_info); if (rc != pcmk_rc_ok) { return; } reply = (const pcmk_controld_api_reply_t *) event_data; if (reply->data.node_info.uname == NULL) { out->err(out, "Node is not known to cluster"); data->rc = pcmk_rc_node_unknown; return; } data->node_info.have_quorum = reply->data.node_info.have_quorum; data->node_info.is_remote = reply->data.node_info.is_remote; data->node_info.id = (uint32_t) reply->data.node_info.id; pcmk__str_update(data->node_info.node_name, reply->data.node_info.uname); pcmk__str_update(data->node_info.uuid, reply->data.node_info.uuid); pcmk__str_update(data->node_info.state, reply->data.node_info.state); if (data->show_output) { out->message(out, "node-info", reply->data.node_info.id, reply->data.node_info.uname, reply->data.node_info.uuid, reply->data.node_info.state, reply->data.node_info.have_quorum, reply->data.node_info.is_remote); } data->rc = pcmk_rc_ok; } /*! * \internal * \brief Process a \p pacemakerd status IPC event * * \param[in,out] pacemakerd_api \p pacemakerd connection * \param[in] event_type Type of event that occurred * \param[in] status Event status * \param[in,out] event_data \p pcmk_pacemakerd_api_reply_t object * containing event-specific data * \param[in,out] user_data \p data_t object for API results and options */ static void pacemakerd_event_cb(pcmk_ipc_api_t *pacemakerd_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { data_t *data = user_data; pcmk__output_t *out = data->out; const pcmk_pacemakerd_api_reply_t *reply = NULL; int rc = validate_pcmkd_reply(data, pacemakerd_api, event_type, status, event_data, pcmk_pacemakerd_reply_ping); if (rc != pcmk_rc_ok) { return; } // Parse desired information from reply reply = (const pcmk_pacemakerd_api_reply_t *) event_data; data->pcmkd_state = reply->data.ping.state; data->rc = pcmk_rc_ok; if (!data->show_output) { return; } if (reply->data.ping.status == pcmk_rc_ok) { out->message(out, "pacemakerd-health", reply->data.ping.sys_from, reply->data.ping.state, NULL, reply->data.ping.last_good); } else { out->message(out, "pacemakerd-health", reply->data.ping.sys_from, reply->data.ping.state, "query failed", time(NULL)); } } static pcmk_ipc_api_t * ipc_connect(data_t *data, enum pcmk_ipc_server server, pcmk_ipc_callback_t cb, enum pcmk_ipc_dispatch dispatch_type, bool eremoteio_ok) { int rc; pcmk__output_t *out = data->out; pcmk_ipc_api_t *api = NULL; rc = pcmk_new_ipc_api(&api, server); if (api == NULL) { out->err(out, "error: Could not connect to %s: %s", pcmk_ipc_name(api, true), pcmk_rc_str(rc)); data->rc = rc; return NULL; } if (cb != NULL) { pcmk_register_ipc_callback(api, cb, data); } rc = pcmk__connect_ipc(api, dispatch_type, 5); if (rc != pcmk_rc_ok) { if (rc == EREMOTEIO) { data->pcmkd_state = pcmk_pacemakerd_state_remote; if (eremoteio_ok) { /* EREMOTEIO may be expected and acceptable for some callers * on a Pacemaker Remote node */ crm_debug("Ignoring %s connection failure: No " "Pacemaker Remote connection", pcmk_ipc_name(api, true)); rc = pcmk_rc_ok; } else { out->err(out, "error: Could not connect to %s: %s", pcmk_ipc_name(api, true), pcmk_rc_str(rc)); } } data->rc = rc; pcmk_free_ipc_api(api); return NULL; } return api; } /*! * \internal * \brief Poll an IPC API connection until timeout or a reply is received * * \param[in,out] data API results and options * \param[in,out] api IPC API connection * \param[in] on_node If not \p NULL, name of the node to poll (used only * for logging) * * \note Sets the \p rc member of \p data on error */ static void poll_until_reply(data_t *data, pcmk_ipc_api_t *api, const char *on_node) { pcmk__output_t *out = data->out; uint64_t start_nsec = qb_util_nano_current_get(); uint64_t end_nsec = 0; uint64_t elapsed_ms = 0; uint64_t remaining_ms = data->message_timeout_ms; while (remaining_ms > 0) { int rc = pcmk_poll_ipc(api, remaining_ms); if (rc == EAGAIN) { // Poll timed out break; } if (rc != pcmk_rc_ok) { out->err(out, "error: Failed to poll %s API%s%s: %s", pcmk_ipc_name(api, true), (on_node != NULL)? " on " : "", pcmk__s(on_node, ""), pcmk_rc_str(rc)); data->rc = rc; return; } pcmk_dispatch_ipc(api); if (data->rc != EAGAIN) { // Received a reply return; } end_nsec = qb_util_nano_current_get(); elapsed_ms = (end_nsec - start_nsec) / QB_TIME_NS_IN_MSEC; remaining_ms = data->message_timeout_ms - elapsed_ms; } out->err(out, "error: Timed out after %ums waiting for reply from %s API%s%s", data->message_timeout_ms, pcmk_ipc_name(api, true), (on_node != NULL)? " on " : "", pcmk__s(on_node, "")); data->rc = EAGAIN; } /*! * \internal * \brief Get and output controller status * * \param[in,out] out Output object * \param[in] node_name Name of node whose status is desired * (\p NULL for DC) * \param[in] message_timeout_ms How long to wait for a reply from the * \p pacemaker-controld API. If 0, * \p pcmk_ipc_dispatch_sync will be used. * Otherwise, \p pcmk_ipc_dispatch_poll will * be used. * * \return Standard Pacemaker return code */ int pcmk__controller_status(pcmk__output_t *out, const char *node_name, unsigned int message_timeout_ms) { data_t data = { .out = out, .rc = EAGAIN, .message_timeout_ms = message_timeout_ms, }; enum pcmk_ipc_dispatch dispatch_type = pcmk_ipc_dispatch_poll; pcmk_ipc_api_t *controld_api = NULL; if (message_timeout_ms == 0) { dispatch_type = pcmk_ipc_dispatch_sync; } controld_api = ipc_connect(&data, pcmk_ipc_controld, controller_status_event_cb, dispatch_type, false); if (controld_api != NULL) { int rc = pcmk_controld_api_ping(controld_api, node_name); if (rc != pcmk_rc_ok) { out->err(out, "error: Could not ping controller API on %s: %s", pcmk__s(node_name, "DC"), pcmk_rc_str(rc)); data.rc = rc; } if (dispatch_type == pcmk_ipc_dispatch_poll) { poll_until_reply(&data, controld_api, pcmk__s(node_name, "DC")); } pcmk_free_ipc_api(controld_api); } return data.rc; } // Documented in header int pcmk_controller_status(xmlNodePtr *xml, const char *node_name, unsigned int message_timeout_ms) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } pcmk__register_lib_messages(out); rc = pcmk__controller_status(out, node_name, message_timeout_ms); pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } /*! * \internal * \brief Get and output designated controller node name * * \param[in,out] out Output object * \param[in] message_timeout_ms How long to wait for a reply from the * \p pacemaker-controld API. If 0, * \p pcmk_ipc_dispatch_sync will be used. * Otherwise, \p pcmk_ipc_dispatch_poll will * be used. * * \return Standard Pacemaker return code */ int pcmk__designated_controller(pcmk__output_t *out, unsigned int message_timeout_ms) { data_t data = { .out = out, .rc = EAGAIN, .message_timeout_ms = message_timeout_ms, }; enum pcmk_ipc_dispatch dispatch_type = pcmk_ipc_dispatch_poll; pcmk_ipc_api_t *controld_api = NULL; if (message_timeout_ms == 0) { dispatch_type = pcmk_ipc_dispatch_sync; } controld_api = ipc_connect(&data, pcmk_ipc_controld, designated_controller_event_cb, dispatch_type, false); if (controld_api != NULL) { int rc = pcmk_controld_api_ping(controld_api, NULL); if (rc != pcmk_rc_ok) { out->err(out, "error: Could not ping controller API on DC: %s", pcmk_rc_str(rc)); data.rc = rc; } if (dispatch_type == pcmk_ipc_dispatch_poll) { poll_until_reply(&data, controld_api, "DC"); } pcmk_free_ipc_api(controld_api); } return data.rc; } // Documented in header int pcmk_designated_controller(xmlNodePtr *xml, unsigned int message_timeout_ms) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } pcmk__register_lib_messages(out); rc = pcmk__designated_controller(out, message_timeout_ms); pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } /*! * \internal * \brief Get and optionally output node info corresponding to a node ID from * the controller * * \param[in,out] out Output object * \param[in,out] node_id ID of node whose name to get. If \p NULL * or 0, get the local node name. If not * \p NULL, store the true node ID here on * success. * \param[out] node_name If not \p NULL, where to store the node * name * \param[out] uuid If not \p NULL, where to store the node * UUID * \param[out] state If not \p NULL, where to store the * membership state * \param[out] is_remote If not \p NULL, where to store whether the * node is a Pacemaker Remote node * \param[out] have_quorum If not \p NULL, where to store whether the * node has quorum * \param[in] show_output Whether to show the node info * \param[in] message_timeout_ms How long to wait for a reply from the * \p pacemaker-controld API. If 0, * \p pcmk_ipc_dispatch_sync will be used. * Otherwise, \p pcmk_ipc_dispatch_poll will * be used. * * \return Standard Pacemaker return code * * \note The caller is responsible for freeing \p *node_name, \p *uuid, and * \p *state using \p free(). */ int pcmk__query_node_info(pcmk__output_t *out, uint32_t *node_id, char **node_name, char **uuid, char **state, bool *have_quorum, bool *is_remote, bool show_output, unsigned int message_timeout_ms) { data_t data = { .out = out, .show_output = show_output, .rc = EAGAIN, .message_timeout_ms = message_timeout_ms, .node_info = { .id = (node_id == NULL)? 0 : *node_id, .node_name = node_name, .uuid = uuid, .state = state, }, }; enum pcmk_ipc_dispatch dispatch_type = pcmk_ipc_dispatch_poll; pcmk_ipc_api_t *controld_api = NULL; if (node_name != NULL) { *node_name = NULL; } if (uuid != NULL) { *uuid = NULL; } if (state != NULL) { *state = NULL; } if (message_timeout_ms == 0) { dispatch_type = pcmk_ipc_dispatch_sync; } controld_api = ipc_connect(&data, pcmk_ipc_controld, node_info_event_cb, dispatch_type, false); if (controld_api != NULL) { int rc = pcmk_controld_api_node_info(controld_api, (node_id != NULL)? *node_id : 0); if (rc != pcmk_rc_ok) { out->err(out, "error: Could not send request to controller API on local " "node: %s", pcmk_rc_str(rc)); data.rc = rc; } if (dispatch_type == pcmk_ipc_dispatch_poll) { poll_until_reply(&data, controld_api, "local node"); } pcmk_free_ipc_api(controld_api); } if (data.rc != pcmk_rc_ok) { return data.rc; } // String outputs are set in callback if (node_id != NULL) { *node_id = data.node_info.id; } if (have_quorum != NULL) { *have_quorum = data.node_info.have_quorum; } if (is_remote != NULL) { *is_remote = data.node_info.is_remote; } return data.rc; } // Documented in header int pcmk_query_node_info(xmlNodePtr *xml, uint32_t *node_id, char **node_name, char **uuid, char **state, bool *have_quorum, bool *is_remote, bool show_output, unsigned int message_timeout_ms) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; CRM_ASSERT(node_name != NULL); rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } pcmk__register_lib_messages(out); rc = pcmk__query_node_info(out, node_id, node_name, uuid, state, have_quorum, is_remote, show_output, message_timeout_ms); pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } /*! * \internal * \brief Get and optionally output \p pacemakerd status * * \param[in,out] out Output object * \param[in] ipc_name IPC name for request * \param[in] message_timeout_ms How long to wait for a reply from the * \p pacemakerd API. If 0, * \p pcmk_ipc_dispatch_sync will be used. * Otherwise, \p pcmk_ipc_dispatch_poll will * be used. * \param[in] show_output Whether to output the \p pacemakerd state * \param[out] state Where to store the \p pacemakerd state, if * not \p NULL * * \return Standard Pacemaker return code * * \note This function sets \p state to \p pcmk_pacemakerd_state_remote and * returns \p pcmk_rc_ok if the IPC connection attempt returns * \p EREMOTEIO. That code indicates that this is a Pacemaker Remote node * with \p pacemaker-remoted running. The node may be connected to the * cluster. */ int pcmk__pacemakerd_status(pcmk__output_t *out, const char *ipc_name, unsigned int message_timeout_ms, bool show_output, enum pcmk_pacemakerd_state *state) { data_t data = { .out = out, .show_output = show_output, .rc = EAGAIN, .message_timeout_ms = message_timeout_ms, .pcmkd_state = pcmk_pacemakerd_state_invalid, }; enum pcmk_ipc_dispatch dispatch_type = pcmk_ipc_dispatch_poll; pcmk_ipc_api_t *pacemakerd_api = NULL; if (message_timeout_ms == 0) { dispatch_type = pcmk_ipc_dispatch_sync; } pacemakerd_api = ipc_connect(&data, pcmk_ipc_pacemakerd, pacemakerd_event_cb, dispatch_type, true); if (pacemakerd_api != NULL) { int rc = pcmk_pacemakerd_api_ping(pacemakerd_api, ipc_name); if (rc != pcmk_rc_ok) { out->err(out, "error: Could not ping launcher API: %s", pcmk_rc_str(rc)); data.rc = rc; } if (dispatch_type == pcmk_ipc_dispatch_poll) { poll_until_reply(&data, pacemakerd_api, NULL); } pcmk_free_ipc_api(pacemakerd_api); } else if ((data.pcmkd_state == pcmk_pacemakerd_state_remote) && show_output) { // No API connection so the callback wasn't run out->message(out, "pacemakerd-health", NULL, data.pcmkd_state, NULL, time(NULL)); } if (state != NULL) { *state = data.pcmkd_state; } return data.rc; } // Documented in header int pcmk_pacemakerd_status(xmlNodePtr *xml, const char *ipc_name, unsigned int message_timeout_ms) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } pcmk__register_lib_messages(out); rc = pcmk__pacemakerd_status(out, ipc_name, message_timeout_ms, true, NULL); pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } /* user data for looping through remote node xpath searches */ struct node_data { pcmk__output_t *out; int found; const char *field; /* XML attribute to check for node name */ const char *type; bool bash_export; }; static void remote_node_print_helper(xmlNode *result, void *user_data) { struct node_data *data = user_data; pcmk__output_t *out = data->out; const char *name = crm_element_value(result, PCMK_XA_UNAME); const char *id = crm_element_value(result, data->field); // node name and node id are the same for remote/guest nodes out->message(out, "crmadmin-node", data->type, pcmk__s(name, id), id, data->bash_export); data->found++; } // \return Standard Pacemaker return code int pcmk__list_nodes(pcmk__output_t *out, const char *node_types, bool bash_export) { xmlNode *xml_node = NULL; int rc; rc = cib__signon_query(out, NULL, &xml_node); if (rc == pcmk_rc_ok) { struct node_data data = { .out = out, .found = 0, .bash_export = bash_export }; - out->begin_list(out, NULL, NULL, "nodes"); + /* PCMK_XE_NODES acts as the list's element name for CLI tools that + * force --xml-simple-list. Otherwise PCMK_XE_NODES is the value of the + * list's PCMK_XA_NAME attribute. + */ + out->begin_list(out, NULL, NULL, PCMK_XE_NODES); if (!pcmk__str_empty(node_types) && strstr(node_types, "all")) { node_types = NULL; } if (pcmk__str_empty(node_types) || strstr(node_types, "cluster")) { data.field = PCMK_XA_ID; data.type = "cluster"; crm_foreach_xpath_result(xml_node, PCMK__XP_MEMBER_NODE_CONFIG, remote_node_print_helper, &data); } if (pcmk__str_empty(node_types) || strstr(node_types, "guest")) { data.field = PCMK_XA_VALUE; data.type = "guest"; crm_foreach_xpath_result(xml_node, PCMK__XP_GUEST_NODE_CONFIG, remote_node_print_helper, &data); } if (pcmk__str_empty(node_types) || pcmk__str_eq(node_types, ",|^remote", pcmk__str_regex)) { data.field = PCMK_XA_ID; data.type = "remote"; crm_foreach_xpath_result(xml_node, PCMK__XP_REMOTE_NODE_CONFIG, remote_node_print_helper, &data); } out->end_list(out); if (data.found == 0) { out->info(out, "No nodes configured"); } free_xml(xml_node); } return rc; } int pcmk_list_nodes(xmlNodePtr *xml, const char *node_types) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } pcmk__register_lib_messages(out); rc = pcmk__list_nodes(out, node_types, FALSE); pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } diff --git a/tools/attrd_updater.c b/tools/attrd_updater.c index 5f91356428..6cc1cd70cb 100644 --- a/tools/attrd_updater.c +++ b/tools/attrd_updater.c @@ -1,524 +1,524 @@ /* * Copyright 2004-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SUMMARY "query and update Pacemaker node attributes" static pcmk__supported_format_t formats[] = { PCMK__SUPPORTED_FORMAT_NONE, PCMK__SUPPORTED_FORMAT_TEXT, PCMK__SUPPORTED_FORMAT_XML, { NULL, NULL, NULL } }; GError *error = NULL; bool printed_values = false; struct { char command; gchar *attr_dampen; gchar *attr_name; gchar *attr_pattern; gchar *attr_node; gchar *attr_set; char *attr_value; uint32_t attr_options; gboolean query_all; gboolean quiet; } options = { .attr_options = pcmk__node_attr_none, .command = 'Q', }; static gboolean command_cb (const gchar *option_name, const gchar *optarg, gpointer data, GError **err) { pcmk__str_update(&options.attr_value, optarg); if (pcmk__str_any_of(option_name, "--update-both", "-B", NULL)) { options.command = 'B'; } else if (pcmk__str_any_of(option_name, "--delete", "-D", NULL)) { options.command = 'D'; } else if (pcmk__str_any_of(option_name, "--query", "-Q", NULL)) { options.command = 'Q'; } else if (pcmk__str_any_of(option_name, "--refresh", "-R", NULL)) { options.command = 'R'; } else if (pcmk__str_any_of(option_name, "--update", "-U", "-v", NULL)) { options.command = 'U'; } else if (pcmk__str_any_of(option_name, "--update-delay", "-Y", NULL)) { options.command = 'Y'; } return TRUE; } static gboolean private_cb (const gchar *option_name, const gchar *optarg, gpointer data, GError **err) { pcmk__set_node_attr_flags(options.attr_options, pcmk__node_attr_private); return TRUE; } static gboolean section_cb (const gchar *option_name, const gchar *optarg, gpointer data, GError **err) { - if (pcmk__str_any_of(optarg, "nodes", "forever", NULL)) { + if (pcmk__str_any_of(optarg, PCMK_XE_NODES, "forever", NULL)) { pcmk__set_node_attr_flags(options.attr_options, pcmk__node_attr_perm); - } else if (pcmk__str_any_of(optarg, "status", "reboot", NULL)) { + } else if (pcmk__str_any_of(optarg, PCMK_XE_STATUS, "reboot", NULL)) { pcmk__clear_node_attr_flags(options.attr_options, pcmk__node_attr_perm); } else { g_set_error(err, PCMK__EXITC_ERROR, CRM_EX_USAGE, "Unknown value for --lifetime: %s", optarg); return FALSE; } return TRUE; } static gboolean attr_set_type_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (pcmk__str_any_of(option_name, "-z", "--utilization", NULL)) { pcmk__set_node_attr_flags(options.attr_options, pcmk__node_attr_utilization); } return TRUE; } static gboolean wait_cb (const gchar *option_name, const gchar *optarg, gpointer data, GError **err) { if (pcmk__str_eq(optarg, "no", pcmk__str_none)) { pcmk__clear_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local | pcmk__node_attr_sync_cluster); return TRUE; } else if (pcmk__str_eq(optarg, PCMK__VALUE_LOCAL, pcmk__str_none)) { pcmk__clear_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local | pcmk__node_attr_sync_cluster); pcmk__set_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local); return TRUE; } else if (pcmk__str_eq(optarg, PCMK__VALUE_CLUSTER, pcmk__str_none)) { pcmk__clear_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local | pcmk__node_attr_sync_cluster); pcmk__set_node_attr_flags(options.attr_options, pcmk__node_attr_sync_cluster); return TRUE; } else { g_set_error(err, PCMK__EXITC_ERROR, CRM_EX_USAGE, "--wait= must be one of 'no', 'local', 'cluster'"); return FALSE; } } #define INDENT " " static GOptionEntry required_entries[] = { { "name", 'n', 0, G_OPTION_ARG_STRING, &options.attr_name, "The attribute's name", "NAME" }, { "pattern", 'P', 0, G_OPTION_ARG_STRING, &options.attr_pattern, "Operate on all attributes matching this pattern\n" INDENT "(with -B, -D, -U, or -Y)", "PATTERN" }, { NULL } }; static GOptionEntry command_entries[] = { { "update", 'U', 0, G_OPTION_ARG_CALLBACK, command_cb, "Update attribute's value. Required: -n/--name or -P/--pattern.\n" INDENT "Optional: -d/--delay (if specified, the delay will be used if\n" INDENT "the attribute needs to be created, but ignored if the\n" INDENT "attribute already exists), -s/--set, -p/--private, -W/--wait,\n" INDENT "-z/--utilization.", "VALUE" }, { "update-both", 'B', 0, G_OPTION_ARG_CALLBACK, command_cb, "Update attribute's value and time to wait (dampening) in\n" INDENT "pacemaker-attrd. If this causes the value or dampening to change,\n" INDENT "the attribute will also be written to the cluster configuration,\n" INDENT "so be aware that repeatedly changing the dampening reduces its\n" INDENT "effectiveness.\n" INDENT "Requires -d/--delay", "VALUE" }, { "update-delay", 'Y', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Update attribute's dampening in pacemaker-attrd. If this causes\n" INDENT "the dampening to change, the attribute will also be written\n" INDENT "to the cluster configuration, so be aware that repeatedly\n" INDENT "changing the dampening reduces its effectiveness.\n" INDENT "Requires -d/--delay", NULL }, { "query", 'Q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Query the attribute's value from pacemaker-attrd", NULL }, { "delete", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Unset attribute from pacemaker-attrd. At the moment, there is no way\n" INDENT "to remove an attribute. This option will instead set its value\n" INDENT "to the empty string.", NULL }, { "refresh", 'R', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "(Advanced) Force the pacemaker-attrd daemon to resend all current\n" INDENT "values to the CIB", NULL }, { NULL } }; static GOptionEntry addl_entries[] = { { "delay", 'd', 0, G_OPTION_ARG_STRING, &options.attr_dampen, "The time to wait (dampening) in seconds for further changes\n" INDENT "before sending to the CIB", "SECONDS" }, { "set", 's', 0, G_OPTION_ARG_STRING, &options.attr_set, "(Advanced) The attribute set in which to place the value", "SET" }, { "node", 'N', 0, G_OPTION_ARG_STRING, &options.attr_node, "Set the attribute for the named node (instead of the local one)", "NODE" }, { "all", 'A', 0, G_OPTION_ARG_NONE, &options.query_all, "Show values of the attribute for all nodes (query only)", NULL }, { "lifetime", 'l', 0, G_OPTION_ARG_CALLBACK, section_cb, "(Not yet implemented) Lifetime of the node attribute (silently\n" INDENT "ignored by cluster)", "SECTION" }, { "private", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, private_cb, "If this creates a new attribute, never write the attribute to CIB", NULL }, { "wait", 'W', 0, G_OPTION_ARG_CALLBACK, wait_cb, "Wait for some event to occur before returning. Values are 'no' (wait\n" INDENT "only for the attribute daemon to acknowledge the request),\n" INDENT "'local' (wait until the change has propagated to where a local\n" INDENT "query will return the request value, or the value set by a\n" INDENT "later request), or 'cluster' (wait until the change has propagated\n" INDENT "to where a query anywhere on the cluster will return the requested\n" INDENT "value, or the value set by a later request). Default is 'no'.", "UNTIL" }, { "utilization", 'z', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, attr_set_type_cb, "When creating a new attribute, create it as a node utilization attribute\n" INDENT "instead of an instance attribute. If the attribute already exists,\n" INDENT "its existing type (utilization vs. instance) will be used regardless.\n" INDENT "(with -B, -U, -Y)", NULL }, { NULL } }; static GOptionEntry deprecated_entries[] = { { "quiet", 'q', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.quiet, NULL, NULL }, { "update", 'v', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, command_cb, NULL, NULL }, { "section", 'S', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, section_cb, NULL, NULL }, { NULL } }; static int send_attrd_query(pcmk__output_t *out, const char *attr_name, const char *attr_node, gboolean query_all); static int send_attrd_update(char command, const char *attr_node, const char *attr_name, const char *attr_value, const char *attr_set, const char *attr_dampen, uint32_t attr_options); static bool pattern_used_correctly(void) { /* --pattern can only be used with: * -B (update-both), -D (delete), -U (update), or -Y (update-delay) */ return options.command == 'B' || options.command == 'D' || options.command == 'U' || options.command == 'Y'; } static GOptionContext * build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) { GOptionContext *context = NULL; context = pcmk__build_arg_context(args, "text (default), xml", group, NULL); pcmk__add_arg_group(context, "required", "Required Arguments:", "Show required arguments", required_entries); pcmk__add_arg_group(context, "command", "Command:", "Show command options (mutually exclusive)", command_entries); pcmk__add_arg_group(context, "additional", "Additional Options:", "Show additional options", addl_entries); pcmk__add_arg_group(context, "deprecated", "Deprecated Options:", "Show deprecated options", deprecated_entries); return context; } int main(int argc, char **argv) { int rc = pcmk_rc_ok; crm_exit_t exit_code = CRM_EX_OK; pcmk__output_t *out = NULL; GOptionGroup *output_group = NULL; pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); GOptionContext *context = build_arg_context(args, &output_group); gchar **processed_args = pcmk__cmdline_preproc(argv, "dlnsvBNUS"); pcmk__register_formats(output_group, formats); if (!g_option_context_parse_strv(context, &processed_args, &error)) { exit_code = CRM_EX_USAGE; goto done; } pcmk__cli_init_logging("attrd_updater", args->verbosity); rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv); if (rc != pcmk_rc_ok) { exit_code = CRM_EX_ERROR; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Error creating output format %s: %s", args->output_ty, pcmk_rc_str(rc)); goto done; } if (args->version) { out->version(out, false); goto done; } if (options.attr_pattern) { if (options.attr_name) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Error: --name and --pattern cannot be used at the same time"); goto done; } if (!pattern_used_correctly()) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Error: pattern can only be used with delete or update"); goto done; } g_free(options.attr_name); options.attr_name = options.attr_pattern; options.attr_options |= pcmk__node_attr_pattern; } if (options.command != 'R' && options.attr_name == NULL) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Command requires --name or --pattern argument"); goto done; } else if ((options.command == 'B'|| options.command == 'Y') && options.attr_dampen == NULL) { out->info(out, "Warning: '%c' command given without required --delay", options.command); } pcmk__register_lib_messages(out); if (options.command == 'Q') { int rc = send_attrd_query(out, options.attr_name, options.attr_node, options.query_all); exit_code = pcmk_rc2exitc(rc); } 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". */ int rc = send_attrd_update(options.command, options.attr_node, options.attr_name, options.attr_value, options.attr_set, options.attr_dampen, options.attr_options); exit_code = pcmk_rc2exitc(rc); } done: g_strfreev(processed_args); pcmk__free_arg_context(context); g_free(options.attr_dampen); g_free(options.attr_name); g_free(options.attr_node); g_free(options.attr_set); free(options.attr_value); pcmk__output_and_clear_error(&error, out); if (out != NULL) { out->finish(out, exit_code, true, NULL); pcmk__output_free(out); } pcmk__unregister_formats(); crm_exit(exit_code); } /*! * \brief Print the attribute values in a pacemaker-attrd XML query reply * * \param[in,out] out Output object * \param[in] reply List of attribute name/value pairs * * \return true if any values were printed */ static void print_attrd_values(pcmk__output_t *out, const GList *reply) { for (const GList *iter = reply; iter != NULL; iter = iter->next) { const pcmk__attrd_query_pair_t *pair = iter->data; out->message(out, "attribute", NULL, NULL, pair->name, pair->value, pair->node); printed_values = true; } } static void attrd_event_cb(pcmk_ipc_api_t *attrd_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { pcmk__output_t *out = (pcmk__output_t *) user_data; pcmk__attrd_api_reply_t *reply = event_data; if (event_type != pcmk_ipc_event_reply || status != CRM_EX_OK) { return; } /* Print the values from the reply. */ if (reply->reply_type == pcmk__attrd_reply_query) { print_attrd_values(out, reply->data.pairs); } } /*! * \brief Submit a query to pacemaker-attrd and print reply * * \param[in,out] out Output object * \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 * * \return Standard Pacemaker return code */ static int send_attrd_query(pcmk__output_t *out, const char *attr_name, const char *attr_node, gboolean query_all) { uint32_t options = pcmk__node_attr_none; pcmk_ipc_api_t *attrd_api = NULL; int rc = pcmk_rc_ok; // Create attrd IPC object rc = pcmk_new_ipc_api(&attrd_api, pcmk_ipc_attrd); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, "Could not connect to attrd: %s", pcmk_rc_str(rc)); return ENOTCONN; } pcmk_register_ipc_callback(attrd_api, attrd_event_cb, out); // Connect to attrd (without main loop) rc = pcmk__connect_ipc(attrd_api, pcmk_ipc_dispatch_sync, 5); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, "Could not connect to %s: %s", pcmk_ipc_name(attrd_api, true), pcmk_rc_str(rc)); pcmk_free_ipc_api(attrd_api); return rc; } /* Decide which node(s) to query */ if (query_all == TRUE) { options |= pcmk__node_attr_query_all; } rc = pcmk__attrd_api_query(attrd_api, attr_node, attr_name, options); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, "Could not query value of %s: %s (%d)", attr_name, pcmk_rc_str(rc), rc); } else if (!printed_values) { rc = pcmk_rc_schema_validation; g_set_error(&error, PCMK__RC_ERROR, rc, "Could not query value of %s: attribute does not exist", attr_name); } pcmk_disconnect_ipc(attrd_api); pcmk_free_ipc_api(attrd_api); return rc; } static int send_attrd_update(char command, const char *attr_node, const char *attr_name, const char *attr_value, const char *attr_set, const char *attr_dampen, uint32_t attr_options) { int rc = pcmk_rc_ok; switch (command) { case 'B': rc = pcmk__attrd_api_update(NULL, attr_node, attr_name, attr_value, attr_dampen, attr_set, NULL, attr_options | pcmk__node_attr_value | pcmk__node_attr_delay); break; case 'D': rc = pcmk__attrd_api_delete(NULL, attr_node, attr_name, attr_options); break; case 'R': rc = pcmk__attrd_api_refresh(NULL, attr_node); break; case 'U': rc = pcmk__attrd_api_update(NULL, attr_node, attr_name, attr_value, attr_dampen, attr_set, NULL, attr_options | pcmk__node_attr_value); break; case 'Y': rc = pcmk__attrd_api_update(NULL, attr_node, attr_name, NULL, attr_dampen, attr_set, NULL, attr_options | pcmk__node_attr_delay); break; } if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, "Could not update %s=%s: %s (%d)", attr_name, attr_value, pcmk_rc_str(rc), rc); } return rc; }