summaryrefslogtreecommitdiffstats
path: root/tools/crm_simulate.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 06:53:20 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 06:53:20 +0000
commite5a812082ae033afb1eed82c0f2df3d0f6bdc93f (patch)
treea6716c9275b4b413f6c9194798b34b91affb3cc7 /tools/crm_simulate.c
parentInitial commit. (diff)
downloadpacemaker-e5a812082ae033afb1eed82c0f2df3d0f6bdc93f.tar.xz
pacemaker-e5a812082ae033afb1eed82c0f2df3d0f6bdc93f.zip
Adding upstream version 2.1.6.upstream/2.1.6
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools/crm_simulate.c')
-rw-r--r--tools/crm_simulate.c587
1 files changed, 587 insertions, 0 deletions
diff --git a/tools/crm_simulate.c b/tools/crm_simulate.c
new file mode 100644
index 0000000..932c5bd
--- /dev/null
+++ b/tools/crm_simulate.c
@@ -0,0 +1,587 @@
+/*
+ * Copyright 2009-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU General Public License version 2
+ * or later (GPLv2+) WITHOUT ANY WARRANTY.
+ */
+
+#include <crm_internal.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#include <crm/crm.h>
+#include <crm/cib.h>
+#include <crm/cib/internal.h>
+#include <crm/common/cmdline_internal.h>
+#include <crm/common/output_internal.h>
+#include <crm/common/output.h>
+#include <crm/common/util.h>
+#include <crm/common/iso8601.h>
+#include <crm/pengine/status.h>
+#include <pacemaker-internal.h>
+#include <pacemaker.h>
+
+#define SUMMARY "crm_simulate - simulate a Pacemaker cluster's response to events"
+
+struct {
+ char *dot_file;
+ char *graph_file;
+ gchar *input_file;
+ pcmk_injections_t *injections;
+ unsigned int flags;
+ gchar *output_file;
+ long long repeat;
+ gboolean store;
+ gchar *test_dir;
+ char *use_date;
+ char *xml_file;
+} options = {
+ .flags = pcmk_sim_show_pending | pcmk_sim_sanitized,
+ .repeat = 1
+};
+
+uint32_t section_opts = 0;
+char *temp_shadow = NULL;
+crm_exit_t exit_code = CRM_EX_OK;
+
+#define INDENT " "
+
+static pcmk__supported_format_t formats[] = {
+ PCMK__SUPPORTED_FORMAT_NONE,
+ PCMK__SUPPORTED_FORMAT_TEXT,
+ PCMK__SUPPORTED_FORMAT_XML,
+ { NULL, NULL, NULL }
+};
+
+static gboolean
+all_actions_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ options.flags |= pcmk_sim_all_actions;
+ return TRUE;
+}
+
+static gboolean
+attrs_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ section_opts |= pcmk_section_attributes;
+ return TRUE;
+}
+
+static gboolean
+failcounts_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ section_opts |= pcmk_section_failcounts | pcmk_section_failures;
+ return TRUE;
+}
+
+static gboolean
+in_place_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ options.store = TRUE;
+ options.flags |= pcmk_sim_process | pcmk_sim_simulate;
+ return TRUE;
+}
+
+static gboolean
+live_check_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ if (options.xml_file) {
+ free(options.xml_file);
+ }
+
+ options.xml_file = NULL;
+ options.flags &= ~pcmk_sim_sanitized;
+ return TRUE;
+}
+
+static gboolean
+node_down_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ options.injections->node_down = g_list_append(options.injections->node_down, g_strdup(optarg));
+ return TRUE;
+}
+
+static gboolean
+node_fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ options.injections->node_fail = g_list_append(options.injections->node_fail, g_strdup(optarg));
+ return TRUE;
+}
+
+static gboolean
+node_up_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ pcmk__simulate_node_config = true;
+ options.injections->node_up = g_list_append(options.injections->node_up, g_strdup(optarg));
+ return TRUE;
+}
+
+static gboolean
+op_fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ options.flags |= pcmk_sim_process | pcmk_sim_simulate;
+ options.injections->op_fail = g_list_append(options.injections->op_fail, g_strdup(optarg));
+ return TRUE;
+}
+
+static gboolean
+op_inject_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ options.injections->op_inject = g_list_append(options.injections->op_inject, g_strdup(optarg));
+ return TRUE;
+}
+
+static gboolean
+pending_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ options.flags |= pcmk_sim_show_pending;
+ return TRUE;
+}
+
+static gboolean
+process_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ options.flags |= pcmk_sim_process;
+ return TRUE;
+}
+
+static gboolean
+quorum_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ pcmk__str_update(&options.injections->quorum, optarg);
+ return TRUE;
+}
+
+static gboolean
+save_dotfile_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ options.flags |= pcmk_sim_process;
+ pcmk__str_update(&options.dot_file, optarg);
+ return TRUE;
+}
+
+static gboolean
+save_graph_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ options.flags |= pcmk_sim_process;
+ pcmk__str_update(&options.graph_file, optarg);
+ return TRUE;
+}
+
+static gboolean
+show_scores_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ options.flags |= pcmk_sim_process | pcmk_sim_show_scores;
+ return TRUE;
+}
+
+static gboolean
+simulate_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ options.flags |= pcmk_sim_process | pcmk_sim_simulate;
+ return TRUE;
+}
+
+static gboolean
+ticket_activate_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ options.injections->ticket_activate = g_list_append(options.injections->ticket_activate, g_strdup(optarg));
+ return TRUE;
+}
+
+static gboolean
+ticket_grant_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ options.injections->ticket_grant = g_list_append(options.injections->ticket_grant, g_strdup(optarg));
+ return TRUE;
+}
+
+static gboolean
+ticket_revoke_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ options.injections->ticket_revoke = g_list_append(options.injections->ticket_revoke, g_strdup(optarg));
+ return TRUE;
+}
+
+static gboolean
+ticket_standby_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ options.injections->ticket_standby = g_list_append(options.injections->ticket_standby, g_strdup(optarg));
+ return TRUE;
+}
+
+static gboolean
+utilization_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ options.flags |= pcmk_sim_process | pcmk_sim_show_utilization;
+ return TRUE;
+}
+
+static gboolean
+watchdog_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ pcmk__str_update(&options.injections->watchdog, optarg);
+ return TRUE;
+}
+
+static gboolean
+xml_file_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ pcmk__str_update(&options.xml_file, optarg);
+ options.flags |= pcmk_sim_sanitized;
+ return TRUE;
+}
+
+static gboolean
+xml_pipe_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ pcmk__str_update(&options.xml_file, "-");
+ options.flags |= pcmk_sim_sanitized;
+ return TRUE;
+}
+
+static GOptionEntry operation_entries[] = {
+ { "run", 'R', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, process_cb,
+ "Process the supplied input and show what actions the cluster will take in response",
+ NULL },
+ { "simulate", 'S', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, simulate_cb,
+ "Like --run, but also simulate taking those actions and show the resulting new status",
+ NULL },
+ { "in-place", 'X', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, in_place_cb,
+ "Like --simulate, but also store the results back to the input file",
+ NULL },
+ { "show-attrs", 'A', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, attrs_cb,
+ "Show node attributes",
+ NULL },
+ { "show-failcounts", 'c', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, failcounts_cb,
+ "Show resource fail counts",
+ NULL },
+ { "show-scores", 's', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_scores_cb,
+ "Show allocation scores",
+ NULL },
+ { "show-utilization", 'U', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, utilization_cb,
+ "Show utilization information",
+ NULL },
+ { "profile", 'P', 0, G_OPTION_ARG_FILENAME, &options.test_dir,
+ "Process all the XML files in the named directory to create profiling data",
+ "DIR" },
+ { "repeat", 'N', 0, G_OPTION_ARG_INT, &options.repeat,
+ "With --profile, repeat each test N times and print timings",
+ "N" },
+ /* Deprecated */
+ { "pending", 'j', G_OPTION_FLAG_NO_ARG|G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, pending_cb,
+ "Display pending state if 'record-pending' is enabled",
+ NULL },
+
+ { NULL }
+};
+
+static GOptionEntry synthetic_entries[] = {
+ { "node-up", 'u', 0, G_OPTION_ARG_CALLBACK, node_up_cb,
+ "Simulate bringing a node online",
+ "NODE" },
+ { "node-down", 'd', 0, G_OPTION_ARG_CALLBACK, node_down_cb,
+ "Simulate taking a node offline",
+ "NODE" },
+ { "node-fail", 'f', 0, G_OPTION_ARG_CALLBACK, node_fail_cb,
+ "Simulate a node failing",
+ "NODE" },
+ { "op-inject", 'i', 0, G_OPTION_ARG_CALLBACK, op_inject_cb,
+ "Generate a failure for the cluster to react to in the simulation.\n"
+ INDENT "See `Operation Specification` help for more information.",
+ "OPSPEC" },
+ { "op-fail", 'F', 0, G_OPTION_ARG_CALLBACK, op_fail_cb,
+ "If the specified task occurs during the simulation, have it fail with return code ${rc}.\n"
+ INDENT "The transition will normally stop at the failed action.\n"
+ INDENT "Save the result with --save-output and re-run with --xml-file.\n"
+ INDENT "See `Operation Specification` help for more information.",
+ "OPSPEC" },
+ { "set-datetime", 't', 0, G_OPTION_ARG_STRING, &options.use_date,
+ "Set date/time (ISO 8601 format, see https://en.wikipedia.org/wiki/ISO_8601)",
+ "DATETIME" },
+ { "quorum", 'q', 0, G_OPTION_ARG_CALLBACK, quorum_cb,
+ "Set to '1' (or 'true') to indicate cluster has quorum",
+ "QUORUM" },
+ { "watchdog", 'w', 0, G_OPTION_ARG_CALLBACK, watchdog_cb,
+ "Set to '1' (or 'true') to indicate cluster has an active watchdog device",
+ "DEVICE" },
+ { "ticket-grant", 'g', 0, G_OPTION_ARG_CALLBACK, ticket_grant_cb,
+ "Simulate granting a ticket",
+ "TICKET" },
+ { "ticket-revoke", 'r', 0, G_OPTION_ARG_CALLBACK, ticket_revoke_cb,
+ "Simulate revoking a ticket",
+ "TICKET" },
+ { "ticket-standby", 'b', 0, G_OPTION_ARG_CALLBACK, ticket_standby_cb,
+ "Simulate making a ticket standby",
+ "TICKET" },
+ { "ticket-activate", 'e', 0, G_OPTION_ARG_CALLBACK, ticket_activate_cb,
+ "Simulate activating a ticket",
+ "TICKET" },
+
+ { NULL }
+};
+
+static GOptionEntry artifact_entries[] = {
+ { "save-input", 'I', 0, G_OPTION_ARG_FILENAME, &options.input_file,
+ "Save the input configuration to the named file",
+ "FILE" },
+ { "save-output", 'O', 0, G_OPTION_ARG_FILENAME, &options.output_file,
+ "Save the output configuration to the named file",
+ "FILE" },
+ { "save-graph", 'G', 0, G_OPTION_ARG_CALLBACK, save_graph_cb,
+ "Save the transition graph (XML format) to the named file",
+ "FILE" },
+ { "save-dotfile", 'D', 0, G_OPTION_ARG_CALLBACK, save_dotfile_cb,
+ "Save the transition graph (DOT format) to the named file",
+ "FILE" },
+ { "all-actions", 'a', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, all_actions_cb,
+ "Display all possible actions in DOT graph (even if not part of transition)",
+ NULL },
+
+ { NULL }
+};
+
+static GOptionEntry source_entries[] = {
+ { "live-check", 'L', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, live_check_cb,
+ "Connect to CIB manager and use the current CIB contents as input",
+ NULL },
+ { "xml-file", 'x', 0, G_OPTION_ARG_CALLBACK, xml_file_cb,
+ "Retrieve XML from the named file",
+ "FILE" },
+ { "xml-pipe", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, xml_pipe_cb,
+ "Retrieve XML from stdin",
+ NULL },
+
+ { NULL }
+};
+
+static int
+setup_input(pcmk__output_t *out, const char *input, const char *output,
+ GError **error)
+{
+ int rc = pcmk_rc_ok;
+ xmlNode *cib_object = NULL;
+ char *local_output = NULL;
+
+ if (input == NULL) {
+ /* Use live CIB */
+ rc = cib__signon_query(out, NULL, &cib_object);
+ if (rc != pcmk_rc_ok) {
+ // cib__signon_query() outputs any relevant error
+ return rc;
+ }
+
+ } else if (pcmk__str_eq(input, "-", pcmk__str_casei)) {
+ cib_object = filename2xml(NULL);
+
+ } else {
+ cib_object = filename2xml(input);
+ }
+
+ 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 pcmk_rc_transform_failed;
+ }
+
+ if (validate_xml(cib_object, NULL, FALSE) != TRUE) {
+ free_xml(cib_object);
+ return pcmk_rc_schema_validation;
+ }
+
+ if (output == NULL) {
+ char *pid = pcmk__getpid_s();
+
+ local_output = get_shadow_file(pid);
+ temp_shadow = strdup(local_output);
+ output = local_output;
+ free(pid);
+ }
+
+ rc = write_xml_file(cib_object, output, FALSE);
+ free_xml(cib_object);
+ cib_object = NULL;
+
+ if (rc < 0) {
+ rc = pcmk_legacy2rc(rc);
+ g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_CANTCREAT,
+ "Could not create '%s': %s", output, pcmk_rc_str(rc));
+ return rc;
+ } else {
+ setenv("CIB_file", output, 1);
+ free(local_output);
+ return pcmk_rc_ok;
+ }
+}
+
+static GOptionContext *
+build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
+ GOptionContext *context = NULL;
+
+ GOptionEntry extra_prog_entries[] = {
+ { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &(args->quiet),
+ "Display only essential output",
+ NULL },
+
+ { NULL }
+ };
+
+ const char *description = "Operation Specification:\n\n"
+ "The OPSPEC in any command line option is of the form\n"
+ "${resource}_${task}_${interval_in_ms}@${node}=${rc}\n"
+ "(memcached_monitor_20000@bart.example.com=7, for example).\n"
+ "${rc} is an OCF return code. For more information on these\n"
+ "return codes, refer to https://clusterlabs.org/pacemaker/doc/2.1/Pacemaker_Administration/html/agents.html#ocf-return-codes\n\n"
+ "Examples:\n\n"
+ "Pretend a recurring monitor action found memcached stopped on node\n"
+ "fred.example.com and, during recovery, that the memcached stop\n"
+ "action failed:\n\n"
+ "\tcrm_simulate -LS --op-inject memcached:0_monitor_20000@bart.example.com=7 "
+ "--op-fail memcached:0_stop_0@fred.example.com=1 --save-output /tmp/memcached-test.xml\n\n"
+ "Now see what the reaction to the stop failed would be:\n\n"
+ "\tcrm_simulate -S --xml-file /tmp/memcached-test.xml\n\n";
+
+ context = pcmk__build_arg_context(args, "text (default), xml", group, NULL);
+ pcmk__add_main_args(context, extra_prog_entries);
+ g_option_context_set_description(context, description);
+
+ pcmk__add_arg_group(context, "operations", "Operations:",
+ "Show operations options", operation_entries);
+ pcmk__add_arg_group(context, "synthetic", "Synthetic Cluster Events:",
+ "Show synthetic cluster event options", synthetic_entries);
+ pcmk__add_arg_group(context, "artifact", "Artifact Options:",
+ "Show artifact options", artifact_entries);
+ pcmk__add_arg_group(context, "source", "Data Source:",
+ "Show data source options", source_entries);
+
+ return context;
+}
+
+int
+main(int argc, char **argv)
+{
+ int rc = pcmk_rc_ok;
+ pe_working_set_t *data_set = NULL;
+ pcmk__output_t *out = NULL;
+
+ GError *error = NULL;
+
+ GOptionGroup *output_group = NULL;
+ pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
+ gchar **processed_args = pcmk__cmdline_preproc(argv, "bdefgiqrtuwxDFGINOP");
+ GOptionContext *context = build_arg_context(args, &output_group);
+
+ options.injections = calloc(1, sizeof(pcmk_injections_t));
+ if (options.injections == NULL) {
+ rc = ENOMEM;
+ goto done;
+ }
+
+ /* This must come before g_option_context_parse_strv. */
+ options.xml_file = strdup("-");
+
+ pcmk__register_formats(output_group, formats);
+ if (!g_option_context_parse_strv(context, &processed_args, &error)) {
+ exit_code = CRM_EX_USAGE;
+ goto done;
+ }
+
+ pcmk__cli_init_logging("crm_simulate", args->verbosity);
+
+ rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
+ if (rc != pcmk_rc_ok) {
+ fprintf(stderr, "Error creating output format %s: %s\n",
+ args->output_ty, pcmk_rc_str(rc));
+ exit_code = CRM_EX_ERROR;
+ goto done;
+ }
+
+ if (pcmk__str_eq(args->output_ty, "text", pcmk__str_null_matches) &&
+ !pcmk_is_set(options.flags, pcmk_sim_show_scores) &&
+ !pcmk_is_set(options.flags, pcmk_sim_show_utilization)) {
+ pcmk__force_args(context, &error, "%s --text-fancy", g_get_prgname());
+ } else if (pcmk__str_eq(args->output_ty, "xml", pcmk__str_none)) {
+ pcmk__force_args(context, &error, "%s --xml-simple-list --xml-substitute", g_get_prgname());
+ }
+
+ pe__register_messages(out);
+ pcmk__register_lib_messages(out);
+
+ out->quiet = args->quiet;
+
+ if (args->version) {
+ out->version(out, false);
+ goto done;
+ }
+
+ if (args->verbosity > 0) {
+ options.flags |= pcmk_sim_verbose;
+
+#ifdef PCMK__COMPAT_2_0
+ /* Redirect stderr to stdout so we can grep the output */
+ close(STDERR_FILENO);
+ dup2(STDOUT_FILENO, STDERR_FILENO);
+#endif
+ }
+
+ data_set = pe_new_working_set();
+ if (data_set == NULL) {
+ rc = ENOMEM;
+ g_set_error(&error, PCMK__RC_ERROR, rc, "Could not allocate working set");
+ goto done;
+ }
+
+ if (pcmk_is_set(options.flags, pcmk_sim_show_scores)) {
+ pe__set_working_set_flags(data_set, pe_flag_show_scores);
+ }
+ if (pcmk_is_set(options.flags, pcmk_sim_show_utilization)) {
+ pe__set_working_set_flags(data_set, pe_flag_show_utilization);
+ }
+ pe__set_working_set_flags(data_set, pe_flag_no_compat);
+
+ if (options.test_dir != NULL) {
+ data_set->priv = out;
+ pcmk__profile_dir(options.test_dir, options.repeat, data_set, options.use_date);
+ rc = pcmk_rc_ok;
+ goto done;
+ }
+
+ rc = setup_input(out, options.xml_file,
+ options.store? options.xml_file : options.output_file,
+ &error);
+ if (rc != pcmk_rc_ok) {
+ goto done;
+ }
+
+ rc = pcmk__simulate(data_set, out, options.injections, options.flags, section_opts,
+ options.use_date, options.input_file, options.graph_file,
+ options.dot_file);
+
+ done:
+ pcmk__output_and_clear_error(&error, NULL);
+
+ /* There sure is a lot to free in options. */
+ free(options.dot_file);
+ free(options.graph_file);
+ g_free(options.input_file);
+ g_free(options.output_file);
+ g_free(options.test_dir);
+ free(options.use_date);
+ free(options.xml_file);
+
+ pcmk_free_injections(options.injections);
+ pcmk__free_arg_context(context);
+ g_strfreev(processed_args);
+
+ if (data_set) {
+ pe_free_working_set(data_set);
+ }
+
+ fflush(stderr);
+
+ if (temp_shadow) {
+ unlink(temp_shadow);
+ free(temp_shadow);
+ }
+
+ if (rc != pcmk_rc_ok) {
+ exit_code = pcmk_rc2exitc(rc);
+ }
+
+ if (out != NULL) {
+ out->finish(out, exit_code, true, NULL);
+ pcmk__output_free(out);
+ }
+
+ pcmk__unregister_formats();
+ crm_exit(exit_code);
+}