diff --git a/include/crm/common/output_internal.h b/include/crm/common/output_internal.h index 7dfc57821e..26b205a0bf 100644 --- a/include/crm/common/output_internal.h +++ b/include/crm/common/output_internal.h @@ -1,885 +1,885 @@ /* * Copyright 2019-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. */ #ifndef PCMK__OUTPUT_INTERNAL__H # define PCMK__OUTPUT_INTERNAL__H # include # include # include # include # include # include #ifdef __cplusplus extern "C" { #endif /** * \file * \brief Formatted output for pacemaker tools */ # define PCMK__API_VERSION "2.21" #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. * * \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. * * \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 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. * * \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 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 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 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. * * \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 0 on success or an error code on error. */ int pcmk__register_format(GOptionGroup *group, const char *name, pcmk__output_factory_t create, 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, 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, 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 should be used when implementing * custom message functions instead of printf. * * \param[in,out] out The output functions structure. */ 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 should be used when implementing custom message * functions 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 should be * used with implementing custom message functions instead of printf. * * \param[in,out] out The output functions structure. */ 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 should be used when implementing custom message * functions 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__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 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, int log_level); /*! * \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 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 be added as a child. */ void pcmk__output_xml_add_node(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] node The node to be added/ */ void pcmk__output_xml_push_parent(pcmk__output_t *out, xmlNodePtr node); /*! * \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 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__out_prologue(pcmk__output_t **out, xmlNodePtr *xml); +int pcmk__xml_output_new(pcmk__output_t **out, xmlNodePtr *xml); void pcmk__out_epilogue(pcmk__output_t *out, xmlNodePtr *xml, int retval); #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 52432116ff..aa00a7d5c7 100644 --- a/lib/common/output.c +++ b/lib/common/output.c @@ -1,209 +1,213 @@ /* - * Copyright 2019-2021 the Pacemaker project contributors + * Copyright 2019-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 static GHashTable *formatters = NULL; void pcmk__output_free(pcmk__output_t *out) { out->free_priv(out); if (out->messages != NULL) { g_hash_table_destroy(out->messages); } g_free(out->request); free(out); } int pcmk__output_new(pcmk__output_t **out, const char *fmt_name, const char *filename, char **argv) { pcmk__output_factory_t create = NULL;; if (formatters == NULL) { return EINVAL; } /* 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) { 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__register_format(GOptionGroup *group, const char *name, pcmk__output_factory_t create, GOptionEntry *options) { if (create == NULL) { return -EINVAL; } 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 0; } void pcmk__register_formats(GOptionGroup *group, pcmk__supported_format_t *formats) { pcmk__supported_format_t *entry = NULL; if (formats == NULL) { return; } for (entry = formats; entry->name != NULL; entry++) { pcmk__register_format(group, entry->name, entry->create, entry->options); } } void pcmk__unregister_formats() { if (formatters != NULL) { g_hash_table_destroy(formatters); } } 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; 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) { g_hash_table_replace(out->messages, strdup(message_id), fn); } void pcmk__register_messages(pcmk__output_t *out, pcmk__message_entry_t *table) { pcmk__message_entry_t *entry; for (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) { 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 * - * \param[out] out Where to put newly created output object - * \param[in] xml If non-NULL, this will be freed + * 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__out_prologue(pcmk__output_t **out, xmlNodePtr *xml) { +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 Free an XML-only output object * * \param[in] out Output object to free * \param[out] xml Where to store XML output if needed * \param[in] retval Store XML output if this is pcmk_rc_ok */ void pcmk__out_epilogue(pcmk__output_t *out, xmlNodePtr *xml, int retval) { if (retval == pcmk_rc_ok) { out->finish(out, 0, FALSE, (void **) xml); } pcmk__output_free(out); } diff --git a/lib/pacemaker/pcmk_cluster_queries.c b/lib/pacemaker/pcmk_cluster_queries.c index c30a9b889a..bf8ac309d1 100644 --- a/lib/pacemaker/pcmk_cluster_queries.c +++ b/lib/pacemaker/pcmk_cluster_queries.c @@ -1,502 +1,502 @@ /* - * Copyright 2020-2021 the Pacemaker project contributors + * Copyright 2020-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 // gboolean, GMainLoop, etc. #include // xmlNode #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEFAULT_MESSAGE_TIMEOUT_MS 30000 typedef struct { pcmk__output_t *out; GMainLoop *mainloop; int rc; guint message_timer_id; guint message_timeout_ms; } data_t; static void quit_main_loop(data_t *data) { if (data->mainloop != NULL) { GMainLoop *mloop = data->mainloop; data->mainloop = NULL; // Don't re-enter this block pcmk_quit_main_loop(mloop, 10); g_main_loop_unref(mloop); } } static gboolean admin_message_timeout(gpointer user_data) { data_t *data = user_data; pcmk__output_t *out = data->out; out->err(out, "error: No reply received from controller before timeout (%dms)", data->message_timeout_ms); data->message_timer_id = 0; data->rc = ETIMEDOUT; quit_main_loop(data); return FALSE; // Tells glib to remove source } static void start_main_loop(data_t *data) { if (data->message_timeout_ms < 1) { data->message_timeout_ms = DEFAULT_MESSAGE_TIMEOUT_MS; } data->rc = ECONNRESET; // For unexpected disconnects data->mainloop = g_main_loop_new(NULL, FALSE); data->message_timer_id = g_timeout_add(data->message_timeout_ms, admin_message_timeout, data); g_main_loop_run(data->mainloop); } static void event_done(data_t *data, pcmk_ipc_api_t *api) { pcmk_disconnect_ipc(api); quit_main_loop(data); } static pcmk_controld_api_reply_t * controld_event_reply(data_t *data, pcmk_ipc_api_t *controld_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data) { pcmk__output_t *out = data->out; pcmk_controld_api_reply_t *reply = event_data; switch (event_type) { case pcmk_ipc_event_disconnect: if (data->rc == ECONNRESET) { // Unexpected out->err(out, "error: Lost connection to controller"); } event_done(data, controld_api); return NULL; case pcmk_ipc_event_reply: break; default: return NULL; } if (data->message_timer_id != 0) { g_source_remove(data->message_timer_id); data->message_timer_id = 0; } if (status != CRM_EX_OK) { out->err(out, "error: Bad reply from controller: %s", crm_exit_str(status)); data->rc = EBADMSG; event_done(data, controld_api); return NULL; } if (reply->reply_type != pcmk_controld_reply_ping) { out->err(out, "error: Unknown reply type %d from controller", reply->reply_type); data->rc = EBADMSG; event_done(data, controld_api); return NULL; } return reply; } static void controller_status_event_cb(pcmk_ipc_api_t *controld_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { data_t *data = user_data; pcmk__output_t *out = data->out; pcmk_controld_api_reply_t *reply = controld_event_reply(data, controld_api, event_type, status, event_data); if (reply != NULL) { out->message(out, "health", reply->data.ping.sys_from, reply->host_from, reply->data.ping.fsa_state, reply->data.ping.result); data->rc = pcmk_rc_ok; } event_done(data, controld_api); } static void designated_controller_event_cb(pcmk_ipc_api_t *controld_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { data_t *data = user_data; pcmk__output_t *out = data->out; pcmk_controld_api_reply_t *reply = controld_event_reply(data, controld_api, event_type, status, event_data); if (reply != NULL) { out->message(out, "dc", reply->host_from); data->rc = pcmk_rc_ok; } event_done(data, controld_api); } static void pacemakerd_event_cb(pcmk_ipc_api_t *pacemakerd_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { data_t *data = user_data; pcmk__output_t *out = data->out; pcmk_pacemakerd_api_reply_t *reply = event_data; crm_time_t *crm_when; char *pinged_buf = NULL; switch (event_type) { case pcmk_ipc_event_disconnect: if (data->rc == ECONNRESET) { // Unexpected out->err(out, "error: Lost connection to pacemakerd"); } event_done(data, pacemakerd_api); return; case pcmk_ipc_event_reply: break; default: return; } if (data->message_timer_id != 0) { g_source_remove(data->message_timer_id); data->message_timer_id = 0; } if (status != CRM_EX_OK) { out->err(out, "error: Bad reply from pacemakerd: %s", crm_exit_str(status)); event_done(data, pacemakerd_api); return; } if (reply->reply_type != pcmk_pacemakerd_reply_ping) { out->err(out, "error: Unknown reply type %d from pacemakerd", reply->reply_type); event_done(data, pacemakerd_api); return; } // Parse desired information from reply crm_when = crm_time_new(NULL); crm_time_set_timet(crm_when, &reply->data.ping.last_good); pinged_buf = crm_time_as_string(crm_when, crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone); out->message(out, "pacemakerd-health", reply->data.ping.sys_from, (reply->data.ping.status == pcmk_rc_ok)? pcmk_pacemakerd_api_daemon_state_enum2text( reply->data.ping.state):"query failed", (reply->data.ping.status == pcmk_rc_ok)?pinged_buf:""); data->rc = pcmk_rc_ok; crm_time_free(crm_when); free(pinged_buf); event_done(data, pacemakerd_api); } static pcmk_ipc_api_t * ipc_connect(data_t *data, enum pcmk_ipc_server server, pcmk_ipc_callback_t cb) { int rc; pcmk__output_t *out = data->out; pcmk_ipc_api_t *api = NULL; rc = pcmk_new_ipc_api(&api, server); if (api == NULL) { out->err(out, "error: Could not connect to %s: %s", pcmk_ipc_name(api, true), pcmk_rc_str(rc)); data->rc = rc; return NULL; } if (cb != NULL) { pcmk_register_ipc_callback(api, cb, data); } rc = pcmk_connect_ipc(api, pcmk_ipc_dispatch_main); if (rc != pcmk_rc_ok) { out->err(out, "error: Could not connect to %s: %s", pcmk_ipc_name(api, true), pcmk_rc_str(rc)); data->rc = rc; return NULL; } return api; } int pcmk__controller_status(pcmk__output_t *out, char *dest_node, guint message_timeout_ms) { data_t data = { .out = out, .mainloop = NULL, .rc = pcmk_rc_ok, .message_timer_id = 0, .message_timeout_ms = message_timeout_ms }; pcmk_ipc_api_t *controld_api = ipc_connect(&data, pcmk_ipc_controld, controller_status_event_cb); if (controld_api != NULL) { int rc = pcmk_controld_api_ping(controld_api, dest_node); if (rc != pcmk_rc_ok) { out->err(out, "error: Command failed: %s", pcmk_rc_str(rc)); data.rc = rc; } start_main_loop(&data); pcmk_free_ipc_api(controld_api); } return data.rc; } int pcmk_controller_status(xmlNodePtr *xml, char *dest_node, unsigned int message_timeout_ms) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; - rc = pcmk__out_prologue(&out, xml); + rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } pcmk__register_lib_messages(out); rc = pcmk__controller_status(out, dest_node, (guint) message_timeout_ms); pcmk__out_epilogue(out, xml, rc); return rc; } int pcmk__designated_controller(pcmk__output_t *out, guint message_timeout_ms) { data_t data = { .out = out, .mainloop = NULL, .rc = pcmk_rc_ok, .message_timer_id = 0, .message_timeout_ms = message_timeout_ms }; pcmk_ipc_api_t *controld_api = ipc_connect(&data, pcmk_ipc_controld, designated_controller_event_cb); if (controld_api != NULL) { int rc = pcmk_controld_api_ping(controld_api, NULL); if (rc != pcmk_rc_ok) { out->err(out, "error: Command failed: %s", pcmk_rc_str(rc)); data.rc = rc; } start_main_loop(&data); pcmk_free_ipc_api(controld_api); } return data.rc; } int pcmk_designated_controller(xmlNodePtr *xml, unsigned int message_timeout_ms) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; - rc = pcmk__out_prologue(&out, xml); + rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } pcmk__register_lib_messages(out); rc = pcmk__designated_controller(out, (guint) message_timeout_ms); pcmk__out_epilogue(out, xml, rc); return rc; } int pcmk__pacemakerd_status(pcmk__output_t *out, char *ipc_name, guint message_timeout_ms) { data_t data = { .out = out, .mainloop = NULL, .rc = pcmk_rc_ok, .message_timer_id = 0, .message_timeout_ms = message_timeout_ms }; pcmk_ipc_api_t *pacemakerd_api = ipc_connect(&data, pcmk_ipc_pacemakerd, pacemakerd_event_cb); if (pacemakerd_api != NULL) { int rc = pcmk_pacemakerd_api_ping(pacemakerd_api, ipc_name); if (rc != pcmk_rc_ok) { out->err(out, "error: Command failed: %s", pcmk_rc_str(rc)); data.rc = rc; } start_main_loop(&data); pcmk_free_ipc_api(pacemakerd_api); } return data.rc; } int pcmk_pacemakerd_status(xmlNodePtr *xml, char *ipc_name, unsigned int message_timeout_ms) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; - rc = pcmk__out_prologue(&out, xml); + 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, (guint) message_timeout_ms); pcmk__out_epilogue(out, xml, rc); return rc; } /* user data for looping through remote node xpath searches */ struct node_data { pcmk__output_t *out; int found; const char *field; /* XML attribute to check for node name */ const char *type; gboolean BASH_EXPORT; }; static void remote_node_print_helper(xmlNode *result, void *user_data) { struct node_data *data = user_data; pcmk__output_t *out = data->out; const char *name = crm_element_value(result, XML_ATTR_UNAME); const char *id = crm_element_value(result, data->field); // node name and node id are the same for remote/guest nodes out->message(out, "crmadmin-node", data->type, name ? name : id, id, data->BASH_EXPORT); data->found++; } // \return Standard Pacemaker return code int pcmk__list_nodes(pcmk__output_t *out, char *node_types, gboolean BASH_EXPORT) { xmlNode *xml_node = NULL; int rc; rc = cib__signon_query(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__strcmp(node_types, ",|^remote", pcmk__str_regex)) { data.field = "id"; data.type = "remote"; crm_foreach_xpath_result(xml_node, PCMK__XP_REMOTE_NODE_CONFIG, remote_node_print_helper, &data); } out->end_list(out); if (data.found == 0) { out->info(out, "No nodes configured"); } free_xml(xml_node); } return rc; } int pcmk_list_nodes(xmlNodePtr *xml, char *node_types) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; - rc = pcmk__out_prologue(&out, xml); + 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__out_epilogue(out, xml, rc); return rc; } diff --git a/lib/pacemaker/pcmk_fence.c b/lib/pacemaker/pcmk_fence.c index 35f2e45aec..fededaa6a9 100644 --- a/lib/pacemaker/pcmk_fence.c +++ b/lib/pacemaker/pcmk_fence.c @@ -1,604 +1,604 @@ /* * Copyright 2009-2022 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, char *target, int fence_level, stonith_key_value_t *devices, bool added) { char *node = NULL; char *pattern = NULL; 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); 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; } st->cmds->register_callback(st, call_id, async_fence_data.timeout/1000, 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, 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); } rc = st->cmds->history(st, opts, pcmk__str_eq(target, "*", pcmk__str_none)? NULL : 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, stonith__later_succeeded(hp, history), (uint32_t) pcmk_show_failed_detail); out->increment_list(out); } if (latest) { if (out->is_quiet(out)) { pcmk__formatted_printf(out, "%lld\n", (long long) latest->completed); } else if (!verbose) { // already printed if verbose out->message(out, "stonith-event", latest, 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, 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__out_prologue(&out, xml); + 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__out_epilogue(out, xml, rc); 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); /* list_agents returns 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 *dIter = devices; dIter; dIter = dIter->next) { out->list_item(out, "device", "%s", dIter->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__out_prologue(&out, xml); + 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__out_epilogue(out, xml, rc); 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__out_prologue(&out, xml); + 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__out_epilogue(out, xml, rc); 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__out_prologue(&out, xml); + 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__out_epilogue(out, xml, rc); return rc; } #endif int pcmk__fence_metadata(pcmk__output_t *out, stonith_t *st, 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, char *agent, unsigned int timeout) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; - rc = pcmk__out_prologue(&out, xml); + 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__out_epilogue(out, xml, rc); return rc; } #endif int pcmk__fence_registered(pcmk__output_t *out, stonith_t *st, 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 *dIter = devices; dIter; dIter = dIter->next) { out->list_item(out, "device", "%s", dIter->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, char *target, unsigned int timeout) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; - rc = pcmk__out_prologue(&out, xml); + 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__out_epilogue(out, xml, rc); return rc; } #endif int pcmk__fence_register_level(stonith_t *st, char *target, int fence_level, 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, char *target, int fence_level, stonith_key_value_t *devices) { return pcmk__fence_register_level(st, target, fence_level, devices); } #endif int pcmk__fence_unregister_level(stonith_t *st, 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, 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, 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, stonith_key_value_t *params, unsigned int timeout) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; - rc = pcmk__out_prologue(&out, xml); + 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__out_epilogue(out, xml, rc); 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) { 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 197edf8e93..f5f8e8e0cc 100644 --- a/lib/pacemaker/pcmk_resource.c +++ b/lib/pacemaker/pcmk_resource.c @@ -1,140 +1,140 @@ /* - * Copyright 2021 the Pacemaker project contributors + * Copyright 2021-2022 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(pe_resource_t *rsc, pe_node_t *node, pe_working_set_t *data_set) { char *xpath = NULL; xmlNode *history = NULL; xmlNode *best = NULL; // Find node's resource history xpath = crm_strdup_printf(XPATH_OP_HISTORY, node->details->uname, rsc->id); history = get_xpath_object(xpath, data_set->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; crm_element_value_ms(lrm_rsc_op, XML_LRM_ATTR_INTERVAL, &interval_ms); if (pcmk__ends_with(ID(lrm_rsc_op), "_last_failure_0") || (interval_ms != 0)) { // Only use last failure or recurring op if nothing else available if (best == NULL) { best = lrm_rsc_op; } continue; } best = lrm_rsc_op; if (digest != NULL) { // Any non-recurring action with a restart digest is sufficient break; } } return best; } /*! * \internal * \brief Calculate and output resource operation digests * * \param[in] out Output object * \param[in] rsc Resource to calculate digests for * \param[in] node Node whose operation history should be used * \param[in] overrides Hash table of configuration parameters to override * \param[in] data_set Cluster working set (with status) * * \return Standard Pacemaker return code */ int pcmk__resource_digests(pcmk__output_t *out, pe_resource_t *rsc, pe_node_t *node, GHashTable *overrides, pe_working_set_t *data_set) { 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) || (data_set == NULL)) { return EINVAL; } if (rsc->variant != pe_native) { // Only primitives get operation digests return EOPNOTSUPP; } // Find XML of operation history to use xml_op = best_op(rsc, node, data_set); // 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 = RSC_START; interval_ms = 0; } // Calculate and show digests digests = pe__calculate_digests(rsc, task, &interval_ms, node, xml_op, overrides, true, data_set); rc = out->message(out, "digests", rsc, node, task, interval_ms, digests); pe__free_digests(digests); return rc; } int pcmk_resource_digests(xmlNodePtr *xml, pe_resource_t *rsc, pe_node_t *node, GHashTable *overrides, pe_working_set_t *data_set) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; - rc = pcmk__out_prologue(&out, xml); + 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, data_set); pcmk__out_epilogue(out, xml, rc); return rc; } diff --git a/lib/pacemaker/pcmk_simulate.c b/lib/pacemaker/pcmk_simulate.c index 81fe1059a4..2c675b2d27 100644 --- a/lib/pacemaker/pcmk_simulate.c +++ b/lib/pacemaker/pcmk_simulate.c @@ -1,981 +1,981 @@ /* * Copyright 2021-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 #include #include #include #include #include #include "libpacemaker_private.h" #define STATUS_PATH_MAX 512 static pcmk__output_t *out = NULL; static cib_t *fake_cib = NULL; static GList *fake_resource_list = NULL; static GList *fake_op_fail_list = NULL; static void set_effective_date(pe_working_set_t *data_set, bool print_original, 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(pe_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, pe_action_pseudo)) { action_host = ""; } if (pcmk__str_eq(action->task, RSC_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, RSC_NOTIFY, RSC_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, CRM_OP_FENCE, pcmk__str_casei)) { 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] data_set Cluster working set * \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(pe_working_set_t *data_set, uint32_t show_opts, uint32_t section_opts, const char *title, bool print_spacer) { pcmk__output_t *out = data_set->priv; GList *all = NULL; crm_exit_t stonith_rc = 0; 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", data_set, 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] data_set Cluster working set (fully scheduled) * \param[in] print_spacer Whether to display a spacer first */ static void print_transition_summary(pe_working_set_t *data_set, bool print_spacer) { pcmk__output_t *out = data_set->priv; PCMK__OUTPUT_SPACER_IF(out, print_spacer); out->begin_list(out, NULL, NULL, "Transition Summary"); pcmk__output_actions(data_set); out->end_list(out); } /*! * \internal * \brief Reset a cluster working set's input, output, date, and flags * * \param[in] data_set Cluster working set * \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 Cluster flags to add (pe_flag_*) */ static void reset(pe_working_set_t *data_set, xmlNodePtr input, pcmk__output_t *out, char *use_date, unsigned int flags) { data_set->input = input; data_set->priv = out; set_effective_date(data_set, true, use_date); if (pcmk_is_set(flags, pcmk_sim_sanitized)) { pe__set_working_set_flags(data_set, pe_flag_sanitized); } if (pcmk_is_set(flags, pcmk_sim_show_scores)) { pe__set_working_set_flags(data_set, pe_flag_show_scores); } if (pcmk_is_set(flags, pcmk_sim_show_utilization)) { pe__set_working_set_flags(data_set, pe_flag_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] data_set Working set for the cluster * \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(pe_working_set_t *data_set, const char *dot_file, bool all_actions, bool verbose) { GList *gIter = NULL; FILE *dot_strm = fopen(dot_file, "w"); if (dot_strm == NULL) { return errno; } fprintf(dot_strm, " digraph \"g\" {\n"); for (gIter = data_set->actions; gIter != NULL; gIter = gIter->next) { pe_action_t *action = (pe_action_t *) gIter->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, pe_action_pseudo)) { font = "orange"; } if (pcmk_is_set(action->flags, pe_action_dumped)) { style = "bold"; color = "green"; } else if ((action->rsc != NULL) && !pcmk_is_set(action->rsc->flags, pe_rsc_managed)) { color = "red"; font = "purple"; if (!all_actions) { goto do_not_write; } } else if (pcmk_is_set(action->flags, pe_action_optional)) { color = "blue"; if (!all_actions) { goto do_not_write; } } else { color = "red"; CRM_LOG_ASSERT(!pcmk_is_set(action->flags, pe_action_runnable)); } pe__set_action_flags(action, pe_action_dumped); fprintf(dot_strm, "\"%s\" [ style=%s color=\"%s\" fontcolor=\"%s\"]\n", action_name, style, color, font); do_not_write: free(action_name); } for (gIter = data_set->actions; gIter != NULL; gIter = gIter->next) { pe_action_t *action = (pe_action_t *) gIter->data; GList *gIter2 = NULL; for (gIter2 = action->actions_before; gIter2 != NULL; gIter2 = gIter2->next) { pe_action_wrapper_t *before = (pe_action_wrapper_t *) gIter2->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 (before->type == pe_order_none) { continue; } else if (pcmk_is_set(before->action->flags, pe_action_dumped) && pcmk_is_set(action->flags, pe_action_dumped) && before->type != pe_order_load) { 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 data_set->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] data_set Working set for the cluster * \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, pe_working_set_t *data_set, char *use_date) { pcmk__output_t *out = data_set->priv; xmlNode *cib_object = NULL; clock_t start = 0; clock_t end; unsigned long long data_set_flags = pe_flag_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(data_set->flags, pe_flag_show_scores)) { data_set_flags |= pe_flag_show_scores; } if (pcmk_is_set(data_set->flags, pe_flag_show_utilization)) { data_set_flags |= pe_flag_show_utilization; } for (int i = 0; i < repeat; ++i) { xmlNode *input = (repeat == 1)? cib_object : copy_xml(cib_object); data_set->input = input; set_effective_date(data_set, false, use_date); pcmk__schedule_actions(input, data_set_flags, data_set); pe_reset_working_set(data_set); } end = clock(); out->message(out, "profile", xml_file, start, end); } void pcmk__profile_dir(const char *dir, long long repeat, pe_working_set_t *data_set, char *use_date) { pcmk__output_t *out = data_set->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, data_set, 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 data_set->priv must have been set to a valid \p pcmk__output_t * object before this function is called. * * \param[in,out] data_set Working set for the cluster * \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(pe_working_set_t *data_set, bool print_original, char *use_date) { pcmk__output_t *out = data_set->priv; time_t original_date = 0; CRM_ASSERT(out != NULL); crm_element_value_epoch(data_set->input, "execution-date", &original_date); if (use_date) { data_set->now = crm_time_new(use_date); out->info(out, "Setting effective cluster time: %s", use_date); crm_time_log(LOG_NOTICE, "Pretending 'now' is", data_set->now, crm_time_log_date | crm_time_log_timeofday); } else if (original_date) { data_set->now = crm_time_new(NULL); crm_time_set_timet(data_set->now, &original_date); if (print_original) { char *when = crm_time_as_string(data_set->now, crm_time_log_date|crm_time_log_timeofday); 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] graph Graph to update with pseudo-action result * \param[in] action Pseudo-action to simulate executing * * \return TRUE */ static gboolean simulate_pseudo_action(crm_graph_t *graph, crm_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); crm__set_graph_action_flags(action, pcmk__graph_action_confirmed); out->message(out, "inject-pseudo-action", node, task); pcmk__update_graph(graph, action); return TRUE; } /*! * \internal * \brief Simulate executing a resource action in a graph * * \param[in] graph Graph to update with resource action result * \param[in] action Resource action to simulate executing * * \return TRUE if action is validly specified, otherwise FALSE */ static gboolean simulate_resource_action(crm_graph_t *graph, crm_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 FALSE; } /* 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 FALSE; } 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, "delete", RSC_METADATA, 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 FALSE; } // 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 (GList *iter = fake_op_fail_list; iter != NULL; iter = iter->next) { char *spec = (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); crm__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); crm__set_graph_action_flags(action, pcmk__graph_action_confirmed); pcmk__update_graph(graph, action); return TRUE; } /*! * \internal * \brief Simulate successfully executing a cluster action * * \param[in] graph Graph to update with action result * \param[in] action Cluster action to simulate * * \return TRUE */ static gboolean simulate_cluster_action(crm_graph_t *graph, crm_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); crm__set_graph_action_flags(action, pcmk__graph_action_confirmed); out->message(out, "inject-cluster-action", node, task, rsc); pcmk__update_graph(graph, action); return TRUE; } /*! * \internal * \brief Simulate successfully executing a fencing action * * \param[in] graph Graph to update with action result * \param[in] action Fencing action to simulate * * \return TRUE */ static gboolean simulate_fencing_action(crm_graph_t *graph, crm_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, "on", pcmk__str_casei)) { int rc = pcmk_ok; char xpath[STATUS_PATH_MAX]; // 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 snprintf(xpath, STATUS_PATH_MAX, "//node_state[@uname='%s']/%s", target, XML_CIB_TAG_LRM); fake_cib->cmds->remove(fake_cib, xpath, NULL, cib_xpath|cib_sync_call|cib_scope_local); snprintf(xpath, STATUS_PATH_MAX, "//node_state[@uname='%s']/%s", target, XML_TAG_TRANSIENT_NODEATTRS); fake_cib->cmds->remove(fake_cib, xpath, NULL, cib_xpath|cib_sync_call|cib_scope_local); free_xml(cib_node); } crm__set_graph_action_flags(action, pcmk__graph_action_confirmed); pcmk__update_graph(graph, action); free(target); return TRUE; } enum transition_status pcmk__simulate_transition(pe_working_set_t *data_set, cib_t *cib, GList *op_fail_list) { crm_graph_t *transition = NULL; enum transition_status graph_rc; crm_graph_functions_t simulation_fns = { simulate_pseudo_action, simulate_resource_action, simulate_cluster_action, simulate_fencing_action, }; out = data_set->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(data_set->graph, crm_system_name); pcmk__log_graph(LOG_DEBUG, transition); fake_resource_list = data_set->resources; do { graph_rc = pcmk__execute_graph(transition); } while (graph_rc == transition_active); fake_resource_list = NULL; if (graph_rc != transition_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(data_set); data_set->input = cib_object; out->end_list(out); } return graph_rc; } int pcmk__simulate(pe_working_set_t *data_set, pcmk__output_t *out, pcmk_injections_t *injections, unsigned int flags, uint32_t section_opts, char *use_date, char *input_file, char *graph_file, 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(&cib, &input); if (rc != pcmk_rc_ok) { goto simulate_done; } reset(data_set, input, out, use_date, flags); cluster_status(data_set); if (!out->is_quiet(out)) { if (pcmk_is_set(data_set->flags, pe_flag_maintenance_mode)) { printed = out->message(out, "maint-mode", data_set->flags); } if (data_set->disabled_resources || data_set->blocked_resources) { PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok); printed = out->info(out, "%d of %d resource instances DISABLED and " "%d BLOCKED from further action due to failure", data_set->disabled_resources, data_set->ninstances, data_set->blocked_resources); } /* Most formatted output headers use caps for each word, but this one * only has the first word capitalized for compatibility with pcs. */ print_cluster_status(data_set, pcmk_is_set(flags, pcmk_sim_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(data_set, 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(data_set); reset(data_set, input, out, use_date, flags); cluster_status(data_set); } 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 data_set_flags = pe_flag_no_compat; if (pcmk_is_set(data_set->flags, pe_flag_show_scores)) { data_set_flags |= pe_flag_show_scores; } if (pcmk_is_set(data_set->flags, pe_flag_show_utilization)) { data_set_flags |= pe_flag_show_utilization; } if (pcmk_all_flags_set(data_set->flags, pe_flag_show_scores|pe_flag_show_utilization)) { PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok); out->begin_list(out, NULL, NULL, "Allocation Scores and Utilization Information"); printed = pcmk_rc_ok; } else if (pcmk_is_set(data_set->flags, pe_flag_show_scores)) { PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok); out->begin_list(out, NULL, NULL, "Allocation Scores"); printed = pcmk_rc_ok; } else if (pcmk_is_set(data_set->flags, pe_flag_show_utilization)) { PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok); out->begin_list(out, NULL, NULL, "Utilization Information"); printed = pcmk_rc_ok; } else { logger_out = pcmk__new_logger(); if (logger_out == NULL) { rc = pcmk_rc_error; goto simulate_done; } data_set->priv = logger_out; } pcmk__schedule_actions(input, data_set_flags, data_set); if (logger_out == NULL) { out->end_list(out); } else { logger_out->finish(logger_out, CRM_EX_OK, true, NULL); pcmk__output_free(logger_out); data_set->priv = out; } input = NULL; /* Don't try and free it twice */ if (graph_file != NULL) { rc = write_xml_file(data_set->graph, graph_file, FALSE); if (rc < 0) { rc = pcmk_rc_graph_error; goto simulate_done; } } if (dot_file != NULL) { rc = write_sim_dotfile(data_set, 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(data_set, 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(data_set, cib, injections->op_fail) != transition_complete) { rc = pcmk_rc_invalid_transition; } if (out->is_quiet(out)) { goto simulate_done; } set_effective_date(data_set, true, use_date); if (pcmk_is_set(flags, pcmk_sim_show_scores)) { pe__set_working_set_flags(data_set, pe_flag_show_scores); } if (pcmk_is_set(flags, pcmk_sim_show_utilization)) { pe__set_working_set_flags(data_set, pe_flag_show_utilization); } cluster_status(data_set); print_cluster_status(data_set, 0, section_opts, "Revised Cluster Status", true); simulate_done: cib__clean_up_connection(&cib); return rc; } int pcmk_simulate(xmlNodePtr *xml, pe_working_set_t *data_set, pcmk_injections_t *injections, unsigned int flags, unsigned int section_opts, char *use_date, char *input_file, char *graph_file, char *dot_file) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; - rc = pcmk__out_prologue(&out, xml); + 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(data_set, out, injections, flags, section_opts, use_date, input_file, graph_file, dot_file); pcmk__out_epilogue(out, xml, rc); return rc; } diff --git a/lib/pacemaker/pcmk_status.c b/lib/pacemaker/pcmk_status.c index 12136eaf67..22a1e90804 100644 --- a/lib/pacemaker/pcmk_status.c +++ b/lib/pacemaker/pcmk_status.c @@ -1,369 +1,369 @@ /* * Copyright 2004-2022 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 int cib_connect(pcmk__output_t *out, cib_t *cib, xmlNode **current_cib) { int rc = pcmk_rc_ok; CRM_CHECK(cib != NULL, return EINVAL); if (cib->state == cib_connected_query || cib->state == cib_connected_command) { return rc; } crm_trace("Connecting to the CIB"); rc = cib->cmds->signon(cib, crm_system_name, cib_query); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { out->err(out, "Could not connect to the CIB: %s", pcmk_rc_str(rc)); return rc; } rc = cib->cmds->query(cib, NULL, current_cib, cib_scope_local | cib_sync_call); rc = pcmk_legacy2rc(rc); return rc; } 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; } } 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) { pcmk_pacemakerd_api_reply_t *reply = event_data; enum pcmk_pacemakerd_state *state = (enum pcmk_pacemakerd_state *) user_data; /* we are just interested in the latest reply */ *state = pcmk_pacemakerd_state_invalid; if (event_type != pcmk_ipc_event_reply || status != CRM_EX_OK) { return; } if (reply->reply_type == pcmk_pacemakerd_reply_ping && reply->data.ping.last_good != (time_t) 0 && reply->data.ping.status == pcmk_rc_ok) { *state = reply->data.ping.state; } } static int pacemakerd_status(pcmk__output_t *out) { int rc = pcmk_rc_ok; pcmk_ipc_api_t *pacemakerd_api = NULL; enum pcmk_pacemakerd_state state = pcmk_pacemakerd_state_invalid; rc = pcmk_new_ipc_api(&pacemakerd_api, pcmk_ipc_pacemakerd); if (pacemakerd_api == NULL) { out->err(out, "Could not connect to pacemakerd: %s", pcmk_rc_str(rc)); return rc; } pcmk_register_ipc_callback(pacemakerd_api, pacemakerd_event_cb, (void *) &state); rc = pcmk_connect_ipc(pacemakerd_api, pcmk_ipc_dispatch_sync); if (rc == EREMOTEIO) { return pcmk_rc_ok; } else if (rc != pcmk_rc_ok) { out->err(out, "Could not connect to pacemakerd: %s", pcmk_rc_str(rc)); pcmk_free_ipc_api(pacemakerd_api); return rc; } rc = pcmk_pacemakerd_api_ping(pacemakerd_api, crm_system_name); if (rc != pcmk_rc_ok) { /* Got some error from pcmk_pacemakerd_api_ping, so return it. */ } else if (state == pcmk_pacemakerd_state_running) { rc = pcmk_rc_ok; } else if (state == pcmk_pacemakerd_state_shutting_down) { rc = ENOTCONN; } else { rc = EAGAIN; } pcmk_free_ipc_api(pacemakerd_api); return rc; } int pcmk__output_cluster_status(pcmk__output_t *out, stonith_t *st, cib_t *cib, xmlNode *current_cib, enum pcmk__fence_history fence_history, uint32_t show, uint32_t show_opts, char *only_node, char *only_rsc, char *neg_location_prefix, bool simple_output) { xmlNode *cib_copy = copy_xml(current_cib); stonith_history_t *stonith_history = NULL; int history_rc = 0; pe_working_set_t *data_set = 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); 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(st, &stonith_history, fence_history); } data_set = pe_new_working_set(); CRM_ASSERT(data_set != NULL); pe__set_working_set_flags(data_set, pe_flag_no_compat); data_set->input = cib_copy; data_set->priv = out; cluster_status(data_set); /* 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(data_set); } unames = pe__build_node_name_list(data_set, only_node); resources = pe__build_rsc_list(data_set, only_rsc); /* Always print DC if NULL. */ if (data_set->dc_node == NULL) { show |= pcmk_section_dc; } if (simple_output) { rc = pcmk__output_simple_status(out, data_set); } else { out->message(out, "cluster-status", data_set, 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_reset_working_set(data_set); 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__out_prologue(&out, xml); + 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); pcmk__out_epilogue(out, xml, rc); cib_delete(cib); return rc; } int pcmk__status(pcmk__output_t *out, cib_t *cib, enum pcmk__fence_history fence_history, uint32_t show, uint32_t show_opts, char *only_node, char *only_rsc, char *neg_location_prefix, bool simple_output) { xmlNode *current_cib = NULL; int rc = pcmk_rc_ok; stonith_t *st = NULL; if (cib == NULL) { return ENOTCONN; } if (cib->variant == cib_native) { if (cib->state == cib_connected_query || cib->state == cib_connected_command) { rc = pcmk_rc_ok; } else { rc = pacemakerd_status(out); } } if (rc != pcmk_rc_ok) { return rc; } if (fence_history != pcmk__fence_history_none && cib->variant == cib_native) { st = fencing_connect(); if (st == NULL) { return ENOTCONN; } } rc = cib_connect(out, cib, ¤t_cib); if (rc != pcmk_rc_ok) { goto done; } rc = pcmk__output_cluster_status(out, st, cib, current_cib, fence_history, show, show_opts, only_node, only_rsc, neg_location_prefix, simple_output); done: if (st != NULL) { if (st->state != stonith_disconnected) { st->cmds->remove_notification(st, NULL); st->cmds->disconnect(st); } stonith_api_delete(st); } return rc; } /* This is an internal-only function that is planned to be deprecated and removed. * It should only ever be called from crm_mon. */ int pcmk__output_simple_status(pcmk__output_t *out, pe_working_set_t *data_set) { int nodes_online = 0; int nodes_standby = 0; int nodes_maintenance = 0; char *offline_nodes = NULL; size_t offline_nodes_len = 0; bool no_dc = false; bool offline = false; bool has_warnings = false; if (data_set->dc_node == NULL) { has_warnings = true; no_dc = true; } for (GList *iter = data_set->nodes; iter != NULL; iter = iter->next) { pe_node_t *node = (pe_node_t *) iter->data; if (node->details->standby && node->details->online) { nodes_standby++; } else if (node->details->maintenance && node->details->online) { nodes_maintenance++; } else if (node->details->online) { nodes_online++; } else { char *s = crm_strdup_printf("offline node: %s", node->details->uname); /* coverity[leaked_storage] False positive */ pcmk__add_word(&offline_nodes, &offline_nodes_len, s); free(s); has_warnings = true; offline = true; } } if (has_warnings) { out->info(out, "CLUSTER WARN: %s%s%s", no_dc ? "No DC" : "", no_dc && offline ? ", " : "", (offline? offline_nodes : "")); free(offline_nodes); } 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_maintenance > 0) { nodes_maint_s = crm_strdup_printf(", %d maintenance node%s", nodes_maintenance, pcmk__plural_s(nodes_maintenance)); } 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 : "", data_set->ninstances, pcmk__plural_s(data_set->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 */ }