summaryrefslogtreecommitdiffstats
path: root/daemons/controld/controld_execd_state.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/controld/controld_execd_state.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/controld/controld_execd_state.c')
-rw-r--r--daemons/controld/controld_execd_state.c814
1 files changed, 814 insertions, 0 deletions
diff --git a/daemons/controld/controld_execd_state.c b/daemons/controld/controld_execd_state.c
new file mode 100644
index 0000000..8c68bfc
--- /dev/null
+++ b/daemons/controld/controld_execd_state.c
@@ -0,0 +1,814 @@
+/*
+ * 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 General Public License version 2
+ * or later (GPLv2+) WITHOUT ANY WARRANTY.
+ */
+
+#include <crm_internal.h>
+
+#include <errno.h>
+
+#include <crm/crm.h>
+#include <crm/msg_xml.h>
+#include <crm/common/iso8601.h>
+#include <crm/pengine/rules.h>
+#include <crm/pengine/rules_internal.h>
+#include <crm/lrmd_internal.h>
+
+#include <pacemaker-internal.h>
+#include <pacemaker-controld.h>
+
+static GHashTable *lrm_state_table = NULL;
+extern GHashTable *proxy_table;
+int lrmd_internal_proxy_send(lrmd_t * lrmd, xmlNode *msg);
+void lrmd_internal_set_proxy_callback(lrmd_t * lrmd, void *userdata, void (*callback)(lrmd_t *lrmd, void *userdata, xmlNode *msg));
+
+static void
+free_rsc_info(gpointer value)
+{
+ lrmd_rsc_info_t *rsc_info = value;
+
+ lrmd_free_rsc_info(rsc_info);
+}
+
+static void
+free_deletion_op(gpointer value)
+{
+ struct pending_deletion_op_s *op = value;
+
+ free(op->rsc);
+ delete_ha_msg_input(op->input);
+ free(op);
+}
+
+static void
+free_recurring_op(gpointer value)
+{
+ active_op_t *op = value;
+
+ free(op->user_data);
+ free(op->rsc_id);
+ free(op->op_type);
+ free(op->op_key);
+ if (op->params) {
+ g_hash_table_destroy(op->params);
+ }
+ free(op);
+}
+
+static gboolean
+fail_pending_op(gpointer key, gpointer value, gpointer user_data)
+{
+ lrmd_event_data_t event = { 0, };
+ lrm_state_t *lrm_state = user_data;
+ active_op_t *op = value;
+
+ crm_trace("Pre-emptively failing " PCMK__OP_FMT " on %s (call=%s, %s)",
+ op->rsc_id, op->op_type, op->interval_ms,
+ lrm_state->node_name, (char*)key, op->user_data);
+
+ event.type = lrmd_event_exec_complete;
+ event.rsc_id = op->rsc_id;
+ event.op_type = op->op_type;
+ event.user_data = op->user_data;
+ event.timeout = 0;
+ event.interval_ms = op->interval_ms;
+ lrmd__set_result(&event, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_NOT_CONNECTED,
+ "Action was pending when executor connection was dropped");
+ event.t_run = (unsigned int) op->start_time;
+ event.t_rcchange = (unsigned int) op->start_time;
+
+ event.call_id = op->call_id;
+ event.remote_nodename = lrm_state->node_name;
+ event.params = op->params;
+
+ process_lrm_event(lrm_state, &event, op, NULL);
+ lrmd__reset_result(&event);
+ return TRUE;
+}
+
+gboolean
+lrm_state_is_local(lrm_state_t *lrm_state)
+{
+ return (lrm_state != NULL)
+ && pcmk__str_eq(lrm_state->node_name, controld_globals.our_nodename,
+ pcmk__str_casei);
+}
+
+/*!
+ * \internal
+ * \brief Create executor state entry for a node and add it to the state table
+ *
+ * \param[in] node_name Node to create entry for
+ *
+ * \return Newly allocated executor state object initialized for \p node_name
+ */
+static lrm_state_t *
+lrm_state_create(const char *node_name)
+{
+ lrm_state_t *state = NULL;
+
+ if (!node_name) {
+ crm_err("No node name given for lrm state object");
+ return NULL;
+ }
+
+ state = calloc(1, sizeof(lrm_state_t));
+ if (!state) {
+ return NULL;
+ }
+
+ state->node_name = strdup(node_name);
+ state->rsc_info_cache = pcmk__strkey_table(NULL, free_rsc_info);
+ state->deletion_ops = pcmk__strkey_table(free, free_deletion_op);
+ state->active_ops = pcmk__strkey_table(free, free_recurring_op);
+ state->resource_history = pcmk__strkey_table(NULL, history_free);
+ state->metadata_cache = metadata_cache_new();
+
+ g_hash_table_insert(lrm_state_table, (char *)state->node_name, state);
+ return state;
+}
+
+void
+lrm_state_destroy(const char *node_name)
+{
+ g_hash_table_remove(lrm_state_table, node_name);
+}
+
+static gboolean
+remote_proxy_remove_by_node(gpointer key, gpointer value, gpointer user_data)
+{
+ remote_proxy_t *proxy = value;
+ const char *node_name = user_data;
+
+ if (pcmk__str_eq(node_name, proxy->node_name, pcmk__str_casei)) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static remote_proxy_t *
+find_connected_proxy_by_node(const char * node_name)
+{
+ GHashTableIter gIter;
+ remote_proxy_t *proxy = NULL;
+
+ CRM_CHECK(proxy_table != NULL, return NULL);
+
+ g_hash_table_iter_init(&gIter, proxy_table);
+
+ while (g_hash_table_iter_next(&gIter, NULL, (gpointer *) &proxy)) {
+ if (proxy->source
+ && pcmk__str_eq(node_name, proxy->node_name, pcmk__str_casei)) {
+ return proxy;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+remote_proxy_disconnect_by_node(const char * node_name)
+{
+ remote_proxy_t *proxy = NULL;
+
+ CRM_CHECK(proxy_table != NULL, return);
+
+ while ((proxy = find_connected_proxy_by_node(node_name)) != NULL) {
+ /* mainloop_del_ipc_client() eventually calls remote_proxy_disconnected()
+ * , which removes the entry from proxy_table.
+ * Do not do this in a g_hash_table_iter_next() loop. */
+ if (proxy->source) {
+ mainloop_del_ipc_client(proxy->source);
+ }
+ }
+
+ return;
+}
+
+static void
+internal_lrm_state_destroy(gpointer data)
+{
+ lrm_state_t *lrm_state = data;
+
+ if (!lrm_state) {
+ return;
+ }
+
+ /* Rather than directly remove the recorded proxy entries from proxy_table,
+ * make sure any connected proxies get disconnected. So that
+ * remote_proxy_disconnected() will be called and as well remove the
+ * entries from proxy_table.
+ */
+ remote_proxy_disconnect_by_node(lrm_state->node_name);
+
+ crm_trace("Destroying proxy table %s with %u members",
+ lrm_state->node_name, g_hash_table_size(proxy_table));
+ // Just in case there's still any leftovers in proxy_table
+ g_hash_table_foreach_remove(proxy_table, remote_proxy_remove_by_node, (char *) lrm_state->node_name);
+ remote_ra_cleanup(lrm_state);
+ lrmd_api_delete(lrm_state->conn);
+
+ if (lrm_state->rsc_info_cache) {
+ crm_trace("Destroying rsc info cache with %u members",
+ g_hash_table_size(lrm_state->rsc_info_cache));
+ g_hash_table_destroy(lrm_state->rsc_info_cache);
+ }
+ if (lrm_state->resource_history) {
+ crm_trace("Destroying history op cache with %u members",
+ g_hash_table_size(lrm_state->resource_history));
+ g_hash_table_destroy(lrm_state->resource_history);
+ }
+ if (lrm_state->deletion_ops) {
+ crm_trace("Destroying deletion op cache with %u members",
+ g_hash_table_size(lrm_state->deletion_ops));
+ g_hash_table_destroy(lrm_state->deletion_ops);
+ }
+ if (lrm_state->active_ops != NULL) {
+ crm_trace("Destroying pending op cache with %u members",
+ g_hash_table_size(lrm_state->active_ops));
+ g_hash_table_destroy(lrm_state->active_ops);
+ }
+ metadata_cache_free(lrm_state->metadata_cache);
+
+ free((char *)lrm_state->node_name);
+ free(lrm_state);
+}
+
+void
+lrm_state_reset_tables(lrm_state_t * lrm_state, gboolean reset_metadata)
+{
+ if (lrm_state->resource_history) {
+ crm_trace("Resetting resource history cache with %u members",
+ g_hash_table_size(lrm_state->resource_history));
+ g_hash_table_remove_all(lrm_state->resource_history);
+ }
+ if (lrm_state->deletion_ops) {
+ crm_trace("Resetting deletion operations cache with %u members",
+ g_hash_table_size(lrm_state->deletion_ops));
+ g_hash_table_remove_all(lrm_state->deletion_ops);
+ }
+ if (lrm_state->active_ops != NULL) {
+ crm_trace("Resetting active operations cache with %u members",
+ g_hash_table_size(lrm_state->active_ops));
+ g_hash_table_remove_all(lrm_state->active_ops);
+ }
+ if (lrm_state->rsc_info_cache) {
+ crm_trace("Resetting resource information cache with %u members",
+ g_hash_table_size(lrm_state->rsc_info_cache));
+ g_hash_table_remove_all(lrm_state->rsc_info_cache);
+ }
+ if (reset_metadata) {
+ metadata_cache_reset(lrm_state->metadata_cache);
+ }
+}
+
+gboolean
+lrm_state_init_local(void)
+{
+ if (lrm_state_table) {
+ return TRUE;
+ }
+
+ lrm_state_table = pcmk__strikey_table(NULL, internal_lrm_state_destroy);
+ if (!lrm_state_table) {
+ return FALSE;
+ }
+
+ proxy_table = pcmk__strikey_table(NULL, remote_proxy_free);
+ if (!proxy_table) {
+ g_hash_table_destroy(lrm_state_table);
+ lrm_state_table = NULL;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void
+lrm_state_destroy_all(void)
+{
+ if (lrm_state_table) {
+ crm_trace("Destroying state table with %u members",
+ g_hash_table_size(lrm_state_table));
+ g_hash_table_destroy(lrm_state_table); lrm_state_table = NULL;
+ }
+ if(proxy_table) {
+ crm_trace("Destroying proxy table with %u members",
+ g_hash_table_size(proxy_table));
+ g_hash_table_destroy(proxy_table); proxy_table = NULL;
+ }
+}
+
+lrm_state_t *
+lrm_state_find(const char *node_name)
+{
+ if (!node_name) {
+ return NULL;
+ }
+ return g_hash_table_lookup(lrm_state_table, node_name);
+}
+
+lrm_state_t *
+lrm_state_find_or_create(const char *node_name)
+{
+ lrm_state_t *lrm_state;
+
+ lrm_state = g_hash_table_lookup(lrm_state_table, node_name);
+ if (!lrm_state) {
+ lrm_state = lrm_state_create(node_name);
+ }
+
+ return lrm_state;
+}
+
+GList *
+lrm_state_get_list(void)
+{
+ return g_hash_table_get_values(lrm_state_table);
+}
+
+void
+lrm_state_disconnect_only(lrm_state_t * lrm_state)
+{
+ int removed = 0;
+
+ if (!lrm_state->conn) {
+ return;
+ }
+ crm_trace("Disconnecting %s", lrm_state->node_name);
+
+ remote_proxy_disconnect_by_node(lrm_state->node_name);
+
+ ((lrmd_t *) lrm_state->conn)->cmds->disconnect(lrm_state->conn);
+
+ if (!pcmk_is_set(controld_globals.fsa_input_register, R_SHUTDOWN)) {
+ removed = g_hash_table_foreach_remove(lrm_state->active_ops,
+ fail_pending_op, lrm_state);
+ crm_trace("Synthesized %d operation failures for %s", removed, lrm_state->node_name);
+ }
+}
+
+void
+lrm_state_disconnect(lrm_state_t * lrm_state)
+{
+ if (!lrm_state->conn) {
+ return;
+ }
+
+ lrm_state_disconnect_only(lrm_state);
+
+ lrmd_api_delete(lrm_state->conn);
+ lrm_state->conn = NULL;
+}
+
+int
+lrm_state_is_connected(lrm_state_t * lrm_state)
+{
+ if (!lrm_state->conn) {
+ return FALSE;
+ }
+ return ((lrmd_t *) lrm_state->conn)->cmds->is_connected(lrm_state->conn);
+}
+
+int
+lrm_state_poke_connection(lrm_state_t * lrm_state)
+{
+
+ if (!lrm_state->conn) {
+ return -ENOTCONN;
+ }
+ return ((lrmd_t *) lrm_state->conn)->cmds->poke_connection(lrm_state->conn);
+}
+
+// \return Standard Pacemaker return code
+int
+controld_connect_local_executor(lrm_state_t *lrm_state)
+{
+ int rc = pcmk_rc_ok;
+
+ if (lrm_state->conn == NULL) {
+ lrmd_t *api = NULL;
+
+ rc = lrmd__new(&api, NULL, NULL, 0);
+ if (rc != pcmk_rc_ok) {
+ return rc;
+ }
+ api->cmds->set_callback(api, lrm_op_callback);
+ lrm_state->conn = api;
+ }
+
+ rc = ((lrmd_t *) lrm_state->conn)->cmds->connect(lrm_state->conn,
+ CRM_SYSTEM_CRMD, NULL);
+ rc = pcmk_legacy2rc(rc);
+
+ if (rc == pcmk_rc_ok) {
+ lrm_state->num_lrm_register_fails = 0;
+ } else {
+ lrm_state->num_lrm_register_fails++;
+ }
+ return rc;
+}
+
+static remote_proxy_t *
+crmd_remote_proxy_new(lrmd_t *lrmd, const char *node_name, const char *session_id, const char *channel)
+{
+ struct ipc_client_callbacks proxy_callbacks = {
+ .dispatch = remote_proxy_dispatch,
+ .destroy = remote_proxy_disconnected
+ };
+ remote_proxy_t *proxy = remote_proxy_new(lrmd, &proxy_callbacks, node_name,
+ session_id, channel);
+ return proxy;
+}
+
+gboolean
+crmd_is_proxy_session(const char *session)
+{
+ return g_hash_table_lookup(proxy_table, session) ? TRUE : FALSE;
+}
+
+void
+crmd_proxy_send(const char *session, xmlNode *msg)
+{
+ remote_proxy_t *proxy = g_hash_table_lookup(proxy_table, session);
+ lrm_state_t *lrm_state = NULL;
+
+ if (!proxy) {
+ return;
+ }
+ crm_log_xml_trace(msg, "to-proxy");
+ lrm_state = lrm_state_find(proxy->node_name);
+ if (lrm_state) {
+ crm_trace("Sending event to %.8s on %s", proxy->session_id, proxy->node_name);
+ remote_proxy_relay_event(proxy, msg);
+ }
+}
+
+static void
+crmd_proxy_dispatch(const char *session, xmlNode *msg)
+{
+ crm_trace("Processing proxied IPC message from session %s", session);
+ crm_log_xml_trace(msg, "controller[inbound]");
+ crm_xml_add(msg, F_CRM_SYS_FROM, session);
+ if (controld_authorize_ipc_message(msg, NULL, session)) {
+ route_message(C_IPC_MESSAGE, msg);
+ }
+ controld_trigger_fsa();
+}
+
+static void
+remote_config_check(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
+{
+ if (rc != pcmk_ok) {
+ crm_err("Query resulted in an error: %s", pcmk_strerror(rc));
+
+ if (rc == -EACCES || rc == -pcmk_err_schema_validation) {
+ crm_err("The cluster is mis-configured - shutting down and staying down");
+ }
+
+ } else {
+ lrmd_t * lrmd = (lrmd_t *)user_data;
+ crm_time_t *now = crm_time_new(NULL);
+ GHashTable *config_hash = pcmk__strkey_table(free, free);
+
+ crm_debug("Call %d : Parsing CIB options", call_id);
+
+ pe_unpack_nvpairs(output, output, XML_CIB_TAG_PROPSET, NULL,
+ config_hash, CIB_OPTIONS_FIRST, FALSE, now, NULL);
+
+ /* Now send it to the remote peer */
+ lrmd__validate_remote_settings(lrmd, config_hash);
+
+ g_hash_table_destroy(config_hash);
+ crm_time_free(now);
+ }
+}
+
+static void
+crmd_remote_proxy_cb(lrmd_t *lrmd, void *userdata, xmlNode *msg)
+{
+ lrm_state_t *lrm_state = userdata;
+ const char *session = crm_element_value(msg, F_LRMD_IPC_SESSION);
+ remote_proxy_t *proxy = g_hash_table_lookup(proxy_table, session);
+
+ const char *op = crm_element_value(msg, F_LRMD_IPC_OP);
+ if (pcmk__str_eq(op, LRMD_IPC_OP_NEW, pcmk__str_casei)) {
+ const char *channel = crm_element_value(msg, F_LRMD_IPC_IPC_SERVER);
+
+ proxy = crmd_remote_proxy_new(lrmd, lrm_state->node_name, session, channel);
+ if (!remote_ra_controlling_guest(lrm_state)) {
+ if (proxy != NULL) {
+ cib_t *cib_conn = controld_globals.cib_conn;
+
+ /* Look up stonith-watchdog-timeout and send to the remote peer for validation */
+ int rc = cib_conn->cmds->query(cib_conn, XML_CIB_TAG_CRMCONFIG,
+ NULL, cib_scope_local);
+ cib_conn->cmds->register_callback_full(cib_conn, rc, 10, FALSE,
+ lrmd,
+ "remote_config_check",
+ remote_config_check,
+ NULL);
+ }
+ } else {
+ crm_debug("Skipping remote_config_check for guest-nodes");
+ }
+
+ } else if (pcmk__str_eq(op, LRMD_IPC_OP_SHUTDOWN_REQ, pcmk__str_casei)) {
+ char *now_s = NULL;
+
+ crm_notice("%s requested shutdown of its remote connection",
+ lrm_state->node_name);
+
+ if (!remote_ra_is_in_maintenance(lrm_state)) {
+ now_s = pcmk__ttoa(time(NULL));
+ update_attrd(lrm_state->node_name, XML_CIB_ATTR_SHUTDOWN, now_s, NULL, TRUE);
+ free(now_s);
+
+ remote_proxy_ack_shutdown(lrmd);
+
+ crm_warn("Reconnection attempts to %s may result in failures that must be cleared",
+ lrm_state->node_name);
+ } else {
+ remote_proxy_nack_shutdown(lrmd);
+
+ crm_notice("Remote resource for %s is not managed so no ordered shutdown happening",
+ lrm_state->node_name);
+ }
+ return;
+
+ } else if (pcmk__str_eq(op, LRMD_IPC_OP_REQUEST, pcmk__str_casei) && proxy && proxy->is_local) {
+ /* This is for the controller, which we are, so don't try
+ * to send to ourselves over IPC -- do it directly.
+ */
+ int flags = 0;
+ xmlNode *request = get_message_xml(msg, F_LRMD_IPC_MSG);
+
+ CRM_CHECK(request != NULL, return);
+ CRM_CHECK(lrm_state->node_name, return);
+ crm_xml_add(request, XML_ACL_TAG_ROLE, "pacemaker-remote");
+ pcmk__update_acl_user(request, F_LRMD_IPC_USER, lrm_state->node_name);
+
+ /* Pacemaker Remote nodes don't know their own names (as known to the
+ * cluster). When getting a node info request with no name or ID, add
+ * the name, so we don't return info for ourselves instead of the
+ * Pacemaker Remote node.
+ */
+ if (pcmk__str_eq(crm_element_value(request, F_CRM_TASK), CRM_OP_NODE_INFO, pcmk__str_casei)) {
+ int node_id = 0;
+
+ crm_element_value_int(request, XML_ATTR_ID, &node_id);
+ if ((node_id <= 0)
+ && (crm_element_value(request, XML_ATTR_UNAME) == NULL)) {
+ crm_xml_add(request, XML_ATTR_UNAME, lrm_state->node_name);
+ }
+ }
+
+ crmd_proxy_dispatch(session, request);
+
+ crm_element_value_int(msg, F_LRMD_IPC_MSG_FLAGS, &flags);
+ if (flags & crm_ipc_client_response) {
+ int msg_id = 0;
+ xmlNode *op_reply = create_xml_node(NULL, "ack");
+
+ crm_xml_add(op_reply, "function", __func__);
+ crm_xml_add_int(op_reply, "line", __LINE__);
+
+ crm_element_value_int(msg, F_LRMD_IPC_MSG_ID, &msg_id);
+ remote_proxy_relay_response(proxy, op_reply, msg_id);
+
+ free_xml(op_reply);
+ }
+
+ } else {
+ remote_proxy_cb(lrmd, lrm_state->node_name, msg);
+ }
+}
+
+
+// \return Standard Pacemaker return code
+int
+controld_connect_remote_executor(lrm_state_t *lrm_state, const char *server,
+ int port, int timeout_ms)
+{
+ int rc = pcmk_rc_ok;
+
+ if (lrm_state->conn == NULL) {
+ lrmd_t *api = NULL;
+
+ rc = lrmd__new(&api, lrm_state->node_name, server, port);
+ if (rc != pcmk_rc_ok) {
+ crm_warn("Pacemaker Remote connection to %s:%s failed: %s "
+ CRM_XS " rc=%d", server, port, pcmk_rc_str(rc), rc);
+
+ return rc;
+ }
+ lrm_state->conn = api;
+ api->cmds->set_callback(api, remote_lrm_op_callback);
+ lrmd_internal_set_proxy_callback(api, lrm_state, crmd_remote_proxy_cb);
+ }
+
+ crm_trace("Initiating remote connection to %s:%d with timeout %dms",
+ server, port, timeout_ms);
+ rc = ((lrmd_t *) lrm_state->conn)->cmds->connect_async(lrm_state->conn,
+ lrm_state->node_name,
+ timeout_ms);
+ if (rc == pcmk_ok) {
+ lrm_state->num_lrm_register_fails = 0;
+ } else {
+ lrm_state->num_lrm_register_fails++; // Ignored for remote connections
+ }
+ return pcmk_legacy2rc(rc);
+}
+
+int
+lrm_state_get_metadata(lrm_state_t * lrm_state,
+ const char *class,
+ const char *provider,
+ const char *agent, char **output, enum lrmd_call_options options)
+{
+ lrmd_key_value_t *params = NULL;
+
+ if (!lrm_state->conn) {
+ return -ENOTCONN;
+ }
+
+ /* Add the node name to the environment, as is done with normal resource
+ * action calls. Meta-data calls shouldn't need it, but some agents are
+ * written with an ocf_local_nodename call at the beginning regardless of
+ * action. Without the environment variable, the agent would try to contact
+ * the controller to get the node name -- but the controller would be
+ * blocking on the synchronous meta-data call.
+ *
+ * At this point, we have to assume that agents are unlikely to make other
+ * calls that require the controller, such as crm_node --quorum or
+ * --cluster-id.
+ *
+ * @TODO Make meta-data calls asynchronous. (This will be part of a larger
+ * project to make meta-data calls via the executor rather than directly.)
+ */
+ params = lrmd_key_value_add(params, CRM_META "_" XML_LRM_ATTR_TARGET,
+ lrm_state->node_name);
+
+ return ((lrmd_t *) lrm_state->conn)->cmds->get_metadata_params(lrm_state->conn,
+ class, provider, agent, output, options, params);
+}
+
+int
+lrm_state_cancel(lrm_state_t *lrm_state, const char *rsc_id, const char *action,
+ guint interval_ms)
+{
+ if (!lrm_state->conn) {
+ return -ENOTCONN;
+ }
+
+ /* Figure out a way to make this async?
+ * NOTICE: Currently it's synced and directly acknowledged in do_lrm_invoke(). */
+ if (is_remote_lrmd_ra(NULL, NULL, rsc_id)) {
+ return remote_ra_cancel(lrm_state, rsc_id, action, interval_ms);
+ }
+ return ((lrmd_t *) lrm_state->conn)->cmds->cancel(lrm_state->conn, rsc_id,
+ action, interval_ms);
+}
+
+lrmd_rsc_info_t *
+lrm_state_get_rsc_info(lrm_state_t * lrm_state, const char *rsc_id, enum lrmd_call_options options)
+{
+ lrmd_rsc_info_t *rsc = NULL;
+
+ if (!lrm_state->conn) {
+ return NULL;
+ }
+ if (is_remote_lrmd_ra(NULL, NULL, rsc_id)) {
+ return remote_ra_get_rsc_info(lrm_state, rsc_id);
+ }
+
+ rsc = g_hash_table_lookup(lrm_state->rsc_info_cache, rsc_id);
+ if (rsc == NULL) {
+ /* only contact the lrmd if we don't already have a cached rsc info */
+ rsc = ((lrmd_t *) lrm_state->conn)->cmds->get_rsc_info(lrm_state->conn, rsc_id, options);
+ if (rsc == NULL) {
+ return NULL;
+ }
+ /* cache the result */
+ g_hash_table_insert(lrm_state->rsc_info_cache, rsc->id, rsc);
+ }
+
+ return lrmd_copy_rsc_info(rsc);
+
+}
+
+/*!
+ * \internal
+ * \brief Initiate a resource agent action
+ *
+ * \param[in,out] lrm_state Executor state object
+ * \param[in] rsc_id ID of resource for action
+ * \param[in] action Action to execute
+ * \param[in] userdata String to copy and pass to execution callback
+ * \param[in] interval_ms Action interval (in milliseconds)
+ * \param[in] timeout_ms Action timeout (in milliseconds)
+ * \param[in] start_delay_ms Delay (in ms) before initiating action
+ * \param[in] parameters Hash table of resource parameters
+ * \param[out] call_id Where to store call ID on success
+ *
+ * \return Standard Pacemaker return code
+ */
+int
+controld_execute_resource_agent(lrm_state_t *lrm_state, const char *rsc_id,
+ const char *action, const char *userdata,
+ guint interval_ms, int timeout_ms,
+ int start_delay_ms, GHashTable *parameters,
+ int *call_id)
+{
+ int rc = pcmk_rc_ok;
+ lrmd_key_value_t *params = NULL;
+
+ if (lrm_state->conn == NULL) {
+ return ENOTCONN;
+ }
+
+ // Convert parameters from hash table to list
+ if (parameters != NULL) {
+ const char *key = NULL;
+ const char *value = NULL;
+ GHashTableIter iter;
+
+ g_hash_table_iter_init(&iter, parameters);
+ while (g_hash_table_iter_next(&iter, (gpointer *) &key,
+ (gpointer *) &value)) {
+ params = lrmd_key_value_add(params, key, value);
+ }
+ }
+
+ if (is_remote_lrmd_ra(NULL, NULL, rsc_id)) {
+ rc = controld_execute_remote_agent(lrm_state, rsc_id, action,
+ userdata, interval_ms, timeout_ms,
+ start_delay_ms, params, call_id);
+
+ } else {
+ rc = ((lrmd_t *) lrm_state->conn)->cmds->exec(lrm_state->conn, rsc_id,
+ action, userdata,
+ interval_ms, timeout_ms,
+ start_delay_ms,
+ lrmd_opt_notify_changes_only,
+ params);
+ if (rc < 0) {
+ rc = pcmk_legacy2rc(rc);
+ } else {
+ *call_id = rc;
+ rc = pcmk_rc_ok;
+ }
+ }
+ return rc;
+}
+
+int
+lrm_state_register_rsc(lrm_state_t * lrm_state,
+ const char *rsc_id,
+ const char *class,
+ const char *provider, const char *agent, enum lrmd_call_options options)
+{
+ lrmd_t *conn = (lrmd_t *) lrm_state->conn;
+
+ if (conn == NULL) {
+ return -ENOTCONN;
+ }
+
+ if (is_remote_lrmd_ra(agent, provider, NULL)) {
+ return lrm_state_find_or_create(rsc_id)? pcmk_ok : -EINVAL;
+ }
+
+ /* @TODO Implement an asynchronous version of this (currently a blocking
+ * call to the lrmd).
+ */
+ return conn->cmds->register_rsc(lrm_state->conn, rsc_id, class, provider,
+ agent, options);
+}
+
+int
+lrm_state_unregister_rsc(lrm_state_t * lrm_state,
+ const char *rsc_id, enum lrmd_call_options options)
+{
+ if (!lrm_state->conn) {
+ return -ENOTCONN;
+ }
+
+ if (is_remote_lrmd_ra(NULL, NULL, rsc_id)) {
+ lrm_state_destroy(rsc_id);
+ return pcmk_ok;
+ }
+
+ g_hash_table_remove(lrm_state->rsc_info_cache, rsc_id);
+
+ /* @TODO Optimize this ... this function is a blocking round trip from
+ * client to daemon. The controld_execd_state.c code path that uses this
+ * function should always treat it as an async operation. The executor API
+ * should make an async version available.
+ */
+ return ((lrmd_t *) lrm_state->conn)->cmds->unregister_rsc(lrm_state->conn, rsc_id, options);
+}