summaryrefslogtreecommitdiffstats
path: root/daemons/execd/cts-exec-helper.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 /daemons/execd/cts-exec-helper.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 'daemons/execd/cts-exec-helper.c')
-rw-r--r--daemons/execd/cts-exec-helper.c624
1 files changed, 624 insertions, 0 deletions
diff --git a/daemons/execd/cts-exec-helper.c b/daemons/execd/cts-exec-helper.c
new file mode 100644
index 0000000..2af5e16
--- /dev/null
+++ b/daemons/execd/cts-exec-helper.c
@@ -0,0 +1,624 @@
+/*
+ * Copyright 2012-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#include <crm_internal.h>
+
+#include <glib.h>
+#include <unistd.h>
+
+#include <crm/crm.h>
+#include <crm/services.h>
+#include <crm/common/cmdline_internal.h>
+#include <crm/common/mainloop.h>
+
+#include <crm/pengine/status.h>
+#include <crm/pengine/internal.h>
+#include <crm/cib.h>
+#include <crm/cib/internal.h>
+#include <crm/lrmd.h>
+
+#define SUMMARY "cts-exec-helper - inject commands into the Pacemaker executor and watch for events"
+
+static int exec_call_id = 0;
+static gboolean start_test(gpointer user_data);
+static void try_connect(void);
+
+static char *key = NULL;
+static char *val = NULL;
+
+static struct {
+ int verbose;
+ int quiet;
+ guint interval_ms;
+ int timeout;
+ int start_delay;
+ int cancel_call_id;
+ gboolean no_wait;
+ gboolean is_running;
+ gboolean no_connect;
+ int exec_call_opts;
+ const char *api_call;
+ const char *rsc_id;
+ const char *provider;
+ const char *class;
+ const char *type;
+ const char *action;
+ const char *listen;
+ gboolean use_tls;
+ lrmd_key_value_t *params;
+} options;
+
+static gboolean
+interval_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ options.interval_ms = crm_parse_interval_spec(optarg);
+ return errno == 0;
+}
+
+static gboolean
+notify_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ if (pcmk__str_any_of(option_name, "--notify-orig", "-n", NULL)) {
+ options.exec_call_opts = lrmd_opt_notify_orig_only;
+ } else if (pcmk__str_any_of(option_name, "--notify-changes", "-o", NULL)) {
+ options.exec_call_opts = lrmd_opt_notify_changes_only;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+param_key_val_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ if (pcmk__str_any_of(option_name, "--param-key", "-k", NULL)) {
+ pcmk__str_update(&key, optarg);
+ } else if (pcmk__str_any_of(option_name, "--param-val", "-v", NULL)) {
+ pcmk__str_update(&val, optarg);
+ }
+
+ if (key != NULL && val != NULL) {
+ options.params = lrmd_key_value_add(options.params, key, val);
+ pcmk__str_update(&key, NULL);
+ pcmk__str_update(&val, NULL);
+ }
+
+ return TRUE;
+}
+
+static GOptionEntry basic_entries[] = {
+ { "api-call", 'c', 0, G_OPTION_ARG_STRING, &options.api_call,
+ "Directly relates to executor API functions",
+ NULL },
+
+ { "is-running", 'R', 0, G_OPTION_ARG_NONE, &options.is_running,
+ "Determine if a resource is registered and running",
+ NULL },
+
+ { "listen", 'l', 0, G_OPTION_ARG_STRING, &options.listen,
+ "Listen for a specific event string",
+ NULL },
+
+ { "no-wait", 'w', 0, G_OPTION_ARG_NONE, &options.no_wait,
+ "Make api call and do not wait for result",
+ NULL },
+
+ { "notify-changes", 'o', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, notify_cb,
+ "Only notify client changes to recurring operations",
+ NULL },
+
+ { "notify-orig", 'n', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, notify_cb,
+ "Only notify this client of the results of an API action",
+ NULL },
+
+ { "tls", 'S', 0, G_OPTION_ARG_NONE, &options.use_tls,
+ "Use TLS backend for local connection",
+ NULL },
+
+ { NULL }
+};
+
+static GOptionEntry api_call_entries[] = {
+ { "action", 'a', 0, G_OPTION_ARG_STRING, &options.action,
+ NULL, NULL },
+
+ { "cancel-call-id", 'x', 0, G_OPTION_ARG_INT, &options.cancel_call_id,
+ NULL, NULL },
+
+ { "class", 'C', 0, G_OPTION_ARG_STRING, &options.class,
+ NULL, NULL },
+
+ { "interval", 'i', 0, G_OPTION_ARG_CALLBACK, interval_cb,
+ NULL, NULL },
+
+ { "param-key", 'k', 0, G_OPTION_ARG_CALLBACK, param_key_val_cb,
+ NULL, NULL },
+
+ { "param-val", 'v', 0, G_OPTION_ARG_CALLBACK, param_key_val_cb,
+ NULL, NULL },
+
+ { "provider", 'P', 0, G_OPTION_ARG_STRING, &options.provider,
+ NULL, NULL },
+
+ { "rsc-id", 'r', 0, G_OPTION_ARG_STRING, &options.rsc_id,
+ NULL, NULL },
+
+ { "start-delay", 's', 0, G_OPTION_ARG_INT, &options.start_delay,
+ NULL, NULL },
+
+ { "timeout", 't', 0, G_OPTION_ARG_INT, &options.timeout,
+ NULL, NULL },
+
+ { "type", 'T', 0, G_OPTION_ARG_STRING, &options.type,
+ NULL, NULL },
+
+ { NULL }
+};
+
+static GMainLoop *mainloop = NULL;
+static lrmd_t *lrmd_conn = NULL;
+
+static char event_buf_v0[1024];
+
+static crm_exit_t
+test_exit(crm_exit_t exit_code)
+{
+ lrmd_api_delete(lrmd_conn);
+ return crm_exit(exit_code);
+}
+
+#define print_result(fmt, args...) \
+ if (!options.quiet) { \
+ printf(fmt "\n" , ##args); \
+ }
+
+#define report_event(event) \
+ snprintf(event_buf_v0, sizeof(event_buf_v0), "NEW_EVENT event_type:%s rsc_id:%s action:%s rc:%s op_status:%s", \
+ lrmd_event_type2str(event->type), \
+ event->rsc_id, \
+ event->op_type ? event->op_type : "none", \
+ services_ocf_exitcode_str(event->rc), \
+ pcmk_exec_status_str(event->op_status)); \
+ crm_info("%s", event_buf_v0);
+
+static void
+test_shutdown(int nsig)
+{
+ lrmd_api_delete(lrmd_conn);
+ lrmd_conn = NULL;
+}
+
+static void
+read_events(lrmd_event_data_t * event)
+{
+ report_event(event);
+ if (options.listen) {
+ if (pcmk__str_eq(options.listen, event_buf_v0, pcmk__str_casei)) {
+ print_result("LISTEN EVENT SUCCESSFUL");
+ test_exit(CRM_EX_OK);
+ }
+ }
+
+ if (exec_call_id && (event->call_id == exec_call_id)) {
+ if (event->op_status == 0 && event->rc == 0) {
+ print_result("API-CALL SUCCESSFUL for 'exec'");
+ } else {
+ print_result("API-CALL FAILURE for 'exec', rc:%d lrmd_op_status:%s",
+ event->rc, pcmk_exec_status_str(event->op_status));
+ test_exit(CRM_EX_ERROR);
+ }
+
+ if (!options.listen) {
+ test_exit(CRM_EX_OK);
+ }
+ }
+}
+
+static gboolean
+timeout_err(gpointer data)
+{
+ print_result("LISTEN EVENT FAILURE - timeout occurred, never found");
+ test_exit(CRM_EX_TIMEOUT);
+ return FALSE;
+}
+
+static void
+connection_events(lrmd_event_data_t * event)
+{
+ int rc = event->connection_rc;
+
+ if (event->type != lrmd_event_connect) {
+ /* ignore */
+ return;
+ }
+
+ if (!rc) {
+ crm_info("Executor client connection established");
+ start_test(NULL);
+ return;
+ } else {
+ sleep(1);
+ try_connect();
+ crm_notice("Executor client connection failed");
+ }
+}
+
+static void
+try_connect(void)
+{
+ int tries = 10;
+ static int num_tries = 0;
+ int rc = 0;
+
+ lrmd_conn->cmds->set_callback(lrmd_conn, connection_events);
+ for (; num_tries < tries; num_tries++) {
+ rc = lrmd_conn->cmds->connect_async(lrmd_conn, crm_system_name, 3000);
+
+ if (!rc) {
+ return; /* we'll hear back in async callback */
+ }
+ sleep(1);
+ }
+
+ print_result("API CONNECTION FAILURE");
+ test_exit(CRM_EX_ERROR);
+}
+
+static gboolean
+start_test(gpointer user_data)
+{
+ int rc = 0;
+
+ if (!options.no_connect) {
+ if (!lrmd_conn->cmds->is_connected(lrmd_conn)) {
+ try_connect();
+ /* async connect -- this function will get called back into */
+ return 0;
+ }
+ }
+ lrmd_conn->cmds->set_callback(lrmd_conn, read_events);
+
+ if (options.timeout) {
+ g_timeout_add(options.timeout, timeout_err, NULL);
+ }
+
+ if (!options.api_call) {
+ return 0;
+ }
+
+ if (pcmk__str_eq(options.api_call, "exec", pcmk__str_casei)) {
+ rc = lrmd_conn->cmds->exec(lrmd_conn,
+ options.rsc_id,
+ options.action,
+ NULL,
+ options.interval_ms,
+ options.timeout,
+ options.start_delay,
+ options.exec_call_opts,
+ options.params);
+
+ if (rc > 0) {
+ exec_call_id = rc;
+ print_result("API-CALL 'exec' action pending, waiting on response");
+ }
+
+ } else if (pcmk__str_eq(options.api_call, "register_rsc", pcmk__str_casei)) {
+ rc = lrmd_conn->cmds->register_rsc(lrmd_conn,
+ options.rsc_id,
+ options.class, options.provider, options.type, 0);
+ } else if (pcmk__str_eq(options.api_call, "get_rsc_info", pcmk__str_casei)) {
+ lrmd_rsc_info_t *rsc_info;
+
+ rsc_info = lrmd_conn->cmds->get_rsc_info(lrmd_conn, options.rsc_id, 0);
+
+ if (rsc_info) {
+ print_result("RSC_INFO: id:%s class:%s provider:%s type:%s",
+ rsc_info->id, rsc_info->standard,
+ (rsc_info->provider? rsc_info->provider : "<none>"),
+ rsc_info->type);
+ lrmd_free_rsc_info(rsc_info);
+ rc = pcmk_ok;
+ } else {
+ rc = -1;
+ }
+ } else if (pcmk__str_eq(options.api_call, "unregister_rsc", pcmk__str_casei)) {
+ rc = lrmd_conn->cmds->unregister_rsc(lrmd_conn, options.rsc_id, 0);
+ } else if (pcmk__str_eq(options.api_call, "cancel", pcmk__str_casei)) {
+ rc = lrmd_conn->cmds->cancel(lrmd_conn, options.rsc_id, options.action,
+ options.interval_ms);
+ } else if (pcmk__str_eq(options.api_call, "metadata", pcmk__str_casei)) {
+ char *output = NULL;
+
+ rc = lrmd_conn->cmds->get_metadata(lrmd_conn,
+ options.class,
+ options.provider, options.type, &output, 0);
+ if (rc == pcmk_ok) {
+ print_result("%s", output);
+ free(output);
+ }
+ } else if (pcmk__str_eq(options.api_call, "list_agents", pcmk__str_casei)) {
+ lrmd_list_t *list = NULL;
+ lrmd_list_t *iter = NULL;
+
+ rc = lrmd_conn->cmds->list_agents(lrmd_conn, &list, options.class, options.provider);
+
+ if (rc > 0) {
+ print_result("%d agents found", rc);
+ for (iter = list; iter != NULL; iter = iter->next) {
+ print_result("%s", iter->val);
+ }
+ lrmd_list_freeall(list);
+ rc = 0;
+ } else {
+ print_result("API_CALL FAILURE - no agents found");
+ rc = -1;
+ }
+ } else if (pcmk__str_eq(options.api_call, "list_ocf_providers", pcmk__str_casei)) {
+ lrmd_list_t *list = NULL;
+ lrmd_list_t *iter = NULL;
+
+ rc = lrmd_conn->cmds->list_ocf_providers(lrmd_conn, options.type, &list);
+
+ if (rc > 0) {
+ print_result("%d providers found", rc);
+ for (iter = list; iter != NULL; iter = iter->next) {
+ print_result("%s", iter->val);
+ }
+ lrmd_list_freeall(list);
+ rc = 0;
+ } else {
+ print_result("API_CALL FAILURE - no providers found");
+ rc = -1;
+ }
+
+ } else if (pcmk__str_eq(options.api_call, "list_standards", pcmk__str_casei)) {
+ lrmd_list_t *list = NULL;
+ lrmd_list_t *iter = NULL;
+
+ rc = lrmd_conn->cmds->list_standards(lrmd_conn, &list);
+
+ if (rc > 0) {
+ print_result("%d standards found", rc);
+ for (iter = list; iter != NULL; iter = iter->next) {
+ print_result("%s", iter->val);
+ }
+ lrmd_list_freeall(list);
+ rc = 0;
+ } else {
+ print_result("API_CALL FAILURE - no providers found");
+ rc = -1;
+ }
+
+ } else if (pcmk__str_eq(options.api_call, "get_recurring_ops", pcmk__str_casei)) {
+ GList *op_list = NULL;
+ GList *op_item = NULL;
+ rc = lrmd_conn->cmds->get_recurring_ops(lrmd_conn, options.rsc_id, 0, 0,
+ &op_list);
+
+ for (op_item = op_list; op_item != NULL; op_item = op_item->next) {
+ lrmd_op_info_t *op_info = op_item->data;
+
+ print_result("RECURRING_OP: %s_%s_%s timeout=%sms",
+ op_info->rsc_id, op_info->action,
+ op_info->interval_ms_s, op_info->timeout_ms_s);
+ lrmd_free_op_info(op_info);
+ }
+ g_list_free(op_list);
+
+ } else if (options.api_call) {
+ print_result("API-CALL FAILURE unknown action '%s'", options.action);
+ test_exit(CRM_EX_ERROR);
+ }
+
+ if (rc < 0) {
+ print_result("API-CALL FAILURE for '%s' api_rc:%d",
+ options.api_call, rc);
+ test_exit(CRM_EX_ERROR);
+ }
+
+ if (options.api_call && rc == pcmk_ok) {
+ print_result("API-CALL SUCCESSFUL for '%s'", options.api_call);
+ if (!options.listen) {
+ test_exit(CRM_EX_OK);
+ }
+ }
+
+ if (options.no_wait) {
+ /* just make the call and exit regardless of anything else. */
+ test_exit(CRM_EX_OK);
+ }
+
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Generate resource parameters from CIB if none explicitly given
+ *
+ * \return Standard Pacemaker return code
+ */
+static int
+generate_params(void)
+{
+ int rc = pcmk_rc_ok;
+ pe_working_set_t *data_set = NULL;
+ xmlNode *cib_xml_copy = NULL;
+ pe_resource_t *rsc = NULL;
+ GHashTable *params = NULL;
+ GHashTable *meta = NULL;
+ GHashTableIter iter;
+ char *key = NULL;
+ char *value = NULL;
+
+ if (options.params != NULL) {
+ return pcmk_rc_ok; // User specified parameters explicitly
+ }
+
+ // Retrieve and update CIB
+ rc = cib__signon_query(NULL, NULL, &cib_xml_copy);
+ if (rc != pcmk_rc_ok) {
+ return rc;
+ }
+ if (!cli_config_update(&cib_xml_copy, NULL, FALSE)) {
+ crm_err("Could not update CIB");
+ return pcmk_rc_cib_corrupt;
+ }
+
+ // Calculate cluster status
+ data_set = pe_new_working_set();
+ if (data_set == NULL) {
+ crm_crit("Could not allocate working set");
+ return ENOMEM;
+ }
+ pe__set_working_set_flags(data_set, pe_flag_no_counts|pe_flag_no_compat);
+ data_set->input = cib_xml_copy;
+ data_set->now = crm_time_new(NULL);
+ cluster_status(data_set);
+
+ // Find resource in CIB
+ rsc = pe_find_resource_with_flags(data_set->resources, options.rsc_id,
+ pe_find_renamed|pe_find_any);
+ if (rsc == NULL) {
+ crm_err("Resource does not exist in config");
+ pe_free_working_set(data_set);
+ return EINVAL;
+ }
+
+ // Add resource instance parameters to options.params
+ params = pe_rsc_params(rsc, NULL, data_set);
+ if (params != NULL) {
+ g_hash_table_iter_init(&iter, params);
+ while (g_hash_table_iter_next(&iter, (gpointer *) &key,
+ (gpointer *) &value)) {
+ options.params = lrmd_key_value_add(options.params, key, value);
+ }
+ }
+
+ // Add resource meta-attributes to options.params
+ meta = pcmk__strkey_table(free, free);
+ get_meta_attributes(meta, rsc, NULL, data_set);
+ g_hash_table_iter_init(&iter, meta);
+ while (g_hash_table_iter_next(&iter, (gpointer *) &key,
+ (gpointer *) &value)) {
+ char *crm_name = crm_meta_name(key);
+
+ options.params = lrmd_key_value_add(options.params, crm_name, value);
+ free(crm_name);
+ }
+ g_hash_table_destroy(meta);
+
+ pe_free_working_set(data_set);
+ return rc;
+}
+
+static GOptionContext *
+build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
+ GOptionContext *context = NULL;
+
+ context = pcmk__build_arg_context(args, NULL, group, NULL);
+
+ pcmk__add_main_args(context, basic_entries);
+ pcmk__add_arg_group(context, "api-call", "API Call Options:",
+ "Parameters for api-call option", api_call_entries);
+
+ return context;
+}
+
+int
+main(int argc, char **argv)
+{
+ GError *error = NULL;
+ crm_exit_t exit_code = CRM_EX_OK;
+ crm_trigger_t *trig = NULL;
+
+ pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
+ /* Typically we'd pass all the single character options that take an argument
+ * as the second parameter here (and there's a bunch of those in this tool).
+ * However, we control how this program is called so we can just not call it
+ * in a way where the preprocessing ever matters.
+ */
+ gchar **processed_args = pcmk__cmdline_preproc(argv, NULL);
+ GOptionContext *context = build_arg_context(args, NULL);
+
+ if (!g_option_context_parse_strv(context, &processed_args, &error)) {
+ exit_code = CRM_EX_USAGE;
+ goto done;
+ }
+
+ /* We have to use crm_log_init here to set up the logging because there's
+ * different handling for daemons vs. command line programs, and
+ * pcmk__cli_init_logging is set up to only handle the latter.
+ */
+ crm_log_init(NULL, LOG_INFO, TRUE, (args->verbosity? TRUE : FALSE), argc,
+ argv, FALSE);
+
+ for (int i = 0; i < args->verbosity; i++) {
+ crm_bump_log_level(argc, argv);
+ }
+
+ if (!options.listen && pcmk__strcase_any_of(options.api_call, "metadata", "list_agents",
+ "list_standards", "list_ocf_providers", NULL)) {
+ options.no_connect = TRUE;
+ }
+
+ if (options.is_running) {
+ int rc = pcmk_rc_ok;
+
+ if (options.rsc_id == NULL) {
+ exit_code = CRM_EX_USAGE;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ "--is-running requires --rsc-id");
+ goto done;
+ }
+
+ options.interval_ms = 0;
+ if (options.timeout == 0) {
+ options.timeout = 30000;
+ }
+
+ rc = generate_params();
+ if (rc != pcmk_rc_ok) {
+ exit_code = pcmk_rc2exitc(rc);
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ "Can not determine resource status: "
+ "unable to get parameters from CIB");
+ goto done;
+ }
+ options.api_call = "exec";
+ options.action = "monitor";
+ options.exec_call_opts = lrmd_opt_notify_orig_only;
+ }
+
+ if (!options.api_call && !options.listen) {
+ exit_code = CRM_EX_USAGE;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ "Must specify at least one of --api-call, --listen, "
+ "or --is-running");
+ goto done;
+ }
+
+ if (options.use_tls) {
+ lrmd_conn = lrmd_remote_api_new(NULL, "localhost", 0);
+ } else {
+ lrmd_conn = lrmd_api_new();
+ }
+ trig = mainloop_add_trigger(G_PRIORITY_HIGH, start_test, NULL);
+ mainloop_set_trigger(trig);
+ mainloop_add_signal(SIGTERM, test_shutdown);
+
+ crm_info("Starting");
+ mainloop = g_main_loop_new(NULL, FALSE);
+ g_main_loop_run(mainloop);
+
+done:
+ g_strfreev(processed_args);
+ pcmk__free_arg_context(context);
+
+ free(key);
+ free(val);
+
+ pcmk__output_and_clear_error(&error, NULL);
+ return test_exit(exit_code);
+}