diff --git a/include/crm/common/output_internal.h b/include/crm/common/output_internal.h index 274bd852bc..4e1beedad5 100644 --- a/include/crm/common/output_internal.h +++ b/include/crm/common/output_internal.h @@ -1,994 +1,994 @@ /* * Copyright 2019-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PCMK__OUTPUT_INTERNAL__H # define PCMK__OUTPUT_INTERNAL__H # include # include # include # include # include # include # include #ifdef __cplusplus extern "C" { #endif /** * \file * \brief Formatted output for pacemaker tools */ #if defined(PCMK__WITH_ATTRIBUTE_OUTPUT_ARGS) # define PCMK__OUTPUT_ARGS(ARGS...) __attribute__((output_args(ARGS))) #else # define PCMK__OUTPUT_ARGS(ARGS...) #endif typedef struct pcmk__output_s pcmk__output_t; /*! * \internal * \brief The type of a function that creates a ::pcmk__output_t. * * Instances of this type are passed to pcmk__register_format(), stored in an * internal data structure, and later accessed by pcmk__output_new(). For * examples, see pcmk__mk_xml_output() and pcmk__mk_text_output(). * * \param[in] argv The list of command line arguments. */ typedef pcmk__output_t * (*pcmk__output_factory_t)(char **argv); /*! * \internal * \brief The type of a custom message formatting function. * * These functions are defined by various libraries to support formatting of * types aside from the basic types provided by a ::pcmk__output_t. * * The meaning of the return value will be different for each message. * In general, however, 0 should be returned on success and a positive value * on error. * * \param[in,out] out Output object to use to display message * \param[in,out] args Message-specific arguments needed * * \note These functions must not call va_start or va_end - that is done * automatically before the custom formatting function is called. */ typedef int (*pcmk__message_fn_t)(pcmk__output_t *out, va_list args); /*! * \internal * \brief Internal type for tracking custom messages. * * Each library can register functions that format custom message types. These * are commonly used to handle some library-specific type. Registration is * done by first defining a table of ::pcmk__message_entry_t structures and * then passing that table to pcmk__register_messages(). Separate handlers * can be defined for the same message, but for different formats (xml vs. * text). Unknown formats will be ignored. * * Additionally, a "default" value for fmt_table can be used. In this case, * fn will be registered for all supported formats. It is also possible to * register a default and then override that registration with a format-specific * function if necessary. * * \note The ::pcmk__message_entry_t table is processed in one pass, in order, * from top to bottom. This means later entries with the same message_id will * override previous ones. Thus, any default entry must come before any * format-specific entries for the same message_id. */ typedef struct pcmk__message_entry_s { /*! * \brief The message to be handled. * * This must be the same ID that is passed to the message function of * a ::pcmk__output_t. Unknown message IDs will be ignored. */ const char *message_id; /*! * \brief The format type this handler is for. * * This name must match the fmt_name of the currently active formatter in * order for the registered function to be called. It is valid to have * multiple entries for the same message_id but with different fmt_name * values. */ const char *fmt_name; /*! * \brief The function to be called for message_id given a match on * fmt_name. See comments on ::pcmk__message_fn_t. */ pcmk__message_fn_t fn; } pcmk__message_entry_t; /*! * \internal * \brief This structure contains everything needed to add support for a * single output formatter to a command line program. */ typedef struct pcmk__supported_format_s { /*! * \brief The name of this output formatter, which should match the * fmt_name parameter in some ::pcmk__output_t structure. */ const char *name; /*! * \brief A function that creates a ::pcmk__output_t. */ pcmk__output_factory_t create; /*! * \brief Format-specific command line options. This can be NULL if * no command line options should be supported. */ GOptionEntry *options; } pcmk__supported_format_t; /* The following three blocks need to be updated each time a new base formatter * is added. */ extern GOptionEntry pcmk__html_output_entries[]; extern GOptionEntry pcmk__log_output_entries[]; extern GOptionEntry pcmk__none_output_entries[]; extern GOptionEntry pcmk__text_output_entries[]; extern GOptionEntry pcmk__xml_output_entries[]; pcmk__output_t *pcmk__mk_html_output(char **argv); pcmk__output_t *pcmk__mk_log_output(char **argv); pcmk__output_t *pcmk__mk_none_output(char **argv); pcmk__output_t *pcmk__mk_text_output(char **argv); pcmk__output_t *pcmk__mk_xml_output(char **argv); #define PCMK__SUPPORTED_FORMAT_HTML { "html", pcmk__mk_html_output, pcmk__html_output_entries } #define PCMK__SUPPORTED_FORMAT_LOG { "log", pcmk__mk_log_output, pcmk__log_output_entries } #define PCMK__SUPPORTED_FORMAT_NONE { PCMK__VALUE_NONE, pcmk__mk_none_output, \ pcmk__none_output_entries } #define PCMK__SUPPORTED_FORMAT_TEXT { "text", pcmk__mk_text_output, pcmk__text_output_entries } #define PCMK__SUPPORTED_FORMAT_XML { "xml", pcmk__mk_xml_output, pcmk__xml_output_entries } /*! * \brief This structure contains everything that makes up a single output * formatter. * * Instances of this structure may be created by calling pcmk__output_new() * with the name of the desired formatter. They should later be freed with * pcmk__output_free(). */ struct pcmk__output_s { /*! * \brief The name of this output formatter. */ const char *fmt_name; /*! * \brief Should this formatter supress most output? * * \note This setting is not respected by all formatters. In general, * machine-readable output formats will not support this while * user-oriented formats will. Callers should use is_quiet() * to test whether to print or not. */ bool quiet; /*! * \brief A copy of the request that generated this output. * * In the case of command line usage, this would be the command line * arguments. For other use cases, it could be different. */ gchar *request; /*! * \brief Where output should be written. * * This could be a file handle, or stdout or stderr. This is really only * useful internally. */ FILE *dest; /*! * \brief Custom messages that are currently registered on this formatter. * * Keys are the string message IDs, values are ::pcmk__message_fn_t function * pointers. */ GHashTable *messages; /*! * \brief Implementation-specific private data. * * Each individual formatter may have some private data useful in its * implementation. This points to that data. Callers should not rely on * its contents or structure. */ void *priv; /*! * \internal * \brief Take whatever actions are necessary to prepare out for use. This is * called by pcmk__output_new(). End users should not need to call this. * * \note For formatted output implementers - This function should be written in * such a way that it can be called repeatedly on an already initialized * object without causing problems, or on a previously finished object * without crashing. * * \param[in,out] out The output functions structure. * * \return true on success, false on error. */ bool (*init) (pcmk__output_t *out); /*! * \internal * \brief Free the private formatter-specific data. * * This is called from pcmk__output_free() and does not typically need to be * called directly. * * \param[in,out] out The output functions structure. */ void (*free_priv) (pcmk__output_t *out); /*! * \internal * \brief Take whatever actions are necessary to end formatted output. * * This could include flushing output to a file, but does not include freeing * anything. The finish method can potentially be fairly complicated, adding * additional information to the internal data structures or doing whatever * else. It is therefore suggested that finish only be called once. * * \note The print parameter will only affect those formatters that do all * their output at the end. Console-oriented formatters typically print * a line at a time as they go, so this parameter will not affect them. * Structured formatters will honor it, however. * * \note The copy_dest parameter does not apply to all formatters. Console- * oriented formatters do not build up a structure as they go, and thus * do not have anything to return. Structured formatters will honor it, * however. Note that each type of formatter will return a different * type of value in this parameter. To use this parameter, call this * function like so: * * \code * xmlNode *dest = NULL; * out->finish(out, exit_code, false, (void **) &dest); * \endcode * * \param[in,out] out The output functions structure. * \param[in] exit_status The exit value of the whole program. * \param[in] print Whether this function should write any output. * \param[out] copy_dest A destination to store a copy of the internal * data structure for this output, or NULL if no * copy is required. The caller should free this * memory when done with it. */ void (*finish) (pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest); /*! * \internal * \brief Finalize output and then immediately set back up to start a new set * of output. * * This is conceptually the same as calling finish and then init, though in * practice more be happening behind the scenes. * * \note This function differs from finish in that no exit_status is added. * The idea is that the program is not shutting down, so there is not * yet a final exit code. Call finish on the last time through if this * is needed. * * \param[in,out] out The output functions structure. */ void (*reset) (pcmk__output_t *out); /*! * \internal * \brief Register a custom message. * * \param[in,out] out The output functions structure. * \param[in] message_id The name of the message to register. This name * will be used as the message_id parameter to the * message function in order to call the custom * format function. * \param[in] fn The custom format function to call for message_id. */ void (*register_message) (pcmk__output_t *out, const char *message_id, pcmk__message_fn_t fn); /*! * \internal * \brief Call a previously registered custom message. * * \param[in,out] out The output functions structure. * \param[in] message_id The name of the message to call. This name must * be the same as the message_id parameter of some * previous call to register_message. * \param[in] ... Arguments to be passed to the registered function. * * \return A standard Pacemaker return code. Generally: 0 if a function was * registered for the message, that function was called, and returned * successfully; EINVAL if no function was registered; or pcmk_rc_no_output * if a function was called but produced no output. */ int (*message) (pcmk__output_t *out, const char *message_id, ...); /*! * \internal * \brief Format the output of a completed subprocess. * * \param[in,out] out The output functions structure. * \param[in] exit_status The exit value of the subprocess. * \param[in] proc_stdout stdout from the completed subprocess. * \param[in] proc_stderr stderr from the completed subprocess. */ void (*subprocess_output) (pcmk__output_t *out, int exit_status, const char *proc_stdout, const char *proc_stderr); /*! * \internal * \brief Format version information. This is useful for the --version * argument of command line tools. * * \param[in,out] out The output functions structure. * \param[in] extended Add additional version information. */ void (*version) (pcmk__output_t *out, bool extended); /*! * \internal * \brief Format an informational message that should be shown to * to an interactive user. Not all formatters will do this. * * \note A newline will automatically be added to the end of the format * string, so callers should not include a newline. * * \note It is possible for a formatter that supports this method to * still not print anything out if is_quiet returns true. * * \param[in,out] out The output functions structure. * \param[in] buf The message to be printed. * \param[in] ... Arguments to be formatted. * * \return A standard Pacemaker return code. Generally: pcmk_rc_ok * if output was produced and pcmk_rc_no_output if it was not. * As not all formatters implement this function, those that * do not will always just return pcmk_rc_no_output. */ int (*info) (pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3); /*! * \internal * \brief Like \p info() but for messages that should appear only * transiently. Not all formatters will do this. * * The originally envisioned use case is for console output, where a * transient status-related message may be quickly overwritten by a refresh. * * \param[in,out] out The output functions structure. * \param[in] format The format string of the message to be printed. * \param[in] ... Arguments to be formatted. * * \return A standard Pacemaker return code. Generally: \p pcmk_rc_ok if * output was produced and \p pcmk_rc_no_output if it was not. As * not all formatters implement this function, those that do not * will always just return \p pcmk_rc_no_output. */ int (*transient) (pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3); /*! * \internal * \brief Format an error message that should be shown to an interactive * user. Not all formatters will do this. * * \note A newline will automatically be added to the end of the format * string, so callers should not include a newline. * * \note Formatters that support this method should always generate output, * even if is_quiet returns true. * * \param[in,out] out The output functions structure. * \param[in] buf The message to be printed. * \param[in] ... Arguments to be formatted. */ void (*err) (pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3); /*! * \internal * \brief Format already formatted XML. * * \param[in,out] out The output functions structure. * \param[in] name A name to associate with the XML. * \param[in] buf The XML in a string. */ void (*output_xml) (pcmk__output_t *out, const char *name, const char *buf); /*! * \internal * \brief Start a new list of items. * * \note For text output, this corresponds to another level of indentation. For * XML output, this corresponds to wrapping any following output in another * layer of tags. * * \note If singular_noun and plural_noun are non-NULL, calling end_list will * result in a summary being added. * * \param[in,out] out The output functions structure. * \param[in] singular_noun When outputting the summary for a list with * one item, the noun to use. * \param[in] plural_noun When outputting the summary for a list with * more than one item, the noun to use. * \param[in] format The format string. * \param[in] ... Arguments to be formatted. */ void (*begin_list) (pcmk__output_t *out, const char *singular_noun, const char *plural_noun, const char *format, ...) G_GNUC_PRINTF(4, 5); /*! * \internal * \brief Format a single item in a list. * * \param[in,out] out The output functions structure. * \param[in] name A name to associate with this item. * \param[in] format The format string. * \param[in] ... Arguments to be formatted. */ void (*list_item) (pcmk__output_t *out, const char *name, const char *format, ...) G_GNUC_PRINTF(3, 4); /*! * \internal * \brief Increment the internal counter of the current list's length. * * Typically, this counter is maintained behind the scenes as a side effect * of calling list_item(). However, custom functions that maintain lists * some other way will need to manage this counter manually. This is * useful for implementing custom message functions and should not be * needed otherwise. * * \param[in,out] out The output functions structure. */ void (*increment_list) (pcmk__output_t *out); /*! * \internal * \brief Conclude a list. * * \note If begin_list was called with non-NULL for both the singular_noun * and plural_noun arguments, this function will output a summary. * Otherwise, no summary will be added. * * \param[in,out] out The output functions structure. */ void (*end_list) (pcmk__output_t *out); /*! * \internal * \brief Should anything be printed to the user? * * \note This takes into account both the \p quiet value as well as the * current formatter. * * \param[in,out] out The output functions structure. * * \return true if output should be supressed, false otherwise. */ bool (*is_quiet) (pcmk__output_t *out); /*! * \internal * \brief Output a spacer. Not all formatters will do this. * * \param[in,out] out The output functions structure. */ void (*spacer) (pcmk__output_t *out); /*! * \internal * \brief Output a progress indicator. This is likely only useful for * plain text, console based formatters. * * \param[in,out] out The output functions structure * \param[in] end If true, output a newline afterwards (this should * only be used the last time this function is called) * */ void (*progress) (pcmk__output_t *out, bool end); /*! * \internal * \brief Prompt the user for input. Not all formatters will do this. * * \note This function is part of pcmk__output_t, but unlike all other * function it does not take that as an argument. In general, a * prompt will go directly to the screen and therefore bypass any * need to use the formatted output code to decide where and how * to display. * * \param[in] prompt The prompt to display. This is required. * \param[in] echo If true, echo the user's input to the screen. Set * to false for password entry. * \param[out] dest Where to store the user's response. This is * required. */ void (*prompt) (const char *prompt, bool echo, char **dest); }; /*! * \internal * \brief Call a formatting function for a previously registered message. * * \note This function is for implementing custom formatters. It should not * be called directly. Instead, call out->message. * * \param[in,out] out The output functions structure. * \param[in] message_id The message to be handled. Unknown messages * will be ignored. * \param[in] ... Arguments to be passed to the registered function. */ int pcmk__call_message(pcmk__output_t *out, const char *message_id, ...); /*! * \internal * \brief Free a ::pcmk__output_t structure that was previously created by * pcmk__output_new(). * * \note While the create and finish functions are designed in such a way that * they can be called repeatedly, this function will completely free the * memory of the object. Once this function has been called, producing * more output requires starting over from pcmk__output_new(). * * \param[in,out] out The output structure. */ void pcmk__output_free(pcmk__output_t *out); /*! * \internal * \brief Create a new ::pcmk__output_t structure. * * This also registers message functions from libcrmcommon. * * \param[in,out] out The destination of the new ::pcmk__output_t. * \param[in] fmt_name How should output be formatted? * \param[in] filename Where should formatted output be written to? This * can be a filename (which will be overwritten if it * already exists), or NULL or "-" for stdout. For no * output, pass a filename of "/dev/null". * \param[in] argv The list of command line arguments. * * \return Standard Pacemaker return code */ int pcmk__output_new(pcmk__output_t **out, const char *fmt_name, const char *filename, char **argv); /*! * \internal * \brief Register a new output formatter, making it available for use * the same as a base formatter. * * \param[in,out] group A ::GOptionGroup that formatted output related command * line arguments should be added to. This can be NULL * for use outside of command line programs. * \param[in] name The name of the format. This will be used to select a * format from command line options and for displaying help. * \param[in] create A function that creates a ::pcmk__output_t. * \param[in] options Format-specific command line options. These will be * added to the context. This argument can also be NULL. * * \return Standard Pacemaker return code */ int pcmk__register_format(GOptionGroup *group, const char *name, pcmk__output_factory_t create, const GOptionEntry *options); /*! * \internal * \brief Register an entire table of output formatters at once. * * \param[in,out] group A ::GOptionGroup that formatted output related command * line arguments should be added to. This can be NULL * for use outside of command line programs. * \param[in] table An array of ::pcmk__supported_format_t which should * all be registered. This array must be NULL-terminated. * */ void pcmk__register_formats(GOptionGroup *group, const pcmk__supported_format_t *table); /*! * \internal * \brief Unregister a previously registered table of custom formatting * functions and destroy the internal data structures associated with them. */ void pcmk__unregister_formats(void); /*! * \internal * \brief Register a function to handle a custom message. * * \note This function is for implementing custom formatters. It should not * be called directly. Instead, call out->register_message. * * \param[in,out] out The output functions structure. * \param[in] message_id The message to be handled. * \param[in] fn The custom format function to call for message_id. */ void pcmk__register_message(pcmk__output_t *out, const char *message_id, pcmk__message_fn_t fn); /*! * \internal * \brief Register an entire table of custom formatting functions at once. * * This table can contain multiple formatting functions for the same message ID * if they are for different format types. * * \param[in,out] out The output functions structure. * \param[in] table An array of ::pcmk__message_entry_t values which should * all be registered. This array must be NULL-terminated. */ void pcmk__register_messages(pcmk__output_t *out, const pcmk__message_entry_t *table); /* Functions that are useful for implementing custom message formatters */ /*! * \internal * \brief A printf-like function. * * This function writes to out->dest and indents the text to the current level * of the text formatter's nesting. This function should be used when implementing * custom message functions for the text output format. It should not be used * for any other purpose. * * Typically, this function should be used instead of printf. * * \param[in,out] out The output functions structure. * \param[in] format The format string. * \param[in] ... Arguments to be passed to the format string. */ void pcmk__indented_printf(pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3); /*! * \internal * \brief A vprintf-like function. * * This function is like pcmk__indented_printf(), except it takes a va_list instead * of a list of arguments. This function should be used when implementing custom * functions for the text output format. It should not be used for any other purpose. * * Typically, this function should be used instead of vprintf. * * \param[in,out] out The output functions structure. * \param[in] format The format string. * \param[in] args A list of arguments to apply to the format string. */ void pcmk__indented_vprintf(pcmk__output_t *out, const char *format, va_list args) G_GNUC_PRINTF(2, 0); /*! * \internal * \brief A printf-like function. * * This function writes to out->dest without indenting the text. This function * should be used when implementing custom message functions for the text output * format. It should not be used for any other purpose. * * \param[in,out] out The output functions structure. * \param[in] format The format string. * \param[in] ... Arguments to be passed to the format string. */ void pcmk__formatted_printf(pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3); /*! * \internal * \brief A vprintf-like function. * * This function is like pcmk__formatted_printf(), except it takes a va_list instead * of a list of arguments. This function should be used when implementing custom * message functions for the text output format. It should not be used for any * other purpose. * * \param[in,out] out The output functions structure. * \param[in] format The format string. * \param[in] args A list of arguments to apply to the format string. */ void pcmk__formatted_vprintf(pcmk__output_t *out, const char *format, va_list args) G_GNUC_PRINTF(2, 0); /*! * \internal * \brief Prompt the user for input. * * \param[in] prompt The prompt to display * \param[in] echo If true, echo the user's input to the screen. Set * to false for password entry. * \param[out] dest Where to store the user's response. */ void pcmk__text_prompt(const char *prompt, bool echo, char **dest); /*! * \internal * \brief Get the log level used by the formatted output logger * * \param[in] out Output object * * \return Log level used by \p out */ uint8_t pcmk__output_get_log_level(const pcmk__output_t *out); /*! * \internal * \brief Set the log level used by the formatted output logger. * * \param[in,out] out The output functions structure. * \param[in] log_level The log level constant (LOG_INFO, LOG_ERR, etc.) * to use. * * \note By default, LOG_INFO is used. * \note Almost all formatted output messages will respect this setting. * However, out->err will always log at LOG_ERR. */ void pcmk__output_set_log_level(pcmk__output_t *out, uint8_t log_level); void pcmk__output_set_log_filter(pcmk__output_t *out, const char *file, const char *function, uint32_t line, uint32_t tags); /*! * \internal * \brief Create and return a new XML node with the given name, as a child of the * current list parent. The new node is then added as the new list parent, * meaning all subsequent nodes will be its children. This is used when * implementing custom functions. * * \param[in,out] out The output functions structure. * \param[in] name The name of the node to be created. * \param[in] ... Name/value pairs to set as XML properties. */ xmlNodePtr pcmk__output_xml_create_parent(pcmk__output_t *out, const char *name, ...) G_GNUC_NULL_TERMINATED; /*! * \internal * \brief Add a copy of the given node as a child of the current list parent. * This is used when implementing custom message functions. * * \param[in,out] out The output functions structure. * \param[in] node An XML node to copy as a child. */ void pcmk__output_xml_add_node_copy(pcmk__output_t *out, xmlNodePtr node); /*! * \internal * \brief Create and return a new XML node with the given name, as a child of the * current list parent. This is used when implementing custom functions. * * \param[in,out] out The output functions structure. * \param[in] name The name of the node to be created. * \param[in] ... Name/value pairs to set as XML properties. */ xmlNodePtr pcmk__output_create_xml_node(pcmk__output_t *out, const char *name, ...) G_GNUC_NULL_TERMINATED; /*! * \internal * \brief Like pcmk__output_create_xml_node(), but add the given text content to the * new node. * * \param[in,out] out The output functions structure. * \param[in] name The name of the node to be created. * \param[in] content The text content of the node. */ xmlNodePtr pcmk__output_create_xml_text_node(pcmk__output_t *out, const char *name, const char *content); /*! * \internal * \brief Push a parent XML node onto the stack. This is used when implementing * custom message functions. * * The XML output formatter maintains an internal stack to keep track of which nodes * are parents in order to build up the tree structure. This function can be used * to temporarily push a new node onto the stack. After calling this function, any * other formatting functions will have their nodes added as children of this new * parent. * * \param[in,out] out The output functions structure * \param[in] parent XML node to add */ void pcmk__output_xml_push_parent(pcmk__output_t *out, xmlNodePtr parent); /*! * \internal * \brief Pop a parent XML node onto the stack. This is used when implementing * custom message functions. * * This function removes a parent node from the stack. See pcmk__xml_push_parent() * for more details. * * \note Little checking is done with this function. Be sure you only pop parents * that were previously pushed. In general, it is best to keep the code between * push and pop simple. * * \param[in,out] out The output functions structure. */ void pcmk__output_xml_pop_parent(pcmk__output_t *out); /*! * \internal * \brief Peek a parent XML node onto the stack. This is used when implementing * custom message functions. * * This function peeks a parent node on stack. See pcmk__xml_push_parent() * for more details. It has no side-effect and can be called for an empty stack. * * \note Little checking is done with this function. * * \param[in,out] out The output functions structure. * * \return NULL if stack is empty, otherwise the parent of the stack. */ xmlNodePtr pcmk__output_xml_peek_parent(pcmk__output_t *out); /*! * \internal * \brief Create a new XML node consisting of the provided text inside an HTML * element node of the given name. * * \param[in,out] out The output functions structure. * \param[in] element_name The name of the new HTML element. * \param[in] id The CSS ID selector to apply to this element. * If NULL, no ID is added. * \param[in] class_name The CSS class selector to apply to this element. * If NULL, no class is added. * \param[in] text The text content of the node. */ xmlNodePtr pcmk__output_create_html_node(pcmk__output_t *out, const char *element_name, const char *id, const char *class_name, const char *text); /*! * \internal * \brief Add an HTML tag to the section. * * The arguments after name are a NULL-terminated list of keys and values, * all of which will be added as attributes to the given tag. For instance, * the following code would generate the tag "": * * \code * pcmk__html_add_header("meta", "http-equiv", "refresh", "content", "19", NULL); * \endcode * * \param[in] name The HTML tag for the new node. * \param[in] ... A NULL-terminated key/value list of attributes. */ void pcmk__html_add_header(const char *name, ...) G_GNUC_NULL_TERMINATED; /*! * \internal * \brief Handle end-of-program error reporting * * \param[in,out] error A GError object potentially containing some error. * If NULL, do nothing. * \param[in,out] out The output functions structure. If NULL, any errors * will simply be printed to stderr. */ void pcmk__output_and_clear_error(GError **error, pcmk__output_t *out); int pcmk__xml_output_new(pcmk__output_t **out, xmlNodePtr *xml); -void pcmk__xml_output_finish(pcmk__output_t *out, xmlNodePtr *xml); +void pcmk__xml_output_finish(pcmk__output_t *out, crm_exit_t exit_status, xmlNodePtr *xml); int pcmk__log_output_new(pcmk__output_t **out); int pcmk__text_output_new(pcmk__output_t **out, const char *filename); /*! * \internal * \brief Select an updated return code for an operation on a \p pcmk__output_t * * This function helps to keep an up-to-date record of the most relevant return * code from a series of operations on a \p pcmk__output_t object. For example, * suppose the object has already produced some output, and we've saved a * \p pcmk_rc_ok return code. A new operation did not produce any output and * returned \p pcmk_rc_no_output. We can ignore the new \p pcmk_rc_no_output * return code and keep the previous \p pcmk_rc_ok return code. * * It prioritizes return codes as follows (from highest to lowest priority): * 1. Other return codes (unexpected errors) * 2. \p pcmk_rc_ok * 3. \p pcmk_rc_no_output * * \param[in] old_rc Saved return code from \p pcmk__output_t operations * \param[in] new_rc New return code from a \p pcmk__output_t operation * * \retval \p old_rc \p new_rc is \p pcmk_rc_no_output, or \p new_rc is * \p pcmk_rc_ok and \p old_rc is not \p pcmk_rc_no_output * \retval \p new_rc Otherwise */ static inline int pcmk__output_select_rc(int old_rc, int new_rc) { switch (new_rc) { case pcmk_rc_no_output: return old_rc; case pcmk_rc_ok: switch (old_rc) { case pcmk_rc_no_output: return new_rc; default: return old_rc; } default: return new_rc; } } #if defined(PCMK__UNIT_TESTING) /* If we are building libcrmcommon_test.a, add this accessor function so we can * inspect the internal formatters hash table. */ GHashTable *pcmk__output_formatters(void); #endif #define PCMK__OUTPUT_SPACER_IF(out_obj, cond) \ if (cond) { \ out->spacer(out); \ } #define PCMK__OUTPUT_LIST_HEADER(out_obj, cond, retcode, title...) \ if (retcode == pcmk_rc_no_output) { \ PCMK__OUTPUT_SPACER_IF(out_obj, cond); \ retcode = pcmk_rc_ok; \ out_obj->begin_list(out_obj, NULL, NULL, title); \ } #define PCMK__OUTPUT_LIST_FOOTER(out_obj, retcode) \ if (retcode == pcmk_rc_ok) { \ out_obj->end_list(out_obj); \ } #ifdef __cplusplus } #endif #endif diff --git a/lib/common/output.c b/lib/common/output.c index 2ea9b0bb7a..23053f63a9 100644 --- a/lib/common/output.c +++ b/lib/common/output.c @@ -1,318 +1,321 @@ /* * Copyright 2019-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include "crmcommon_private.h" static GHashTable *formatters = NULL; #if defined(PCMK__UNIT_TESTING) GHashTable * pcmk__output_formatters(void) { return formatters; } #endif void pcmk__output_free(pcmk__output_t *out) { if (out == NULL) { return; } out->free_priv(out); if (out->messages != NULL) { g_hash_table_destroy(out->messages); } g_free(out->request); free(out); } /*! * \internal * \brief Create a new \p pcmk__output_t structure * * This function does not register any message functions with the newly created * object. * * \param[in,out] out Where to store the new output object * \param[in] fmt_name How to format output * \param[in] filename Where to write formatted output. This can be a * filename (the file will be overwritten if it already * exists), or \p NULL or \p "-" for stdout. For no * output, pass a filename of \p "/dev/null". * \param[in] argv List of command line arguments * * \return Standard Pacemaker return code */ int pcmk__bare_output_new(pcmk__output_t **out, const char *fmt_name, const char *filename, char **argv) { pcmk__output_factory_t create = NULL; CRM_ASSERT(formatters != NULL && out != NULL); /* If no name was given, just try "text". It's up to each tool to register * what it supports so this also may not be valid. */ if (fmt_name == NULL) { create = g_hash_table_lookup(formatters, "text"); } else { create = g_hash_table_lookup(formatters, fmt_name); } if (create == NULL) { return pcmk_rc_unknown_format; } *out = create(argv); if (*out == NULL) { return ENOMEM; } if (pcmk__str_eq(filename, "-", pcmk__str_null_matches)) { (*out)->dest = stdout; } else { (*out)->dest = fopen(filename, "w"); if ((*out)->dest == NULL) { pcmk__output_free(*out); *out = NULL; return errno; } } (*out)->quiet = false; (*out)->messages = pcmk__strkey_table(free, NULL); if ((*out)->init(*out) == false) { pcmk__output_free(*out); return ENOMEM; } setenv("OCF_OUTPUT_FORMAT", (*out)->fmt_name, 1); return pcmk_rc_ok; } int pcmk__output_new(pcmk__output_t **out, const char *fmt_name, const char *filename, char **argv) { int rc = pcmk__bare_output_new(out, fmt_name, filename, argv); if (rc == pcmk_rc_ok) { /* Register libcrmcommon messages (currently they exist only for * patchset) */ pcmk__register_patchset_messages(*out); } return rc; } int pcmk__register_format(GOptionGroup *group, const char *name, pcmk__output_factory_t create, const GOptionEntry *options) { CRM_ASSERT(create != NULL && !pcmk__str_empty(name)); if (formatters == NULL) { formatters = pcmk__strkey_table(free, NULL); } if (options != NULL && group != NULL) { g_option_group_add_entries(group, options); } g_hash_table_insert(formatters, strdup(name), create); return pcmk_rc_ok; } void pcmk__register_formats(GOptionGroup *group, const pcmk__supported_format_t *formats) { if (formats == NULL) { return; } for (const pcmk__supported_format_t *entry = formats; entry->name != NULL; entry++) { pcmk__register_format(group, entry->name, entry->create, entry->options); } } void pcmk__unregister_formats(void) { if (formatters != NULL) { g_hash_table_destroy(formatters); formatters = NULL; } } int pcmk__call_message(pcmk__output_t *out, const char *message_id, ...) { va_list args; int rc = pcmk_rc_ok; pcmk__message_fn_t fn; CRM_ASSERT(out != NULL && !pcmk__str_empty(message_id)); fn = g_hash_table_lookup(out->messages, message_id); if (fn == NULL) { crm_debug("Called unknown output message '%s' for format '%s'", message_id, out->fmt_name); return EINVAL; } va_start(args, message_id); rc = fn(out, args); va_end(args); return rc; } void pcmk__register_message(pcmk__output_t *out, const char *message_id, pcmk__message_fn_t fn) { CRM_ASSERT(out != NULL && !pcmk__str_empty(message_id) && fn != NULL); g_hash_table_replace(out->messages, strdup(message_id), fn); } void pcmk__register_messages(pcmk__output_t *out, const pcmk__message_entry_t *table) { for (const pcmk__message_entry_t *entry = table; entry->message_id != NULL; entry++) { if (pcmk__strcase_any_of(entry->fmt_name, "default", out->fmt_name, NULL)) { pcmk__register_message(out, entry->message_id, entry->fn); } } } void pcmk__output_and_clear_error(GError **error, pcmk__output_t *out) { if (error == NULL || *error == NULL) { return; } if (out != NULL) { out->err(out, "%s: %s", g_get_prgname(), (*error)->message); } else { fprintf(stderr, "%s: %s\n", g_get_prgname(), (*error)->message); } g_clear_error(error); } /*! * \internal * \brief Create an XML-only output object * * Create an output object that supports only the XML format, and free * existing XML if supplied (particularly useful for libpacemaker public API * functions that want to free any previous result supplied by the caller). * * \param[out] out Where to put newly created output object * \param[in,out] xml If non-NULL, this will be freed * * \return Standard Pacemaker return code */ int pcmk__xml_output_new(pcmk__output_t **out, xmlNodePtr *xml) { pcmk__supported_format_t xml_format[] = { PCMK__SUPPORTED_FORMAT_XML, { NULL, NULL, NULL } }; if (*xml != NULL) { xmlFreeNode(*xml); *xml = NULL; } pcmk__register_formats(NULL, xml_format); return pcmk__output_new(out, "xml", NULL, NULL); } /*! * \internal * \brief Finish and free an XML-only output object * - * \param[in,out] out Output object to free - * \param[out] xml If not NULL, where to store XML output + * \param[in,out] out Output object to free + * \param[in] exit_status The exit value of the whole program + * \param[out] xml If not NULL, where to store XML output */ void -pcmk__xml_output_finish(pcmk__output_t *out, xmlNodePtr *xml) { - out->finish(out, 0, FALSE, (void **) xml); +pcmk__xml_output_finish(pcmk__output_t *out, crm_exit_t exit_status, + xmlNodePtr *xml) +{ + out->finish(out, exit_status, FALSE, (void **) xml); pcmk__output_free(out); } /*! * \internal * \brief Create a new output object using the "log" format * * \param[out] out Where to store newly allocated output object * * \return Standard Pacemaker return code */ int pcmk__log_output_new(pcmk__output_t **out) { int rc = pcmk_rc_ok; const char* argv[] = { "", NULL }; pcmk__supported_format_t formats[] = { PCMK__SUPPORTED_FORMAT_LOG, { NULL, NULL, NULL } }; pcmk__register_formats(NULL, formats); rc = pcmk__output_new(out, "log", NULL, (char **) argv); if ((rc != pcmk_rc_ok) || (*out == NULL)) { crm_err("Can't log certain messages due to internal error: %s", pcmk_rc_str(rc)); return rc; } return pcmk_rc_ok; } /*! * \internal * \brief Create a new output object using the "text" format * * \param[out] out Where to store newly allocated output object * \param[in] filename Name of output destination file * * \return Standard Pacemaker return code */ int pcmk__text_output_new(pcmk__output_t **out, const char *filename) { int rc = pcmk_rc_ok; const char* argv[] = { "", NULL }; pcmk__supported_format_t formats[] = { PCMK__SUPPORTED_FORMAT_TEXT, { NULL, NULL, NULL } }; pcmk__register_formats(NULL, formats); rc = pcmk__output_new(out, "text", filename, (char **) argv); if ((rc != pcmk_rc_ok) || (*out == NULL)) { crm_err("Can't create text output object to internal error: %s", pcmk_rc_str(rc)); return rc; } return pcmk_rc_ok; } diff --git a/lib/pacemaker/pcmk_agents.c b/lib/pacemaker/pcmk_agents.c index 6fec140a3a..4dacf403f6 100644 --- a/lib/pacemaker/pcmk_agents.c +++ b/lib/pacemaker/pcmk_agents.c @@ -1,243 +1,243 @@ /* * Copyright 2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include int pcmk__list_alternatives(pcmk__output_t *out, const char *agent_spec) { int rc = pcmk_rc_ok; lrmd_t *lrmd_conn = NULL; lrmd_list_t *list = NULL; CRM_ASSERT(out != NULL && agent_spec != NULL); rc = lrmd__new(&lrmd_conn, NULL, NULL, 0); if (rc != pcmk_rc_ok) { goto error; } rc = lrmd_conn->cmds->list_ocf_providers(lrmd_conn, agent_spec, &list); if (rc > 0) { rc = out->message(out, "alternatives-list", list, agent_spec); } else { rc = pcmk_rc_error; } error: if (rc != pcmk_rc_ok) { out->err(out, _("No %s found for %s"), "OCF providers", agent_spec); rc = ENXIO; } lrmd_api_delete(lrmd_conn); return rc; } // Documented in pacemaker.h int pcmk_list_alternatives(xmlNodePtr *xml, const char *agent_spec) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } lrmd__register_messages(out); rc = pcmk__list_alternatives(out, agent_spec); - pcmk__xml_output_finish(out, xml); + pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } /*! * \internal * \brief List all agents available for the named standard and/or provider * * \param[in,out] out Output object * \param[in] agent_spec STD[:PROV] * * \return Standard Pacemaker return code */ int pcmk__list_agents(pcmk__output_t *out, char *agent_spec) { int rc = pcmk_rc_ok; char *provider = NULL; lrmd_t *lrmd_conn = NULL; lrmd_list_t *list = NULL; CRM_ASSERT(out != NULL && agent_spec != NULL); rc = lrmd__new(&lrmd_conn, NULL, NULL, 0); if (rc != pcmk_rc_ok) { goto error; } provider = strchr(agent_spec, ':'); if (provider) { *provider++ = 0; } rc = lrmd_conn->cmds->list_agents(lrmd_conn, &list, agent_spec, provider); if (rc > 0) { rc = out->message(out, "agents-list", list, agent_spec, provider); } else { rc = pcmk_rc_error; } error: if (rc != pcmk_rc_ok) { if (provider == NULL) { out->err(out, _("No agents found for standard '%s'"), agent_spec); } else { out->err(out, _("No agents found for standard '%s' and provider '%s'"), agent_spec, provider); } } lrmd_api_delete(lrmd_conn); return rc; } // Documented in pacemaker.h int pcmk_list_agents(xmlNodePtr *xml, char *agent_spec) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } lrmd__register_messages(out); rc = pcmk__list_agents(out, agent_spec); - pcmk__xml_output_finish(out, xml); + pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } int pcmk__list_providers(pcmk__output_t *out, const char *agent_spec) { int rc = pcmk_rc_ok; lrmd_t *lrmd_conn = NULL; lrmd_list_t *list = NULL; CRM_ASSERT(out != NULL); rc = lrmd__new(&lrmd_conn, NULL, NULL, 0); if (rc != pcmk_rc_ok) { goto error; } rc = lrmd_conn->cmds->list_ocf_providers(lrmd_conn, agent_spec, &list); if (rc > 0) { rc = out->message(out, "providers-list", list, agent_spec); } else { rc = pcmk_rc_error; } error: if (rc != pcmk_rc_ok) { if (agent_spec == NULL) { out->err(out, _("No %s found"), "OCF providers"); } else { out->err(out, _("No %s found for %s"), "OCF providers", agent_spec); } rc = ENXIO; } lrmd_api_delete(lrmd_conn); return rc; } // Documented in pacemaker.h int pcmk_list_providers(xmlNodePtr *xml, const char *agent_spec) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } lrmd__register_messages(out); rc = pcmk__list_providers(out, agent_spec); - pcmk__xml_output_finish(out, xml); + pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } int pcmk__list_standards(pcmk__output_t *out) { int rc = pcmk_rc_ok; lrmd_t *lrmd_conn = NULL; lrmd_list_t *list = NULL; CRM_ASSERT(out != NULL); rc = lrmd__new(&lrmd_conn, NULL, NULL, 0); if (rc != pcmk_rc_ok) { goto error; } rc = lrmd_conn->cmds->list_standards(lrmd_conn, &list); if (rc > 0) { rc = out->message(out, "standards-list", list); } else { rc = pcmk_rc_error; } error: if (rc != pcmk_rc_ok) { out->err(out, _("No %s found"), "standards"); rc = ENXIO; } lrmd_api_delete(lrmd_conn); return rc; } // Documented in pacemaker.h int pcmk_list_standards(xmlNodePtr *xml) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } lrmd__register_messages(out); rc = pcmk__list_standards(out); - pcmk__xml_output_finish(out, xml); + pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } diff --git a/lib/pacemaker/pcmk_cluster_queries.c b/lib/pacemaker/pcmk_cluster_queries.c index 6a12c459a5..746263ad0a 100644 --- a/lib/pacemaker/pcmk_cluster_queries.c +++ b/lib/pacemaker/pcmk_cluster_queries.c @@ -1,899 +1,899 @@ /* * Copyright 2020-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include // 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, xml); + 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, xml); + 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, xml); + 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, xml); + 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, XML_ATTR_UNAME); const char *id = crm_element_value(result, data->field); // node name and node id are the same for remote/guest nodes out->message(out, "crmadmin-node", data->type, 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"); if (!pcmk__str_empty(node_types) && strstr(node_types, "all")) { node_types = NULL; } if (pcmk__str_empty(node_types) || strstr(node_types, "cluster")) { data.field = "id"; data.type = "cluster"; crm_foreach_xpath_result(xml_node, PCMK__XP_MEMBER_NODE_CONFIG, remote_node_print_helper, &data); } if (pcmk__str_empty(node_types) || strstr(node_types, "guest")) { data.field = "value"; data.type = "guest"; crm_foreach_xpath_result(xml_node, PCMK__XP_GUEST_NODE_CONFIG, remote_node_print_helper, &data); } if (pcmk__str_empty(node_types) || pcmk__str_eq(node_types, ",|^remote", pcmk__str_regex)) { data.field = "id"; data.type = "remote"; crm_foreach_xpath_result(xml_node, PCMK__XP_REMOTE_NODE_CONFIG, remote_node_print_helper, &data); } out->end_list(out); if (data.found == 0) { out->info(out, "No nodes configured"); } free_xml(xml_node); } 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, xml); + pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } diff --git a/lib/pacemaker/pcmk_fence.c b/lib/pacemaker/pcmk_fence.c index 9f86e46edb..d780798ac8 100644 --- a/lib/pacemaker/pcmk_fence.c +++ b/lib/pacemaker/pcmk_fence.c @@ -1,635 +1,635 @@ /* * Copyright 2009-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 static const int st_opts = st_opt_sync_call | st_opt_allow_suicide; static GMainLoop *mainloop = NULL; static struct { stonith_t *st; const char *target; const char *action; char *name; unsigned int timeout; unsigned int tolerance; int delay; pcmk__action_result_t result; } async_fence_data = { NULL, }; static int handle_level(stonith_t *st, const char *target, int fence_level, const stonith_key_value_t *devices, bool added) { const char *node = NULL; const char *pattern = NULL; const char *name = NULL; char *value = NULL; int rc = pcmk_rc_ok; if (target == NULL) { // Not really possible, but makes static analysis happy return EINVAL; } /* Determine if targeting by attribute, node name pattern or node name */ value = strchr(target, '='); if (value != NULL) { name = target; *value++ = '\0'; } else if (*target == '@') { pattern = target + 1; } else { node = target; } /* Register or unregister level as appropriate */ if (added) { rc = st->cmds->register_level_full(st, st_opts, node, pattern, name, value, fence_level, devices); } else { rc = st->cmds->remove_level_full(st, st_opts, node, pattern, name, value, fence_level); } return pcmk_legacy2rc(rc); } static stonith_history_t * reduce_fence_history(stonith_history_t *history) { stonith_history_t *new, *hp, *np; if (!history) { return history; } new = history; hp = new->next; new->next = NULL; while (hp) { stonith_history_t *hp_next = hp->next; hp->next = NULL; for (np = new; ; np = np->next) { if ((hp->state == st_done) || (hp->state == st_failed)) { /* action not in progress */ if (pcmk__str_eq(hp->target, np->target, pcmk__str_casei) && pcmk__str_eq(hp->action, np->action, pcmk__str_none) && (hp->state == np->state) && ((hp->state == st_done) || pcmk__str_eq(hp->delegate, np->delegate, pcmk__str_casei))) { /* purge older hp */ stonith_history_free(hp); break; } } if (!np->next) { np->next = hp; break; } } hp = hp_next; } return new; } static void notify_callback(stonith_t * st, stonith_event_t * e) { if (pcmk__str_eq(async_fence_data.target, e->target, pcmk__str_casei) && pcmk__str_eq(async_fence_data.action, e->action, pcmk__str_none)) { pcmk__set_result(&async_fence_data.result, stonith__event_exit_status(e), stonith__event_execution_status(e), stonith__event_exit_reason(e)); g_main_loop_quit(mainloop); } } static void fence_callback(stonith_t * stonith, stonith_callback_data_t * data) { pcmk__set_result(&async_fence_data.result, stonith__exit_status(data), stonith__execution_status(data), stonith__exit_reason(data)); g_main_loop_quit(mainloop); } static gboolean async_fence_helper(gpointer user_data) { stonith_t *st = async_fence_data.st; int call_id = 0; int rc = stonith_api_connect_retry(st, async_fence_data.name, 10); int timeout = 0; if (rc != pcmk_ok) { g_main_loop_quit(mainloop); pcmk__set_result(&async_fence_data.result, CRM_EX_ERROR, PCMK_EXEC_NOT_CONNECTED, pcmk_strerror(rc)); return TRUE; } st->cmds->register_notification(st, T_STONITH_NOTIFY_FENCE, notify_callback); call_id = st->cmds->fence_with_delay(st, st_opt_allow_suicide, async_fence_data.target, async_fence_data.action, async_fence_data.timeout/1000, async_fence_data.tolerance/1000, async_fence_data.delay); if (call_id < 0) { g_main_loop_quit(mainloop); pcmk__set_result(&async_fence_data.result, CRM_EX_ERROR, PCMK_EXEC_ERROR, pcmk_strerror(call_id)); return TRUE; } timeout = async_fence_data.timeout / 1000; if (async_fence_data.delay > 0) { timeout += async_fence_data.delay; } st->cmds->register_callback(st, call_id, timeout, st_opt_timeout_updates, NULL, "callback", fence_callback); return TRUE; } 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) { crm_trigger_t *trig; int rc = pcmk_rc_ok; async_fence_data.st = st; async_fence_data.name = strdup(name); async_fence_data.target = target; async_fence_data.action = action; async_fence_data.timeout = timeout; async_fence_data.tolerance = tolerance; async_fence_data.delay = delay; pcmk__set_result(&async_fence_data.result, CRM_EX_ERROR, PCMK_EXEC_UNKNOWN, NULL); trig = mainloop_add_trigger(G_PRIORITY_HIGH, async_fence_helper, NULL); mainloop_set_trigger(trig); mainloop = g_main_loop_new(NULL, FALSE); g_main_loop_run(mainloop); free(async_fence_data.name); if (reason != NULL) { // Give the caller ownership of the exit reason *reason = async_fence_data.result.exit_reason; async_fence_data.result.exit_reason = NULL; } rc = stonith__result2rc(&async_fence_data.result); pcmk__reset_result(&async_fence_data.result); return rc; } #ifdef BUILD_PUBLIC_LIBPACEMAKER 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) { return pcmk__request_fencing(st, target, action, name, timeout, tolerance, delay, reason); } #endif int pcmk__fence_history(pcmk__output_t *out, stonith_t *st, const char *target, unsigned int timeout, int verbose, bool broadcast, bool cleanup) { stonith_history_t *history = NULL, *hp, *latest = NULL; int rc = pcmk_rc_ok; int opts = 0; if (cleanup) { out->info(out, "cleaning up fencing-history%s%s", target ? " for node " : "", target ? target : ""); } if (broadcast) { out->info(out, "gather fencing-history from all nodes"); } stonith__set_call_options(opts, target, st_opts); if (cleanup) { stonith__set_call_options(opts, target, st_opt_cleanup); } if (broadcast) { stonith__set_call_options(opts, target, st_opt_broadcast); } if (pcmk__str_eq(target, "*", pcmk__str_none)) { target = NULL; } rc = st->cmds->history(st, opts, target, &history, (timeout / 1000)); if (cleanup) { // Cleanup doesn't return a history list stonith_history_free(history); return pcmk_legacy2rc(rc); } out->begin_list(out, "event", "events", "Fencing history"); history = stonith__sort_history(history); for (hp = history; hp; hp = hp->next) { if (hp->state == st_done) { latest = hp; } if (out->is_quiet(out) || !verbose) { continue; } out->message(out, "stonith-event", hp, true, false, stonith__later_succeeded(hp, history), (uint32_t) pcmk_show_failed_detail); out->increment_list(out); } if (latest) { if (out->is_quiet(out)) { out->message(out, "stonith-event", latest, false, true, NULL, (uint32_t) pcmk_show_failed_detail); } else if (!verbose) { // already printed if verbose out->message(out, "stonith-event", latest, false, false, NULL, (uint32_t) pcmk_show_failed_detail); out->increment_list(out); } } out->end_list(out); stonith_history_free(history); return pcmk_legacy2rc(rc); } #ifdef BUILD_PUBLIC_LIBPACEMAKER int pcmk_fence_history(xmlNodePtr *xml, stonith_t *st, const char *target, unsigned int timeout, bool quiet, int verbose, bool broadcast, bool cleanup) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } stonith__register_messages(out); out->quiet = quiet; rc = pcmk__fence_history(out, st, target, timeout, verbose, broadcast, cleanup); - pcmk__xml_output_finish(out, xml); + pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } #endif int pcmk__fence_installed(pcmk__output_t *out, stonith_t *st, unsigned int timeout) { stonith_key_value_t *devices = NULL; int rc = pcmk_rc_ok; rc = st->cmds->list_agents(st, st_opt_sync_call, NULL, &devices, (timeout / 1000)); // rc is a negative error code or a positive number of agents if (rc < 0) { return pcmk_legacy2rc(rc); } out->begin_list(out, "fence device", "fence devices", "Installed fence devices"); for (stonith_key_value_t *iter = devices; iter != NULL; iter = iter->next) { out->list_item(out, "device", "%s", iter->value); } out->end_list(out); stonith_key_value_freeall(devices, 1, 1); return pcmk_rc_ok; } #ifdef BUILD_PUBLIC_LIBPACEMAKER int pcmk_fence_installed(xmlNodePtr *xml, stonith_t *st, unsigned int timeout) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } stonith__register_messages(out); rc = pcmk__fence_installed(out, st, timeout); - pcmk__xml_output_finish(out, xml); + pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } #endif int pcmk__fence_last(pcmk__output_t *out, const char *target, bool as_nodeid) { time_t when = 0; if (target == NULL) { return pcmk_rc_ok; } if (as_nodeid) { when = stonith_api_time(atol(target), NULL, FALSE); } else { when = stonith_api_time(0, target, FALSE); } return out->message(out, "last-fenced", target, when); } #ifdef BUILD_PUBLIC_LIBPACEMAKER int pcmk_fence_last(xmlNodePtr *xml, const char *target, bool as_nodeid) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } stonith__register_messages(out); rc = pcmk__fence_last(out, target, as_nodeid); - pcmk__xml_output_finish(out, xml); + pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } #endif int pcmk__fence_list_targets(pcmk__output_t *out, stonith_t *st, const char *device_id, unsigned int timeout) { GList *targets = NULL; char *lists = NULL; int rc = pcmk_rc_ok; rc = st->cmds->list(st, st_opts, device_id, &lists, timeout/1000); if (rc != pcmk_rc_ok) { return pcmk_legacy2rc(rc); } targets = stonith__parse_targets(lists); out->begin_list(out, "fence target", "fence targets", "Fence Targets"); while (targets != NULL) { out->list_item(out, NULL, "%s", (const char *) targets->data); targets = targets->next; } out->end_list(out); free(lists); return rc; } #ifdef BUILD_PUBLIC_LIBPACEMAKER int pcmk_fence_list_targets(xmlNodePtr *xml, stonith_t *st, const char *device_id, unsigned int timeout) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } stonith__register_messages(out); rc = pcmk__fence_list_targets(out, st, device_id, timeout); - pcmk__xml_output_finish(out, xml); + pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } #endif int pcmk__fence_metadata(pcmk__output_t *out, stonith_t *st, const char *agent, unsigned int timeout) { char *buffer = NULL; int rc = st->cmds->metadata(st, st_opt_sync_call, agent, NULL, &buffer, timeout/1000); if (rc != pcmk_rc_ok) { return pcmk_legacy2rc(rc); } out->output_xml(out, "metadata", buffer); free(buffer); return rc; } #ifdef BUILD_PUBLIC_LIBPACEMAKER int pcmk_fence_metadata(xmlNodePtr *xml, stonith_t *st, const char *agent, unsigned int timeout) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } stonith__register_messages(out); rc = pcmk__fence_metadata(out, st, agent, timeout); - pcmk__xml_output_finish(out, xml); + pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } #endif int pcmk__fence_registered(pcmk__output_t *out, stonith_t *st, const char *target, unsigned int timeout) { stonith_key_value_t *devices = NULL; int rc = pcmk_rc_ok; rc = st->cmds->query(st, st_opts, target, &devices, timeout/1000); /* query returns a negative error code or a positive number of results. */ if (rc < 0) { return pcmk_legacy2rc(rc); } out->begin_list(out, "fence device", "fence devices", "Registered fence devices"); for (stonith_key_value_t *iter = devices; iter != NULL; iter = iter->next) { out->list_item(out, "device", "%s", iter->value); } out->end_list(out); stonith_key_value_freeall(devices, 1, 1); /* Return pcmk_rc_ok here, not the number of results. Callers probably * don't care. */ return pcmk_rc_ok; } #ifdef BUILD_PUBLIC_LIBPACEMAKER int pcmk_fence_registered(xmlNodePtr *xml, stonith_t *st, const char *target, unsigned int timeout) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } stonith__register_messages(out); rc = pcmk__fence_registered(out, st, target, timeout); - pcmk__xml_output_finish(out, xml); + pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } #endif int pcmk__fence_register_level(stonith_t *st, const char *target, int fence_level, const stonith_key_value_t *devices) { return handle_level(st, target, fence_level, devices, true); } #ifdef BUILD_PUBLIC_LIBPACEMAKER int pcmk_fence_register_level(stonith_t *st, const char *target, int fence_level, const stonith_key_value_t *devices) { return pcmk__fence_register_level(st, target, fence_level, devices); } #endif int pcmk__fence_unregister_level(stonith_t *st, const char *target, int fence_level) { return handle_level(st, target, fence_level, NULL, false); } #ifdef BUILD_PUBLIC_LIBPACEMAKER int pcmk_fence_unregister_level(stonith_t *st, const char *target, int fence_level) { return pcmk__fence_unregister_level(st, target, fence_level); } #endif int pcmk__fence_validate(pcmk__output_t *out, stonith_t *st, const char *agent, const char *id, const stonith_key_value_t *params, unsigned int timeout) { char *output = NULL; char *error_output = NULL; int rc; rc = st->cmds->validate(st, st_opt_sync_call, id, NULL, agent, params, timeout/1000, &output, &error_output); out->message(out, "validate", agent, id, output, error_output, rc); return pcmk_legacy2rc(rc); } #ifdef BUILD_PUBLIC_LIBPACEMAKER int pcmk_fence_validate(xmlNodePtr *xml, stonith_t *st, const char *agent, const char *id, const stonith_key_value_t *params, unsigned int timeout) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } stonith__register_messages(out); rc = pcmk__fence_validate(out, st, agent, id, params, timeout); - pcmk__xml_output_finish(out, xml); + pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } #endif int pcmk__get_fencing_history(stonith_t *st, stonith_history_t **stonith_history, enum pcmk__fence_history fence_history) { int rc = pcmk_rc_ok; if ((st == NULL) || (st->state == stonith_disconnected)) { rc = ENOTCONN; } else if (fence_history != pcmk__fence_history_none) { rc = st->cmds->history(st, st_opt_sync_call, NULL, stonith_history, 120); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { return rc; } *stonith_history = stonith__sort_history(*stonith_history); if (fence_history == pcmk__fence_history_reduced) { *stonith_history = reduce_fence_history(*stonith_history); } } return rc; } diff --git a/lib/pacemaker/pcmk_resource.c b/lib/pacemaker/pcmk_resource.c index 7a1783847c..4799a21445 100644 --- a/lib/pacemaker/pcmk_resource.c +++ b/lib/pacemaker/pcmk_resource.c @@ -1,173 +1,173 @@ /* * Copyright 2021-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 // Search path for resource operation history (takes node name and resource ID) #define XPATH_OP_HISTORY "//" XML_CIB_TAG_STATUS \ "/" XML_CIB_TAG_STATE "[@" XML_ATTR_UNAME "='%s']" \ "/" XML_CIB_TAG_LRM "/" XML_LRM_TAG_RESOURCES \ "/" XML_LRM_TAG_RESOURCE "[@" XML_ATTR_ID "='%s']" static xmlNode * best_op(const pcmk_resource_t *rsc, const pcmk_node_t *node) { char *xpath = NULL; xmlNode *history = NULL; xmlNode *best = NULL; bool best_effective_op = false; guint best_interval = 0; bool best_failure = false; const char *best_digest = NULL; // Find node's resource history xpath = crm_strdup_printf(XPATH_OP_HISTORY, node->details->uname, rsc->id); history = get_xpath_object(xpath, rsc->cluster->input, LOG_NEVER); free(xpath); // Examine each history entry for (xmlNode *lrm_rsc_op = first_named_child(history, XML_LRM_TAG_RSC_OP); lrm_rsc_op != NULL; lrm_rsc_op = crm_next_same_xml(lrm_rsc_op)) { const char *digest = crm_element_value(lrm_rsc_op, XML_LRM_ATTR_RESTART_DIGEST); guint interval_ms = 0; const char *task = crm_element_value(lrm_rsc_op, XML_LRM_ATTR_TASK); bool effective_op = false; bool failure = pcmk__ends_with(ID(lrm_rsc_op), "_last_failure_0"); crm_element_value_ms(lrm_rsc_op, XML_LRM_ATTR_INTERVAL, &interval_ms); effective_op = interval_ms == 0 && pcmk__strcase_any_of(task, PCMK_ACTION_MONITOR, PCMK_ACTION_START, PCMK_ACTION_PROMOTE, PCMK_ACTION_MIGRATE_FROM, NULL); if (best == NULL) { goto is_best; } if (best_effective_op) { // Do not use an ineffective op if there's an effective one. if (!effective_op) { continue; } // Do not use an ineffective non-recurring op if there's a recurring one } else if (best_interval != 0 && !effective_op && interval_ms == 0) { continue; } // Do not use last failure if there's a successful one. if (!best_failure && failure) { continue; } // Do not use an op without a restart digest if there's one with. if (best_digest != NULL && digest == NULL) { continue; } // Do not use an older op if there's a newer one. if (pe__is_newer_op(best, lrm_rsc_op, true) > 0) { continue; } is_best: best = lrm_rsc_op; best_effective_op = effective_op; best_interval = interval_ms; best_failure = failure; best_digest = digest; } return best; } /*! * \internal * \brief Calculate and output resource operation digests * * \param[in,out] out Output object * \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 * * \return Standard Pacemaker return code */ int pcmk__resource_digests(pcmk__output_t *out, pcmk_resource_t *rsc, const pcmk_node_t *node, GHashTable *overrides) { const char *task = NULL; xmlNode *xml_op = NULL; op_digest_cache_t *digests = NULL; guint interval_ms = 0; int rc = pcmk_rc_ok; if ((out == NULL) || (rsc == NULL) || (node == NULL)) { return EINVAL; } if (rsc->variant != pcmk_rsc_variant_primitive) { // Only primitives get operation digests return EOPNOTSUPP; } // Find XML of operation history to use xml_op = best_op(rsc, node); // Generate an operation key if (xml_op != NULL) { task = crm_element_value(xml_op, XML_LRM_ATTR_TASK); crm_element_value_ms(xml_op, XML_LRM_ATTR_INTERVAL_MS, &interval_ms); } if (task == NULL) { // Assume start if no history is available task = PCMK_ACTION_START; interval_ms = 0; } // Calculate and show digests digests = pe__calculate_digests(rsc, task, &interval_ms, node, xml_op, overrides, true, rsc->cluster); rc = out->message(out, "digests", rsc, node, task, interval_ms, digests); pe__free_digests(digests); return rc; } int pcmk_resource_digests(xmlNodePtr *xml, pcmk_resource_t *rsc, const pcmk_node_t *node, GHashTable *overrides, pcmk_scheduler_t *scheduler) { 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__resource_digests(out, rsc, node, overrides); - pcmk__xml_output_finish(out, xml); + pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } diff --git a/lib/pacemaker/pcmk_result_code.c b/lib/pacemaker/pcmk_result_code.c index 4f502764bc..b45eee18d5 100644 --- a/lib/pacemaker/pcmk_result_code.c +++ b/lib/pacemaker/pcmk_result_code.c @@ -1,167 +1,167 @@ /* * Copyright 2022 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 // PRIx32 #include // uint32_t /*! * \internal * \brief Display the name and/or description of a result code * * \param[in,out] out Output object * \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(pcmk__output_t *out, int code, enum pcmk_result_type type, uint32_t flags) { int rc = pcmk_rc_ok; bool quiet_orig = out->quiet; const char *name = NULL; const char *desc = NULL; rc = pcmk_result_get_strings(code, type, &name, &desc); if (rc != pcmk_rc_ok) { out->err(out, "Error looking up result code %d", code); return rc; } // out->quiet controls whether the code is shown (if quiet is supported) out->quiet = !pcmk_is_set(flags, pcmk_rc_disp_code); out->message(out, "result-code", code, pcmk_is_set(flags, pcmk_rc_disp_name)? name : NULL, pcmk_is_set(flags, pcmk_rc_disp_desc)? desc : NULL); out->quiet = quiet_orig; return rc; } // Documented in header int pcmk_show_result_code(xmlNodePtr *xml, int code, enum pcmk_result_type type, uint32_t flags) { 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__show_result_code(out, code, type, flags); - pcmk__xml_output_finish(out, xml); + pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } /*! * \internal * \brief List all valid result codes in a particular family * * \param[in,out] out Output object * \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(pcmk__output_t *out, enum pcmk_result_type type, uint32_t flags) { int rc = pcmk_rc_ok; int start = 0; int end = 0; int code = 0; bool quiet_orig = out->quiet; const char *name = NULL; const char *desc = NULL; rc = pcmk__result_bounds(type, &start, &end); if (rc != pcmk_rc_ok) { out->err(out, "Failed to get result code bounds for result code type " "%#010x" PRIx32, (uint32_t) type); return rc; } code = start; while (code <= end) { int local_rc = pcmk_rc_ok; if (code == (pcmk_rc_error + 1)) { /* Values between pcmk_rc_error and pcmk_rc_ok are reserved for * callers, so skip them */ code = pcmk_rc_ok; continue; } // Shouldn't affect the return code of the whole list operation local_rc = pcmk_result_get_strings(code, type, &name, &desc); if ((local_rc != pcmk_rc_ok) || (name == NULL) || pcmk__str_any_of(name, "Unknown", "CRM_EX_UNKNOWN", NULL)) { code++; continue; } // out->quiet controls whether the code is shown (if quiet is supported) out->quiet = !pcmk_is_set(flags, pcmk_rc_disp_code); out->message(out, "result-code", code, pcmk_is_set(flags, pcmk_rc_disp_name)? name : NULL, pcmk_is_set(flags, pcmk_rc_disp_desc)? desc : NULL); out->quiet = quiet_orig; code++; } return rc; } // Documented in header int pcmk_list_result_codes(xmlNodePtr *xml, enum pcmk_result_type type, uint32_t flags) { 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_result_codes(out, type, flags); - pcmk__xml_output_finish(out, xml); + pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } diff --git a/lib/pacemaker/pcmk_rule.c b/lib/pacemaker/pcmk_rule.c index 99c0b233d5..9d2c70323f 100644 --- a/lib/pacemaker/pcmk_rule.c +++ b/lib/pacemaker/pcmk_rule.c @@ -1,296 +1,296 @@ /* * Copyright 2022-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include /*! * \internal * \brief Evaluate a date expression for a specific time * * \param[in] expr date_expression XML * \param[in] now Time for which to evaluate expression * * \return Standard Pacemaker return code */ static int eval_date_expression(const xmlNode *expr, crm_time_t *now) { pe_rule_eval_data_t rule_data = { .node_hash = NULL, .role = pcmk_role_unknown, .now = now, .match_data = NULL, .rsc_data = NULL, .op_data = NULL }; return pe__eval_date_expr(expr, &rule_data, NULL); } /*! * \internal * \brief Initialize scheduler data for checking rules * * Make our own copies of the CIB XML and date/time object, if they're not * \c NULL. This way we don't have to take ownership of the objects passed via * the API. * * \param[in,out] out Output object * \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[out] scheduler Where to store initialized scheduler data * * \return Standard Pacemaker return code */ static int init_rule_check(pcmk__output_t *out, xmlNodePtr input, const crm_time_t *date, pcmk_scheduler_t **scheduler) { // Allows for cleaner syntax than dereferencing the scheduler argument pcmk_scheduler_t *new_scheduler = NULL; new_scheduler = pe_new_working_set(); if (new_scheduler == NULL) { return ENOMEM; } pe__set_working_set_flags(new_scheduler, pcmk_sched_no_counts|pcmk_sched_no_compat); // Populate the scheduler data // Make our own copy of the given input or fetch the CIB and use that if (input != NULL) { new_scheduler->input = copy_xml(input); if (new_scheduler->input == NULL) { out->err(out, "Failed to copy input XML"); pe_free_working_set(new_scheduler); return ENOMEM; } } else { int rc = cib__signon_query(out, NULL, &(new_scheduler->input)); if (rc != pcmk_rc_ok) { pe_free_working_set(new_scheduler); return rc; } } // Make our own copy of the given crm_time_t object; otherwise // cluster_status() populates with the current time if (date != NULL) { // pcmk_copy_time() guarantees non-NULL new_scheduler->now = pcmk_copy_time(date); } // Unpack everything cluster_status(new_scheduler); *scheduler = new_scheduler; return pcmk_rc_ok; } #define XPATH_NODE_RULE "//" XML_TAG_RULE "[@" XML_ATTR_ID "='%s']" /*! * \internal * \brief Check whether a given rule is in effect * * \param[in] scheduler Scheduler data * \param[in] rule_id The ID of the rule to check * \param[out] error Where to store a rule evaluation error message * * \return Standard Pacemaker return code */ static int eval_rule(pcmk_scheduler_t *scheduler, const char *rule_id, const char **error) { xmlNodePtr cib_constraints = NULL; xmlNodePtr match = NULL; xmlXPathObjectPtr xpath_obj = NULL; char *xpath = NULL; int rc = pcmk_rc_ok; int num_results = 0; *error = NULL; /* Rules are under the constraints node in the XML, so first find that. */ cib_constraints = pcmk_find_cib_element(scheduler->input, XML_CIB_TAG_CONSTRAINTS); /* Get all rules matching the given ID that are also simple enough for us * to check. For the moment, these rules must only have a single * date_expression child and: * - Do not have a date_spec operation, or * - Have a date_spec operation that contains years= but does not contain * moon=. * * We do this in steps to provide better error messages. First, check that * there's any rule with the given ID. */ xpath = crm_strdup_printf(XPATH_NODE_RULE, rule_id); xpath_obj = xpath_search(cib_constraints, xpath); num_results = numXpathResults(xpath_obj); free(xpath); freeXpathObject(xpath_obj); if (num_results == 0) { *error = "Rule not found"; return ENXIO; } if (num_results > 1) { // Should not be possible; schema prevents this *error = "Found more than one rule with matching ID"; return pcmk_rc_duplicate_id; } /* Next, make sure it has exactly one date_expression. */ xpath = crm_strdup_printf(XPATH_NODE_RULE "//date_expression", rule_id); xpath_obj = xpath_search(cib_constraints, xpath); num_results = numXpathResults(xpath_obj); free(xpath); freeXpathObject(xpath_obj); if (num_results != 1) { if (num_results == 0) { *error = "Rule does not have a date expression"; } else { *error = "Rule has more than one date expression"; } return EOPNOTSUPP; } /* Then, check that it's something we actually support. */ xpath = crm_strdup_printf(XPATH_NODE_RULE "//date_expression[" "@" XML_EXPR_ATTR_OPERATION "!='date_spec']", rule_id); xpath_obj = xpath_search(cib_constraints, xpath); num_results = numXpathResults(xpath_obj); free(xpath); if (num_results == 0) { freeXpathObject(xpath_obj); xpath = crm_strdup_printf(XPATH_NODE_RULE "//date_expression[" "@" XML_EXPR_ATTR_OPERATION "='date_spec' " "and date_spec/@years " "and not(date_spec/@moon)]", rule_id); xpath_obj = xpath_search(cib_constraints, xpath); num_results = numXpathResults(xpath_obj); free(xpath); if (num_results == 0) { freeXpathObject(xpath_obj); *error = "Rule must either not use date_spec, or use date_spec " "with years= but not moon="; return EOPNOTSUPP; } } match = getXpathResult(xpath_obj, 0); /* We should have ensured this with the xpath query above, but double- * checking can't hurt. */ CRM_ASSERT(match != NULL); CRM_ASSERT(find_expression_type(match) == time_expr); rc = eval_date_expression(match, scheduler->now); if (rc == pcmk_rc_undetermined) { /* pe__eval_date_expr() should return this only if something is * malformed or missing */ *error = "Error parsing rule"; } freeXpathObject(xpath_obj); return rc; } /*! * \internal * \brief Check whether each rule in a list is in effect * * \param[in,out] out Output object * \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(pcmk__output_t *out, xmlNodePtr input, const crm_time_t *date, const char **rule_ids) { pcmk_scheduler_t *scheduler = NULL; int rc = pcmk_rc_ok; CRM_ASSERT(out != NULL); if (rule_ids == NULL) { // Trivial case; every rule specified is in effect return pcmk_rc_ok; } rc = init_rule_check(out, input, date, &scheduler); if (rc != pcmk_rc_ok) { return rc; } for (const char **rule_id = rule_ids; *rule_id != NULL; rule_id++) { const char *error = NULL; int last_rc = eval_rule(scheduler, *rule_id, &error); out->message(out, "rule-check", *rule_id, last_rc, error); if (last_rc != pcmk_rc_ok) { rc = last_rc; } } pe_free_working_set(scheduler); return rc; } // Documented in pacemaker.h int pcmk_check_rules(xmlNodePtr *xml, xmlNodePtr input, const crm_time_t *date, const char **rule_ids) { 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__check_rules(out, input, date, rule_ids); - pcmk__xml_output_finish(out, xml); + pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } diff --git a/lib/pacemaker/pcmk_simulate.c b/lib/pacemaker/pcmk_simulate.c index 167f8a5f6a..62e4cf49a5 100644 --- a/lib/pacemaker/pcmk_simulate.c +++ b/lib/pacemaker/pcmk_simulate.c @@ -1,1006 +1,1006 @@ /* * Copyright 2021-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include "libpacemaker_private.h" static pcmk__output_t *out = NULL; static cib_t *fake_cib = NULL; static GList *fake_resource_list = NULL; static const GList *fake_op_fail_list = NULL; static void set_effective_date(pcmk_scheduler_t *scheduler, bool print_original, const char *use_date); /*! * \internal * \brief Create an action name for use in a dot graph * * \param[in] action Action to create name for * \param[in] verbose If true, add action ID to name * * \return Newly allocated string with action name * \note It is the caller's responsibility to free the result. */ static char * create_action_name(const pcmk_action_t *action, bool verbose) { 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 != NULL) { action_host = action->node->details->uname; } else if (!pcmk_is_set(action->flags, pcmk_action_pseudo)) { action_host = ""; } if (pcmk__str_eq(action->task, PCMK_ACTION_CANCEL, pcmk__str_none)) { prefix = "Cancel "; task = action->cancel_task; } if (action->rsc != NULL) { clone_name = action->rsc->clone_name; } if (clone_name != NULL) { 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 (pcmk__strcase_any_of(action->task, PCMK_ACTION_NOTIFY, PCMK_ACTION_NOTIFIED, NULL)) { 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 = pcmk__notify_key(clone_name, n_type, n_task); } else { key = pcmk__op_key(clone_name, task, interval_ms); } if (action_host != NULL) { 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 (pcmk__str_eq(action->task, PCMK_ACTION_STONITH, pcmk__str_none)) { 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 (verbose) { char *with_id = crm_strdup_printf("%s (%d)", action_name, action->id); free(action_name); action_name = with_id; } return action_name; } /*! * \internal * \brief Display the status of a cluster * * \param[in,out] scheduler Scheduler data * \param[in] show_opts How to modify display (as pcmk_show_opt_e flags) * \param[in] section_opts Sections to display (as pcmk_section_e flags) * \param[in] title What to use as list title * \param[in] print_spacer Whether to display a spacer first */ static void print_cluster_status(pcmk_scheduler_t *scheduler, uint32_t show_opts, uint32_t section_opts, const char *title, bool print_spacer) { pcmk__output_t *out = scheduler->priv; GList *all = NULL; crm_exit_t stonith_rc = 0; enum pcmk_pacemakerd_state state = pcmk_pacemakerd_state_invalid; section_opts |= pcmk_section_nodes | pcmk_section_resources; show_opts |= pcmk_show_inactive_rscs | pcmk_show_failed_detail; all = g_list_prepend(all, (gpointer) "*"); PCMK__OUTPUT_SPACER_IF(out, print_spacer); out->begin_list(out, NULL, NULL, "%s", title); out->message(out, "cluster-status", scheduler, state, stonith_rc, NULL, false, section_opts, show_opts, NULL, all, all); out->end_list(out); g_list_free(all); } /*! * \internal * \brief Display a summary of all actions scheduled in a transition * * \param[in,out] scheduler Scheduler data (fully scheduled) * \param[in] print_spacer Whether to display a spacer first */ static void print_transition_summary(pcmk_scheduler_t *scheduler, bool print_spacer) { pcmk__output_t *out = scheduler->priv; PCMK__OUTPUT_SPACER_IF(out, print_spacer); out->begin_list(out, NULL, NULL, "Transition Summary"); pcmk__output_actions(scheduler); out->end_list(out); } /*! * \internal * \brief Reset scheduler input, output, date, and flags * * \param[in,out] scheduler Scheduler data * \param[in] input What to set as cluster input * \param[in] out What to set as cluster output object * \param[in] use_date What to set as cluster's current timestamp * \param[in] flags Group of enum pcmk_scheduler_flags to set */ static void reset(pcmk_scheduler_t *scheduler, xmlNodePtr input, pcmk__output_t *out, const char *use_date, unsigned int flags) { scheduler->input = input; scheduler->priv = out; set_effective_date(scheduler, true, use_date); if (pcmk_is_set(flags, pcmk_sim_sanitized)) { pe__set_working_set_flags(scheduler, pcmk_sched_sanitized); } if (pcmk_is_set(flags, pcmk_sim_show_scores)) { pe__set_working_set_flags(scheduler, pcmk_sched_output_scores); } if (pcmk_is_set(flags, pcmk_sim_show_utilization)) { pe__set_working_set_flags(scheduler, pcmk_sched_show_utilization); } } /*! * \brief Write out a file in dot(1) format describing the actions that will * be taken by the scheduler in response to an input CIB file. * * \param[in,out] scheduler Scheduler data * \param[in] dot_file The filename to write * \param[in] all_actions Write all actions, even those that are optional * or are on unmanaged resources * \param[in] verbose Add extra information, such as action IDs, to the * output * * \return Standard Pacemaker return code */ static int write_sim_dotfile(pcmk_scheduler_t *scheduler, const char *dot_file, bool all_actions, bool verbose) { GList *iter = NULL; FILE *dot_strm = fopen(dot_file, "w"); if (dot_strm == NULL) { return errno; } fprintf(dot_strm, " digraph \"g\" {\n"); for (iter = scheduler->actions; iter != NULL; iter = iter->next) { pcmk_action_t *action = (pcmk_action_t *) iter->data; const char *style = "dashed"; const char *font = "black"; const char *color = "black"; char *action_name = create_action_name(action, verbose); if (pcmk_is_set(action->flags, pcmk_action_pseudo)) { font = "orange"; } if (pcmk_is_set(action->flags, pcmk_action_added_to_graph)) { style = "bold"; color = "green"; } else if ((action->rsc != NULL) && !pcmk_is_set(action->rsc->flags, pcmk_rsc_managed)) { color = "red"; font = "purple"; if (!all_actions) { goto do_not_write; } } else if (pcmk_is_set(action->flags, pcmk_action_optional)) { color = "blue"; if (!all_actions) { goto do_not_write; } } else { color = "red"; CRM_LOG_ASSERT(!pcmk_is_set(action->flags, pcmk_action_runnable)); } pe__set_action_flags(action, pcmk_action_added_to_graph); fprintf(dot_strm, "\"%s\" [ style=%s color=\"%s\" fontcolor=\"%s\"]\n", action_name, style, color, font); do_not_write: free(action_name); } for (iter = scheduler->actions; iter != NULL; iter = iter->next) { pcmk_action_t *action = (pcmk_action_t *) iter->data; for (GList *before_iter = action->actions_before; before_iter != NULL; before_iter = before_iter->next) { pcmk__related_action_t *before = before_iter->data; char *before_name = NULL; char *after_name = NULL; const char *style = "dashed"; bool optional = true; if (before->state == pe_link_dumped) { optional = false; style = "bold"; } else if ((uint32_t) before->type == pcmk__ar_none) { continue; } else if (pcmk_is_set(before->action->flags, pcmk_action_added_to_graph) && pcmk_is_set(action->flags, pcmk_action_added_to_graph) && (uint32_t) before->type != pcmk__ar_if_on_same_node_or_target) { optional = false; } if (all_actions || !optional) { before_name = create_action_name(before->action, verbose); after_name = create_action_name(action, verbose); 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); return pcmk_rc_ok; } /*! * \brief Profile the configuration updates and scheduler actions in a single * CIB file, printing the profiling timings. * * \note \p scheduler->priv must have been set to a valid \p pcmk__output_t * object before this function is called. * * \param[in] xml_file The CIB file to profile * \param[in] repeat Number of times to run * \param[in,out] scheduler Scheduler data * \param[in] use_date The date to set the cluster's time to (may be NULL) */ static void profile_file(const char *xml_file, long long repeat, pcmk_scheduler_t *scheduler, const char *use_date) { pcmk__output_t *out = scheduler->priv; xmlNode *cib_object = NULL; clock_t start = 0; clock_t end; unsigned long long scheduler_flags = pcmk_sched_no_compat; CRM_ASSERT(out != NULL); cib_object = filename2xml(xml_file); start = clock(); if (pcmk_find_cib_element(cib_object, XML_CIB_TAG_STATUS) == 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; } if (pcmk_is_set(scheduler->flags, pcmk_sched_output_scores)) { scheduler_flags |= pcmk_sched_output_scores; } if (pcmk_is_set(scheduler->flags, pcmk_sched_show_utilization)) { scheduler_flags |= pcmk_sched_show_utilization; } for (int i = 0; i < repeat; ++i) { xmlNode *input = (repeat == 1)? cib_object : copy_xml(cib_object); scheduler->input = input; set_effective_date(scheduler, false, use_date); pcmk__schedule_actions(input, scheduler_flags, scheduler); pe_reset_working_set(scheduler); } end = clock(); out->message(out, "profile", xml_file, start, end); } void pcmk__profile_dir(const char *dir, long long repeat, pcmk_scheduler_t *scheduler, const char *use_date) { pcmk__output_t *out = scheduler->priv; struct dirent **namelist; int file_num = scandir(dir, &namelist, 0, alphasort); CRM_ASSERT(out != NULL); if (file_num > 0) { struct stat prop; char buffer[FILENAME_MAX]; out->begin_list(out, NULL, NULL, "Timings"); 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_file(buffer, repeat, scheduler, use_date); } free(namelist[file_num]); } free(namelist); out->end_list(out); } } /*! * \brief Set the date of the cluster, either to the value given by * \p use_date, or to the "execution-date" value in the CIB. * * \note \p scheduler->priv must have been set to a valid \p pcmk__output_t * object before this function is called. * * \param[in,out] scheduler Scheduler data * \param[in] print_original If \p true, the "execution-date" should * also be printed * \param[in] use_date The date to set the cluster's time to * (may be NULL) */ static void set_effective_date(pcmk_scheduler_t *scheduler, bool print_original, const char *use_date) { pcmk__output_t *out = scheduler->priv; time_t original_date = 0; CRM_ASSERT(out != NULL); crm_element_value_epoch(scheduler->input, "execution-date", &original_date); if (use_date) { scheduler->now = crm_time_new(use_date); out->info(out, "Setting effective cluster time: %s", use_date); crm_time_log(LOG_NOTICE, "Pretending 'now' is", scheduler->now, crm_time_log_date | crm_time_log_timeofday); } else if (original_date != 0) { scheduler->now = pcmk__copy_timet(original_date); if (print_original) { char *when = crm_time_as_string(scheduler->now, crm_time_log_date|crm_time_log_timeofday); out->info(out, "Using the original execution date of: %s", when); free(when); } } } /*! * \internal * \brief Simulate successfully executing a pseudo-action in a graph * * \param[in,out] graph Graph to update with pseudo-action result * \param[in,out] action Pseudo-action to simulate executing * * \return Standard Pacemaker return code */ static int simulate_pseudo_action(pcmk__graph_t *graph, pcmk__graph_action_t *action) { const char *node = crm_element_value(action->xml, XML_LRM_ATTR_TARGET); const char *task = crm_element_value(action->xml, XML_LRM_ATTR_TASK_KEY); pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed); out->message(out, "inject-pseudo-action", node, task); pcmk__update_graph(graph, action); return pcmk_rc_ok; } /*! * \internal * \brief Simulate executing a resource action in a graph * * \param[in,out] graph Graph to update with resource action result * \param[in,out] action Resource action to simulate executing * * \return Standard Pacemaker return code */ static int simulate_resource_action(pcmk__graph_t *graph, pcmk__graph_action_t *action) { int rc; lrmd_event_data_t *op = NULL; int target_outcome = PCMK_OCF_OK; const char *rtype = NULL; const char *rclass = NULL; const char *resource = NULL; const char *rprovider = NULL; const char *resource_config_name = NULL; const char *operation = crm_element_value(action->xml, "operation"); const char *target_rc_s = crm_meta_value(action->params, XML_ATTR_TE_TARGET_RC); xmlNode *cib_node = NULL; xmlNode *cib_resource = NULL; xmlNode *action_rsc = first_named_child(action->xml, XML_CIB_TAG_RESOURCE); char *node = crm_element_value_copy(action->xml, XML_LRM_ATTR_TARGET); char *uuid = NULL; const char *router_node = crm_element_value(action->xml, XML_LRM_ATTR_ROUTER_NODE); // Certain actions don't need to be displayed or history entries if (pcmk__str_eq(operation, CRM_OP_REPROBE, pcmk__str_none)) { crm_debug("No history injection for %s op on %s", operation, node); goto done; // Confirm action and update graph } if (action_rsc == NULL) { // Shouldn't be possible crm_log_xml_err(action->xml, "Bad"); free(node); return EPROTO; } /* A resource might be known by different names in the configuration and in * the action (for example, a clone instance). Grab the configuration name * (which is preferred when writing history), and if necessary, the instance * name. */ resource_config_name = crm_element_value(action_rsc, XML_ATTR_ID); if (resource_config_name == NULL) { // Shouldn't be possible crm_log_xml_err(action->xml, "No ID"); free(node); return EPROTO; } resource = resource_config_name; if (pe_find_resource(fake_resource_list, resource) == NULL) { const char *longname = crm_element_value(action_rsc, XML_ATTR_ID_LONG); if ((longname != NULL) && (pe_find_resource(fake_resource_list, longname) != NULL)) { resource = longname; } } // Certain actions need to be displayed but don't need history entries if (pcmk__strcase_any_of(operation, PCMK_ACTION_DELETE, PCMK_ACTION_META_DATA, NULL)) { out->message(out, "inject-rsc-action", resource, operation, node, (guint) 0); goto done; // Confirm action and update graph } rclass = crm_element_value(action_rsc, XML_AGENT_ATTR_CLASS); rtype = crm_element_value(action_rsc, XML_ATTR_TYPE); rprovider = crm_element_value(action_rsc, XML_AGENT_ATTR_PROVIDER); pcmk__scan_min_int(target_rc_s, &target_outcome, 0); CRM_ASSERT(fake_cib->cmds->query(fake_cib, NULL, NULL, cib_sync_call|cib_scope_local) == pcmk_ok); // Ensure the action node is in the CIB uuid = crm_element_value_copy(action->xml, XML_LRM_ATTR_TARGET_UUID); cib_node = pcmk__inject_node(fake_cib, node, ((router_node == NULL)? uuid: node)); free(uuid); CRM_ASSERT(cib_node != NULL); // Add a history entry for the action cib_resource = pcmk__inject_resource_history(out, cib_node, resource, resource_config_name, rclass, rtype, rprovider); if (cib_resource == NULL) { crm_err("Could not simulate action %d history for resource %s", action->id, resource); free(node); free_xml(cib_node); return EINVAL; } // Simulate and display an executor event for the action result op = pcmk__event_from_graph_action(cib_resource, action, PCMK_EXEC_DONE, target_outcome, "User-injected result"); out->message(out, "inject-rsc-action", resource, op->op_type, node, op->interval_ms); // Check whether action is in a list of desired simulated failures for (const GList *iter = fake_op_fail_list; iter != NULL; iter = iter->next) { const char *spec = (const char *) iter->data; char *key = NULL; const char *match_name = NULL; // Allow user to specify anonymous clone with or without instance number key = crm_strdup_printf(PCMK__OP_FMT "@%s=", resource, op->op_type, op->interval_ms, node); if (strncasecmp(key, spec, strlen(key)) == 0) { match_name = resource; } free(key); // If not found, try the resource's name in the configuration if ((match_name == NULL) && (strcmp(resource, resource_config_name) != 0)) { key = crm_strdup_printf(PCMK__OP_FMT "@%s=", resource_config_name, op->op_type, op->interval_ms, node); if (strncasecmp(key, spec, strlen(key)) == 0) { match_name = resource_config_name; } free(key); } if (match_name == NULL) { continue; // This failed action entry doesn't match } // ${match_name}_${task}_${interval_in_ms}@${node}=${rc} rc = sscanf(spec, "%*[^=]=%d", (int *) &op->rc); if (rc != 1) { out->err(out, "Invalid failed operation '%s' " "(result code must be integer)", spec); continue; // Keep checking other list entries } out->info(out, "Pretending action %d failed with rc=%d", action->id, op->rc); pcmk__set_graph_action_flags(action, pcmk__graph_action_failed); graph->abort_priority = INFINITY; pcmk__inject_failcount(out, cib_node, match_name, op->op_type, op->interval_ms, op->rc); break; } pcmk__inject_action_result(cib_resource, op, target_outcome); lrmd_free_event(op); rc = fake_cib->cmds->modify(fake_cib, XML_CIB_TAG_STATUS, cib_node, cib_sync_call|cib_scope_local); CRM_ASSERT(rc == pcmk_ok); done: free(node); free_xml(cib_node); pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed); pcmk__update_graph(graph, action); return pcmk_rc_ok; } /*! * \internal * \brief Simulate successfully executing a cluster action * * \param[in,out] graph Graph to update with action result * \param[in,out] action Cluster action to simulate * * \return Standard Pacemaker return code */ static int simulate_cluster_action(pcmk__graph_t *graph, pcmk__graph_action_t *action) { const char *node = crm_element_value(action->xml, XML_LRM_ATTR_TARGET); const char *task = crm_element_value(action->xml, XML_LRM_ATTR_TASK); xmlNode *rsc = first_named_child(action->xml, XML_CIB_TAG_RESOURCE); pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed); out->message(out, "inject-cluster-action", node, task, rsc); pcmk__update_graph(graph, action); return pcmk_rc_ok; } /*! * \internal * \brief Simulate successfully executing a fencing action * * \param[in,out] graph Graph to update with action result * \param[in,out] action Fencing action to simulate * * \return Standard Pacemaker return code */ static int simulate_fencing_action(pcmk__graph_t *graph, pcmk__graph_action_t *action) { const char *op = crm_meta_value(action->params, "stonith_action"); char *target = crm_element_value_copy(action->xml, XML_LRM_ATTR_TARGET); out->message(out, "inject-fencing-action", target, op); if (!pcmk__str_eq(op, PCMK_ACTION_ON, pcmk__str_casei)) { int rc = pcmk_ok; GString *xpath = g_string_sized_new(512); // Set node state to offline xmlNode *cib_node = pcmk__inject_node_state_change(fake_cib, target, false); CRM_ASSERT(cib_node != NULL); crm_xml_add(cib_node, XML_ATTR_ORIGIN, __func__); rc = fake_cib->cmds->replace(fake_cib, XML_CIB_TAG_STATUS, cib_node, cib_sync_call|cib_scope_local); CRM_ASSERT(rc == pcmk_ok); // Simulate controller clearing node's resource history and attributes pcmk__g_strcat(xpath, "//" XML_CIB_TAG_STATE "[@" XML_ATTR_UNAME "='", target, "']/" XML_CIB_TAG_LRM, NULL); fake_cib->cmds->remove(fake_cib, (const char *) xpath->str, NULL, cib_xpath|cib_sync_call|cib_scope_local); g_string_truncate(xpath, 0); pcmk__g_strcat(xpath, "//" XML_CIB_TAG_STATE "[@" XML_ATTR_UNAME "='", target, "']" "/" XML_TAG_TRANSIENT_NODEATTRS, NULL); fake_cib->cmds->remove(fake_cib, (const char *) xpath->str, NULL, cib_xpath|cib_sync_call|cib_scope_local); free_xml(cib_node); g_string_free(xpath, TRUE); } pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed); pcmk__update_graph(graph, action); free(target); return pcmk_rc_ok; } enum pcmk__graph_status pcmk__simulate_transition(pcmk_scheduler_t *scheduler, cib_t *cib, const GList *op_fail_list) { pcmk__graph_t *transition = NULL; enum pcmk__graph_status graph_rc; pcmk__graph_functions_t simulation_fns = { simulate_pseudo_action, simulate_resource_action, simulate_cluster_action, simulate_fencing_action, }; out = scheduler->priv; fake_cib = cib; fake_op_fail_list = op_fail_list; if (!out->is_quiet(out)) { out->begin_list(out, NULL, NULL, "Executing Cluster Transition"); } pcmk__set_graph_functions(&simulation_fns); transition = pcmk__unpack_graph(scheduler->graph, crm_system_name); pcmk__log_graph(LOG_DEBUG, transition); fake_resource_list = scheduler->resources; do { graph_rc = pcmk__execute_graph(transition); } while (graph_rc == pcmk__graph_active); fake_resource_list = NULL; if (graph_rc != pcmk__graph_complete) { out->err(out, "Transition failed: %s", pcmk__graph_status2text(graph_rc)); pcmk__log_graph(LOG_ERR, transition); out->err(out, "An invalid transition was produced"); } pcmk__free_graph(transition); if (!out->is_quiet(out)) { // If not quiet, we'll need the resulting CIB for later display xmlNode *cib_object = NULL; int rc = fake_cib->cmds->query(fake_cib, NULL, &cib_object, cib_sync_call|cib_scope_local); CRM_ASSERT(rc == pcmk_ok); pe_reset_working_set(scheduler); scheduler->input = cib_object; out->end_list(out); } return graph_rc; } int pcmk__simulate(pcmk_scheduler_t *scheduler, pcmk__output_t *out, const pcmk_injections_t *injections, unsigned int flags, uint32_t section_opts, const char *use_date, const char *input_file, const char *graph_file, const char *dot_file) { int printed = pcmk_rc_no_output; int rc = pcmk_rc_ok; xmlNodePtr input = NULL; cib_t *cib = NULL; rc = cib__signon_query(out, &cib, &input); if (rc != pcmk_rc_ok) { goto simulate_done; } reset(scheduler, input, out, use_date, flags); cluster_status(scheduler); if ((cib->variant == cib_native) && pcmk_is_set(section_opts, pcmk_section_times)) { if (pcmk__our_nodename == NULL) { // Currently used only in the times section pcmk__query_node_name(out, 0, &pcmk__our_nodename, 0); } scheduler->localhost = pcmk__our_nodename; } if (!out->is_quiet(out)) { const bool show_pending = pcmk_is_set(flags, pcmk_sim_show_pending); if (pcmk_is_set(scheduler->flags, pcmk_sched_in_maintenance)) { printed = out->message(out, "maint-mode", scheduler->flags); } if (scheduler->disabled_resources || scheduler->blocked_resources) { PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok); printed = out->info(out, "%d of %d resource instances DISABLED and " "%d BLOCKED from further action due to failure", scheduler->disabled_resources, scheduler->ninstances, scheduler->blocked_resources); } /* Most formatted output headers use caps for each word, but this one * only has the first word capitalized for compatibility with pcs. */ print_cluster_status(scheduler, (show_pending? pcmk_show_pending : 0), section_opts, "Current cluster status", (printed == pcmk_rc_ok)); printed = pcmk_rc_ok; } // If the user requested any injections, handle them if ((injections->node_down != NULL) || (injections->node_fail != NULL) || (injections->node_up != NULL) || (injections->op_inject != NULL) || (injections->ticket_activate != NULL) || (injections->ticket_grant != NULL) || (injections->ticket_revoke != NULL) || (injections->ticket_standby != NULL) || (injections->watchdog != NULL)) { PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok); pcmk__inject_scheduler_input(scheduler, cib, injections); printed = pcmk_rc_ok; rc = cib->cmds->query(cib, NULL, &input, cib_sync_call); if (rc != pcmk_rc_ok) { rc = pcmk_legacy2rc(rc); goto simulate_done; } cleanup_calculations(scheduler); reset(scheduler, input, out, use_date, flags); cluster_status(scheduler); } if (input_file != NULL) { rc = write_xml_file(input, input_file, FALSE); if (rc < 0) { rc = pcmk_legacy2rc(rc); goto simulate_done; } } if (pcmk_any_flags_set(flags, pcmk_sim_process | pcmk_sim_simulate)) { pcmk__output_t *logger_out = NULL; unsigned long long scheduler_flags = pcmk_sched_no_compat; if (pcmk_is_set(scheduler->flags, pcmk_sched_output_scores)) { scheduler_flags |= pcmk_sched_output_scores; } if (pcmk_is_set(scheduler->flags, pcmk_sched_show_utilization)) { scheduler_flags |= pcmk_sched_show_utilization; } if (pcmk_all_flags_set(scheduler->flags, pcmk_sched_output_scores |pcmk_sched_show_utilization)) { PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok); out->begin_list(out, NULL, NULL, "Assignment Scores and Utilization Information"); printed = pcmk_rc_ok; } else if (pcmk_is_set(scheduler->flags, pcmk_sched_output_scores)) { PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok); out->begin_list(out, NULL, NULL, "Assignment Scores"); printed = pcmk_rc_ok; } else if (pcmk_is_set(scheduler->flags, pcmk_sched_show_utilization)) { PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok); out->begin_list(out, NULL, NULL, "Utilization Information"); printed = pcmk_rc_ok; } else { rc = pcmk__log_output_new(&logger_out); if (rc != pcmk_rc_ok) { goto simulate_done; } pe__register_messages(logger_out); pcmk__register_lib_messages(logger_out); scheduler->priv = logger_out; } pcmk__schedule_actions(input, scheduler_flags, scheduler); if (logger_out == NULL) { out->end_list(out); } else { logger_out->finish(logger_out, CRM_EX_OK, true, NULL); pcmk__output_free(logger_out); scheduler->priv = out; } input = NULL; /* Don't try and free it twice */ if (graph_file != NULL) { rc = write_xml_file(scheduler->graph, graph_file, FALSE); if (rc < 0) { rc = pcmk_rc_graph_error; goto simulate_done; } } if (dot_file != NULL) { rc = write_sim_dotfile(scheduler, dot_file, pcmk_is_set(flags, pcmk_sim_all_actions), pcmk_is_set(flags, pcmk_sim_verbose)); if (rc != pcmk_rc_ok) { rc = pcmk_rc_dot_error; goto simulate_done; } } if (!out->is_quiet(out)) { print_transition_summary(scheduler, printed == pcmk_rc_ok); } } rc = pcmk_rc_ok; if (!pcmk_is_set(flags, pcmk_sim_simulate)) { goto simulate_done; } PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok); if (pcmk__simulate_transition(scheduler, cib, injections->op_fail) != pcmk__graph_complete) { rc = pcmk_rc_invalid_transition; } if (out->is_quiet(out)) { goto simulate_done; } set_effective_date(scheduler, true, use_date); if (pcmk_is_set(flags, pcmk_sim_show_scores)) { pe__set_working_set_flags(scheduler, pcmk_sched_output_scores); } if (pcmk_is_set(flags, pcmk_sim_show_utilization)) { pe__set_working_set_flags(scheduler, pcmk_sched_show_utilization); } cluster_status(scheduler); print_cluster_status(scheduler, 0, section_opts, "Revised Cluster Status", true); simulate_done: cib__clean_up_connection(&cib); return rc; } 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) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } pe__register_messages(out); pcmk__register_lib_messages(out); rc = pcmk__simulate(scheduler, out, injections, flags, section_opts, use_date, input_file, graph_file, dot_file); - pcmk__xml_output_finish(out, xml); + pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } diff --git a/lib/pacemaker/pcmk_status.c b/lib/pacemaker/pcmk_status.c index 77b6c903ff..462d9c6a0e 100644 --- a/lib/pacemaker/pcmk_status.c +++ b/lib/pacemaker/pcmk_status.c @@ -1,381 +1,381 @@ /* * 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 static stonith_t * fencing_connect(void) { stonith_t *st = stonith_api_new(); int rc = pcmk_rc_ok; if (st == NULL) { return NULL; } rc = st->cmds->connect(st, crm_system_name, NULL); if (rc == pcmk_rc_ok) { return st; } else { stonith_api_delete(st); return NULL; } } /*! * \internal * \brief Output the cluster status given a fencer and CIB connection * * \param[in,out] out Output object * \param[in,out] stonith Fencer connection * \param[in,out] cib CIB connection * \param[in] current_cib Current CIB XML * \param[in] pcmkd_state \p pacemakerd state * \param[in] fence_history How much of the fencing history to output * \param[in] show Group of \p pcmk_section_e flags * \param[in] show_opts Group of \p pcmk_show_opt_e flags * \param[in] only_node If a node name or tag, include only the * matching node(s) (if any) in the output. * If \p "*" or \p NULL, include all nodes * in the output. * \param[in] only_rsc If a resource ID or tag, include only the * matching resource(s) (if any) in the * output. If \p "*" or \p NULL, include all * resources in the output. * \param[in] neg_location_prefix Prefix denoting a ban in a constraint ID * \param[in] simple_output Whether to use a simple output format. * Note: This is for use by \p crm_mon only * and is planned to be deprecated. * * \return Standard Pacemaker return code */ int pcmk__output_cluster_status(pcmk__output_t *out, stonith_t *stonith, cib_t *cib, xmlNode *current_cib, enum pcmk_pacemakerd_state pcmkd_state, enum pcmk__fence_history fence_history, uint32_t show, uint32_t show_opts, const char *only_node, const char *only_rsc, const char *neg_location_prefix, bool simple_output) { xmlNode *cib_copy = copy_xml(current_cib); stonith_history_t *stonith_history = NULL; int history_rc = 0; pcmk_scheduler_t *scheduler = NULL; GList *unames = NULL; GList *resources = NULL; int rc = pcmk_rc_ok; if (cli_config_update(&cib_copy, NULL, FALSE) == FALSE) { cib__clean_up_connection(&cib); free_xml(cib_copy); rc = pcmk_rc_schema_validation; out->err(out, "Upgrade failed: %s", pcmk_rc_str(rc)); return rc; } /* get the stonith-history if there is evidence we need it */ if (fence_history != pcmk__fence_history_none) { history_rc = pcmk__get_fencing_history(stonith, &stonith_history, fence_history); } scheduler = pe_new_working_set(); CRM_ASSERT(scheduler != NULL); pe__set_working_set_flags(scheduler, pcmk_sched_no_compat); scheduler->input = cib_copy; scheduler->priv = out; cluster_status(scheduler); if ((cib->variant == cib_native) && pcmk_is_set(show, pcmk_section_times)) { if (pcmk__our_nodename == NULL) { // Currently used only in the times section pcmk__query_node_name(out, 0, &pcmk__our_nodename, 0); } scheduler->localhost = pcmk__our_nodename; } /* Unpack constraints if any section will need them * (tickets may be referenced in constraints but not granted yet, * and bans need negative location constraints) */ if (pcmk_is_set(show, pcmk_section_bans) || pcmk_is_set(show, pcmk_section_tickets)) { pcmk__unpack_constraints(scheduler); } unames = pe__build_node_name_list(scheduler, only_node); resources = pe__build_rsc_list(scheduler, only_rsc); /* Always print DC if NULL. */ if (scheduler->dc_node == NULL) { show |= pcmk_section_dc; } if (simple_output) { rc = pcmk__output_simple_status(out, scheduler); } else { out->message(out, "cluster-status", scheduler, pcmkd_state, pcmk_rc2exitc(history_rc), stonith_history, fence_history, show, show_opts, neg_location_prefix, unames, resources); } g_list_free_full(unames, free); g_list_free_full(resources, free); stonith_history_free(stonith_history); stonith_history = NULL; pe_free_working_set(scheduler); return rc; } int pcmk_status(xmlNodePtr *xml) { cib_t *cib = NULL; pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; uint32_t show_opts = pcmk_show_pending |pcmk_show_inactive_rscs |pcmk_show_timing; cib = cib_new(); if (cib == NULL) { return pcmk_rc_cib_corrupt; } rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { cib_delete(cib); return rc; } pcmk__register_lib_messages(out); pe__register_messages(out); stonith__register_messages(out); rc = pcmk__status(out, cib, pcmk__fence_history_full, pcmk_section_all, show_opts, NULL, NULL, NULL, false, 0); - pcmk__xml_output_finish(out, xml); + pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); cib_delete(cib); return rc; } /*! * \internal * \brief Query and output the cluster status * * The operation is considered a success if we're able to get the \p pacemakerd * state. If possible, we'll also try to connect to the fencer and CIB and * output their respective status information. * * \param[in,out] out Output object * \param[in,out] cib CIB connection * \param[in] fence_history How much of the fencing history to output * \param[in] show Group of \p pcmk_section_e flags * \param[in] show_opts Group of \p pcmk_show_opt_e flags * \param[in] only_node If a node name or tag, include only the * matching node(s) (if any) in the output. * If \p "*" or \p NULL, include all nodes * in the output. * \param[in] only_rsc If a resource ID or tag, include only the * matching resource(s) (if any) in the * output. If \p "*" or \p NULL, include all * resources in the output. * \param[in] neg_location_prefix Prefix denoting a ban in a constraint ID * \param[in] simple_output Whether to use a simple output format. * Note: This is for use by \p crm_mon only * and is planned to be deprecated. * \param[in] timeout_ms How long to wait for a reply from the * \p pacemakerd API. If 0, * \p pcmk_ipc_dispatch_sync will be used. * If positive, \p pcmk_ipc_dispatch_main * will be used, and a new mainloop will be * created for this purpose (freed before * return). * * \return Standard Pacemaker return code */ int pcmk__status(pcmk__output_t *out, cib_t *cib, enum pcmk__fence_history fence_history, uint32_t show, uint32_t show_opts, const char *only_node, const char *only_rsc, const char *neg_location_prefix, bool simple_output, unsigned int timeout_ms) { xmlNode *current_cib = NULL; int rc = pcmk_rc_ok; stonith_t *stonith = NULL; enum pcmk_pacemakerd_state pcmkd_state = pcmk_pacemakerd_state_invalid; time_t last_updated = 0; if (cib == NULL) { return ENOTCONN; } if (cib->variant == cib_native) { rc = pcmk__pacemakerd_status(out, crm_system_name, timeout_ms, false, &pcmkd_state); if (rc != pcmk_rc_ok) { return rc; } last_updated = time(NULL); switch (pcmkd_state) { case pcmk_pacemakerd_state_running: case pcmk_pacemakerd_state_shutting_down: case pcmk_pacemakerd_state_remote: /* Fencer and CIB may still be available while shutting down or * running on a Pacemaker Remote node */ break; default: // Fencer and CIB are definitely unavailable out->message(out, "pacemakerd-health", NULL, pcmkd_state, NULL, last_updated); return rc; } if (fence_history != pcmk__fence_history_none) { stonith = fencing_connect(); } } rc = cib__signon_query(out, &cib, ¤t_cib); if (rc != pcmk_rc_ok) { if (pcmkd_state != pcmk_pacemakerd_state_invalid) { // Invalid at this point means we didn't query the pcmkd state out->message(out, "pacemakerd-health", NULL, pcmkd_state, NULL, last_updated); } goto done; } rc = pcmk__output_cluster_status(out, stonith, cib, current_cib, pcmkd_state, fence_history, show, show_opts, only_node, only_rsc, neg_location_prefix, simple_output); if (rc != pcmk_rc_ok) { out->err(out, "Error outputting status info from the fencer or CIB"); } done: stonith_api_delete(stonith); free_xml(current_cib); return pcmk_rc_ok; } /*! * \internal * \brief Output cluster status in Nagios Plugin format * * \param[in,out] out Output object * \param[in] scheduler Scheduler data * * \return Standard Pacemaker return code * \note This is for a deprecated crm_mon option and should be called only for * that. */ int pcmk__output_simple_status(pcmk__output_t *out, const pcmk_scheduler_t *scheduler) { int nodes_online = 0; int nodes_standby = 0; int nodes_maint = 0; GString *offline_nodes = NULL; bool no_dc = false; bool offline = false; bool has_warnings = false; if (scheduler->dc_node == NULL) { has_warnings = true; no_dc = true; } for (GList *iter = scheduler->nodes; iter != NULL; iter = iter->next) { pcmk_node_t *node = (pcmk_node_t *) iter->data; if (node->details->standby && node->details->online) { nodes_standby++; } else if (node->details->maintenance && node->details->online) { nodes_maint++; } else if (node->details->online) { nodes_online++; } else { pcmk__add_word(&offline_nodes, 1024, "offline node:"); pcmk__add_word(&offline_nodes, 0, pe__node_name(node)); has_warnings = true; offline = true; } } if (has_warnings) { out->info(out, "CLUSTER WARN: %s%s%s", no_dc ? "No DC" : "", no_dc && offline ? ", " : "", (offline? (const char *) offline_nodes->str : "")); if (offline_nodes != NULL) { g_string_free(offline_nodes, TRUE); } } else { char *nodes_standby_s = NULL; char *nodes_maint_s = NULL; if (nodes_standby > 0) { nodes_standby_s = crm_strdup_printf(", %d standby node%s", nodes_standby, pcmk__plural_s(nodes_standby)); } if (nodes_maint > 0) { nodes_maint_s = crm_strdup_printf(", %d maintenance node%s", nodes_maint, pcmk__plural_s(nodes_maint)); } out->info(out, "CLUSTER OK: %d node%s online%s%s, " "%d resource instance%s configured", nodes_online, pcmk__plural_s(nodes_online), nodes_standby_s != NULL ? nodes_standby_s : "", nodes_maint_s != NULL ? nodes_maint_s : "", scheduler->ninstances, pcmk__plural_s(scheduler->ninstances)); free(nodes_standby_s); free(nodes_maint_s); } if (has_warnings) { return pcmk_rc_error; } else { return pcmk_rc_ok; } /* coverity[leaked_storage] False positive */ }