diff --git a/include/pacemaker.h b/include/pacemaker.h index 268d5ddea1..d3380ad4e4 100644 --- a/include/pacemaker.h +++ b/include/pacemaker.h @@ -1,643 +1,654 @@ /* * Copyright 2019-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. */ #ifndef PCMK__PACEMAKER__H # define PCMK__PACEMAKER__H # include # include # include # include # include #ifdef __cplusplus extern "C" { #endif /** * \file * \brief High Level API * \ingroup pacemaker */ /*! * \brief Modify operation of running a cluster simulation. */ enum pcmk_sim_flags { pcmk_sim_none = 0, pcmk_sim_all_actions = 1 << 0, pcmk_sim_show_pending = 1 << 1, pcmk_sim_process = 1 << 2, pcmk_sim_show_scores = 1 << 3, pcmk_sim_show_utilization = 1 << 4, pcmk_sim_simulate = 1 << 5, pcmk_sim_sanitized = 1 << 6, pcmk_sim_verbose = 1 << 7, }; /*! * \brief Synthetic cluster events that can be injected into the cluster * for running simulations. */ typedef struct { /*! A list of node names (gchar *) to simulate bringing online */ GList *node_up; /*! A list of node names (gchar *) to simulate bringing offline */ GList *node_down; /*! A list of node names (gchar *) to simulate failing */ GList *node_fail; /*! A list of operations (gchar *) to inject. The format of these strings * is described in the "Operation Specification" section of crm_simulate * help output. */ GList *op_inject; /*! A list of operations (gchar *) that should return a given error code * if they fail. The format of these strings is described in the * "Operation Specification" section of crm_simulate help output. */ GList *op_fail; /*! A list of tickets (gchar *) to simulate granting */ GList *ticket_grant; /*! A list of tickets (gchar *) to simulate revoking */ GList *ticket_revoke; /*! A list of tickets (gchar *) to simulate putting on standby */ GList *ticket_standby; /*! A list of tickets (gchar *) to simulate activating */ GList *ticket_activate; /*! Does the cluster have an active watchdog device? */ char *watchdog; /*! Does the cluster have quorum? */ char *quorum; } pcmk_injections_t; /*! * \brief Get and output controller status * * \param[in,out] xml Destination for the result, as an XML tree * \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(xmlNodePtr *xml, const char *node_name, unsigned int message_timeout_ms); /*! * \brief Get and output designated controller node name * * \param[in,out] xml Destination for the result, as an XML tree * \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(xmlNodePtr *xml, unsigned int message_timeout_ms); /*! * \brief Free a :pcmk_injections_t structure * * \param[in,out] injections The structure to be freed */ void pcmk_free_injections(pcmk_injections_t *injections); /*! * \brief Get and optionally output node info corresponding to a node ID from * the controller * * \param[in,out] xml Destination for the result, as an XML tree * \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 output 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(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); /*! * \brief Get the node name corresponding to a node ID from the controller * * \param[in,out] xml Destination for the result, as an XML tree * \param[in,out] node_id ID of node whose name to get (or 0 for the * local node) * \param[out] node_name If not \p NULL, where to store the node * name * \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 using \p free(). */ static inline int pcmk_query_node_name(xmlNodePtr *xml, uint32_t node_id, char **node_name, unsigned int message_timeout_ms) { return pcmk_query_node_info(xml, &node_id, node_name, NULL, NULL, NULL, NULL, false, message_timeout_ms); } /*! * \brief Get and output \p pacemakerd status * * \param[in,out] xml Destination for the result, as an XML tree * \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. * * \return Standard Pacemaker return code */ int pcmk_pacemakerd_status(xmlNodePtr *xml, const char *ipc_name, unsigned int message_timeout_ms); /*! * \brief Remove a resource * * \param[in,out] xml Destination for the result, as an XML tree * \param[in] rsc_id Resource to remove * \param[in] rsc_type Type of the resource ("primitive", "group", etc.) * * \return Standard Pacemaker return code * \note This function will return \p pcmk_rc_ok if \p rsc_id doesn't exist * or if \p rsc_type is incorrect for \p rsc_id (deleting something * that doesn't exist always succeeds). */ int pcmk_resource_delete(xmlNodePtr *xml, const char *rsc_id, const char *rsc_type); /*! * \brief Calculate and output resource operation digests * * \param[out] xml Where to store XML with result * \param[in,out] rsc Resource to calculate digests for * \param[in] node Node whose operation history should be used * \param[in] overrides Hash table of configuration parameters to override * \param[in] scheduler Scheduler data (with status) * * \return Standard Pacemaker return code */ int pcmk_resource_digests(xmlNodePtr *xml, pcmk_resource_t *rsc, const pcmk_node_t *node, GHashTable *overrides, pcmk_scheduler_t *scheduler); /*! * \brief Simulate a cluster's response to events * * This high-level function essentially implements crm_simulate(8). It operates * on an input CIB file and various lists of events that can be simulated. It * optionally writes out a variety of artifacts to show the results of the * simulation. Output can be modified with various flags. * * \param[in,out] xml The destination for the result, as an XML tree * \param[in,out] scheduler Scheduler data * \param[in] injections A structure containing cluster events * (node up/down, tickets, injected operations) * \param[in] flags A bitfield of :pcmk_sim_flags to modify * operation of the simulation * \param[in] section_opts Which portions of the cluster status output * should be displayed? * \param[in] use_date Date to set the cluster's time to (may be NULL) * \param[in] input_file The source CIB file, which may be overwritten by * this function (may be NULL) * \param[in] graph_file Where to write the XML-formatted transition graph * (may be NULL, in which case no file will be * written) * \param[in] dot_file Where to write the dot(1) formatted transition * graph (may be NULL, in which case no file will * be written) * * \return Standard Pacemaker return code */ int pcmk_simulate(xmlNodePtr *xml, pcmk_scheduler_t *scheduler, const pcmk_injections_t *injections, unsigned int flags, unsigned int section_opts, const char *use_date, const char *input_file, const char *graph_file, const char *dot_file); /*! * \brief Verify that a CIB is error-free or output errors and warnings * * This high-level function essentially implements crm_verify(8). It operates * on an input CIB file, which can be inputted through one of several ways. It * writes out XML-formatted output. * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] cib_source Source of the CIB: * NULL -> use live cib, "-" -> stdin * "<..." -> xml str, otherwise -> xml file name * * \return Standard Pacemaker return code */ int pcmk_verify(xmlNodePtr *xml, const char *cib_source); /*! * \brief Get nodes list * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] node_types Node type(s) to return (default: all) * * \return Standard Pacemaker return code */ int pcmk_list_nodes(xmlNodePtr *xml, const char *node_types); /*! * \brief Output cluster status formatted like `crm_mon --output-as=xml` * * \param[in,out] xml The destination for the result, as an XML tree * * \return Standard Pacemaker return code */ int pcmk_status(xmlNodePtr *xml); /*! * \brief Check whether each rule in a list is in effect * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] input The CIB XML to check (if \c NULL, use current CIB) * \param[in] date Check whether the rule is in effect at this date and * time (if \c NULL, use current date and time) * \param[in] rule_ids The IDs of the rules to check, as a NULL- * terminated list. * * \return Standard Pacemaker return code */ int pcmk_check_rules(xmlNodePtr *xml, xmlNodePtr input, const crm_time_t *date, const char **rule_ids); /*! * \brief Check whether a given rule is in effect * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] input The CIB XML to check (if \c NULL, use current CIB) * \param[in] date Check whether the rule is in effect at this date and * time (if \c NULL, use current date and time) * \param[in] rule_ids The ID of the rule to check * * \return Standard Pacemaker return code */ static inline int pcmk_check_rule(xmlNodePtr *xml, xmlNodePtr input, const crm_time_t *date, const char *rule_id) { const char *rule_ids[] = {rule_id, NULL}; return pcmk_check_rules(xml, input, date, rule_ids); } /*! * \enum pcmk_rc_disp_flags * \brief Bit flags to control which fields of result code info are displayed */ enum pcmk_rc_disp_flags { pcmk_rc_disp_none = 0, //!< (Does nothing) pcmk_rc_disp_code = (1 << 0), //!< Display result code number pcmk_rc_disp_name = (1 << 1), //!< Display result code name pcmk_rc_disp_desc = (1 << 2), //!< Display result code description }; /*! * \brief Display the name and/or description of a result code * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] code The result code * \param[in] type Interpret \c code as this type of result code. * Supported values: \c pcmk_result_legacy, * \c pcmk_result_rc, \c pcmk_result_exitcode. * \param[in] flags Group of \c pcmk_rc_disp_flags * * \return Standard Pacemaker return code */ int pcmk_show_result_code(xmlNodePtr *xml, int code, enum pcmk_result_type type, uint32_t flags); /*! * \brief List all valid result codes in a particular family * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] type The family of result codes to list. Supported * values: \c pcmk_result_legacy, \c pcmk_result_rc, * \c pcmk_result_exitcode. * \param[in] flags Group of \c pcmk_rc_disp_flags * * \return Standard Pacemaker return code */ int pcmk_list_result_codes(xmlNodePtr *xml, enum pcmk_result_type type, uint32_t flags); /*! * \brief List available providers for the given OCF agent * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] agent_spec Resource agent name * * \return Standard Pacemaker return code */ int pcmk_list_alternatives(xmlNodePtr *xml, const char *agent_spec); /*! * \brief List all agents available for the named standard and/or provider * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] agent_spec STD[:PROV] * * \return Standard Pacemaker return code */ int pcmk_list_agents(xmlNodePtr *xml, char *agent_spec); /*! * \brief List all available OCF providers for the given agent * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] agent_spec Resource agent name * * \return Standard Pacemaker return code */ int pcmk_list_providers(xmlNodePtr *xml, const char *agent_spec); /*! * \brief List all available resource agent standards * * \param[in,out] xml The destination for the result, as an XML tree * * \return Standard Pacemaker return code */ int pcmk_list_standards(xmlNodePtr *xml); /*! * \brief List all available cluster options * * These are options that affect the entire cluster. * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] all If \c true, include advanced and deprecated options * (currently always treated as true) * * \return Standard Pacemaker return code */ int pcmk_list_cluster_options(xmlNode **xml, bool all); /*! * \brief List common fencing resource parameters * * These are parameters that are available for all fencing resources, regardless * of type. They are processed by Pacemaker, rather than by the fence agent or * the fencing library. * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] all If \c true, include advanced and deprecated options * (currently always treated as true) * * \return Standard Pacemaker return code */ int pcmk_list_fencing_params(xmlNode **xml, bool all); /*! * \internal * \brief List meta-attributes applicable to primitive resources as OCF-like XML * * \param[in,out] out Output object * \param[in] all If \c true, include advanced and deprecated options (this * is always treated as true for XML output objects) * * \return Standard Pacemaker return code */ int pcmk_list_primitive_meta(xmlNode **xml, bool all); /*! * \brief Return constraints that apply to the given ticket * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] ticket_id Ticket to find constraint for, or \c NULL for * all ticket constraints * * \return Standard Pacemaker return code */ int pcmk_ticket_constraints(xmlNodePtr *xml, const char *ticket_id); /*! * \brief Return the value of a ticket's attribute * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] ticket_id Ticket to find attribute value for * \param[in] attr_name Attribute's name to find value for * \param[in] attr_default If either the ticket or the attribute do not * exist, use this as the value in \p xml * * \return Standard Pacemaker return code */ int pcmk_ticket_get_attr(xmlNodePtr *xml, const char *ticket_id, const char *attr_name, const char *attr_default); +/*! + * \brief Return information about the given ticket + * + * \param[in,out] xml The destination for the result, as an XML tree + * \param[in] ticket_id Ticket to find info value for, or \c NULL for + * all tickets + * + * \return Standard Pacemaker return code + */ +int pcmk_ticket_info(xmlNodePtr *xml, const char *ticket_id); + #ifdef BUILD_PUBLIC_LIBPACEMAKER /*! * \brief Ask the cluster to perform fencing * * \param[in,out] st A connection to the fencer API * \param[in] target The node that should be fenced * \param[in] action The fencing action (on, off, reboot) to perform * \param[in] name Who requested the fence action? * \param[in] timeout How long to wait for operation to complete (in ms) * \param[in] tolerance If a successful action for \p target happened within * this many ms, return 0 without performing the action * again * \param[in] delay Apply this delay (in milliseconds) before initiating * fencing action (-1 applies no delay and also * disables any fencing delay from pcmk_delay_base and * pcmk_delay_max) * \param[out] reason If not NULL, where to put descriptive failure reason * * \return Standard Pacemaker return code * \note If \p reason is not NULL, the caller is responsible for freeing its * returned value. */ int pcmk_request_fencing(stonith_t *st, const char *target, const char *action, const char *name, unsigned int timeout, unsigned int tolerance, int delay, char **reason); /*! * \brief List the fencing operations that have occurred for a specific node * * \note If \p xml is not NULL, it will be freed first and the previous * contents lost. * * \param[in,out] xml The destination for the result, as an XML tree * \param[in,out] st A connection to the fencer API * \param[in] target The node to get history for * \param[in] timeout How long to wait for operation to complete (in ms) * \param[in] quiet Suppress most output * \param[in] verbose Include additional output * \param[in] broadcast Gather fencing history from all nodes * \param[in] cleanup Clean up fencing history after listing * * \return Standard Pacemaker return code */ int pcmk_fence_history(xmlNodePtr *xml, stonith_t *st, const char *target, unsigned int timeout, bool quiet, int verbose, bool broadcast, bool cleanup); /*! * \brief List all installed fence agents * * \param[in,out] xml The destination for the result, as an XML tree (if * not NULL, previous contents will be freed and lost) * \param[in,out] st A connection to the fencer API * \param[in] timeout How long to wait for operation to complete (in ms) * * \return Standard Pacemaker return code */ int pcmk_fence_installed(xmlNodePtr *xml, stonith_t *st, unsigned int timeout); /*! * \brief When was a device last fenced? * * \param[in,out] xml The destination for the result, as an XML tree (if * not NULL, previous contents will be freed and lost) * \param[in] target The node that was fenced * \param[in] as_nodeid If true, \p target has node ID rather than name * * \return Standard Pacemaker return code */ int pcmk_fence_last(xmlNodePtr *xml, const char *target, bool as_nodeid); /*! * \brief List nodes that can be fenced * * \param[in,out] xml The destination for the result, as an XML tree (if * not NULL, previous contents will be freed and lost) * \param[in,out] st A connection to the fencer API * \param[in] device_id Resource ID of fence device to check * \param[in] timeout How long to wait for operation to complete (in ms) * * \return Standard Pacemaker return code */ int pcmk_fence_list_targets(xmlNodePtr *xml, stonith_t *st, const char *device_id, unsigned int timeout); /*! * \brief Get metadata for a fence agent * * \note If \p xml is not NULL, it will be freed first and the previous * contents lost. * * \param[in,out] xml The destination for the result, as an XML tree (if * not NULL, previous contents will be freed and lost) * \param[in,out] st A connection to the fencer API * \param[in] agent The fence agent to get metadata for * \param[in] timeout How long to wait for operation to complete (in ms) * * \return Standard Pacemaker return code */ int pcmk_fence_metadata(xmlNodePtr *xml, stonith_t *st, const char *agent, unsigned int timeout); /*! * \brief List registered fence devices * * \param[in,out] xml The destination for the result, as an XML tree (if * not NULL, previous contents will be freed and lost) * \param[in,out] st A connection to the fencer API * \param[in] target If not NULL, return only devices that can fence this * \param[in] timeout How long to wait for operation to complete (in ms) * * \return Standard Pacemaker return code */ int pcmk_fence_registered(xmlNodePtr *xml, stonith_t *st, const char *target, unsigned int timeout); /*! * \brief Register a fencing topology level * * \param[in,out] st A connection to the fencer API * \param[in] target What fencing level targets (as "name=value" to * target by given node attribute, or "@pattern" to * target by node name pattern, or a node name) * \param[in] fence_level Index number of level to add * \param[in] devices Devices to use in level * * \return Standard Pacemaker return code */ int pcmk_fence_register_level(stonith_t *st, const char *target, int fence_level, const stonith_key_value_t *devices); /*! * \brief Unregister a fencing topology level * * \param[in,out] st A connection to the fencer API * \param[in] target What fencing level targets (as "name=value" to * target by given node attribute, or "@pattern" to * target by node name pattern, or a node name) * \param[in] fence_level Index number of level to remove * * \return Standard Pacemaker return code */ int pcmk_fence_unregister_level(stonith_t *st, const char *target, int fence_level); /*! * \brief Validate a fence device configuration * * \param[in,out] xml The destination for the result, as an XML tree (if * not NULL, previous contents will be freed and lost) * \param[in,out] st A connection to the fencer API * \param[in] agent The agent to validate (for example, "fence_xvm") * \param[in] id Fence device ID (may be NULL) * \param[in] params Fence device configuration parameters * \param[in] timeout How long to wait for operation to complete (in ms) * * \return Standard Pacemaker return code */ int pcmk_fence_validate(xmlNodePtr *xml, stonith_t *st, const char *agent, const char *id, const stonith_key_value_t *params, unsigned int timeout); #endif #ifdef __cplusplus } #endif #endif diff --git a/include/pcmki/pcmki_ticket.h b/include/pcmki/pcmki_ticket.h index 32463352bd..5a32f4c82d 100644 --- a/include/pcmki/pcmki_ticket.h +++ b/include/pcmki/pcmki_ticket.h @@ -1,46 +1,68 @@ /* * Copyright 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. */ #ifndef PCMK__PCMKI_PCMKI_TICKET__H # define PCMK__PCMKI_PCMKI_TICKET__H #include #include /*! * \internal * \brief Display the constraints that apply to a given ticket * * \param[in,out] out Output object * \param[in] cib Open CIB connection * \param[in] ticket_id Ticket to find constraints for, * or \c NULL for all ticket constraints * * \return Standard Pacemaker return code */ int pcmk__ticket_constraints(pcmk__output_t *out, cib_t *cib, const char *ticket_id); /*! * \internal * \brief Return the value of a ticket's attribute * * \param[in,out] out Output object * \param[in,out] scheduler Scheduler data * \param[in] ticket_id Ticket to find attribute value for * \param[in] attr_name Attribute's name to find value for * \param[in] attr_default If either the ticket or the attribute do not * exist, use this as the value in the output * * \return Standard Pacemaker return code */ int pcmk__ticket_get_attr(pcmk__output_t *out, pcmk_scheduler_t *scheduler, const char *ticket_id, const char *attr_name, const char *attr_default); + +/*! + * \brief Return information about the given ticket + * + * \param[in,out] out Output object + * \param[in,out] scheduler Scheduler data + * \param[in] ticket_id Ticket to display info for, or \c NULL for + * all tickets + * \param[in] details If true (and \p out is not an XML format + * object), output any additional attributes + * set on a ticket beyond the basics + * \param[in] raw If true (and \p out is not an XML format + * object), simply list the IDs of all tickets. + * This does not make a lot of sense if + * \p ticket_id is not NULL, but that will not + * raise an error. + * + * \return Standard Pacemaker return code + */ +int pcmk__ticket_info(pcmk__output_t *out, pcmk_scheduler_t *scheduler, + const char *ticket_id, bool details, bool raw); + #endif diff --git a/lib/pacemaker/pcmk_ticket.c b/lib/pacemaker/pcmk_ticket.c index 4d45dc9e79..02a1f969d6 100644 --- a/lib/pacemaker/pcmk_ticket.c +++ b/lib/pacemaker/pcmk_ticket.c @@ -1,126 +1,184 @@ /* * Copyright 2024 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 "libpacemaker_private.h" int pcmk__ticket_constraints(pcmk__output_t *out, cib_t *cib, const char *ticket_id) { int rc = pcmk_rc_ok; xmlNode *result = NULL; const char *xpath_base = NULL; char *xpath = NULL; CRM_ASSERT(out != NULL && cib != NULL); xpath_base = pcmk_cib_xpath_for(PCMK_XE_CONSTRAINTS); CRM_ASSERT(xpath_base != NULL); if (ticket_id != NULL) { xpath = crm_strdup_printf("%s/" PCMK_XE_RSC_TICKET "[@" PCMK_XA_TICKET "=\"%s\"]", xpath_base, ticket_id); } else { xpath = crm_strdup_printf("%s/" PCMK_XE_RSC_TICKET, xpath_base); } rc = cib->cmds->query(cib, (const char *) xpath, &result, cib_sync_call | cib_scope_local | cib_xpath); rc = pcmk_legacy2rc(rc); if (result != NULL) { out->message(out, "ticket-constraints", result); free_xml(result); } free(xpath); return rc; } int pcmk_ticket_constraints(xmlNodePtr *xml, const char *ticket_id) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; cib_t *cib = NULL; rc = pcmk__setup_output_cib_sched(&out, &cib, NULL, xml); if (rc != pcmk_rc_ok) { goto done; } rc = pcmk__ticket_constraints(out, cib, ticket_id); done: if (cib != NULL) { cib__clean_up_connection(&cib); } pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } int pcmk__ticket_get_attr(pcmk__output_t *out, pcmk_scheduler_t *scheduler, const char *ticket_id, const char *attr_name, const char *attr_default) { int rc = pcmk_rc_ok; const char *attr_value = NULL; pcmk_ticket_t *ticket = NULL; CRM_ASSERT(out != NULL && scheduler != NULL); if (ticket_id == NULL || attr_name == NULL) { return EINVAL; } ticket = g_hash_table_lookup(scheduler->tickets, ticket_id); if (ticket != NULL) { attr_value = g_hash_table_lookup(ticket->state, attr_name); } if (attr_value != NULL) { out->message(out, "ticket-attribute", ticket_id, attr_name, attr_value); } else if (attr_default != NULL) { out->message(out, "ticket-attribute", ticket_id, attr_name, attr_default); } else { rc = ENXIO; } return rc; } int pcmk_ticket_get_attr(xmlNodePtr *xml, const char *ticket_id, const char *attr_name, const char *attr_default) { pcmk_scheduler_t *scheduler = NULL; pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__setup_output_cib_sched(&out, NULL, &scheduler, xml); if (rc != pcmk_rc_ok) { goto done; } rc = pcmk__ticket_get_attr(out, scheduler, ticket_id, attr_name, attr_default); done: pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); pe_free_working_set(scheduler); return rc; } + +int +pcmk__ticket_info(pcmk__output_t *out, pcmk_scheduler_t *scheduler, + const char *ticket_id, bool details, bool raw) +{ + int rc = pcmk_rc_ok; + + CRM_ASSERT(out != NULL && scheduler != NULL); + + if (ticket_id != NULL) { + GHashTable *tickets = NULL; + pcmk_ticket_t *ticket = g_hash_table_lookup(scheduler->tickets, ticket_id); + + if (ticket == NULL) { + return ENXIO; + } + + /* The ticket-list message expects a GHashTable, so we'll construct + * one with just this single item. + */ + tickets = pcmk__strkey_table(free, NULL); + g_hash_table_insert(tickets, strdup(ticket->id), ticket); + out->message(out, "ticket-list", tickets, false, raw, details); + g_hash_table_destroy(tickets); + + } else { + out->message(out, "ticket-list", scheduler->tickets, false, raw, details); + } + + return rc; +} + +int +pcmk_ticket_info(xmlNodePtr *xml, const char *ticket_id) +{ + pcmk_scheduler_t *scheduler = NULL; + pcmk__output_t *out = NULL; + int rc = pcmk_rc_ok; + + rc = pcmk__setup_output_cib_sched(&out, NULL, &scheduler, xml); + if (rc != pcmk_rc_ok) { + goto done; + } + + pe__register_messages(out); + + /* XML output (which is the only format supported by public API functions + * due to the use of pcmk__xml_output_new above) always prints all details, + * so just pass false for the last two arguments. + */ + rc = pcmk__ticket_info(out, scheduler, ticket_id, false, false); + +done: + pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); + pe_free_working_set(scheduler); + return rc; +} diff --git a/tools/crm_ticket.c b/tools/crm_ticket.c index 67b09ed40d..096012e982 100644 --- a/tools/crm_ticket.c +++ b/tools/crm_ticket.c @@ -1,897 +1,885 @@ /* * Copyright 2012-2024 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 GError *error = NULL; #define SUMMARY "Perform tasks related to cluster tickets\n\n" \ "Allows ticket attributes to be queried, modified and deleted." struct { gchar *attr_default; gchar *attr_id; char *attr_name; char *attr_value; gboolean force; char *get_attr_name; gboolean quiet; gchar *set_name; char ticket_cmd; gchar *ticket_id; gchar *xml_file; } options = { .ticket_cmd = 'S' }; GList *attr_delete; GHashTable *attr_set; bool modified = false; int cib_options = cib_sync_call; static pcmk__output_t *out = NULL; #define INDENT " " static pcmk__supported_format_t formats[] = { PCMK__SUPPORTED_FORMAT_NONE, PCMK__SUPPORTED_FORMAT_TEXT, PCMK__SUPPORTED_FORMAT_XML, { NULL, NULL, NULL } }; static gboolean attr_value_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) { pcmk__str_update(&options.attr_value, optarg); if (!options.attr_name || !options.attr_value) { return TRUE; } pcmk__insert_dup(attr_set, options.attr_name, options.attr_value); pcmk__str_update(&options.attr_name, NULL); pcmk__str_update(&options.attr_value, NULL); modified = true; return TRUE; } static gboolean command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) { if (pcmk__str_any_of(option_name, "--info", "-l", NULL)) { options.ticket_cmd = 'l'; } else if (pcmk__str_any_of(option_name, "--details", "-L", NULL)) { options.ticket_cmd = 'L'; } else if (pcmk__str_any_of(option_name, "--raw", "-w", NULL)) { options.ticket_cmd = 'w'; } else if (pcmk__str_any_of(option_name, "--query-xml", "-q", NULL)) { options.ticket_cmd = 'q'; } else if (pcmk__str_any_of(option_name, "--constraints", "-c", NULL)) { options.ticket_cmd = 'c'; } else if (pcmk__str_any_of(option_name, "--cleanup", "-C", NULL)) { options.ticket_cmd = 'C'; } return TRUE; } static gboolean delete_attr_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) { attr_delete = g_list_append(attr_delete, strdup(optarg)); modified = true; return TRUE; } static gboolean get_attr_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) { pcmk__str_update(&options.get_attr_name, optarg); options.ticket_cmd = 'G'; return TRUE; } static gboolean grant_standby_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) { if (pcmk__str_any_of(option_name, "--grant", "-g", NULL)) { pcmk__insert_dup(attr_set, PCMK__XA_GRANTED, PCMK_VALUE_TRUE); modified = true; } else if (pcmk__str_any_of(option_name, "--revoke", "-r", NULL)) { pcmk__insert_dup(attr_set, PCMK__XA_GRANTED, PCMK_VALUE_FALSE); modified = true; } else if (pcmk__str_any_of(option_name, "--standby", "-s", NULL)) { pcmk__insert_dup(attr_set, PCMK_XA_STANDBY, PCMK_VALUE_TRUE); modified = true; } else if (pcmk__str_any_of(option_name, "--activate", "-a", NULL)) { pcmk__insert_dup(attr_set, PCMK_XA_STANDBY, PCMK_VALUE_FALSE); modified = true; } return TRUE; } static gboolean set_attr_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) { pcmk__str_update(&options.attr_name, optarg); if (!options.attr_name || !options.attr_value) { return TRUE; } pcmk__insert_dup(attr_set, options.attr_name, options.attr_value); pcmk__str_update(&options.attr_name, NULL); pcmk__str_update(&options.attr_value, NULL); modified = true; return TRUE; } static GOptionEntry query_entries[] = { { "info", 'l', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display the information of ticket(s)", NULL }, { "details", 'L', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display the details of ticket(s)", NULL }, { "raw", 'w', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display the IDs of ticket(s)", NULL }, { "query-xml", 'q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Query the XML of ticket(s)", NULL }, { "constraints", 'c', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display the " PCMK_XE_RSC_TICKET " constraints that apply to ticket(s)", NULL }, { NULL } }; static GOptionEntry command_entries[] = { { "grant", 'g', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, grant_standby_cb, "Grant a ticket to this cluster site", NULL }, { "revoke", 'r', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, grant_standby_cb, "Revoke a ticket from this cluster site", NULL }, { "standby", 's', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, grant_standby_cb, "Tell this cluster site this ticket is standby", NULL }, { "activate", 'a', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, grant_standby_cb, "Tell this cluster site this ticket is active", NULL }, { NULL } }; static GOptionEntry advanced_entries[] = { { "get-attr", 'G', 0, G_OPTION_ARG_CALLBACK, get_attr_cb, "Display the named attribute for a ticket", "ATTRIBUTE" }, { "set-attr", 'S', 0, G_OPTION_ARG_CALLBACK, set_attr_cb, "Set the named attribute for a ticket", "ATTRIBUTE" }, { "delete-attr", 'D', 0, G_OPTION_ARG_CALLBACK, delete_attr_cb, "Delete the named attribute for a ticket", "ATTRIBUTE" }, { "cleanup", 'C', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Delete all state of a ticket at this cluster site", NULL }, { NULL} }; static GOptionEntry addl_entries[] = { { "attr-value", 'v', 0, G_OPTION_ARG_CALLBACK, attr_value_cb, "Attribute value to use with -S", "VALUE" }, { "default", 'd', 0, G_OPTION_ARG_STRING, &options.attr_default, "(Advanced) Default attribute value to display if none is found\n" INDENT "(for use with -G)", "VALUE" }, { "force", 'f', 0, G_OPTION_ARG_NONE, &options.force, "(Advanced) Force the action to be performed", NULL }, { "ticket", 't', 0, G_OPTION_ARG_STRING, &options.ticket_id, "Ticket ID", "ID" }, { "xml-file", 'x', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.xml_file, NULL, NULL }, { NULL } }; static GOptionEntry deprecated_entries[] = { { "set-name", 'n', 0, G_OPTION_ARG_STRING, &options.set_name, "(Advanced) ID of the " PCMK_XE_INSTANCE_ATTRIBUTES " object to change", "ID" }, { "nvpair", 'i', 0, G_OPTION_ARG_STRING, &options.attr_id, "(Advanced) ID of the nvpair object to change/delete", "ID" }, { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &options.quiet, "Print only the value on stdout", NULL }, { NULL } }; static pcmk_ticket_t * find_ticket(gchar *ticket_id, pcmk_scheduler_t *scheduler) { return g_hash_table_lookup(scheduler->tickets, ticket_id); } static int find_ticket_state(cib_t * the_cib, gchar *ticket_id, xmlNode ** ticket_state_xml) { int rc = pcmk_rc_ok; xmlNode *xml_search = NULL; GString *xpath = NULL; CRM_ASSERT(ticket_state_xml != NULL); *ticket_state_xml = NULL; xpath = g_string_sized_new(1024); g_string_append(xpath, "/" PCMK_XE_CIB "/" PCMK_XE_STATUS "/" PCMK_XE_TICKETS); if (ticket_id != NULL) { pcmk__g_strcat(xpath, "/" PCMK__XE_TICKET_STATE "[@" PCMK_XA_ID "=\"", ticket_id, "\"]", NULL); } rc = the_cib->cmds->query(the_cib, (const char *) xpath->str, &xml_search, cib_sync_call | cib_scope_local | cib_xpath); rc = pcmk_legacy2rc(rc); g_string_free(xpath, TRUE); if (rc != pcmk_rc_ok) { return rc; } crm_log_xml_debug(xml_search, "Match"); if (xml_search->children != NULL) { if (ticket_id) { out->info(out, "Multiple " PCMK__XE_TICKET_STATE "s match ticket=%s", ticket_id); } *ticket_state_xml = xml_search; } else { *ticket_state_xml = xml_search; } return rc; } PCMK__OUTPUT_ARGS("ticket-state", "gchar *", "xmlNode *") static int ticket_state_default(pcmk__output_t *out, va_list args) { gchar *ticket_id G_GNUC_UNUSED = va_arg(args, gchar *); xmlNode *state_xml = va_arg(args, xmlNode *); GString *buf = g_string_sized_new(1024); pcmk__formatted_printf(out, "State XML:\n\n"); pcmk__xml_string(state_xml, pcmk__xml_fmt_pretty, buf, 0); out->output_xml(out, PCMK__XE_TICKET_STATE, buf->str); g_string_free(buf, TRUE); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("ticket-state", "gchar *", "xmlNode *") static int ticket_state_xml(pcmk__output_t *out, va_list args) { gchar *ticket_id = va_arg(args, gchar *); xmlNode *state_xml = va_arg(args, xmlNode *); xmlNode *ticket_node = NULL; /* Create: * * * */ pcmk__output_xml_create_parent(out, PCMK_XE_TICKETS, NULL); ticket_node = pcmk__output_create_xml_node(out, PCMK_XE_TICKET, PCMK_XA_ID, ticket_id, NULL); copy_in_properties(ticket_node, state_xml); pcmk__output_xml_pop_parent(out); return pcmk_rc_ok; } static void ticket_grant_warning(gchar *ticket_id) { out->err(out, "This command cannot help you verify whether '%s' has " "been already granted elsewhere.\n" "If you really want to grant '%s' to this site now, and " "you know what you are doing,\n" "please specify --force.", ticket_id, ticket_id); } static void ticket_revoke_warning(gchar *ticket_id) { out->err(out, "Revoking '%s' can trigger the specified '" PCMK_XA_LOSS_POLICY "'(s) relating to '%s'.\n\n" "You can check that with:\n" "crm_ticket --ticket %s --constraints\n\n" "Otherwise before revoking '%s', you may want to make '%s'" "standby with:\n" "crm_ticket --ticket %s --standby\n\n" "If you really want to revoke '%s' from this site now, and " "you know what you are doing,\n" "please specify --force.", ticket_id, ticket_id, ticket_id, ticket_id, ticket_id, ticket_id, ticket_id); } static bool allow_modification(gchar *ticket_id) { const char *value = NULL; GList *list_iter = NULL; if (options.force) { return true; } if (g_hash_table_lookup_extended(attr_set, PCMK__XA_GRANTED, NULL, (gpointer *) &value)) { if (crm_is_true(value)) { ticket_grant_warning(ticket_id); return false; } else { ticket_revoke_warning(ticket_id); return false; } } for(list_iter = attr_delete; list_iter; list_iter = list_iter->next) { const char *key = (const char *)list_iter->data; if (pcmk__str_eq(key, PCMK__XA_GRANTED, pcmk__str_none)) { ticket_revoke_warning(ticket_id); return false; } } return true; } static int modify_ticket_state(gchar *ticket_id, cib_t *cib, pcmk_scheduler_t *scheduler) { int rc = pcmk_rc_ok; xmlNode *xml_top = NULL; xmlNode *ticket_state_xml = NULL; bool found = false; GList *list_iter = NULL; GHashTableIter hash_iter; char *key = NULL; char *value = NULL; pcmk_ticket_t *ticket = NULL; rc = find_ticket_state(cib, ticket_id, &ticket_state_xml); if (rc == pcmk_rc_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_rc_ok; } else { xmlNode *xml_obj = NULL; xml_top = pcmk__xe_create(NULL, PCMK_XE_STATUS); xml_obj = pcmk__xe_create(xml_top, PCMK_XE_TICKETS); ticket_state_xml = pcmk__xe_create(xml_obj, PCMK__XE_TICKET_STATE); crm_xml_add(ticket_state_xml, PCMK_XA_ID, ticket_id); } for(list_iter = attr_delete; list_iter; list_iter = list_iter->next) { const char *key = (const char *)list_iter->data; pcmk__xe_remove_attr(ticket_state_xml, key); } ticket = find_ticket(ticket_id, scheduler); 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 (pcmk__str_eq(key, PCMK__XA_GRANTED, pcmk__str_none) && (ticket == NULL || ticket->granted == FALSE) && crm_is_true(value)) { char *now = pcmk__ttoa(time(NULL)); crm_xml_add(ticket_state_xml, PCMK_XA_LAST_GRANTED, now); free(now); } } if (found && (attr_delete != NULL)) { crm_log_xml_debug(xml_top, "Replace"); rc = cib->cmds->replace(cib, PCMK_XE_STATUS, ticket_state_xml, cib_options); rc = pcmk_legacy2rc(rc); } else { crm_log_xml_debug(xml_top, "Update"); rc = cib->cmds->modify(cib, PCMK_XE_STATUS, xml_top, cib_options); rc = pcmk_legacy2rc(rc); } free_xml(xml_top); return rc; } static int delete_ticket_state(gchar *ticket_id, cib_t * cib) { xmlNode *ticket_state_xml = NULL; int rc = pcmk_rc_ok; rc = find_ticket_state(cib, ticket_id, &ticket_state_xml); if (rc == ENXIO) { return pcmk_rc_ok; } else if (rc != pcmk_rc_ok) { return rc; } crm_log_xml_debug(ticket_state_xml, "Delete"); rc = cib->cmds->remove(cib, PCMK_XE_STATUS, ticket_state_xml, cib_options); rc = pcmk_legacy2rc(rc); if (rc == pcmk_rc_ok) { out->info(out, "Cleaned up %s", ticket_id); } free_xml(ticket_state_xml); return rc; } static GOptionContext * build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) { GOptionContext *context = NULL; const char *description = "Examples:\n\n" "Display the info of tickets:\n\n" "\tcrm_ticket --info\n\n" "Display the detailed info of tickets:\n\n" "\tcrm_ticket --details\n\n" "Display the XML of 'ticketA':\n\n" "\tcrm_ticket --ticket ticketA --query-xml\n\n" "Display the " PCMK_XE_RSC_TICKET " constraints that apply to 'ticketA':\n\n" "\tcrm_ticket --ticket ticketA --constraints\n\n" "Grant 'ticketA' to this cluster site:\n\n" "\tcrm_ticket --ticket ticketA --grant\n\n" "Revoke 'ticketA' from this cluster site:\n\n" "\tcrm_ticket --ticket ticketA --revoke\n\n" "Make 'ticketA' standby (the cluster site will treat a granted\n" "'ticketA' as 'standby', and the dependent resources will be\n" "stopped or demoted gracefully without triggering loss-policies):\n\n" "\tcrm_ticket --ticket ticketA --standby\n\n" "Activate 'ticketA' from being standby:\n\n" "\tcrm_ticket --ticket ticketA --activate\n\n" "Get the value of the 'granted' attribute for 'ticketA':\n\n" "\tcrm_ticket --ticket ticketA --get-attr granted\n\n" "Set the value of the 'standby' attribute for 'ticketA':\n\n" "\tcrm_ticket --ticket ticketA --set-attr standby --attr-value true\n\n" "Delete the 'granted' attribute for 'ticketA':\n\n" "\tcrm_ticket --ticket ticketA --delete-attr granted\n\n" "Erase the operation history of 'ticketA' at this cluster site,\n" "causing the cluster site to 'forget' the existing ticket state:\n\n" "\tcrm_ticket --ticket ticketA --cleanup\n\n"; context = pcmk__build_arg_context(args, "text (default), xml", group, NULL); g_option_context_set_description(context, description); pcmk__add_arg_group(context, "queries", "Queries:", "Show queries", query_entries); pcmk__add_arg_group(context, "commands", "Commands:", "Show command options", command_entries); pcmk__add_arg_group(context, "advanced", "Advanced Options:", "Show advanced options", advanced_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; } static pcmk__message_entry_t fmt_functions[] = { { "ticket-state", "default", ticket_state_default }, { "ticket-state", "xml", ticket_state_xml }, { NULL, NULL, NULL } }; int main(int argc, char **argv) { pcmk_scheduler_t *scheduler = NULL; xmlNode *cib_xml_copy = NULL; cib_t *cib_conn = NULL; crm_exit_t exit_code = CRM_EX_OK; int rc = pcmk_rc_ok; GOptionGroup *output_group = NULL; pcmk__common_args_t *args = NULL; GOptionContext *context = NULL; gchar **processed_args = NULL; attr_set = pcmk__strkey_table(free, free); attr_delete = NULL; args = pcmk__new_common_args(SUMMARY); context = build_arg_context(args, &output_group); processed_args = pcmk__cmdline_preproc(argv, "dintvxCDGS"); pcmk__register_formats(output_group, formats); if (!g_option_context_parse_strv(context, &processed_args, &error)) { exit_code = CRM_EX_USAGE; goto done; } pcmk__cli_init_logging("crm_ticket", args->verbosity); rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv); if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Error creating output format %s: %s", args->output_ty, pcmk_rc_str(rc)); goto done; } pe__register_messages(out); pcmk__register_lib_messages(out); pcmk__register_messages(out, fmt_functions); if (args->version) { out->version(out, false); goto done; } scheduler = pe_new_working_set(); if (scheduler == NULL) { rc = errno; exit_code = pcmk_rc2exitc(rc); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not allocate scheduler data: %s", pcmk_rc_str(rc)); goto done; } pcmk__set_scheduler_flags(scheduler, pcmk_sched_no_counts|pcmk_sched_no_compat); cib_conn = cib_new(); if (cib_conn == NULL) { exit_code = CRM_EX_DISCONNECT; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not connect to the CIB manager"); goto done; } rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not connect to the CIB: %s", pcmk_rc_str(rc)); goto done; } if (options.xml_file != NULL) { cib_xml_copy = pcmk__xml_read(options.xml_file); } else { rc = cib_conn->cmds->query(cib_conn, NULL, &cib_xml_copy, cib_scope_local | cib_sync_call); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not get local CIB: %s", pcmk_rc_str(rc)); goto done; } } if (!cli_config_update(&cib_xml_copy, NULL, FALSE)) { exit_code = CRM_EX_CONFIG; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not update local CIB to latest schema version"); goto done; } scheduler->input = cib_xml_copy; scheduler->now = crm_time_new(NULL); cluster_status(scheduler); /* For recording the tickets that are referenced in PCMK_XE_RSC_TICKET * constraints but have never been granted yet. */ pcmk__unpack_constraints(scheduler); if (options.ticket_cmd == 'l' || options.ticket_cmd == 'L' || options.ticket_cmd == 'w') { bool raw = false; bool details = false; if (options.ticket_cmd == 'L') { details = true; } else if (options.ticket_cmd == 'w') { raw = true; } - if (options.ticket_id) { - GHashTable *tickets = NULL; - pcmk_ticket_t *ticket = find_ticket(options.ticket_id, scheduler); - - if (ticket == NULL) { - exit_code = CRM_EX_NOSUCH; - g_set_error(&error, PCMK__EXITC_ERROR, exit_code, - "No such ticket '%s'", options.ticket_id); - goto done; - } - - /* The ticket-list message expects a GHashTable, so we'll construct - * one with just this single item. - */ - tickets = pcmk__strkey_table(free, NULL); - g_hash_table_insert(tickets, strdup(ticket->id), ticket); - out->message(out, "ticket-list", tickets, false, raw, details); - g_hash_table_destroy(tickets); + rc = pcmk__ticket_info(out, scheduler, options.ticket_id, details, raw); + exit_code = pcmk_rc2exitc(rc); - } else { - out->message(out, "ticket-list", scheduler->tickets, false, raw, details); + if (rc == ENXIO) { + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "No such ticket '%s'", options.ticket_id); + } else if (rc != pcmk_rc_ok) { + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "Could not get ticket info: %s", pcmk_rc_str(rc)); } } else if (options.ticket_cmd == 'q') { xmlNode *state_xml = NULL; rc = find_ticket_state(cib_conn, options.ticket_id, &state_xml); if (state_xml != NULL) { out->message(out, "ticket-state", options.ticket_id, state_xml); free_xml(state_xml); } exit_code = pcmk_rc2exitc(rc); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not query ticket XML: %s", pcmk_rc_str(rc)); } } else if (options.ticket_cmd == 'c') { rc = pcmk__ticket_constraints(out, cib_conn, options.ticket_id); exit_code = pcmk_rc2exitc(rc); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not show ticket constraints: %s", pcmk_rc_str(rc)); } } else if (options.ticket_cmd == 'G') { if (options.ticket_id == NULL) { exit_code = CRM_EX_NOSUCH; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Must supply ticket ID with -t"); goto done; } rc = pcmk__ticket_get_attr(out, scheduler, options.ticket_id, options.get_attr_name, options.attr_default); exit_code = pcmk_rc2exitc(rc); } else if (options.ticket_cmd == 'C') { if (options.ticket_id == NULL) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Must supply ticket ID with -t"); goto done; } if (options.force == FALSE) { pcmk_ticket_t *ticket = NULL; ticket = find_ticket(options.ticket_id, scheduler); if (ticket == NULL) { exit_code = CRM_EX_NOSUCH; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "No such ticket '%s'", options.ticket_id); goto done; } if (ticket->granted) { ticket_revoke_warning(options.ticket_id); exit_code = CRM_EX_INSUFFICIENT_PRIV; goto done; } } rc = delete_ticket_state(options.ticket_id, cib_conn); exit_code = pcmk_rc2exitc(rc); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not clean up ticket: %s", pcmk_rc_str(rc)); } } else if (modified) { if (options.ticket_id == NULL) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Must supply ticket ID with -t"); goto done; } if (options.attr_value && (pcmk__str_empty(options.attr_name))) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Must supply attribute name with -S for -v %s", options.attr_value); goto done; } if (options.attr_name && (pcmk__str_empty(options.attr_value))) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Must supply attribute value with -v for -S %s", options.attr_value); goto done; } if (!allow_modification(options.ticket_id)) { exit_code = CRM_EX_INSUFFICIENT_PRIV; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Ticket modification not allowed"); goto done; } rc = modify_ticket_state(options.ticket_id, cib_conn, scheduler); exit_code = pcmk_rc2exitc(rc); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not modify ticket: %s", pcmk_rc_str(rc)); } } else if (options.ticket_cmd == 'S') { /* Correct usage was handled in the "if (modified)" block above, so * this is just for reporting usage errors */ if (pcmk__str_empty(options.attr_name)) { // We only get here if ticket_cmd was left as default exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Must supply a command"); goto done; } if (options.ticket_id == NULL) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Must supply ticket ID with -t"); goto done; } if (pcmk__str_empty(options.attr_value)) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Must supply value with -v for -S %s", options.attr_name); goto done; } } else { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Unknown command: %c", options.ticket_cmd); } done: if (attr_set) { g_hash_table_destroy(attr_set); } attr_set = NULL; if (attr_delete) { g_list_free_full(attr_delete, free); } attr_delete = NULL; pe_free_working_set(scheduler); scheduler = NULL; cib__clean_up_connection(&cib_conn); g_strfreev(processed_args); pcmk__free_arg_context(context); g_free(options.attr_default); g_free(options.attr_id); free(options.attr_name); free(options.attr_value); free(options.get_attr_name); g_free(options.set_name); g_free(options.ticket_id); g_free(options.xml_file); 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); }