Page MenuHomeClusterLabs Projects

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/daemons/attrd/attrd_alerts.c b/daemons/attrd/attrd_alerts.c
index df7cee1ebe..cf2caf5238 100644
--- a/daemons/attrd/attrd_alerts.c
+++ b/daemons/attrd/attrd_alerts.c
@@ -1,146 +1,146 @@
/*
- * Copyright 2015-2019 the Pacemaker project contributors
+ * Copyright 2015-2020 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/internal.h>
#include <crm/msg_xml.h>
#include <crm/cluster/internal.h>
#include <crm/cluster/election.h>
#include <crm/common/alerts_internal.h>
#include <crm/pengine/rules_internal.h>
#include <crm/lrmd_internal.h>
#include "pacemaker-attrd.h"
static GListPtr attrd_alert_list = NULL;
static void
attrd_lrmd_callback(lrmd_event_data_t * op)
{
CRM_CHECK(op != NULL, return);
switch (op->type) {
case lrmd_event_disconnect:
crm_info("Lost connection to executor");
attrd_lrmd_disconnect();
break;
default:
break;
}
}
static lrmd_t *
attrd_lrmd_connect()
{
if (the_lrmd == NULL) {
the_lrmd = lrmd_api_new();
the_lrmd->cmds->set_callback(the_lrmd, attrd_lrmd_callback);
}
if (!the_lrmd->cmds->is_connected(the_lrmd)) {
const unsigned int max_attempts = 10;
int ret = -ENOTCONN;
for (int fails = 0; fails < max_attempts; ++fails) {
ret = the_lrmd->cmds->connect(the_lrmd, T_ATTRD, NULL);
if (ret == pcmk_ok) {
break;
}
crm_debug("Could not connect to executor, %d tries remaining",
(max_attempts - fails));
/* @TODO We don't want to block here with sleep, but we should wait
* some time between connection attempts. We could possibly add a
* timer with a callback, but then we'd likely need an alert queue.
*/
}
if (ret != pcmk_ok) {
attrd_lrmd_disconnect();
}
}
return the_lrmd;
}
void
attrd_lrmd_disconnect() {
if (the_lrmd) {
lrmd_t *conn = the_lrmd;
the_lrmd = NULL; /* in case we're called recursively */
lrmd_api_delete(conn); /* will disconnect if necessary */
}
}
static void
config_query_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
{
xmlNode *crmalerts = NULL;
if (rc == -ENXIO) {
crm_debug("Local CIB has no alerts section");
return;
} else if (rc != pcmk_ok) {
crm_notice("Could not query local CIB: %s", pcmk_strerror(rc));
return;
}
crmalerts = output;
if (crmalerts && !crm_str_eq(crm_element_name(crmalerts),
XML_CIB_TAG_ALERTS, TRUE)) {
crmalerts = first_named_child(crmalerts, XML_CIB_TAG_ALERTS);
}
if (!crmalerts) {
crm_notice("CIB query result has no " XML_CIB_TAG_ALERTS " section");
return;
}
pe_free_alert_list(attrd_alert_list);
attrd_alert_list = pe_unpack_alerts(crmalerts);
}
#define XPATH_ALERTS \
"/" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION "/" XML_CIB_TAG_ALERTS
gboolean
attrd_read_options(gpointer user_data)
{
int call_id;
CRM_CHECK(the_cib != NULL, return TRUE);
call_id = the_cib->cmds->query(the_cib, XPATH_ALERTS, NULL,
cib_xpath | cib_scope_local);
the_cib->cmds->register_callback_full(the_cib, call_id, 120, FALSE, NULL,
"config_query_callback",
config_query_callback, free);
crm_trace("Querying the CIB... call %d", call_id);
return TRUE;
}
void
attrd_cib_updated_cb(const char *event, xmlNode * msg)
{
- if (!attrd_shutting_down() && crm_patchset_contains_alert(msg, FALSE)) {
+ if (!attrd_shutting_down() && pcmk__alert_in_patchset(msg, FALSE)) {
mainloop_set_trigger(attrd_config_read);
}
}
int
attrd_send_attribute_alert(const char *node, int nodeid,
const char *attr, const char *value)
{
if (attrd_alert_list == NULL) {
return pcmk_ok;
}
return lrmd_send_attribute_alert(attrd_lrmd_connect(), attrd_alert_list,
node, nodeid, attr, value);
}
diff --git a/daemons/attrd/pacemaker-attrd.c b/daemons/attrd/pacemaker-attrd.c
index 15c6a8075e..141ed8277f 100644
--- a/daemons/attrd/pacemaker-attrd.c
+++ b/daemons/attrd/pacemaker-attrd.c
@@ -1,441 +1,441 @@
/*
* Copyright 2013-2019 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/cib/internal.h>
#include <crm/msg_xml.h>
#include <crm/pengine/rules.h>
#include <crm/common/iso8601.h>
#include <crm/common/ipc.h>
#include <crm/common/ipcs.h>
#include <crm/common/xml.h>
#include <crm/cluster/internal.h>
-#include <crm/attrd.h>
+#include <crm/common/attrd_internal.h>
#include "pacemaker-attrd.h"
lrmd_t *the_lrmd = NULL;
crm_cluster_t *attrd_cluster = NULL;
crm_trigger_t *attrd_config_read = NULL;
static crm_exit_t attrd_exit_status = CRM_EX_OK;
static void
attrd_cpg_dispatch(cpg_handle_t handle,
const struct cpg_name *groupName,
uint32_t nodeid, uint32_t pid, void *msg, size_t msg_len)
{
uint32_t kind = 0;
xmlNode *xml = NULL;
const char *from = NULL;
char *data = pcmk_message_common_cs(handle, nodeid, pid, msg, &kind, &from);
if(data == NULL) {
return;
}
if (kind == crm_class_cluster) {
xml = string2xml(data);
}
if (xml == NULL) {
crm_err("Bad message of class %d received from %s[%u]: '%.120s'", kind, from, nodeid, data);
} else {
crm_node_t *peer = crm_get_peer(nodeid, from);
attrd_peer_message(peer, xml);
}
free_xml(xml);
free(data);
}
static void
attrd_cpg_destroy(gpointer unused)
{
if (attrd_shutting_down()) {
crm_info("Corosync disconnection complete");
} else {
crm_crit("Lost connection to cluster layer, shutting down");
attrd_exit_status = CRM_EX_DISCONNECT;
attrd_shutdown(0);
}
}
static void
attrd_cib_replaced_cb(const char *event, xmlNode * msg)
{
if (attrd_shutting_down()) {
return;
}
if (attrd_election_won()) {
crm_notice("Updating all attributes after %s event", event);
write_attributes(TRUE, FALSE);
}
// Check for changes in alerts
mainloop_set_trigger(attrd_config_read);
}
static void
attrd_cib_destroy_cb(gpointer user_data)
{
cib_t *conn = user_data;
conn->cmds->signoff(conn); /* Ensure IPC is cleaned up */
if (attrd_shutting_down()) {
crm_info("Connection disconnection complete");
} else {
/* eventually this should trigger a reconnect, not a shutdown */
crm_crit("Lost connection to the CIB manager, shutting down");
attrd_exit_status = CRM_EX_DISCONNECT;
attrd_shutdown(0);
}
return;
}
static void
attrd_erase_cb(xmlNode *msg, int call_id, int rc, xmlNode *output,
void *user_data)
{
do_crm_log_unlikely((rc? LOG_NOTICE : LOG_DEBUG),
"Cleared transient attributes: %s "
CRM_XS " xpath=%s rc=%d",
pcmk_strerror(rc), (char *) user_data, rc);
}
#define XPATH_TRANSIENT "//node_state[@uname='%s']/" XML_TAG_TRANSIENT_NODEATTRS
/*!
* \internal
* \brief Wipe all transient attributes for this node from the CIB
*
* Clear any previous transient node attributes from the CIB. This is
* normally done by the DC's controller when this node leaves the cluster, but
* this handles the case where the node restarted so quickly that the
* cluster layer didn't notice.
*
* \todo If pacemaker-attrd respawns after crashing (see PCMK_respawned),
* ideally we'd skip this and sync our attributes from the writer.
* However, currently we reject any values for us that the writer has, in
* attrd_peer_update().
*/
static void
attrd_erase_attrs()
{
int call_id;
char *xpath = crm_strdup_printf(XPATH_TRANSIENT, attrd_cluster->uname);
crm_info("Clearing transient attributes from CIB " CRM_XS " xpath=%s",
xpath);
call_id = the_cib->cmds->remove(the_cib, xpath, NULL,
cib_quorum_override | cib_xpath);
the_cib->cmds->register_callback_full(the_cib, call_id, 120, FALSE, xpath,
"attrd_erase_cb", attrd_erase_cb,
free);
}
static int
attrd_cib_connect(int max_retry)
{
static int attempts = 0;
int rc = -ENOTCONN;
the_cib = cib_new();
if (the_cib == NULL) {
return -ENOTCONN;
}
do {
if(attempts > 0) {
sleep(attempts);
}
attempts++;
crm_debug("Connection attempt %d to the CIB manager", attempts);
rc = the_cib->cmds->signon(the_cib, T_ATTRD, cib_command);
} while(rc != pcmk_ok && attempts < max_retry);
if (rc != pcmk_ok) {
crm_err("Connection to the CIB manager failed: %s " CRM_XS " rc=%d",
pcmk_strerror(rc), rc);
goto cleanup;
}
crm_debug("Connected to the CIB manager after %d attempts", attempts);
rc = the_cib->cmds->set_connection_dnotify(the_cib, attrd_cib_destroy_cb);
if (rc != pcmk_ok) {
crm_err("Could not set disconnection callback");
goto cleanup;
}
rc = the_cib->cmds->add_notify_callback(the_cib, T_CIB_REPLACE_NOTIFY, attrd_cib_replaced_cb);
if(rc != pcmk_ok) {
crm_err("Could not set CIB notification callback");
goto cleanup;
}
rc = the_cib->cmds->add_notify_callback(the_cib, T_CIB_DIFF_NOTIFY, attrd_cib_updated_cb);
if (rc != pcmk_ok) {
crm_err("Could not set CIB notification callback (update)");
goto cleanup;
}
return pcmk_ok;
cleanup:
the_cib->cmds->signoff(the_cib);
cib_delete(the_cib);
the_cib = NULL;
return -ENOTCONN;
}
/*!
* \internal
* \brief Prepare the CIB after cluster is connected
*/
static void
attrd_cib_init()
{
// We have no attribute values in memory, wipe the CIB to match
attrd_erase_attrs();
// Set a trigger for reading the CIB (for the alerts section)
attrd_config_read = mainloop_add_trigger(G_PRIORITY_HIGH, attrd_read_options, NULL);
// Always read the CIB at start-up
mainloop_set_trigger(attrd_config_read);
}
static qb_ipcs_service_t *ipcs = NULL;
static int32_t
attrd_ipc_dispatch(qb_ipcs_connection_t * c, void *data, size_t size)
{
uint32_t id = 0;
uint32_t flags = 0;
crm_client_t *client = crm_client_get(c);
xmlNode *xml = crm_ipcs_recv(client, data, size, &id, &flags);
const char *op;
if (xml == NULL) {
crm_debug("No msg from %d (%p)", crm_ipcs_client_pid(c), c);
return 0;
}
#if ENABLE_ACL
CRM_ASSERT(client->user != NULL);
crm_acl_get_set_user(xml, F_ATTRD_USER, client->user);
#endif
crm_trace("Processing msg from %d (%p)", crm_ipcs_client_pid(c), c);
crm_log_xml_trace(xml, __FUNCTION__);
op = crm_element_value(xml, F_ATTRD_TASK);
if (client->name == NULL) {
const char *value = crm_element_value(xml, F_ORIG);
client->name = crm_strdup_printf("%s.%d", value?value:"unknown", client->pid);
}
if (safe_str_eq(op, ATTRD_OP_PEER_REMOVE)) {
attrd_send_ack(client, id, flags);
attrd_client_peer_remove(client->name, xml);
} else if (safe_str_eq(op, ATTRD_OP_CLEAR_FAILURE)) {
attrd_send_ack(client, id, flags);
attrd_client_clear_failure(xml);
} else if (safe_str_eq(op, ATTRD_OP_UPDATE)) {
attrd_send_ack(client, id, flags);
attrd_client_update(xml);
} else if (safe_str_eq(op, ATTRD_OP_UPDATE_BOTH)) {
attrd_send_ack(client, id, flags);
attrd_client_update(xml);
} else if (safe_str_eq(op, ATTRD_OP_UPDATE_DELAY)) {
attrd_send_ack(client, id, flags);
attrd_client_update(xml);
} else if (safe_str_eq(op, ATTRD_OP_REFRESH)) {
attrd_send_ack(client, id, flags);
attrd_client_refresh();
} else if (safe_str_eq(op, ATTRD_OP_QUERY)) {
/* queries will get reply, so no ack is necessary */
attrd_client_query(client, id, flags, xml);
} else {
crm_info("Ignoring request from client %s with unknown operation %s",
client->name, op);
}
free_xml(xml);
return 0;
}
void
attrd_ipc_fini()
{
if (ipcs != NULL) {
crm_client_disconnect_all(ipcs);
qb_ipcs_destroy(ipcs);
ipcs = NULL;
}
}
static int
attrd_cluster_connect()
{
attrd_cluster = calloc(1, sizeof(crm_cluster_t));
attrd_cluster->destroy = attrd_cpg_destroy;
attrd_cluster->cpg.cpg_deliver_fn = attrd_cpg_dispatch;
attrd_cluster->cpg.cpg_confchg_fn = pcmk_cpg_membership;
crm_set_status_callback(&attrd_peer_change_cb);
if (crm_cluster_connect(attrd_cluster) == FALSE) {
crm_err("Cluster connection failed");
return -ENOTCONN;
}
return pcmk_ok;
}
/* *INDENT-OFF* */
static struct crm_option long_options[] = {
/* Top-level Options */
{"help", 0, 0, '?', "\tThis text"},
{"verbose", 0, 0, 'V', "\tIncrease debug output"},
{0, 0, 0, 0}
};
/* *INDENT-ON* */
int
main(int argc, char **argv)
{
int flag = 0;
int index = 0;
int argerr = 0;
crm_ipc_t *old_instance = NULL;
attrd_init_mainloop();
crm_log_preinit(NULL, argc, argv);
crm_set_options(NULL, "[options]", long_options,
"Daemon for aggregating and atomically storing node attribute updates into the CIB");
mainloop_add_signal(SIGTERM, attrd_shutdown);
while (1) {
flag = crm_get_option(argc, argv, &index);
if (flag == -1)
break;
switch (flag) {
case 'V':
crm_bump_log_level(argc, argv);
break;
case 'h': /* Help message */
crm_help(flag, CRM_EX_OK);
break;
default:
++argerr;
break;
}
}
if (optind > argc) {
++argerr;
}
if (argerr) {
crm_help('?', CRM_EX_USAGE);
}
crm_log_init(T_ATTRD, LOG_INFO, TRUE, FALSE, argc, argv, FALSE);
crm_notice("Starting Pacemaker node attribute manager");
old_instance = crm_ipc_new(T_ATTRD, 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-attrd 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;
}
attributes = g_hash_table_new_full(crm_str_hash, g_str_equal, NULL, free_attribute);
/* Connect to the CIB before connecting to the cluster or listening for IPC.
* This allows us to assume the CIB is connected whenever we process a
* cluster or IPC message (which also avoids start-up race conditions).
*/
if (attrd_cib_connect(10) != pcmk_ok) {
attrd_exit_status = CRM_EX_FATAL;
goto done;
}
crm_info("CIB connection active");
if (attrd_cluster_connect() != pcmk_ok) {
attrd_exit_status = CRM_EX_FATAL;
goto done;
}
crm_info("Cluster connection active");
// Initialization that requires the cluster to be connected
attrd_election_init();
attrd_cib_init();
/* Set a private attribute for ourselves with the protocol version we
* support. This lets all nodes determine the minimum supported version
* across all nodes. It also ensures that the writer learns our node name,
* so it can send our attributes to the CIB.
*/
attrd_broadcast_protocol();
attrd_init_ipc(&ipcs, attrd_ipc_dispatch);
crm_notice("Pacemaker node attribute manager successfully started and accepting connections");
attrd_run_mainloop();
done:
crm_info("Shutting down attribute manager");
attrd_election_fini();
attrd_ipc_fini();
attrd_lrmd_disconnect();
attrd_cib_disconnect();
g_hash_table_destroy(attributes);
crm_exit(attrd_exit_status);
}
diff --git a/daemons/controld/controld_attrd.c b/daemons/controld/controld_attrd.c
index 8ae6992c22..059e357fc4 100644
--- a/daemons/controld/controld_attrd.c
+++ b/daemons/controld/controld_attrd.c
@@ -1,184 +1,186 @@
/*
- * Copyright 2006-2019 the Pacemaker project contributors
+ * Copyright 2006-2020 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/attrd.h>
+#include <crm/common/attrd_internal.h>
#include <crm/msg_xml.h>
#include <pacemaker-controld.h>
static crm_ipc_t *attrd_ipc = NULL;
void
controld_close_attrd_ipc()
{
if (attrd_ipc) {
crm_trace("Closing connection to pacemaker-attrd");
crm_ipc_close(attrd_ipc);
crm_ipc_destroy(attrd_ipc);
attrd_ipc = NULL;
}
}
static void
log_attrd_error(const char *host, const char *name, const char *value,
gboolean is_remote, char command, int rc)
{
const char *node_type = (is_remote? "Pacemaker Remote" : "cluster");
gboolean shutting_down = is_set(fsa_input_register, R_SHUTDOWN);
const char *when = (shutting_down? " at shutdown" : "");
switch (command) {
case 0:
crm_err("Could not clear failure attributes for %s on %s node %s%s: %s "
CRM_XS " rc=%d", (name? name : "all resources"), node_type,
- host, when, pcmk_strerror(rc), rc);
+ host, when, pcmk_rc_str(rc), rc);
break;
case 'C':
crm_err("Could not purge %s node %s in attribute manager%s: %s "
CRM_XS " rc=%d",
- node_type, host, when, pcmk_strerror(rc), rc);
+ node_type, host, when, pcmk_rc_str(rc), rc);
break;
case 'U':
/* We weren't able to update an attribute after several retries,
* so something is horribly wrong with the attribute manager or the
* underlying system.
*/
do_crm_log(AM_I_DC? LOG_CRIT : LOG_ERR,
"Could not update attribute %s=%s for %s node %s%s: %s "
CRM_XS " rc=%d", name, value, node_type, host, when,
- pcmk_strerror(rc), rc);
+ pcmk_rc_str(rc), rc);
if (AM_I_DC) {
/* We are unable to provide accurate information to the
* scheduler, so allow another node to take over DC.
* @TODO Should we do this unconditionally on any failure?
*/
crmd_exit(CRM_EX_FATAL);
} else if (shutting_down) {
// Fast-track shutdown since unable to request via attribute
register_fsa_input(C_FSA_INTERNAL, I_FAIL, NULL);
}
break;
}
}
static void
update_attrd_helper(const char *host, const char *name, const char *value,
const char *interval_spec, const char *user_name,
gboolean is_remote_node, char command)
{
int rc;
- int attrd_opts = attrd_opt_none;
+ int attrd_opts = pcmk__node_attr_none;
if (is_remote_node) {
- attrd_opts |= attrd_opt_remote;
+ attrd_opts |= pcmk__node_attr_remote;
}
if (attrd_ipc == NULL) {
attrd_ipc = crm_ipc_new(T_ATTRD, 0);
}
for (int attempt = 1; attempt <= 4; ++attempt) {
- rc = pcmk_ok;
+ rc = pcmk_rc_ok;
// If we're not already connected, try to connect
if (crm_ipc_connected(attrd_ipc) == FALSE) {
if (attempt == 1) {
// Start with a clean slate
crm_ipc_close(attrd_ipc);
}
if (crm_ipc_connect(attrd_ipc) == FALSE) {
rc = errno;
}
crm_debug("Attribute manager connection attempt %d of 4: %s (%d)",
- attempt, pcmk_strerror(rc), rc);
+ attempt, pcmk_rc_str(rc), rc);
}
- if (rc == pcmk_ok) {
- rc = command?
- attrd_update_delegate(attrd_ipc, command, host, name, value,
- XML_CIB_TAG_STATUS, NULL, NULL,
- user_name, attrd_opts)
-
+ if (rc == pcmk_rc_ok) {
+ if (command) {
+ rc = pcmk__node_attr_request(attrd_ipc, command, host, name,
+ value, XML_CIB_TAG_STATUS, NULL,
+ NULL, user_name, attrd_opts);
+ } else {
/* No command means clear fail count (name/value is really
* resource/operation)
*/
- : attrd_clear_delegate(attrd_ipc, host, name, value,
- interval_spec, user_name, attrd_opts);
+ rc = pcmk__node_attr_request_clear(attrd_ipc, host, name,
+ value, interval_spec,
+ user_name, attrd_opts);
+ }
crm_debug("Attribute manager request attempt %d of 4: %s (%d)",
- attempt, pcmk_strerror(rc), rc);
+ attempt, pcmk_rc_str(rc), rc);
}
- if (rc == pcmk_ok) {
+ if (rc == pcmk_rc_ok) {
// Success, we're done
break;
} else if ((rc != EAGAIN) && (rc != EALREADY)) {
/* EAGAIN or EALREADY indicates a temporary block, so just try
* again. Otherwise, close the connection for a clean slate.
*/
crm_ipc_close(attrd_ipc);
}
/* @TODO If the attribute manager remains unavailable the entire time,
* this function takes more than 6 seconds. Maybe set a timer for
* retries, to let the main loop do other work.
*/
if (attempt < 4) {
sleep(attempt);
}
}
- if (rc != pcmk_ok) {
+ if (rc != pcmk_rc_ok) {
log_attrd_error(host, name, value, is_remote_node, command, rc);
}
}
void
update_attrd(const char *host, const char *name, const char *value,
const char *user_name, gboolean is_remote_node)
{
update_attrd_helper(host, name, value, NULL, user_name, is_remote_node,
'U');
}
void
update_attrd_remote_node_removed(const char *host, const char *user_name)
{
crm_trace("Asking attribute manager to purge Pacemaker Remote node %s",
host);
update_attrd_helper(host, NULL, NULL, NULL, user_name, TRUE, 'C');
}
void
update_attrd_clear_failures(const char *host, const char *rsc, const char *op,
const char *interval_spec, gboolean is_remote_node)
{
const char *op_desc = NULL;
const char *interval_desc = NULL;
const char *node_type = is_remote_node? "Pacemaker Remote" : "cluster";
if (op) {
interval_desc = interval_spec? interval_spec : "nonrecurring";
op_desc = op;
} else {
interval_desc = "all";
op_desc = "operations";
}
crm_info("Asking pacemaker-attrd to clear failure of %s %s for %s on %s node %s",
interval_desc, op_desc, rsc, node_type, host);
update_attrd_helper(host, rsc, op, interval_spec, NULL, is_remote_node, 0);
}
diff --git a/daemons/controld/controld_based.c b/daemons/controld/controld_based.c
index 008a02d5f4..42e321fa06 100644
--- a/daemons/controld/controld_based.c
+++ b/daemons/controld/controld_based.c
@@ -1,245 +1,245 @@
/*
- * Copyright 2004-2019 the Pacemaker project contributors
+ * Copyright 2004-2020 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 <unistd.h> /* sleep */
#include <crm/common/alerts_internal.h>
#include <crm/common/xml.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <pacemaker-controld.h>
int cib_retries = 0;
static void
do_cib_updated(const char *event, xmlNode * msg)
{
- if (crm_patchset_contains_alert(msg, TRUE)) {
+ if (pcmk__alert_in_patchset(msg, TRUE)) {
mainloop_set_trigger(config_read);
}
}
static void
do_cib_replaced(const char *event, xmlNode * msg)
{
crm_debug("Updating the CIB after a replace: DC=%s", AM_I_DC ? "true" : "false");
if (AM_I_DC == FALSE) {
return;
} else if (fsa_state == S_FINALIZE_JOIN && is_set(fsa_input_register, R_CIB_ASKED)) {
/* no need to restart the join - we asked for this replace op */
return;
}
/* start the join process again so we get everyone's LRM status */
populate_cib_nodes(node_update_quick|node_update_all, __FUNCTION__);
register_fsa_input(C_FSA_INTERNAL, I_ELECTION, NULL);
}
/* A_CIB_STOP, A_CIB_START, O_CIB_RESTART */
void
do_cib_control(long long action,
enum crmd_fsa_cause cause,
enum crmd_fsa_state cur_state,
enum crmd_fsa_input current_input, fsa_data_t * msg_data)
{
CRM_ASSERT(fsa_cib_conn != NULL);
if (action & A_CIB_STOP) {
if (fsa_cib_conn->state != cib_disconnected && last_resource_update != 0) {
crm_info("Waiting for resource update %d to complete", last_resource_update);
crmd_fsa_stall(FALSE);
return;
}
crm_info("Disconnecting from the CIB manager");
clear_bit(fsa_input_register, R_CIB_CONNECTED);
fsa_cib_conn->cmds->del_notify_callback(fsa_cib_conn, T_CIB_DIFF_NOTIFY, do_cib_updated);
if (fsa_cib_conn->state != cib_disconnected) {
fsa_cib_conn->cmds->set_slave(fsa_cib_conn, cib_scope_local);
fsa_cib_conn->cmds->signoff(fsa_cib_conn);
}
crm_notice("Disconnected from the CIB manager");
}
if (action & A_CIB_START) {
int rc = pcmk_ok;
if (cur_state == S_STOPPING) {
crm_err("Ignoring request to connect to the CIB manager after shutdown");
return;
}
rc = fsa_cib_conn->cmds->signon(fsa_cib_conn, CRM_SYSTEM_CRMD, cib_command_nonblocking);
if (rc != pcmk_ok) {
/* a short wait that usually avoids stalling the FSA */
sleep(1);
rc = fsa_cib_conn->cmds->signon(fsa_cib_conn, CRM_SYSTEM_CRMD, cib_command_nonblocking);
}
if (rc != pcmk_ok) {
crm_info("Could not connect to the CIB manager: %s", pcmk_strerror(rc));
} else if (pcmk_ok !=
fsa_cib_conn->cmds->set_connection_dnotify(fsa_cib_conn,
crmd_cib_connection_destroy)) {
crm_err("Could not set dnotify callback");
} else if (pcmk_ok !=
fsa_cib_conn->cmds->add_notify_callback(fsa_cib_conn, T_CIB_REPLACE_NOTIFY,
do_cib_replaced)) {
crm_err("Could not set CIB notification callback (replace)");
} else if (pcmk_ok !=
fsa_cib_conn->cmds->add_notify_callback(fsa_cib_conn, T_CIB_DIFF_NOTIFY,
do_cib_updated)) {
crm_err("Could not set CIB notification callback (update)");
} else {
set_bit(fsa_input_register, R_CIB_CONNECTED);
cib_retries = 0;
}
if (is_not_set(fsa_input_register, R_CIB_CONNECTED)) {
cib_retries++;
crm_warn("Couldn't complete CIB registration %d"
" times... pause and retry", cib_retries);
if (cib_retries < 30) {
controld_start_timer(wait_timer);
crmd_fsa_stall(FALSE);
} else {
crm_err("Could not complete CIB"
" registration %d times..." " hard error", cib_retries);
register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
}
}
}
}
/*!
* \internal
* \brief Get CIB call options to use local scope if master unavailable
*
* \return CIB call options
*/
int crmd_cib_smart_opt()
{
int call_opt = cib_quorum_override;
if (fsa_state == S_ELECTION || fsa_state == S_PENDING) {
crm_info("Sending update to local CIB in state: %s", fsa_state2string(fsa_state));
call_opt |= cib_scope_local;
}
return call_opt;
}
/*!
* \internal
* \brief Check whether an action type should be recorded in the CIB
*
* \param[in] action Action type
*
* \return TRUE if action should be recorded, FALSE otherwise
*/
bool
controld_action_is_recordable(const char *action)
{
if (safe_str_eq(action, CRMD_ACTION_CANCEL)
|| safe_str_eq(action, CRMD_ACTION_DELETE)
|| safe_str_eq(action, CRMD_ACTION_NOTIFY)
|| safe_str_eq(action, CRMD_ACTION_METADATA)) {
return FALSE;
}
return TRUE;
}
static void
cib_delete_callback(xmlNode *msg, int call_id, int rc, xmlNode *output,
void *user_data)
{
char *desc = user_data;
if (rc == 0) {
crm_debug("Deletion of %s (via CIB call %d) succeeded", desc, call_id);
} else {
crm_warn("Deletion of %s (via CIB call %d) failed: %s " CRM_XS " rc=%d",
desc, call_id, pcmk_strerror(rc), rc);
}
}
// Searches for various portions of node_state to delete
// Match a particular node's node_state (takes node name 1x)
#define XPATH_NODE_STATE "//" XML_CIB_TAG_STATE "[@" XML_ATTR_UNAME "='%s']"
// Node's lrm section (name 1x)
#define XPATH_NODE_LRM XPATH_NODE_STATE "/" XML_CIB_TAG_LRM
// Node's transient_attributes section (name 1x)
#define XPATH_NODE_ATTRS XPATH_NODE_STATE "/" XML_TAG_TRANSIENT_NODEATTRS
// Everything under node_state (name 1x)
#define XPATH_NODE_ALL XPATH_NODE_STATE "/*"
/*!
* \internal
* \brief Delete subsection of a node's CIB node_state
*
* \param[in] uname Desired node
* \param[in] section Subsection of node_state to delete
* \param[in] options CIB call options to use
*/
void
controld_delete_node_state(const char *uname, enum controld_section_e section,
int options)
{
char *xpath = NULL;
char *desc = NULL;
CRM_CHECK(uname != NULL, return);
switch (section) {
case controld_section_lrm:
xpath = crm_strdup_printf(XPATH_NODE_LRM, uname);
desc = crm_strdup_printf("resource history for node %s", uname);
break;
case controld_section_attrs:
xpath = crm_strdup_printf(XPATH_NODE_ATTRS, uname);
desc = crm_strdup_printf("transient attributes for node %s", uname);
break;
case controld_section_all:
xpath = crm_strdup_printf(XPATH_NODE_ALL, uname);
desc = crm_strdup_printf("all state for node %s", uname);
break;
}
if (fsa_cib_conn == NULL) {
crm_warn("Unable to delete %s: no CIB connection", desc);
free(desc);
} else {
int call_id;
options |= cib_quorum_override|cib_xpath;
call_id = fsa_cib_conn->cmds->remove(fsa_cib_conn, xpath, NULL, options);
crm_info("Deleting %s (via CIB call %d) " CRM_XS " xpath=%s",
desc, call_id, xpath);
fsa_register_cib_callback(call_id, FALSE, desc, cib_delete_callback);
// CIB library handles freeing desc
}
free(xpath);
}
diff --git a/daemons/execd/execd_alerts.c b/daemons/execd/execd_alerts.c
index 994fb11c7d..d3d4c474c0 100644
--- a/daemons/execd/execd_alerts.c
+++ b/daemons/execd/execd_alerts.c
@@ -1,173 +1,175 @@
/*
- * Copyright 2016-2018 Andrew Beekhof <andrew@beekhof.net>
+ * Copyright 2016-2020 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 <glib.h>
#include <crm/crm.h>
#include <crm/services.h>
#include <crm/common/ipc.h>
#include <crm/common/ipcs.h>
#include <crm/common/alerts_internal.h>
#include <crm/msg_xml.h>
#include "pacemaker-execd.h"
/* Track in-flight alerts so we can wait for them at shutdown */
static GHashTable *inflight_alerts; /* key = call_id, value = timeout */
static gboolean draining_alerts = FALSE;
static inline void
add_inflight_alert(int call_id, int timeout)
{
if (inflight_alerts == NULL) {
inflight_alerts = g_hash_table_new(g_direct_hash, g_direct_equal);
}
g_hash_table_insert(inflight_alerts, GINT_TO_POINTER(call_id),
GINT_TO_POINTER(timeout));
}
static inline void
remove_inflight_alert(int call_id)
{
if (inflight_alerts != NULL) {
g_hash_table_remove(inflight_alerts, GINT_TO_POINTER(call_id));
}
}
static int
max_inflight_timeout()
{
GHashTableIter iter;
gpointer timeout;
int max_timeout = 0;
if (inflight_alerts) {
g_hash_table_iter_init(&iter, inflight_alerts);
while (g_hash_table_iter_next(&iter, NULL, &timeout)) {
if (GPOINTER_TO_INT(timeout) > max_timeout) {
max_timeout = GPOINTER_TO_INT(timeout);
}
}
}
return max_timeout;
}
struct alert_cb_s {
char *client_id;
int call_id;
};
static void
alert_complete(svc_action_t *action)
{
struct alert_cb_s *cb_data = (struct alert_cb_s *) (action->cb_data);
remove_inflight_alert(cb_data->call_id);
crm_debug("Alert pid %d for %s completed with rc=%d",
action->pid, cb_data->client_id, action->rc);
free(cb_data->client_id);
free(action->cb_data);
action->cb_data = NULL;
}
int
process_lrmd_alert_exec(crm_client_t *client, uint32_t id, xmlNode *request)
{
static int alert_sequence_no = 0;
xmlNode *alert_xml = get_xpath_object("//" F_LRMD_ALERT, request, LOG_ERR);
const char *alert_id = crm_element_value(alert_xml, F_LRMD_ALERT_ID);
const char *alert_path = crm_element_value(alert_xml, F_LRMD_ALERT_PATH);
svc_action_t *action = NULL;
int alert_timeout = 0;
int rc = pcmk_ok;
GHashTable *params = NULL;
struct alert_cb_s *cb_data = NULL;
if ((alert_id == NULL) || (alert_path == NULL)) {
return -EINVAL;
}
if (draining_alerts) {
return pcmk_ok;
}
crm_element_value_int(alert_xml, F_LRMD_TIMEOUT, &alert_timeout);
crm_info("Executing alert %s for %s", alert_id, client->id);
params = xml2list(alert_xml);
- crm_insert_alert_key_int(params, CRM_alert_node_sequence,
- ++alert_sequence_no);
+ pcmk__add_alert_key_int(params, PCMK__alert_key_node_sequence,
+ ++alert_sequence_no);
cb_data = calloc(1, sizeof(struct alert_cb_s));
CRM_CHECK(cb_data != NULL,
rc = -ENOMEM; goto err);
cb_data->client_id = strdup(client->id);
CRM_CHECK(cb_data->client_id != NULL,
rc = -ENOMEM; goto err);
crm_element_value_int(request, F_LRMD_CALLID, &(cb_data->call_id));
action = services_alert_create(alert_id, alert_path, alert_timeout, params,
alert_sequence_no, cb_data);
rc = services_action_user(action, CRM_DAEMON_USER);
if (rc < 0) {
goto err;
}
add_inflight_alert(cb_data->call_id, alert_timeout);
if (services_alert_async(action, alert_complete) == FALSE) {
services_action_free(action);
}
return pcmk_ok;
err:
if (cb_data) {
if (cb_data->client_id) {
free(cb_data->client_id);
}
free(cb_data);
}
if (action) {
services_action_free(action);
}
return rc;
}
static bool
drain_check(guint remaining_timeout_ms)
{
if (inflight_alerts != NULL) {
guint count = g_hash_table_size(inflight_alerts);
if (count > 0) {
crm_trace("%d alerts pending (%.3fs timeout remaining)",
count, remaining_timeout_ms / 1000.0);
return TRUE;
}
}
return FALSE;
}
void
lrmd_drain_alerts(GMainLoop *mloop)
{
if (inflight_alerts != NULL) {
guint timer_ms = max_inflight_timeout() + 5000;
crm_trace("Draining in-flight alerts (timeout %.3fs)",
timer_ms / 1000.0);
draining_alerts = TRUE;
pcmk_drain_main_loop(mloop, timer_ms, drain_check);
g_hash_table_destroy(inflight_alerts);
inflight_alerts = NULL;
}
}
diff --git a/include/crm/Makefile.am b/include/crm/Makefile.am
index 566e9feafc..8f11a56557 100644
--- a/include/crm/Makefile.am
+++ b/include/crm/Makefile.am
@@ -1,19 +1,19 @@
#
# Copyright 2004-2019 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.
#
MAINTAINERCLEANFILES = Makefile.in
headerdir=$(pkgincludedir)/crm
-header_HEADERS = attrd.h cib.h cluster.h compatibility.h crm.h \
+header_HEADERS = cib.h cluster.h compatibility.h crm.h \
lrmd.h msg_xml.h services.h stonith-ng.h
noinst_HEADERS = lrmd_internal.h
SUBDIRS = common pengine cib fencing cluster
diff --git a/include/crm/attrd.h b/include/crm/attrd.h
deleted file mode 100644
index 5ec1948670..0000000000
--- a/include/crm/attrd.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2004-2018 the Pacemaker project contributors
- *
- * The version control history for this file may have further details.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- */
-#ifndef CRM_ATTRD__H
-# define CRM_ATTRD__H
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-# include <crm/common/ipc.h>
-
-/* attribute options for clients to use with these functions */
-#define attrd_opt_none 0x000
-#define attrd_opt_remote 0x001
-#define attrd_opt_private 0x002
-
-const char *attrd_get_target(const char *name);
-
-int attrd_update_delegate(crm_ipc_t * ipc, char command, const char *host,
- const char *name, const char *value, const char *section,
- const char *set, const char *dampen, const char *user_name, int options);
-int attrd_clear_delegate(crm_ipc_t *ipc, const char *host, const char *resource,
- const char *operation, const char *interval_spec,
- const char *user_name, int options);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/include/crm/common/Makefile.am b/include/crm/common/Makefile.am
index 7c1c090129..4e77e50c34 100644
--- a/include/crm/common/Makefile.am
+++ b/include/crm/common/Makefile.am
@@ -1,18 +1,19 @@
#
# Copyright 2004-2019 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.
#
MAINTAINERCLEANFILES = Makefile.in
headerdir=$(pkgincludedir)/crm/common
header_HEADERS = xml.h ipc.h util.h iso8601.h mainloop.h logging.h results.h \
nvpair.h
noinst_HEADERS = cib_secrets.h ipcs.h internal.h alerts_internal.h \
iso8601_internal.h remote_internal.h xml_internal.h \
- ipc_internal.h output.h cmdline_internal.h curses_internal.h
+ ipc_internal.h output.h cmdline_internal.h curses_internal.h \
+ attrd_internal.h
diff --git a/include/crm/common/alerts_internal.h b/include/crm/common/alerts_internal.h
index 2cd2e638e7..e0fa655cbc 100644
--- a/include/crm/common/alerts_internal.h
+++ b/include/crm/common/alerts_internal.h
@@ -1,99 +1,92 @@
/*
- * Copyright 2015-2019 the Pacemaker project contributors
+ * Copyright 2015-2020 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 ALERT_INTERNAL_H
#define ALERT_INTERNAL_H
#include <glib.h>
#include <stdbool.h>
/* Default-Timeout to use before killing a alerts script (in milliseconds) */
-# define CRM_ALERT_DEFAULT_TIMEOUT_MS (30000)
+# define PCMK__ALERT_DEFAULT_TIMEOUT_MS (30000)
/* Default-Format-String used to pass timestamps to the alerts scripts */
-# define CRM_ALERT_DEFAULT_TSTAMP_FORMAT "%H:%M:%S.%06N"
+# define PCMK__ALERT_DEFAULT_TSTAMP_FORMAT "%H:%M:%S.%06N"
-enum crm_alert_flags {
- crm_alert_none = 0x0000,
- crm_alert_node = 0x0001,
- crm_alert_fencing = 0x0002,
- crm_alert_resource = 0x0004,
- crm_alert_attribute = 0x0008,
- crm_alert_default = crm_alert_node|crm_alert_fencing|crm_alert_resource
+enum pcmk__alert_flags {
+ pcmk__alert_none = 0,
+ pcmk__alert_node = (1 << 0),
+ pcmk__alert_fencing = (1 << 1),
+ pcmk__alert_resource = (1 << 2),
+ pcmk__alert_attribute = (1 << 3),
+ pcmk__alert_default = pcmk__alert_node|pcmk__alert_fencing|
+ pcmk__alert_resource,
};
typedef struct {
char *id;
char *path;
char *tstamp_format;
char *recipient;
char **select_attribute_name;
GHashTable *envvars;
int timeout;
uint32_t flags;
-} crm_alert_entry_t;
+} pcmk__alert_t;
-enum crm_alert_keys_e {
- CRM_alert_recipient = 0,
- CRM_alert_node,
- CRM_alert_nodeid,
- CRM_alert_rsc,
- CRM_alert_task,
- CRM_alert_interval,
- CRM_alert_desc,
- CRM_alert_status,
- CRM_alert_target_rc,
- CRM_alert_rc,
- CRM_alert_kind,
- CRM_alert_version,
- CRM_alert_node_sequence,
- CRM_alert_timestamp,
- CRM_alert_attribute_name,
- CRM_alert_attribute_value,
- CRM_alert_timestamp_epoch,
- CRM_alert_timestamp_usec,
- CRM_alert_exec_time,
- CRM_alert_select_kind,
- CRM_alert_select_attribute_name
+enum pcmk__alert_keys_e {
+ PCMK__alert_key_recipient = 0,
+ PCMK__alert_key_node,
+ PCMK__alert_key_nodeid,
+ PCMK__alert_key_rsc,
+ PCMK__alert_key_task,
+ PCMK__alert_key_interval,
+ PCMK__alert_key_desc,
+ PCMK__alert_key_status,
+ PCMK__alert_key_target_rc,
+ PCMK__alert_key_rc,
+ PCMK__alert_key_kind,
+ PCMK__alert_key_version,
+ PCMK__alert_key_node_sequence,
+ PCMK__alert_key_timestamp,
+ PCMK__alert_key_attribute_name,
+ PCMK__alert_key_attribute_value,
+ PCMK__alert_key_timestamp_epoch,
+ PCMK__alert_key_timestamp_usec,
+ PCMK__alert_key_exec_time,
+ PCMK__alert_key_select_kind,
+ PCMK__alert_key_select_attribute_name
};
-#define CRM_ALERT_INTERNAL_KEY_MAX 19
-#define CRM_ALERT_NODE_SEQUENCE "CRM_alert_node_sequence"
+#define PCMK__ALERT_INTERNAL_KEY_MAX 19
+#define PCMK__ALERT_NODE_SEQUENCE "CRM_alert_node_sequence"
-extern const char *crm_alert_keys[CRM_ALERT_INTERNAL_KEY_MAX][3];
+extern const char *pcmk__alert_keys[PCMK__ALERT_INTERNAL_KEY_MAX][3];
-crm_alert_entry_t *crm_dup_alert_entry(crm_alert_entry_t *entry);
-crm_alert_entry_t *crm_alert_entry_new(const char *id, const char *path);
-void crm_free_alert_entry(crm_alert_entry_t *entry);
-void crm_insert_alert_key(GHashTable *table, enum crm_alert_keys_e name,
- const char *value);
-void crm_insert_alert_key_int(GHashTable *table, enum crm_alert_keys_e name,
- int value);
-void crm_unset_alert_keys(void);
-void crm_set_envvar_list(crm_alert_entry_t *entry);
-void crm_unset_envvar_list(crm_alert_entry_t *entry);
-bool crm_patchset_contains_alert(xmlNode *msg, bool config);
+pcmk__alert_t *pcmk__dup_alert(pcmk__alert_t *entry);
+pcmk__alert_t *pcmk__alert_new(const char *id, const char *path);
+void pcmk__free_alert(pcmk__alert_t *entry);
+void pcmk__add_alert_key(GHashTable *table, enum pcmk__alert_keys_e name,
+ const char *value);
+void pcmk__add_alert_key_int(GHashTable *table, enum pcmk__alert_keys_e name,
+ int value);
+bool pcmk__alert_in_patchset(xmlNode *msg, bool config);
static inline const char *
-crm_alert_flag2text(enum crm_alert_flags flag)
+pcmk__alert_flag2text(enum pcmk__alert_flags flag)
{
switch (flag) {
- case crm_alert_node:
- return "node";
- case crm_alert_fencing:
- return "fencing";
- case crm_alert_resource:
- return "resource";
- case crm_alert_attribute:
- return "attribute";
- default:
- return "unknown";
+ case pcmk__alert_node: return "node";
+ case pcmk__alert_fencing: return "fencing";
+ case pcmk__alert_resource: return "resource";
+ case pcmk__alert_attribute: return "attribute";
+ default: return "unknown";
}
}
#endif
diff --git a/include/crm/common/attrd_internal.h b/include/crm/common/attrd_internal.h
new file mode 100644
index 0000000000..0aa5468848
--- /dev/null
+++ b/include/crm/common/attrd_internal.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2004-2020 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 CRM_ATTRD__H
+# define CRM_ATTRD__H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+# include <crm/common/ipc.h>
+
+// Options for clients to use with functions below
+enum pcmk__node_attr_opts {
+ pcmk__node_attr_none = 0,
+ pcmk__node_attr_remote = (1 << 0),
+ pcmk__node_attr_private = (1 << 1),
+};
+
+int pcmk__node_attr_request(crm_ipc_t * ipc, char command, const char *host,
+ const char *name, const char *value,
+ const char *section, const char *set,
+ const char *dampen, const char *user_name,
+ int options);
+
+int pcmk__node_attr_request_clear(crm_ipc_t *ipc, const char *host,
+ const char *resource, const char *operation,
+ const char *interval_spec,
+ const char *user_name, int options);
+
+const char *pcmk__node_attr_target(const char *name);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/common/results.h b/include/crm/common/results.h
index 7a32110508..b29a016564 100644
--- a/include/crm/common/results.h
+++ b/include/crm/common/results.h
@@ -1,165 +1,222 @@
/*
- * Copyright 2012-2019 the Pacemaker project contributors
+ * Copyright 2012-2020 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 CRM_RESULTS__H
# define CRM_RESULTS__H
#ifdef __cplusplus
extern "C" {
#endif
/*!
* \file
* \brief Function and executable result codes
* \ingroup core
*/
// Lifted from config.h
/* The _Noreturn keyword of C11. */
#ifndef _Noreturn
# if (defined __cplusplus \
&& ((201103 <= __cplusplus && !(__GNUC__ == 4 && __GNUC_MINOR__ == 7)) \
|| (defined _MSC_VER && 1900 <= _MSC_VER)))
# define _Noreturn [[noreturn]]
# elif ((!defined __cplusplus || defined __clang__) \
&& (201112 <= (defined __STDC_VERSION__ ? __STDC_VERSION__ : 0) \
|| 4 < __GNUC__ + (7 <= __GNUC_MINOR__)))
/* _Noreturn works as-is. */
# elif 2 < __GNUC__ + (8 <= __GNUC_MINOR__) || 0x5110 <= __SUNPRO_C
# define _Noreturn __attribute__ ((__noreturn__))
# elif 1200 <= (defined _MSC_VER ? _MSC_VER : 0)
# define _Noreturn __declspec (noreturn)
# else
# define _Noreturn
# endif
#endif
# define CRM_ASSERT(expr) do { \
if(__unlikely((expr) == FALSE)) { \
crm_abort(__FILE__, __FUNCTION__, __LINE__, #expr, TRUE, FALSE); \
abort(); /* Redundant but it makes static analyzers happy */ \
} \
} while(0)
/*
* Function return codes
*
+ * Most Pacemaker API functions return an integer return code. There are two
+ * alternative interpretations. The legacy interpration is that the absolute
+ * value of the return code is either a system error number or a custom
+ * pcmk_err_* number. This is less than ideal because system error numbers are
+ * constrained only to the positive int range, so there's the possibility
+ * (though not noticed in the wild) that system errors and custom errors could
+ * collide. The new intepretation is that negative values are from the pcmk_rc_e
+ * enum, and positive values are system error numbers. Both use 0 for success.
+ *
* For system error codes, see:
* - /usr/include/asm-generic/errno.h
* - /usr/include/asm-generic/errno-base.h
*/
+// Legacy custom return codes for Pacemaker API functions (deprecated)
# define pcmk_ok 0
# define PCMK_ERROR_OFFSET 190 /* Replacements on non-linux systems, see include/portability.h */
# define PCMK_CUSTOM_OFFSET 200 /* Purely custom codes */
# define pcmk_err_generic 201
# define pcmk_err_no_quorum 202
# define pcmk_err_schema_validation 203
# define pcmk_err_transform_failed 204
# define pcmk_err_old_data 205
# define pcmk_err_diff_failed 206
# define pcmk_err_diff_resync 207
# define pcmk_err_cib_modified 208
# define pcmk_err_cib_backup 209
# define pcmk_err_cib_save 210
# define pcmk_err_schema_unchanged 211
# define pcmk_err_cib_corrupt 212
# define pcmk_err_multiple 213
# define pcmk_err_node_unknown 214
# define pcmk_err_already 215
# define pcmk_err_bad_nvpair 216
# define pcmk_err_unknown_format 217
+/*!
+ * \enum pcmk_rc_e
+ * \brief Return codes for Pacemaker API functions
+ *
+ * Any Pacemaker API function documented as returning a "standard Pacemaker
+ * return code" will return pcmk_rc_ok (0) on success, and one of this
+ * enumeration's other (negative) values or a (positive) system error number
+ * otherwise. The custom codes are at -1001 and lower, so that the caller may
+ * use -1 through -1000 for their own custom values if desired. While generally
+ * referred to as "errors", nonzero values simply indicate a result, which might
+ * or might not be an error depending on the calling context.
+ */
+enum pcmk_rc_e {
+ /* When adding new values, use consecutively lower numbers, update the array
+ * in lib/common/results.c, and test with crm_error.
+ */
+ pcmk_rc_no_quorum = -1017,
+ pcmk_rc_schema_validation = -1016,
+ pcmk_rc_schema_unchanged = -1015,
+ pcmk_rc_transform_failed = -1014,
+ pcmk_rc_old_data = -1013,
+ pcmk_rc_diff_failed = -1012,
+ pcmk_rc_diff_resync = -1011,
+ pcmk_rc_cib_modified = -1010,
+ pcmk_rc_cib_backup = -1009,
+ pcmk_rc_cib_save = -1008,
+ pcmk_rc_cib_corrupt = -1007,
+ pcmk_rc_multiple = -1006,
+ pcmk_rc_node_unknown = -1005,
+ pcmk_rc_already = -1004,
+ pcmk_rc_bad_nvpair = -1003,
+ pcmk_rc_unknown_format = -1002,
+ // Developers: Use a more specific code than pcmk_rc_error whenever possible
+ pcmk_rc_error = -1001,
+
+ // Values -1 through -1000 reserved for caller use
+
+ pcmk_rc_ok = 0
+
+ // Positive values reserved for system error numbers
+};
+
/*
* Exit status codes
*
* We want well-specified (i.e. OS-invariant) exit status codes for our daemons
* and applications so they can be relied on by callers. (Function return codes
* and errno's do not make good exit statuses.)
*
* The only hard rule is that exit statuses must be between 0 and 255; all else
* is convention. Universally, 0 is success, and 1 is generic error (excluding
* OSes we don't support -- for example, OpenVMS considers 1 success!).
*
* For init scripts, the LSB gives meaning to 0-7, and sets aside 150-199 for
* application use. OCF adds 8-9 and 189-199.
*
* sysexits.h was an attempt to give additional meanings, but never really
* caught on. It uses 0 and 64-78.
*
* Bash reserves 2 ("incorrect builtin usage") and 126-255 (126 is "command
* found but not executable", 127 is "command not found", 128 + n is
* "interrupted by signal n").
*
* tldp.org recommends 64-113 for application use.
*
* We try to overlap with the above conventions when practical.
*/
typedef enum crm_exit_e {
// Common convention
CRM_EX_OK = 0,
CRM_EX_ERROR = 1,
// LSB + OCF
CRM_EX_INVALID_PARAM = 2,
CRM_EX_UNIMPLEMENT_FEATURE = 3,
CRM_EX_INSUFFICIENT_PRIV = 4,
CRM_EX_NOT_INSTALLED = 5,
CRM_EX_NOT_CONFIGURED = 6,
CRM_EX_NOT_RUNNING = 7,
// sysexits.h
CRM_EX_USAGE = 64, // command line usage error
CRM_EX_DATAERR = 65, // user-supplied data incorrect
CRM_EX_NOINPUT = 66, // input file not available
CRM_EX_NOUSER = 67, // user does not exist
CRM_EX_NOHOST = 68, // host unknown
CRM_EX_UNAVAILABLE = 69, // needed service unavailable
CRM_EX_SOFTWARE = 70, // internal software bug
CRM_EX_OSERR = 71, // external (OS/environmental) problem
CRM_EX_OSFILE = 72, // system file not usable
CRM_EX_CANTCREAT = 73, // file couldn't be created
CRM_EX_IOERR = 74, // file I/O error
CRM_EX_TEMPFAIL = 75, // try again
CRM_EX_PROTOCOL = 76, // protocol violated
CRM_EX_NOPERM = 77, // non-file permission issue
CRM_EX_CONFIG = 78, // misconfiguration
// Custom
CRM_EX_FATAL = 100, // do not respawn
CRM_EX_PANIC = 101, // panic the local host
CRM_EX_DISCONNECT = 102, // lost connection to something
CRM_EX_OLD = 103, // update older than existing config
CRM_EX_DIGEST = 104, // digest comparison failed
CRM_EX_NOSUCH = 105, // requested item does not exist
CRM_EX_QUORUM = 106, // local partition does not have quorum
CRM_EX_UNSAFE = 107, // requires --force or new conditions
CRM_EX_EXISTS = 108, // requested item already exists
CRM_EX_MULTIPLE = 109, // requested item has multiple matches
CRM_EX_EXPIRED = 110, // requested item has expired
CRM_EX_NOT_YET_IN_EFFECT = 111, // requested item is not in effect
CRM_EX_INDETERMINATE = 112, // could not determine status
// Other
CRM_EX_TIMEOUT = 124, // convention from timeout(1)
CRM_EX_MAX = 255, // ensure crm_exit_t can hold this
} crm_exit_t;
+const char *pcmk_rc_name(int rc);
+const char *pcmk_rc_str(int rc);
+crm_exit_t pcmk_rc2exitc(int rc);
+int pcmk_rc2legacy(int rc);
+int pcmk_legacy2rc(int legacy_rc);
const char *pcmk_strerror(int rc);
const char *pcmk_errorname(int rc);
const char *bz2_strerror(int rc);
crm_exit_t crm_errno2exit(int rc);
const char *crm_exit_name(crm_exit_t exit_code);
const char *crm_exit_str(crm_exit_t exit_code);
_Noreturn crm_exit_t crm_exit(crm_exit_t rc);
#ifdef __cplusplus
}
#endif
#endif
diff --git a/include/crm_internal.h b/include/crm_internal.h
index bccce54ee9..1f25686353 100644
--- a/include/crm_internal.h
+++ b/include/crm_internal.h
@@ -1,310 +1,310 @@
/*
- * Copyright 2006-2019 the Pacemaker project contributors
+ * Copyright 2006-2020 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 CRM_INTERNAL__H
# define CRM_INTERNAL__H
# include <config.h>
# include <portability.h>
# include <glib.h>
# include <stdbool.h>
# include <libxml/tree.h>
# include <crm/lrmd.h>
# include <crm/common/logging.h>
# include <crm/common/ipcs.h>
# include <crm/common/internal.h>
/* This symbol allows us to deprecate public API and prevent internal code from
* using it while still keeping it for backward compatibility.
*/
#define PCMK__NO_COMPAT
/* Dynamic loading of libraries */
void *find_library_function(void **handle, const char *lib, const char *fn, int fatal);
/* For ACLs */
-char *uid2username(uid_t uid);
+char *pcmk__uid2username(uid_t uid);
const char *crm_acl_get_set_user(xmlNode * request, const char *field, const char *peer_user);
# if ENABLE_ACL
# include <string.h>
static inline gboolean
is_privileged(const char *user)
{
if (user == NULL) {
return FALSE;
} else if (strcmp(user, CRM_DAEMON_USER) == 0) {
return TRUE;
} else if (strcmp(user, "root") == 0) {
return TRUE;
}
return FALSE;
}
# endif
/* CLI option processing*/
# ifdef HAVE_GETOPT_H
# include <getopt.h>
# else
# define no_argument 0
# define required_argument 1
# endif
# define pcmk_option_default 0x00000
# define pcmk_option_hidden 0x00001
# define pcmk_option_paragraph 0x00002
# define pcmk_option_example 0x00004
struct crm_option {
/* Fields from 'struct option' in getopt.h */
/* name of long option */
const char *name;
/*
* one of no_argument, required_argument, and optional_argument:
* whether option takes an argument
*/
int has_arg;
/* if not NULL, set *flag to val when option found */
int *flag;
/* if flag not NULL, value to set *flag to; else return value */
int val;
/* Custom fields */
const char *desc;
long flags;
};
void crm_set_options(const char *short_options, const char *usage, struct crm_option *long_options,
const char *app_desc);
int crm_get_option(int argc, char **argv, int *index);
int crm_get_option_long(int argc, char **argv, int *index, const char **longname);
_Noreturn void crm_help(char cmd, crm_exit_t exit_code);
/* Cluster Option Processing */
typedef struct pe_cluster_option_s {
const char *name;
const char *alt_name;
const char *type;
const char *values;
const char *default_value;
gboolean(*is_valid) (const char *);
const char *description_short;
const char *description_long;
} pe_cluster_option;
const char *cluster_option(GHashTable * options, gboolean(*validate) (const char *),
const char *name, const char *old_name, const char *def_value);
const char *get_cluster_pref(GHashTable * options, pe_cluster_option * option_list, int len,
const char *name);
void config_metadata(const char *name, const char *version, const char *desc_short,
const char *desc_long, pe_cluster_option * option_list, int len);
void verify_all_options(GHashTable * options, pe_cluster_option * option_list, int len);
gboolean check_time(const char *value);
gboolean check_timer(const char *value);
gboolean check_boolean(const char *value);
gboolean check_number(const char *value);
gboolean check_positive_number(const char *value);
gboolean check_quorum(const char *value);
gboolean check_script(const char *value);
gboolean check_utilization(const char *value);
long crm_get_sbd_timeout(void);
long crm_auto_watchdog_timeout(void);
gboolean check_sbd_timeout(const char *value);
void crm_args_fini(void);
/* char2score */
extern int node_score_red;
extern int node_score_green;
extern int node_score_yellow;
/* Assorted convenience functions */
void crm_make_daemon(const char *name, gboolean daemonize, const char *pidfile);
// printf-style format to create operation ID from resource, action, interval
#define CRM_OP_FMT "%s_%s_%u"
static inline long long
crm_clear_bit(const char *function, int line, const char *target, long long word, long long bit)
{
long long rc = (word & ~bit);
if (rc == word) {
/* Unchanged */
} else if (target) {
crm_trace("Bit 0x%.8llx for %s cleared by %s:%d", bit, target, function, line);
} else {
crm_trace("Bit 0x%.8llx cleared by %s:%d", bit, function, line);
}
return rc;
}
static inline long long
crm_set_bit(const char *function, int line, const char *target, long long word, long long bit)
{
long long rc = (word | bit);
if (rc == word) {
/* Unchanged */
} else if (target) {
crm_trace("Bit 0x%.8llx for %s set by %s:%d", bit, target, function, line);
} else {
crm_trace("Bit 0x%.8llx set by %s:%d", bit, function, line);
}
return rc;
}
# define set_bit(word, bit) word = crm_set_bit(__FUNCTION__, __LINE__, NULL, word, bit)
# define clear_bit(word, bit) word = crm_clear_bit(__FUNCTION__, __LINE__, NULL, word, bit)
char *generate_hash_key(const char *crm_msg_reference, const char *sys);
const char *daemon_option(const char *option);
void set_daemon_option(const char *option, const char *value);
gboolean daemon_option_enabled(const char *daemon, const char *option);
void strip_text_nodes(xmlNode * xml);
void pcmk_panic(const char *origin);
pid_t pcmk_locate_sbd(void);
# define crm_config_err(fmt...) { crm_config_error = TRUE; crm_err(fmt); }
# define crm_config_warn(fmt...) { crm_config_warning = TRUE; crm_warn(fmt); }
# define F_ATTRD_KEY "attr_key"
# define F_ATTRD_ATTRIBUTE "attr_name"
# define F_ATTRD_REGEX "attr_regex"
# define F_ATTRD_TASK "task"
# define F_ATTRD_VALUE "attr_value"
# define F_ATTRD_SET "attr_set"
# define F_ATTRD_IS_REMOTE "attr_is_remote"
# define F_ATTRD_IS_PRIVATE "attr_is_private"
# define F_ATTRD_SECTION "attr_section"
# define F_ATTRD_DAMPEN "attr_dampening"
# define F_ATTRD_HOST "attr_host"
# define F_ATTRD_HOST_ID "attr_host_id"
# define F_ATTRD_USER "attr_user"
# define F_ATTRD_WRITER "attr_writer"
# define F_ATTRD_VERSION "attr_version"
# define F_ATTRD_RESOURCE "attr_resource"
# define F_ATTRD_OPERATION "attr_clear_operation"
# define F_ATTRD_INTERVAL "attr_clear_interval"
# define F_ATTRD_IS_FORCE_WRITE "attrd_is_force_write"
/* attrd operations */
# define ATTRD_OP_PEER_REMOVE "peer-remove"
# define ATTRD_OP_UPDATE "update"
# define ATTRD_OP_UPDATE_BOTH "update-both"
# define ATTRD_OP_UPDATE_DELAY "update-delay"
# define ATTRD_OP_QUERY "query"
# define ATTRD_OP_REFRESH "refresh"
# define ATTRD_OP_FLUSH "flush"
# define ATTRD_OP_SYNC "sync"
# define ATTRD_OP_SYNC_RESPONSE "sync-response"
# define ATTRD_OP_CLEAR_FAILURE "clear-failure"
# define PCMK_ENV_PHYSICAL_HOST "physical_host"
# if SUPPORT_COROSYNC
# include <qb/qbipc_common.h>
# include <corosync/corotypes.h>
typedef struct qb_ipc_request_header cs_ipc_header_request_t;
typedef struct qb_ipc_response_header cs_ipc_header_response_t;
# else
typedef struct {
int size __attribute__ ((aligned(8)));
int id __attribute__ ((aligned(8)));
} __attribute__ ((aligned(8))) cs_ipc_header_request_t;
typedef struct {
int size __attribute__ ((aligned(8)));
int id __attribute__ ((aligned(8)));
int error __attribute__ ((aligned(8)));
} __attribute__ ((aligned(8))) cs_ipc_header_response_t;
# endif
void
attrd_ipc_server_init(qb_ipcs_service_t **ipcs, struct qb_ipcs_service_handlers *cb);
void
stonith_ipc_server_init(qb_ipcs_service_t **ipcs, struct qb_ipcs_service_handlers *cb);
qb_ipcs_service_t *
crmd_ipc_server_init(struct qb_ipcs_service_handlers *cb);
void cib_ipc_servers_init(qb_ipcs_service_t **ipcs_ro,
qb_ipcs_service_t **ipcs_rw,
qb_ipcs_service_t **ipcs_shm,
struct qb_ipcs_service_handlers *ro_cb,
struct qb_ipcs_service_handlers *rw_cb);
void cib_ipc_servers_destroy(qb_ipcs_service_t *ipcs_ro,
qb_ipcs_service_t *ipcs_rw,
qb_ipcs_service_t *ipcs_shm);
static inline void *realloc_safe(void *ptr, size_t size)
{
void *ret = realloc(ptr, size);
if (ret == NULL) {
free(ptr); /* make coverity happy */
abort();
}
return ret;
}
const char *crm_xml_add_last_written(xmlNode *xml_node);
void crm_xml_dump(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth);
void crm_buffer_add_char(char **buffer, int *offset, int *max, char c);
gboolean crm_digest_verify(xmlNode *input, const char *expected);
/* cross-platform compatibility functions */
char *crm_compat_realpath(const char *path);
/* IPC Proxy Backend Shared Functions */
typedef struct remote_proxy_s {
char *node_name;
char *session_id;
gboolean is_local;
crm_ipc_t *ipc;
mainloop_io_t *source;
uint32_t last_request_id;
lrmd_t *lrm;
} remote_proxy_t;
remote_proxy_t *remote_proxy_new(
lrmd_t *lrmd, struct ipc_client_callbacks *proxy_callbacks,
const char *node_name, const char *session_id, const char *channel);
int remote_proxy_check(lrmd_t *lrmd, GHashTable *hash);
void remote_proxy_cb(lrmd_t *lrmd, const char *node_name, xmlNode *msg);
void remote_proxy_ack_shutdown(lrmd_t *lrmd);
void remote_proxy_nack_shutdown(lrmd_t *lrmd);
int remote_proxy_dispatch(const char *buffer, ssize_t length, gpointer userdata);
void remote_proxy_disconnected(gpointer data);
void remote_proxy_free(gpointer data);
void remote_proxy_relay_event(remote_proxy_t *proxy, xmlNode *msg);
void remote_proxy_relay_response(remote_proxy_t *proxy, xmlNode *msg, int msg_id);
#endif /* CRM_INTERNAL__H */
diff --git a/lib/common/acl.c b/lib/common/acl.c
index 5ecfb6458c..4ceb77ef9d 100644
--- a/lib/common/acl.c
+++ b/lib/common/acl.c
@@ -1,801 +1,801 @@
/*
- * Copyright 2004-2019 the Pacemaker project contributors
+ * Copyright 2004-2020 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 <stdio.h>
#include <sys/types.h>
#include <pwd.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <libxml/tree.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include "crmcommon_private.h"
#define MAX_XPATH_LEN 4096
typedef struct xml_acl_s {
enum xml_private_flags mode;
char *xpath;
} xml_acl_t;
static void
__xml_acl_free(void *data)
{
if (data) {
xml_acl_t *acl = data;
free(acl->xpath);
free(acl);
}
}
void
pcmk__free_acls(GList *acls)
{
g_list_free_full(acls, __xml_acl_free);
}
static GList *
__xml_acl_create(xmlNode *xml, GList *acls, enum xml_private_flags mode)
{
xml_acl_t *acl = NULL;
const char *tag = crm_element_value(xml, XML_ACL_ATTR_TAG);
const char *ref = crm_element_value(xml, XML_ACL_ATTR_REF);
const char *xpath = crm_element_value(xml, XML_ACL_ATTR_XPATH);
const char *attr = crm_element_value(xml, XML_ACL_ATTR_ATTRIBUTE);
if (tag == NULL) {
// @COMPAT rolling upgrades <=1.1.11
tag = crm_element_value(xml, XML_ACL_ATTR_TAGv1);
}
if (ref == NULL) {
// @COMPAT rolling upgrades <=1.1.11
ref = crm_element_value(xml, XML_ACL_ATTR_REFv1);
}
if ((tag == NULL) && (ref == NULL) && (xpath == NULL)) {
// Schema should prevent this, but to be safe ...
crm_trace("Ignoring ACL <%s> element without selection criteria",
crm_element_name(xml));
return NULL;
}
acl = calloc(1, sizeof (xml_acl_t));
CRM_ASSERT(acl != NULL);
acl->mode = mode;
if (xpath) {
acl->xpath = strdup(xpath);
CRM_ASSERT(acl->xpath != NULL);
crm_trace("Unpacked ACL <%s> element using xpath: %s",
crm_element_name(xml), acl->xpath);
} else {
int offset = 0;
char buffer[MAX_XPATH_LEN];
if (tag) {
offset += snprintf(buffer + offset, MAX_XPATH_LEN - offset,
"//%s", tag);
} else {
offset += snprintf(buffer + offset, MAX_XPATH_LEN - offset,
"//*");
}
if (ref || attr) {
offset += snprintf(buffer + offset, MAX_XPATH_LEN - offset,
"[");
}
if (ref) {
offset += snprintf(buffer + offset, MAX_XPATH_LEN - offset,
"@id='%s'", ref);
}
// NOTE: schema currently does not allow this
if (ref && attr) {
offset += snprintf(buffer + offset, MAX_XPATH_LEN - offset,
" and ");
}
if (attr) {
offset += snprintf(buffer + offset, MAX_XPATH_LEN - offset,
"@%s", attr);
}
if (ref || attr) {
offset += snprintf(buffer + offset, MAX_XPATH_LEN - offset,
"]");
}
CRM_LOG_ASSERT(offset > 0);
acl->xpath = strdup(buffer);
CRM_ASSERT(acl->xpath != NULL);
crm_trace("Unpacked ACL <%s> element as xpath: %s",
crm_element_name(xml), acl->xpath);
}
return g_list_append(acls, acl);
}
/*!
* \internal
* \brief Unpack a user, group, or role subtree of the ACLs section
*
* \param[in] acl_top XML of entire ACLs section
* \param[in] acl_entry XML of ACL element being unpacked
* \param[in,out] acls List of ACLs unpacked so far
*
* \return New head of (possibly modified) acls
*/
static GList *
__xml_acl_parse_entry(xmlNode *acl_top, xmlNode *acl_entry, GList *acls)
{
xmlNode *child = NULL;
for (child = __xml_first_child_element(acl_entry); child;
child = __xml_next_element(child)) {
const char *tag = crm_element_name(child);
const char *kind = crm_element_value(child, XML_ACL_ATTR_KIND);
if (strcmp(XML_ACL_TAG_PERMISSION, tag) == 0){
CRM_ASSERT(kind != NULL);
crm_trace("Unpacking ACL <%s> element of kind '%s'", tag, kind);
tag = kind;
} else {
crm_trace("Unpacking ACL <%s> element", tag);
}
if (strcmp(XML_ACL_TAG_ROLE_REF, tag) == 0
|| strcmp(XML_ACL_TAG_ROLE_REFv1, tag) == 0) {
const char *ref_role = crm_element_value(child, XML_ATTR_ID);
if (ref_role) {
xmlNode *role = NULL;
for (role = __xml_first_child_element(acl_top); role;
role = __xml_next_element(role)) {
if (!strcmp(XML_ACL_TAG_ROLE, (const char *) role->name)) {
const char *role_id = crm_element_value(role,
XML_ATTR_ID);
if (role_id && strcmp(ref_role, role_id) == 0) {
crm_trace("Unpacking referenced role '%s' in ACL <%s> element",
role_id, crm_element_name(acl_entry));
acls = __xml_acl_parse_entry(acl_top, role, acls);
break;
}
}
}
}
} else if (strcmp(XML_ACL_TAG_READ, tag) == 0) {
acls = __xml_acl_create(child, acls, xpf_acl_read);
} else if (strcmp(XML_ACL_TAG_WRITE, tag) == 0) {
acls = __xml_acl_create(child, acls, xpf_acl_write);
} else if (strcmp(XML_ACL_TAG_DENY, tag) == 0) {
acls = __xml_acl_create(child, acls, xpf_acl_deny);
} else {
crm_warn("Ignoring unknown ACL %s '%s'",
(kind? "kind" : "element"), tag);
}
}
return acls;
}
/*
<acls>
<acl_target id="l33t-haxor"><role id="auto-l33t-haxor"/></acl_target>
<acl_role id="auto-l33t-haxor">
<acl_permission id="crook-nothing" kind="deny" xpath="/cib"/>
</acl_role>
<acl_target id="niceguy">
<role id="observer"/>
</acl_target>
<acl_role id="observer">
<acl_permission id="observer-read-1" kind="read" xpath="/cib"/>
<acl_permission id="observer-write-1" kind="write" xpath="//nvpair[@name='stonith-enabled']"/>
<acl_permission id="observer-write-2" kind="write" xpath="//nvpair[@name='target-role']"/>
</acl_role>
<acl_target id="badidea"><role id="auto-badidea"/></acl_target>
<acl_role id="auto-badidea">
<acl_permission id="badidea-resources" kind="read" xpath="//meta_attributes"/>
<acl_permission id="badidea-resources-2" kind="deny" reference="dummy-meta_attributes"/>
</acl_role>
</acls>
*/
static const char *
__xml_acl_to_text(enum xml_private_flags flags)
{
if (is_set(flags, xpf_acl_deny)) {
return "deny";
} else if (is_set(flags, xpf_acl_write) || is_set(flags, xpf_acl_create)) {
return "read/write";
} else if (is_set(flags, xpf_acl_read)) {
return "read";
}
return "none";
}
void
pcmk__apply_acl(xmlNode *xml)
{
GListPtr aIter = NULL;
xml_private_t *p = xml->doc->_private;
xmlXPathObjectPtr xpathObj = NULL;
if (xml_acl_enabled(xml) == FALSE) {
crm_trace("Skipping ACLs for user '%s' because not enabled for this XML",
p->user);
return;
}
for (aIter = p->acls; aIter != NULL; aIter = aIter->next) {
int max = 0, lpc = 0;
xml_acl_t *acl = aIter->data;
xpathObj = xpath_search(xml, acl->xpath);
max = numXpathResults(xpathObj);
for (lpc = 0; lpc < max; lpc++) {
xmlNode *match = getXpathResult(xpathObj, lpc);
char *path = xml_get_path(match);
p = match->_private;
crm_trace("Applying %s ACL to %s matched by %s",
__xml_acl_to_text(acl->mode), path, acl->xpath);
#ifdef SUSE_ACL_COMPAT
if (is_not_set(p->flags, acl->mode)
&& (is_set(p->flags, xpf_acl_read)
|| is_set(p->flags, xpf_acl_write)
|| is_set(p->flags, xpf_acl_deny))) {
crm_config_warn("Configuration element %s is matched by "
"multiple ACL rules, only the first applies "
"('%s' wins over '%s')",
path, __xml_acl_to_text(p->flags),
__xml_acl_to_text(acl->mode));
free(path);
continue;
}
#endif
p->flags |= acl->mode;
free(path);
}
crm_trace("Applied %s ACL %s (%d match%s)",
__xml_acl_to_text(acl->mode), acl->xpath, max,
((max == 1)? "" : "es"));
freeXpathObject(xpathObj);
}
p = xml->_private;
if (is_not_set(p->flags, xpf_acl_read)
&& is_not_set(p->flags, xpf_acl_write)) {
p->flags |= xpf_acl_deny;
p = xml->doc->_private;
crm_info("Applied default deny ACL for user '%s' to <%s>",
p->user, crm_element_name(xml));
}
}
/*!
* \internal
* \brief Unpack ACLs for a given user
*
* \param[in] source XML with ACL definitions
* \param[in,out] target XML that ACLs will be applied to
* \param[in] user Username whose ACLs need to be unpacked
*/
void
pcmk__unpack_acl(xmlNode *source, xmlNode *target, const char *user)
{
#if ENABLE_ACL
xml_private_t *p = NULL;
if ((target == NULL) || (target->doc == NULL)
|| (target->doc->_private == NULL)) {
return;
}
p = target->doc->_private;
if (pcmk_acl_required(user) == FALSE) {
crm_trace("Not unpacking ACLs because not required for user '%s'",
user);
} else if (p->acls == NULL) {
xmlNode *acls = get_xpath_object("//" XML_CIB_TAG_ACLS,
source, LOG_NEVER);
free(p->user);
p->user = strdup(user);
if (acls) {
xmlNode *child = NULL;
for (child = __xml_first_child_element(acls); child;
child = __xml_next_element(child)) {
const char *tag = crm_element_name(child);
if (!strcmp(tag, XML_ACL_TAG_USER)
|| !strcmp(tag, XML_ACL_TAG_USERv1)) {
const char *id = crm_element_value(child, XML_ATTR_ID);
if (id && strcmp(id, user) == 0) {
crm_debug("Unpacking ACLs for user '%s'", id);
p->acls = __xml_acl_parse_entry(acls, child, p->acls);
}
}
}
}
}
#endif
}
static inline bool
__xml_acl_mode_test(enum xml_private_flags allowed,
enum xml_private_flags requested)
{
if (is_set(allowed, xpf_acl_deny)) {
return FALSE;
} else if (is_set(allowed, requested)) {
return TRUE;
} else if (is_set(requested, xpf_acl_read)
&& is_set(allowed, xpf_acl_write)) {
return TRUE;
} else if (is_set(requested, xpf_acl_create)
&& is_set(allowed, xpf_acl_write)) {
return TRUE;
} else if (is_set(requested, xpf_acl_create)
&& is_set(allowed, xpf_created)) {
return TRUE;
}
return FALSE;
}
/* rc = TRUE if orig_cib has been filtered
* That means '*result' rather than 'xml' should be exploited afterwards
*/
static bool
__xml_purge_attributes(xmlNode *xml)
{
xmlNode *child = NULL;
xmlAttr *xIter = NULL;
bool readable_children = FALSE;
xml_private_t *p = xml->_private;
if (__xml_acl_mode_test(p->flags, xpf_acl_read)) {
crm_trace("%s[@id=%s] is readable", crm_element_name(xml), ID(xml));
return TRUE;
}
xIter = xml->properties;
while (xIter != NULL) {
xmlAttr *tmp = xIter;
const char *prop_name = (const char *)xIter->name;
xIter = xIter->next;
if (strcmp(prop_name, XML_ATTR_ID) == 0) {
continue;
}
xmlUnsetProp(xml, tmp->name);
}
child = __xml_first_child(xml);
while ( child != NULL ) {
xmlNode *tmp = child;
child = __xml_next(child);
readable_children |= __xml_purge_attributes(tmp);
}
if (readable_children == FALSE) {
free_xml(xml); /* Nothing readable under here, purge completely */
}
return readable_children;
}
/*!
* \internal
* \brief Copy ACL-allowed portions of specified XML
*
* \param[in] user Username whose ACLs should be used
* \param[in] acl_source XML containing ACLs
* \param[in] xml XML to be copied
* \param[out] result Copy of XML portions readable via ACLs
*
* \return TRUE if xml exists and ACLs are required for user, otherwise FALSE
*/
bool
xml_acl_filtered_copy(const char *user, xmlNode *acl_source, xmlNode *xml,
xmlNode **result)
{
GListPtr aIter = NULL;
xmlNode *target = NULL;
xml_private_t *p = NULL;
xml_private_t *doc = NULL;
*result = NULL;
if (xml == NULL || pcmk_acl_required(user) == FALSE) {
crm_trace("Not filtering XML because ACLs not required for user '%s'",
user);
return FALSE;
}
crm_trace("Filtering XML copy using user '%s' ACLs", user);
target = copy_xml(xml);
if (target == NULL) {
return TRUE;
}
pcmk__unpack_acl(acl_source, target, user);
pcmk__set_xml_flag(target, xpf_acl_enabled);
pcmk__apply_acl(target);
doc = target->doc->_private;
for(aIter = doc->acls; aIter != NULL && target; aIter = aIter->next) {
int max = 0;
xml_acl_t *acl = aIter->data;
if (acl->mode != xpf_acl_deny) {
/* Nothing to do */
} else if (acl->xpath) {
int lpc = 0;
xmlXPathObjectPtr xpathObj = xpath_search(target, acl->xpath);
max = numXpathResults(xpathObj);
for(lpc = 0; lpc < max; lpc++) {
xmlNode *match = getXpathResult(xpathObj, lpc);
if (__xml_purge_attributes(match) == FALSE && match == target) {
crm_trace("ACLs deny user '%s' access to entire XML document",
user);
freeXpathObject(xpathObj);
return TRUE;
}
}
crm_trace("ACLs deny user '%s' access to %s (%d match%s)",
user, acl->xpath, max, ((max == 1)? "" : "es"));
freeXpathObject(xpathObj);
}
}
p = target->_private;
if (is_set(p->flags, xpf_acl_deny)
&& (__xml_purge_attributes(target) == FALSE)) {
crm_trace("ACLs deny user '%s' access to entire XML document", user);
return TRUE;
}
if (doc->acls) {
g_list_free_full(doc->acls, __xml_acl_free);
doc->acls = NULL;
} else {
crm_trace("User '%s' without ACLs denied access to entire XML document",
user);
free_xml(target);
target = NULL;
}
if (target) {
*result = target;
}
return TRUE;
}
/*!
* \internal
* \brief Check whether creation of an XML element is implicitly allowed
*
* Check whether XML is a "scaffolding" element whose creation is implicitly
* allowed regardless of ACLs (that is, it is not in the ACL section and has
* no attributes other than "id").
*
* \param[in] xml XML element to check
*
* \return TRUE if XML element is implicitly allowed, FALSE otherwise
*/
static bool
implicitly_allowed(xmlNode *xml)
{
char *path = NULL;
for (xmlAttr *prop = xml->properties; prop != NULL; prop = prop->next) {
if (strcmp((const char *) prop->name, XML_ATTR_ID) != 0) {
return FALSE;
}
}
path = xml_get_path(xml);
if (strstr(path, "/" XML_CIB_TAG_ACLS "/") != NULL) {
free(path);
return FALSE;
}
free(path);
return TRUE;
}
#define display_id(xml) (ID(xml)? ID(xml) : "<unset>")
/*!
* \internal
* \brief Drop XML nodes created in violation of ACLs
*
* Given an XML element, free all of its descendent nodes created in violation
* of ACLs, with the exception of allowing "scaffolding" elements (i.e. those
* that aren't in the ACL section and don't have any attributes other than
* "id").
*
* \param[in,out] xml XML to check
* \param[in] check_top Whether to apply checks to argument itself
* (if TRUE, xml might get freed)
*/
void
pcmk__apply_creation_acl(xmlNode *xml, bool check_top)
{
xml_private_t *p = xml->_private;
if (is_set(p->flags, xpf_created)) {
if (implicitly_allowed(xml)) {
crm_trace("Creation of <%s> scaffolding with id=\"%s\""
" is implicitly allowed",
crm_element_name(xml), display_id(xml));
} else if (pcmk__check_acl(xml, NULL, xpf_acl_write)) {
crm_trace("ACLs allow creation of <%s> with id=\"%s\"",
crm_element_name(xml), display_id(xml));
} else if (check_top) {
crm_trace("ACLs disallow creation of <%s> with id=\"%s\"",
crm_element_name(xml), display_id(xml));
pcmk_free_xml_subtree(xml);
return;
} else {
crm_notice("ACLs would disallow creation of %s<%s> with id=\"%s\" ",
((xml == xmlDocGetRootElement(xml->doc))? "root element " : ""),
crm_element_name(xml), display_id(xml));
}
}
for (xmlNode *cIter = __xml_first_child(xml); cIter != NULL; ) {
xmlNode *child = cIter;
cIter = __xml_next(cIter); /* In case it is free'd */
pcmk__apply_creation_acl(child, TRUE);
}
}
bool
xml_acl_denied(xmlNode *xml)
{
if (xml && xml->doc && xml->doc->_private){
xml_private_t *p = xml->doc->_private;
return is_set(p->flags, xpf_acl_denied);
}
return FALSE;
}
void
xml_acl_disable(xmlNode *xml)
{
if (xml_acl_enabled(xml)) {
xml_private_t *p = xml->doc->_private;
/* Catch anything that was created but shouldn't have been */
pcmk__apply_acl(xml);
pcmk__apply_creation_acl(xml, FALSE);
clear_bit(p->flags, xpf_acl_enabled);
}
}
bool
xml_acl_enabled(xmlNode *xml)
{
if (xml && xml->doc && xml->doc->_private){
xml_private_t *p = xml->doc->_private;
return is_set(p->flags, xpf_acl_enabled);
}
return FALSE;
}
bool
pcmk__check_acl(xmlNode *xml, const char *name, enum xml_private_flags mode)
{
CRM_ASSERT(xml);
CRM_ASSERT(xml->doc);
CRM_ASSERT(xml->doc->_private);
#if ENABLE_ACL
if (pcmk__tracking_xml_changes(xml, FALSE) && xml_acl_enabled(xml)) {
int offset = 0;
xmlNode *parent = xml;
char buffer[MAX_XPATH_LEN];
xml_private_t *docp = xml->doc->_private;
offset = pcmk__element_xpath(NULL, xml, buffer, offset,
sizeof(buffer));
if (name) {
offset += snprintf(buffer + offset, MAX_XPATH_LEN - offset,
"[@%s]", name);
}
CRM_LOG_ASSERT(offset > 0);
if (docp->acls == NULL) {
crm_trace("User '%s' without ACLs denied %s access to %s",
docp->user, __xml_acl_to_text(mode), buffer);
pcmk__set_xml_flag(xml, xpf_acl_denied);
return FALSE;
}
/* Walk the tree upwards looking for xml_acl_* flags
* - Creating an attribute requires write permissions for the node
* - Creating a child requires write permissions for the parent
*/
if (name) {
xmlAttr *attr = xmlHasProp(xml, (pcmkXmlStr) name);
if (attr && mode == xpf_acl_create) {
mode = xpf_acl_write;
}
}
while (parent && parent->_private) {
xml_private_t *p = parent->_private;
if (__xml_acl_mode_test(p->flags, mode)) {
return TRUE;
} else if (is_set(p->flags, xpf_acl_deny)) {
crm_trace("Parent ACL denies user '%s' %s access to %s",
docp->user, __xml_acl_to_text(mode), buffer);
pcmk__set_xml_flag(xml, xpf_acl_denied);
return FALSE;
}
parent = parent->parent;
}
crm_trace("Default ACL denies user '%s' %s access to %s",
docp->user, __xml_acl_to_text(mode), buffer);
pcmk__set_xml_flag(xml, xpf_acl_denied);
return FALSE;
}
#endif
return TRUE;
}
bool
pcmk_acl_required(const char *user)
{
#if ENABLE_ACL
if ((user == NULL) || (*user == '\0')) {
crm_trace("ACLs not required because no user set");
return FALSE;
} else if (!strcmp(user, CRM_DAEMON_USER) || !strcmp(user, "root")) {
crm_trace("ACLs not required for privileged user %s", user);
return FALSE;
}
crm_trace("ACLs required for %s", user);
return TRUE;
#else
crm_trace("ACLs not required because not supported by this build");
return FALSE;
#endif
}
#if ENABLE_ACL
char *
-uid2username(uid_t uid)
+pcmk__uid2username(uid_t uid)
{
struct passwd *pwent = getpwuid(uid);
if (pwent == NULL) {
crm_perror(LOG_INFO, "Cannot get user details for user ID %d", uid);
return NULL;
}
return strdup(pwent->pw_name);
}
const char *
crm_acl_get_set_user(xmlNode *request, const char *field, const char *peer_user)
{
static const char *effective_user = NULL;
const char *requested_user = NULL;
const char *user = NULL;
if (effective_user == NULL) {
- effective_user = uid2username(geteuid());
+ effective_user = pcmk__uid2username(geteuid());
if (effective_user == NULL) {
effective_user = strdup("#unprivileged");
CRM_CHECK(effective_user != NULL, return NULL);
crm_err("Unable to determine effective user, assuming unprivileged for ACLs");
}
}
requested_user = crm_element_value(request, XML_ACL_TAG_USER);
if (requested_user == NULL) {
/* @COMPAT rolling upgrades <=1.1.11
*
* field is checked for backward compatibility with older versions that
* did not use XML_ACL_TAG_USER.
*/
requested_user = crm_element_value(request, field);
}
if (is_privileged(effective_user) == FALSE) {
/* We're not running as a privileged user, set or overwrite any existing
* value for $XML_ACL_TAG_USER
*/
user = effective_user;
} else if (peer_user == NULL && requested_user == NULL) {
/* No user known or requested, use 'effective_user' and make sure one is
* set for the request
*/
user = effective_user;
} else if (peer_user == NULL) {
/* No user known, trusting 'requested_user' */
user = requested_user;
} else if (is_privileged(peer_user) == FALSE) {
/* The peer is not a privileged user, set or overwrite any existing
* value for $XML_ACL_TAG_USER
*/
user = peer_user;
} else if (requested_user == NULL) {
/* Even if we're privileged, make sure there is always a value set */
user = peer_user;
} else {
/* Legal delegation to 'requested_user' */
user = requested_user;
}
// This requires pointer comparison, not string comparison
if (user != crm_element_value(request, XML_ACL_TAG_USER)) {
crm_xml_add(request, XML_ACL_TAG_USER, user);
}
if (field != NULL && user != crm_element_value(request, field)) {
crm_xml_add(request, field, user);
}
return requested_user;
}
#endif
diff --git a/lib/common/agents.c b/lib/common/agents.c
index 23c364f847..ccb6aa0761 100644
--- a/lib/common/agents.c
+++ b/lib/common/agents.c
@@ -1,146 +1,148 @@
/*
- * Copyright 2004-2018 Andrew Beekhof <andrew@beekhof.net>
+ * Copyright 2004-2020 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>
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <crm/crm.h>
#include <crm/common/util.h>
/*!
* \brief Get capabilities of a resource agent standard
*
* \param[in] standard Standard name
*
* \return Bitmask of enum pcmk_ra_caps values
*/
uint32_t
pcmk_get_ra_caps(const char *standard)
{
/* @COMPAT This should probably be case-sensitive, but isn't,
* for backward compatibility.
*/
if (standard == NULL) {
return pcmk_ra_cap_none;
} else if (!strcasecmp(standard, PCMK_RESOURCE_CLASS_OCF)) {
return pcmk_ra_cap_provider | pcmk_ra_cap_params
| pcmk_ra_cap_unique | pcmk_ra_cap_promotable;
} else if (!strcasecmp(standard, PCMK_RESOURCE_CLASS_STONITH)) {
/* @COMPAT Stonith resources can't really be unique clones, but we've
* allowed it in the past and have it in some scheduler regression tests
* (which were likely never used as real configurations).
*
* @TODO Remove pcmk_ra_cap_unique at the next major schema version
* bump, with a transform to remove globally-unique from the config.
*/
return pcmk_ra_cap_params | pcmk_ra_cap_unique;
} else if (!strcasecmp(standard, PCMK_RESOURCE_CLASS_SYSTEMD)
|| !strcasecmp(standard, PCMK_RESOURCE_CLASS_SERVICE)
|| !strcasecmp(standard, PCMK_RESOURCE_CLASS_LSB)
|| !strcasecmp(standard, PCMK_RESOURCE_CLASS_UPSTART)) {
/* Since service can map to LSB, systemd, or upstart, these should
* have identical capabilities
*/
return pcmk_ra_cap_status;
} else if (!strcasecmp(standard, PCMK_RESOURCE_CLASS_NAGIOS)) {
return pcmk_ra_cap_params;
}
return pcmk_ra_cap_none;
}
char *
crm_generate_ra_key(const char *standard, const char *provider,
const char *type)
{
if (!standard && !provider && !type) {
return NULL;
}
return crm_strdup_printf("%s%s%s:%s",
(standard? standard : ""),
(provider? ":" : ""), (provider? provider : ""),
(type? type : ""));
}
/*!
* \deprecated
* \brief Check whether a resource standard requires a provider to be specified
*
* \param[in] standard Standard name
*
* \return TRUE if standard requires a provider, FALSE otherwise
*/
bool
crm_provider_required(const char *standard)
{
return is_set(pcmk_get_ra_caps(standard), pcmk_ra_cap_provider);
}
/*!
* \brief Parse a "standard[:provider]:type" agent specification
*
* \param[in] spec Agent specification
* \param[out] standard Newly allocated memory containing agent standard (or NULL)
* \param[out] provider Newly allocated memory containing agent provider (or NULL)
* \param[put] type Newly allocated memory containing agent type (or NULL)
*
* \return pcmk_ok if the string could be parsed, -EINVAL otherwise
*
* \note It is acceptable for the type to contain a ':' if the standard supports
* that. For example, systemd supports the form "systemd:UNIT@A:B".
* \note It is the caller's responsibility to free the returned values.
*/
int
crm_parse_agent_spec(const char *spec, char **standard, char **provider,
char **type)
{
char *colon;
CRM_CHECK(spec && standard && provider && type, return -EINVAL);
*standard = NULL;
*provider = NULL;
*type = NULL;
colon = strchr(spec, ':');
if ((colon == NULL) || (colon == spec)) {
return -EINVAL;
}
*standard = strndup(spec, colon - spec);
spec = colon + 1;
if (is_set(pcmk_get_ra_caps(*standard), pcmk_ra_cap_provider)) {
colon = strchr(spec, ':');
if ((colon == NULL) || (colon == spec)) {
free(*standard);
return -EINVAL;
}
*provider = strndup(spec, colon - spec);
spec = colon + 1;
}
if (*spec == '\0') {
free(*standard);
free(*provider);
return -EINVAL;
}
*type = strdup(spec);
return pcmk_ok;
}
diff --git a/lib/common/alerts.c b/lib/common/alerts.c
index a1e8df39cb..3c95cb1364 100644
--- a/lib/common/alerts.c
+++ b/lib/common/alerts.c
@@ -1,263 +1,255 @@
/*
- * Copyright 2015-2019 Andrew Beekhof <andrew@beekhof.net>
+ * Copyright 2015-2020 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/crm.h>
#include <crm/lrmd.h>
#include <crm/msg_xml.h>
#include <crm/common/alerts_internal.h>
#include <crm/cib/internal.h> /* for F_CIB_UPDATE_RESULT */
-/*
- * to allow script compatibility we can have more than one
- * set of environment variables
+/*
+ * to allow script compatibility we can have more than one
+ * set of environment variables
*/
-const char *crm_alert_keys[CRM_ALERT_INTERNAL_KEY_MAX][3] =
-{
- [CRM_alert_recipient] = {"CRM_notify_recipient", "CRM_alert_recipient", NULL},
- [CRM_alert_node] = {"CRM_notify_node", "CRM_alert_node", NULL},
- [CRM_alert_nodeid] = {"CRM_notify_nodeid", "CRM_alert_nodeid", NULL},
- [CRM_alert_rsc] = {"CRM_notify_rsc", "CRM_alert_rsc", NULL},
- [CRM_alert_task] = {"CRM_notify_task", "CRM_alert_task", NULL},
- [CRM_alert_interval] = {"CRM_notify_interval", "CRM_alert_interval", NULL},
- [CRM_alert_desc] = {"CRM_notify_desc", "CRM_alert_desc", NULL},
- [CRM_alert_status] = {"CRM_notify_status", "CRM_alert_status", NULL},
- [CRM_alert_target_rc] = {"CRM_notify_target_rc", "CRM_alert_target_rc", NULL},
- [CRM_alert_rc] = {"CRM_notify_rc", "CRM_alert_rc", NULL},
- [CRM_alert_kind] = {"CRM_notify_kind", "CRM_alert_kind", NULL},
- [CRM_alert_version] = {"CRM_notify_version", "CRM_alert_version", NULL},
- [CRM_alert_node_sequence] = {"CRM_notify_node_sequence", CRM_ALERT_NODE_SEQUENCE, NULL},
- [CRM_alert_timestamp] = {"CRM_notify_timestamp", "CRM_alert_timestamp", NULL},
- [CRM_alert_attribute_name] = {"CRM_notify_attribute_name", "CRM_alert_attribute_name", NULL},
- [CRM_alert_attribute_value] = {"CRM_notify_attribute_value", "CRM_alert_attribute_value", NULL},
- [CRM_alert_timestamp_epoch] = {"CRM_notify_timestamp_epoch", "CRM_alert_timestamp_epoch", NULL},
- [CRM_alert_timestamp_usec] = {"CRM_notify_timestamp_usec", "CRM_alert_timestamp_usec", NULL},
- [CRM_alert_exec_time] = {"CRM_notify_exec_time", "CRM_alert_exec_time", NULL}
+const char *pcmk__alert_keys[PCMK__ALERT_INTERNAL_KEY_MAX][3] =
+{
+ [PCMK__alert_key_recipient] = {
+ "CRM_notify_recipient", "CRM_alert_recipient", NULL
+ },
+ [PCMK__alert_key_node] = {
+ "CRM_notify_node", "CRM_alert_node", NULL
+ },
+ [PCMK__alert_key_nodeid] = {
+ "CRM_notify_nodeid", "CRM_alert_nodeid", NULL
+ },
+ [PCMK__alert_key_rsc] = {
+ "CRM_notify_rsc", "CRM_alert_rsc", NULL
+ },
+ [PCMK__alert_key_task] = {
+ "CRM_notify_task", "CRM_alert_task", NULL
+ },
+ [PCMK__alert_key_interval] = {
+ "CRM_notify_interval", "CRM_alert_interval", NULL
+ },
+ [PCMK__alert_key_desc] = {
+ "CRM_notify_desc", "CRM_alert_desc", NULL
+ },
+ [PCMK__alert_key_status] = {
+ "CRM_notify_status", "CRM_alert_status", NULL
+ },
+ [PCMK__alert_key_target_rc] = {
+ "CRM_notify_target_rc", "CRM_alert_target_rc", NULL
+ },
+ [PCMK__alert_key_rc] = {
+ "CRM_notify_rc", "CRM_alert_rc", NULL
+ },
+ [PCMK__alert_key_kind] = {
+ "CRM_notify_kind", "CRM_alert_kind", NULL
+ },
+ [PCMK__alert_key_version] = {
+ "CRM_notify_version", "CRM_alert_version", NULL
+ },
+ [PCMK__alert_key_node_sequence] = {
+ "CRM_notify_node_sequence", PCMK__ALERT_NODE_SEQUENCE, NULL
+ },
+ [PCMK__alert_key_timestamp] = {
+ "CRM_notify_timestamp", "CRM_alert_timestamp", NULL
+ },
+ [PCMK__alert_key_attribute_name] = {
+ "CRM_notify_attribute_name", "CRM_alert_attribute_name", NULL
+ },
+ [PCMK__alert_key_attribute_value] = {
+ "CRM_notify_attribute_value", "CRM_alert_attribute_value", NULL
+ },
+ [PCMK__alert_key_timestamp_epoch] = {
+ "CRM_notify_timestamp_epoch", "CRM_alert_timestamp_epoch", NULL
+ },
+ [PCMK__alert_key_timestamp_usec] = {
+ "CRM_notify_timestamp_usec", "CRM_alert_timestamp_usec", NULL
+ },
+ [PCMK__alert_key_exec_time] = {
+ "CRM_notify_exec_time", "CRM_alert_exec_time", NULL
+ }
};
/*!
* \brief Create a new alert entry structure
*
* \param[in] id ID to use
* \param[in] path Path to alert agent executable
*
* \return Pointer to newly allocated alert entry
* \note Non-string fields will be filled in with defaults.
* It is the caller's responsibility to free the result,
- * using crm_free_alert_entry().
+ * using pcmk__free_alert().
*/
-crm_alert_entry_t *
-crm_alert_entry_new(const char *id, const char *path)
+pcmk__alert_t *
+pcmk__alert_new(const char *id, const char *path)
{
- crm_alert_entry_t *entry = calloc(1, sizeof(crm_alert_entry_t));
+ pcmk__alert_t *entry = calloc(1, sizeof(pcmk__alert_t));
CRM_ASSERT(entry && id && path);
entry->id = strdup(id);
entry->path = strdup(path);
- entry->timeout = CRM_ALERT_DEFAULT_TIMEOUT_MS;
- entry->flags = crm_alert_default;
+ entry->timeout = PCMK__ALERT_DEFAULT_TIMEOUT_MS;
+ entry->flags = pcmk__alert_default;
return entry;
}
void
-crm_free_alert_entry(crm_alert_entry_t *entry)
-{
+pcmk__free_alert(pcmk__alert_t *entry)
+{
if (entry) {
free(entry->id);
free(entry->path);
free(entry->tstamp_format);
free(entry->recipient);
g_strfreev(entry->select_attribute_name);
if (entry->envvars) {
g_hash_table_destroy(entry->envvars);
}
free(entry);
}
-}
+}
/*!
* \internal
* \brief Duplicate an alert entry
*
* \param[in] entry Alert entry to duplicate
*
* \return Duplicate of alert entry
*/
-crm_alert_entry_t *
-crm_dup_alert_entry(crm_alert_entry_t *entry)
+pcmk__alert_t *
+pcmk__dup_alert(pcmk__alert_t *entry)
{
- crm_alert_entry_t *new_entry = crm_alert_entry_new(entry->id, entry->path);
+ pcmk__alert_t *new_entry = pcmk__alert_new(entry->id, entry->path);
new_entry->timeout = entry->timeout;
new_entry->flags = entry->flags;
new_entry->envvars = crm_str_table_dup(entry->envvars);
if (entry->tstamp_format) {
new_entry->tstamp_format = strdup(entry->tstamp_format);
}
if (entry->recipient) {
new_entry->recipient = strdup(entry->recipient);
}
if (entry->select_attribute_name) {
new_entry->select_attribute_name = g_strdupv(entry->select_attribute_name);
}
return new_entry;
}
void
-crm_unset_alert_keys()
-{
- const char **key;
- enum crm_alert_keys_e name;
-
- for(name = 0; name < DIMOF(crm_alert_keys); name++) {
- for(key = crm_alert_keys[name]; *key; key++) {
- crm_trace("Unsetting alert key %s", *key);
- unsetenv(*key);
- }
- }
-}
-
-void
-crm_insert_alert_key(GHashTable *table, enum crm_alert_keys_e name,
- const char *value)
+pcmk__add_alert_key(GHashTable *table, enum pcmk__alert_keys_e name,
+ const char *value)
{
- for (const char **key = crm_alert_keys[name]; *key; key++) {
+ for (const char **key = pcmk__alert_keys[name]; *key; key++) {
crm_trace("Inserting alert key %s = '%s'", *key, value);
if (value) {
g_hash_table_insert(table, strdup(*key), strdup(value));
} else {
g_hash_table_remove(table, *key);
}
}
}
void
-crm_insert_alert_key_int(GHashTable *table, enum crm_alert_keys_e name,
- int value)
+pcmk__add_alert_key_int(GHashTable *table, enum pcmk__alert_keys_e name,
+ int value)
{
- for (const char **key = crm_alert_keys[name]; *key; key++) {
+ for (const char **key = pcmk__alert_keys[name]; *key; key++) {
crm_trace("Inserting alert key %s = %d", *key, value);
g_hash_table_insert(table, strdup(*key), crm_itoa(value));
}
}
-static void
-set_envvar(gpointer key, gpointer value, gpointer user_data)
-{
- gboolean always_unset = GPOINTER_TO_INT(user_data);
-
- crm_trace("%s environment variable %s='%s'",
- (value? "Setting" : "Unsetting"),
- (char*)key, (value? (char*)value : ""));
- if (value && !always_unset) {
- setenv(key, value, 1);
- } else {
- unsetenv(key);
- }
-}
-
-void
-crm_set_envvar_list(crm_alert_entry_t *entry)
-{
- if (entry->envvars) {
- g_hash_table_foreach(entry->envvars, set_envvar, GINT_TO_POINTER(FALSE));
- }
-}
-
-/*
- * \note We have no way of restoring a previous value if one was set.
- */
-void
-crm_unset_envvar_list(crm_alert_entry_t *entry)
-{
- if (entry->envvars) {
- g_hash_table_foreach(entry->envvars, set_envvar, GINT_TO_POINTER(TRUE));
- }
-}
-
#define XPATH_PATCHSET1_DIFF "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED
#define XPATH_PATCHSET1_CRMCONFIG XPATH_PATCHSET1_DIFF "//" XML_CIB_TAG_CRMCONFIG
#define XPATH_PATCHSET1_ALERTS XPATH_PATCHSET1_DIFF "//" XML_CIB_TAG_ALERTS
#define XPATH_PATCHSET1_EITHER \
XPATH_PATCHSET1_CRMCONFIG " | " XPATH_PATCHSET1_ALERTS
#define XPATH_CONFIG "/" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION
#define XPATH_CRMCONFIG XPATH_CONFIG "/" XML_CIB_TAG_CRMCONFIG "/"
#define XPATH_ALERTS XPATH_CONFIG "/" XML_CIB_TAG_ALERTS
/*!
* \internal
* \brief Check whether a CIB update affects alerts
*
* \param[in] msg XML containing CIB update
* \param[in] config Whether to check for crmconfig change as well
*
* \return TRUE if update affects alerts, FALSE otherwise
*/
bool
-crm_patchset_contains_alert(xmlNode *msg, bool config)
+pcmk__alert_in_patchset(xmlNode *msg, bool config)
{
int rc = -1;
int format= 1;
xmlNode *patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT);
xmlNode *change = NULL;
xmlXPathObject *xpathObj = NULL;
CRM_CHECK(msg != NULL, return FALSE);
crm_element_value_int(msg, F_CIB_RC, &rc);
if (rc < pcmk_ok) {
crm_trace("Ignore failed CIB update: %s (%d)", pcmk_strerror(rc), rc);
return FALSE;
}
crm_element_value_int(patchset, "format", &format);
if (format == 1) {
const char *diff = (config? XPATH_PATCHSET1_EITHER : XPATH_PATCHSET1_ALERTS);
if ((xpathObj = xpath_search(msg, diff)) != NULL) {
freeXpathObject(xpathObj);
return TRUE;
}
} else if (format == 2) {
for (change = __xml_first_child(patchset); change != NULL; change = __xml_next(change)) {
const char *xpath = crm_element_value(change, XML_DIFF_PATH);
if (xpath == NULL) {
continue;
}
if ((!config || !strstr(xpath, XPATH_CRMCONFIG))
&& !strstr(xpath, XPATH_ALERTS)) {
/* this is not a change to an existing section ... */
xmlNode *section = NULL;
const char *name = NULL;
if ((strcmp(xpath, XPATH_CONFIG) != 0) ||
((section = __xml_first_child(change)) == NULL) ||
((name = crm_element_name(section)) == NULL) ||
(strcmp(name, XML_CIB_TAG_ALERTS) != 0)) {
/* ... nor is it a newly added alerts section */
continue;
}
}
return TRUE;
}
} else {
crm_warn("Unknown patch format: %d", format);
}
return FALSE;
}
diff --git a/lib/common/attrd_client.c b/lib/common/attrd_client.c
index 7a3b2503de..56158043c0 100644
--- a/lib/common/attrd_client.c
+++ b/lib/common/attrd_client.c
@@ -1,306 +1,314 @@
/*
- * Copyright 2011-2018 Andrew Beekhof <andrew@beekhof.net>
+ * Copyright 2011-2020 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 _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <crm_internal.h>
#include <stdio.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
-#include <crm/attrd.h>
+#include <crm/common/attrd_internal.h>
/*!
* \internal
* \brief Create a generic pacemaker-attrd operation
*
* \param[in] user_name If not NULL, ACL user to set for operation
*
* \return XML of pacemaker-attrd operation
*/
static xmlNode *
create_attrd_op(const char *user_name)
{
xmlNode *attrd_op = create_xml_node(NULL, __FUNCTION__);
crm_xml_add(attrd_op, F_TYPE, T_ATTRD);
crm_xml_add(attrd_op, F_ORIG, (crm_system_name? crm_system_name: "unknown"));
#if ENABLE_ACL
crm_xml_add(attrd_op, F_ATTRD_USER, user_name);
#endif
return attrd_op;
}
/*!
* \internal
* \brief Send an operation to pacemaker-attrd via IPC
*
- * \param[in] ipc Connection to pacemaker-attrd (or NULL to use a local connection)
+ * \param[in] ipc Connection to pacemaker-attrd (or create one if NULL)
* \param[in] attrd_op XML of pacemaker-attrd operation to send
*
- * \return pcmk_ok on success, -errno otherwise
+ * \return Standard Pacemaker return code
*/
static int
send_attrd_op(crm_ipc_t *ipc, xmlNode *attrd_op)
{
- int rc = -ENOTCONN;
+ int rc = -ENOTCONN; // initially handled as legacy return code
int max = 5;
static gboolean connected = TRUE;
static crm_ipc_t *local_ipc = NULL;
static enum crm_ipc_flags flags = crm_ipc_flags_none;
if (ipc == NULL && local_ipc == NULL) {
local_ipc = crm_ipc_new(T_ATTRD, 0);
flags |= crm_ipc_client_response;
connected = FALSE;
}
if (ipc == NULL) {
ipc = local_ipc;
}
while (max > 0) {
if (connected == FALSE) {
crm_info("Connecting to cluster... %d retries remaining", max);
connected = crm_ipc_connect(ipc);
}
if (connected) {
rc = crm_ipc_send(ipc, attrd_op, flags, 0, NULL);
} else {
crm_perror(LOG_INFO, "Connection to cluster attribute manager failed");
}
if (ipc != local_ipc) {
break;
} else if (rc > 0) {
break;
} else if (rc == -EAGAIN || rc == -EALREADY) {
sleep(5 - max);
max--;
} else {
crm_ipc_close(ipc);
connected = FALSE;
sleep(5 - max);
max--;
}
}
if (rc > 0) {
rc = pcmk_ok;
}
- return rc;
+ return pcmk_legacy2rc(rc);
}
/*!
+ * \internal
* \brief Send a request to pacemaker-attrd
*
* \param[in] ipc Connection to pacemaker-attrd (or NULL to use a local connection)
* \param[in] command A character indicating the type of pacemaker-attrd request:
* U or v: update attribute (or refresh if name is NULL)
* u: update attributes matching regular expression in name
* D: delete attribute (value must be NULL)
* R: refresh
* B: update both attribute and its dampening
* Y: update attribute dampening only
* Q: query attribute
* C: remove peer specified by host
* \param[in] host Affect only this host (or NULL for all hosts)
* \param[in] name Name of attribute to affect
* \param[in] value Attribute value to set
* \param[in] section Status or nodes
* \param[in] set ID of attribute set to use (or NULL to choose first)
* \param[in] dampen Attribute dampening to use with B/Y, and U/v if creating
* \param[in] user_name ACL user to pass to pacemaker-attrd
- * \param[in] options Bitmask that may include:
- * attrd_opt_remote: host is a Pacemaker Remote node
- * attrd_opt_private: attribute is private (not kept in CIB)
+ * \param[in] options Bitmask of pcmk__node_attr_opts
*
- * \return pcmk_ok if request was successfully submitted to pacemaker-attrd, else -errno
+ * \return Standard Pacemaker return code
*/
int
-attrd_update_delegate(crm_ipc_t *ipc, char command, const char *host,
- const char *name, const char *value, const char *section,
- const char *set, const char *dampen,
- const char *user_name, int options)
+pcmk__node_attr_request(crm_ipc_t *ipc, char command, const char *host,
+ const char *name, const char *value,
+ const char *section, const char *set,
+ const char *dampen, const char *user_name, int options)
{
- int rc = pcmk_ok;
+ int rc = pcmk_rc_ok;
const char *task = NULL;
const char *name_as = NULL;
const char *display_host = (host ? host : "localhost");
const char *display_command = NULL; /* for commands without name/value */
xmlNode *update = create_attrd_op(user_name);
/* remap common aliases */
if (safe_str_eq(section, "reboot")) {
section = XML_CIB_TAG_STATUS;
} else if (safe_str_eq(section, "forever")) {
section = XML_CIB_TAG_NODES;
}
if (name == NULL && command == 'U') {
command = 'R';
}
switch (command) {
case 'u':
task = ATTRD_OP_UPDATE;
name_as = F_ATTRD_REGEX;
break;
case 'D':
case 'U':
case 'v':
task = ATTRD_OP_UPDATE;
name_as = F_ATTRD_ATTRIBUTE;
break;
case 'R':
task = ATTRD_OP_REFRESH;
display_command = "refresh";
break;
case 'B':
task = ATTRD_OP_UPDATE_BOTH;
name_as = F_ATTRD_ATTRIBUTE;
break;
case 'Y':
task = ATTRD_OP_UPDATE_DELAY;
name_as = F_ATTRD_ATTRIBUTE;
break;
case 'Q':
task = ATTRD_OP_QUERY;
name_as = F_ATTRD_ATTRIBUTE;
break;
case 'C':
task = ATTRD_OP_PEER_REMOVE;
display_command = "purge";
break;
}
if (name_as != NULL) {
if (name == NULL) {
- rc = -EINVAL;
+ rc = EINVAL;
goto done;
}
crm_xml_add(update, name_as, name);
}
crm_xml_add(update, F_ATTRD_TASK, task);
crm_xml_add(update, F_ATTRD_VALUE, value);
crm_xml_add(update, F_ATTRD_DAMPEN, dampen);
crm_xml_add(update, F_ATTRD_SECTION, section);
crm_xml_add(update, F_ATTRD_HOST, host);
crm_xml_add(update, F_ATTRD_SET, set);
- crm_xml_add_int(update, F_ATTRD_IS_REMOTE, is_set(options, attrd_opt_remote));
- crm_xml_add_int(update, F_ATTRD_IS_PRIVATE, is_set(options, attrd_opt_private));
+ crm_xml_add_int(update, F_ATTRD_IS_REMOTE,
+ is_set(options, pcmk__node_attr_remote));
+ crm_xml_add_int(update, F_ATTRD_IS_PRIVATE,
+ is_set(options, pcmk__node_attr_private));
rc = send_attrd_op(ipc, update);
done:
free_xml(update);
if (display_command) {
crm_debug("Asked pacemaker-attrd to %s %s: %s (%d)",
- display_command, display_host, pcmk_strerror(rc), rc);
+ display_command, display_host, pcmk_rc_str(rc), rc);
} else {
crm_debug("Asked pacemaker-attrd to update %s=%s for %s: %s (%d)",
- name, value, display_host, pcmk_strerror(rc), rc);
+ name, value, display_host, pcmk_rc_str(rc), rc);
}
return rc;
}
/*!
+ * \internal
* \brief Send a request to pacemaker-attrd to clear resource failure
*
* \param[in] ipc Connection to pacemaker-attrd (NULL to use local connection)
* \param[in] host Affect only this host (or NULL for all hosts)
* \param[in] resource Name of resource to clear (or NULL for all)
* \param[in] operation Name of operation to clear (or NULL for all)
* \param[in] interval_spec If operation is not NULL, its interval
* \param[in] user_name ACL user to pass to pacemaker-attrd
- * \param[in] options attrd_opt_remote if host is a Pacemaker Remote node
+ * \param[in] options Bitmask of pcmk__node_attr_opts
*
* \return pcmk_ok if request was successfully submitted to pacemaker-attrd, else -errno
*/
int
-attrd_clear_delegate(crm_ipc_t *ipc, const char *host, const char *resource,
- const char *operation, const char *interval_spec,
- const char *user_name, int options)
+pcmk__node_attr_request_clear(crm_ipc_t *ipc, const char *host,
+ const char *resource, const char *operation,
+ const char *interval_spec, const char *user_name,
+ int options)
{
- int rc = pcmk_ok;
+ int rc = pcmk_rc_ok;
xmlNode *clear_op = create_attrd_op(user_name);
const char *interval_desc = NULL;
const char *op_desc = NULL;
crm_xml_add(clear_op, F_ATTRD_TASK, ATTRD_OP_CLEAR_FAILURE);
crm_xml_add(clear_op, F_ATTRD_HOST, host);
crm_xml_add(clear_op, F_ATTRD_RESOURCE, resource);
crm_xml_add(clear_op, F_ATTRD_OPERATION, operation);
crm_xml_add(clear_op, F_ATTRD_INTERVAL, interval_spec);
- crm_xml_add_int(clear_op, F_ATTRD_IS_REMOTE, is_set(options, attrd_opt_remote));
+ crm_xml_add_int(clear_op, F_ATTRD_IS_REMOTE,
+ is_set(options, pcmk__node_attr_remote));
rc = send_attrd_op(ipc, clear_op);
free_xml(clear_op);
if (operation) {
interval_desc = interval_spec? interval_spec : "nonrecurring";
op_desc = operation;
} else {
interval_desc = "all";
op_desc = "operations";
}
crm_debug("Asked pacemaker-attrd to clear failure of %s %s for %s on %s: %s (%d)",
interval_desc, op_desc, (resource? resource : "all resources"),
- (host? host : "all nodes"), pcmk_strerror(rc), rc);
+ (host? host : "all nodes"), pcmk_rc_str(rc), rc);
return rc;
}
#define LRM_TARGET_ENV "OCF_RESKEY_" CRM_META "_" XML_LRM_ATTR_TARGET
+/*!
+ * \internal
+ */
const char *
-attrd_get_target(const char *name)
+pcmk__node_attr_target(const char *name)
{
if(safe_str_eq(name, "auto") || safe_str_eq(name, "localhost")) {
name = NULL;
}
if(name != NULL) {
return name;
} else {
char *target_var = crm_meta_name(XML_RSC_ATTR_TARGET);
char *phys_var = crm_meta_name(PCMK_ENV_PHYSICAL_HOST);
const char *target = getenv(target_var);
const char *host_physical = getenv(phys_var);
// It is important to use the name by which the scheduler knows us
if (host_physical && safe_str_eq(target, "host")) {
name = host_physical;
} else {
const char *host_pcmk = getenv(LRM_TARGET_ENV);
if (host_pcmk) {
name = host_pcmk;
}
}
free(target_var);
free(phys_var);
}
// TODO? Call get_local_node_name() if name == NULL
// (currently would require linkage against libcrmcluster)
return name;
}
diff --git a/lib/common/ipc.c b/lib/common/ipc.c
index 25b6c4cb6d..5dd97f5e1c 100644
--- a/lib/common/ipc.c
+++ b/lib/common/ipc.c
@@ -1,1603 +1,1603 @@
/*
- * Copyright 2004-2019 the Pacemaker project contributors
+ * Copyright 2004-2020 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>
#if defined(US_AUTH_PEERCRED_UCRED) || defined(US_AUTH_PEERCRED_SOCKPEERCRED)
# ifdef US_AUTH_PEERCRED_UCRED
# ifndef _GNU_SOURCE
# define _GNU_SOURCE
# endif
# endif
# include <sys/socket.h>
#elif defined(US_AUTH_GETPEERUCRED)
# include <ucred.h>
#endif
#include <sys/param.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <grp.h>
#include <errno.h>
#include <fcntl.h>
#include <bzlib.h>
#include <crm/crm.h> /* indirectly: pcmk_err_generic */
#include <crm/msg_xml.h>
#include <crm/common/ipc.h>
#include <crm/common/ipcs.h>
#include <crm/common/ipc_internal.h> /* PCMK__SPECIAL_PID* */
#define PCMK_IPC_VERSION 1
/* Evict clients whose event queue grows this large (by default) */
#define PCMK_IPC_DEFAULT_QUEUE_MAX 500
struct crm_ipc_response_header {
struct qb_ipc_response_header qb;
uint32_t size_uncompressed;
uint32_t size_compressed;
uint32_t flags;
uint8_t version; /* Protect against version changes for anyone that might bother to statically link us */
};
static int hdr_offset = 0;
static unsigned int ipc_buffer_max = 0;
static unsigned int pick_ipc_buffer(unsigned int max);
static inline void
crm_ipc_init(void)
{
if (hdr_offset == 0) {
hdr_offset = sizeof(struct crm_ipc_response_header);
}
if (ipc_buffer_max == 0) {
ipc_buffer_max = pick_ipc_buffer(0);
}
}
unsigned int
crm_ipc_default_buffer_size(void)
{
return pick_ipc_buffer(0);
}
static char *
generateReference(const char *custom1, const char *custom2)
{
static uint ref_counter = 0;
return crm_strdup_printf("%s-%s-%lld-%u",
(custom1? custom1 : "_empty_"),
(custom2? custom2 : "_empty_"),
(long long) time(NULL), ref_counter++);
}
xmlNode *
create_request_adv(const char *task, xmlNode * msg_data,
const char *host_to, const char *sys_to,
const char *sys_from, const char *uuid_from, const char *origin)
{
char *true_from = NULL;
xmlNode *request = NULL;
char *reference = generateReference(task, sys_from);
if (uuid_from != NULL) {
true_from = generate_hash_key(sys_from, uuid_from);
} else if (sys_from != NULL) {
true_from = strdup(sys_from);
} else {
crm_err("No sys from specified");
}
// host_from will get set for us if necessary by the controller when routed
request = create_xml_node(NULL, __FUNCTION__);
crm_xml_add(request, F_CRM_ORIGIN, origin);
crm_xml_add(request, F_TYPE, T_CRM);
crm_xml_add(request, F_CRM_VERSION, CRM_FEATURE_SET);
crm_xml_add(request, F_CRM_MSG_TYPE, XML_ATTR_REQUEST);
crm_xml_add(request, F_CRM_REFERENCE, reference);
crm_xml_add(request, F_CRM_TASK, task);
crm_xml_add(request, F_CRM_SYS_TO, sys_to);
crm_xml_add(request, F_CRM_SYS_FROM, true_from);
/* HOSTTO will be ignored if it is to the DC anyway. */
if (host_to != NULL && strlen(host_to) > 0) {
crm_xml_add(request, F_CRM_HOST_TO, host_to);
}
if (msg_data != NULL) {
add_message_xml(request, F_CRM_DATA, msg_data);
}
free(reference);
free(true_from);
return request;
}
/*
* This method adds a copy of xml_response_data
*/
xmlNode *
create_reply_adv(xmlNode * original_request, xmlNode * xml_response_data, const char *origin)
{
xmlNode *reply = NULL;
const char *host_from = crm_element_value(original_request, F_CRM_HOST_FROM);
const char *sys_from = crm_element_value(original_request, F_CRM_SYS_FROM);
const char *sys_to = crm_element_value(original_request, F_CRM_SYS_TO);
const char *type = crm_element_value(original_request, F_CRM_MSG_TYPE);
const char *operation = crm_element_value(original_request, F_CRM_TASK);
const char *crm_msg_reference = crm_element_value(original_request, F_CRM_REFERENCE);
if (type == NULL) {
crm_err("Cannot create new_message, no message type in original message");
CRM_ASSERT(type != NULL);
return NULL;
#if 0
} else if (strcasecmp(XML_ATTR_REQUEST, type) != 0) {
crm_err("Cannot create new_message, original message was not a request");
return NULL;
#endif
}
reply = create_xml_node(NULL, __FUNCTION__);
if (reply == NULL) {
crm_err("Cannot create new_message, malloc failed");
return NULL;
}
crm_xml_add(reply, F_CRM_ORIGIN, origin);
crm_xml_add(reply, F_TYPE, T_CRM);
crm_xml_add(reply, F_CRM_VERSION, CRM_FEATURE_SET);
crm_xml_add(reply, F_CRM_MSG_TYPE, XML_ATTR_RESPONSE);
crm_xml_add(reply, F_CRM_REFERENCE, crm_msg_reference);
crm_xml_add(reply, F_CRM_TASK, operation);
/* since this is a reply, we reverse the from and to */
crm_xml_add(reply, F_CRM_SYS_TO, sys_from);
crm_xml_add(reply, F_CRM_SYS_FROM, sys_to);
/* HOSTTO will be ignored if it is to the DC anyway. */
if (host_from != NULL && strlen(host_from) > 0) {
crm_xml_add(reply, F_CRM_HOST_TO, host_from);
}
if (xml_response_data != NULL) {
add_message_xml(reply, F_CRM_DATA, xml_response_data);
}
return reply;
}
/* Libqb based IPC */
/* Server... */
GHashTable *client_connections = NULL;
crm_client_t *
crm_client_get(qb_ipcs_connection_t * c)
{
if (client_connections) {
return g_hash_table_lookup(client_connections, c);
}
crm_trace("No client found for %p", c);
return NULL;
}
crm_client_t *
crm_client_get_by_id(const char *id)
{
gpointer key;
crm_client_t *client;
GHashTableIter iter;
if (client_connections && id) {
g_hash_table_iter_init(&iter, client_connections);
while (g_hash_table_iter_next(&iter, &key, (gpointer *) & client)) {
if (strcmp(client->id, id) == 0) {
return client;
}
}
}
crm_trace("No client found with id=%s", id);
return NULL;
}
const char *
crm_client_name(crm_client_t * c)
{
if (c == NULL) {
return "null";
} else if (c->name == NULL && c->id == NULL) {
return "unknown";
} else if (c->name == NULL) {
return c->id;
} else {
return c->name;
}
}
const char *
crm_client_type_text(enum client_type client_type)
{
switch (client_type) {
case CRM_CLIENT_IPC:
return "IPC";
case CRM_CLIENT_TCP:
return "TCP";
#ifdef HAVE_GNUTLS_GNUTLS_H
case CRM_CLIENT_TLS:
return "TLS";
#endif
default:
return "unknown";
}
}
void
crm_client_init(void)
{
if (client_connections == NULL) {
crm_trace("Creating client hash table");
client_connections = g_hash_table_new(g_direct_hash, g_direct_equal);
}
}
void
crm_client_cleanup(void)
{
if (client_connections != NULL) {
int active = g_hash_table_size(client_connections);
if (active) {
crm_err("Exiting with %d active connections", active);
}
g_hash_table_destroy(client_connections); client_connections = NULL;
}
}
void
crm_client_disconnect_all(qb_ipcs_service_t *service)
{
qb_ipcs_connection_t *c = NULL;
if (service == NULL) {
return;
}
c = qb_ipcs_connection_first_get(service);
while (c != NULL) {
qb_ipcs_connection_t *last = c;
c = qb_ipcs_connection_next_get(service, last);
/* There really shouldn't be anyone connected at this point */
crm_notice("Disconnecting client %p, pid=%d...", last, crm_ipcs_client_pid(last));
qb_ipcs_disconnect(last);
qb_ipcs_connection_unref(last);
}
}
/*!
* \internal
* \brief Allocate a new crm_client_t object based on an IPC connection
*
* \param[in] c IPC connection (or NULL to allocate generic client)
* \param[in] key Connection table key (or NULL to use sane default)
* \param[in] uid_client UID corresponding to c (ignored if c is NULL)
*
* \return Pointer to new crm_client_t (or NULL on error)
*/
static crm_client_t *
client_from_connection(qb_ipcs_connection_t *c, void *key, uid_t uid_client)
{
crm_client_t *client = calloc(1, sizeof(crm_client_t));
if (client == NULL) {
crm_perror(LOG_ERR, "Allocating client");
return NULL;
}
if (c) {
#if ENABLE_ACL
- client->user = uid2username(uid_client);
+ client->user = pcmk__uid2username(uid_client);
if (client->user == NULL) {
client->user = strdup("#unprivileged");
CRM_CHECK(client->user != NULL, free(client); return NULL);
crm_err("Unable to enforce ACLs for user ID %d, assuming unprivileged",
uid_client);
}
#endif
client->ipcs = c;
client->kind = CRM_CLIENT_IPC;
client->pid = crm_ipcs_client_pid(c);
if (key == NULL) {
key = c;
}
}
client->id = crm_generate_uuid();
if (client->id == NULL) {
crm_err("Could not generate UUID for client");
free(client->user);
free(client);
return NULL;
}
if (key == NULL) {
key = client->id;
}
g_hash_table_insert(client_connections, key, client);
return client;
}
/*!
* \brief Allocate a new crm_client_t object and generate its ID
*
* \param[in] key What to use as connections hash table key (NULL to use ID)
*
* \return Pointer to new crm_client_t (asserts on failure)
*/
crm_client_t *
crm_client_alloc(void *key)
{
crm_client_t *client = client_from_connection(NULL, key, 0);
CRM_ASSERT(client != NULL);
return client;
}
crm_client_t *
crm_client_new(qb_ipcs_connection_t * c, uid_t uid_client, gid_t gid_client)
{
gid_t uid_cluster = 0;
gid_t gid_cluster = 0;
crm_client_t *client = NULL;
CRM_CHECK(c != NULL, return NULL);
if (pcmk_daemon_user(&uid_cluster, &gid_cluster) < 0) {
static bool need_log = TRUE;
if (need_log) {
crm_warn("Could not find user and group IDs for user %s",
CRM_DAEMON_USER);
need_log = FALSE;
}
}
if (uid_client != 0) {
crm_trace("Giving access to group %u", gid_cluster);
/* Passing -1 to chown(2) means don't change */
qb_ipcs_connection_auth_set(c, -1, gid_cluster, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
}
crm_client_init();
/* TODO: Do our own auth checking, return NULL if unauthorized */
client = client_from_connection(c, NULL, uid_client);
if (client == NULL) {
return NULL;
}
if ((uid_client == 0) || (uid_client == uid_cluster)) {
/* Remember when a connection came from root or hacluster */
set_bit(client->flags, crm_client_flag_ipc_privileged);
}
crm_debug("Connecting %p for uid=%d gid=%d pid=%u id=%s", c, uid_client, gid_client, client->pid, client->id);
return client;
}
static struct iovec *
pcmk__new_ipc_event()
{
struct iovec *iov = calloc(2, sizeof(struct iovec));
CRM_ASSERT(iov != NULL);
return iov;
}
/*!
* \brief Free an I/O vector created by crm_ipc_prepare()
*
* \param[in] event I/O vector to free
*/
void
pcmk_free_ipc_event(struct iovec *event)
{
if (event != NULL) {
free(event[0].iov_base);
free(event[1].iov_base);
free(event);
}
}
static void
free_event(gpointer data)
{
pcmk_free_ipc_event((struct iovec *) data);
}
static void
add_event(crm_client_t *c, struct iovec *iov)
{
if (c->event_queue == NULL) {
c->event_queue = g_queue_new();
}
g_queue_push_tail(c->event_queue, iov);
}
void
crm_client_destroy(crm_client_t * c)
{
if (c == NULL) {
return;
}
if (client_connections) {
if (c->ipcs) {
crm_trace("Destroying %p/%p (%d remaining)",
c, c->ipcs, crm_hash_table_size(client_connections) - 1);
g_hash_table_remove(client_connections, c->ipcs);
} else {
crm_trace("Destroying remote connection %p (%d remaining)",
c, crm_hash_table_size(client_connections) - 1);
g_hash_table_remove(client_connections, c->id);
}
}
if (c->event_timer) {
g_source_remove(c->event_timer);
}
if (c->event_queue) {
crm_debug("Destroying %d events", g_queue_get_length(c->event_queue));
g_queue_free_full(c->event_queue, free_event);
}
free(c->id);
free(c->name);
free(c->user);
if (c->remote) {
if (c->remote->auth_timeout) {
g_source_remove(c->remote->auth_timeout);
}
free(c->remote->buffer);
free(c->remote);
}
free(c);
}
/*!
* \brief Raise IPC eviction threshold for a client, if allowed
*
* \param[in,out] client Client to modify
* \param[in] queue_max New threshold (as string)
*
* \return TRUE if change was allowed, FALSE otherwise
*/
bool
crm_set_client_queue_max(crm_client_t *client, const char *qmax)
{
if (is_set(client->flags, crm_client_flag_ipc_privileged)) {
int qmax_int = crm_int_helper(qmax, NULL);
if ((errno == 0) && (qmax_int > 0)) {
client->queue_max = qmax_int;
return TRUE;
}
}
return FALSE;
}
int
crm_ipcs_client_pid(qb_ipcs_connection_t * c)
{
struct qb_ipcs_connection_stats stats;
stats.client_pid = 0;
qb_ipcs_connection_stats_get(c, &stats, 0);
return stats.client_pid;
}
xmlNode *
crm_ipcs_recv(crm_client_t * c, void *data, size_t size, uint32_t * id, uint32_t * flags)
{
xmlNode *xml = NULL;
char *uncompressed = NULL;
char *text = ((char *)data) + sizeof(struct crm_ipc_response_header);
struct crm_ipc_response_header *header = data;
if (id) {
*id = ((struct qb_ipc_response_header *)data)->id;
}
if (flags) {
*flags = header->flags;
}
if (is_set(header->flags, crm_ipc_proxied)) {
/* Mark this client as being the endpoint of a proxy connection.
* Proxy connections responses are sent on the event channel, to avoid
* blocking the controller serving as proxy.
*/
c->flags |= crm_client_flag_ipc_proxied;
}
if(header->version > PCMK_IPC_VERSION) {
crm_err("Filtering incompatible v%d IPC message, we only support versions <= %d",
header->version, PCMK_IPC_VERSION);
return NULL;
}
if (header->size_compressed) {
int rc = 0;
unsigned int size_u = 1 + header->size_uncompressed;
uncompressed = calloc(1, size_u);
crm_trace("Decompressing message data %u bytes into %u bytes",
header->size_compressed, size_u);
rc = BZ2_bzBuffToBuffDecompress(uncompressed, &size_u, text, header->size_compressed, 1, 0);
text = uncompressed;
if (rc != BZ_OK) {
crm_err("Decompression failed: %s " CRM_XS " bzerror=%d",
bz2_strerror(rc), rc);
free(uncompressed);
return NULL;
}
}
CRM_ASSERT(text[header->size_uncompressed - 1] == 0);
crm_trace("Received %.200s", text);
xml = string2xml(text);
free(uncompressed);
return xml;
}
ssize_t crm_ipcs_flush_events(crm_client_t * c);
static gboolean
crm_ipcs_flush_events_cb(gpointer data)
{
crm_client_t *c = data;
c->event_timer = 0;
crm_ipcs_flush_events(c);
return FALSE;
}
/*!
* \internal
* \brief Add progressive delay before next event queue flush
*
* \param[in,out] c Client connection to add delay to
* \param[in] queue_len Current event queue length
*/
static inline void
delay_next_flush(crm_client_t *c, unsigned int queue_len)
{
/* Delay a maximum of 1.5 seconds */
guint delay = (queue_len < 5)? (1000 + 100 * queue_len) : 1500;
c->event_timer = g_timeout_add(delay, crm_ipcs_flush_events_cb, c);
}
ssize_t
crm_ipcs_flush_events(crm_client_t * c)
{
ssize_t rc = 0;
unsigned int sent = 0;
unsigned int queue_len = 0;
if (c == NULL) {
return pcmk_ok;
} else if (c->event_timer) {
/* There is already a timer, wait until it goes off */
crm_trace("Timer active for %p - %d", c->ipcs, c->event_timer);
return pcmk_ok;
}
if (c->event_queue) {
queue_len = g_queue_get_length(c->event_queue);
}
while (sent < 100) {
struct crm_ipc_response_header *header = NULL;
struct iovec *event = NULL;
if (c->event_queue) {
// We don't pop unless send is successful
event = g_queue_peek_head(c->event_queue);
}
if (event == NULL) { // Queue is empty
break;
}
rc = qb_ipcs_event_sendv(c->ipcs, event, 2);
if (rc < 0) {
break;
}
event = g_queue_pop_head(c->event_queue);
sent++;
header = event[0].iov_base;
if (header->size_compressed) {
crm_trace("Event %d to %p[%d] (%lld compressed bytes) sent",
header->qb.id, c->ipcs, c->pid, (long long) rc);
} else {
crm_trace("Event %d to %p[%d] (%lld bytes) sent: %.120s",
header->qb.id, c->ipcs, c->pid, (long long) rc,
(char *) (event[1].iov_base));
}
pcmk_free_ipc_event(event);
}
queue_len -= sent;
if (sent > 0 || queue_len) {
crm_trace("Sent %d events (%d remaining) for %p[%d]: %s (%lld)",
sent, queue_len, c->ipcs, c->pid,
pcmk_strerror(rc < 0 ? rc : 0), (long long) rc);
}
if (queue_len) {
/* Allow clients to briefly fall behind on processing incoming messages,
* but drop completely unresponsive clients so the connection doesn't
* consume resources indefinitely.
*/
if (queue_len > QB_MAX(c->queue_max, PCMK_IPC_DEFAULT_QUEUE_MAX)) {
if ((c->queue_backlog <= 1) || (queue_len < c->queue_backlog)) {
/* Don't evict for a new or shrinking backlog */
crm_warn("Client with process ID %u has a backlog of %u messages "
CRM_XS " %p", c->pid, queue_len, c->ipcs);
} else {
crm_err("Evicting client with process ID %u due to backlog of %u messages "
CRM_XS " %p", c->pid, queue_len, c->ipcs);
c->queue_backlog = 0;
qb_ipcs_disconnect(c->ipcs);
return rc;
}
}
c->queue_backlog = queue_len;
delay_next_flush(c, queue_len);
} else {
/* Event queue is empty, there is no backlog */
c->queue_backlog = 0;
}
return rc;
}
ssize_t
crm_ipc_prepare(uint32_t request, xmlNode * message, struct iovec ** result, uint32_t max_send_size)
{
static unsigned int biggest = 0;
struct iovec *iov;
unsigned int total = 0;
char *compressed = NULL;
char *buffer = dump_xml_unformatted(message);
struct crm_ipc_response_header *header = calloc(1, sizeof(struct crm_ipc_response_header));
CRM_ASSERT(result != NULL);
crm_ipc_init();
if (max_send_size == 0) {
max_send_size = ipc_buffer_max;
}
CRM_LOG_ASSERT(max_send_size != 0);
*result = NULL;
iov = pcmk__new_ipc_event();
iov[0].iov_len = hdr_offset;
iov[0].iov_base = header;
header->version = PCMK_IPC_VERSION;
header->size_uncompressed = 1 + strlen(buffer);
total = iov[0].iov_len + header->size_uncompressed;
if (total < max_send_size) {
iov[1].iov_base = buffer;
iov[1].iov_len = header->size_uncompressed;
} else {
unsigned int new_size = 0;
if (crm_compress_string
(buffer, header->size_uncompressed, max_send_size, &compressed, &new_size)) {
header->flags |= crm_ipc_compressed;
header->size_compressed = new_size;
iov[1].iov_len = header->size_compressed;
iov[1].iov_base = compressed;
free(buffer);
biggest = QB_MAX(header->size_compressed, biggest);
} else {
ssize_t rc = -EMSGSIZE;
crm_log_xml_trace(message, "EMSGSIZE");
biggest = QB_MAX(header->size_uncompressed, biggest);
crm_err
("Could not compress the message (%u bytes) into less than the configured ipc limit (%u bytes). "
"Set PCMK_ipc_buffer to a higher value (%u bytes suggested)",
header->size_uncompressed, max_send_size, 4 * biggest);
free(compressed);
free(buffer);
pcmk_free_ipc_event(iov);
return rc;
}
}
header->qb.size = iov[0].iov_len + iov[1].iov_len;
header->qb.id = (int32_t)request; /* Replying to a specific request */
*result = iov;
CRM_ASSERT(header->qb.size > 0);
return header->qb.size;
}
ssize_t
crm_ipcs_sendv(crm_client_t * c, struct iovec * iov, enum crm_ipc_flags flags)
{
ssize_t rc;
static uint32_t id = 1;
struct crm_ipc_response_header *header = iov[0].iov_base;
if (c->flags & crm_client_flag_ipc_proxied) {
/* _ALL_ replies to proxied connections need to be sent as events */
if (is_not_set(flags, crm_ipc_server_event)) {
flags |= crm_ipc_server_event;
/* this flag lets us know this was originally meant to be a response.
* even though we're sending it over the event channel. */
flags |= crm_ipc_proxied_relay_response;
}
}
header->flags |= flags;
if (flags & crm_ipc_server_event) {
header->qb.id = id++; /* We don't really use it, but doesn't hurt to set one */
if (flags & crm_ipc_server_free) {
crm_trace("Sending the original to %p[%d]", c->ipcs, c->pid);
add_event(c, iov);
} else {
struct iovec *iov_copy = pcmk__new_ipc_event();
crm_trace("Sending a copy to %p[%d]", c->ipcs, c->pid);
iov_copy[0].iov_len = iov[0].iov_len;
iov_copy[0].iov_base = malloc(iov[0].iov_len);
memcpy(iov_copy[0].iov_base, iov[0].iov_base, iov[0].iov_len);
iov_copy[1].iov_len = iov[1].iov_len;
iov_copy[1].iov_base = malloc(iov[1].iov_len);
memcpy(iov_copy[1].iov_base, iov[1].iov_base, iov[1].iov_len);
add_event(c, iov_copy);
}
} else {
CRM_LOG_ASSERT(header->qb.id != 0); /* Replying to a specific request */
rc = qb_ipcs_response_sendv(c->ipcs, iov, 2);
if (rc < header->qb.size) {
crm_notice("Response %d to pid %d failed: %s "
CRM_XS " bytes=%u rc=%lld ipcs=%p",
header->qb.id, c->pid, pcmk_strerror(rc),
header->qb.size, (long long) rc, c->ipcs);
} else {
crm_trace("Response %d sent, %lld bytes to %p[%d]",
header->qb.id, (long long) rc, c->ipcs, c->pid);
}
if (flags & crm_ipc_server_free) {
pcmk_free_ipc_event(iov);
}
}
if (flags & crm_ipc_server_event) {
rc = crm_ipcs_flush_events(c);
} else {
crm_ipcs_flush_events(c);
}
if (rc == -EPIPE || rc == -ENOTCONN) {
crm_trace("Client %p disconnected", c->ipcs);
}
return rc;
}
ssize_t
crm_ipcs_send(crm_client_t * c, uint32_t request, xmlNode * message,
enum crm_ipc_flags flags)
{
struct iovec *iov = NULL;
ssize_t rc = 0;
if(c == NULL) {
return -EDESTADDRREQ;
}
crm_ipc_init();
rc = crm_ipc_prepare(request, message, &iov, ipc_buffer_max);
if (rc > 0) {
rc = crm_ipcs_sendv(c, iov, flags | crm_ipc_server_free);
} else {
pcmk_free_ipc_event(iov);
crm_notice("Message to pid %d failed: %s " CRM_XS " rc=%lld ipcs=%p",
c->pid, pcmk_strerror(rc), (long long) rc, c->ipcs);
}
return rc;
}
void
crm_ipcs_send_ack(crm_client_t * c, uint32_t request, uint32_t flags, const char *tag, const char *function,
int line)
{
if (flags & crm_ipc_client_response) {
xmlNode *ack = create_xml_node(NULL, tag);
crm_trace("Ack'ing msg from %s (%p)", crm_client_name(c), c);
c->request_id = 0;
crm_xml_add(ack, "function", function);
crm_xml_add_int(ack, "line", line);
crm_ipcs_send(c, request, ack, flags);
free_xml(ack);
}
}
/* Client... */
#define MIN_MSG_SIZE 12336 /* sizeof(struct qb_ipc_connection_response) */
#define MAX_MSG_SIZE 128*1024 /* 128k default */
struct crm_ipc_s {
struct pollfd pfd;
/* the max size we can send/receive over ipc */
unsigned int max_buf_size;
/* Size of the allocated 'buffer' */
unsigned int buf_size;
int msg_size;
int need_reply;
char *buffer;
char *name;
qb_ipcc_connection_t *ipc;
};
static unsigned int
pick_ipc_buffer(unsigned int max)
{
static unsigned int global_max = 0;
if (global_max == 0) {
const char *env = getenv("PCMK_ipc_buffer");
if (env) {
int env_max = crm_parse_int(env, "0");
global_max = (env_max > 0)? QB_MAX(MIN_MSG_SIZE, env_max) : MAX_MSG_SIZE;
} else {
global_max = MAX_MSG_SIZE;
}
}
return QB_MAX(max, global_max);
}
crm_ipc_t *
crm_ipc_new(const char *name, size_t max_size)
{
crm_ipc_t *client = NULL;
client = calloc(1, sizeof(crm_ipc_t));
client->name = strdup(name);
client->buf_size = pick_ipc_buffer(max_size);
client->buffer = malloc(client->buf_size);
/* Clients initiating connection pick the max buf size */
client->max_buf_size = client->buf_size;
client->pfd.fd = -1;
client->pfd.events = POLLIN;
client->pfd.revents = 0;
return client;
}
/*!
* \brief Establish an IPC connection to a Pacemaker component
*
* \param[in] client Connection instance obtained from crm_ipc_new()
*
* \return TRUE on success, FALSE otherwise (in which case errno will be set;
* specifically, in case of discovering the remote side is not
* authentic, its value is set to ECONNABORTED).
*/
bool
crm_ipc_connect(crm_ipc_t * client)
{
uid_t cl_uid = 0;
gid_t cl_gid = 0;
pid_t found_pid = 0; uid_t found_uid = 0; gid_t found_gid = 0;
int rv;
client->need_reply = FALSE;
client->ipc = qb_ipcc_connect(client->name, client->buf_size);
if (client->ipc == NULL) {
crm_debug("Could not establish %s connection: %s (%d)", client->name, pcmk_strerror(errno), errno);
return FALSE;
}
client->pfd.fd = crm_ipc_get_fd(client);
if (client->pfd.fd < 0) {
rv = errno;
/* message already omitted */
crm_ipc_close(client);
errno = rv;
return FALSE;
}
rv = pcmk_daemon_user(&cl_uid, &cl_gid);
if (rv < 0) {
/* message already omitted */
crm_ipc_close(client);
errno = -rv;
return FALSE;
}
if (!(rv = crm_ipc_is_authentic_process(client->pfd.fd, cl_uid, cl_gid,
&found_pid, &found_uid,
&found_gid))) {
crm_err("Daemon (IPC %s) is not authentic:"
" process %lld (uid: %lld, gid: %lld)",
client->name, (long long) PCMK__SPECIAL_PID_AS_0(found_pid),
(long long) found_uid, (long long) found_gid);
crm_ipc_close(client);
errno = ECONNABORTED;
return FALSE;
} else if (rv < 0) {
errno = -rv;
crm_perror(LOG_ERR, "Could not verify authenticity of daemon (IPC %s)",
client->name);
crm_ipc_close(client);
errno = -rv;
return FALSE;
}
qb_ipcc_context_set(client->ipc, client);
#ifdef HAVE_IPCS_GET_BUFFER_SIZE
client->max_buf_size = qb_ipcc_get_buffer_size(client->ipc);
if (client->max_buf_size > client->buf_size) {
free(client->buffer);
client->buffer = calloc(1, client->max_buf_size);
client->buf_size = client->max_buf_size;
}
#endif
return TRUE;
}
void
crm_ipc_close(crm_ipc_t * client)
{
if (client) {
crm_trace("Disconnecting %s IPC connection %p (%p)", client->name, client, client->ipc);
if (client->ipc) {
qb_ipcc_connection_t *ipc = client->ipc;
client->ipc = NULL;
qb_ipcc_disconnect(ipc);
}
}
}
void
crm_ipc_destroy(crm_ipc_t * client)
{
if (client) {
if (client->ipc && qb_ipcc_is_connected(client->ipc)) {
crm_notice("Destroying an active IPC connection to %s", client->name);
/* The next line is basically unsafe
*
* If this connection was attached to mainloop and mainloop is active,
* the 'disconnected' callback will end up back here and we'll end
* up free'ing the memory twice - something that can still happen
* even without this if we destroy a connection and it closes before
* we call exit
*/
/* crm_ipc_close(client); */
}
crm_trace("Destroying IPC connection to %s: %p", client->name, client);
free(client->buffer);
free(client->name);
free(client);
}
}
int
crm_ipc_get_fd(crm_ipc_t * client)
{
int fd = 0;
if (client && client->ipc && (qb_ipcc_fd_get(client->ipc, &fd) == 0)) {
return fd;
}
errno = EINVAL;
crm_perror(LOG_ERR, "Could not obtain file IPC descriptor for %s",
(client? client->name : "unspecified client"));
return -errno;
}
bool
crm_ipc_connected(crm_ipc_t * client)
{
bool rc = FALSE;
if (client == NULL) {
crm_trace("No client");
return FALSE;
} else if (client->ipc == NULL) {
crm_trace("No connection");
return FALSE;
} else if (client->pfd.fd < 0) {
crm_trace("Bad descriptor");
return FALSE;
}
rc = qb_ipcc_is_connected(client->ipc);
if (rc == FALSE) {
client->pfd.fd = -EINVAL;
}
return rc;
}
/*!
* \brief Check whether an IPC connection is ready to be read
*
* \param[in] client Connection to check
*
* \return Positive value if ready to be read, 0 if not ready, -errno on error
*/
int
crm_ipc_ready(crm_ipc_t *client)
{
int rc;
CRM_ASSERT(client != NULL);
if (crm_ipc_connected(client) == FALSE) {
return -ENOTCONN;
}
client->pfd.revents = 0;
rc = poll(&(client->pfd), 1, 0);
return (rc < 0)? -errno : rc;
}
static int
crm_ipc_decompress(crm_ipc_t * client)
{
struct crm_ipc_response_header *header = (struct crm_ipc_response_header *)(void*)client->buffer;
if (header->size_compressed) {
int rc = 0;
unsigned int size_u = 1 + header->size_uncompressed;
/* never let buf size fall below our max size required for ipc reads. */
unsigned int new_buf_size = QB_MAX((hdr_offset + size_u), client->max_buf_size);
char *uncompressed = calloc(1, new_buf_size);
crm_trace("Decompressing message data %u bytes into %u bytes",
header->size_compressed, size_u);
rc = BZ2_bzBuffToBuffDecompress(uncompressed + hdr_offset, &size_u,
client->buffer + hdr_offset, header->size_compressed, 1, 0);
if (rc != BZ_OK) {
crm_err("Decompression failed: %s " CRM_XS " bzerror=%d",
bz2_strerror(rc), rc);
free(uncompressed);
return -EILSEQ;
}
/*
* This assert no longer holds true. For an identical msg, some clients may
* require compression, and others may not. If that same msg (event) is sent
* to multiple clients, it could result in some clients receiving a compressed
* msg even though compression was not explicitly required for them.
*
* CRM_ASSERT((header->size_uncompressed + hdr_offset) >= ipc_buffer_max);
*/
CRM_ASSERT(size_u == header->size_uncompressed);
memcpy(uncompressed, client->buffer, hdr_offset); /* Preserve the header */
header = (struct crm_ipc_response_header *)(void*)uncompressed;
free(client->buffer);
client->buf_size = new_buf_size;
client->buffer = uncompressed;
}
CRM_ASSERT(client->buffer[hdr_offset + header->size_uncompressed - 1] == 0);
return pcmk_ok;
}
long
crm_ipc_read(crm_ipc_t * client)
{
struct crm_ipc_response_header *header = NULL;
CRM_ASSERT(client != NULL);
CRM_ASSERT(client->ipc != NULL);
CRM_ASSERT(client->buffer != NULL);
crm_ipc_init();
client->buffer[0] = 0;
client->msg_size = qb_ipcc_event_recv(client->ipc, client->buffer,
client->buf_size, 0);
if (client->msg_size >= 0) {
int rc = crm_ipc_decompress(client);
if (rc != pcmk_ok) {
return rc;
}
header = (struct crm_ipc_response_header *)(void*)client->buffer;
if(header->version > PCMK_IPC_VERSION) {
crm_err("Filtering incompatible v%d IPC message, we only support versions <= %d",
header->version, PCMK_IPC_VERSION);
return -EBADMSG;
}
crm_trace("Received %s event %d, size=%u, rc=%d, text: %.100s",
client->name, header->qb.id, header->qb.size, client->msg_size,
client->buffer + hdr_offset);
} else {
crm_trace("No message from %s received: %s", client->name, pcmk_strerror(client->msg_size));
}
if (crm_ipc_connected(client) == FALSE || client->msg_size == -ENOTCONN) {
crm_err("Connection to %s failed", client->name);
}
if (header) {
/* Data excluding the header */
return header->size_uncompressed;
}
return -ENOMSG;
}
const char *
crm_ipc_buffer(crm_ipc_t * client)
{
CRM_ASSERT(client != NULL);
return client->buffer + sizeof(struct crm_ipc_response_header);
}
uint32_t
crm_ipc_buffer_flags(crm_ipc_t * client)
{
struct crm_ipc_response_header *header = NULL;
CRM_ASSERT(client != NULL);
if (client->buffer == NULL) {
return 0;
}
header = (struct crm_ipc_response_header *)(void*)client->buffer;
return header->flags;
}
const char *
crm_ipc_name(crm_ipc_t * client)
{
CRM_ASSERT(client != NULL);
return client->name;
}
static int
internal_ipc_send_recv(crm_ipc_t * client, const void *iov)
{
int rc = 0;
do {
rc = qb_ipcc_sendv_recv(client->ipc, iov, 2, client->buffer, client->buf_size, -1);
} while (rc == -EAGAIN && crm_ipc_connected(client));
return rc;
}
static int
internal_ipc_send_request(crm_ipc_t * client, const void *iov, int ms_timeout)
{
int rc = 0;
time_t timeout = time(NULL) + 1 + (ms_timeout / 1000);
do {
rc = qb_ipcc_sendv(client->ipc, iov, 2);
} while (rc == -EAGAIN && time(NULL) < timeout && crm_ipc_connected(client));
return rc;
}
static int
internal_ipc_get_reply(crm_ipc_t * client, int request_id, int ms_timeout)
{
time_t timeout = time(NULL) + 1 + (ms_timeout / 1000);
int rc = 0;
crm_ipc_init();
/* get the reply */
crm_trace("client %s waiting on reply to msg id %d", client->name, request_id);
do {
rc = qb_ipcc_recv(client->ipc, client->buffer, client->buf_size, 1000);
if (rc > 0) {
struct crm_ipc_response_header *hdr = NULL;
int rc = crm_ipc_decompress(client);
if (rc != pcmk_ok) {
return rc;
}
hdr = (struct crm_ipc_response_header *)(void*)client->buffer;
if (hdr->qb.id == request_id) {
/* Got it */
break;
} else if (hdr->qb.id < request_id) {
xmlNode *bad = string2xml(crm_ipc_buffer(client));
crm_err("Discarding old reply %d (need %d)", hdr->qb.id, request_id);
crm_log_xml_notice(bad, "OldIpcReply");
} else {
xmlNode *bad = string2xml(crm_ipc_buffer(client));
crm_err("Discarding newer reply %d (need %d)", hdr->qb.id, request_id);
crm_log_xml_notice(bad, "ImpossibleReply");
CRM_ASSERT(hdr->qb.id <= request_id);
}
} else if (crm_ipc_connected(client) == FALSE) {
crm_err("Server disconnected client %s while waiting for msg id %d", client->name,
request_id);
break;
}
} while (time(NULL) < timeout);
return rc;
}
int
crm_ipc_send(crm_ipc_t * client, xmlNode * message, enum crm_ipc_flags flags, int32_t ms_timeout,
xmlNode ** reply)
{
long rc = 0;
struct iovec *iov;
static uint32_t id = 0;
static int factor = 8;
struct crm_ipc_response_header *header;
crm_ipc_init();
if (client == NULL) {
crm_notice("Invalid connection");
return -ENOTCONN;
} else if (crm_ipc_connected(client) == FALSE) {
/* Don't even bother */
crm_notice("Connection to %s closed", client->name);
return -ENOTCONN;
}
if (ms_timeout == 0) {
ms_timeout = 5000;
}
if (client->need_reply) {
crm_trace("Trying again to obtain pending reply from %s", client->name);
rc = qb_ipcc_recv(client->ipc, client->buffer, client->buf_size, ms_timeout);
if (rc < 0) {
crm_warn("Sending to %s (%p) is disabled until pending reply is received", client->name,
client->ipc);
return -EALREADY;
} else {
crm_notice("Lost reply from %s (%p) finally arrived, sending re-enabled", client->name,
client->ipc);
client->need_reply = FALSE;
}
}
id++;
CRM_LOG_ASSERT(id != 0); /* Crude wrap-around detection */
rc = crm_ipc_prepare(id, message, &iov, client->max_buf_size);
if(rc < 0) {
return rc;
}
header = iov[0].iov_base;
header->flags |= flags;
if(is_set(flags, crm_ipc_proxied)) {
/* Don't look for a synchronous response */
clear_bit(flags, crm_ipc_client_response);
}
if(header->size_compressed) {
if(factor < 10 && (client->max_buf_size / 10) < (rc / factor)) {
crm_notice("Compressed message exceeds %d0%% of the configured ipc limit (%u bytes), "
"consider setting PCMK_ipc_buffer to %u or higher",
factor, client->max_buf_size, 2 * client->max_buf_size);
factor++;
}
}
crm_trace("Sending from client: %s request id: %d bytes: %u timeout:%d msg...",
client->name, header->qb.id, header->qb.size, ms_timeout);
if (ms_timeout > 0 || is_not_set(flags, crm_ipc_client_response)) {
rc = internal_ipc_send_request(client, iov, ms_timeout);
if (rc <= 0) {
crm_trace("Failed to send from client %s request %d with %u bytes...",
client->name, header->qb.id, header->qb.size);
goto send_cleanup;
} else if (is_not_set(flags, crm_ipc_client_response)) {
crm_trace("Message sent, not waiting for reply to %d from %s to %u bytes...",
header->qb.id, client->name, header->qb.size);
goto send_cleanup;
}
rc = internal_ipc_get_reply(client, header->qb.id, ms_timeout);
if (rc < 0) {
/* No reply, for now, disable sending
*
* The alternative is to close the connection since we don't know
* how to detect and discard out-of-sequence replies
*
* TODO - implement the above
*/
client->need_reply = TRUE;
}
} else {
rc = internal_ipc_send_recv(client, iov);
}
if (rc > 0) {
struct crm_ipc_response_header *hdr = (struct crm_ipc_response_header *)(void*)client->buffer;
crm_trace("Received response %d, size=%u, rc=%ld, text: %.200s", hdr->qb.id, hdr->qb.size,
rc, crm_ipc_buffer(client));
if (reply) {
*reply = string2xml(crm_ipc_buffer(client));
}
} else {
crm_trace("Response not received: rc=%ld, errno=%d", rc, errno);
}
send_cleanup:
if (crm_ipc_connected(client) == FALSE) {
crm_notice("Connection to %s closed: %s (%ld)", client->name, pcmk_strerror(rc), rc);
} else if (rc == -ETIMEDOUT) {
crm_warn("Request %d to %s (%p) failed: %s (%ld) after %dms",
header->qb.id, client->name, client->ipc, pcmk_strerror(rc), rc, ms_timeout);
crm_write_blackbox(0, NULL);
} else if (rc <= 0) {
crm_warn("Request %d to %s (%p) failed: %s (%ld)",
header->qb.id, client->name, client->ipc, pcmk_strerror(rc), rc);
}
pcmk_free_ipc_event(iov);
return rc;
}
int
crm_ipc_is_authentic_process(int sock, uid_t refuid, gid_t refgid,
pid_t *gotpid, uid_t *gotuid, gid_t *gotgid) {
int ret = 0;
pid_t found_pid = 0; uid_t found_uid = 0; gid_t found_gid = 0;
#if defined(US_AUTH_PEERCRED_UCRED)
struct ucred ucred;
socklen_t ucred_len = sizeof(ucred);
if (!getsockopt(sock, SOL_SOCKET, SO_PEERCRED,
&ucred, &ucred_len)
&& ucred_len == sizeof(ucred)) {
found_pid = ucred.pid; found_uid = ucred.uid; found_gid = ucred.gid;
#elif defined(US_AUTH_PEERCRED_SOCKPEERCRED)
struct sockpeercred sockpeercred;
socklen_t sockpeercred_len = sizeof(sockpeercred);
if (!getsockopt(sock, SOL_SOCKET, SO_PEERCRED,
&sockpeercred, &sockpeercred_len)
&& sockpeercred_len == sizeof(sockpeercred_len)) {
found_pid = sockpeercred.pid;
found_uid = sockpeercred.uid; found_gid = sockpeercred.gid;
#elif defined(US_AUTH_GETPEEREID)
if (!getpeereid(sock, &found_uid, &found_gid)) {
found_pid = PCMK__SPECIAL_PID; /* cannot obtain PID (FreeBSD) */
#elif defined(US_AUTH_GETPEERUCRED)
ucred_t *ucred;
if (!getpeerucred(sock, &ucred)) {
errno = 0;
found_pid = ucred_getpid(ucred);
found_uid = ucred_geteuid(ucred); found_gid = ucred_getegid(ucred);
ret = -errno;
ucred_free(ucred);
if (ret) {
return (ret < 0) ? ret : -pcmk_err_generic;
}
#else
# error "No way to authenticate a Unix socket peer"
errno = 0;
if (0) {
#endif
if (gotpid != NULL) {
*gotpid = found_pid;
}
if (gotuid != NULL) {
*gotuid = found_uid;
}
if (gotgid != NULL) {
*gotgid = found_gid;
}
ret = (found_uid == 0 || found_uid == refuid || found_gid == refgid);
} else {
ret = (errno > 0) ? -errno : -pcmk_err_generic;
}
return ret;
}
int
pcmk__ipc_is_authentic_process_active(const char *name, uid_t refuid,
gid_t refgid, pid_t *gotpid) {
static char last_asked_name[PATH_MAX / 2] = ""; /* log spam prevention */
int fd, ret = 0;
pid_t found_pid = 0; uid_t found_uid = 0; gid_t found_gid = 0;
qb_ipcc_connection_t *c;
if ((c = qb_ipcc_connect(name, 0)) == NULL) {
crm_info("Could not connect to %s IPC: %s", name, strerror(errno));
} else if ((ret = qb_ipcc_fd_get(c, &fd))) {
crm_err("Could not get fd from %s IPC: %s (%d)", name,
strerror(-ret), -ret);
ret = -1;
} else if ((ret = crm_ipc_is_authentic_process(fd, refuid, refgid,
&found_pid, &found_uid,
&found_gid)) < 0) {
if (ret == -pcmk_err_generic) {
crm_err("Could not get peer credentials from %s IPC", name);
} else {
crm_err("Could not get peer credentials from %s IPC: %s (%d)",
name, strerror(-ret), -ret);
}
ret = -1;
} else {
if (gotpid != NULL) {
*gotpid = found_pid;
}
if (!ret) {
crm_err("Daemon (IPC %s) effectively blocked with unauthorized"
" process %lld (uid: %lld, gid: %lld)",
name, (long long) PCMK__SPECIAL_PID_AS_0(found_pid),
(long long) found_uid, (long long) found_gid);
ret = -2;
} else if ((found_uid != refuid || found_gid != refgid)
&& strncmp(last_asked_name, name, sizeof(last_asked_name))) {
if (!found_uid && refuid) {
crm_warn("Daemon (IPC %s) runs as root, whereas the expected"
" credentials are %lld:%lld, hazard of violating"
" the least privilege principle",
name, (long long) refuid, (long long) refgid);
} else {
crm_notice("Daemon (IPC %s) runs as %lld:%lld, whereas the"
" expected credentials are %lld:%lld, which may"
" mean a different set of privileges than expected",
name, (long long) found_uid, (long long) found_gid,
(long long) refuid, (long long) refgid);
}
memccpy(last_asked_name, name, '\0', sizeof(last_asked_name));
}
}
if (ret) { /* here, !ret only when we could not initially connect */
qb_ipcc_disconnect(c);
}
return ret;
}
/* Utils */
xmlNode *
create_hello_message(const char *uuid,
const char *client_name, const char *major_version, const char *minor_version)
{
xmlNode *hello_node = NULL;
xmlNode *hello = NULL;
if (uuid == NULL || strlen(uuid) == 0
|| client_name == NULL || strlen(client_name) == 0
|| major_version == NULL || strlen(major_version) == 0
|| minor_version == NULL || strlen(minor_version) == 0) {
crm_err("Missing fields, Hello message will not be valid.");
return NULL;
}
hello_node = create_xml_node(NULL, XML_TAG_OPTIONS);
crm_xml_add(hello_node, "major_version", major_version);
crm_xml_add(hello_node, "minor_version", minor_version);
crm_xml_add(hello_node, "client_name", client_name);
crm_xml_add(hello_node, "client_uuid", uuid);
crm_trace("creating hello message");
hello = create_request(CRM_OP_HELLO, hello_node, NULL, NULL, client_name, uuid);
free_xml(hello_node);
return hello;
}
diff --git a/lib/common/results.c b/lib/common/results.c
index b80191c3ae..189648f544 100644
--- a/lib/common/results.c
+++ b/lib/common/results.c
@@ -1,501 +1,709 @@
/*
- * Copyright 2004-2019 the Pacemaker project contributors
+ * Copyright 2004-2020 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>
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <bzlib.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <qb/qbdefs.h>
#include <crm/common/mainloop.h>
#include <crm/common/xml.h>
+// @COMPAT Legacy function return codes
+
+//! \deprecated Use standard return codes and pcmk_rc_name() instead
const char *
pcmk_errorname(int rc)
{
- int error = abs(rc);
-
- switch (error) {
- case E2BIG: return "E2BIG";
- case EACCES: return "EACCES";
- case EADDRINUSE: return "EADDRINUSE";
- case EADDRNOTAVAIL: return "EADDRNOTAVAIL";
- case EAFNOSUPPORT: return "EAFNOSUPPORT";
- case EAGAIN: return "EAGAIN";
- case EALREADY: return "EALREADY";
- case EBADF: return "EBADF";
- case EBADMSG: return "EBADMSG";
- case EBUSY: return "EBUSY";
- case ECANCELED: return "ECANCELED";
- case ECHILD: return "ECHILD";
- case ECOMM: return "ECOMM";
- case ECONNABORTED: return "ECONNABORTED";
- case ECONNREFUSED: return "ECONNREFUSED";
- case ECONNRESET: return "ECONNRESET";
- /* case EDEADLK: return "EDEADLK"; */
- case EDESTADDRREQ: return "EDESTADDRREQ";
- case EDOM: return "EDOM";
- case EDQUOT: return "EDQUOT";
- case EEXIST: return "EEXIST";
- case EFAULT: return "EFAULT";
- case EFBIG: return "EFBIG";
- case EHOSTDOWN: return "EHOSTDOWN";
- case EHOSTUNREACH: return "EHOSTUNREACH";
- case EIDRM: return "EIDRM";
- case EILSEQ: return "EILSEQ";
- case EINPROGRESS: return "EINPROGRESS";
- case EINTR: return "EINTR";
- case EINVAL: return "EINVAL";
- case EIO: return "EIO";
- case EISCONN: return "EISCONN";
- case EISDIR: return "EISDIR";
- case ELIBACC: return "ELIBACC";
- case ELOOP: return "ELOOP";
- case EMFILE: return "EMFILE";
- case EMLINK: return "EMLINK";
- case EMSGSIZE: return "EMSGSIZE";
-#ifdef EMULTIHOP // Not available on OpenBSD
- case EMULTIHOP: return "EMULTIHOP";
-#endif
- case ENAMETOOLONG: return "ENAMETOOLONG";
- case ENETDOWN: return "ENETDOWN";
- case ENETRESET: return "ENETRESET";
- case ENETUNREACH: return "ENETUNREACH";
- case ENFILE: return "ENFILE";
- case ENOBUFS: return "ENOBUFS";
- case ENODATA: return "ENODATA";
- case ENODEV: return "ENODEV";
- case ENOENT: return "ENOENT";
- case ENOEXEC: return "ENOEXEC";
- case ENOKEY: return "ENOKEY";
- case ENOLCK: return "ENOLCK";
-#ifdef ENOLINK // Not available on OpenBSD
- case ENOLINK: return "ENOLINK";
-#endif
- case ENOMEM: return "ENOMEM";
- case ENOMSG: return "ENOMSG";
- case ENOPROTOOPT: return "ENOPROTOOPT";
- case ENOSPC: return "ENOSPC";
- case ENOSR: return "ENOSR";
- case ENOSTR: return "ENOSTR";
- case ENOSYS: return "ENOSYS";
- case ENOTBLK: return "ENOTBLK";
- case ENOTCONN: return "ENOTCONN";
- case ENOTDIR: return "ENOTDIR";
- case ENOTEMPTY: return "ENOTEMPTY";
- case ENOTSOCK: return "ENOTSOCK";
- /* case ENOTSUP: return "ENOTSUP"; */
- case ENOTTY: return "ENOTTY";
- case ENOTUNIQ: return "ENOTUNIQ";
- case ENXIO: return "ENXIO";
- case EOPNOTSUPP: return "EOPNOTSUPP";
- case EOVERFLOW: return "EOVERFLOW";
- case EPERM: return "EPERM";
- case EPFNOSUPPORT: return "EPFNOSUPPORT";
- case EPIPE: return "EPIPE";
- case EPROTO: return "EPROTO";
- case EPROTONOSUPPORT: return "EPROTONOSUPPORT";
- case EPROTOTYPE: return "EPROTOTYPE";
- case ERANGE: return "ERANGE";
- case EREMOTE: return "EREMOTE";
- case EREMOTEIO: return "EREMOTEIO";
-
- case EROFS: return "EROFS";
- case ESHUTDOWN: return "ESHUTDOWN";
- case ESPIPE: return "ESPIPE";
- case ESOCKTNOSUPPORT: return "ESOCKTNOSUPPORT";
- case ESRCH: return "ESRCH";
- case ESTALE: return "ESTALE";
- case ETIME: return "ETIME";
- case ETIMEDOUT: return "ETIMEDOUT";
- case ETXTBSY: return "ETXTBSY";
- case EUNATCH: return "EUNATCH";
- case EUSERS: return "EUSERS";
- /* case EWOULDBLOCK: return "EWOULDBLOCK"; */
- case EXDEV: return "EXDEV";
-
-#ifdef EBADE
- /* Not available on OSX */
- case EBADE: return "EBADE";
- case EBADFD: return "EBADFD";
- case EBADSLT: return "EBADSLT";
- case EDEADLOCK: return "EDEADLOCK";
- case EBADR: return "EBADR";
- case EBADRQC: return "EBADRQC";
- case ECHRNG: return "ECHRNG";
-#ifdef EISNAM /* Not available on Illumos/Solaris */
- case EISNAM: return "EISNAM";
- case EKEYEXPIRED: return "EKEYEXPIRED";
- case EKEYREJECTED: return "EKEYREJECTED";
- case EKEYREVOKED: return "EKEYREVOKED";
-#endif
- case EL2HLT: return "EL2HLT";
- case EL2NSYNC: return "EL2NSYNC";
- case EL3HLT: return "EL3HLT";
- case EL3RST: return "EL3RST";
- case ELIBBAD: return "ELIBBAD";
- case ELIBMAX: return "ELIBMAX";
- case ELIBSCN: return "ELIBSCN";
- case ELIBEXEC: return "ELIBEXEC";
-#ifdef ENOMEDIUM /* Not available on Illumos/Solaris */
- case ENOMEDIUM: return "ENOMEDIUM";
- case EMEDIUMTYPE: return "EMEDIUMTYPE";
-#endif
- case ENONET: return "ENONET";
- case ENOPKG: return "ENOPKG";
- case EREMCHG: return "EREMCHG";
- case ERESTART: return "ERESTART";
- case ESTRPIPE: return "ESTRPIPE";
-#ifdef EUCLEAN /* Not available on Illumos/Solaris */
- case EUCLEAN: return "EUCLEAN";
-#endif
- case EXFULL: return "EXFULL";
-#endif
-
+ rc = abs(rc);
+ switch (rc) {
case pcmk_err_generic: return "pcmk_err_generic";
case pcmk_err_no_quorum: return "pcmk_err_no_quorum";
case pcmk_err_schema_validation: return "pcmk_err_schema_validation";
case pcmk_err_transform_failed: return "pcmk_err_transform_failed";
case pcmk_err_old_data: return "pcmk_err_old_data";
case pcmk_err_diff_failed: return "pcmk_err_diff_failed";
case pcmk_err_diff_resync: return "pcmk_err_diff_resync";
case pcmk_err_cib_modified: return "pcmk_err_cib_modified";
case pcmk_err_cib_backup: return "pcmk_err_cib_backup";
case pcmk_err_cib_save: return "pcmk_err_cib_save";
case pcmk_err_cib_corrupt: return "pcmk_err_cib_corrupt";
case pcmk_err_multiple: return "pcmk_err_multiple";
case pcmk_err_node_unknown: return "pcmk_err_node_unknown";
case pcmk_err_already: return "pcmk_err_already";
case pcmk_err_bad_nvpair: return "pcmk_err_bad_nvpair";
case pcmk_err_unknown_format: return "pcmk_err_unknown_format";
+ default: return pcmk_rc_name(rc); // system errno
}
- return "Unknown";
}
+//! \deprecated Use standard return codes and pcmk_rc_str() instead
const char *
pcmk_strerror(int rc)
{
- int error = abs(rc);
-
- if (error == 0) {
+ if (rc == 0) {
return "OK";
+ }
- // Of course error > 0 ... unless someone passed INT_MIN as rc
- } else if ((error > 0) && (error < PCMK_ERROR_OFFSET)) {
- return strerror(error);
+ rc = abs(rc);
+
+ // Of course rc > 0 ... unless someone passed INT_MIN as rc
+ if ((rc > 0) && (rc < PCMK_ERROR_OFFSET)) {
+ return strerror(rc);
}
- switch (error) {
+ switch (rc) {
case pcmk_err_generic:
return "Generic Pacemaker error";
case pcmk_err_no_quorum:
return "Operation requires quorum";
case pcmk_err_schema_validation:
return "Update does not conform to the configured schema";
case pcmk_err_transform_failed:
return "Schema transform failed";
case pcmk_err_old_data:
return "Update was older than existing configuration";
case pcmk_err_diff_failed:
return "Application of an update diff failed";
case pcmk_err_diff_resync:
return "Application of an update diff failed, requesting a full refresh";
case pcmk_err_cib_modified:
return "The on-disk configuration was manually modified";
case pcmk_err_cib_backup:
return "Could not archive the previous configuration";
case pcmk_err_cib_save:
return "Could not save the new configuration to disk";
case pcmk_err_cib_corrupt:
return "Could not parse on-disk configuration";
case pcmk_err_multiple:
return "Resource active on multiple nodes";
case pcmk_err_node_unknown:
return "Node not found";
case pcmk_err_already:
return "Situation already as requested";
case pcmk_err_bad_nvpair:
return "Bad name/value pair given";
case pcmk_err_schema_unchanged:
return "Schema is already the latest available";
case pcmk_err_unknown_format:
return "Unknown output format";
/* The following cases will only be hit on systems for which they are non-standard */
/* coverity[dead_error_condition] False positive on non-Linux */
case ENOTUNIQ:
return "Name not unique on network";
/* coverity[dead_error_condition] False positive on non-Linux */
case ECOMM:
return "Communication error on send";
/* coverity[dead_error_condition] False positive on non-Linux */
case ELIBACC:
return "Can not access a needed shared library";
/* coverity[dead_error_condition] False positive on non-Linux */
case EREMOTEIO:
return "Remote I/O error";
/* coverity[dead_error_condition] False positive on non-Linux */
case EUNATCH:
return "Protocol driver not attached";
/* coverity[dead_error_condition] False positive on non-Linux */
case ENOKEY:
return "Required key not available";
}
-
crm_err("Unknown error code: %d", rc);
return "Unknown error";
}
+// Standard Pacemaker API return codes
+
+/* This array is used only for nonzero values of pcmk_rc_e. Its values must be
+ * kept in the exact reverse order of the enum value numbering (i.e. add new
+ * values to the end of the array).
+ */
+static struct pcmk__rc_info {
+ const char *name;
+ const char *desc;
+ int legacy_rc;
+} pcmk__rcs[] = {
+ { "pcmk_rc_error",
+ "Error",
+ -pcmk_err_generic,
+ },
+ { "pcmk_rc_unknown_format",
+ "Unknown output format",
+ -pcmk_err_unknown_format,
+ },
+ { "pcmk_rc_bad_nvpair",
+ "Bad name/value pair given",
+ -pcmk_err_bad_nvpair,
+ },
+ { "pcmk_rc_already",
+ "Already in requested state",
+ -pcmk_err_already,
+ },
+ { "pcmk_rc_node_unknown",
+ "Node not found",
+ -pcmk_err_node_unknown,
+ },
+ { "pcmk_rc_multiple",
+ "Resource active on multiple nodes",
+ -pcmk_err_multiple,
+ },
+ { "pcmk_rc_cib_corrupt",
+ "Could not parse on-disk configuration",
+ -pcmk_err_cib_corrupt,
+ },
+ { "pcmk_rc_cib_save",
+ "Could not save new configuration to disk",
+ -pcmk_err_cib_save,
+ },
+ { "pcmk_rc_cib_backup",
+ "Could not archive previous configuration",
+ -pcmk_err_cib_backup,
+ },
+ { "pcmk_rc_cib_modified",
+ "On-disk configuration was manually modified",
+ -pcmk_err_cib_modified,
+ },
+ { "pcmk_rc_diff_resync",
+ "Application of update diff failed, requesting full refresh",
+ -pcmk_err_diff_resync,
+ },
+ { "pcmk_rc_diff_failed",
+ "Application of update diff failed",
+ -pcmk_err_diff_failed,
+ },
+ { "pcmk_rc_old_data",
+ "Update was older than existing configuration",
+ -pcmk_err_old_data,
+ },
+ { "pcmk_rc_transform_failed",
+ "Schema transform failed",
+ -pcmk_err_transform_failed,
+ },
+ { "pcmk_rc_schema_unchanged",
+ "Schema is already the latest available",
+ -pcmk_err_schema_unchanged,
+ },
+ { "pcmk_rc_schema_validation",
+ "Update does not conform to the configured schema",
+ -pcmk_err_schema_validation,
+ },
+ { "pcmk_rc_no_quorum",
+ "Operation requires quorum",
+ -pcmk_err_no_quorum,
+ },
+};
+
+#define PCMK__N_RC (sizeof(pcmk__rcs) / sizeof(struct pcmk__rc_info))
+
+/*!
+ * \brief Get a return code constant name as a string
+ *
+ * \param[in] rc Integer return code to convert
+ *
+ * \return String of constant name corresponding to rc
+ */
+const char *
+pcmk_rc_name(int rc)
+{
+ if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < PCMK__N_RC)) {
+ return pcmk__rcs[pcmk_rc_error - rc].name;
+ }
+ switch (rc) {
+ case pcmk_rc_ok: return "pcmk_rc_ok";
+ case E2BIG: return "E2BIG";
+ case EACCES: return "EACCES";
+ case EADDRINUSE: return "EADDRINUSE";
+ case EADDRNOTAVAIL: return "EADDRNOTAVAIL";
+ case EAFNOSUPPORT: return "EAFNOSUPPORT";
+ case EAGAIN: return "EAGAIN";
+ case EALREADY: return "EALREADY";
+ case EBADF: return "EBADF";
+ case EBADMSG: return "EBADMSG";
+ case EBUSY: return "EBUSY";
+ case ECANCELED: return "ECANCELED";
+ case ECHILD: return "ECHILD";
+ case ECOMM: return "ECOMM";
+ case ECONNABORTED: return "ECONNABORTED";
+ case ECONNREFUSED: return "ECONNREFUSED";
+ case ECONNRESET: return "ECONNRESET";
+ /* case EDEADLK: return "EDEADLK"; */
+ case EDESTADDRREQ: return "EDESTADDRREQ";
+ case EDOM: return "EDOM";
+ case EDQUOT: return "EDQUOT";
+ case EEXIST: return "EEXIST";
+ case EFAULT: return "EFAULT";
+ case EFBIG: return "EFBIG";
+ case EHOSTDOWN: return "EHOSTDOWN";
+ case EHOSTUNREACH: return "EHOSTUNREACH";
+ case EIDRM: return "EIDRM";
+ case EILSEQ: return "EILSEQ";
+ case EINPROGRESS: return "EINPROGRESS";
+ case EINTR: return "EINTR";
+ case EINVAL: return "EINVAL";
+ case EIO: return "EIO";
+ case EISCONN: return "EISCONN";
+ case EISDIR: return "EISDIR";
+ case ELIBACC: return "ELIBACC";
+ case ELOOP: return "ELOOP";
+ case EMFILE: return "EMFILE";
+ case EMLINK: return "EMLINK";
+ case EMSGSIZE: return "EMSGSIZE";
+#ifdef EMULTIHOP // Not available on OpenBSD
+ case EMULTIHOP: return "EMULTIHOP";
+#endif
+ case ENAMETOOLONG: return "ENAMETOOLONG";
+ case ENETDOWN: return "ENETDOWN";
+ case ENETRESET: return "ENETRESET";
+ case ENETUNREACH: return "ENETUNREACH";
+ case ENFILE: return "ENFILE";
+ case ENOBUFS: return "ENOBUFS";
+ case ENODATA: return "ENODATA";
+ case ENODEV: return "ENODEV";
+ case ENOENT: return "ENOENT";
+ case ENOEXEC: return "ENOEXEC";
+ case ENOKEY: return "ENOKEY";
+ case ENOLCK: return "ENOLCK";
+#ifdef ENOLINK // Not available on OpenBSD
+ case ENOLINK: return "ENOLINK";
+#endif
+ case ENOMEM: return "ENOMEM";
+ case ENOMSG: return "ENOMSG";
+ case ENOPROTOOPT: return "ENOPROTOOPT";
+ case ENOSPC: return "ENOSPC";
+ case ENOSR: return "ENOSR";
+ case ENOSTR: return "ENOSTR";
+ case ENOSYS: return "ENOSYS";
+ case ENOTBLK: return "ENOTBLK";
+ case ENOTCONN: return "ENOTCONN";
+ case ENOTDIR: return "ENOTDIR";
+ case ENOTEMPTY: return "ENOTEMPTY";
+ case ENOTSOCK: return "ENOTSOCK";
+#if ENOTSUP != EOPNOTSUPP
+ case ENOTSUP: return "ENOTSUP";
+#endif
+ case ENOTTY: return "ENOTTY";
+ case ENOTUNIQ: return "ENOTUNIQ";
+ case ENXIO: return "ENXIO";
+ case EOPNOTSUPP: return "EOPNOTSUPP";
+ case EOVERFLOW: return "EOVERFLOW";
+ case EPERM: return "EPERM";
+ case EPFNOSUPPORT: return "EPFNOSUPPORT";
+ case EPIPE: return "EPIPE";
+ case EPROTO: return "EPROTO";
+ case EPROTONOSUPPORT: return "EPROTONOSUPPORT";
+ case EPROTOTYPE: return "EPROTOTYPE";
+ case ERANGE: return "ERANGE";
+ case EREMOTE: return "EREMOTE";
+ case EREMOTEIO: return "EREMOTEIO";
+ case EROFS: return "EROFS";
+ case ESHUTDOWN: return "ESHUTDOWN";
+ case ESPIPE: return "ESPIPE";
+ case ESOCKTNOSUPPORT: return "ESOCKTNOSUPPORT";
+ case ESRCH: return "ESRCH";
+ case ESTALE: return "ESTALE";
+ case ETIME: return "ETIME";
+ case ETIMEDOUT: return "ETIMEDOUT";
+ case ETXTBSY: return "ETXTBSY";
+ case EUNATCH: return "EUNATCH";
+ case EUSERS: return "EUSERS";
+ /* case EWOULDBLOCK: return "EWOULDBLOCK"; */
+ case EXDEV: return "EXDEV";
+
+#ifdef EBADE // Not available on OS X
+ case EBADE: return "EBADE";
+ case EBADFD: return "EBADFD";
+ case EBADSLT: return "EBADSLT";
+ case EDEADLOCK: return "EDEADLOCK";
+ case EBADR: return "EBADR";
+ case EBADRQC: return "EBADRQC";
+ case ECHRNG: return "ECHRNG";
+#ifdef EISNAM // Not available on OS X, Illumos, Solaris
+ case EISNAM: return "EISNAM";
+ case EKEYEXPIRED: return "EKEYEXPIRED";
+ case EKEYREJECTED: return "EKEYREJECTED";
+ case EKEYREVOKED: return "EKEYREVOKED";
+#endif
+ case EL2HLT: return "EL2HLT";
+ case EL2NSYNC: return "EL2NSYNC";
+ case EL3HLT: return "EL3HLT";
+ case EL3RST: return "EL3RST";
+ case ELIBBAD: return "ELIBBAD";
+ case ELIBMAX: return "ELIBMAX";
+ case ELIBSCN: return "ELIBSCN";
+ case ELIBEXEC: return "ELIBEXEC";
+#ifdef ENOMEDIUM // Not available on OS X, Illumos, Solaris
+ case ENOMEDIUM: return "ENOMEDIUM";
+ case EMEDIUMTYPE: return "EMEDIUMTYPE";
+#endif
+ case ENONET: return "ENONET";
+ case ENOPKG: return "ENOPKG";
+ case EREMCHG: return "EREMCHG";
+ case ERESTART: return "ERESTART";
+ case ESTRPIPE: return "ESTRPIPE";
+#ifdef EUCLEAN // Not available on OS X, Illumos, Solaris
+ case EUCLEAN: return "EUCLEAN";
+#endif
+ case EXFULL: return "EXFULL";
+#endif // EBADE
+ default: return "Unknown";
+ }
+}
+
+/*!
+ * \brief Get a user-friendly description of a return code
+ *
+ * \param[in] rc Integer return code to convert
+ *
+ * \return String description of rc
+ */
+const char *
+pcmk_rc_str(int rc)
+{
+ if (rc == pcmk_rc_ok) {
+ return "OK";
+ }
+ if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < PCMK__N_RC)) {
+ return pcmk__rcs[pcmk_rc_error - rc].desc;
+ }
+ if (rc < 0) {
+ return "Unknown error";
+ }
+ return strerror(rc);
+}
+
+// This returns negative values for errors
+//! \deprecated Use standard return codes instead
+int
+pcmk_rc2legacy(int rc)
+{
+ if (rc >= 0) {
+ return -rc; // OK or system errno
+ }
+ if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < PCMK__N_RC)) {
+ return pcmk__rcs[pcmk_rc_error - rc].legacy_rc;
+ }
+ return -pcmk_err_generic;
+}
+
+//! \deprecated Use standard return codes instead
+int
+pcmk_legacy2rc(int legacy_rc)
+{
+ legacy_rc = abs(legacy_rc);
+ switch (legacy_rc) {
+ case pcmk_err_no_quorum: return pcmk_rc_no_quorum;
+ case pcmk_err_schema_validation: return pcmk_rc_schema_validation;
+ case pcmk_err_schema_unchanged: return pcmk_rc_schema_unchanged;
+ case pcmk_err_transform_failed: return pcmk_rc_transform_failed;
+ case pcmk_err_old_data: return pcmk_rc_old_data;
+ case pcmk_err_diff_failed: return pcmk_rc_diff_failed;
+ case pcmk_err_diff_resync: return pcmk_rc_diff_resync;
+ case pcmk_err_cib_modified: return pcmk_rc_cib_modified;
+ case pcmk_err_cib_backup: return pcmk_rc_cib_backup;
+ case pcmk_err_cib_save: return pcmk_rc_cib_save;
+ case pcmk_err_cib_corrupt: return pcmk_rc_cib_corrupt;
+ case pcmk_err_multiple: return pcmk_rc_multiple;
+ case pcmk_err_node_unknown: return pcmk_rc_node_unknown;
+ case pcmk_err_already: return pcmk_rc_already;
+ case pcmk_err_bad_nvpair: return pcmk_rc_bad_nvpair;
+ case pcmk_err_unknown_format: return pcmk_rc_unknown_format;
+ case pcmk_err_generic: return pcmk_rc_error;
+ case pcmk_ok: return pcmk_rc_ok;
+ default: return legacy_rc; // system errno
+ }
+}
+
+// Exit status codes
+
const char *
crm_exit_name(crm_exit_t exit_code)
{
switch (exit_code) {
case CRM_EX_OK: return "CRM_EX_OK";
case CRM_EX_ERROR: return "CRM_EX_ERROR";
case CRM_EX_INVALID_PARAM: return "CRM_EX_INVALID_PARAM";
case CRM_EX_UNIMPLEMENT_FEATURE: return "CRM_EX_UNIMPLEMENT_FEATURE";
case CRM_EX_INSUFFICIENT_PRIV: return "CRM_EX_INSUFFICIENT_PRIV";
case CRM_EX_NOT_INSTALLED: return "CRM_EX_NOT_INSTALLED";
case CRM_EX_NOT_CONFIGURED: return "CRM_EX_NOT_CONFIGURED";
case CRM_EX_NOT_RUNNING: return "CRM_EX_NOT_RUNNING";
case CRM_EX_USAGE: return "CRM_EX_USAGE";
case CRM_EX_DATAERR: return "CRM_EX_DATAERR";
case CRM_EX_NOINPUT: return "CRM_EX_NOINPUT";
case CRM_EX_NOUSER: return "CRM_EX_NOUSER";
case CRM_EX_NOHOST: return "CRM_EX_NOHOST";
case CRM_EX_UNAVAILABLE: return "CRM_EX_UNAVAILABLE";
case CRM_EX_SOFTWARE: return "CRM_EX_SOFTWARE";
case CRM_EX_OSERR: return "CRM_EX_OSERR";
case CRM_EX_OSFILE: return "CRM_EX_OSFILE";
case CRM_EX_CANTCREAT: return "CRM_EX_CANTCREAT";
case CRM_EX_IOERR: return "CRM_EX_IOERR";
case CRM_EX_TEMPFAIL: return "CRM_EX_TEMPFAIL";
case CRM_EX_PROTOCOL: return "CRM_EX_PROTOCOL";
case CRM_EX_NOPERM: return "CRM_EX_NOPERM";
case CRM_EX_CONFIG: return "CRM_EX_CONFIG";
case CRM_EX_FATAL: return "CRM_EX_FATAL";
case CRM_EX_PANIC: return "CRM_EX_PANIC";
case CRM_EX_DISCONNECT: return "CRM_EX_DISCONNECT";
case CRM_EX_DIGEST: return "CRM_EX_DIGEST";
case CRM_EX_NOSUCH: return "CRM_EX_NOSUCH";
case CRM_EX_QUORUM: return "CRM_EX_QUORUM";
case CRM_EX_UNSAFE: return "CRM_EX_UNSAFE";
case CRM_EX_EXISTS: return "CRM_EX_EXISTS";
case CRM_EX_MULTIPLE: return "CRM_EX_MULTIPLE";
case CRM_EX_EXPIRED: return "CRM_EX_EXPIRED";
case CRM_EX_NOT_YET_IN_EFFECT: return "CRM_EX_NOT_YET_IN_EFFECT";
case CRM_EX_INDETERMINATE: return "CRM_EX_INDETERMINATE";
case CRM_EX_OLD: return "CRM_EX_OLD";
case CRM_EX_TIMEOUT: return "CRM_EX_TIMEOUT";
case CRM_EX_MAX: return "CRM_EX_UNKNOWN";
}
return "CRM_EX_UNKNOWN";
}
const char *
crm_exit_str(crm_exit_t exit_code)
{
switch (exit_code) {
case CRM_EX_OK: return "OK";
case CRM_EX_ERROR: return "Error occurred";
case CRM_EX_INVALID_PARAM: return "Invalid parameter";
case CRM_EX_UNIMPLEMENT_FEATURE: return "Unimplemented";
case CRM_EX_INSUFFICIENT_PRIV: return "Insufficient privileges";
case CRM_EX_NOT_INSTALLED: return "Not installed";
case CRM_EX_NOT_CONFIGURED: return "Not configured";
case CRM_EX_NOT_RUNNING: return "Not running";
case CRM_EX_USAGE: return "Incorrect usage";
case CRM_EX_DATAERR: return "Invalid data given";
case CRM_EX_NOINPUT: return "Input file not available";
case CRM_EX_NOUSER: return "User does not exist";
case CRM_EX_NOHOST: return "Host does not exist";
case CRM_EX_UNAVAILABLE: return "Necessary service unavailable";
case CRM_EX_SOFTWARE: return "Internal software bug";
case CRM_EX_OSERR: return "Operating system error occurred";
case CRM_EX_OSFILE: return "System file not available";
case CRM_EX_CANTCREAT: return "Cannot create output file";
case CRM_EX_IOERR: return "I/O error occurred";
case CRM_EX_TEMPFAIL: return "Temporary failure, try again";
case CRM_EX_PROTOCOL: return "Protocol violated";
case CRM_EX_NOPERM: return "Insufficient privileges";
case CRM_EX_CONFIG: return "Invalid configuration";
case CRM_EX_FATAL: return "Fatal error occurred, will not respawn";
case CRM_EX_PANIC: return "System panic required";
case CRM_EX_DISCONNECT: return "Not connected";
case CRM_EX_DIGEST: return "Digest mismatch";
case CRM_EX_NOSUCH: return "No such object";
case CRM_EX_QUORUM: return "Quorum required";
case CRM_EX_UNSAFE: return "Operation not safe";
case CRM_EX_EXISTS: return "Requested item already exists";
case CRM_EX_MULTIPLE: return "Multiple items match request";
case CRM_EX_EXPIRED: return "Requested item has expired";
case CRM_EX_NOT_YET_IN_EFFECT: return "Requested item is not yet in effect";
case CRM_EX_INDETERMINATE: return "Could not determine status";
case CRM_EX_OLD: return "Update was older than existing configuration";
case CRM_EX_TIMEOUT: return "Timeout occurred";
case CRM_EX_MAX: return "Error occurred";
}
- if (exit_code > 128) {
+ if ((exit_code > 128) && (exit_code < CRM_EX_MAX)) {
return "Interrupted by signal";
}
return "Unknown exit status";
}
-/*!
- * \brief Map an errno to a similar exit status
- *
- * \param[in] errno Error number to map
- *
- * \return Exit status corresponding to errno
- */
+//! \deprecated Use standard return codes and pcmk_rc2exitc() instead
crm_exit_t
crm_errno2exit(int rc)
{
rc = abs(rc); // Convenience for functions that return -errno
- if (rc == EOPNOTSUPP) {
- rc = ENOTSUP; // Values are same on Linux, can't use both in case
- }
switch (rc) {
case pcmk_ok:
return CRM_EX_OK;
case pcmk_err_no_quorum:
return CRM_EX_QUORUM;
case pcmk_err_old_data:
return CRM_EX_OLD;
case pcmk_err_schema_validation:
case pcmk_err_transform_failed:
return CRM_EX_CONFIG;
case pcmk_err_bad_nvpair:
return CRM_EX_INVALID_PARAM;
+ case pcmk_err_already:
+ return CRM_EX_EXISTS;
+
+ case pcmk_err_multiple:
+ return CRM_EX_MULTIPLE;
+
+ case pcmk_err_node_unknown:
+ case pcmk_err_unknown_format:
+ return CRM_EX_NOSUCH;
+
+ default:
+ return pcmk_rc2exitc(rc); // system errno
+ }
+}
+
+/*!
+ * \brief Map a function return code to the most similar exit code
+ *
+ * \param[in] rc Function return code
+ *
+ * \return Most similar exit code
+ */
+crm_exit_t
+pcmk_rc2exitc(int rc)
+{
+ switch (rc) {
+ case pcmk_rc_ok:
+ return CRM_EX_OK;
+
+ case pcmk_rc_no_quorum:
+ return CRM_EX_QUORUM;
+
+ case pcmk_rc_old_data:
+ return CRM_EX_OLD;
+
+ case pcmk_rc_schema_validation:
+ case pcmk_rc_transform_failed:
+ return CRM_EX_CONFIG;
+
+ case pcmk_rc_bad_nvpair:
+ return CRM_EX_INVALID_PARAM;
+
case EACCES:
return CRM_EX_INSUFFICIENT_PRIV;
case EBADF:
case EINVAL:
case EFAULT:
case ENOSYS:
case EOVERFLOW:
return CRM_EX_SOFTWARE;
case EBADMSG:
case EMSGSIZE:
case ENOMSG:
case ENOPROTOOPT:
case EPROTO:
case EPROTONOSUPPORT:
case EPROTOTYPE:
return CRM_EX_PROTOCOL;
case ECOMM:
case ENOMEM:
return CRM_EX_OSERR;
case ECONNABORTED:
case ECONNREFUSED:
case ECONNRESET:
case ENOTCONN:
return CRM_EX_DISCONNECT;
case EEXIST:
- case pcmk_err_already:
+ case pcmk_rc_already:
return CRM_EX_EXISTS;
case EIO:
return CRM_EX_IOERR;
case ENOTSUP:
+#if EOPNOTSUPP != ENOTSUP
+ case EOPNOTSUPP:
+#endif
return CRM_EX_UNIMPLEMENT_FEATURE;
case ENOTUNIQ:
- case pcmk_err_multiple:
+ case pcmk_rc_multiple:
return CRM_EX_MULTIPLE;
case ENXIO:
- case pcmk_err_node_unknown:
- case pcmk_err_unknown_format:
+ case pcmk_rc_node_unknown:
+ case pcmk_rc_unknown_format:
return CRM_EX_NOSUCH;
case ETIME:
case ETIMEDOUT:
return CRM_EX_TIMEOUT;
default:
return CRM_EX_ERROR;
}
}
+// Other functions
+
const char *
bz2_strerror(int rc)
{
// See ftp://sources.redhat.com/pub/bzip2/docs/manual_3.html#SEC17
switch (rc) {
case BZ_OK:
case BZ_RUN_OK:
case BZ_FLUSH_OK:
case BZ_FINISH_OK:
case BZ_STREAM_END:
return "Ok";
case BZ_CONFIG_ERROR:
return "libbz2 has been improperly compiled on your platform";
case BZ_SEQUENCE_ERROR:
return "library functions called in the wrong order";
case BZ_PARAM_ERROR:
return "parameter is out of range or otherwise incorrect";
case BZ_MEM_ERROR:
return "memory allocation failed";
case BZ_DATA_ERROR:
return "data integrity error is detected during decompression";
case BZ_DATA_ERROR_MAGIC:
return "the compressed stream does not start with the correct magic bytes";
case BZ_IO_ERROR:
return "error reading or writing in the compressed file";
case BZ_UNEXPECTED_EOF:
return "compressed file finishes before the logical end of stream is detected";
case BZ_OUTBUFF_FULL:
return "output data will not fit into the buffer provided";
}
return "Unknown error";
}
crm_exit_t
crm_exit(crm_exit_t rc)
{
/* A compiler could theoretically use any type for crm_exit_t, but an int
* should always hold it, so cast to int to keep static analysis happy.
*/
if ((((int) rc) < 0) || (((int) rc) > CRM_EX_MAX)) {
rc = CRM_EX_ERROR;
}
mainloop_cleanup();
crm_xml_cleanup();
qb_log_fini();
crm_args_fini();
if (crm_system_name) {
crm_info("Exiting %s " CRM_XS " with status %d", crm_system_name, rc);
free(crm_system_name);
} else {
crm_trace("Exiting with status %d", rc);
}
exit(rc);
}
diff --git a/lib/lrmd/lrmd_alerts.c b/lib/lrmd/lrmd_alerts.c
index 8238e166c3..329ad755d8 100644
--- a/lib/lrmd/lrmd_alerts.c
+++ b/lib/lrmd/lrmd_alerts.c
@@ -1,388 +1,399 @@
/*
- * Copyright 2015-2019 the Pacemaker project contributors
+ * Copyright 2015-2020 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/msg_xml.h>
#include <crm/services.h>
#include <crm/common/mainloop.h>
#include <crm/common/alerts_internal.h>
#include <crm/common/iso8601_internal.h>
#include <crm/lrmd_internal.h>
#include <crm/pengine/status.h>
#include <crm/cib.h>
#include <crm/lrmd.h>
static lrmd_key_value_t *
-alert_key2param(lrmd_key_value_t *head, enum crm_alert_keys_e name,
+alert_key2param(lrmd_key_value_t *head, enum pcmk__alert_keys_e name,
const char *value)
{
const char **key;
if (value == NULL) {
value = "";
}
- for (key = crm_alert_keys[name]; *key; key++) {
+ for (key = pcmk__alert_keys[name]; *key; key++) {
crm_trace("Setting alert key %s = '%s'", *key, value);
head = lrmd_key_value_add(head, *key, value);
}
return head;
}
static lrmd_key_value_t *
-alert_key2param_int(lrmd_key_value_t *head, enum crm_alert_keys_e name,
+alert_key2param_int(lrmd_key_value_t *head, enum pcmk__alert_keys_e name,
int value)
{
char *value_s = crm_itoa(value);
head = alert_key2param(head, name, value_s);
free(value_s);
return head;
}
static lrmd_key_value_t *
-alert_key2param_ms(lrmd_key_value_t *head, enum crm_alert_keys_e name,
+alert_key2param_ms(lrmd_key_value_t *head, enum pcmk__alert_keys_e name,
guint value)
{
char *value_s = crm_strdup_printf("%u", value);
head = alert_key2param(head, name, value_s);
free(value_s);
return head;
}
static void
set_ev_kv(gpointer key, gpointer value, gpointer user_data)
{
lrmd_key_value_t **head = (lrmd_key_value_t **) user_data;
if (value) {
crm_trace("Setting environment variable %s='%s'",
(char*)key, (char*)value);
*head = lrmd_key_value_add(*head, key, value);
}
}
static lrmd_key_value_t *
-alert_envvar2params(lrmd_key_value_t *head, crm_alert_entry_t *entry)
+alert_envvar2params(lrmd_key_value_t *head, pcmk__alert_t *entry)
{
if (entry->envvars) {
g_hash_table_foreach(entry->envvars, set_ev_kv, &head);
}
return head;
}
/*
* We could use g_strv_contains() instead of this function,
* but that has only been available since glib 2.43.2.
*/
static gboolean
is_target_alert(char **list, const char *value)
{
int target_list_num = 0;
gboolean rc = FALSE;
CRM_CHECK(value != NULL, return FALSE);
if (list == NULL) {
return TRUE;
}
target_list_num = g_strv_length(list);
for (int cnt = 0; cnt < target_list_num; cnt++) {
if (strcmp(list[cnt], value) == 0) {
rc = TRUE;
break;
}
}
return rc;
}
/*!
* \internal
* \brief Execute alert agents for an event
*
* \param[in] lrmd Executor connection to use
* \param[in] alert_list Alerts to execute
* \param[in] kind Type of event that is being alerted for
- * \param[in] attr_name If crm_alert_attribute, the attribute name
+ * \param[in] attr_name If pcmk__alert_attribute, the attribute name
* \param[in,out] params Environment variables to pass to agents
*
* \retval pcmk_ok on success
* \retval -1 if some alerts failed
* \retval -2 if all alerts failed
*/
static int
-exec_alert_list(lrmd_t *lrmd, GList *alert_list, enum crm_alert_flags kind,
+exec_alert_list(lrmd_t *lrmd, GList *alert_list, enum pcmk__alert_flags kind,
const char *attr_name, lrmd_key_value_t *params)
{
bool any_success = FALSE, any_failure = FALSE;
- const char *kind_s = crm_alert_flag2text(kind);
+ const char *kind_s = pcmk__alert_flag2text(kind);
crm_time_hr_t *now = NULL;
struct timeval tv_now;
char timestamp_epoch[20];
char timestamp_usec[7];
- params = alert_key2param(params, CRM_alert_kind, kind_s);
- params = alert_key2param(params, CRM_alert_version, VERSION);
+ params = alert_key2param(params, PCMK__alert_key_kind, kind_s);
+ params = alert_key2param(params, PCMK__alert_key_version, VERSION);
for (GList *iter = g_list_first(alert_list); iter; iter = g_list_next(iter)) {
- crm_alert_entry_t *entry = (crm_alert_entry_t *)(iter->data);
+ pcmk__alert_t *entry = (pcmk__alert_t *)(iter->data);
lrmd_key_value_t *copy_params = NULL;
lrmd_key_value_t *head = NULL;
int rc;
if (is_not_set(entry->flags, kind)) {
crm_trace("Filtering unwanted %s alert to %s via %s",
kind_s, entry->recipient, entry->id);
continue;
}
- if ((kind == crm_alert_attribute)
+ if ((kind == pcmk__alert_attribute)
&& !is_target_alert(entry->select_attribute_name, attr_name)) {
crm_trace("Filtering unwanted attribute '%s' alert to %s via %s",
attr_name, entry->recipient, entry->id);
continue;
}
if (now == NULL) {
if (gettimeofday(&tv_now, NULL) == 0) {
now = crm_time_timeval_hr_convert(NULL, &tv_now);
}
}
crm_info("Sending %s alert via %s to %s",
kind_s, entry->id, entry->recipient);
/* Make a copy of the parameters, because each alert will be unique */
for (head = params; head != NULL; head = head->next) {
copy_params = lrmd_key_value_add(copy_params, head->key, head->value);
}
- copy_params = alert_key2param(copy_params, CRM_alert_recipient,
+ copy_params = alert_key2param(copy_params, PCMK__alert_key_recipient,
entry->recipient);
if (now) {
char *timestamp = crm_time_format_hr(entry->tstamp_format, now);
if (timestamp) {
- copy_params = alert_key2param(copy_params, CRM_alert_timestamp,
+ copy_params = alert_key2param(copy_params,
+ PCMK__alert_key_timestamp,
timestamp);
free(timestamp);
}
snprintf(timestamp_epoch, sizeof(timestamp_epoch), "%lld",
(long long) tv_now.tv_sec);
- copy_params = alert_key2param(copy_params, CRM_alert_timestamp_epoch, timestamp_epoch);
+ copy_params = alert_key2param(copy_params,
+ PCMK__alert_key_timestamp_epoch,
+ timestamp_epoch);
snprintf(timestamp_usec, sizeof(timestamp_usec), "%06d", now->useconds);
- copy_params = alert_key2param(copy_params, CRM_alert_timestamp_usec, timestamp_usec);
+ copy_params = alert_key2param(copy_params,
+ PCMK__alert_key_timestamp_usec,
+ timestamp_usec);
}
copy_params = alert_envvar2params(copy_params, entry);
rc = lrmd->cmds->exec_alert(lrmd, entry->id, entry->path,
entry->timeout, copy_params);
if (rc < 0) {
crm_err("Could not execute alert %s: %s " CRM_XS " rc=%d",
entry->id, pcmk_strerror(rc), rc);
any_failure = TRUE;
} else {
any_success = TRUE;
}
}
if (now) {
free(now);
}
if (any_failure) {
return (any_success? -1 : -2);
}
return pcmk_ok;
}
/*!
* \internal
* \brief Send an alert for a node attribute change
*
* \param[in] lrmd Executor connection to use
* \param[in] alert_list List of alert agents to execute
* \param[in] node Name of node with attribute change
* \param[in] nodeid Node ID of node with attribute change
* \param[in] attr_name Name of attribute that changed
* \param[in] attr_value New value of attribute that changed
*
* \retval pcmk_ok on success
* \retval -1 if some alert agents failed
* \retval -2 if all alert agents failed
*/
int
lrmd_send_attribute_alert(lrmd_t *lrmd, GList *alert_list,
const char *node, uint32_t nodeid,
const char *attr_name, const char *attr_value)
{
int rc = pcmk_ok;
lrmd_key_value_t *params = NULL;
if (lrmd == NULL) {
return -2;
}
- params = alert_key2param(params, CRM_alert_node, node);
- params = alert_key2param_int(params, CRM_alert_nodeid, nodeid);
- params = alert_key2param(params, CRM_alert_attribute_name, attr_name);
- params = alert_key2param(params, CRM_alert_attribute_value, attr_value);
+ params = alert_key2param(params, PCMK__alert_key_node, node);
+ params = alert_key2param_int(params, PCMK__alert_key_nodeid, nodeid);
+ params = alert_key2param(params, PCMK__alert_key_attribute_name, attr_name);
+ params = alert_key2param(params, PCMK__alert_key_attribute_value,
+ attr_value);
- rc = exec_alert_list(lrmd, alert_list, crm_alert_attribute, attr_name,
+ rc = exec_alert_list(lrmd, alert_list, pcmk__alert_attribute, attr_name,
params);
lrmd_key_value_freeall(params);
return rc;
}
/*!
* \internal
* \brief Send an alert for a node membership event
*
* \param[in] lrmd Executor connection to use
* \param[in] alert_list List of alert agents to execute
* \param[in] node Name of node with change
* \param[in] nodeid Node ID of node with change
* \param[in] state New state of node with change
*
* \retval pcmk_ok on success
* \retval -1 if some alert agents failed
* \retval -2 if all alert agents failed
*/
int
lrmd_send_node_alert(lrmd_t *lrmd, GList *alert_list,
const char *node, uint32_t nodeid, const char *state)
{
int rc = pcmk_ok;
lrmd_key_value_t *params = NULL;
if (lrmd == NULL) {
return -2;
}
- params = alert_key2param(params, CRM_alert_node, node);
- params = alert_key2param(params, CRM_alert_desc, state);
- params = alert_key2param_int(params, CRM_alert_nodeid, nodeid);
+ params = alert_key2param(params, PCMK__alert_key_node, node);
+ params = alert_key2param(params, PCMK__alert_key_desc, state);
+ params = alert_key2param_int(params, PCMK__alert_key_nodeid, nodeid);
- rc = exec_alert_list(lrmd, alert_list, crm_alert_node, NULL, params);
+ rc = exec_alert_list(lrmd, alert_list, pcmk__alert_node, NULL, params);
lrmd_key_value_freeall(params);
return rc;
}
/*!
* \internal
* \brief Send an alert for a fencing event
*
* \param[in] lrmd Executor connection to use
* \param[in] alert_list List of alert agents to execute
* \param[in] target Name of fence target node
* \param[in] task Type of fencing event that occurred
* \param[in] desc Readable description of event
* \param[in] op_rc Result of fence action
*
* \retval pcmk_ok on success
* \retval -1 if some alert agents failed
* \retval -2 if all alert agents failed
*/
int
lrmd_send_fencing_alert(lrmd_t *lrmd, GList *alert_list,
const char *target, const char *task, const char *desc,
int op_rc)
{
int rc = pcmk_ok;
lrmd_key_value_t *params = NULL;
if (lrmd == NULL) {
return -2;
}
- params = alert_key2param(params, CRM_alert_node, target);
- params = alert_key2param(params, CRM_alert_task, task);
- params = alert_key2param(params, CRM_alert_desc, desc);
- params = alert_key2param_int(params, CRM_alert_rc, op_rc);
+ params = alert_key2param(params, PCMK__alert_key_node, target);
+ params = alert_key2param(params, PCMK__alert_key_task, task);
+ params = alert_key2param(params, PCMK__alert_key_desc, desc);
+ params = alert_key2param_int(params, PCMK__alert_key_rc, op_rc);
- rc = exec_alert_list(lrmd, alert_list, crm_alert_fencing, NULL, params);
+ rc = exec_alert_list(lrmd, alert_list, pcmk__alert_fencing, NULL, params);
lrmd_key_value_freeall(params);
return rc;
}
/*!
* \internal
* \brief Send an alert for a resource operation
*
* \param[in] lrmd Executor connection to use
* \param[in] alert_list List of alert agents to execute
* \param[in] node Name of node that executed operation
* \param[in] op Resource operation
*
* \retval pcmk_ok on success
* \retval -1 if some alert agents failed
* \retval -2 if all alert agents failed
*/
int
lrmd_send_resource_alert(lrmd_t *lrmd, GList *alert_list,
const char *node, lrmd_event_data_t *op)
{
int rc = pcmk_ok;
int target_rc = pcmk_ok;
lrmd_key_value_t *params = NULL;
if (lrmd == NULL) {
return -2;
}
target_rc = rsc_op_expected_rc(op);
if ((op->interval_ms == 0) && (target_rc == op->rc)
&& safe_str_eq(op->op_type, RSC_STATUS)) {
/* Don't send alerts for probes with the expected result. Leave it up to
* the agent whether to alert for 'failed' probes. (Even if we find a
* resource running, it was probably because someone did a clean-up of
* the status section.)
*/
return pcmk_ok;
}
- params = alert_key2param(params, CRM_alert_node, node);
- params = alert_key2param(params, CRM_alert_rsc, op->rsc_id);
- params = alert_key2param(params, CRM_alert_task, op->op_type);
- params = alert_key2param_ms(params, CRM_alert_interval, op->interval_ms);
- params = alert_key2param_int(params, CRM_alert_target_rc, target_rc);
- params = alert_key2param_int(params, CRM_alert_status, op->op_status);
- params = alert_key2param_int(params, CRM_alert_rc, op->rc);
+ params = alert_key2param(params, PCMK__alert_key_node, node);
+ params = alert_key2param(params, PCMK__alert_key_rsc, op->rsc_id);
+ params = alert_key2param(params, PCMK__alert_key_task, op->op_type);
+ params = alert_key2param_ms(params, PCMK__alert_key_interval,
+ op->interval_ms);
+ params = alert_key2param_int(params, PCMK__alert_key_target_rc, target_rc);
+ params = alert_key2param_int(params, PCMK__alert_key_status, op->op_status);
+ params = alert_key2param_int(params, PCMK__alert_key_rc, op->rc);
/* Reoccurring operations do not set exec_time, so on timeout, set it
* to the operation timeout since that's closer to the actual value.
*/
if (op->op_status == PCMK_LRM_OP_TIMEOUT && op->exec_time == 0) {
- params = alert_key2param_int(params, CRM_alert_exec_time, op->timeout);
+ params = alert_key2param_int(params, PCMK__alert_key_exec_time,
+ op->timeout);
} else {
- params = alert_key2param_int(params, CRM_alert_exec_time, op->exec_time);
+ params = alert_key2param_int(params, PCMK__alert_key_exec_time,
+ op->exec_time);
}
if (op->op_status == PCMK_LRM_OP_DONE) {
- params = alert_key2param(params, CRM_alert_desc, services_ocf_exitcode_str(op->rc));
+ params = alert_key2param(params, PCMK__alert_key_desc,
+ services_ocf_exitcode_str(op->rc));
} else {
- params = alert_key2param(params, CRM_alert_desc, services_lrm_status_str(op->op_status));
+ params = alert_key2param(params, PCMK__alert_key_desc,
+ services_lrm_status_str(op->op_status));
}
- rc = exec_alert_list(lrmd, alert_list, crm_alert_resource, NULL, params);
+ rc = exec_alert_list(lrmd, alert_list, pcmk__alert_resource, NULL, params);
lrmd_key_value_freeall(params);
return rc;
}
diff --git a/lib/pengine/rules_alerts.c b/lib/pengine/rules_alerts.c
index 938ed13b9f..e7670876db 100644
--- a/lib/pengine/rules_alerts.c
+++ b/lib/pengine/rules_alerts.c
@@ -1,251 +1,251 @@
/*
- * Copyright 2015-2019 the Pacemaker project contributors
+ * Copyright 2015-2020 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/crm.h>
#include <crm/msg_xml.h>
#include <crm/pengine/rules.h>
#include <crm/common/alerts_internal.h>
#include <crm/pengine/rules_internal.h>
static void
-get_meta_attrs_from_cib(xmlNode *basenode, crm_alert_entry_t *entry,
+get_meta_attrs_from_cib(xmlNode *basenode, pcmk__alert_t *entry,
guint *max_timeout)
{
GHashTable *config_hash = crm_str_table_new();
crm_time_t *now = crm_time_new(NULL);
const char *value = NULL;
pe_unpack_nvpairs(basenode, basenode, XML_TAG_META_SETS, NULL, config_hash,
NULL, FALSE, now, NULL);
crm_time_free(now);
value = g_hash_table_lookup(config_hash, XML_ALERT_ATTR_TIMEOUT);
if (value) {
entry->timeout = crm_get_msec(value);
if (entry->timeout <= 0) {
if (entry->timeout == 0) {
crm_trace("Alert %s uses default timeout of %dmsec",
- entry->id, CRM_ALERT_DEFAULT_TIMEOUT_MS);
+ entry->id, PCMK__ALERT_DEFAULT_TIMEOUT_MS);
} else {
crm_warn("Alert %s has invalid timeout value '%s', using default %dmsec",
- entry->id, (char*)value, CRM_ALERT_DEFAULT_TIMEOUT_MS);
+ entry->id, (char*)value, PCMK__ALERT_DEFAULT_TIMEOUT_MS);
}
- entry->timeout = CRM_ALERT_DEFAULT_TIMEOUT_MS;
+ entry->timeout = PCMK__ALERT_DEFAULT_TIMEOUT_MS;
} else {
crm_trace("Alert %s uses timeout of %dmsec",
entry->id, entry->timeout);
}
if (entry->timeout > *max_timeout) {
*max_timeout = entry->timeout;
}
}
value = g_hash_table_lookup(config_hash, XML_ALERT_ATTR_TSTAMP_FORMAT);
if (value) {
/* hard to do any checks here as merely anything can
* can be a valid time-format-string
*/
entry->tstamp_format = strdup(value);
crm_trace("Alert %s uses timestamp format '%s'",
entry->id, entry->tstamp_format);
}
g_hash_table_destroy(config_hash);
}
static void
-get_envvars_from_cib(xmlNode *basenode, crm_alert_entry_t *entry)
+get_envvars_from_cib(xmlNode *basenode, pcmk__alert_t *entry)
{
xmlNode *child;
if ((basenode == NULL) || (entry == NULL)) {
return;
}
child = first_named_child(basenode, XML_TAG_ATTR_SETS);
if (child == NULL) {
return;
}
if (entry->envvars == NULL) {
entry->envvars = crm_str_table_new();
}
for (child = first_named_child(child, XML_CIB_TAG_NVPAIR); child != NULL;
child = crm_next_same_xml(child)) {
const char *name = crm_element_value(child, XML_NVPAIR_ATTR_NAME);
const char *value = crm_element_value(child, XML_NVPAIR_ATTR_VALUE);
if (value == NULL) {
value = "";
}
g_hash_table_insert(entry->envvars, strdup(name), strdup(value));
crm_trace("Alert %s: added environment variable %s='%s'",
entry->id, name, value);
}
}
static void
-unpack_alert_filter(xmlNode *basenode, crm_alert_entry_t *entry)
+unpack_alert_filter(xmlNode *basenode, pcmk__alert_t *entry)
{
xmlNode *select = first_named_child(basenode, XML_CIB_TAG_ALERT_SELECT);
xmlNode *event_type = NULL;
- uint32_t flags = crm_alert_none;
+ uint32_t flags = pcmk__alert_none;
for (event_type = __xml_first_child_element(select); event_type != NULL;
event_type = __xml_next_element(event_type)) {
const char *tagname = crm_element_name(event_type);
if (tagname == NULL) {
continue;
} else if (!strcmp(tagname, XML_CIB_TAG_ALERT_FENCING)) {
- flags |= crm_alert_fencing;
+ flags |= pcmk__alert_fencing;
} else if (!strcmp(tagname, XML_CIB_TAG_ALERT_NODES)) {
- flags |= crm_alert_node;
+ flags |= pcmk__alert_node;
} else if (!strcmp(tagname, XML_CIB_TAG_ALERT_RESOURCES)) {
- flags |= crm_alert_resource;
+ flags |= pcmk__alert_resource;
} else if (!strcmp(tagname, XML_CIB_TAG_ALERT_ATTRIBUTES)) {
xmlNode *attr;
const char *attr_name;
int nattrs = 0;
- flags |= crm_alert_attribute;
+ flags |= pcmk__alert_attribute;
for (attr = first_named_child(event_type, XML_CIB_TAG_ALERT_ATTR);
attr != NULL;
attr = crm_next_same_xml(attr)) {
attr_name = crm_element_value(attr, XML_NVPAIR_ATTR_NAME);
if (attr_name) {
if (nattrs == 0) {
g_strfreev(entry->select_attribute_name);
entry->select_attribute_name = NULL;
}
++nattrs;
entry->select_attribute_name = realloc_safe(entry->select_attribute_name,
(nattrs + 1) * sizeof(char*));
entry->select_attribute_name[nattrs - 1] = strdup(attr_name);
entry->select_attribute_name[nattrs] = NULL;
}
}
}
}
- if (flags != crm_alert_none) {
+ if (flags != pcmk__alert_none) {
entry->flags = flags;
- crm_debug("Alert %s receives events: attributes:%s, fencing:%s, nodes:%s, resources:%s",
+ crm_debug("Alert %s receives events: attributes:%s%s%s%s",
entry->id,
- (flags & crm_alert_attribute)?
- (entry->select_attribute_name? "some" : "all") : "no",
- (flags & crm_alert_fencing)? "yes" : "no",
- (flags & crm_alert_node)? "yes" : "no",
- (flags & crm_alert_resource)? "yes" : "no");
+ (is_set(flags, pcmk__alert_attribute)?
+ (entry->select_attribute_name? "some" : "all") : "none"),
+ (is_set(flags, pcmk__alert_fencing)? " fencing" : ""),
+ (is_set(flags, pcmk__alert_node)? " nodes" : ""),
+ (is_set(flags, pcmk__alert_resource)? " resources" : ""));
}
}
static void
-unpack_alert(xmlNode *alert, crm_alert_entry_t *entry, guint *max_timeout)
+unpack_alert(xmlNode *alert, pcmk__alert_t *entry, guint *max_timeout)
{
get_envvars_from_cib(alert, entry);
get_meta_attrs_from_cib(alert, entry, max_timeout);
unpack_alert_filter(alert, entry);
}
/*!
* \internal
* \brief Unpack a CIB alerts section
*
* \param[in] alerts XML of alerts section
*
* \return List of unpacked alert entries
*
* \note Unlike most unpack functions, this is not used by the scheduler itself,
* but is supplied for use by daemons that need to send alerts.
*/
GListPtr
pe_unpack_alerts(xmlNode *alerts)
{
xmlNode *alert;
- crm_alert_entry_t *entry;
+ pcmk__alert_t *entry;
guint max_timeout = 0;
GListPtr alert_list = NULL;
if (alerts == NULL) {
return alert_list;
}
for (alert = first_named_child(alerts, XML_CIB_TAG_ALERT);
alert != NULL; alert = crm_next_same_xml(alert)) {
xmlNode *recipient;
int recipients = 0;
const char *alert_id = ID(alert);
const char *alert_path = crm_element_value(alert, XML_ALERT_ATTR_PATH);
/* The schema should enforce this, but to be safe ... */
if ((alert_id == NULL) || (alert_path == NULL)) {
crm_warn("Ignoring invalid alert without id and path");
continue;
}
- entry = crm_alert_entry_new(alert_id, alert_path);
+ entry = pcmk__alert_new(alert_id, alert_path);
unpack_alert(alert, entry, &max_timeout);
if (entry->tstamp_format == NULL) {
- entry->tstamp_format = strdup(CRM_ALERT_DEFAULT_TSTAMP_FORMAT);
+ entry->tstamp_format = strdup(PCMK__ALERT_DEFAULT_TSTAMP_FORMAT);
}
crm_debug("Alert %s: path=%s timeout=%dms tstamp-format='%s' %u vars",
entry->id, entry->path, entry->timeout, entry->tstamp_format,
(entry->envvars? g_hash_table_size(entry->envvars) : 0));
for (recipient = first_named_child(alert, XML_CIB_TAG_ALERT_RECIPIENT);
recipient != NULL; recipient = crm_next_same_xml(recipient)) {
- crm_alert_entry_t *recipient_entry = crm_dup_alert_entry(entry);
+ pcmk__alert_t *recipient_entry = pcmk__dup_alert(entry);
recipients++;
recipient_entry->recipient = strdup(crm_element_value(recipient,
XML_ALERT_ATTR_REC_VALUE));
unpack_alert(recipient, recipient_entry, &max_timeout);
alert_list = g_list_prepend(alert_list, recipient_entry);
crm_debug("Alert %s has recipient %s with value %s and %d envvars",
entry->id, ID(recipient), recipient_entry->recipient,
(recipient_entry->envvars?
g_hash_table_size(recipient_entry->envvars) : 0));
}
if (recipients == 0) {
alert_list = g_list_prepend(alert_list, entry);
} else {
- crm_free_alert_entry(entry);
+ pcmk__free_alert(entry);
}
}
return alert_list;
}
/*!
* \internal
* \brief Free an alert list generated by pe_unpack_alerts()
*
* \param[in] alert_list Alert list to free
*/
void
pe_free_alert_list(GListPtr alert_list)
{
if (alert_list) {
- g_list_free_full(alert_list, (GDestroyNotify) crm_free_alert_entry);
+ g_list_free_full(alert_list, (GDestroyNotify) pcmk__free_alert);
}
}
diff --git a/tools/attrd_updater.c b/tools/attrd_updater.c
index 3dfec5c93b..ebba945404 100644
--- a/tools/attrd_updater.c
+++ b/tools/attrd_updater.c
@@ -1,378 +1,380 @@
/*
- * Copyright 2004-2019 the Pacemaker project contributors
+ * Copyright 2004-2020 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 <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <libgen.h>
#include <sys/param.h>
#include <sys/types.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/ipc.h>
-#include <crm/attrd.h>
+#include <crm/common/attrd_internal.h>
/* *INDENT-OFF* */
static struct crm_option long_options[] = {
/* Top-level Options */
{"help", 0, 0, '?', "\tThis text"},
{"version", 0, 0, '$', "\tVersion information" },
{"verbose", 0, 0, 'V', "\tIncrease debug output\n"},
{"name", 1, 0, 'n', "The attribute's name"},
{"-spacer-",1, 0, '-', "\nCommands:"},
{"update", 1, 0, 'U', "Update the attribute's value in pacemaker-attrd. If this causes the value to change, it will also be updated in the cluster configuration"},
{"update-both", 1, 0, 'B', "Update the attribute's value and time to wait (dampening) in pacemaker-attrd. If this causes the value or dampening to change, the attribute will also be written to the cluster configuration, so be aware that repeatedly changing the dampening reduces its effectiveness."},
{"update-delay", 0, 0, 'Y', "Update the attribute's dampening in pacemaker-attrd (requires -d/--delay). If this causes the dampening to change, the attribute will also be written to the cluster configuration, so be aware that repeatedly changing the dampening reduces its effectiveness."},
{"query", 0, 0, 'Q', "\tQuery the attribute's value from pacemaker-attrd"},
{"delete", 0, 0, 'D', "\tDelete the attribute in pacemaker-attrd. If a value was previously set, it will also be removed from the cluster configuration"},
{"refresh", 0, 0, 'R', "\t(Advanced) Force the pacemaker-attrd daemon to resend all current values to the CIB\n"},
{"-spacer-",1, 0, '-', "\nAdditional options:"},
{"delay", 1, 0, 'd', "The time to wait (dampening) in seconds for further changes before writing"},
{"set", 1, 0, 's', "(Advanced) The attribute set in which to place the value"},
{"node", 1, 0, 'N', "Set the attribute for the named node (instead of the local one)"},
{"all", 0, 0, 'A', "Show values of the attribute for all nodes (query only)"},
/* lifetime could be implemented if there is sufficient user demand */
{"lifetime",1, 0, 'l', "(Deprecated) Lifetime of the node attribute (silently ignored by cluster)"},
{"private", 0, 0, 'p', "\tIf this creates a new attribute, never write the attribute to the CIB"},
/* Legacy options */
{"quiet", 0, 0, 'q', NULL, pcmk_option_hidden},
{"update", 1, 0, 'v', NULL, pcmk_option_hidden},
{"section", 1, 0, 'S', NULL, pcmk_option_hidden},
{0, 0, 0, 0}
};
/* *INDENT-ON* */
static int do_query(const char *attr_name, const char *attr_node, gboolean query_all);
static int do_update(char command, const char *attr_node, const char *attr_name,
const char *attr_value, const char *attr_section,
const char *attr_set, const char *attr_dampen, int attr_options);
// Free memory at exit to make analyzers happy
#define cleanup_memory() \
free(attr_dampen); \
free(attr_name); \
free(attr_node); \
free(attr_section); \
free(attr_set);
#define set_option(option_var) \
if (option_var) { \
free(option_var); \
} \
option_var = strdup(optarg);
int
main(int argc, char **argv)
{
int index = 0;
int argerr = 0;
- int attr_options = attrd_opt_none;
+ int attr_options = pcmk__node_attr_none;
int flag;
crm_exit_t exit_code = CRM_EX_OK;
char *attr_node = NULL;
char *attr_name = NULL;
char *attr_set = NULL;
char *attr_section = NULL;
char *attr_dampen = NULL;
const char *attr_value = NULL;
char command = 'Q';
gboolean query_all = FALSE;
crm_log_cli_init("attrd_updater");
crm_set_options(NULL, "command -n attribute [options]", long_options,
"Tool for updating cluster node attributes");
if (argc < 2) {
crm_help('?', CRM_EX_USAGE);
}
while (1) {
flag = crm_get_option(argc, argv, &index);
if (flag == -1)
break;
switch (flag) {
case 'V':
crm_bump_log_level(argc, argv);
break;
case '?':
case '$':
cleanup_memory();
crm_help(flag, CRM_EX_OK);
break;
case 'n':
set_option(attr_name);
break;
case 's':
set_option(attr_set);
break;
case 'd':
set_option(attr_dampen);
break;
case 'l':
case 'S':
set_option(attr_section);
break;
case 'N':
set_option(attr_node);
break;
case 'A':
query_all = TRUE;
break;
case 'p':
- set_bit(attr_options, attrd_opt_private);
+ set_bit(attr_options, pcmk__node_attr_private);
break;
case 'q':
break;
case 'Y':
command = flag;
crm_log_args(argc, argv); /* Too much? */
break;
case 'Q':
case 'B':
case 'R':
case 'D':
case 'U':
case 'v':
command = flag;
attr_value = optarg;
crm_log_args(argc, argv); /* Too much? */
break;
default:
++argerr;
break;
}
}
if (optind > argc) {
++argerr;
}
if (command != 'R' && attr_name == NULL) {
++argerr;
}
if (argerr) {
cleanup_memory();
crm_help('?', CRM_EX_USAGE);
}
if (command == 'Q') {
exit_code = crm_errno2exit(do_query(attr_name, attr_node, query_all));
} else {
/* @TODO We don't know whether the specified node is a Pacemaker Remote
- * node or not, so we can't set attrd_opt_remote when appropriate.
+ * node or not, so we can't set pcmk__node_attr_remote when appropriate.
* However, it's not a big problem, because pacemaker-attrd will learn
* and remember a node's "remoteness".
*/
- exit_code = crm_errno2exit(do_update(command,
- attrd_get_target(attr_node), attr_name,
- attr_value, attr_section, attr_set,
- attr_dampen, attr_options));
+ exit_code = pcmk_rc2exitc(do_update(command,
+ pcmk__node_attr_target(attr_node),
+ attr_name, attr_value,
+ attr_section, attr_set,
+ attr_dampen, attr_options));
}
cleanup_memory();
crm_exit(exit_code);
}
/*!
* \internal
* \brief Submit a query request to pacemaker-attrd and wait for reply
*
* \param[in] name Name of attribute to query
* \param[in] host Query applies to this host only (or all hosts if NULL)
* \param[out] reply On success, will be set to new XML tree with reply
*
* \return pcmk_ok on success, -errno on error
* \note On success, caller is responsible for freeing result via free_xml(*reply)
*/
static int
send_attrd_query(const char *name, const char *host, xmlNode **reply)
{
int rc;
crm_ipc_t *ipc;
xmlNode *query;
/* Build the query XML */
query = create_xml_node(NULL, __FUNCTION__);
if (query == NULL) {
return -ENOMEM;
}
crm_xml_add(query, F_TYPE, T_ATTRD);
crm_xml_add(query, F_ORIG, crm_system_name);
crm_xml_add(query, F_ATTRD_HOST, host);
crm_xml_add(query, F_ATTRD_TASK, ATTRD_OP_QUERY);
crm_xml_add(query, F_ATTRD_ATTRIBUTE, name);
/* Connect to pacemaker-attrd, send query XML and get reply */
crm_debug("Sending query for value of %s on %s", name, (host? host : "all nodes"));
ipc = crm_ipc_new(T_ATTRD, 0);
if (crm_ipc_connect(ipc) == FALSE) {
crm_perror(LOG_ERR, "Connection to cluster attribute manager failed");
rc = -ENOTCONN;
} else {
rc = crm_ipc_send(ipc, query, crm_ipc_flags_none|crm_ipc_client_response, 0, reply);
if (rc > 0) {
rc = pcmk_ok;
}
crm_ipc_close(ipc);
}
free_xml(query);
return(rc);
}
/*!
* \brief Validate pacemaker-attrd's XML reply to an query
*
* param[in] reply Root of reply XML tree to validate
* param[in] attr_name Name of attribute that was queried
*
* \return pcmk_ok on success,
* -errno on error (-ENXIO = requested attribute does not exist)
*/
static int
validate_attrd_reply(xmlNode *reply, const char *attr_name)
{
const char *reply_attr;
if (reply == NULL) {
fprintf(stderr, "Could not query value of %s: reply did not contain valid XML\n",
attr_name);
return -pcmk_err_schema_validation;
}
crm_log_xml_trace(reply, "Reply");
reply_attr = crm_element_value(reply, F_ATTRD_ATTRIBUTE);
if (reply_attr == NULL) {
fprintf(stderr, "Could not query value of %s: attribute does not exist\n",
attr_name);
return -ENXIO;
}
if (safe_str_neq(crm_element_value(reply, F_TYPE), T_ATTRD)
|| (crm_element_value(reply, F_ATTRD_VERSION) == NULL)
|| strcmp(reply_attr, attr_name)) {
fprintf(stderr,
"Could not query value of %s: reply did not contain expected identification\n",
attr_name);
return -pcmk_err_schema_validation;
}
return pcmk_ok;
}
/*!
* \brief Print the attribute values in a pacemaker-attrd XML query reply
*
* \param[in] reply Root of XML tree with query reply
* \param[in] attr_name Name of attribute that was queried
*
* \return TRUE if any values were printed
*/
static gboolean
print_attrd_values(xmlNode *reply, const char *attr_name)
{
xmlNode *child;
const char *reply_host, *reply_value;
gboolean have_values = FALSE;
/* Iterate through reply's XML tags (a node tag for each host-value pair) */
for (child = __xml_first_child(reply); child != NULL; child = __xml_next(child)) {
if (safe_str_neq((const char*)child->name, XML_CIB_TAG_NODE)) {
crm_warn("Ignoring unexpected %s tag in query reply", child->name);
} else {
reply_host = crm_element_value(child, F_ATTRD_HOST);
reply_value = crm_element_value(child, F_ATTRD_VALUE);
if (reply_host == NULL) {
crm_warn("Ignoring %s tag without %s attribute in query reply",
XML_CIB_TAG_NODE, F_ATTRD_HOST);
} else {
printf("name=\"%s\" host=\"%s\" value=\"%s\"\n",
attr_name, reply_host, (reply_value? reply_value : ""));
have_values = TRUE;
}
}
}
return have_values;
}
/*!
* \brief Submit a query to pacemaker-attrd and print reply
*
* \param[in] attr_name Name of attribute to be affected by request
* \param[in] attr_node Name of host to query for (or NULL for localhost)
* \param[in] query_all If TRUE, ignore attr_node and query all nodes instead
*
* \return pcmk_ok on success, -errno on error
*/
static int
do_query(const char *attr_name, const char *attr_node, gboolean query_all)
{
xmlNode *reply = NULL;
int rc;
/* Decide which node(s) to query */
if (query_all == TRUE) {
attr_node = NULL;
} else {
- attr_node = attrd_get_target(attr_node);
+ attr_node = pcmk__node_attr_target(attr_node);
}
/* Build and send pacemaker-attrd request, and get XML reply */
rc = send_attrd_query(attr_name, attr_node, &reply);
if (rc != pcmk_ok) {
fprintf(stderr, "Could not query value of %s: %s (%d)\n", attr_name, pcmk_strerror(rc), rc);
return rc;
}
/* Validate the XML reply */
rc = validate_attrd_reply(reply, attr_name);
if (rc != pcmk_ok) {
if (reply != NULL) {
free_xml(reply);
}
return rc;
}
/* Print the values from the reply */
if (print_attrd_values(reply, attr_name) == FALSE) {
fprintf(stderr,
"Could not query value of %s: reply had attribute name but no host values\n",
attr_name);
free_xml(reply);
return -pcmk_err_schema_validation;
}
return pcmk_ok;
}
static int
do_update(char command, const char *attr_node, const char *attr_name,
const char *attr_value, const char *attr_section,
const char *attr_set, const char *attr_dampen, int attr_options)
{
- int rc = attrd_update_delegate(NULL, command, attr_node, attr_name,
- attr_value, attr_section, attr_set,
- attr_dampen, NULL, attr_options);
- if (rc != pcmk_ok) {
- fprintf(stderr, "Could not update %s=%s: %s (%d)\n", attr_name, attr_value, pcmk_strerror(rc), rc);
+ int rc = pcmk__node_attr_request(NULL, command, attr_node, attr_name,
+ attr_value, attr_section, attr_set,
+ attr_dampen, NULL, attr_options);
+ if (rc != pcmk_rc_ok) {
+ fprintf(stderr, "Could not update %s=%s: %s (%d)\n",
+ attr_name, attr_value, pcmk_rc_str(rc), rc);
}
return rc;
}
diff --git a/tools/crm_attribute.c b/tools/crm_attribute.c
index 9892c06688..37274caebc 100644
--- a/tools/crm_attribute.c
+++ b/tools/crm_attribute.c
@@ -1,350 +1,354 @@
/*
- * Copyright 2004-2019 the Pacemaker project contributors
+ * Copyright 2004-2020 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 <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <time.h>
#include <sys/param.h>
#include <sys/types.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/common/ipc.h>
#include <crm/common/util.h>
#include <crm/cluster.h>
#include <crm/cib.h>
-#include <crm/attrd.h>
+#include <crm/common/attrd_internal.h>
#include <sys/utsname.h>
gboolean BE_QUIET = FALSE;
char command = 'G';
const char *dest_uname = NULL;
char *dest_node = NULL;
char *set_name = NULL;
char *attr_id = NULL;
char *attr_name = NULL;
char *attr_pattern = NULL;
const char *type = NULL;
const char *rsc_id = NULL;
const char *attr_value = NULL;
const char *attr_default = NULL;
const char *set_type = NULL;
/* *INDENT-OFF* */
static struct crm_option long_options[] = {
/* Top-level Options */
{"help", 0, 0, '?', "\tThis text"},
{"version", 0, 0, '$', "\tVersion information" },
{"verbose", 0, 0, 'V', "\tIncrease debug output"},
{"quiet", 0, 0, 'q', "\tPrint only the value on stdout\n"},
{"name", 1, 0, 'n', "Name of the attribute/option to operate on"},
{"pattern", 1, 0, 'P', "Pattern matching names of attributes (only with -v/-D and -l reboot)"},
{"-spacer-", 0, 0, '-', "\nCommands:"},
{"query", 0, 0, 'G', "\tQuery the current value of the attribute/option"},
{"update", 1, 0, 'v', "Update the value of the attribute/option"},
{"delete", 0, 0, 'D', "\tDelete the attribute/option"},
{"-spacer-", 0, 0, '-', "\nAdditional Options:"},
{"node", 1, 0, 'N', "Set an attribute for the named node (instead of a cluster option). See also: -l"},
{"type", 1, 0, 't', "Which part of the configuration to update/delete/query the option in"},
{"-spacer-", 0, 0, '-', "\t\t\tValid values: crm_config, rsc_defaults, op_defaults, tickets"},
{"lifetime", 1, 0, 'l', "Lifetime of the node attribute"},
{"-spacer-", 0, 0, '-', "\t\t\tValid values: reboot, forever"},
{"utilization", 0, 0, 'z', "Set an utilization attribute for the node."},
{"set-name", 1, 0, 's', "(Advanced) The attribute set in which to place the value"},
{"id", 1, 0, 'i', "\t(Advanced) The ID used to identify the attribute"},
{"default", 1, 0, 'd', "(Advanced) The default value to display if none is found in the configuration"},
{"inhibit-policy-engine", 0, 0, '!', NULL, 1},
/* legacy */
{"quiet", 0, 0, 'Q', NULL, 1},
{"node-uname", 1, 0, 'U', NULL, 1},
{"get-value", 0, 0, 'G', NULL, 1},
{"delete-attr", 0, 0, 'D', NULL, 1},
{"attr-value", 1, 0, 'v', NULL, 1},
{"attr-name", 1, 0, 'n', NULL, 1},
{"attr-id", 1, 0, 'i', NULL, 1},
{"-spacer-", 1, 0, '-', "\nExamples:", pcmk_option_paragraph},
{"-spacer-", 1, 0, '-', "Add a new node attribute called 'location' with the value of 'office' for host 'myhost':", pcmk_option_paragraph},
{"-spacer-", 1, 0, '-', " crm_attribute --node myhost --name location --update office", pcmk_option_example},
{"-spacer-", 1, 0, '-', "Query the value of the 'location' node attribute for host 'myhost':", pcmk_option_paragraph},
{"-spacer-", 1, 0, '-', " crm_attribute --node myhost --name location --query", pcmk_option_example},
{"-spacer-", 1, 0, '-', "Change the value of the 'location' node attribute for host 'myhost':", pcmk_option_paragraph},
{"-spacer-", 1, 0, '-', " crm_attribute --node myhost --name location --update backoffice", pcmk_option_example},
{"-spacer-", 1, 0, '-', "Delete the 'location' node attribute for host 'myhost':", pcmk_option_paragraph},
{"-spacer-", 1, 0, '-', " crm_attribute --node myhost --name location --delete", pcmk_option_example},
{"-spacer-", 1, 0, '-', "Query the value of the cluster-delay cluster option:", pcmk_option_paragraph},
{"-spacer-", 1, 0, '-', " crm_attribute --type crm_config --name cluster-delay --query", pcmk_option_example},
{"-spacer-", 1, 0, '-', "Query the value of the cluster-delay cluster option. Only print the value:", pcmk_option_paragraph},
{"-spacer-", 1, 0, '-', " crm_attribute --type crm_config --name cluster-delay --query --quiet", pcmk_option_example},
{0, 0, 0, 0}
};
/* *INDENT-ON* */
int
main(int argc, char **argv)
{
cib_t *the_cib = NULL;
int rc = pcmk_ok;
int cib_opts = cib_sync_call;
int argerr = 0;
int flag;
int option_index = 0;
int is_remote_node = 0;
bool try_attrd = true;
+ int attrd_opts = pcmk__node_attr_none;
crm_log_cli_init("crm_attribute");
crm_set_options(NULL, "<command> -n <attribute> [options]", long_options,
"Manage node's attributes and cluster options."
"\n\nAllows node attributes and cluster options to be queried, modified and deleted.\n");
if (argc < 2) {
crm_help('?', CRM_EX_USAGE);
}
while (1) {
flag = crm_get_option(argc, argv, &option_index);
if (flag == -1)
break;
switch (flag) {
case 'V':
crm_bump_log_level(argc, argv);
break;
case '$':
case '?':
crm_help(flag, CRM_EX_OK);
break;
case 'G':
command = flag;
attr_value = optarg;
break;
case 'D':
case 'v':
command = flag;
attr_value = optarg;
crm_log_args(argc, argv);
break;
case 'q':
case 'Q':
BE_QUIET = TRUE;
break;
case 'U':
case 'N':
dest_uname = strdup(optarg);
break;
case 's':
set_name = strdup(optarg);
break;
case 'l':
case 't':
type = optarg;
break;
case 'z':
type = XML_CIB_TAG_NODES;
set_type = XML_TAG_UTILIZATION;
break;
case 'n':
attr_name = strdup(optarg);
break;
case 'P':
attr_pattern = strdup(optarg);
break;
case 'i':
attr_id = strdup(optarg);
break;
case 'r':
rsc_id = optarg;
break;
case 'd':
attr_default = optarg;
break;
case '!':
crm_warn("Inhibiting notifications for this update");
cib_opts |= cib_inhibit_notify;
break;
default:
printf("Argument code 0%o (%c) is not (?yet?) supported\n", flag, flag);
++argerr;
break;
}
}
if (optind < argc) {
printf("non-option ARGV-elements: ");
while (optind < argc)
printf("%s ", argv[optind++]);
printf("\n");
}
if (optind > argc) {
++argerr;
}
if (argerr) {
crm_help('?', CRM_EX_USAGE);
}
the_cib = cib_new();
rc = the_cib->cmds->signon(the_cib, crm_system_name, cib_command);
if (rc != pcmk_ok) {
fprintf(stderr, "Error connecting to the CIB manager: %s\n",
pcmk_strerror(rc));
crm_exit(crm_errno2exit(rc));
}
if (type == NULL && dest_uname != NULL) {
type = "forever";
}
if (safe_str_eq(type, "reboot")) {
type = XML_CIB_TAG_STATUS;
} else if (safe_str_eq(type, "forever")) {
type = XML_CIB_TAG_NODES;
}
if (type == NULL && dest_uname == NULL) {
/* we're updating cluster options - don't populate dest_node */
type = XML_CIB_TAG_CRMCONFIG;
} else if (safe_str_eq(type, XML_CIB_TAG_CRMCONFIG)) {
} else if (safe_str_neq(type, XML_CIB_TAG_TICKETS)) {
/* If we are being called from a resource agent via the cluster,
* the correct local node name will be passed as an environment
* variable. Otherwise, we have to ask the cluster.
*/
- dest_uname = attrd_get_target(dest_uname);
+ dest_uname = pcmk__node_attr_target(dest_uname);
if (dest_uname == NULL) {
dest_uname = get_local_node_name();
}
rc = query_node_uuid(the_cib, dest_uname, &dest_node, &is_remote_node);
if (pcmk_ok != rc) {
fprintf(stderr, "Could not map name=%s to a UUID\n", dest_uname);
the_cib->cmds->signoff(the_cib);
cib_delete(the_cib);
crm_exit(crm_errno2exit(rc));
}
}
if ((command == 'D') && (attr_name == NULL) && (attr_pattern == NULL)) {
fprintf(stderr, "Error: must specify attribute name or pattern to delete\n");
crm_exit(CRM_EX_USAGE);
}
if (attr_pattern) {
if (((command != 'v') && (command != 'D'))
|| safe_str_neq(type, XML_CIB_TAG_STATUS)) {
fprintf(stderr, "Error: pattern can only be used with till-reboot update or delete\n");
crm_exit(CRM_EX_USAGE);
}
command = 'u';
free(attr_name);
attr_name = attr_pattern;
}
// Only go through attribute manager for transient attributes
try_attrd = safe_str_eq(type, XML_CIB_TAG_STATUS);
// Don't try to contact attribute manager if we're using a file as CIB
if (getenv("CIB_file") || getenv("CIB_shadow")) {
try_attrd = FALSE;
}
+ if (is_remote_node) {
+ attrd_opts = pcmk__node_attr_remote;
+ }
if (((command == 'v') || (command == 'D') || (command == 'u')) && try_attrd
- && pcmk_ok == attrd_update_delegate(NULL, command, dest_uname, attr_name,
- attr_value, type, set_name, NULL, NULL,
- is_remote_node?attrd_opt_remote:attrd_opt_none)) {
+ && (pcmk__node_attr_request(NULL, command, dest_uname, attr_name,
+ attr_value, type, set_name, NULL, NULL,
+ attrd_opts) == pcmk_rc_ok)) {
crm_info("Update %s=%s sent via pacemaker-attrd",
attr_name, ((command == 'D')? "<none>" : attr_value));
} else if (command == 'D') {
rc = delete_attr_delegate(the_cib, cib_opts, type, dest_node, set_type, set_name,
attr_id, attr_name, attr_value, TRUE, NULL);
if (rc == -ENXIO) {
/* Nothing to delete...
* which means it's not there...
* which is what the admin wanted
*/
rc = pcmk_ok;
}
} else if (command == 'v') {
CRM_LOG_ASSERT(type != NULL);
CRM_LOG_ASSERT(attr_name != NULL);
CRM_LOG_ASSERT(attr_value != NULL);
rc = update_attr_delegate(the_cib, cib_opts, type, dest_node, set_type, set_name,
attr_id, attr_name, attr_value, TRUE, NULL, is_remote_node ? "remote" : NULL);
} else { /* query */
char *read_value = NULL;
rc = read_attr_delegate(the_cib, type, dest_node, set_type, set_name,
attr_id, attr_name, &read_value, TRUE, NULL);
if (rc == -ENXIO && attr_default) {
read_value = strdup(attr_default);
rc = pcmk_ok;
}
crm_info("Read %s=%s %s%s",
attr_name, crm_str(read_value), set_name ? "in " : "", set_name ? set_name : "");
if (rc == -ENOTUNIQ) {
// Multiple matches (already displayed) are not error for queries
rc = pcmk_ok;
} else if (BE_QUIET == FALSE) {
fprintf(stdout, "%s%s %s%s %s%s value=%s\n",
type ? "scope=" : "", type ? type : "",
attr_id ? "id=" : "", attr_id ? attr_id : "",
attr_name ? "name=" : "", attr_name ? attr_name : "",
read_value ? read_value : "(null)");
} else if (read_value != NULL) {
fprintf(stdout, "%s\n", read_value);
}
free(read_value);
}
if (rc == -ENOTUNIQ) {
printf("Please choose from one of the matches above and supply the 'id' with --attr-id\n");
} else if (rc != pcmk_ok) {
fprintf(stderr, "Error performing operation: %s\n", pcmk_strerror(rc));
}
the_cib->cmds->signoff(the_cib);
cib_delete(the_cib);
crm_exit(crm_errno2exit(rc));
}
diff --git a/tools/crm_error.c b/tools/crm_error.c
index f6dc73cd4a..0dcae056dc 100644
--- a/tools/crm_error.c
+++ b/tools/crm_error.c
@@ -1,113 +1,139 @@
-/*
- * Copyright 2012-2018 the Pacemaker project contributors
+/*
+ * Copyright 2012-2020 the Pacemaker project contributors
*
* The version control history for this file may have further details.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * 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>
/* *INDENT-OFF* */
static struct crm_option long_options[] = {
/* Top-level Options */
{"help", 0, 0, '?', "\tThis text"},
{"version", 0, 0, '$', "\tVersion information" },
{"verbose", 0, 0, 'V', "\tIncrease debug output"},
{"name", 0, 0, 'n', "\tShow the error's name with its description."
"\n\t\t\tUseful for looking for sources of the error in source code"},
{"list", 0, 0, 'l', "\tShow all known errors."},
- {"exit", 0, 0, 'X', "\tInterpret as exit code rather than function return value"},
+ {"exit", 0, 0, 'X', "\tInterpret as exit code rather than legacy function return value"},
+ {"rc", 0, 0, 'r', "\tInterpret as return code rather than legacy function return value"},
{0, 0, 0, 0}
};
/* *INDENT-ON* */
+static bool as_exit_code = false;
+static bool as_rc = false;
+
+static void
+get_strings(int rc, const char **name, const char **str)
+{
+ if (as_exit_code) {
+ *str = crm_exit_str((crm_exit_t) rc);
+ *name = crm_exit_name(rc);
+ } else if (as_rc) {
+ *str = pcmk_rc_str(rc);
+ *name = pcmk_rc_name(rc);
+ } else {
+ *str = pcmk_strerror(rc);
+ *name = pcmk_errorname(rc);
+ }
+}
+
int
main(int argc, char **argv)
{
int rc = 0;
int lpc = 0;
int flag = 0;
int option_index = 0;
bool do_list = FALSE;
bool with_name = FALSE;
- bool as_exit_code = FALSE;
+
+ const char *name = NULL;
+ const char *desc = NULL;
crm_log_cli_init("crm_error");
- crm_set_options(NULL, "[options] -- rc", long_options,
+ crm_set_options(NULL, "[options] -- <rc> [...]", long_options,
"Tool for displaying the textual name or description of a reported error code");
while (flag >= 0) {
flag = crm_get_option(argc, argv, &option_index);
switch (flag) {
case -1:
break;
case 'V':
crm_bump_log_level(argc, argv);
break;
case '$':
case '?':
crm_help(flag, CRM_EX_OK);
break;
case 'n':
with_name = TRUE;
break;
case 'l':
do_list = TRUE;
break;
+ case 'r':
+ as_rc = true;
+ break;
case 'X':
as_exit_code = TRUE;
break;
default:
crm_help(flag, CRM_EX_OK);
break;
}
}
if(do_list) {
- for (rc = 0; rc < 256; rc++) {
- const char *name = as_exit_code? crm_exit_name(rc) : pcmk_errorname(rc);
- const char *desc = as_exit_code? crm_exit_str(rc) : pcmk_strerror(rc);
+ int start, end, width;
+
+ // 256 is a hacky magic number that "should" be enough
+ if (as_rc) {
+ start = pcmk_rc_error - 256;
+ end = PCMK_CUSTOM_OFFSET;
+ width = 4;
+ } else {
+ start = 0;
+ end = 256;
+ width = 3;
+ }
+
+ for (rc = start; rc < end; rc++) {
+ if (rc == (pcmk_rc_error + 1)) {
+ // Values in between are reserved for callers, no use iterating
+ rc = pcmk_rc_ok;
+ }
+ get_strings(rc, &name, &desc);
if (!name || !strcmp(name, "Unknown") || !strcmp(name, "CRM_EX_UNKNOWN")) {
- /* Unknown */
+ // Undefined
} else if(with_name) {
- printf("%.3d: %-26s %s\n", rc, name, desc);
+ printf("% .*d: %-26s %s\n", width, rc, name, desc);
} else {
- printf("%.3d: %s\n", rc, desc);
+ printf("% .*d: %s\n", width, rc, desc);
}
}
- return CRM_EX_OK;
- }
- for (lpc = optind; lpc < argc; lpc++) {
- const char *str, *name;
-
- rc = crm_atoi(argv[lpc], NULL);
- str = as_exit_code? crm_exit_str(rc) : pcmk_strerror(rc);
- if(with_name) {
- name = as_exit_code? crm_exit_name(rc) : pcmk_errorname(rc);
- printf("%s - %s\n", name, str);
- } else {
- printf("%s\n", str);
+ } else {
+ for (lpc = optind; lpc < argc; lpc++) {
+ rc = crm_atoi(argv[lpc], NULL);
+ get_strings(rc, &name, &desc);
+ if (with_name) {
+ printf("%s - %s\n", name, desc);
+ } else {
+ printf("%s\n", desc);
+ }
}
}
return CRM_EX_OK;
}
diff --git a/tools/crm_node.c b/tools/crm_node.c
index ff806d8010..0214afde34 100644
--- a/tools/crm_node.c
+++ b/tools/crm_node.c
@@ -1,664 +1,664 @@
/*
* Copyright 2004-2019 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 <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <crm/crm.h>
#include <crm/common/cmdline_internal.h>
#include <crm/common/mainloop.h>
#include <crm/msg_xml.h>
#include <crm/cib.h>
-#include <crm/attrd.h>
+#include <crm/common/attrd_internal.h>
#define SUMMARY "crm_node - Tool for displaying low-level node information"
struct {
gboolean corosync;
gboolean dangerous_cmd;
gboolean force_flag;
char command;
int nodeid;
char *target_uname;
} options = {
.command = '\0',
.force_flag = FALSE
};
gboolean command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean name_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean remove_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
static char *pid_s = NULL;
static GMainLoop *mainloop = NULL;
static crm_exit_t exit_code = CRM_EX_OK;
#define INDENT " "
static GOptionEntry command_entries[] = {
{ "cluster-id", 'i', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
"Display this node's cluster id",
NULL },
{ "list", 'l', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
"Display all known members (past and present) of this cluster",
NULL },
{ "name", 'n', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
"Display the name used by the cluster for this node",
NULL },
{ "partition", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
"Display the members of this partition",
NULL },
{ "quorum", 'q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
"Display a 1 if our partition has quorum, 0 if not",
NULL },
{ "name-for-id", 'N', 0, G_OPTION_ARG_CALLBACK, name_cb,
"Display the name used by the cluster for the node with the specified ID",
"ID" },
{ "remove", 'R', 0, G_OPTION_ARG_CALLBACK, remove_cb,
"(Advanced) Remove the (stopped) node with the specified name from Pacemaker's\n"
INDENT "configuration and caches (the node must already have been removed from\n"
INDENT "the underlying cluster stack configuration",
"NAME" },
{ NULL }
};
static GOptionEntry addl_entries[] = {
{ "force", 'f', 0, G_OPTION_ARG_NONE, &options.force_flag,
NULL,
NULL },
#if SUPPORT_COROSYNC
/* Unused and deprecated */
{ "corosync", 'C', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.corosync,
NULL,
NULL },
#endif
// @TODO add timeout option for when IPC replies are needed
{ NULL }
};
gboolean
command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
if (safe_str_eq("-i", option_name) || safe_str_eq("--cluster-id", option_name)) {
options.command = 'i';
} else if (safe_str_eq("-l", option_name) || safe_str_eq("--list", option_name)) {
options.command = 'l';
} else if (safe_str_eq("-n", option_name) || safe_str_eq("--name", option_name)) {
options.command = 'n';
} else if (safe_str_eq("-p", option_name) || safe_str_eq("--partition", option_name)) {
options.command = 'p';
} else if (safe_str_eq("-q", option_name) || safe_str_eq("--quorum", option_name)) {
options.command = 'q';
} else {
g_set_error(error, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, "Unknown param passed to command_cb: %s\n", option_name);
return FALSE;
}
return TRUE;
}
gboolean
name_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
options.command = 'N';
options.nodeid = crm_parse_int(optarg, NULL);
return TRUE;
}
gboolean
remove_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
if (optarg == NULL) {
crm_err("-R option requires an argument");
g_set_error(error, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, "-R option requires an argument");
return FALSE;
}
options.command = 'R';
options.dangerous_cmd = TRUE;
options.target_uname = strdup(optarg);
return TRUE;
}
/*!
* \internal
* \brief Exit crm_node
* Clean up memory, and either quit mainloop (if running) or exit
*
* \param[in] value Exit status
*/
static void
crm_node_exit(crm_exit_t value)
{
if (pid_s) {
free(pid_s);
pid_s = NULL;
}
exit_code = value;
if (mainloop && g_main_loop_is_running(mainloop)) {
g_main_loop_quit(mainloop);
} else {
crm_exit(exit_code);
}
}
static void
exit_disconnect(gpointer user_data)
{
fprintf(stderr, "error: Lost connection to cluster\n");
crm_node_exit(CRM_EX_DISCONNECT);
}
typedef int (*ipc_dispatch_fn) (const char *buffer, ssize_t length,
gpointer userdata);
static crm_ipc_t *
new_mainloop_for_ipc(const char *system, ipc_dispatch_fn dispatch)
{
mainloop_io_t *source = NULL;
crm_ipc_t *ipc = NULL;
struct ipc_client_callbacks ipc_callbacks = {
.dispatch = dispatch,
.destroy = exit_disconnect
};
mainloop = g_main_loop_new(NULL, FALSE);
source = mainloop_add_ipc_client(system, G_PRIORITY_DEFAULT, 0,
NULL, &ipc_callbacks);
ipc = mainloop_get_ipc_client(source);
if (ipc == NULL) {
fprintf(stderr,
"error: Could not connect to cluster (is it running?)\n");
crm_node_exit(CRM_EX_DISCONNECT);
}
return ipc;
}
static void
run_mainloop_and_exit()
{
g_main_loop_run(mainloop);
g_main_loop_unref(mainloop);
mainloop = NULL;
crm_node_exit(exit_code);
}
static int
send_controller_hello(crm_ipc_t *controller)
{
xmlNode *hello = NULL;
int rc;
pid_s = crm_getpid_s();
hello = create_hello_message(pid_s, crm_system_name, "1", "0");
rc = crm_ipc_send(controller, hello, 0, 0, NULL);
free_xml(hello);
return (rc < 0)? rc : 0;
}
static int
send_node_info_request(crm_ipc_t *controller, uint32_t nodeid)
{
xmlNode *ping = NULL;
int rc;
ping = create_request(CRM_OP_NODE_INFO, NULL, NULL, CRM_SYSTEM_CRMD,
crm_system_name, pid_s);
if (nodeid > 0) {
crm_xml_add_int(ping, XML_ATTR_ID, nodeid);
}
rc = crm_ipc_send(controller, ping, 0, 0, NULL);
free_xml(ping);
return (rc < 0)? rc : 0;
}
static int
dispatch_controller(const char *buffer, ssize_t length, gpointer userdata)
{
xmlNode *message = string2xml(buffer);
xmlNode *data = NULL;
const char *value = NULL;
if (message == NULL) {
fprintf(stderr, "error: Could not understand reply from controller\n");
crm_node_exit(CRM_EX_PROTOCOL);
return 0;
}
crm_log_xml_trace(message, "controller reply");
exit_code = CRM_EX_PROTOCOL;
// Validate reply
value = crm_element_value(message, F_CRM_MSG_TYPE);
if (safe_str_neq(value, XML_ATTR_RESPONSE)) {
fprintf(stderr, "error: Message from controller was not a reply\n");
goto done;
}
value = crm_element_value(message, XML_ATTR_REFERENCE);
if (value == NULL) {
fprintf(stderr, "error: Controller reply did not specify original message\n");
goto done;
}
data = get_message_xml(message, F_CRM_DATA);
if (data == NULL) {
fprintf(stderr, "error: Controller reply did not contain any data\n");
goto done;
}
switch (options.command) {
case 'i':
value = crm_element_value(data, XML_ATTR_ID);
if (value == NULL) {
fprintf(stderr, "error: Controller reply did not contain node ID\n");
} else {
printf("%s\n", value);
exit_code = CRM_EX_OK;
}
break;
case 'n':
case 'N':
value = crm_element_value(data, XML_ATTR_UNAME);
if (value == NULL) {
fprintf(stderr, "Node is not known to cluster\n");
exit_code = CRM_EX_NOHOST;
} else {
printf("%s\n", value);
exit_code = CRM_EX_OK;
}
break;
case 'q':
value = crm_element_value(data, XML_ATTR_HAVE_QUORUM);
if (value == NULL) {
fprintf(stderr, "error: Controller reply did not contain quorum status\n");
} else {
bool quorum = crm_is_true(value);
printf("%d\n", quorum);
exit_code = quorum? CRM_EX_OK : CRM_EX_QUORUM;
}
break;
default:
fprintf(stderr, "internal error: Controller reply not expected\n");
exit_code = CRM_EX_SOFTWARE;
break;
}
done:
free_xml(message);
crm_node_exit(exit_code);
return 0;
}
static void
run_controller_mainloop(uint32_t nodeid)
{
crm_ipc_t *controller = NULL;
int rc;
controller = new_mainloop_for_ipc(CRM_SYSTEM_CRMD, dispatch_controller);
rc = send_controller_hello(controller);
if (rc < 0) {
fprintf(stderr, "error: Could not register with controller: %s\n",
pcmk_strerror(rc));
crm_node_exit(crm_errno2exit(rc));
}
rc = send_node_info_request(controller, nodeid);
if (rc < 0) {
fprintf(stderr, "error: Could not ping controller: %s\n",
pcmk_strerror(rc));
crm_node_exit(crm_errno2exit(rc));
}
// Run main loop to get controller reply via dispatch_controller()
run_mainloop_and_exit();
}
static void
print_node_name()
{
// Check environment first (i.e. when called by resource agent)
const char *name = getenv("OCF_RESKEY_" CRM_META "_" XML_LRM_ATTR_TARGET);
if (name != NULL) {
printf("%s\n", name);
crm_node_exit(CRM_EX_OK);
} else {
// Otherwise ask the controller
run_controller_mainloop(0);
}
}
static int
cib_remove_node(long id, const char *name)
{
int rc;
cib_t *cib = NULL;
xmlNode *node = NULL;
xmlNode *node_state = NULL;
crm_trace("Removing %s from the CIB", name);
if(name == NULL && id == 0) {
return -ENOTUNIQ;
}
node = create_xml_node(NULL, XML_CIB_TAG_NODE);
node_state = create_xml_node(NULL, XML_CIB_TAG_STATE);
crm_xml_add(node, XML_ATTR_UNAME, name);
crm_xml_add(node_state, XML_ATTR_UNAME, name);
if (id > 0) {
crm_xml_set_id(node, "%ld", id);
crm_xml_add(node_state, XML_ATTR_ID, ID(node));
}
cib = cib_new();
cib->cmds->signon(cib, crm_system_name, cib_command);
rc = cib->cmds->remove(cib, XML_CIB_TAG_NODES, node, cib_sync_call);
if (rc != pcmk_ok) {
printf("Could not remove %s[%ld] from " XML_CIB_TAG_NODES ": %s",
name, id, pcmk_strerror(rc));
}
rc = cib->cmds->remove(cib, XML_CIB_TAG_STATUS, node_state, cib_sync_call);
if (rc != pcmk_ok) {
printf("Could not remove %s[%ld] from " XML_CIB_TAG_STATUS ": %s",
name, id, pcmk_strerror(rc));
}
cib->cmds->signoff(cib);
cib_delete(cib);
return rc;
}
static int
tools_remove_node_cache(const char *node_name, long nodeid, const char *target)
{
int rc = -1;
crm_ipc_t *conn = crm_ipc_new(target, 0);
xmlNode *cmd = NULL;
if (!conn) {
return -ENOTCONN;
}
if (!crm_ipc_connect(conn)) {
crm_perror(LOG_ERR, "Connection to %s failed", target);
crm_ipc_destroy(conn);
return -ENOTCONN;
}
if(safe_str_eq(target, CRM_SYSTEM_CRMD)) {
// The controller requires a hello message before sending a request
rc = send_controller_hello(conn);
if (rc < 0) {
fprintf(stderr, "error: Could not register with controller: %s\n",
pcmk_strerror(rc));
return rc;
}
}
crm_trace("Removing %s[%ld] from the %s membership cache",
node_name, nodeid, target);
if(safe_str_eq(target, T_ATTRD)) {
cmd = create_xml_node(NULL, __FUNCTION__);
crm_xml_add(cmd, F_TYPE, T_ATTRD);
crm_xml_add(cmd, F_ORIG, crm_system_name);
crm_xml_add(cmd, F_ATTRD_TASK, ATTRD_OP_PEER_REMOVE);
crm_xml_add(cmd, F_ATTRD_HOST, node_name);
if (nodeid > 0) {
crm_xml_add_int(cmd, F_ATTRD_HOST_ID, (int) nodeid);
}
} else {
cmd = create_request(CRM_OP_RM_NODE_CACHE,
NULL, NULL, target, crm_system_name, pid_s);
if (nodeid > 0) {
crm_xml_set_id(cmd, "%ld", nodeid);
}
crm_xml_add(cmd, XML_ATTR_UNAME, node_name);
}
rc = crm_ipc_send(conn, cmd, 0, 0, NULL);
crm_debug("%s peer cache cleanup for %s (%ld): %d",
target, node_name, nodeid, rc);
if (rc > 0) {
rc = cib_remove_node(nodeid, node_name);
}
if (conn) {
crm_ipc_close(conn);
crm_ipc_destroy(conn);
}
free_xml(cmd);
return rc > 0 ? 0 : rc;
}
static void
remove_node(const char *target_uname)
{
int d = 0;
long nodeid = 0;
const char *node_name = NULL;
char *endptr = NULL;
const char *daemons[] = {
CRM_SYSTEM_CRMD,
"stonith-ng",
T_ATTRD,
CRM_SYSTEM_MCP,
};
// Check whether node was specified by name or numeric ID
errno = 0;
nodeid = strtol(target_uname, &endptr, 10);
if ((errno != 0) || (endptr == target_uname) || (*endptr != '\0')
|| (nodeid <= 0)) {
// It's not a positive integer, so assume it's a node name
nodeid = 0;
node_name = target_uname;
}
for (d = 0; d < DIMOF(daemons); d++) {
if (tools_remove_node_cache(node_name, nodeid, daemons[d])) {
crm_err("Failed to connect to %s to remove node '%s'",
daemons[d], target_uname);
crm_node_exit(CRM_EX_ERROR);
return;
}
}
crm_node_exit(CRM_EX_OK);
}
static gint
compare_node_xml(gconstpointer a, gconstpointer b)
{
const char *a_name = crm_element_value((xmlNode*) a, "uname");
const char *b_name = crm_element_value((xmlNode*) b, "uname");
return strcmp((a_name? a_name : ""), (b_name? b_name : ""));
}
static int
node_mcp_dispatch(const char *buffer, ssize_t length, gpointer userdata)
{
GList *nodes = NULL;
xmlNode *node = NULL;
xmlNode *msg = string2xml(buffer);
const char *uname;
const char *state;
if (msg == NULL) {
fprintf(stderr, "error: Could not understand pacemakerd response\n");
crm_node_exit(CRM_EX_PROTOCOL);
return 0;
}
crm_log_xml_trace(msg, "message");
for (node = __xml_first_child(msg); node != NULL; node = __xml_next(node)) {
nodes = g_list_insert_sorted(nodes, node, compare_node_xml);
}
for (GList *iter = nodes; iter; iter = iter->next) {
node = (xmlNode*) iter->data;
uname = crm_element_value(node, "uname");
state = crm_element_value(node, "state");
if (options.command == 'l') {
int id = 0;
crm_element_value_int(node, "id", &id);
printf("%d %s %s\n", id, (uname? uname : ""), (state? state : ""));
// This is CRM_NODE_MEMBER but we don't want to include cluster header
} else if ((options.command == 'p') && safe_str_eq(state, "member")) {
printf("%s ", (uname? uname : ""));
}
}
if (options.command == 'p') {
fprintf(stdout, "\n");
}
free_xml(msg);
crm_node_exit(CRM_EX_OK);
return 0;
}
static void
run_pacemakerd_mainloop()
{
crm_ipc_t *ipc = NULL;
xmlNode *poke = NULL;
ipc = new_mainloop_for_ipc(CRM_SYSTEM_MCP, node_mcp_dispatch);
// Sending anything will get us a list of nodes
poke = create_xml_node(NULL, "poke");
crm_ipc_send(ipc, poke, 0, 0, NULL);
free_xml(poke);
// Handle reply via node_mcp_dispatch()
run_mainloop_and_exit();
}
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),
"Be less descriptive in output.",
NULL },
{ NULL }
};
context = pcmk__build_arg_context(args, NULL, &group);
/* Add the -q option, which cannot be part of the globally supported options
* because some tools use that flag for something else.
*/
pcmk__add_main_args(context, extra_prog_entries);
pcmk__add_arg_group(context, "commands", "Commands:",
"Show command help", command_entries);
pcmk__add_arg_group(context, "additional", "Additional Options:",
"Show additional options", addl_entries);
return context;
}
int
main(int argc, char **argv)
{
pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
GError *error = NULL;
GOptionContext *context = NULL;
GOptionGroup *output_group = NULL;
gchar **processed_args = NULL;
context = build_arg_context(args, output_group);
crm_log_cli_init("crm_node");
processed_args = pcmk__cmdline_preproc(argc, argv, "NR");
if (!g_option_context_parse_strv(context, &processed_args, &error)) {
fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
exit_code = CRM_EX_USAGE;
goto done;
}
for (int i = 0; i < args->verbosity; i++) {
crm_bump_log_level(argc, argv);
}
if (args->version) {
/* FIXME: When crm_node is converted to use formatted output, this can go. */
crm_help('v', CRM_EX_USAGE);
}
if (optind > argc || options.command == 0) {
fprintf(stderr, "%s", g_option_context_get_help(context, TRUE, NULL));
exit_code = CRM_EX_USAGE;
goto done;
}
if (options.dangerous_cmd && options.force_flag == FALSE) {
fprintf(stderr, "The supplied command is considered dangerous."
" To prevent accidental destruction of the cluster,"
" the --force flag is required in order to proceed.\n");
exit_code = CRM_EX_USAGE;
goto done;
}
switch (options.command) {
case 'n':
print_node_name();
break;
case 'R':
remove_node(options.target_uname);
break;
case 'i':
case 'q':
case 'N':
run_controller_mainloop(options.nodeid);
break;
case 'l':
case 'p':
run_pacemakerd_mainloop();
break;
default:
break;
}
done:
g_strfreev(processed_args);
pcmk__free_arg_context(context);
crm_node_exit(exit_code);
return exit_code;
}
diff --git a/tools/crm_resource.c b/tools/crm_resource.c
index e1096cc118..d19b440f51 100644
--- a/tools/crm_resource.c
+++ b/tools/crm_resource.c
@@ -1,1418 +1,1419 @@
/*
- * Copyright 2004-2019 the Pacemaker project contributors
+ * Copyright 2004-2020 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_resource.h>
#include <pacemaker-internal.h>
#include <sys/param.h>
#include <crm/crm.h>
#include <crm/stonith-ng.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <time.h>
bool BE_QUIET = FALSE;
bool scope_master = FALSE;
int cib_options = cib_sync_call;
static GMainLoop *mainloop = NULL;
#define MESSAGE_TIMEOUT_S 60
static gboolean
resource_ipc_timeout(gpointer data)
{
fprintf(stderr, "Aborting because no messages received in %d seconds\n",
MESSAGE_TIMEOUT_S);
crm_err("No messages received in %d seconds", MESSAGE_TIMEOUT_S);
crm_exit(CRM_EX_TIMEOUT);
}
static void
resource_ipc_connection_destroy(gpointer user_data)
{
crm_info("Connection to controller was terminated");
crm_exit(CRM_EX_DISCONNECT);
}
static void
start_mainloop(void)
{
if (crmd_replies_needed == 0) {
return;
}
mainloop = g_main_loop_new(NULL, FALSE);
fprintf(stderr, "Waiting for %d repl%s from the controller",
crmd_replies_needed, (crmd_replies_needed == 1)? "y" : "ies");
crm_debug("Waiting for %d repl%s from the controller",
crmd_replies_needed, (crmd_replies_needed == 1)? "y" : "ies");
g_timeout_add(MESSAGE_TIMEOUT_S * 1000, resource_ipc_timeout, NULL);
g_main_loop_run(mainloop);
}
static int
resource_ipc_callback(const char *buffer, ssize_t length, gpointer userdata)
{
xmlNode *msg = string2xml(buffer);
fprintf(stderr, ".");
crm_log_xml_trace(msg, "[inbound]");
crmd_replies_needed--;
if ((crmd_replies_needed == 0) && mainloop
&& g_main_loop_is_running(mainloop)) {
fprintf(stderr, " OK\n");
crm_debug("Got all the replies we expected");
crm_exit(CRM_EX_OK);
}
free_xml(msg);
return 0;
}
static int
compare_id(gconstpointer a, gconstpointer b)
{
return strcmp((const char *)a, (const char *)b);
}
static GListPtr
build_constraint_list(xmlNode *root)
{
GListPtr retval = NULL;
xmlNode *cib_constraints = NULL;
xmlXPathObjectPtr xpathObj = NULL;
int ndx = 0;
cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, root);
xpathObj = xpath_search(cib_constraints, "//" XML_CONS_TAG_RSC_LOCATION);
for (ndx = 0; ndx < numXpathResults(xpathObj); ndx++) {
xmlNode *match = getXpathResult(xpathObj, ndx);
retval = g_list_insert_sorted(retval, (gpointer) ID(match), compare_id);
}
freeXpathObject(xpathObj);
return retval;
}
struct ipc_client_callbacks crm_callbacks = {
.dispatch = resource_ipc_callback,
.destroy = resource_ipc_connection_destroy,
};
/* short option letters still available: eEJkKXyYZ */
/* *INDENT-OFF* */
static struct crm_option long_options[] = {
/* Top-level Options */
{
"help", no_argument, NULL, '?',
"\t\tDisplay this text and exit"
},
{
"version", no_argument, NULL, '$',
"\t\tDisplay version information and exit"
},
{
"verbose", no_argument, NULL, 'V',
"\t\tIncrease debug output (may be specified multiple times)"
},
{
"quiet", no_argument, NULL, 'Q',
"\t\tBe less descriptive in results"
},
{
"resource", required_argument, NULL, 'r',
"\tResource ID"
},
{ "-spacer-", no_argument, NULL, '-', "\nQueries:" },
{
"list", no_argument, NULL, 'L',
"\t\tList all cluster resources with status"},
{
"list-raw", no_argument, NULL, 'l',
"\t\tList IDs of all instantiated resources (individual members rather than groups etc.)"
},
{
"list-cts", no_argument, NULL, 'c',
NULL, pcmk_option_hidden
},
{
"list-operations", no_argument, NULL, 'O',
"\tList active resource operations, optionally filtered by --resource and/or --node"
},
{
"list-all-operations", no_argument, NULL, 'o',
"List all resource operations, optionally filtered by --resource and/or --node"
},
{
"list-standards", no_argument, NULL, 0,
"\tList supported standards"
},
{
"list-ocf-providers", no_argument, NULL, 0,
"List all available OCF providers"
},
{
"list-agents", required_argument, NULL, 0,
"List all agents available for the named standard and/or provider."
},
{
"list-ocf-alternatives", required_argument, NULL, 0,
"List all available providers for the named OCF agent"
},
{
"show-metadata", required_argument, NULL, 0,
"Show the metadata for the named class:provider:agent"
},
{
"query-xml", no_argument, NULL, 'q',
"\tShow XML configuration of resource (after any template expansion)"
},
{
"query-xml-raw", no_argument, NULL, 'w',
"\tShow XML configuration of resource (before any template expansion)"
},
{
"get-parameter", required_argument, NULL, 'g',
"Display named parameter for resource.\n"
"\t\t\t\tUse instance attribute unless --meta or --utilization is specified"
},
{
"get-property", required_argument, NULL, 'G',
"Display named property of resource ('class', 'type', or 'provider') (requires --resource)",
pcmk_option_hidden
},
{
"locate", no_argument, NULL, 'W',
"\t\tShow node(s) currently running resource"
},
{
"stack", no_argument, NULL, 'A',
"\t\tDisplay the prerequisites and dependents of a resource"
},
{
"constraints", no_argument, NULL, 'a',
"\tDisplay the (co)location constraints that apply to a resource"
},
{
"why", no_argument, NULL, 'Y',
"\t\tShow why resources are not running, optionally filtered by --resource and/or --node"
},
{ "-spacer-", no_argument, NULL, '-', "\nCommands:" },
{
"validate", no_argument, NULL, 0,
"\t\tValidate resource configuration by calling agent's validate-all action.\n"
"\t\t\t\tThe configuration may be specified either by giving an existing\n"
"\t\t\t\tresource name with -r, or by specifying --class, --agent, and\n"
"\t\t\t\t--provider arguments, along with any number of --option arguments."
},
{
"cleanup", no_argument, NULL, 'C',
"\t\tIf resource has any past failures, clear its history and fail count.\n"
"\t\t\t\tOptionally filtered by --resource, --node, --operation, and --interval (otherwise all).\n"
"\t\t\t\t--operation and --interval apply to fail counts, but entire history is always cleared,\n"
"\t\t\t\tto allow current state to be rechecked. If the named resource is part of a group, or\n"
"\t\t\t\tone numbered instance of a clone or bundled resource, the clean-up applies to the\n"
"\t\t\t\twhole collective resource unless --force is given."
},
{
"refresh", no_argument, NULL, 'R',
"\t\tDelete resource's history (including failures) so its current state is rechecked.\n"
"\t\t\t\tOptionally filtered by --resource and --node (otherwise all). If the named resource is\n"
"\t\t\t\tpart of a group, or one numbered instance of a clone or bundled resource, the clean-up\n"
"applies to the whole collective resource unless --force is given."
},
{
"set-parameter", required_argument, NULL, 'p',
"Set named parameter for resource (requires -v).\n"
"\t\t\t\tUse instance attribute unless --meta or --utilization is specified."
},
{
"delete-parameter", required_argument, NULL, 'd',
"Delete named parameter for resource.\n"
"\t\t\t\tUse instance attribute unless --meta or --utilization is specified."
},
{
"set-property", required_argument, NULL, 'S',
"Set named property of resource ('class', 'type', or 'provider') (requires -r, -t, -v)",
pcmk_option_hidden
},
{ "-spacer-", no_argument, NULL, '-', "\nResource location:" },
{
"move", no_argument, NULL, 'M',
"\t\tCreate a constraint to move resource. If --node is specified, the constraint\n"
"\t\t\t\twill be to move to that node, otherwise it will be to ban the current node.\n"
"\t\t\t\tUnless --force is specified, this will return an error if the resource is\n"
"\t\t\t\talready running on the specified node. If --force is specified, this will\n"
"\t\t\t\talways ban the current node. Optional: --lifetime, --master.\n"
"\t\t\t\tNOTE: This may prevent the resource from running on its previous location\n"
"\t\t\t\tuntil the implicit constraint expires or is removed with --clear."
},
{
"ban", no_argument, NULL, 'B',
"\t\tCreate a constraint to keep resource off a node. Optional: --node, --lifetime, --master.\n"
"\t\t\t\tNOTE: This will prevent the resource from running on the affected node\n"
"\t\t\t\tuntil the implicit constraint expires or is removed with --clear.\n"
"\t\t\t\tIf --node is not specified, it defaults to the node currently running the resource\n"
"\t\t\t\tfor primitives and groups, or the master for promotable clones with promoted-max=1\n"
"\t\t\t\t(all other situations result in an error as there is no sane default).\n"
},
{
"clear", no_argument, NULL, 'U',
"\t\tRemove all constraints created by the --ban and/or --move commands.\n"
"\t\t\t\tRequires: --resource. Optional: --node, --master, --expired.\n"
"\t\t\t\tIf --node is not specified, all constraints created by --ban and --move\n"
"\t\t\t\twill be removed for the named resource. If --node and --force are specified,\n"
"\t\t\t\tany constraint created by --move will be cleared, even if it is not for the specified node.\n"
"\t\t\t\tIf --expired is specified, only those constraints whose lifetimes have expired will\n"
"\t\t\t\tbe removed.\n"
},
{
"expired", no_argument, NULL, 'e',
"\t\tModifies the --clear argument to remove constraints with expired lifetimes.\n"
},
{
"lifetime", required_argument, NULL, 'u',
"\tLifespan (as ISO 8601 duration) of created constraints (with -B, -M)\n"
"\t\t\t\t(see https://en.wikipedia.org/wiki/ISO_8601#Durations)"
},
{
"master", no_argument, NULL, 0,
"\t\tLimit scope of command to the Master role (with -B, -M, -U).\n"
"\t\t\t\tFor -B and -M, the previous master may remain active in the Slave role."
},
{ "-spacer-", no_argument, NULL, '-', "\nAdvanced Commands:" },
{
"delete", no_argument, NULL, 'D',
"\t\t(Advanced) Delete a resource from the CIB. Required: -t"
},
{
"fail", no_argument, NULL, 'F',
"\t\t(Advanced) Tell the cluster this resource has failed"
},
{
"restart", no_argument, NULL, 0,
"\t\t(Advanced) Tell the cluster to restart this resource and anything that depends on it"
},
{
"wait", no_argument, NULL, 0,
"\t\t(Advanced) Wait until the cluster settles into a stable state"
},
{
"force-demote", no_argument, NULL, 0,
"\t(Advanced) Bypass the cluster and demote a resource on the local node.\n"
"\t\t\t\tUnless --force is specified, this will refuse to do so if the cluster\n"
"\t\t\t\tbelieves the resource is a clone instance already running on the local node."
},
{
"force-stop", no_argument, NULL, 0,
"\t(Advanced) Bypass the cluster and stop a resource on the local node."
},
{
"force-start", no_argument, NULL, 0,
"\t(Advanced) Bypass the cluster and start a resource on the local node.\n"
"\t\t\t\tUnless --force is specified, this will refuse to do so if the cluster\n"
"\t\t\t\tbelieves the resource is a clone instance already running on the local node."
},
{
"force-promote", no_argument, NULL, 0,
"\t(Advanced) Bypass the cluster and promote a resource on the local node.\n"
"\t\t\t\tUnless --force is specified, this will refuse to do so if the cluster\n"
"\t\t\t\tbelieves the resource is a clone instance already running on the local node."
},
{
"force-check", no_argument, NULL, 0,
"\t(Advanced) Bypass the cluster and check the state of a resource on the local node."
},
{ "-spacer-", no_argument, NULL, '-', "\nValidate Options:" },
{
"class", required_argument, NULL, 0,
"\tThe standard the resource agent confirms to (for example, ocf).\n"
"\t\t\t\tUse with --agent, --provider, --option, and --validate."
},
{
"agent", required_argument, NULL, 0,
"\tThe agent to use (for example, IPaddr).\n"
"\t\t\t\tUse with --class, --provider, --option, and --validate."
},
{
"provider", required_argument, NULL, 0,
"\tThe vendor that supplies the resource agent (for example, heartbeat).\n"
"\t\t\t\tuse with --class, --agent, --option, and --validate."
},
{
"option", required_argument, NULL, 0,
"\tSpecify a device configuration parameter as NAME=VALUE\n"
"\t\t\t\t(may be specified multiple times). Use with --validate\n"
"\t\t\t\tand without the -r option."
},
{ "-spacer-", no_argument, NULL, '-', "\nAdditional Options:" },
{
"node", required_argument, NULL, 'N',
"\tNode name"
},
{
"recursive", no_argument, NULL, 0,
"\tFollow colocation chains when using --set-parameter"
},
{
"resource-type", required_argument, NULL, 't',
"Resource XML element (primitive, group, etc.) (with -D)"
},
{
"parameter-value", required_argument, NULL, 'v',
"Value to use with -p"
},
{
"meta", no_argument, NULL, 'm',
"\t\tUse resource meta-attribute instead of instance attribute (with -p, -g, -d)"
},
{
"utilization", no_argument, NULL, 'z',
"\tUse resource utilization attribute instead of instance attribute (with -p, -g, -d)"
},
{
"operation", required_argument, NULL, 'n',
"\tOperation to clear instead of all (with -C -r)"
},
{
"interval", required_argument, NULL, 'I',
"\tInterval of operation to clear (default 0) (with -C -r -n)"
},
{
"set-name", required_argument, NULL, 's',
"\t(Advanced) XML ID of attributes element to use (with -p, -d)"
},
{
"nvpair", required_argument, NULL, 'i',
"\t(Advanced) XML ID of nvpair element to use (with -p, -d)"
},
{
"timeout", required_argument, NULL, 'T',
"\t(Advanced) Abort if command does not finish in this time (with --restart, --wait, --force-*)"
},
{
"force", no_argument, NULL, 'f',
"\t\tIf making CIB changes, do so regardless of quorum.\n"
"\t\t\t\tSee help for individual commands for additional behavior.\n"
},
{
"xml-file", required_argument, NULL, 'x',
NULL, pcmk_option_hidden
},
/* legacy options */
{"host-uname", required_argument, NULL, 'H', NULL, pcmk_option_hidden},
{"-spacer-", 1, NULL, '-', "\nExamples:", pcmk_option_paragraph},
{"-spacer-", 1, NULL, '-', "List the available OCF agents:", pcmk_option_paragraph},
{"-spacer-", 1, NULL, '-', " crm_resource --list-agents ocf", pcmk_option_example},
{"-spacer-", 1, NULL, '-', "List the available OCF agents from the linux-ha project:", pcmk_option_paragraph},
{"-spacer-", 1, NULL, '-', " crm_resource --list-agents ocf:heartbeat", pcmk_option_example},
{"-spacer-", 1, NULL, '-', "Move 'myResource' to a specific node:", pcmk_option_paragraph},
{"-spacer-", 1, NULL, '-', " crm_resource --resource myResource --move --node altNode", pcmk_option_example},
{"-spacer-", 1, NULL, '-', "Allow (but not force) 'myResource' to move back to its original location:", pcmk_option_paragraph},
{"-spacer-", 1, NULL, '-', " crm_resource --resource myResource --clear", pcmk_option_example},
{"-spacer-", 1, NULL, '-', "Stop 'myResource' (and anything that depends on it):", pcmk_option_paragraph},
{"-spacer-", 1, NULL, '-', " crm_resource --resource myResource --set-parameter target-role --meta --parameter-value Stopped", pcmk_option_example},
{"-spacer-", 1, NULL, '-', "Tell the cluster not to manage 'myResource':", pcmk_option_paragraph},
{"-spacer-", 1, NULL, '-', "The cluster will not attempt to start or stop the resource under any circumstances."},
{"-spacer-", 1, NULL, '-', "Useful when performing maintenance tasks on a resource.", pcmk_option_paragraph},
{"-spacer-", 1, NULL, '-', " crm_resource --resource myResource --set-parameter is-managed --meta --parameter-value false", pcmk_option_example},
{"-spacer-", 1, NULL, '-', "Erase the operation history of 'myResource' on 'aNode':", pcmk_option_paragraph},
{"-spacer-", 1, NULL, '-', "The cluster will 'forget' the existing resource state (including any errors) and attempt to recover the resource."},
{"-spacer-", 1, NULL, '-', "Useful when a resource had failed permanently and has been repaired by an administrator.", pcmk_option_paragraph},
{"-spacer-", 1, NULL, '-', " crm_resource --resource myResource --cleanup --node aNode", pcmk_option_example},
{0, 0, 0, 0}
};
/* *INDENT-ON* */
int
main(int argc, char **argv)
{
char rsc_cmd = 'L';
const char *v_class = NULL;
const char *v_agent = NULL;
const char *v_provider = NULL;
char *name = NULL;
char *value = NULL;
GHashTable *validate_options = NULL;
const char *rsc_id = NULL;
const char *host_uname = NULL;
const char *prop_name = NULL;
const char *prop_value = NULL;
const char *rsc_type = NULL;
const char *prop_id = NULL;
const char *prop_set = NULL;
const char *rsc_long_cmd = NULL;
const char *longname = NULL;
const char *operation = NULL;
const char *interval_spec = NULL;
const char *cib_file = getenv("CIB_file");
GHashTable *override_params = NULL;
char *xml_file = NULL;
crm_ipc_t *crmd_channel = NULL;
pe_working_set_t *data_set = NULL;
xmlNode *cib_xml_copy = NULL;
cib_t *cib_conn = NULL;
resource_t *rsc = NULL;
bool recursive = FALSE;
char *our_pid = NULL;
bool validate_cmdline = FALSE; /* whether we are just validating based on command line options */
bool require_resource = TRUE; /* whether command requires that resource be specified */
bool require_dataset = TRUE; /* whether command requires populated dataset instance */
bool require_crmd = FALSE; // whether command requires controller connection
bool clear_expired = FALSE;
int rc = pcmk_ok;
int is_ocf_rc = 0;
int option_index = 0;
int timeout_ms = 0;
int argerr = 0;
int flag;
int find_flags = 0; // Flags to use when searching for resource
crm_exit_t exit_code = CRM_EX_OK;
crm_log_cli_init("crm_resource");
crm_set_options(NULL, "(query|command) [options]", long_options,
"Perform tasks related to cluster resources.\nAllows resources to be queried (definition and location), modified, and moved around the cluster.\n");
validate_options = crm_str_table_new();
while (1) {
flag = crm_get_option_long(argc, argv, &option_index, &longname);
if (flag == -1)
break;
switch (flag) {
case 0: /* long options with no short equivalent */
if (safe_str_eq("master", longname)) {
scope_master = TRUE;
} else if(safe_str_eq(longname, "recursive")) {
recursive = TRUE;
} else if (safe_str_eq("wait", longname)) {
rsc_cmd = flag;
rsc_long_cmd = longname;
require_resource = FALSE;
require_dataset = FALSE;
} else if (
safe_str_eq("validate", longname)
|| safe_str_eq("restart", longname)
|| safe_str_eq("force-demote", longname)
|| safe_str_eq("force-stop", longname)
|| safe_str_eq("force-start", longname)
|| safe_str_eq("force-promote", longname)
|| safe_str_eq("force-check", longname)) {
rsc_cmd = flag;
rsc_long_cmd = longname;
find_flags = pe_find_renamed|pe_find_anon;
crm_log_args(argc, argv);
} else if (safe_str_eq("list-ocf-providers", longname)
|| safe_str_eq("list-ocf-alternatives", longname)
|| safe_str_eq("list-standards", longname)) {
const char *text = NULL;
lrmd_list_t *list = NULL;
lrmd_list_t *iter = NULL;
lrmd_t *lrmd_conn = lrmd_api_new();
if (safe_str_eq("list-ocf-providers", longname)
|| safe_str_eq("list-ocf-alternatives", longname)) {
rc = lrmd_conn->cmds->list_ocf_providers(lrmd_conn, optarg, &list);
text = "OCF providers";
} else if (safe_str_eq("list-standards", longname)) {
rc = lrmd_conn->cmds->list_standards(lrmd_conn, &list);
text = "standards";
}
if (rc > 0) {
for (iter = list; iter != NULL; iter = iter->next) {
printf("%s\n", iter->val);
}
lrmd_list_freeall(list);
} else if (optarg) {
fprintf(stderr, "No %s found for %s\n", text, optarg);
exit_code = CRM_EX_NOSUCH;
} else {
fprintf(stderr, "No %s found\n", text);
exit_code = CRM_EX_NOSUCH;
}
lrmd_api_delete(lrmd_conn);
crm_exit(exit_code);
} else if (safe_str_eq("show-metadata", longname)) {
char *standard = NULL;
char *provider = NULL;
char *type = NULL;
char *metadata = NULL;
lrmd_t *lrmd_conn = lrmd_api_new();
rc = crm_parse_agent_spec(optarg, &standard, &provider, &type);
if (rc == pcmk_ok) {
rc = lrmd_conn->cmds->get_metadata(lrmd_conn, standard,
provider, type,
&metadata, 0);
} else {
fprintf(stderr,
"'%s' is not a valid agent specification\n",
optarg);
rc = -ENXIO;
}
if (metadata) {
printf("%s\n", metadata);
} else {
fprintf(stderr, "Metadata query for %s failed: %s\n",
optarg, pcmk_strerror(rc));
exit_code = crm_errno2exit(rc);
}
lrmd_api_delete(lrmd_conn);
crm_exit(exit_code);
} else if (safe_str_eq("list-agents", longname)) {
lrmd_list_t *list = NULL;
lrmd_list_t *iter = NULL;
char *provider = strchr (optarg, ':');
lrmd_t *lrmd_conn = lrmd_api_new();
if (provider) {
*provider++ = 0;
}
rc = lrmd_conn->cmds->list_agents(lrmd_conn, &list, optarg, provider);
if (rc > 0) {
for (iter = list; iter != NULL; iter = iter->next) {
printf("%s\n", iter->val);
}
lrmd_list_freeall(list);
} else {
fprintf(stderr, "No agents found for standard=%s, provider=%s\n",
optarg, (provider? provider : "*"));
exit_code = CRM_EX_NOSUCH;
}
lrmd_api_delete(lrmd_conn);
crm_exit(exit_code);
} else if (safe_str_eq("class", longname)) {
if (!(pcmk_get_ra_caps(optarg) & pcmk_ra_cap_params)) {
if (BE_QUIET == FALSE) {
fprintf(stdout, "Standard %s does not support parameters\n",
optarg);
}
crm_exit(exit_code);
} else {
v_class = optarg;
}
validate_cmdline = TRUE;
require_resource = FALSE;
} else if (safe_str_eq("agent", longname)) {
validate_cmdline = TRUE;
require_resource = FALSE;
v_agent = optarg;
} else if (safe_str_eq("provider", longname)) {
validate_cmdline = TRUE;
require_resource = FALSE;
v_provider = optarg;
} else if (safe_str_eq("option", longname)) {
crm_info("Scanning: --option %s", optarg);
rc = pcmk_scan_nvpair(optarg, &name, &value);
if (rc != 2) {
fprintf(stderr, "Invalid option: --option %s: %s", optarg, pcmk_strerror(rc));
argerr++;
} else {
crm_info("Got: '%s'='%s'", name, value);
}
g_hash_table_replace(validate_options, name, value);
} else {
crm_err("Unhandled long option: %s", longname);
}
break;
case 'V':
resource_verbose++;
crm_bump_log_level(argc, argv);
break;
case '$':
case '?':
crm_help(flag, CRM_EX_OK);
break;
case 'x':
xml_file = strdup(optarg);
break;
case 'Q':
BE_QUIET = TRUE;
break;
case 'm':
attr_set_type = XML_TAG_META_SETS;
break;
case 'z':
attr_set_type = XML_TAG_UTILIZATION;
break;
case 'u':
move_lifetime = strdup(optarg);
break;
case 'f':
do_force = TRUE;
crm_log_args(argc, argv);
break;
case 'i':
prop_id = optarg;
break;
case 's':
prop_set = optarg;
break;
case 'r':
rsc_id = optarg;
break;
case 'v':
prop_value = optarg;
break;
case 't':
rsc_type = optarg;
break;
case 'T':
timeout_ms = crm_get_msec(optarg);
break;
case 'e':
clear_expired = TRUE;
require_resource = FALSE;
break;
case 'C':
case 'R':
crm_log_args(argc, argv);
require_resource = FALSE;
if (cib_file == NULL) {
require_crmd = TRUE;
}
rsc_cmd = flag;
find_flags = pe_find_renamed|pe_find_anon;
break;
case 'n':
operation = optarg;
break;
case 'I':
interval_spec = optarg;
break;
case 'D':
require_dataset = FALSE;
crm_log_args(argc, argv);
rsc_cmd = flag;
find_flags = pe_find_renamed|pe_find_any;
break;
case 'F':
require_crmd = TRUE;
crm_log_args(argc, argv);
rsc_cmd = flag;
break;
case 'U':
case 'B':
case 'M':
crm_log_args(argc, argv);
rsc_cmd = flag;
find_flags = pe_find_renamed|pe_find_anon;
break;
case 'c':
case 'L':
case 'l':
case 'O':
case 'o':
require_resource = FALSE;
rsc_cmd = flag;
break;
case 'Y':
require_resource = FALSE;
rsc_cmd = flag;
find_flags = pe_find_renamed|pe_find_anon;
break;
case 'q':
case 'w':
rsc_cmd = flag;
find_flags = pe_find_renamed|pe_find_any;
break;
case 'W':
case 'A':
case 'a':
rsc_cmd = flag;
find_flags = pe_find_renamed|pe_find_anon;
break;
case 'S':
require_dataset = FALSE;
crm_log_args(argc, argv);
prop_name = optarg;
rsc_cmd = flag;
find_flags = pe_find_renamed|pe_find_any;
break;
case 'p':
case 'd':
crm_log_args(argc, argv);
prop_name = optarg;
rsc_cmd = flag;
find_flags = pe_find_renamed|pe_find_any;
break;
case 'G':
case 'g':
prop_name = optarg;
rsc_cmd = flag;
find_flags = pe_find_renamed|pe_find_any;
break;
case 'H':
case 'N':
crm_trace("Option %c => %s", flag, optarg);
host_uname = optarg;
break;
default:
CMD_ERR("Argument code 0%o (%c) is not (?yet?) supported", flag, flag);
++argerr;
break;
}
}
// Catch the case where the user didn't specify a command
if (rsc_cmd == 'L') {
require_resource = FALSE;
}
// --expired without --clear/-U doesn't make sense
if (clear_expired == TRUE && rsc_cmd != 'U') {
CMD_ERR("--expired requires --clear or -U");
argerr++;
}
if (optind < argc
&& argv[optind] != NULL
&& rsc_cmd == 0
&& rsc_long_cmd) {
override_params = crm_str_table_new();
while (optind < argc && argv[optind] != NULL) {
char *name = calloc(1, strlen(argv[optind]));
char *value = calloc(1, strlen(argv[optind]));
int rc = sscanf(argv[optind], "%[^=]=%s", name, value);
if(rc == 2) {
g_hash_table_replace(override_params, name, value);
} else {
CMD_ERR("Error parsing '%s' as a name=value pair for --%s", argv[optind], rsc_long_cmd);
free(value);
free(name);
argerr++;
}
optind++;
}
} else if (optind < argc && argv[optind] != NULL && rsc_cmd == 0) {
CMD_ERR("non-option ARGV-elements: ");
while (optind < argc && argv[optind] != NULL) {
CMD_ERR("[%d of %d] %s ", optind, argc, argv[optind]);
optind++;
argerr++;
}
}
if (optind > argc) {
++argerr;
}
// Sanity check validating from command line parameters. If everything checks out,
// go ahead and run the validation. This way we don't need a CIB connection.
if (validate_cmdline == TRUE) {
// -r cannot be used with any of --class, --agent, or --provider
if (rsc_id != NULL) {
CMD_ERR("--resource cannot be used with --class, --agent, and --provider");
argerr++;
// If --class, --agent, or --provider are given, --validate must also be given.
} else if (!safe_str_eq(rsc_long_cmd, "validate")) {
CMD_ERR("--class, --agent, and --provider require --validate");
argerr++;
// Not all of --class, --agent, and --provider need to be given. Not all
// classes support the concept of a provider. Check that what we were given
// is valid.
} else if (crm_str_eq(v_class, "stonith", TRUE)) {
if (v_provider != NULL) {
CMD_ERR("stonith does not support providers");
argerr++;
} else if (stonith_agent_exists(v_agent, 0) == FALSE) {
CMD_ERR("%s is not a known stonith agent", v_agent ? v_agent : "");
argerr++;
}
} else if (resources_agent_exists(v_class, v_provider, v_agent) == FALSE) {
CMD_ERR("%s:%s:%s is not a known resource",
v_class ? v_class : "",
v_provider ? v_provider : "",
v_agent ? v_agent : "");
argerr++;
}
if (argerr == 0) {
rc = cli_resource_execute_from_params("test", v_class, v_provider, v_agent,
"validate-all", validate_options,
override_params, timeout_ms);
exit_code = crm_errno2exit(rc);
crm_exit(exit_code);
}
}
if (argerr) {
CMD_ERR("Invalid option(s) supplied, use --help for valid usage");
crm_exit(CRM_EX_USAGE);
}
our_pid = crm_getpid_s();
if (do_force) {
crm_debug("Forcing...");
cib_options |= cib_quorum_override;
}
if (require_resource && !rsc_id) {
CMD_ERR("Must supply a resource id with -r");
rc = -ENXIO;
goto bail;
}
if (find_flags && rsc_id) {
require_dataset = TRUE;
}
/* Establish a connection to the CIB manager */
cib_conn = cib_new();
rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command);
if (rc != pcmk_ok) {
CMD_ERR("Error connecting to the CIB manager: %s", pcmk_strerror(rc));
goto bail;
}
/* Populate working set from XML file if specified or CIB query otherwise */
if (require_dataset) {
if (xml_file != NULL) {
cib_xml_copy = filename2xml(xml_file);
} else {
rc = cib_conn->cmds->query(cib_conn, NULL, &cib_xml_copy, cib_scope_local | cib_sync_call);
}
if(rc != pcmk_ok) {
goto bail;
}
/* Populate the working set instance */
data_set = pe_new_working_set();
if (data_set == NULL) {
rc = -ENOMEM;
goto bail;
}
set_bit(data_set->flags, pe_flag_no_counts);
rc = update_working_set_xml(data_set, &cib_xml_copy);
if (rc != pcmk_ok) {
goto bail;
}
cluster_status(data_set);
}
// If command requires that resource exist if specified, find it
if (find_flags && rsc_id) {
rsc = pe_find_resource_with_flags(data_set->resources, rsc_id,
find_flags);
if (rsc == NULL) {
CMD_ERR("Resource '%s' not found", rsc_id);
rc = -ENXIO;
goto bail;
}
}
// Establish a connection to the controller if needed
if (require_crmd) {
xmlNode *xml = NULL;
mainloop_io_t *source =
mainloop_add_ipc_client(CRM_SYSTEM_CRMD, G_PRIORITY_DEFAULT, 0, NULL, &crm_callbacks);
crmd_channel = mainloop_get_ipc_client(source);
if (crmd_channel == NULL) {
CMD_ERR("Error connecting to the controller");
rc = -ENOTCONN;
goto bail;
}
xml = create_hello_message(our_pid, crm_system_name, "0", "1");
crm_ipc_send(crmd_channel, xml, 0, 0, NULL);
free_xml(xml);
}
/* Handle rsc_cmd appropriately */
if (rsc_cmd == 'L') {
rc = pcmk_ok;
cli_resource_print_list(data_set, FALSE);
} else if (rsc_cmd == 'l') {
int found = 0;
GListPtr lpc = NULL;
rc = pcmk_ok;
for (lpc = data_set->resources; lpc != NULL; lpc = lpc->next) {
rsc = (resource_t *) lpc->data;
found++;
cli_resource_print_raw(rsc);
}
if (found == 0) {
printf("NO resources configured\n");
rc = -ENXIO;
}
} else if (rsc_cmd == 0 && rsc_long_cmd && safe_str_eq(rsc_long_cmd, "restart")) {
/* We don't pass data_set because rsc needs to stay valid for the entire
* lifetime of cli_resource_restart(), but it will reset and update the
* working set multiple times, so it needs to use its own copy.
*/
rc = cli_resource_restart(rsc, host_uname, timeout_ms, cib_conn);
} else if (rsc_cmd == 0 && rsc_long_cmd && safe_str_eq(rsc_long_cmd, "wait")) {
rc = wait_till_stable(timeout_ms, cib_conn);
} else if (rsc_cmd == 0 && rsc_long_cmd) {
// validate, force-(stop|start|demote|promote|check)
rc = cli_resource_execute(rsc, rsc_id, rsc_long_cmd, override_params,
timeout_ms, cib_conn, data_set);
if (rc >= 0) {
is_ocf_rc = 1;
}
} else if (rsc_cmd == 'A' || rsc_cmd == 'a') {
GListPtr lpc = NULL;
xmlNode *cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS,
data_set->input);
unpack_constraints(cib_constraints, data_set);
// Constraints apply to group/clone, not member/instance
rsc = uber_parent(rsc);
for (lpc = data_set->resources; lpc != NULL; lpc = lpc->next) {
resource_t *r = (resource_t *) lpc->data;
clear_bit(r->flags, pe_rsc_allocating);
}
cli_resource_print_colocation(rsc, TRUE, rsc_cmd == 'A', 1);
fprintf(stdout, "* %s\n", rsc->id);
cli_resource_print_location(rsc, NULL);
for (lpc = data_set->resources; lpc != NULL; lpc = lpc->next) {
resource_t *r = (resource_t *) lpc->data;
clear_bit(r->flags, pe_rsc_allocating);
}
cli_resource_print_colocation(rsc, FALSE, rsc_cmd == 'A', 1);
} else if (rsc_cmd == 'c') {
GListPtr lpc = NULL;
rc = pcmk_ok;
for (lpc = data_set->resources; lpc != NULL; lpc = lpc->next) {
rsc = (resource_t *) lpc->data;
cli_resource_print_cts(rsc);
}
cli_resource_print_cts_constraints(data_set);
} else if (rsc_cmd == 'F') {
rc = cli_resource_fail(crmd_channel, host_uname, rsc_id, data_set);
if (rc == pcmk_ok) {
start_mainloop();
}
} else if (rsc_cmd == 'O') {
rc = cli_resource_print_operations(rsc_id, host_uname, TRUE, data_set);
} else if (rsc_cmd == 'o') {
rc = cli_resource_print_operations(rsc_id, host_uname, FALSE, data_set);
} else if (rsc_cmd == 'W') {
rc = cli_resource_search(rsc, rsc_id, data_set);
if (rc >= 0) {
rc = pcmk_ok;
}
} else if (rsc_cmd == 'q') {
rc = cli_resource_print(rsc, data_set, TRUE);
} else if (rsc_cmd == 'w') {
rc = cli_resource_print(rsc, data_set, FALSE);
} else if (rsc_cmd == 'Y') {
node_t *dest = NULL;
if (host_uname) {
dest = pe_find_node(data_set->nodes, host_uname);
if (dest == NULL) {
rc = -pcmk_err_node_unknown;
goto bail;
}
}
cli_resource_why(cib_conn, data_set->resources, rsc, dest);
rc = pcmk_ok;
} else if (rsc_cmd == 'U') {
GListPtr before = NULL;
GListPtr after = NULL;
GListPtr remaining = NULL;
GListPtr ele = NULL;
node_t *dest = NULL;
if (BE_QUIET == FALSE) {
before = build_constraint_list(data_set->input);
}
if (clear_expired == TRUE) {
rc = cli_resource_clear_all_expired(data_set->input, cib_conn, rsc_id, host_uname, scope_master);
} else if (host_uname) {
dest = pe_find_node(data_set->nodes, host_uname);
if (dest == NULL) {
rc = -pcmk_err_node_unknown;
if (BE_QUIET == FALSE) {
g_list_free(before);
}
goto bail;
}
rc = cli_resource_clear(rsc_id, dest->details->uname, NULL, cib_conn, TRUE);
} else {
rc = cli_resource_clear(rsc_id, NULL, data_set->nodes, cib_conn, TRUE);
}
if (BE_QUIET == FALSE) {
rc = cib_conn->cmds->query(cib_conn, NULL, &cib_xml_copy, cib_scope_local | cib_sync_call);
if (rc != pcmk_ok) {
CMD_ERR("Could not get modified CIB: %s\n", pcmk_strerror(rc));
g_list_free(before);
goto bail;
}
data_set->input = cib_xml_copy;
cluster_status(data_set);
after = build_constraint_list(data_set->input);
remaining = subtract_lists(before, after, (GCompareFunc) strcmp);
for (ele = remaining; ele != NULL; ele = ele->next) {
printf("Removing constraint: %s\n", (char *) ele->data);
}
g_list_free(before);
g_list_free(after);
g_list_free(remaining);
}
} else if (rsc_cmd == 'M' && host_uname) {
rc = cli_resource_move(rsc, rsc_id, host_uname, cib_conn, data_set);
} else if (rsc_cmd == 'B' && host_uname) {
node_t *dest = pe_find_node(data_set->nodes, host_uname);
if (dest == NULL) {
rc = -pcmk_err_node_unknown;
goto bail;
}
rc = cli_resource_ban(rsc_id, dest->details->uname, NULL, cib_conn);
} else if (rsc_cmd == 'B' || rsc_cmd == 'M') {
pe_node_t *current = NULL;
unsigned int nactive = 0;
current = pe__find_active_requires(rsc, &nactive);
if (nactive == 1) {
rc = cli_resource_ban(rsc_id, current->details->uname, NULL, cib_conn);
} else if (is_set(rsc->flags, pe_rsc_promotable)) {
int count = 0;
GListPtr iter = NULL;
current = NULL;
for(iter = rsc->children; iter; iter = iter->next) {
resource_t *child = (resource_t *)iter->data;
enum rsc_role_e child_role = child->fns->state(child, TRUE);
if(child_role == RSC_ROLE_MASTER) {
count++;
current = pe__current_node(child);
}
}
if(count == 1 && current) {
rc = cli_resource_ban(rsc_id, current->details->uname, NULL, cib_conn);
} else {
rc = -EINVAL;
exit_code = CRM_EX_USAGE;
CMD_ERR("Resource '%s' not moved: active in %d locations (promoted in %d).",
rsc_id, nactive, count);
CMD_ERR("To prevent '%s' from running on a specific location, "
"specify a node.", rsc_id);
CMD_ERR("To prevent '%s' from being promoted at a specific "
"location, specify a node and the master option.",
rsc_id);
}
} else {
rc = -EINVAL;
exit_code = CRM_EX_USAGE;
CMD_ERR("Resource '%s' not moved: active in %d locations.", rsc_id, nactive);
CMD_ERR("To prevent '%s' from running on a specific location, "
"specify a node.", rsc_id);
}
} else if (rsc_cmd == 'G') {
rc = cli_resource_print_property(rsc, prop_name, data_set);
} else if (rsc_cmd == 'S') {
xmlNode *msg_data = NULL;
if ((rsc_type == NULL) || !strlen(rsc_type)) {
CMD_ERR("Must specify -t with resource type");
rc = -ENXIO;
goto bail;
} else if ((prop_value == NULL) || !strlen(prop_value)) {
CMD_ERR("Must supply -v with new value");
rc = -EINVAL;
goto bail;
}
CRM_LOG_ASSERT(prop_name != NULL);
msg_data = create_xml_node(NULL, rsc_type);
crm_xml_add(msg_data, XML_ATTR_ID, rsc_id);
crm_xml_add(msg_data, prop_name, prop_value);
rc = cib_conn->cmds->modify(cib_conn, XML_CIB_TAG_RESOURCES, msg_data, cib_options);
free_xml(msg_data);
} else if (rsc_cmd == 'g') {
rc = cli_resource_print_attribute(rsc, prop_name, data_set);
} else if (rsc_cmd == 'p') {
if (prop_value == NULL || strlen(prop_value) == 0) {
CMD_ERR("You need to supply a value with the -v option");
rc = -EINVAL;
goto bail;
}
/* coverity[var_deref_model] False positive */
rc = cli_resource_update_attribute(rsc, rsc_id, prop_set, prop_id,
prop_name, prop_value, recursive,
cib_conn, data_set);
} else if (rsc_cmd == 'd') {
/* coverity[var_deref_model] False positive */
rc = cli_resource_delete_attribute(rsc, rsc_id, prop_set, prop_id,
prop_name, cib_conn, data_set);
} else if ((rsc_cmd == 'C') && rsc) {
if (do_force == FALSE) {
rsc = uber_parent(rsc);
}
crmd_replies_needed = 0;
crm_debug("Erasing failures of %s (%s requested) on %s",
rsc->id, rsc_id, (host_uname? host_uname: "all nodes"));
rc = cli_resource_delete(crmd_channel, host_uname, rsc,
operation, interval_spec, TRUE, data_set);
if ((rc == pcmk_ok) && !BE_QUIET) {
// Show any reasons why resource might stay stopped
cli_resource_check(cib_conn, rsc);
}
if (rc == pcmk_ok) {
start_mainloop();
}
} else if (rsc_cmd == 'C') {
rc = cli_cleanup_all(crmd_channel, host_uname, operation, interval_spec,
data_set);
if (rc == pcmk_ok) {
start_mainloop();
}
} else if ((rsc_cmd == 'R') && rsc) {
if (do_force == FALSE) {
rsc = uber_parent(rsc);
}
crmd_replies_needed = 0;
crm_debug("Re-checking the state of %s (%s requested) on %s",
rsc->id, rsc_id, (host_uname? host_uname: "all nodes"));
rc = cli_resource_delete(crmd_channel, host_uname, rsc,
NULL, 0, FALSE, data_set);
if ((rc == pcmk_ok) && !BE_QUIET) {
// Show any reasons why resource might stay stopped
cli_resource_check(cib_conn, rsc);
}
if (rc == pcmk_ok) {
start_mainloop();
}
} else if (rsc_cmd == 'R') {
const char *router_node = host_uname;
xmlNode *msg_data = NULL;
xmlNode *cmd = NULL;
- int attr_options = attrd_opt_none;
+ int attr_options = pcmk__node_attr_none;
if (host_uname) {
node_t *node = pe_find_node(data_set->nodes, host_uname);
if (pe__is_guest_or_remote_node(node)) {
node = pe__current_node(node->details->remote_rsc);
if (node == NULL) {
CMD_ERR("No cluster connection to Pacemaker Remote node %s detected",
host_uname);
rc = -ENXIO;
goto bail;
}
router_node = node->details->uname;
- attr_options |= attrd_opt_remote;
+ attr_options |= pcmk__node_attr_remote;
}
}
if (crmd_channel == NULL) {
printf("Dry run: skipping clean-up of %s due to CIB_file\n",
host_uname? host_uname : "all nodes");
rc = pcmk_ok;
goto bail;
}
msg_data = create_xml_node(NULL, "crm-resource-reprobe-op");
crm_xml_add(msg_data, XML_LRM_ATTR_TARGET, host_uname);
if (safe_str_neq(router_node, host_uname)) {
crm_xml_add(msg_data, XML_LRM_ATTR_ROUTER_NODE, router_node);
}
cmd = create_request(CRM_OP_REPROBE, msg_data, router_node,
CRM_SYSTEM_CRMD, crm_system_name, our_pid);
free_xml(msg_data);
crm_debug("Re-checking the state of all resources on %s", host_uname?host_uname:"all nodes");
- rc = attrd_clear_delegate(NULL, host_uname, NULL, NULL, NULL, NULL,
- attr_options);
+ rc = pcmk_rc2legacy(pcmk__node_attr_request_clear(NULL, host_uname,
+ NULL, NULL, NULL,
+ NULL, attr_options));
if (crm_ipc_send(crmd_channel, cmd, 0, 0, NULL) > 0) {
start_mainloop();
}
free_xml(cmd);
} else if (rsc_cmd == 'D') {
xmlNode *msg_data = NULL;
if (rsc_type == NULL) {
CMD_ERR("You need to specify a resource type with -t");
rc = -ENXIO;
goto bail;
}
msg_data = create_xml_node(NULL, rsc_type);
crm_xml_add(msg_data, XML_ATTR_ID, rsc_id);
rc = cib_conn->cmds->remove(cib_conn, XML_CIB_TAG_RESOURCES, msg_data, cib_options);
free_xml(msg_data);
} else {
CMD_ERR("Unknown command: %c", rsc_cmd);
}
bail:
free(our_pid);
pe_free_working_set(data_set);
if (cib_conn != NULL) {
cib_conn->cmds->signoff(cib_conn);
cib_delete(cib_conn);
}
if (is_ocf_rc) {
exit_code = rc;
} else if (rc != pcmk_ok) {
CMD_ERR("Error performing operation: %s", pcmk_strerror(rc));
if (rc == -pcmk_err_no_quorum) {
CMD_ERR("To ignore quorum, use the force option");
}
if (exit_code == CRM_EX_OK) {
exit_code = crm_errno2exit(rc);
}
}
crm_exit(exit_code);
}
diff --git a/tools/crm_resource.h b/tools/crm_resource.h
index 48236b6695..84b094b1b6 100644
--- a/tools/crm_resource.h
+++ b/tools/crm_resource.h
@@ -1,103 +1,103 @@
/*
* Copyright 2004-2019 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/crm.h>
#include <crm/msg_xml.h>
#include <crm/services.h>
#include <crm/common/xml.h>
#include <crm/common/mainloop.h>
#include <crm/cib.h>
-#include <crm/attrd.h>
+#include <crm/common/attrd_internal.h>
#include <crm/pengine/rules.h>
#include <crm/pengine/status.h>
#include <crm/pengine/internal.h>
#include <pacemaker-internal.h>
extern bool print_pending;
extern bool scope_master;
extern bool do_force;
extern bool BE_QUIET;
extern int resource_verbose;
extern int cib_options;
extern int crmd_replies_needed;
extern char *move_lifetime;
extern const char *attr_set_type;
/* ban */
int cli_resource_prefer(const char *rsc_id, const char *host, cib_t * cib_conn);
int cli_resource_ban(const char *rsc_id, const char *host, GListPtr allnodes, cib_t * cib_conn);
int cli_resource_clear(const char *rsc_id, const char *host, GListPtr allnodes, cib_t * cib_conn,
bool clear_ban_constraints);
int cli_resource_clear_all_expired(xmlNode *root, cib_t *cib_conn, const char *rsc, const char *node, bool scope_master);
/* print */
void cli_resource_print_cts(resource_t * rsc);
void cli_resource_print_raw(resource_t * rsc);
void cli_resource_print_cts_constraints(pe_working_set_t * data_set);
void cli_resource_print_location(resource_t * rsc, const char *prefix);
void cli_resource_print_colocation(resource_t * rsc, bool dependents, bool recursive, int offset);
int cli_resource_print(resource_t *rsc, pe_working_set_t *data_set,
bool expanded);
int cli_resource_print_list(pe_working_set_t * data_set, bool raw);
int cli_resource_print_attribute(resource_t *rsc, const char *attr,
pe_working_set_t *data_set);
int cli_resource_print_property(resource_t *rsc, const char *attr,
pe_working_set_t *data_set);
int cli_resource_print_operations(const char *rsc_id, const char *host_uname, bool active, pe_working_set_t * data_set);
/* runtime */
void cli_resource_check(cib_t * cib, resource_t *rsc);
int cli_resource_fail(crm_ipc_t * crmd_channel, const char *host_uname, const char *rsc_id, pe_working_set_t * data_set);
int cli_resource_search(resource_t *rsc, const char *requested_name,
pe_working_set_t *data_set);
int cli_resource_delete(crm_ipc_t *crmd_channel, const char *host_uname,
resource_t *rsc, const char *operation,
const char *interval_spec, bool just_failures,
pe_working_set_t *data_set);
int cli_cleanup_all(crm_ipc_t *crmd_channel, const char *node_name,
const char *operation, const char *interval_spec,
pe_working_set_t *data_set);
int cli_resource_restart(pe_resource_t *rsc, const char *host, int timeout_ms,
cib_t *cib);
int cli_resource_move(resource_t *rsc, const char *rsc_id,
const char *host_name, cib_t *cib,
pe_working_set_t *data_set);
int cli_resource_execute_from_params(const char *rsc_name, const char *rsc_class,
const char *rsc_prov, const char *rsc_type,
const char *rsc_action, GHashTable *params,
GHashTable *override_hash, int timeout_ms);
int cli_resource_execute(resource_t *rsc, const char *requested_name,
const char *rsc_action, GHashTable *override_hash,
int timeout_ms, cib_t *cib,
pe_working_set_t *data_set);
int cli_resource_update_attribute(resource_t *rsc, const char *requested_name,
const char *attr_set, const char *attr_id,
const char *attr_name, const char *attr_value,
bool recursive, cib_t *cib,
pe_working_set_t *data_set);
int cli_resource_delete_attribute(resource_t *rsc, const char *requested_name,
const char *attr_set, const char *attr_id,
const char *attr_name, cib_t *cib,
pe_working_set_t *data_set);
GList* subtract_lists(GList *from, GList *items, GCompareFunc cmp);
int update_working_set_xml(pe_working_set_t *data_set, xmlNode **xml);
int wait_till_stable(int timeout_ms, cib_t * cib);
void cli_resource_why(cib_t *cib_conn, GListPtr resources, resource_t *rsc,
node_t *node);
diff --git a/tools/crm_resource_runtime.c b/tools/crm_resource_runtime.c
index 01919f67e2..61eb82a56e 100644
--- a/tools/crm_resource_runtime.c
+++ b/tools/crm_resource_runtime.c
@@ -1,2103 +1,2104 @@
/*
- * Copyright 2004-2019 the Pacemaker project contributors
+ * Copyright 2004-2020 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_resource.h>
int resource_verbose = 0;
bool do_force = FALSE;
int crmd_replies_needed = 1; /* The welcome message */
const char *attr_set_type = XML_TAG_ATTR_SETS;
static int
do_find_resource(const char *rsc, resource_t * the_rsc, pe_working_set_t * data_set)
{
int found = 0;
GListPtr lpc = NULL;
for (lpc = the_rsc->running_on; lpc != NULL; lpc = lpc->next) {
node_t *node = (node_t *) lpc->data;
if (BE_QUIET) {
fprintf(stdout, "%s\n", node->details->uname);
} else {
const char *state = "";
if (!pe_rsc_is_clone(the_rsc) && the_rsc->fns->state(the_rsc, TRUE) == RSC_ROLE_MASTER) {
state = "Master";
}
fprintf(stdout, "resource %s is running on: %s %s\n", rsc, node->details->uname, state);
}
found++;
}
if (BE_QUIET == FALSE && found == 0) {
fprintf(stderr, "resource %s is NOT running\n", rsc);
}
return found;
}
int
cli_resource_search(resource_t *rsc, const char *requested_name,
pe_working_set_t *data_set)
{
int found = 0;
resource_t *parent = uber_parent(rsc);
if (pe_rsc_is_clone(rsc)) {
for (GListPtr iter = rsc->children; iter != NULL; iter = iter->next) {
found += do_find_resource(requested_name, iter->data, data_set);
}
/* The anonymous clone children's common ID is supplied */
} else if (pe_rsc_is_clone(parent)
&& is_not_set(rsc->flags, pe_rsc_unique)
&& rsc->clone_name
&& safe_str_eq(requested_name, rsc->clone_name)
&& safe_str_neq(requested_name, rsc->id)) {
for (GListPtr iter = parent->children; iter; iter = iter->next) {
found += do_find_resource(requested_name, iter->data, data_set);
}
} else {
found += do_find_resource(requested_name, rsc, data_set);
}
return found;
}
#define XPATH_MAX 1024
static int
find_resource_attr(cib_t * the_cib, const char *attr, const char *rsc, const char *set_type,
const char *set_name, const char *attr_id, const char *attr_name, char **value)
{
int offset = 0;
int rc = pcmk_ok;
xmlNode *xml_search = NULL;
char *xpath_string = NULL;
if(value) {
*value = NULL;
}
if(the_cib == NULL) {
return -ENOTCONN;
}
xpath_string = calloc(1, XPATH_MAX);
offset +=
snprintf(xpath_string + offset, XPATH_MAX - offset, "%s", get_object_path("resources"));
offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "//*[@id=\"%s\"]", rsc);
if (set_type) {
offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "/%s", set_type);
if (set_name) {
offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "[@id=\"%s\"]", set_name);
}
}
offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "//nvpair[");
if (attr_id) {
offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "@id=\"%s\"", attr_id);
}
if (attr_name) {
if (attr_id) {
offset += snprintf(xpath_string + offset, XPATH_MAX - offset, " and ");
}
offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "@name=\"%s\"", attr_name);
}
offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "]");
CRM_LOG_ASSERT(offset > 0);
rc = the_cib->cmds->query(the_cib, xpath_string, &xml_search,
cib_sync_call | cib_scope_local | cib_xpath);
if (rc != pcmk_ok) {
goto bail;
}
crm_log_xml_debug(xml_search, "Match");
if (xml_has_children(xml_search)) {
xmlNode *child = NULL;
rc = -EINVAL;
printf("Multiple attributes match name=%s\n", attr_name);
for (child = __xml_first_child(xml_search); child != NULL; child = __xml_next(child)) {
printf(" Value: %s \t(id=%s)\n",
crm_element_value(child, XML_NVPAIR_ATTR_VALUE), ID(child));
}
} else if(value) {
const char *tmp = crm_element_value(xml_search, attr);
if (tmp) {
*value = strdup(tmp);
}
}
bail:
free(xpath_string);
free_xml(xml_search);
return rc;
}
/* PRIVATE. Use the find_matching_attr_resources instead. */
static void
find_matching_attr_resources_recursive(GList/* <resource_t*> */ ** result, resource_t * rsc, const char * rsc_id,
const char * attr_set, const char * attr_id,
const char * attr_name, cib_t * cib, const char * cmd, int depth)
{
int rc = pcmk_ok;
char *lookup_id = clone_strip(rsc->id);
char *local_attr_id = NULL;
/* visit the children */
for(GList *gIter = rsc->children; gIter; gIter = gIter->next) {
find_matching_attr_resources_recursive(result, (resource_t*)gIter->data, rsc_id, attr_set, attr_id, attr_name, cib, cmd, depth+1);
/* do it only once for clones */
if(pe_clone == rsc->variant) {
break;
}
}
rc = find_resource_attr(cib, XML_ATTR_ID, lookup_id, attr_set_type, attr_set, attr_id, attr_name, &local_attr_id);
/* Post-order traversal.
* The root is always on the list and it is the last item. */
if((0 == depth) || (pcmk_ok == rc)) {
/* push the head */
*result = g_list_append(*result, rsc);
}
free(local_attr_id);
free(lookup_id);
}
/* The result is a linearized pre-ordered tree of resources. */
static GList/*<resource_t*>*/ *
find_matching_attr_resources(resource_t * rsc, const char * rsc_id, const char * attr_set, const char * attr_id,
const char * attr_name, cib_t * cib, const char * cmd)
{
int rc = pcmk_ok;
char *lookup_id = NULL;
char *local_attr_id = NULL;
GList * result = NULL;
/* If --force is used, update only the requested resource (clone or primitive).
* Otherwise, if the primitive has the attribute, use that.
* Otherwise use the clone. */
if(do_force == TRUE) {
return g_list_append(result, rsc);
}
if(rsc->parent && pe_clone == rsc->parent->variant) {
int rc = pcmk_ok;
char *local_attr_id = NULL;
rc = find_resource_attr(cib, XML_ATTR_ID, rsc_id, attr_set_type, attr_set, attr_id, attr_name, &local_attr_id);
free(local_attr_id);
if(rc != pcmk_ok) {
rsc = rsc->parent;
if (BE_QUIET == FALSE) {
printf("Performing %s of '%s' on '%s', the parent of '%s'\n", cmd, attr_name, rsc->id, rsc_id);
}
}
return g_list_append(result, rsc);
} else if(rsc->parent == NULL && rsc->children && pe_clone == rsc->variant) {
resource_t *child = rsc->children->data;
if(child->variant == pe_native) {
lookup_id = clone_strip(child->id); /* Could be a cloned group! */
rc = find_resource_attr(cib, XML_ATTR_ID, lookup_id, attr_set_type, attr_set, attr_id, attr_name, &local_attr_id);
if(rc == pcmk_ok) {
rsc = child;
if (BE_QUIET == FALSE) {
printf("A value for '%s' already exists in child '%s', performing %s on that instead of '%s'\n", attr_name, lookup_id, cmd, rsc_id);
}
}
free(local_attr_id);
free(lookup_id);
}
return g_list_append(result, rsc);
}
/* If the resource is a group ==> children inherit the attribute if defined. */
find_matching_attr_resources_recursive(&result, rsc, rsc_id, attr_set, attr_id, attr_name, cib, cmd, 0);
return result;
}
int
cli_resource_update_attribute(resource_t *rsc, const char *requested_name,
const char *attr_set, const char *attr_id,
const char *attr_name, const char *attr_value,
bool recursive, cib_t *cib,
pe_working_set_t *data_set)
{
int rc = pcmk_ok;
static bool need_init = TRUE;
char *local_attr_id = NULL;
char *local_attr_set = NULL;
GList/*<resource_t*>*/ *resources = NULL;
const char *common_attr_id = attr_id;
if(attr_id == NULL
&& do_force == FALSE
&& find_resource_attr(
cib, XML_ATTR_ID, uber_parent(rsc)->id, NULL, NULL, NULL, attr_name, NULL) == -EINVAL) {
printf("\n");
}
if (safe_str_eq(attr_set_type, XML_TAG_ATTR_SETS)) {
if (do_force == FALSE) {
rc = find_resource_attr(cib, XML_ATTR_ID, uber_parent(rsc)->id,
XML_TAG_META_SETS, attr_set, attr_id,
attr_name, &local_attr_id);
if (rc == pcmk_ok && BE_QUIET == FALSE) {
printf("WARNING: There is already a meta attribute for '%s' called '%s' (id=%s)\n",
uber_parent(rsc)->id, attr_name, local_attr_id);
printf(" Delete '%s' first or use the force option to override\n",
local_attr_id);
}
free(local_attr_id);
if (rc == pcmk_ok) {
return -ENOTUNIQ;
}
}
resources = g_list_append(resources, rsc);
} else {
resources = find_matching_attr_resources(rsc, requested_name, attr_set,
attr_id, attr_name, cib, "update");
}
/* If either attr_set or attr_id is specified,
* one clearly intends to modify a single resource.
* It is the last item on the resource list.*/
for(GList *gIter = (attr_set||attr_id) ? g_list_last(resources) : resources
; gIter; gIter = gIter->next) {
char *lookup_id = NULL;
xmlNode *xml_top = NULL;
xmlNode *xml_obj = NULL;
local_attr_id = NULL;
local_attr_set = NULL;
rsc = (resource_t*)gIter->data;
attr_id = common_attr_id;
lookup_id = clone_strip(rsc->id); /* Could be a cloned group! */
rc = find_resource_attr(cib, XML_ATTR_ID, lookup_id, attr_set_type, attr_set, attr_id, attr_name,
&local_attr_id);
if (rc == pcmk_ok) {
crm_debug("Found a match for name=%s: id=%s", attr_name, local_attr_id);
attr_id = local_attr_id;
} else if (rc != -ENXIO) {
free(lookup_id);
free(local_attr_id);
g_list_free(resources);
return rc;
} else {
const char *tag = crm_element_name(rsc->xml);
if (attr_set == NULL) {
local_attr_set = crm_concat(lookup_id, attr_set_type, '-');
attr_set = local_attr_set;
}
if (attr_id == NULL) {
local_attr_id = crm_concat(attr_set, attr_name, '-');
attr_id = local_attr_id;
}
xml_top = create_xml_node(NULL, tag);
crm_xml_add(xml_top, XML_ATTR_ID, lookup_id);
xml_obj = create_xml_node(xml_top, attr_set_type);
crm_xml_add(xml_obj, XML_ATTR_ID, attr_set);
}
xml_obj = crm_create_nvpair_xml(xml_obj, attr_id, attr_name, attr_value);
if (xml_top == NULL) {
xml_top = xml_obj;
}
crm_log_xml_debug(xml_top, "Update");
rc = cib->cmds->modify(cib, XML_CIB_TAG_RESOURCES, xml_top, cib_options);
if (rc == pcmk_ok && BE_QUIET == FALSE) {
printf("Set '%s' option: id=%s%s%s%s%s value=%s\n", lookup_id, local_attr_id,
attr_set ? " set=" : "", attr_set ? attr_set : "",
attr_name ? " name=" : "", attr_name ? attr_name : "", attr_value);
}
free_xml(xml_top);
free(lookup_id);
free(local_attr_id);
free(local_attr_set);
if(recursive && safe_str_eq(attr_set_type, XML_TAG_META_SETS)) {
GListPtr lpc = NULL;
if(need_init) {
xmlNode *cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, data_set->input);
need_init = FALSE;
unpack_constraints(cib_constraints, data_set);
for (lpc = data_set->resources; lpc != NULL; lpc = lpc->next) {
resource_t *r = (resource_t *) lpc->data;
clear_bit(r->flags, pe_rsc_allocating);
}
}
crm_debug("Looking for dependencies %p", rsc->rsc_cons_lhs);
set_bit(rsc->flags, pe_rsc_allocating);
for (lpc = rsc->rsc_cons_lhs; lpc != NULL; lpc = lpc->next) {
rsc_colocation_t *cons = (rsc_colocation_t *) lpc->data;
resource_t *peer = cons->rsc_lh;
crm_debug("Checking %s %d", cons->id, cons->score);
if (cons->score > 0 && is_not_set(peer->flags, pe_rsc_allocating)) {
/* Don't get into colocation loops */
crm_debug("Setting %s=%s for dependent resource %s", attr_name, attr_value, peer->id);
cli_resource_update_attribute(peer, peer->id, NULL, NULL,
attr_name, attr_value, recursive,
cib, data_set);
}
}
}
}
g_list_free(resources);
return rc;
}
int
cli_resource_delete_attribute(resource_t *rsc, const char *requested_name,
const char *attr_set, const char *attr_id,
const char *attr_name, cib_t *cib,
pe_working_set_t *data_set)
{
int rc = pcmk_ok;
GList/*<resource_t*>*/ *resources = NULL;
if(attr_id == NULL
&& do_force == FALSE
&& find_resource_attr(
cib, XML_ATTR_ID, uber_parent(rsc)->id, NULL, NULL, NULL, attr_name, NULL) == -EINVAL) {
printf("\n");
}
if(safe_str_eq(attr_set_type, XML_TAG_META_SETS)) {
resources = find_matching_attr_resources(rsc, requested_name, attr_set,
attr_id, attr_name, cib, "delete");
} else {
resources = g_list_append(resources, rsc);
}
for(GList *gIter = resources; gIter; gIter = gIter->next) {
char *lookup_id = NULL;
xmlNode *xml_obj = NULL;
char *local_attr_id = NULL;
rsc = (resource_t*)gIter->data;
lookup_id = clone_strip(rsc->id);
rc = find_resource_attr(cib, XML_ATTR_ID, lookup_id, attr_set_type, attr_set, attr_id, attr_name,
&local_attr_id);
if (rc == -ENXIO) {
free(lookup_id);
rc = pcmk_ok;
continue;
} else if (rc != pcmk_ok) {
free(lookup_id);
g_list_free(resources);
return rc;
}
if (attr_id == NULL) {
attr_id = local_attr_id;
}
xml_obj = crm_create_nvpair_xml(NULL, attr_id, attr_name, NULL);
crm_log_xml_debug(xml_obj, "Delete");
CRM_ASSERT(cib);
rc = cib->cmds->remove(cib, XML_CIB_TAG_RESOURCES, xml_obj, cib_options);
if (rc == pcmk_ok && BE_QUIET == FALSE) {
printf("Deleted '%s' option: id=%s%s%s%s%s\n", lookup_id, local_attr_id,
attr_set ? " set=" : "", attr_set ? attr_set : "",
attr_name ? " name=" : "", attr_name ? attr_name : "");
}
free(lookup_id);
free_xml(xml_obj);
free(local_attr_id);
}
g_list_free(resources);
return rc;
}
static int
send_lrm_rsc_op(crm_ipc_t * crmd_channel, const char *op,
const char *host_uname, const char *rsc_id,
bool only_failed, pe_working_set_t * data_set)
{
char *our_pid = NULL;
char *key = NULL;
int rc = -ECOMM;
xmlNode *cmd = NULL;
xmlNode *xml_rsc = NULL;
const char *value = NULL;
const char *router_node = host_uname;
xmlNode *params = NULL;
xmlNode *msg_data = NULL;
resource_t *rsc = pe_find_resource(data_set->resources, rsc_id);
if (rsc == NULL) {
CMD_ERR("Resource %s not found", rsc_id);
return -ENXIO;
} else if (rsc->variant != pe_native) {
CMD_ERR("We can only process primitive resources, not %s", rsc_id);
return -EINVAL;
} else if (host_uname == NULL) {
CMD_ERR("Please specify a node name");
return -EINVAL;
} else {
node_t *node = pe_find_node(data_set->nodes, host_uname);
if (pe__is_guest_or_remote_node(node)) {
node = pe__current_node(node->details->remote_rsc);
if (node == NULL) {
CMD_ERR("No cluster connection to Pacemaker Remote node %s detected",
host_uname);
return -ENXIO;
}
router_node = node->details->uname;
}
}
key = generate_transition_key(0, getpid(), 0, "xxxxxxxx-xrsc-opxx-xcrm-resourcexxxx");
msg_data = create_xml_node(NULL, XML_GRAPH_TAG_RSC_OP);
crm_xml_add(msg_data, XML_ATTR_TRANSITION_KEY, key);
free(key);
crm_xml_add(msg_data, XML_LRM_ATTR_TARGET, host_uname);
if (safe_str_neq(router_node, host_uname)) {
crm_xml_add(msg_data, XML_LRM_ATTR_ROUTER_NODE, router_node);
}
xml_rsc = create_xml_node(msg_data, XML_CIB_TAG_RESOURCE);
if (rsc->clone_name) {
crm_xml_add(xml_rsc, XML_ATTR_ID, rsc->clone_name);
crm_xml_add(xml_rsc, XML_ATTR_ID_LONG, rsc->id);
} else {
crm_xml_add(xml_rsc, XML_ATTR_ID, rsc->id);
}
value = crm_copy_xml_element(rsc->xml, xml_rsc, XML_ATTR_TYPE);
if (value == NULL) {
CMD_ERR("%s has no type! Aborting...", rsc_id);
return -ENXIO;
}
value = crm_copy_xml_element(rsc->xml, xml_rsc, XML_AGENT_ATTR_CLASS);
if (value == NULL) {
CMD_ERR("%s has no class! Aborting...", rsc_id);
return -ENXIO;
}
crm_copy_xml_element(rsc->xml, xml_rsc, XML_AGENT_ATTR_PROVIDER);
params = create_xml_node(msg_data, XML_TAG_ATTRS);
crm_xml_add(params, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET);
key = crm_meta_name(XML_LRM_ATTR_INTERVAL_MS);
crm_xml_add(params, key, "60000"); /* 1 minute */
free(key);
our_pid = crm_getpid_s();
cmd = create_request(op, msg_data, router_node, CRM_SYSTEM_CRMD, crm_system_name, our_pid);
/* crm_log_xml_warn(cmd, "send_lrm_rsc_op"); */
free_xml(msg_data);
if (crm_ipc_send(crmd_channel, cmd, 0, 0, NULL) > 0) {
rc = 0;
} else {
crm_debug("Could not send %s op to the controller", op);
rc = -ENOTCONN;
}
free_xml(cmd);
return rc;
}
/*!
* \internal
* \brief Get resource name as used in failure-related node attributes
*
* \param[in] rsc Resource to check
*
* \return Newly allocated string containing resource's fail name
* \note The caller is responsible for freeing the result.
*/
static inline char *
rsc_fail_name(resource_t *rsc)
{
const char *name = (rsc->clone_name? rsc->clone_name : rsc->id);
return is_set(rsc->flags, pe_rsc_unique)? strdup(name) : clone_strip(name);
}
static int
clear_rsc_history(crm_ipc_t *crmd_channel, const char *host_uname,
const char *rsc_id, pe_working_set_t *data_set)
{
int rc = pcmk_ok;
/* Erase the resource's entire LRM history in the CIB, even if we're only
* clearing a single operation's fail count. If we erased only entries for a
* single operation, we might wind up with a wrong idea of the current
* resource state, and we might not re-probe the resource.
*/
rc = send_lrm_rsc_op(crmd_channel, CRM_OP_LRM_DELETE, host_uname, rsc_id,
TRUE, data_set);
if (rc != pcmk_ok) {
return rc;
}
crmd_replies_needed++;
crm_trace("Processing %d mainloop inputs", crmd_replies_needed);
while (g_main_context_iteration(NULL, FALSE)) {
crm_trace("Processed mainloop input, %d still remaining",
crmd_replies_needed);
}
if (crmd_replies_needed < 0) {
crmd_replies_needed = 0;
}
return rc;
}
static int
clear_rsc_failures(crm_ipc_t *crmd_channel, const char *node_name,
const char *rsc_id, const char *operation,
const char *interval_spec, pe_working_set_t *data_set)
{
int rc = pcmk_ok;
const char *failed_value = NULL;
const char *failed_id = NULL;
const char *interval_ms_s = NULL;
GHashTable *rscs = NULL;
GHashTableIter iter;
/* Create a hash table to use as a set of resources to clean. This lets us
* clean each resource only once (per node) regardless of how many failed
* operations it has.
*/
rscs = g_hash_table_new_full(crm_str_hash, g_str_equal, NULL, NULL);
// Normalize interval to milliseconds for comparison to history entry
if (operation) {
interval_ms_s = crm_strdup_printf("%u",
crm_parse_interval_spec(interval_spec));
}
for (xmlNode *xml_op = __xml_first_child(data_set->failed); xml_op != NULL;
xml_op = __xml_next(xml_op)) {
failed_id = crm_element_value(xml_op, XML_LRM_ATTR_RSCID);
if (failed_id == NULL) {
// Malformed history entry, should never happen
continue;
}
// No resource specified means all resources match
if (rsc_id) {
resource_t *fail_rsc = pe_find_resource_with_flags(data_set->resources,
failed_id,
pe_find_renamed|pe_find_anon);
if (!fail_rsc || safe_str_neq(rsc_id, fail_rsc->id)) {
continue;
}
}
// Host name should always have been provided by this point
failed_value = crm_element_value(xml_op, XML_ATTR_UNAME);
if (safe_str_neq(node_name, failed_value)) {
continue;
}
// No operation specified means all operations match
if (operation) {
failed_value = crm_element_value(xml_op, XML_LRM_ATTR_TASK);
if (safe_str_neq(operation, failed_value)) {
continue;
}
// Interval (if operation was specified) defaults to 0 (not all)
failed_value = crm_element_value(xml_op, XML_LRM_ATTR_INTERVAL_MS);
if (safe_str_neq(interval_ms_s, failed_value)) {
continue;
}
}
/* not available until glib 2.32
g_hash_table_add(rscs, (gpointer) failed_id);
*/
g_hash_table_insert(rscs, (gpointer) failed_id, (gpointer) failed_id);
}
g_hash_table_iter_init(&iter, rscs);
while (g_hash_table_iter_next(&iter, (gpointer *) &failed_id, NULL)) {
crm_debug("Erasing failures of %s on %s", failed_id, node_name);
rc = clear_rsc_history(crmd_channel, node_name, failed_id, data_set);
if (rc != pcmk_ok) {
return rc;
}
}
g_hash_table_destroy(rscs);
return rc;
}
static int
clear_rsc_fail_attrs(resource_t *rsc, const char *operation,
const char *interval_spec, node_t *node)
{
int rc = pcmk_ok;
- int attr_options = attrd_opt_none;
+ int attr_options = pcmk__node_attr_none;
char *rsc_name = rsc_fail_name(rsc);
if (pe__is_guest_or_remote_node(node)) {
- attr_options |= attrd_opt_remote;
+ attr_options |= pcmk__node_attr_remote;
}
- rc = attrd_clear_delegate(NULL, node->details->uname, rsc_name, operation,
- interval_spec, NULL, attr_options);
+ rc = pcmk__node_attr_request_clear(NULL, node->details->uname, rsc_name,
+ operation, interval_spec, NULL,
+ attr_options);
free(rsc_name);
return rc;
}
int
cli_resource_delete(crm_ipc_t *crmd_channel, const char *host_uname,
resource_t *rsc, const char *operation,
const char *interval_spec, bool just_failures,
pe_working_set_t *data_set)
{
int rc = pcmk_ok;
node_t *node = NULL;
if (rsc == NULL) {
return -ENXIO;
} else if (rsc->children) {
GListPtr lpc = NULL;
for (lpc = rsc->children; lpc != NULL; lpc = lpc->next) {
resource_t *child = (resource_t *) lpc->data;
rc = cli_resource_delete(crmd_channel, host_uname, child, operation,
interval_spec, just_failures, data_set);
if (rc != pcmk_ok) {
return rc;
}
}
return pcmk_ok;
} else if (host_uname == NULL) {
GListPtr lpc = NULL;
GListPtr nodes = g_hash_table_get_values(rsc->known_on);
if(nodes == NULL && do_force) {
nodes = node_list_dup(data_set->nodes, FALSE, FALSE);
} else if(nodes == NULL && rsc->exclusive_discover) {
GHashTableIter iter;
pe_node_t *node = NULL;
g_hash_table_iter_init(&iter, rsc->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (void**)&node)) {
if(node->weight >= 0) {
nodes = g_list_prepend(nodes, node);
}
}
} else if(nodes == NULL) {
nodes = g_hash_table_get_values(rsc->allowed_nodes);
}
for (lpc = nodes; lpc != NULL; lpc = lpc->next) {
node = (node_t *) lpc->data;
if (node->details->online) {
rc = cli_resource_delete(crmd_channel, node->details->uname,
rsc, operation, interval_spec,
just_failures, data_set);
}
if (rc != pcmk_ok) {
g_list_free(nodes);
return rc;
}
}
g_list_free(nodes);
return pcmk_ok;
}
node = pe_find_node(data_set->nodes, host_uname);
if (node == NULL) {
printf("Unable to clean up %s because node %s not found\n",
rsc->id, host_uname);
return -ENODEV;
}
if (!node->details->rsc_discovery_enabled) {
printf("Unable to clean up %s because resource discovery disabled on %s\n",
rsc->id, host_uname);
return -EOPNOTSUPP;
}
if (crmd_channel == NULL) {
printf("Dry run: skipping clean-up of %s on %s due to CIB_file\n",
rsc->id, host_uname);
return pcmk_ok;
}
rc = clear_rsc_fail_attrs(rsc, operation, interval_spec, node);
- if (rc != pcmk_ok) {
+ if (rc != pcmk_rc_ok) {
printf("Unable to clean up %s failures on %s: %s\n",
- rsc->id, host_uname, pcmk_strerror(rc));
- return rc;
+ rsc->id, host_uname, pcmk_rc_str(rc));
+ return pcmk_rc2legacy(rc);
}
if (just_failures) {
rc = clear_rsc_failures(crmd_channel, host_uname, rsc->id, operation,
interval_spec, data_set);
} else {
rc = clear_rsc_history(crmd_channel, host_uname, rsc->id, data_set);
}
if (rc != pcmk_ok) {
printf("Cleaned %s failures on %s, but unable to clean history: %s\n",
rsc->id, host_uname, pcmk_strerror(rc));
} else {
printf("Cleaned up %s on %s\n", rsc->id, host_uname);
}
return rc;
}
int
cli_cleanup_all(crm_ipc_t *crmd_channel, const char *node_name,
const char *operation, const char *interval_spec,
pe_working_set_t *data_set)
{
int rc = pcmk_ok;
- int attr_options = attrd_opt_none;
+ int attr_options = pcmk__node_attr_none;
const char *display_name = node_name? node_name : "all nodes";
if (crmd_channel == NULL) {
printf("Dry run: skipping clean-up of %s due to CIB_file\n",
display_name);
return pcmk_ok;
}
crmd_replies_needed = 0;
if (node_name) {
node_t *node = pe_find_node(data_set->nodes, node_name);
if (node == NULL) {
CMD_ERR("Unknown node: %s", node_name);
return -ENXIO;
}
if (pe__is_guest_or_remote_node(node)) {
- attr_options |= attrd_opt_remote;
+ attr_options |= pcmk__node_attr_remote;
}
}
- rc = attrd_clear_delegate(NULL, node_name, NULL, operation, interval_spec,
- NULL, attr_options);
- if (rc != pcmk_ok) {
+ rc = pcmk__node_attr_request_clear(NULL, node_name, NULL, operation,
+ interval_spec, NULL, attr_options);
+ if (rc != pcmk_rc_ok) {
printf("Unable to clean up all failures on %s: %s\n",
- display_name, pcmk_strerror(rc));
- return rc;
+ display_name, pcmk_rc_str(rc));
+ return pcmk_rc2legacy(rc);
}
if (node_name) {
rc = clear_rsc_failures(crmd_channel, node_name, NULL,
operation, interval_spec, data_set);
if (rc != pcmk_ok) {
printf("Cleaned all resource failures on %s, but unable to clean history: %s\n",
node_name, pcmk_strerror(rc));
return rc;
}
} else {
for (GList *iter = data_set->nodes; iter; iter = iter->next) {
pe_node_t *node = (pe_node_t *) iter->data;
rc = clear_rsc_failures(crmd_channel, node->details->uname, NULL,
operation, interval_spec, data_set);
if (rc != pcmk_ok) {
printf("Cleaned all resource failures on all nodes, but unable to clean history: %s\n",
pcmk_strerror(rc));
return rc;
}
}
}
printf("Cleaned up all resources on %s\n", display_name);
return pcmk_ok;
}
void
cli_resource_check(cib_t * cib_conn, resource_t *rsc)
{
int need_nl = 0;
char *role_s = NULL;
char *managed = NULL;
resource_t *parent = uber_parent(rsc);
find_resource_attr(cib_conn, XML_NVPAIR_ATTR_VALUE, parent->id,
NULL, NULL, NULL, XML_RSC_ATTR_MANAGED, &managed);
find_resource_attr(cib_conn, XML_NVPAIR_ATTR_VALUE, parent->id,
NULL, NULL, NULL, XML_RSC_ATTR_TARGET_ROLE, &role_s);
if(role_s) {
enum rsc_role_e role = text2role(role_s);
free(role_s);
if(role == RSC_ROLE_UNKNOWN) {
// Treated as if unset
} else if(role == RSC_ROLE_STOPPED) {
printf("\n * The configuration specifies that '%s' should remain stopped\n", parent->id);
need_nl++;
} else if (is_set(parent->flags, pe_rsc_promotable)
&& (role == RSC_ROLE_SLAVE)) {
printf("\n * The configuration specifies that '%s' should not be promoted\n", parent->id);
need_nl++;
}
}
if(managed && crm_is_true(managed) == FALSE) {
printf("%s * The configuration prevents the cluster from stopping or starting '%s' (unmanaged)\n", need_nl == 0?"\n":"", parent->id);
need_nl++;
}
free(managed);
if(need_nl) {
printf("\n");
}
}
int
cli_resource_fail(crm_ipc_t * crmd_channel, const char *host_uname,
const char *rsc_id, pe_working_set_t * data_set)
{
crm_warn("Failing: %s", rsc_id);
return send_lrm_rsc_op(crmd_channel, CRM_OP_LRM_FAIL, host_uname, rsc_id, FALSE, data_set);
}
static GHashTable *
generate_resource_params(resource_t * rsc, pe_working_set_t * data_set)
{
GHashTable *params = NULL;
GHashTable *meta = NULL;
GHashTable *combined = NULL;
GHashTableIter iter;
if (!rsc) {
crm_err("Resource does not exist in config");
return NULL;
}
params = crm_str_table_new();
meta = crm_str_table_new();
combined = crm_str_table_new();
get_rsc_attributes(params, rsc, NULL /* TODO: Pass in local node */ , data_set);
get_meta_attributes(meta, rsc, NULL /* TODO: Pass in local node */ , data_set);
if (params) {
char *key = NULL;
char *value = NULL;
g_hash_table_iter_init(&iter, params);
while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) {
g_hash_table_insert(combined, strdup(key), strdup(value));
}
g_hash_table_destroy(params);
}
if (meta) {
char *key = NULL;
char *value = NULL;
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);
g_hash_table_insert(combined, crm_name, strdup(value));
}
g_hash_table_destroy(meta);
}
return combined;
}
static bool resource_is_running_on(resource_t *rsc, const char *host)
{
bool found = TRUE;
GListPtr hIter = NULL;
GListPtr hosts = NULL;
if(rsc == NULL) {
return FALSE;
}
rsc->fns->location(rsc, &hosts, TRUE);
for (hIter = hosts; host != NULL && hIter != NULL; hIter = hIter->next) {
pe_node_t *node = (pe_node_t *) hIter->data;
if(strcmp(host, node->details->uname) == 0) {
crm_trace("Resource %s is running on %s\n", rsc->id, host);
goto done;
} else if(strcmp(host, node->details->id) == 0) {
crm_trace("Resource %s is running on %s\n", rsc->id, host);
goto done;
}
}
if(host != NULL) {
crm_trace("Resource %s is not running on: %s\n", rsc->id, host);
found = FALSE;
} else if(host == NULL && hosts == NULL) {
crm_trace("Resource %s is not running\n", rsc->id);
found = FALSE;
}
done:
g_list_free(hosts);
return found;
}
/*!
* \internal
* \brief Create a list of all resources active on host from a given list
*
* \param[in] host Name of host to check whether resources are active
* \param[in] rsc_list List of resources to check
*
* \return New list of resources from list that are active on host
*/
static GList *
get_active_resources(const char *host, GList *rsc_list)
{
GList *rIter = NULL;
GList *active = NULL;
for (rIter = rsc_list; rIter != NULL; rIter = rIter->next) {
resource_t *rsc = (resource_t *) rIter->data;
/* Expand groups to their members, because if we're restarting a member
* other than the first, we can't otherwise tell which resources are
* stopping and starting.
*/
if (rsc->variant == pe_group) {
active = g_list_concat(active,
get_active_resources(host, rsc->children));
} else if (resource_is_running_on(rsc, host)) {
active = g_list_append(active, strdup(rsc->id));
}
}
return active;
}
GList*
subtract_lists(GList *from, GList *items, GCompareFunc cmp)
{
GList *item = NULL;
GList *result = g_list_copy(from);
for (item = items; item != NULL; item = item->next) {
GList *candidate = NULL;
for (candidate = from; candidate != NULL; candidate = candidate->next) {
crm_info("Comparing %s with %s", (const char *) candidate->data,
(const char *) item->data);
if(cmp(candidate->data, item->data) == 0) {
result = g_list_remove(result, candidate->data);
break;
}
}
}
return result;
}
static void dump_list(GList *items, const char *tag)
{
int lpc = 0;
GList *item = NULL;
for (item = items; item != NULL; item = item->next) {
crm_trace("%s[%d]: %s", tag, lpc, (char*)item->data);
lpc++;
}
}
static void display_list(GList *items, const char *tag)
{
GList *item = NULL;
for (item = items; item != NULL; item = item->next) {
fprintf(stdout, "%s%s\n", tag, (const char *)item->data);
}
}
/*!
* \internal
* \brief Upgrade XML to latest schema version and use it as working set input
*
* This also updates the working set timestamp to the current time.
*
* \param[in] data_set Working set instance to update
* \param[in] xml XML to use as input
*
* \return pcmk_ok on success, -ENOKEY if unable to upgrade XML
* \note On success, caller is responsible for freeing memory allocated for
* data_set->now.
* \todo This follows the example of other callers of cli_config_update()
* and returns -ENOKEY ("Required key not available") if that fails,
* but perhaps -pcmk_err_schema_validation would be better in that case.
*/
int
update_working_set_xml(pe_working_set_t *data_set, xmlNode **xml)
{
if (cli_config_update(xml, NULL, FALSE) == FALSE) {
return -ENOKEY;
}
data_set->input = *xml;
data_set->now = crm_time_new(NULL);
return pcmk_ok;
}
/*!
* \internal
* \brief Update a working set's XML input based on a CIB query
*
* \param[in] data_set Data set instance to initialize
* \param[in] cib Connection to the CIB manager
*
* \return pcmk_ok on success, -errno on failure
* \note On success, caller is responsible for freeing memory allocated for
* data_set->input and data_set->now.
*/
static int
update_working_set_from_cib(pe_working_set_t * data_set, cib_t *cib)
{
xmlNode *cib_xml_copy = NULL;
int rc;
rc = cib->cmds->query(cib, NULL, &cib_xml_copy, cib_scope_local | cib_sync_call);
if (rc != pcmk_ok) {
fprintf(stderr, "Could not obtain the current CIB: %s (%d)\n", pcmk_strerror(rc), rc);
return rc;
}
rc = update_working_set_xml(data_set, &cib_xml_copy);
if (rc != pcmk_ok) {
fprintf(stderr, "Could not upgrade the current CIB XML\n");
free_xml(cib_xml_copy);
return rc;
}
return pcmk_ok;
}
static int
update_dataset(cib_t *cib, pe_working_set_t * data_set, bool simulate)
{
char *pid = NULL;
char *shadow_file = NULL;
cib_t *shadow_cib = NULL;
int rc;
pe_reset_working_set(data_set);
rc = update_working_set_from_cib(data_set, cib);
if (rc != pcmk_ok) {
return rc;
}
if(simulate) {
pid = crm_getpid_s();
shadow_cib = cib_shadow_new(pid);
shadow_file = get_shadow_file(pid);
if (shadow_cib == NULL) {
fprintf(stderr, "Could not create shadow cib: '%s'\n", pid);
rc = -ENXIO;
goto cleanup;
}
rc = write_xml_file(data_set->input, shadow_file, FALSE);
if (rc < 0) {
fprintf(stderr, "Could not populate shadow cib: %s (%d)\n", pcmk_strerror(rc), rc);
goto cleanup;
}
rc = shadow_cib->cmds->signon(shadow_cib, crm_system_name, cib_command);
if(rc != pcmk_ok) {
fprintf(stderr, "Could not connect to shadow cib: %s (%d)\n", pcmk_strerror(rc), rc);
goto cleanup;
}
pcmk__schedule_actions(data_set, data_set->input, NULL);
run_simulation(data_set, shadow_cib, NULL, TRUE);
rc = update_dataset(shadow_cib, data_set, FALSE);
} else {
cluster_status(data_set);
}
cleanup:
/* Do not free data_set->input here, we need rsc->xml to be valid later on */
cib_delete(shadow_cib);
free(pid);
if(shadow_file) {
unlink(shadow_file);
free(shadow_file);
}
return rc;
}
static int
max_delay_for_resource(pe_working_set_t * data_set, resource_t *rsc)
{
int delay = 0;
int max_delay = 0;
if(rsc && rsc->children) {
GList *iter = NULL;
for(iter = rsc->children; iter; iter = iter->next) {
resource_t *child = (resource_t *)iter->data;
delay = max_delay_for_resource(data_set, child);
if(delay > max_delay) {
double seconds = delay / 1000.0;
crm_trace("Calculated new delay of %.1fs due to %s", seconds, child->id);
max_delay = delay;
}
}
} else if(rsc) {
char *key = crm_strdup_printf("%s_%s_0", rsc->id, RSC_STOP);
action_t *stop = custom_action(rsc, key, RSC_STOP, NULL, TRUE, FALSE, data_set);
const char *value = g_hash_table_lookup(stop->meta, XML_ATTR_TIMEOUT);
max_delay = crm_int_helper(value, NULL);
pe_free_action(stop);
}
return max_delay;
}
static int
max_delay_in(pe_working_set_t * data_set, GList *resources)
{
int max_delay = 0;
GList *item = NULL;
for (item = resources; item != NULL; item = item->next) {
int delay = 0;
resource_t *rsc = pe_find_resource(data_set->resources, (const char *)item->data);
delay = max_delay_for_resource(data_set, rsc);
if(delay > max_delay) {
double seconds = delay / 1000.0;
crm_trace("Calculated new delay of %.1fs due to %s", seconds, rsc->id);
max_delay = delay;
}
}
return 5 + (max_delay / 1000);
}
#define waiting_for_starts(d, r, h) ((g_list_length(d) > 0) || \
(resource_is_running_on((r), (h)) == FALSE))
/*!
* \internal
* \brief Restart a resource (on a particular host if requested).
*
* \param[in] rsc The resource to restart
* \param[in] host The host to restart the resource on (or NULL for all)
* \param[in] timeout_ms Consider failed if actions do not complete in this time
* (specified in milliseconds, but a two-second
* granularity is actually used; if 0, a timeout will be
* calculated based on the resource timeout)
* \param[in] cib Connection to the CIB manager
*
* \return pcmk_ok on success, -errno on failure (exits on certain failures)
*/
int
cli_resource_restart(pe_resource_t *rsc, const char *host, int timeout_ms,
cib_t *cib)
{
int rc = 0;
int lpc = 0;
int before = 0;
int step_timeout_s = 0;
int sleep_interval = 2;
int timeout = timeout_ms / 1000;
bool stop_via_ban = FALSE;
char *rsc_id = NULL;
char *orig_target_role = NULL;
GList *list_delta = NULL;
GList *target_active = NULL;
GList *current_active = NULL;
GList *restart_target_active = NULL;
pe_working_set_t *data_set = NULL;
if(resource_is_running_on(rsc, host) == FALSE) {
const char *id = rsc->clone_name?rsc->clone_name:rsc->id;
if(host) {
printf("%s is not running on %s and so cannot be restarted\n", id, host);
} else {
printf("%s is not running anywhere and so cannot be restarted\n", id);
}
return -ENXIO;
}
/* We might set the target-role meta-attribute */
attr_set_type = XML_TAG_META_SETS;
rsc_id = strdup(rsc->id);
if ((pe_rsc_is_clone(rsc) || pe_bundle_replicas(rsc)) && host) {
stop_via_ban = TRUE;
}
/*
grab full cib
determine originally active resources
disable or ban
poll cib and watch for affected resources to get stopped
without --timeout, calculate the stop timeout for each step and wait for that
if we hit --timeout or the service timeout, re-enable or un-ban, report failure and indicate which resources we couldn't take down
if everything stopped, re-enable or un-ban
poll cib and watch for affected resources to get started
without --timeout, calculate the start timeout for each step and wait for that
if we hit --timeout or the service timeout, report (different) failure and indicate which resources we couldn't bring back up
report success
Optimizations:
- use constraints to determine ordered list of affected resources
- Allow a --no-deps option (aka. --force-restart)
*/
data_set = pe_new_working_set();
if (data_set == NULL) {
crm_perror(LOG_ERR, "Could not allocate working set");
rc = -ENOMEM;
goto done;
}
set_bit(data_set->flags, pe_flag_no_counts);
rc = update_dataset(cib, data_set, FALSE);
if(rc != pcmk_ok) {
fprintf(stdout, "Could not get new resource list: %s (%d)\n", pcmk_strerror(rc), rc);
goto done;
}
restart_target_active = get_active_resources(host, data_set->resources);
current_active = get_active_resources(host, data_set->resources);
dump_list(current_active, "Origin");
if (stop_via_ban) {
/* Stop the clone or bundle instance by banning it from the host */
BE_QUIET = TRUE;
rc = cli_resource_ban(rsc_id, host, NULL, cib);
} else {
/* Stop the resource by setting target-role to Stopped.
* Remember any existing target-role so we can restore it later
* (though it only makes any difference if it's Slave).
*/
char *lookup_id = clone_strip(rsc->id);
find_resource_attr(cib, XML_NVPAIR_ATTR_VALUE, lookup_id, NULL, NULL,
NULL, XML_RSC_ATTR_TARGET_ROLE, &orig_target_role);
free(lookup_id);
rc = cli_resource_update_attribute(rsc, rsc_id, NULL, NULL,
XML_RSC_ATTR_TARGET_ROLE,
RSC_STOPPED, FALSE, cib, data_set);
}
if(rc != pcmk_ok) {
fprintf(stderr, "Could not set target-role for %s: %s (%d)\n", rsc_id, pcmk_strerror(rc), rc);
if (current_active) {
g_list_free_full(current_active, free);
}
if (restart_target_active) {
g_list_free_full(restart_target_active, free);
}
goto done;
}
rc = update_dataset(cib, data_set, TRUE);
if(rc != pcmk_ok) {
fprintf(stderr, "Could not determine which resources would be stopped\n");
goto failure;
}
target_active = get_active_resources(host, data_set->resources);
dump_list(target_active, "Target");
list_delta = subtract_lists(current_active, target_active, (GCompareFunc) strcmp);
fprintf(stdout, "Waiting for %d resources to stop:\n", g_list_length(list_delta));
display_list(list_delta, " * ");
step_timeout_s = timeout / sleep_interval;
while(g_list_length(list_delta) > 0) {
before = g_list_length(list_delta);
if(timeout_ms == 0) {
step_timeout_s = max_delay_in(data_set, list_delta) / sleep_interval;
}
/* We probably don't need the entire step timeout */
for(lpc = 0; lpc < step_timeout_s && g_list_length(list_delta) > 0; lpc++) {
sleep(sleep_interval);
if(timeout) {
timeout -= sleep_interval;
crm_trace("%ds remaining", timeout);
}
rc = update_dataset(cib, data_set, FALSE);
if(rc != pcmk_ok) {
fprintf(stderr, "Could not determine which resources were stopped\n");
goto failure;
}
if (current_active) {
g_list_free_full(current_active, free);
}
current_active = get_active_resources(host, data_set->resources);
g_list_free(list_delta);
list_delta = subtract_lists(current_active, target_active, (GCompareFunc) strcmp);
dump_list(current_active, "Current");
dump_list(list_delta, "Delta");
}
crm_trace("%d (was %d) resources remaining", g_list_length(list_delta), before);
if(before == g_list_length(list_delta)) {
/* aborted during stop phase, print the contents of list_delta */
fprintf(stderr, "Could not complete shutdown of %s, %d resources remaining\n", rsc_id, g_list_length(list_delta));
display_list(list_delta, " * ");
rc = -ETIME;
goto failure;
}
}
if (stop_via_ban) {
rc = cli_resource_clear(rsc_id, host, NULL, cib, TRUE);
} else if (orig_target_role) {
rc = cli_resource_update_attribute(rsc, rsc_id, NULL, NULL,
XML_RSC_ATTR_TARGET_ROLE,
orig_target_role, FALSE, cib,
data_set);
free(orig_target_role);
orig_target_role = NULL;
} else {
rc = cli_resource_delete_attribute(rsc, rsc_id, NULL, NULL,
XML_RSC_ATTR_TARGET_ROLE, cib,
data_set);
}
if(rc != pcmk_ok) {
fprintf(stderr, "Could not unset target-role for %s: %s (%d)\n", rsc_id, pcmk_strerror(rc), rc);
goto done;
}
if (target_active) {
g_list_free_full(target_active, free);
}
target_active = restart_target_active;
if (list_delta) {
g_list_free(list_delta);
}
list_delta = subtract_lists(target_active, current_active, (GCompareFunc) strcmp);
fprintf(stdout, "Waiting for %d resources to start again:\n", g_list_length(list_delta));
display_list(list_delta, " * ");
step_timeout_s = timeout / sleep_interval;
while (waiting_for_starts(list_delta, rsc, host)) {
before = g_list_length(list_delta);
if(timeout_ms == 0) {
step_timeout_s = max_delay_in(data_set, list_delta) / sleep_interval;
}
/* We probably don't need the entire step timeout */
for (lpc = 0; (lpc < step_timeout_s) && waiting_for_starts(list_delta, rsc, host); lpc++) {
sleep(sleep_interval);
if(timeout) {
timeout -= sleep_interval;
crm_trace("%ds remaining", timeout);
}
rc = update_dataset(cib, data_set, FALSE);
if(rc != pcmk_ok) {
fprintf(stderr, "Could not determine which resources were started\n");
goto failure;
}
if (current_active) {
g_list_free_full(current_active, free);
}
/* It's OK if dependent resources moved to a different node,
* so we check active resources on all nodes.
*/
current_active = get_active_resources(NULL, data_set->resources);
g_list_free(list_delta);
list_delta = subtract_lists(target_active, current_active, (GCompareFunc) strcmp);
dump_list(current_active, "Current");
dump_list(list_delta, "Delta");
}
if(before == g_list_length(list_delta)) {
/* aborted during start phase, print the contents of list_delta */
fprintf(stdout, "Could not complete restart of %s, %d resources remaining\n", rsc_id, g_list_length(list_delta));
display_list(list_delta, " * ");
rc = -ETIME;
goto failure;
}
}
rc = pcmk_ok;
goto done;
failure:
if (stop_via_ban) {
cli_resource_clear(rsc_id, host, NULL, cib, TRUE);
} else if (orig_target_role) {
cli_resource_update_attribute(rsc, rsc_id, NULL, NULL,
XML_RSC_ATTR_TARGET_ROLE,
orig_target_role, FALSE, cib, data_set);
free(orig_target_role);
} else {
cli_resource_delete_attribute(rsc, rsc_id, NULL, NULL,
XML_RSC_ATTR_TARGET_ROLE, cib, data_set);
}
done:
if (list_delta) {
g_list_free(list_delta);
}
if (current_active) {
g_list_free_full(current_active, free);
}
if (target_active && (target_active != restart_target_active)) {
g_list_free_full(target_active, free);
}
if (restart_target_active) {
g_list_free_full(restart_target_active, free);
}
free(rsc_id);
pe_free_working_set(data_set);
return rc;
}
static inline int action_is_pending(action_t *action)
{
if(is_set(action->flags, pe_action_optional)) {
return FALSE;
} else if(is_set(action->flags, pe_action_runnable) == FALSE) {
return FALSE;
} else if(is_set(action->flags, pe_action_pseudo)) {
return FALSE;
} else if(safe_str_eq("notify", action->task)) {
return FALSE;
}
return TRUE;
}
/*!
* \internal
* \brief Return TRUE if any actions in a list are pending
*
* \param[in] actions List of actions to check
*
* \return TRUE if any actions in the list are pending, FALSE otherwise
*/
static bool
actions_are_pending(GListPtr actions)
{
GListPtr action;
for (action = actions; action != NULL; action = action->next) {
action_t *a = (action_t *)action->data;
if (action_is_pending(a)) {
crm_notice("Waiting for %s (flags=0x%.8x)", a->uuid, a->flags);
return TRUE;
}
}
return FALSE;
}
/*!
* \internal
* \brief Print pending actions to stderr
*
* \param[in] actions List of actions to check
*
* \return void
*/
static void
print_pending_actions(GListPtr actions)
{
GListPtr action;
fprintf(stderr, "Pending actions:\n");
for (action = actions; action != NULL; action = action->next) {
action_t *a = (action_t *) action->data;
if (action_is_pending(a)) {
fprintf(stderr, "\tAction %d: %s", a->id, a->uuid);
if (a->node) {
fprintf(stderr, "\ton %s", a->node->details->uname);
}
fprintf(stderr, "\n");
}
}
}
/* For --wait, timeout (in seconds) to use if caller doesn't specify one */
#define WAIT_DEFAULT_TIMEOUT_S (60 * 60)
/* For --wait, how long to sleep between cluster state checks */
#define WAIT_SLEEP_S (2)
/*!
* \internal
* \brief Wait until all pending cluster actions are complete
*
* This waits until either the CIB's transition graph is idle or a timeout is
* reached.
*
* \param[in] timeout_ms Consider failed if actions do not complete in this time
* (specified in milliseconds, but one-second granularity
* is actually used; if 0, a default will be used)
* \param[in] cib Connection to the CIB manager
*
* \return pcmk_ok on success, -errno on failure
*/
int
wait_till_stable(int timeout_ms, cib_t * cib)
{
pe_working_set_t *data_set = NULL;
int rc = -1;
int timeout_s = timeout_ms? ((timeout_ms + 999) / 1000) : WAIT_DEFAULT_TIMEOUT_S;
time_t expire_time = time(NULL) + timeout_s;
time_t time_diff;
bool printed_version_warning = BE_QUIET; // i.e. don't print if quiet
data_set = pe_new_working_set();
if (data_set == NULL) {
return -ENOMEM;
}
set_bit(data_set->flags, pe_flag_no_counts);
do {
/* Abort if timeout is reached */
time_diff = expire_time - time(NULL);
if (time_diff > 0) {
crm_info("Waiting up to %ld seconds for cluster actions to complete", time_diff);
} else {
print_pending_actions(data_set->actions);
pe_free_working_set(data_set);
return -ETIME;
}
if (rc == pcmk_ok) { /* this avoids sleep on first loop iteration */
sleep(WAIT_SLEEP_S);
}
/* Get latest transition graph */
pe_reset_working_set(data_set);
rc = update_working_set_from_cib(data_set, cib);
if (rc != pcmk_ok) {
pe_free_working_set(data_set);
return rc;
}
pcmk__schedule_actions(data_set, data_set->input, NULL);
if (!printed_version_warning) {
/* If the DC has a different version than the local node, the two
* could come to different conclusions about what actions need to be
* done. Warn the user in this case.
*
* @TODO A possible long-term solution would be to reimplement the
* wait as a new controller operation that would be forwarded to the
* DC. However, that would have potential problems of its own.
*/
const char *dc_version = g_hash_table_lookup(data_set->config_hash,
"dc-version");
if (safe_str_neq(dc_version, PACEMAKER_VERSION "-" BUILD_VERSION)) {
printf("warning: wait option may not work properly in "
"mixed-version cluster\n");
printed_version_warning = TRUE;
}
}
} while (actions_are_pending(data_set->actions));
pe_free_working_set(data_set);
return pcmk_ok;
}
int
cli_resource_execute_from_params(const char *rsc_name, const char *rsc_class,
const char *rsc_prov, const char *rsc_type,
const char *action, GHashTable *params,
GHashTable *override_hash, int timeout_ms)
{
GHashTable *params_copy = NULL;
int rc = pcmk_ok;
svc_action_t *op = NULL;
if (safe_str_eq(rsc_class, PCMK_RESOURCE_CLASS_STONITH)) {
CMD_ERR("Sorry, the %s option doesn't support %s resources yet",
action, rsc_class);
crm_exit(CRM_EX_UNIMPLEMENT_FEATURE);
}
/* If no timeout was provided, grab the default. */
if (timeout_ms == 0) {
timeout_ms = crm_get_msec(CRM_DEFAULT_OP_TIMEOUT_S);
}
/* add meta_timeout env needed by some resource agents */
g_hash_table_insert(params, strdup("CRM_meta_timeout"),
crm_strdup_printf("%d", timeout_ms));
/* add crm_feature_set env needed by some resource agents */
g_hash_table_insert(params, strdup(XML_ATTR_CRM_VERSION), strdup(CRM_FEATURE_SET));
/* resources_action_create frees the params hash table it's passed, but we
* may need to reuse it in a second call to resources_action_create. Thus
* we'll make a copy here so that gets freed and the original remains for
* reuse.
*/
params_copy = crm_str_table_dup(params);
op = resources_action_create(rsc_name, rsc_class, rsc_prov, rsc_type, action, 0,
timeout_ms, params_copy, 0);
if (op == NULL) {
/* Re-run with stderr enabled so we can display a sane error message */
crm_enable_stderr(TRUE);
params_copy = crm_str_table_dup(params);
op = resources_action_create(rsc_name, rsc_class, rsc_prov, rsc_type, action, 0,
timeout_ms, params_copy, 0);
/* Callers of cli_resource_execute expect that the params hash table will
* be freed. That function uses this one, so for that reason and for
* making the two act the same, we should free the hash table here too.
*/
g_hash_table_destroy(params);
/* We know op will be NULL, but this makes static analysis happy */
services_action_free(op);
crm_exit(CRM_EX_DATAERR);
}
setenv("HA_debug", resource_verbose > 0 ? "1" : "0", 1);
if(resource_verbose > 1) {
setenv("OCF_TRACE_RA", "1", 1);
}
/* A resource agent using the standard ocf-shellfuncs library will not print
* messages to stderr if it doesn't have a controlling terminal (e.g. if
* crm_resource is called via script or ssh). This forces it to do so.
*/
setenv("OCF_TRACE_FILE", "/dev/stderr", 0);
if (override_hash) {
GHashTableIter iter;
char *name = NULL;
char *value = NULL;
g_hash_table_iter_init(&iter, override_hash);
while (g_hash_table_iter_next(&iter, (gpointer *) & name, (gpointer *) & value)) {
printf("Overriding the cluster configuration for '%s' with '%s' = '%s'\n",
rsc_name, name, value);
g_hash_table_replace(op->params, strdup(name), strdup(value));
}
}
if (services_action_sync(op)) {
int more, lpc, last;
char *local_copy = NULL;
rc = op->rc;
if (op->status == PCMK_LRM_OP_DONE) {
printf("Operation %s for %s (%s:%s:%s) returned: '%s' (%d)\n",
action, rsc_name, rsc_class, rsc_prov ? rsc_prov : "", rsc_type,
services_ocf_exitcode_str(op->rc), op->rc);
} else {
printf("Operation %s for %s (%s:%s:%s) failed: '%s' (%d)\n",
action, rsc_name, rsc_class, rsc_prov ? rsc_prov : "", rsc_type,
services_lrm_status_str(op->status), op->status);
}
/* hide output for validate-all if not in verbose */
if (resource_verbose == 0 && safe_str_eq(action, "validate-all"))
goto done;
if (op->stdout_data) {
local_copy = strdup(op->stdout_data);
more = strlen(local_copy);
last = 0;
for (lpc = 0; lpc < more; lpc++) {
if (local_copy[lpc] == '\n' || local_copy[lpc] == 0) {
local_copy[lpc] = 0;
printf(" > stdout: %s\n", local_copy + last);
last = lpc + 1;
}
}
free(local_copy);
}
if (op->stderr_data) {
local_copy = strdup(op->stderr_data);
more = strlen(local_copy);
last = 0;
for (lpc = 0; lpc < more; lpc++) {
if (local_copy[lpc] == '\n' || local_copy[lpc] == 0) {
local_copy[lpc] = 0;
printf(" > stderr: %s\n", local_copy + last);
last = lpc + 1;
}
}
free(local_copy);
}
} else {
rc = op->rc == 0 ? pcmk_err_generic : op->rc;
}
done:
services_action_free(op);
/* See comment above about why we free params here. */
g_hash_table_destroy(params);
return rc;
}
int
cli_resource_execute(resource_t *rsc, const char *requested_name,
const char *rsc_action, GHashTable *override_hash,
int timeout_ms, cib_t * cib, pe_working_set_t *data_set)
{
int rc = pcmk_ok;
const char *rid = NULL;
const char *rtype = NULL;
const char *rprov = NULL;
const char *rclass = NULL;
const char *action = NULL;
GHashTable *params = NULL;
if (safe_str_eq(rsc_action, "validate")) {
action = "validate-all";
} else if (safe_str_eq(rsc_action, "force-check")) {
action = "monitor";
} else if (safe_str_eq(rsc_action, "force-stop")) {
action = rsc_action+6;
} else if (safe_str_eq(rsc_action, "force-start")
|| safe_str_eq(rsc_action, "force-demote")
|| safe_str_eq(rsc_action, "force-promote")) {
action = rsc_action+6;
if(pe_rsc_is_clone(rsc)) {
rc = cli_resource_search(rsc, requested_name, data_set);
if(rc > 0 && do_force == FALSE) {
CMD_ERR("It is not safe to %s %s here: the cluster claims it is already active",
action, rsc->id);
CMD_ERR("Try setting target-role=Stopped first or specifying "
"the force option");
crm_exit(CRM_EX_UNSAFE);
}
}
} else {
action = rsc_action;
}
if(pe_rsc_is_clone(rsc)) {
/* Grab the first child resource in the hope it's not a group */
rsc = rsc->children->data;
}
if(rsc->variant == pe_group) {
CMD_ERR("Sorry, the %s option doesn't support group resources",
rsc_action);
crm_exit(CRM_EX_UNIMPLEMENT_FEATURE);
}
rclass = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS);
rprov = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER);
rtype = crm_element_value(rsc->xml, XML_ATTR_TYPE);
params = generate_resource_params(rsc, data_set);
if (timeout_ms == 0) {
timeout_ms = pe_get_configured_timeout(rsc, action, data_set);
}
rid = pe_rsc_is_anon_clone(rsc->parent)? requested_name : rsc->id;
rc = cli_resource_execute_from_params(rid, rclass, rprov, rtype, action,
params, override_hash, timeout_ms);
return rc;
}
int
cli_resource_move(resource_t *rsc, const char *rsc_id, const char *host_name,
cib_t *cib, pe_working_set_t *data_set)
{
int rc = pcmk_ok;
unsigned int count = 0;
node_t *current = NULL;
node_t *dest = pe_find_node(data_set->nodes, host_name);
bool cur_is_dest = FALSE;
if (dest == NULL) {
return -pcmk_err_node_unknown;
}
if (scope_master && is_not_set(rsc->flags, pe_rsc_promotable)) {
resource_t *p = uber_parent(rsc);
if (is_set(p->flags, pe_rsc_promotable)) {
CMD_ERR("Using parent '%s' for move instead of '%s'.", rsc->id, rsc_id);
rsc_id = p->id;
rsc = p;
} else {
CMD_ERR("Ignoring master option: %s is not promotable", rsc_id);
scope_master = FALSE;
}
}
current = pe__find_active_requires(rsc, &count);
if (is_set(rsc->flags, pe_rsc_promotable)) {
GListPtr iter = NULL;
unsigned int master_count = 0;
pe_node_t *master_node = NULL;
for(iter = rsc->children; iter; iter = iter->next) {
resource_t *child = (resource_t *)iter->data;
enum rsc_role_e child_role = child->fns->state(child, TRUE);
if(child_role == RSC_ROLE_MASTER) {
rsc = child;
master_node = pe__current_node(child);
master_count++;
}
}
if (scope_master || master_count) {
count = master_count;
current = master_node;
}
}
if (count > 1) {
if (pe_rsc_is_clone(rsc)) {
current = NULL;
} else {
return -pcmk_err_multiple;
}
}
if (current && (current->details == dest->details)) {
cur_is_dest = TRUE;
if (do_force) {
crm_info("%s is already %s on %s, reinforcing placement with location constraint.",
rsc_id, scope_master?"promoted":"active", dest->details->uname);
} else {
return -pcmk_err_already;
}
}
/* Clear any previous prefer constraints across all nodes. */
cli_resource_clear(rsc_id, NULL, data_set->nodes, cib, FALSE);
/* Clear any previous ban constraints on 'dest'. */
cli_resource_clear(rsc_id, dest->details->uname, data_set->nodes, cib, TRUE);
/* Record an explicit preference for 'dest' */
rc = cli_resource_prefer(rsc_id, dest->details->uname, cib);
crm_trace("%s%s now prefers node %s%s",
rsc->id, scope_master?" (master)":"", dest->details->uname, do_force?"(forced)":"");
/* only ban the previous location if current location != destination location.
* it is possible to use -M to enforce a location without regard of where the
* resource is currently located */
if(do_force && (cur_is_dest == FALSE)) {
/* Ban the original location if possible */
if(current) {
(void)cli_resource_ban(rsc_id, current->details->uname, NULL, cib);
} else if(count > 1) {
CMD_ERR("Resource '%s' is currently %s in %d locations. "
"One may now move to %s",
rsc_id, (scope_master? "promoted" : "active"),
count, dest->details->uname);
CMD_ERR("To prevent '%s' from being %s at a specific location, "
"specify a node.",
rsc_id, (scope_master? "promoted" : "active"));
} else {
crm_trace("Not banning %s from its current location: not active", rsc_id);
}
}
return rc;
}
static void
cli_resource_why_without_rsc_and_host(cib_t *cib_conn,GListPtr resources)
{
GListPtr lpc = NULL;
GListPtr hosts = NULL;
for (lpc = resources; lpc != NULL; lpc = lpc->next) {
resource_t *rsc = (resource_t *) lpc->data;
rsc->fns->location(rsc, &hosts, TRUE);
if (hosts == NULL) {
printf("Resource %s is not running\n", rsc->id);
} else {
printf("Resource %s is running\n", rsc->id);
}
cli_resource_check(cib_conn, rsc);
g_list_free(hosts);
hosts = NULL;
}
}
static void
cli_resource_why_with_rsc_and_host(cib_t *cib_conn, GListPtr resources,
resource_t *rsc, const char *host_uname)
{
if (resource_is_running_on(rsc, host_uname)) {
printf("Resource %s is running on host %s\n",rsc->id,host_uname);
} else {
printf("Resource %s is not running on host %s\n", rsc->id, host_uname);
}
cli_resource_check(cib_conn, rsc);
}
static void
cli_resource_why_without_rsc_with_host(cib_t *cib_conn,GListPtr resources,node_t *node)
{
const char* host_uname = node->details->uname;
GListPtr allResources = node->details->allocated_rsc;
GListPtr activeResources = node->details->running_rsc;
GListPtr unactiveResources = subtract_lists(allResources,activeResources,(GCompareFunc) strcmp);
GListPtr lpc = NULL;
for (lpc = activeResources; lpc != NULL; lpc = lpc->next) {
resource_t *rsc = (resource_t *) lpc->data;
printf("Resource %s is running on host %s\n",rsc->id,host_uname);
cli_resource_check(cib_conn,rsc);
}
for(lpc = unactiveResources; lpc != NULL; lpc = lpc->next) {
resource_t *rsc = (resource_t *) lpc->data;
printf("Resource %s is assigned to host %s but not running\n",
rsc->id, host_uname);
cli_resource_check(cib_conn,rsc);
}
g_list_free(allResources);
g_list_free(activeResources);
g_list_free(unactiveResources);
}
static void
cli_resource_why_with_rsc_without_host(cib_t *cib_conn, GListPtr resources,
resource_t *rsc)
{
GListPtr hosts = NULL;
rsc->fns->location(rsc, &hosts, TRUE);
printf("Resource %s is %srunning\n", rsc->id, (hosts? "" : "not "));
cli_resource_check(cib_conn, rsc);
g_list_free(hosts);
}
void cli_resource_why(cib_t *cib_conn, GListPtr resources, resource_t *rsc,
node_t *node)
{
const char *host_uname = (node == NULL)? NULL : node->details->uname;
if ((rsc == NULL) && (host_uname == NULL)) {
cli_resource_why_without_rsc_and_host(cib_conn, resources);
} else if ((rsc != NULL) && (host_uname != NULL)) {
cli_resource_why_with_rsc_and_host(cib_conn, resources, rsc,
host_uname);
} else if ((rsc == NULL) && (host_uname != NULL)) {
cli_resource_why_without_rsc_with_host(cib_conn, resources, node);
} else if ((rsc != NULL) && (host_uname == NULL)) {
cli_resource_why_with_rsc_without_host(cib_conn, resources, rsc);
}
}
diff --git a/tools/notifyServicelogEvent.c b/tools/notifyServicelogEvent.c
index ed4967cad9..44a4c01b4c 100644
--- a/tools/notifyServicelogEvent.c
+++ b/tools/notifyServicelogEvent.c
@@ -1,192 +1,193 @@
/*
* Original copyright 2009 International Business Machines, IBM, Mark Hamzy
- * Later changes copyright 2009-2018 the Pacemaker project contributors
+ * Later changes copyright 2009-2020 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.
*/
/* gcc -o notifyServicelogEvent `pkg-config --cflags servicelog-1` `pkg-config --libs servicelog-1` notifyServicelogEvent.c
*/
#include <crm_internal.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <servicelog.h>
#include <syslog.h>
#include <unistd.h>
#include <config.h>
#include <inttypes.h> /* U64T ~ PRIu64, U64TS ~ SCNu64 */
#include <crm/common/xml.h>
#include <crm/common/util.h>
-#include <crm/attrd.h>
+#include <crm/common/attrd_internal.h>
typedef enum { STATUS_GREEN = 1, STATUS_YELLOW, STATUS_RED } STATUS;
const char *status2char(STATUS status);
STATUS event2status(struct sl_event *event);
const char *
status2char(STATUS status)
{
switch (status) {
default:
case STATUS_GREEN:
return "green";
case STATUS_YELLOW:
return "yellow";
case STATUS_RED:
return "red";
}
}
STATUS
event2status(struct sl_event * event)
{
STATUS status = STATUS_GREEN;
crm_debug("Severity = %d, Disposition = %d", event->severity, event->disposition);
/* @TBD */
if (event->severity == SL_SEV_WARNING) {
status = STATUS_YELLOW;
}
if (event->disposition == SL_DISP_UNRECOVERABLE) {
status = STATUS_RED;
}
return status;
}
static struct crm_option long_options[] = {
/* Top-level Options */
{"help", 0, 0, '?', "\tThis text"},
{"version", 0, 0, '$', "\tVersion information"},
{"-spacer-", 0, 0, '-', "\nUsage: notifyServicelogEvent event_id"},
{"-spacer-", 0, 0, '-',
"\nWhere event_id is unique unsigned event identifier which is then passed into servicelog"},
{0, 0, 0, 0}
};
int
main(int argc, char *argv[])
{
int argerr = 0;
int flag;
int index = 0;
int rc = 0;
servicelog *slog = NULL;
struct sl_event *event = NULL;
uint64_t event_id = 0;
crm_log_cli_init("notifyServicelogEvent");
crm_set_options(NULL, "event_id ", long_options,
"Gets called upon events written to servicelog database");
if (argc < 2) {
argerr++;
}
while (1) {
flag = crm_get_option(argc, argv, &index);
if (flag == -1)
break;
switch (flag) {
case '?':
case '$':
crm_help(flag, CRM_EX_OK);
break;
default:
++argerr;
break;
}
}
if (argc - optind != 1) {
++argerr;
}
if (argerr) {
crm_help('?', CRM_EX_USAGE);
}
openlog("notifyServicelogEvent", LOG_NDELAY, LOG_USER);
if (sscanf(argv[optind], "%" U64TS, &event_id) != 1) {
crm_err("Error: could not read event_id from args!");
rc = 1;
goto cleanup;
}
if (event_id == 0) {
crm_err("Error: event_id is 0!");
rc = 1;
goto cleanup;
}
rc = servicelog_open(&slog, 0); /* flags is one of SL_FLAG_xxx */
if (!slog) {
crm_err("Error: servicelog_open failed, rc = %d", rc);
rc = 1;
goto cleanup;
}
if (slog) {
rc = servicelog_event_get(slog, event_id, &event);
}
if (rc == 0) {
STATUS status = STATUS_GREEN;
const char *health_component = "#health-ipmi";
const char *health_status = NULL;
crm_debug("Event id = %" U64T ", Log timestamp = %s, Event timestamp = %s",
event_id, ctime(&(event->time_logged)), ctime(&(event->time_event)));
status = event2status(event);
health_status = status2char(status);
if (health_status) {
- gboolean rc;
+ int attrd_rc;
- /* @TODO pass attrd_opt_remote when appropriate */
- rc = (attrd_update_delegate(NULL, 'v', NULL, health_component,
- health_status, NULL, NULL, NULL, NULL,
- attrd_opt_none) > 0);
+ // @TODO pass pcmk__node_attr_remote when appropriate
+ attrd_rc = pcmk__node_attr_request(NULL, 'v', NULL,
+ health_component, health_status,
+ NULL, NULL, NULL, NULL,
+ pcmk__node_attr_none);
crm_debug("Updating attribute ('%s', '%s') = %d",
- health_component, health_status, rc);
+ health_component, health_status, attrd_rc);
} else {
crm_err("Error: status2char failed, status = %d", status);
rc = 1;
}
} else {
crm_err("Error: servicelog_event_get failed, rc = %d", rc);
}
cleanup:
if (event) {
servicelog_event_free(event);
}
if (slog) {
servicelog_close(slog);
}
closelog();
return rc;
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Oct 16, 3:09 PM (1 d, 1 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2536485
Default Alt Text
(413 KB)

Event Timeline