Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/daemons/controld/pacemaker-controld.c b/daemons/controld/pacemaker-controld.c
index 686f35d8b1..c3309cdfab 100644
--- a/daemons/controld/pacemaker-controld.c
+++ b/daemons/controld/pacemaker-controld.c
@@ -1,186 +1,185 @@
/*
* 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 <crm_internal.h>
#include <sys/param.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <crm/crm.h>
#include <crm/common/ipc.h>
#include <crm/common/xml.h>
#include <pacemaker-controld.h>
#define OPTARGS "hV"
void usage(const char *cmd, int exit_status);
_Noreturn void crmd_init(void);
void crmd_hamsg_callback(const xmlNode * msg, void *private_data);
extern void init_dotfile(void);
GMainLoop *crmd_mainloop = NULL;
pcmk__output_t *logger_out = NULL;
static pcmk__cli_option_t long_options[] = {
// long option, argument type, storage, short option, description, flags
{
"help", no_argument, NULL, '?',
"\tThis text", pcmk__option_default
},
{
"verbose", no_argument, NULL, 'V',
"\tIncrease debug output", pcmk__option_default
},
{ 0, 0, 0, 0 }
};
int
main(int argc, char **argv)
{
int flag;
int index = 0;
int argerr = 0;
crm_ipc_t *old_instance = NULL;
crmd_mainloop = g_main_loop_new(NULL, FALSE);
crm_log_preinit(NULL, argc, argv);
pcmk__set_cli_options(NULL, "[options]", long_options,
"daemon for coordinating a Pacemaker cluster's "
"response to events");
while (1) {
flag = pcmk__next_cli_option(argc, argv, &index, NULL);
if (flag == -1)
break;
switch (flag) {
case 'V':
crm_bump_log_level(argc, argv);
break;
case 'h': /* Help message */
pcmk__cli_help(flag, CRM_EX_OK);
break;
default:
++argerr;
break;
}
}
if (argc - optind == 1 && pcmk__str_eq("metadata", argv[optind], pcmk__str_casei)) {
crmd_metadata();
return CRM_EX_OK;
} else if (argc - optind == 1 && pcmk__str_eq("version", argv[optind], pcmk__str_casei)) {
fprintf(stdout, "CRM Version: %s (%s)\n", PACEMAKER_VERSION, BUILD_VERSION);
return CRM_EX_OK;
}
crm_log_init(NULL, LOG_INFO, TRUE, FALSE, argc, argv, FALSE);
if (optind > argc) {
++argerr;
}
if (argerr) {
pcmk__cli_help('?', CRM_EX_USAGE);
}
crm_notice("Starting Pacemaker controller");
old_instance = crm_ipc_new(CRM_SYSTEM_CRMD, 0);
if (crm_ipc_connect(old_instance)) {
/* IPC end-point already up */
crm_ipc_close(old_instance);
crm_ipc_destroy(old_instance);
crm_err("pacemaker-controld is already active, aborting startup");
crm_exit(CRM_EX_OK);
} else {
/* not up or not authentic, we'll proceed either way */
crm_ipc_destroy(old_instance);
old_instance = NULL;
}
if (pcmk__daemon_can_write(PE_STATE_DIR, NULL) == FALSE) {
crm_err("Terminating due to bad permissions on " PE_STATE_DIR);
fprintf(stderr,
"ERROR: Bad permissions on " PE_STATE_DIR " (see logs for details)\n");
fflush(stderr);
return CRM_EX_FATAL;
} else if (pcmk__daemon_can_write(CRM_CONFIG_DIR, NULL) == FALSE) {
crm_err("Terminating due to bad permissions on " CRM_CONFIG_DIR);
fprintf(stderr,
"ERROR: Bad permissions on " CRM_CONFIG_DIR " (see logs for details)\n");
fflush(stderr);
return CRM_EX_FATAL;
}
- logger_out = pcmk__new_logger();
- if (logger_out == NULL) {
+ if (pcmk__log_output_new(&logger_out) != pcmk_rc_ok) {
return CRM_EX_FATAL;
}
pcmk__output_set_log_level(logger_out, LOG_TRACE);
crmd_init();
return 0; // not reachable
}
static void
log_deprecation_warnings(void)
{
// Add deprecations here as needed
}
void
crmd_init(void)
{
crm_exit_t exit_code = CRM_EX_OK;
enum crmd_fsa_state state;
log_deprecation_warnings();
fsa_state = S_STARTING;
fsa_input_register = 0; /* zero out the regester */
init_dotfile();
register_fsa_input(C_STARTUP, I_STARTUP, NULL);
crm_peer_init();
state = s_crmd_fsa(C_STARTUP);
if (state == S_PENDING || state == S_STARTING) {
/* Create the mainloop and run it... */
crm_trace("Starting %s's mainloop", crm_system_name);
g_main_loop_run(crmd_mainloop);
if (pcmk_is_set(fsa_input_register, R_STAYDOWN)) {
crm_info("Inhibiting automated respawn");
exit_code = CRM_EX_FATAL;
}
} else {
crm_err("Startup of %s failed. Current state: %s",
crm_system_name, fsa_state2string(state));
exit_code = CRM_EX_ERROR;
}
crm_info("%s[%lu] exiting with status %d (%s)",
crm_system_name, (unsigned long) getpid(), exit_code,
crm_exit_str(exit_code));
crmd_fast_exit(exit_code);
}
diff --git a/daemons/schedulerd/pacemaker-schedulerd.c b/daemons/schedulerd/pacemaker-schedulerd.c
index a8c6d80047..d18868250c 100644
--- a/daemons/schedulerd/pacemaker-schedulerd.c
+++ b/daemons/schedulerd/pacemaker-schedulerd.c
@@ -1,181 +1,180 @@
/*
* 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 <crm_internal.h>
#include <crm/crm.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <errno.h>
#include <crm/common/cmdline_internal.h>
#include <crm/common/ipc_internal.h>
#include <crm/common/mainloop.h>
#include <crm/pengine/internal.h>
#include <pacemaker-internal.h>
#include "pacemaker-schedulerd.h"
#define SUMMARY "pacemaker-schedulerd - daemon for calculating a Pacemaker cluster's response to events"
struct {
gchar **remainder;
} options;
pcmk__output_t *logger_out = NULL;
pcmk__output_t *out = NULL;
static GMainLoop *mainloop = NULL;
static qb_ipcs_service_t *ipcs = NULL;
static crm_exit_t exit_code = CRM_EX_OK;
pcmk__supported_format_t formats[] = {
PCMK__SUPPORTED_FORMAT_NONE,
PCMK__SUPPORTED_FORMAT_TEXT,
PCMK__SUPPORTED_FORMAT_XML,
{ NULL, NULL, NULL }
};
void pengine_shutdown(int nsig);
static GOptionContext *
build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
GOptionContext *context = NULL;
GOptionEntry extra_prog_entries[] = {
{ G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING_ARRAY, &options.remainder,
NULL,
NULL },
{ NULL }
};
context = pcmk__build_arg_context(args, "text (default), xml", group, NULL);
pcmk__add_main_args(context, extra_prog_entries);
return context;
}
int
main(int argc, char **argv)
{
GError *error = NULL;
int rc = pcmk_rc_ok;
GOptionGroup *output_group = NULL;
pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
gchar **processed_args = pcmk__cmdline_preproc(argv, NULL);
GOptionContext *context = build_arg_context(args, &output_group);
crm_log_preinit(NULL, argc, argv);
mainloop_add_signal(SIGTERM, pengine_shutdown);
pcmk__register_formats(NULL, formats);
if (!g_option_context_parse_strv(context, &processed_args, &error)) {
exit_code = CRM_EX_USAGE;
goto done;
}
rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
if ((rc != pcmk_rc_ok) || (out == NULL)) {
exit_code = CRM_EX_FATAL;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Error creating output format %s: %s",
args->output_ty, pcmk_rc_str(rc));
goto done;
}
pe__register_messages(out);
pcmk__register_lib_messages(out);
if (options.remainder) {
if (g_strv_length(options.remainder) == 1 &&
pcmk__str_eq("metadata", options.remainder[0], pcmk__str_casei)) {
pe_metadata(out);
goto done;
} else {
exit_code = CRM_EX_USAGE;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"Unsupported extra command line parameters");
goto done;
}
}
if (args->version) {
out->version(out, false);
goto done;
}
pcmk__cli_init_logging("pacemaker-schedulerd", args->verbosity);
crm_log_init(NULL, LOG_INFO, TRUE, FALSE, argc, argv, FALSE);
crm_notice("Starting Pacemaker scheduler");
if (pcmk__daemon_can_write(PE_STATE_DIR, NULL) == FALSE) {
crm_err("Terminating due to bad permissions on " PE_STATE_DIR);
exit_code = CRM_EX_FATAL;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"ERROR: Bad permissions on %s (see logs for details)", PE_STATE_DIR);
goto done;
}
ipcs = pcmk__serve_schedulerd_ipc(&ipc_callbacks);
if (ipcs == NULL) {
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"Failed to create pacemaker-schedulerd server: exiting and inhibiting respawn");
exit_code = CRM_EX_FATAL;
goto done;
}
- logger_out = pcmk__new_logger();
- if (logger_out == NULL) {
+ if (pcmk__log_output_new(&logger_out) != pcmk_rc_ok) {
exit_code = CRM_EX_FATAL;
goto done;
}
pe__register_messages(logger_out);
pcmk__register_lib_messages(logger_out);
pcmk__output_set_log_level(logger_out, LOG_TRACE);
/* Create the mainloop and run it... */
mainloop = g_main_loop_new(NULL, FALSE);
crm_notice("Pacemaker scheduler successfully started and accepting connections");
g_main_loop_run(mainloop);
done:
g_strfreev(options.remainder);
g_strfreev(processed_args);
pcmk__free_arg_context(context);
pcmk__output_and_clear_error(error, out);
pengine_shutdown(0);
}
void
pengine_shutdown(int nsig)
{
if (ipcs != NULL) {
crm_trace("Closing IPC server");
mainloop_del_ipc_server(ipcs);
ipcs = NULL;
}
if (logger_out != NULL) {
logger_out->finish(logger_out, exit_code, true, NULL);
pcmk__output_free(logger_out);
logger_out = NULL;
}
if (out != NULL) {
out->finish(out, exit_code, true, NULL);
pcmk__output_free(out);
out = NULL;
}
pcmk__unregister_formats();
crm_exit(exit_code);
}
diff --git a/include/crm/common/output_internal.h b/include/crm/common/output_internal.h
index 907d8acbc6..ca16227fe6 100644
--- a/include/crm/common/output_internal.h
+++ b/include/crm/common/output_internal.h
@@ -1,886 +1,886 @@
/*
* 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 <stdbool.h>
# include <stdio.h>
# include <libxml/tree.h>
# include <libxml/HTMLtree.h>
# include <glib.h>
# include <crm/common/results.h>
#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 <head> 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 "<meta http-equiv='refresh' content='19'>":
*
* \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__xml_output_new(pcmk__output_t **out, xmlNodePtr *xml);
void pcmk__xml_output_finish(pcmk__output_t *out, xmlNodePtr *xml);
-pcmk__output_t *pcmk__new_logger(void);
+int pcmk__log_output_new(pcmk__output_t **out);
#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 470d3499d9..1d6342746b 100644
--- a/lib/common/output.c
+++ b/lib/common/output.c
@@ -1,240 +1,238 @@
/*
* 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 <crm_internal.h>
#include <crm/common/util.h>
#include <crm/common/xml.h>
#include <libxml/tree.h>
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
*
* Create an output object that supports only the XML format, and free
* existing XML if supplied (particularly useful for libpacemaker public API
* functions that want to free any previous result supplied by the caller).
*
* \param[out] out Where to put newly created output object
* \param[in,out] xml If non-NULL, this will be freed
*
* \return Standard Pacemaker return code
*/
int
pcmk__xml_output_new(pcmk__output_t **out, xmlNodePtr *xml) {
pcmk__supported_format_t xml_format[] = {
PCMK__SUPPORTED_FORMAT_XML,
{ NULL, NULL, NULL }
};
if (*xml != NULL) {
xmlFreeNode(*xml);
*xml = NULL;
}
pcmk__register_formats(NULL, xml_format);
return pcmk__output_new(out, "xml", NULL, NULL);
}
/*!
* \internal
* \brief Finish and free an XML-only output object
*
* \param[in] out Output object to free
* \param[out] xml Where to store XML output
*/
void
pcmk__xml_output_finish(pcmk__output_t *out, xmlNodePtr *xml) {
out->finish(out, 0, FALSE, (void **) xml);
pcmk__output_free(out);
}
/*!
* \internal
* \brief Create a new output object using the "log" format
*
- * Create a new output object using the "log" format, and register the
- * libpe_status and libpacemaker messages.
+ * \param[out] out Where to store newly allocated output object
*
- * \return Newly created output object, or NULL on error
+ * \return Standard Pacemaker return code
*/
-pcmk__output_t *
-pcmk__new_logger(void)
+int
+pcmk__log_output_new(pcmk__output_t **out)
{
int rc = pcmk_rc_ok;
- pcmk__output_t *out = NULL;
const char* argv[] = { "", NULL };
pcmk__supported_format_t formats[] = {
PCMK__SUPPORTED_FORMAT_LOG,
{ NULL, NULL, NULL }
};
pcmk__register_formats(NULL, formats);
- rc = pcmk__output_new(&out, "log", NULL, (char**)argv);
- if ((rc != pcmk_rc_ok) || (out == NULL)) {
+ rc = pcmk__output_new(out, "log", NULL, (char **) argv);
+ if ((rc != pcmk_rc_ok) || (*out == NULL)) {
crm_err("Can't log certain messages due to internal error: %s",
pcmk_rc_str(rc));
- return NULL;
+ return rc;
}
- return out;
+ return pcmk_rc_ok;
}
diff --git a/lib/pacemaker/pcmk_sched_allocate.c b/lib/pacemaker/pcmk_sched_allocate.c
index 4c76001d10..a8d2c01554 100644
--- a/lib/pacemaker/pcmk_sched_allocate.c
+++ b/lib/pacemaker/pcmk_sched_allocate.c
@@ -1,797 +1,797 @@
/*
* 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 <crm_internal.h>
#include <crm/crm.h>
#include <crm/cib.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h>
#include <glib.h>
#include <crm/pengine/status.h>
#include <pacemaker-internal.h>
#include "libpacemaker_private.h"
CRM_TRACE_INIT_DATA(pacemaker);
/*!
* \internal
* \brief Do deferred action checks after allocation
*
* When unpacking the resource history, the scheduler checks for resource
* configurations that have changed since an action was run. However, at that
* time, bundles using the REMOTE_CONTAINER_HACK don't have their final
* parameter information, so instead they add a deferred check to a list. This
* function processes one entry in that list.
*
* \param[in] rsc Resource that action history is for
* \param[in] node Node that action history is for
* \param[in] rsc_op Action history entry
* \param[in] check Type of deferred check to do
* \param[in] data_set Working set for cluster
*/
static void
check_params(pe_resource_t *rsc, pe_node_t *node, xmlNode *rsc_op,
enum pe_check_parameters check, pe_working_set_t *data_set)
{
const char *reason = NULL;
op_digest_cache_t *digest_data = NULL;
switch (check) {
case pe_check_active:
if (pcmk__check_action_config(rsc, node, rsc_op)
&& pe_get_failcount(node, rsc, NULL, pe_fc_effective, NULL,
data_set)) {
reason = "action definition changed";
}
break;
case pe_check_last_failure:
digest_data = rsc_action_digest_cmp(rsc, rsc_op, node, data_set);
switch (digest_data->rc) {
case RSC_DIGEST_UNKNOWN:
crm_trace("Resource %s history entry %s on %s has "
"no digest to compare",
rsc->id, ID(rsc_op), node->details->id);
break;
case RSC_DIGEST_MATCH:
break;
default:
reason = "resource parameters have changed";
break;
}
break;
}
if (reason != NULL) {
pe__clear_failcount(rsc, node, reason, data_set);
}
}
/*!
* \internal
* \brief Check whether a resource has failcount clearing scheduled on a node
*
* \param[in] node Node to check
* \param[in] rsc Resource to check
*
* \return true if \p rsc has failcount clearing scheduled on \p node,
* otherwise false
*/
static bool
failcount_clear_action_exists(pe_node_t *node, pe_resource_t *rsc)
{
GList *list = pe__resource_actions(rsc, node, CRM_OP_CLEAR_FAILCOUNT, TRUE);
if (list != NULL) {
g_list_free(list);
return true;
}
return false;
}
/*!
* \internal
* \brief Ban a resource from a node if it reached its failure threshold there
*
* \param[in] rsc Resource to check failure threshold for
* \param[in] node Node to check \p rsc on
*/
static void
check_failure_threshold(pe_resource_t *rsc, pe_node_t *node)
{
// If this is a collective resource, apply recursively to children instead
if (rsc->children != NULL) {
g_list_foreach(rsc->children, (GFunc) check_failure_threshold,
node);
return;
} else if (failcount_clear_action_exists(node, rsc)) {
/* Don't force the resource away from this node due to a failcount
* that's going to be cleared.
*
* @TODO Failcount clearing can be scheduled in
* pcmk__handle_rsc_config_changes() via process_rsc_history(), or in
* schedule_resource_actions() via check_params(). This runs well before
* then, so it cannot detect those, meaning we might check the migration
* threshold when we shouldn't. Worst case, we stop or move the
* resource, then move it back in the next transition.
*/
return;
} else {
pe_resource_t *failed = NULL;
if (pcmk__threshold_reached(rsc, node, &failed)) {
resource_location(failed, node, -INFINITY, "__fail_limit__",
rsc->cluster);
}
}
}
/*!
* \internal
* \brief If resource has exclusive discovery, ban node if not allowed
*
* Location constraints have a resource-discovery option that allows users to
* specify where probes are done for the affected resource. If this is set to
* exclusive, probes will only be done on nodes listed in exclusive constraints.
* This function bans the resource from the node if the node is not listed.
*
* \param[in] rsc Resource to check
* \param[in] node Node to check \p rsc on
*/
static void
apply_exclusive_discovery(pe_resource_t *rsc, pe_node_t *node)
{
if (rsc->exclusive_discover || uber_parent(rsc)->exclusive_discover) {
pe_node_t *match = NULL;
// If this is a collective resource, apply recursively to children
g_list_foreach(rsc->children, (GFunc) apply_exclusive_discovery, node);
match = g_hash_table_lookup(rsc->allowed_nodes, node->details->id);
if ((match != NULL)
&& (match->rsc_discover_mode != pe_discover_exclusive)) {
match->weight = -INFINITY;
}
}
}
/*!
* \internal
* \brief Apply stickiness to a resource if appropriate
*
* \param[in] rsc Resource to check for stickiness
* \param[in] data_set Cluster working set
*/
static void
apply_stickiness(pe_resource_t *rsc, pe_working_set_t *data_set)
{
pe_node_t *node = NULL;
// If this is a collective resource, apply recursively to children instead
if (rsc->children != NULL) {
g_list_foreach(rsc->children, (GFunc) apply_stickiness, data_set);
return;
}
/* A resource is sticky if it is managed, has stickiness configured, and is
* active on a single node.
*/
if (!pcmk_is_set(rsc->flags, pe_rsc_managed)
|| (rsc->stickiness < 1) || !pcmk__list_of_1(rsc->running_on)) {
return;
}
node = rsc->running_on->data;
/* In a symmetric cluster, stickiness can always be used. In an
* asymmetric cluster, we have to check whether the resource is still
* allowed on the node, so we don't keep the resource somewhere it is no
* longer explicitly enabled.
*/
if (!pcmk_is_set(rsc->cluster->flags, pe_flag_symmetric_cluster)
&& (pe_hash_table_lookup(rsc->allowed_nodes,
node->details->id) == NULL)) {
pe_rsc_debug(rsc,
"Ignoring %s stickiness because the cluster is "
"asymmetric and node %s is not explicitly allowed",
rsc->id, node->details->uname);
return;
}
pe_rsc_debug(rsc, "Resource %s has %d stickiness on node %s",
rsc->id, rsc->stickiness, node->details->uname);
resource_location(rsc, node, rsc->stickiness, "stickiness",
rsc->cluster);
}
/*!
* \internal
* \brief Apply shutdown locks for all resources as appropriate
*
* \param[in] data_set Cluster working set
*/
static void
apply_shutdown_locks(pe_working_set_t *data_set)
{
if (!pcmk_is_set(data_set->flags, pe_flag_shutdown_lock)) {
return;
}
for (GList *iter = data_set->resources; iter != NULL; iter = iter->next) {
pe_resource_t *rsc = (pe_resource_t *) iter->data;
rsc->cmds->shutdown_lock(rsc);
}
}
/*!
* \internal
* \brief Calculate the number of available nodes in the cluster
*
* \param[in] data_set Cluster working set
*/
static void
count_available_nodes(pe_working_set_t *data_set)
{
if (pcmk_is_set(data_set->flags, pe_flag_no_compat)) {
return;
}
// @COMPAT for API backward compatibility only (cluster does not use value)
for (GList *iter = data_set->nodes; iter != NULL; iter = iter->next) {
pe_node_t *node = (pe_node_t *) iter->data;
if ((node != NULL) && (node->weight >= 0) && node->details->online
&& (node->details->type != node_ping)) {
data_set->max_valid_nodes++;
}
}
crm_trace("Online node count: %d", data_set->max_valid_nodes);
}
/*
* \internal
* \brief Apply node-specific scheduling criteria
*
* After the CIB has been unpacked, process node-specific scheduling criteria
* including shutdown locks, location constraints, resource stickiness,
* migration thresholds, and exclusive resource discovery.
*/
static void
apply_node_criteria(pe_working_set_t *data_set)
{
crm_trace("Applying node-specific scheduling criteria");
apply_shutdown_locks(data_set);
count_available_nodes(data_set);
pcmk__apply_locations(data_set);
g_list_foreach(data_set->resources, (GFunc) apply_stickiness, data_set);
for (GList *node_iter = data_set->nodes; node_iter != NULL;
node_iter = node_iter->next) {
for (GList *rsc_iter = data_set->resources; rsc_iter != NULL;
rsc_iter = rsc_iter->next) {
pe_node_t *node = (pe_node_t *) node_iter->data;
pe_resource_t *rsc = (pe_resource_t *) rsc_iter->data;
check_failure_threshold(rsc, node);
apply_exclusive_discovery(rsc, node);
}
}
}
/*!
* \internal
* \brief Allocate resources to nodes
*
* \param[in] data_set Cluster working set
*/
static void
allocate_resources(pe_working_set_t *data_set)
{
GList *iter = NULL;
crm_trace("Allocating resources to nodes");
if (!pcmk__str_eq(data_set->placement_strategy, "default", pcmk__str_casei)) {
pcmk__sort_resources(data_set);
}
pcmk__show_node_capacities("Original", data_set);
if (pcmk_is_set(data_set->flags, pe_flag_have_remote_nodes)) {
/* Allocate remote connection resources first (which will also allocate
* any colocation dependencies). If the connection is migrating, always
* prefer the partial migration target.
*/
for (iter = data_set->resources; iter != NULL; iter = iter->next) {
pe_resource_t *rsc = (pe_resource_t *) iter->data;
if (rsc->is_remote_node) {
pe_rsc_trace(rsc, "Allocating remote connection resource '%s'",
rsc->id);
rsc->cmds->allocate(rsc, rsc->partial_migration_target,
data_set);
}
}
}
/* now do the rest of the resources */
for (iter = data_set->resources; iter != NULL; iter = iter->next) {
pe_resource_t *rsc = (pe_resource_t *) iter->data;
if (!rsc->is_remote_node) {
pe_rsc_trace(rsc, "Allocating %s resource '%s'",
crm_element_name(rsc->xml), rsc->id);
rsc->cmds->allocate(rsc, NULL, data_set);
}
}
pcmk__show_node_capacities("Remaining", data_set);
}
/*!
* \internal
* \brief Schedule fail count clearing on online nodes if resource is orphaned
*
* \param[in] rsc Resource to check
* \param[in] data_set Cluster working set
*/
static void
clear_failcounts_if_orphaned(pe_resource_t *rsc, pe_working_set_t *data_set)
{
if (!pcmk_is_set(rsc->flags, pe_rsc_orphan)) {
return;
}
crm_trace("Clear fail counts for orphaned resource %s", rsc->id);
/* There's no need to recurse into rsc->children because those
* should just be unallocated clone instances.
*/
for (GList *iter = data_set->nodes; iter != NULL; iter = iter->next) {
pe_node_t *node = (pe_node_t *) iter->data;
pe_action_t *clear_op = NULL;
if (!node->details->online) {
continue;
}
if (pe_get_failcount(node, rsc, NULL, pe_fc_effective, NULL,
data_set) == 0) {
continue;
}
clear_op = pe__clear_failcount(rsc, node, "it is orphaned", data_set);
/* We can't use order_action_then_stop() here because its
* pe_order_preserve breaks things
*/
pcmk__new_ordering(clear_op->rsc, NULL, clear_op, rsc, stop_key(rsc),
NULL, pe_order_optional, data_set);
}
}
/*!
* \internal
* \brief Schedule any resource actions needed
*
* \param[in] data_set Cluster working set
*/
static void
schedule_resource_actions(pe_working_set_t *data_set)
{
// Process deferred action checks
pe__foreach_param_check(data_set, check_params);
pe__free_param_checks(data_set);
if (pcmk_is_set(data_set->flags, pe_flag_startup_probes)) {
crm_trace("Scheduling probes");
pcmk__schedule_probes(data_set);
}
if (pcmk_is_set(data_set->flags, pe_flag_stop_rsc_orphans)) {
g_list_foreach(data_set->resources,
(GFunc) clear_failcounts_if_orphaned, data_set);
}
crm_trace("Scheduling resource actions");
for (GList *iter = data_set->resources; iter != NULL; iter = iter->next) {
pe_resource_t *rsc = (pe_resource_t *) iter->data;
rsc->cmds->create_actions(rsc, data_set);
}
}
/*!
* \internal
* \brief Check whether a resource or any of its descendants are managed
*
* \param[in] rsc Resource to check
*
* \return true if resource or any descendent is managed, otherwise false
*/
static bool
is_managed(const pe_resource_t *rsc)
{
if (pcmk_is_set(rsc->flags, pe_rsc_managed)) {
return true;
}
for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
if (is_managed((pe_resource_t *) iter->data)) {
return true;
}
}
return false;
}
/*!
* \internal
* \brief Check whether any resources in the cluster are managed
*
* \param[in] data_set Cluster working set
*
* \return true if any resource is managed, otherwise false
*/
static bool
any_managed_resources(pe_working_set_t *data_set)
{
for (GList *iter = data_set->resources; iter != NULL; iter = iter->next) {
if (is_managed((pe_resource_t *) iter->data)) {
return true;
}
}
return false;
}
/*!
* \internal
* \brief Check whether a node requires fencing
*
* \param[in] node Node to check
* \param[in] have_managed Whether any resource in cluster is managed
* \param[in] data_set Cluster working set
*
* \return true if \p node should be fenced, otherwise false
*/
static bool
needs_fencing(pe_node_t *node, bool have_managed, pe_working_set_t *data_set)
{
return have_managed && node->details->unclean
&& pe_can_fence(data_set, node);
}
/*!
* \internal
* \brief Check whether a node requires shutdown
*
* \param[in] node Node to check
*
* \return true if \p node should be shut down, otherwise false
*/
static bool
needs_shutdown(pe_node_t *node)
{
if (pe__is_guest_or_remote_node(node)) {
/* Do not send shutdown actions for Pacemaker Remote nodes.
* @TODO We might come up with a good use for this in the future.
*/
return false;
}
return node->details->online && node->details->shutdown;
}
/*!
* \internal
* \brief Track and order non-DC fencing
*
* \param[in] list List of existing non-DC fencing actions
* \param[in] action Fencing action to prepend to \p list
*
* \return (Possibly new) head of \p list
*/
static GList *
add_nondc_fencing(GList *list, pe_action_t *action, pe_working_set_t *data_set)
{
if (!pcmk_is_set(data_set->flags, pe_flag_concurrent_fencing)
&& (list != NULL)) {
/* Concurrent fencing is disabled, so order each non-DC
* fencing in a chain. If there is any DC fencing or
* shutdown, it will be ordered after the last action in the
* chain later.
*/
order_actions((pe_action_t *) list->data, action, pe_order_optional);
}
return g_list_prepend(list, action);
}
/*!
* \internal
* \brief Schedule a node for fencing
*
* \param[in] node Node that requires fencing
* \param[in] data_set Cluster working set
*/
static pe_action_t *
schedule_fencing(pe_node_t *node, pe_working_set_t *data_set)
{
pe_action_t *fencing = pe_fence_op(node, NULL, FALSE, "node is unclean",
FALSE, data_set);
pe_warn("Scheduling node %s for fencing", node->details->uname);
pcmk__order_vs_fence(fencing, data_set);
return fencing;
}
/*!
* \internal
* \brief Create and order node fencing and shutdown actions
*
* \param[in] data_set Cluster working set
*/
static void
schedule_fencing_and_shutdowns(pe_working_set_t *data_set)
{
pe_action_t *dc_down = NULL;
bool integrity_lost = false;
bool have_managed = any_managed_resources(data_set);
GList *fencing_ops = NULL;
GList *shutdown_ops = NULL;
crm_trace("Scheduling fencing and shutdowns as needed");
if (!have_managed) {
crm_notice("No fencing will be done until there are resources to manage");
}
// Check each node for whether it needs fencing or shutdown
for (GList *iter = data_set->nodes; iter != NULL; iter = iter->next) {
pe_node_t *node = (pe_node_t *) iter->data;
pe_action_t *fencing = NULL;
/* Guest nodes are "fenced" by recovering their container resource,
* so handle them separately.
*/
if (pe__is_guest_node(node)) {
if (node->details->remote_requires_reset && have_managed
&& pe_can_fence(data_set, node)) {
pcmk__fence_guest(node, data_set);
}
continue;
}
if (needs_fencing(node, have_managed, data_set)) {
fencing = schedule_fencing(node, data_set);
// Track DC and non-DC fence actions separately
if (node->details->is_dc) {
dc_down = fencing;
} else {
fencing_ops = add_nondc_fencing(fencing_ops, fencing, data_set);
}
} else if (needs_shutdown(node)) {
pe_action_t *down_op = pcmk__new_shutdown_action(node, data_set);
// Track DC and non-DC shutdown actions separately
if (node->details->is_dc) {
dc_down = down_op;
} else {
shutdown_ops = g_list_prepend(shutdown_ops, down_op);
}
}
if ((fencing == NULL) && node->details->unclean) {
integrity_lost = true;
pe_warn("Node %s is unclean but cannot be fenced",
node->details->uname);
}
}
if (integrity_lost) {
if (!pcmk_is_set(data_set->flags, pe_flag_stonith_enabled)) {
pe_warn("Resource functionality and data integrity cannot be "
"guaranteed (configure, enable, and test fencing to "
"correct this)");
} else if (!pcmk_is_set(data_set->flags, pe_flag_have_quorum)) {
crm_notice("Unclean nodes will not be fenced until quorum is "
"attained or no-quorum-policy is set to ignore");
}
}
if (dc_down != NULL) {
/* Order any non-DC shutdowns before any DC shutdown, to avoid repeated
* DC elections. However, we don't want to order non-DC shutdowns before
* a DC *fencing*, because even though we don't want a node that's
* shutting down to become DC, the DC fencing could be ordered before a
* clone stop that's also ordered before the shutdowns, thus leading to
* a graph loop.
*/
if (pcmk__str_eq(dc_down->task, CRM_OP_SHUTDOWN, pcmk__str_none)) {
pcmk__order_after_each(dc_down, shutdown_ops);
}
// Order any non-DC fencing before any DC fencing or shutdown
if (pcmk_is_set(data_set->flags, pe_flag_concurrent_fencing)) {
/* With concurrent fencing, order each non-DC fencing action
* separately before any DC fencing or shutdown.
*/
pcmk__order_after_each(dc_down, fencing_ops);
} else if (fencing_ops != NULL) {
/* Without concurrent fencing, the non-DC fencing actions are
* already ordered relative to each other, so we just need to order
* the DC fencing after the last action in the chain (which is the
* first item in the list).
*/
order_actions((pe_action_t *) fencing_ops->data, dc_down,
pe_order_optional);
}
}
g_list_free(fencing_ops);
g_list_free(shutdown_ops);
}
static void
log_resource_details(pe_working_set_t *data_set)
{
pcmk__output_t *out = data_set->priv;
GList *all = NULL;
/* We need a list of nodes that we are allowed to output information for.
* This is necessary because out->message for all the resource-related
* messages expects such a list, due to the `crm_mon --node=` feature. Here,
* we just make it a list of all the nodes.
*/
all = g_list_prepend(all, (gpointer) "*");
for (GList *item = data_set->resources; item != NULL; item = item->next) {
pe_resource_t *rsc = (pe_resource_t *) item->data;
// Log all resources except inactive orphans
if (!pcmk_is_set(rsc->flags, pe_rsc_orphan)
|| (rsc->role != RSC_ROLE_STOPPED)) {
out->message(out, crm_map_element_name(rsc->xml), 0, rsc, all, all);
}
}
g_list_free(all);
}
static void
log_all_actions(pe_working_set_t *data_set)
{
/* This only ever outputs to the log, so ignore whatever output object was
* previously set and just log instead.
*/
pcmk__output_t *prev_out = data_set->priv;
- pcmk__output_t *out = pcmk__new_logger();
+ pcmk__output_t *out = NULL;
- if (out == NULL) {
+ if (pcmk__log_output_new(&out) != pcmk_rc_ok) {
return;
}
pe__register_messages(out);
pcmk__register_lib_messages(out);
pcmk__output_set_log_level(out, LOG_NOTICE);
data_set->priv = out;
out->begin_list(out, NULL, NULL, "Actions");
pcmk__output_actions(data_set);
out->end_list(out);
out->finish(out, CRM_EX_OK, true, NULL);
pcmk__output_free(out);
data_set->priv = prev_out;
}
/*!
* \internal
* \brief Log all required but unrunnable actions at trace level
*
* \param[in] data_set Cluster working set
*/
static void
log_unrunnable_actions(pe_working_set_t *data_set)
{
const uint64_t flags = pe_action_optional|pe_action_runnable|pe_action_pseudo;
crm_trace("Required but unrunnable actions:");
for (GList *iter = data_set->actions; iter != NULL; iter = iter->next) {
pe_action_t *action = (pe_action_t *) iter->data;
if (!pcmk_any_flags_set(action->flags, flags)) {
pcmk__log_action("\t", action, true);
}
}
}
/*!
* \internal
* \brief Unpack the CIB for scheduling
*
* \param[in] cib CIB XML to unpack (may be NULL if previously unpacked)
* \param[in] flags Working set flags to set in addition to defaults
* \param[in] data_set Cluster working set
*/
static void
unpack_cib(xmlNode *cib, unsigned long long flags, pe_working_set_t *data_set)
{
if (pcmk_is_set(data_set->flags, pe_flag_have_status)) {
crm_trace("Reusing previously calculated cluster status");
pe__set_working_set_flags(data_set, flags);
return;
}
CRM_ASSERT(cib != NULL);
crm_trace("Calculating cluster status");
/* This will zero the entire struct without freeing anything first, so
* callers should never call pcmk__schedule_actions() with a populated data
* set unless pe_flag_have_status is set (i.e. cluster_status() was
* previously called, whether directly or via pcmk__schedule_actions()).
*/
set_working_set_defaults(data_set);
pe__set_working_set_flags(data_set, flags);
data_set->input = cib;
cluster_status(data_set); // Sets pe_flag_have_status
}
/*!
* \internal
* \brief Run the scheduler for a given CIB
*
* \param[in] cib CIB XML to use as scheduler input
* \param[in] flags Working set flags to set in addition to defaults
* \param[in,out] data_set Cluster working set
*/
void
pcmk__schedule_actions(xmlNode *cib, unsigned long long flags,
pe_working_set_t *data_set)
{
unpack_cib(cib, flags, data_set);
pcmk__set_allocation_methods(data_set);
pcmk__apply_node_health(data_set);
pcmk__unpack_constraints(data_set);
if (pcmk_is_set(data_set->flags, pe_flag_check_config)) {
return;
}
if (!pcmk_is_set(data_set->flags, pe_flag_quick_location) &&
pcmk__is_daemon) {
log_resource_details(data_set);
}
apply_node_criteria(data_set);
if (pcmk_is_set(data_set->flags, pe_flag_quick_location)) {
return;
}
pcmk__create_internal_constraints(data_set);
pcmk__handle_rsc_config_changes(data_set);
allocate_resources(data_set);
schedule_resource_actions(data_set);
/* Remote ordering constraints need to happen prior to calculating fencing
* because it is one more place we can mark nodes as needing fencing.
*/
pcmk__order_remote_connection_actions(data_set);
schedule_fencing_and_shutdowns(data_set);
pcmk__apply_orderings(data_set);
log_all_actions(data_set);
pcmk__create_graph(data_set);
if (get_crm_log_level() == LOG_TRACE) {
log_unrunnable_actions(data_set);
}
}
diff --git a/lib/pacemaker/pcmk_simulate.c b/lib/pacemaker/pcmk_simulate.c
index 2e50f6f536..0fcc2980ad 100644
--- a/lib/pacemaker/pcmk_simulate.c
+++ b/lib/pacemaker/pcmk_simulate.c
@@ -1,983 +1,982 @@
/*
* 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 <crm_internal.h>
#include <crm/cib/internal.h>
#include <crm/common/output.h>
#include <crm/common/results.h>
#include <crm/pengine/pe_types.h>
#include <pacemaker-internal.h>
#include <pacemaker.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#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 = "<none>";
}
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;
+ rc = pcmk__log_output_new(&logger_out);
+ if (rc != pcmk_rc_ok) {
goto simulate_done;
}
pe__register_messages(logger_out);
pcmk__register_lib_messages(logger_out);
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__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__xml_output_finish(out, xml);
return rc;
}

File Metadata

Mime Type
text/x-diff
Expires
Tue, Jul 8, 6:15 PM (21 h, 45 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2002545
Default Alt Text
(110 KB)

Event Timeline