Page MenuHomeClusterLabs Projects

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/attrd/attrd_common.h b/attrd/attrd_common.h
index 4e1a771172..3a4336ab02 100644
--- a/attrd/attrd_common.h
+++ b/attrd/attrd_common.h
@@ -1,68 +1,67 @@
/*
* Copyright (C) 2017 Andrew Beekhof <andrew@beekhof.net>
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#ifndef PCMK_ATTRD_COMMON__H
# define PCMK_ATTRD_COMMON__H
#include <regex.h>
#include <glib.h>
#include <crm/crm.h>
#include <crm/cib/internal.h>
void attrd_init_mainloop(void);
void attrd_run_mainloop(void);
gboolean attrd_mainloop_running(void);
void attrd_quit_mainloop(void);
gboolean attrd_shutting_down(void);
void attrd_shutdown(int nsig);
void attrd_init_ipc(qb_ipcs_service_t **ipcs,
qb_ipcs_msg_process_fn dispatch_fn);
void attrd_cib_disconnect(void);
gboolean attrd_value_needs_expansion(const char *value);
int attrd_expand_value(const char *value, const char *old_value);
/* regular expression to clear failures of all resources */
#define ATTRD_RE_CLEAR_ALL \
"^(" CRM_FAIL_COUNT_PREFIX "|" CRM_LAST_FAILURE_PREFIX ")-"
/* regular expression to clear failure of all operations for one resource
* (format takes resource name)
*
* @COMPAT attributes set < 1.1.17:
* also match older attributes that do not have the operation part
*/
#define ATTRD_RE_CLEAR_ONE ATTRD_RE_CLEAR_ALL "%s(#.+_[0-9]+)?$"
/* regular expression to clear failure of one operation for one resource
* (format takes resource name, operation name, and interval)
*
* @COMPAT attributes set < 1.1.17:
* also match older attributes that do not have the operation part
*/
#define ATTRD_RE_CLEAR_OP ATTRD_RE_CLEAR_ALL "%s(#%s_%d)?$"
int attrd_failure_regex(regex_t *regex, const char *rsc, const char *op,
int interval);
extern cib_t *the_cib;
/* Alerts */
extern lrmd_t *the_lrmd;
extern crm_trigger_t *attrd_config_read;
-lrmd_t *attrd_lrmd_connect(void);
void attrd_lrmd_disconnect(void);
gboolean attrd_read_options(gpointer user_data);
void attrd_cib_updated_cb(const char *event, xmlNode *msg);
void attrd_enable_alerts(const char *script, const char *target);
int attrd_send_attribute_alert(const char *node, int nodeid,
const char *attr, const char *value);
#endif /* PCMK_ATTRD_COMMON__H */
diff --git a/attrd/attrd_common_alerts.c b/attrd/attrd_common_alerts.c
index 10aadf9fbf..3ab4c3c3e1 100644
--- a/attrd/attrd_common_alerts.c
+++ b/attrd/attrd_common_alerts.c
@@ -1,152 +1,157 @@
/*
* Copyright (C) 2015 Andrew Beekhof <andrew@beekhof.net>
*
* 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
*/
#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 <internal.h>
#include <crm/common/alerts_internal.h>
-#include <crm/common/iso8601_internal.h>
#include <crm/pengine/rules_internal.h>
#include <crm/lrmd_alerts_internal.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 LRMD");
attrd_lrmd_disconnect();
break;
default:
break;
}
}
-lrmd_t *
+static lrmd_t *
attrd_lrmd_connect()
{
- int ret = -ENOTCONN;
- int fails = 0;
- const unsigned int max_attempts = 10;
-
- if (!the_lrmd) {
+ if (the_lrmd == NULL) {
the_lrmd = lrmd_api_new();
- } else if (the_lrmd->cmds->is_connected(the_lrmd)) {
- return the_lrmd;
+ the_lrmd->cmds->set_callback(the_lrmd, attrd_lrmd_callback);
}
- 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;
+ }
- while (fails < max_attempts) {
- ret = the_lrmd->cmds->connect(the_lrmd, T_ATTRD, NULL);
- if (ret != pcmk_ok) {
- fails++;
crm_debug("Could not connect to LRMD, %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.
*/
- } else {
- break;
}
- }
- if (ret != pcmk_ok) {
- attrd_lrmd_disconnect();
+ 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 != pcmk_ok) {
- crm_err("Local CIB query resulted in an error: %s", pcmk_strerror(rc));
+ 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_err("Local CIB query for " XML_CIB_TAG_ALERTS " section failed");
+ 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;
-
+
if (the_cib) {
- call_id = the_cib->cmds->query(the_cib,
- "//" XML_CIB_TAG_ALERTS,
- NULL, cib_xpath | cib_scope_local);
+ 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);
} else {
- crm_err("Querying the CIB...CIB connection not active");
+ crm_err("Could not check for alerts configuration: CIB connection not active");
}
return TRUE;
}
void
attrd_cib_updated_cb(const char *event, xmlNode * msg)
{
if (crm_patchset_contains_alert(msg, FALSE)) {
mainloop_set_trigger(attrd_config_read);
}
}
int
attrd_send_attribute_alert(const char *node, int nodeid,
const char *attr, const char *value)
{
- return lrmd_send_attribute_alert(attrd_alert_list, attrd_lrmd_connect,
+ 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/attrd/legacy.c b/attrd/legacy.c
index 6d91aba5a1..0954d3f9ad 100644
--- a/attrd/legacy.c
+++ b/attrd/legacy.c
@@ -1,1236 +1,1234 @@
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* 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
*/
#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 <regex.h>
#include <crm/crm.h>
#include <crm/cib/internal.h>
#include <crm/msg_xml.h>
#include <crm/pengine/rules.h>
#include <crm/common/ipc.h>
#include <crm/common/ipcs.h>
#include <crm/cluster/internal.h>
-#include <crm/lrmd_alerts_internal.h>
-#include <crm/common/alerts_internal.h>
#include <crm/common/xml.h>
#include <crm/attrd.h>
#include <attrd_common.h>
#define OPTARGS "hV"
#if SUPPORT_HEARTBEAT
ll_cluster_t *attrd_cluster_conn;
#endif
char *attrd_uname = NULL;
char *attrd_uuid = NULL;
uint32_t attrd_nodeid = 0;
GHashTable *attr_hash = NULL;
lrmd_t *the_lrmd = NULL;
crm_trigger_t *attrd_config_read = NULL;
/* Convenience macro for registering a CIB callback.
* Check the_cib != NULL before using.
*/
#define register_cib_callback(call_id, data, fn, free_fn) \
the_cib->cmds->register_callback_full(the_cib, call_id, 120, FALSE, \
data, #fn, fn, free_fn)
typedef struct attr_hash_entry_s {
char *uuid;
char *id;
char *set;
char *section;
char *value;
char *stored_value;
int timeout;
char *dampen;
guint timer_id;
char *user;
} attr_hash_entry_t;
void attrd_local_callback(xmlNode * msg);
gboolean attrd_timer_callback(void *user_data);
gboolean attrd_trigger_update(attr_hash_entry_t * hash_entry);
void attrd_perform_update(attr_hash_entry_t * hash_entry);
static void update_local_attr(xmlNode *msg, attr_hash_entry_t *hash_entry);
static void
free_hash_entry(gpointer data)
{
attr_hash_entry_t *entry = data;
if (entry == NULL) {
return;
}
free(entry->id);
free(entry->set);
free(entry->dampen);
free(entry->section);
free(entry->uuid);
free(entry->value);
free(entry->stored_value);
free(entry->user);
free(entry);
}
/* Exit code means? */
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 *msg = crm_ipcs_recv(client, data, size, &id, &flags);
crm_ipcs_send_ack(client, id, flags, "ack", __FUNCTION__, __LINE__);
if (msg == 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(msg, F_ATTRD_USER, client->user);
#endif
crm_trace("Processing msg from %d (%p)", crm_ipcs_client_pid(c), c);
crm_log_xml_trace(msg, __FUNCTION__);
attrd_local_callback(msg);
free_xml(msg);
return 0;
}
static void
usage(const char *cmd, int exit_status)
{
FILE *stream;
stream = exit_status ? stderr : stdout;
fprintf(stream, "usage: %s [-srkh] [-c configure file]\n", cmd);
/* fprintf(stream, "\t-d\tsets debug level\n"); */
/* fprintf(stream, "\t-s\tgets daemon status\n"); */
/* fprintf(stream, "\t-r\trestarts daemon\n"); */
/* fprintf(stream, "\t-k\tstops daemon\n"); */
/* fprintf(stream, "\t-h\thelp message\n"); */
fflush(stream);
crm_exit(exit_status);
}
static void
stop_attrd_timer(attr_hash_entry_t * hash_entry)
{
if (hash_entry != NULL && hash_entry->timer_id != 0) {
crm_trace("Stopping %s timer", hash_entry->id);
g_source_remove(hash_entry->timer_id);
hash_entry->timer_id = 0;
}
}
static void
log_hash_entry(int level, attr_hash_entry_t * entry, const char *text)
{
do_crm_log(level, "%s: Set: %s, Name: %s, Value: %s, Timeout: %s",
text, entry->section, entry->id, entry->value, entry->dampen);
}
static attr_hash_entry_t *
find_hash_entry(xmlNode * msg)
{
const char *value = NULL;
const char *attr = crm_element_value(msg, F_ATTRD_ATTRIBUTE);
attr_hash_entry_t *hash_entry = NULL;
if (attr == NULL) {
crm_info("Ignoring message with no attribute name");
return NULL;
}
hash_entry = g_hash_table_lookup(attr_hash, attr);
if (hash_entry == NULL) {
/* create one and add it */
crm_info("Creating hash entry for %s", attr);
hash_entry = calloc(1, sizeof(attr_hash_entry_t));
hash_entry->id = strdup(attr);
g_hash_table_insert(attr_hash, hash_entry->id, hash_entry);
hash_entry = g_hash_table_lookup(attr_hash, attr);
CRM_CHECK(hash_entry != NULL, return NULL);
}
value = crm_element_value(msg, F_ATTRD_SET);
if (value != NULL) {
free(hash_entry->set);
hash_entry->set = strdup(value);
crm_debug("\t%s->set: %s", attr, value);
}
value = crm_element_value(msg, F_ATTRD_SECTION);
if (value == NULL) {
value = XML_CIB_TAG_STATUS;
}
free(hash_entry->section);
hash_entry->section = strdup(value);
crm_trace("\t%s->section: %s", attr, value);
value = crm_element_value(msg, F_ATTRD_DAMPEN);
if (value != NULL) {
free(hash_entry->dampen);
hash_entry->dampen = strdup(value);
hash_entry->timeout = crm_get_msec(value);
crm_trace("\t%s->timeout: %s", attr, value);
}
#if ENABLE_ACL
free(hash_entry->user);
hash_entry->user = NULL;
value = crm_element_value(msg, F_ATTRD_USER);
if (value != NULL) {
hash_entry->user = strdup(value);
crm_trace("\t%s->user: %s", attr, value);
}
#endif
log_hash_entry(LOG_DEBUG_2, hash_entry, "Found (and updated) entry:");
return hash_entry;
}
/*!
* \internal
* \brief Clear failure-related attributes for local node
*
* \param[in] xml XML of ATTRD_OP_CLEAR_FAILURE request
*/
static void
local_clear_failure(xmlNode *xml)
{
const char *rsc = crm_element_value(xml, F_ATTRD_RESOURCE);
const char *what = rsc? rsc : "all resources";
const char *op = crm_element_value(xml, F_ATTRD_OPERATION);
const char *interval_s = crm_element_value(xml, F_ATTRD_INTERVAL);
int interval = crm_get_interval(interval_s);
regex_t regex;
GHashTableIter iter;
attr_hash_entry_t *hash_entry = NULL;
if (attrd_failure_regex(&regex, rsc, op, interval) != pcmk_ok) {
crm_info("Ignoring invalid request to clear %s",
(rsc? rsc : "all resources"));
return;
}
crm_debug("Clearing %s locally", what);
/* Make sure value is not set, so we delete */
if (crm_element_value(xml, F_ATTRD_VALUE)) {
crm_xml_replace(xml, F_ATTRD_VALUE, NULL);
}
g_hash_table_iter_init(&iter, attr_hash);
while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &hash_entry)) {
if (regexec(&regex, hash_entry->id, 0, NULL, 0) == 0) {
crm_trace("Matched %s when clearing %s", hash_entry->id, what);
update_local_attr(xml, hash_entry);
}
}
regfree(&regex);
}
static void
remote_clear_callback(xmlNode *msg, int call_id, int rc, xmlNode *output,
void *user_data)
{
if (rc == 0) {
crm_debug("Successfully cleared failures using %s", (char *) user_data);
} else {
crm_notice("Failed to clear failures: %s " CRM_XS " call=%d xpath=%s rc=%d",
pcmk_strerror(rc), call_id, (char *) user_data, rc);
}
}
/* xpath component to match an id attribute (format takes remote node name) */
#define XPATH_ID "[@" XML_ATTR_UUID "='%s']"
/* Define the start of an xpath to match a remote node transient attribute
* (argument must be either an empty string to match for all remote nodes,
* or XPATH_ID to match for a single remote node)
*/
#define XPATH_REMOTE_ATTR(x) "/" XML_TAG_CIB "/" XML_CIB_TAG_STATUS \
"/" XML_CIB_TAG_STATE "[@" XML_NODE_IS_REMOTE "='true']" x \
"/" XML_TAG_TRANSIENT_NODEATTRS "/" XML_TAG_ATTR_SETS "/" XML_CIB_TAG_NVPAIR
/* xpath component to match an attribute name exactly */
#define XPATH_NAME_IS(x) "@" XML_NVPAIR_ATTR_NAME "='" x "'"
/* xpath component to match an attribute name by prefix */
#define XPATH_NAME_START(x) "starts-with(@" XML_NVPAIR_ATTR_NAME ", '" x "')"
/* xpath ending to clear all resources */
#define XPATH_CLEAR_ALL \
"[" XPATH_NAME_START(CRM_FAIL_COUNT_PREFIX "-") \
" or " XPATH_NAME_START(CRM_LAST_FAILURE_PREFIX "-") "]"
/* xpath ending to clear all operations for one resource
* (format takes resource name x 4)
*
* @COMPAT attributes set < 1.1.17:
* also match older attributes that do not have the operation part
*/
#define XPATH_CLEAR_ONE \
"[" XPATH_NAME_IS(CRM_FAIL_COUNT_PREFIX "-%s") \
" or " XPATH_NAME_IS(CRM_LAST_FAILURE_PREFIX "-%s") \
" or " XPATH_NAME_START(CRM_FAIL_COUNT_PREFIX "-%s#") \
" or " XPATH_NAME_START(CRM_LAST_FAILURE_PREFIX "-%s#") "]"
/* xpath ending to clear one operation for one resource
* (format takes resource name x 2, resource name + operation + interval x 2)
*
* @COMPAT attributes set < 1.1.17:
* also match older attributes that do not have the operation part
*/
#define XPATH_CLEAR_OP \
"[" XPATH_NAME_IS(CRM_FAIL_COUNT_PREFIX "-%s") \
" or " XPATH_NAME_IS(CRM_LAST_FAILURE_PREFIX "-%s") \
" or " XPATH_NAME_IS(CRM_FAIL_COUNT_PREFIX "-%s#%s_%d") \
" or " XPATH_NAME_IS(CRM_LAST_FAILURE_PREFIX "-%s#%s_%d") "]"
/*!
* \internal
* \brief Clear failure-related attributes for Pacemaker Remote node(s)
*
* \param[in] xml XML of ATTRD_OP_CLEAR_FAILURE request
*/
static void
remote_clear_failure(xmlNode *xml)
{
const char *rsc = crm_element_value(xml, F_ATTRD_RESOURCE);
const char *host = crm_element_value(xml, F_ATTRD_HOST);
const char *op = crm_element_value(xml, F_ATTRD_OPERATION);
int rc = pcmk_ok;
char *xpath;
if (the_cib == NULL) {
crm_info("Ignoring request to clear %s on %s because not connected to CIB",
(rsc? rsc : "all resources"),
(host? host: "all remote nodes"));
return;
}
/* Build an xpath to clear appropriate attributes */
if (rsc == NULL) {
/* No resource specified, clear all resources */
if (host == NULL) {
xpath = crm_strdup_printf(XPATH_REMOTE_ATTR("") XPATH_CLEAR_ALL);
} else {
xpath = crm_strdup_printf(XPATH_REMOTE_ATTR(XPATH_ID) XPATH_CLEAR_ALL,
host);
}
} else if (op == NULL) {
/* Resource but no operation specified, clear all operations */
if (host == NULL) {
xpath = crm_strdup_printf(XPATH_REMOTE_ATTR("") XPATH_CLEAR_ONE,
rsc, rsc, rsc, rsc);
} else {
xpath = crm_strdup_printf(XPATH_REMOTE_ATTR(XPATH_ID) XPATH_CLEAR_ONE,
host, rsc, rsc, rsc, rsc);
}
} else {
/* Resource and operation specified */
const char *interval_s = crm_element_value(xml, F_ATTRD_INTERVAL);
int interval = crm_get_interval(interval_s);
if (host == NULL) {
xpath = crm_strdup_printf(XPATH_REMOTE_ATTR("") XPATH_CLEAR_OP,
rsc, rsc, rsc, op, interval,
rsc, op, interval);
} else {
xpath = crm_strdup_printf(XPATH_REMOTE_ATTR(XPATH_ID) XPATH_CLEAR_OP,
host, rsc, rsc, rsc, op, interval,
rsc, op, interval);
}
}
crm_trace("Clearing attributes matching %s", xpath);
rc = the_cib->cmds->delete(the_cib, xpath, NULL, cib_xpath|cib_multiple);
register_cib_callback(rc, xpath, remote_clear_callback, free);
}
static void
process_xml_request(xmlNode *xml)
{
attr_hash_entry_t *hash_entry = NULL;
const char *from = crm_element_value(xml, F_ORIG);
const char *op = crm_element_value(xml, F_ATTRD_TASK);
const char *host = crm_element_value(xml, F_ATTRD_HOST);
const char *ignore = crm_element_value(xml, F_ATTRD_IGNORE_LOCALLY);
if (host && safe_str_eq(host, attrd_uname)) {
crm_info("%s relayed from %s", (op? op : "Request"), from);
attrd_local_callback(xml);
} else if (safe_str_eq(op, ATTRD_OP_PEER_REMOVE)) {
CRM_CHECK(host != NULL, return);
crm_debug("Removing %s from peer caches for %s", host, from);
crm_remote_peer_cache_remove(host);
reap_crm_member(0, host);
} else if (safe_str_eq(op, ATTRD_OP_CLEAR_FAILURE)) {
local_clear_failure(xml);
} else if ((ignore == NULL) || safe_str_neq(from, attrd_uname)) {
crm_trace("%s message from %s", op, from);
hash_entry = find_hash_entry(xml);
stop_attrd_timer(hash_entry);
attrd_perform_update(hash_entry);
}
}
#if SUPPORT_HEARTBEAT
static void
attrd_ha_connection_destroy(gpointer user_data)
{
crm_trace("Invoked");
if (attrd_shutting_down()) {
/* we signed out, so this is expected */
crm_info("Heartbeat disconnection complete");
return;
}
crm_crit("Lost connection to heartbeat service!");
if (attrd_mainloop_running()) {
attrd_quit_mainloop();
return;
}
crm_exit(pcmk_ok);
}
static void
attrd_ha_callback(HA_Message * msg, void *private_data)
{
xmlNode *xml = convert_ha_message(NULL, msg, __FUNCTION__);
process_xml_request(xml);
free_xml(xml);
}
#endif
#if SUPPORT_COROSYNC
static void
attrd_cs_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 received: '%.120s'", data);
}
}
if (xml != NULL) {
/* crm_xml_add_int(xml, F_SEQ, wrapper->id); */
crm_xml_add(xml, F_ORIG, from);
process_xml_request(xml);
free_xml(xml);
}
free(data);
}
static void
attrd_cs_destroy(gpointer unused)
{
if (attrd_shutting_down()) {
/* we signed out, so this is expected */
crm_info("Corosync disconnection complete");
return;
}
crm_crit("Lost connection to Corosync service!");
if (attrd_mainloop_running()) {
attrd_quit_mainloop();
return;
}
crm_exit(EINVAL);
}
#endif
static void
attrd_cib_connection_destroy(gpointer user_data)
{
cib_t *conn = user_data;
conn->cmds->signoff(conn); /* Ensure IPC is cleaned up */
if (attrd_shutting_down()) {
crm_info("Connection to the CIB terminated...");
} else {
/* eventually this will trigger a reconnect, not a shutdown */
crm_err("Connection to the CIB terminated...");
crm_exit(ENOTCONN);
}
return;
}
static void
update_for_hash_entry(gpointer key, gpointer value, gpointer user_data)
{
attr_hash_entry_t *entry = value;
if (entry->value != NULL || entry->stored_value != NULL) {
attrd_timer_callback(value);
}
}
static void
local_update_for_hash_entry(gpointer key, gpointer value, gpointer user_data)
{
attr_hash_entry_t *entry = value;
if (entry->timer_id == 0) {
crm_trace("Performing local-only update after replace for %s", entry->id);
attrd_perform_update(entry);
/* } else {
* just let the timer expire and attrd_timer_callback() will do the right thing
*/
}
}
static void
do_cib_replaced(const char *event, xmlNode * msg)
{
crm_info("Updating all attributes after %s event", event);
g_hash_table_foreach(attr_hash, local_update_for_hash_entry, NULL);
}
static gboolean
cib_connect(void *user_data)
{
static int attempts = 1;
static int max_retry = 20;
gboolean was_err = FALSE;
static cib_t *local_conn = NULL;
if (local_conn == NULL) {
local_conn = cib_new();
}
if (was_err == FALSE) {
int rc = -ENOTCONN;
if (attempts < max_retry) {
crm_debug("CIB signon attempt %d", attempts);
rc = local_conn->cmds->signon(local_conn, T_ATTRD, cib_command);
}
if (rc != pcmk_ok && attempts > max_retry) {
crm_err("Signon to CIB failed: %s", pcmk_strerror(rc));
was_err = TRUE;
} else if (rc != pcmk_ok) {
attempts++;
return TRUE;
}
}
crm_info("Connected to the CIB after %d signon attempts", attempts);
if (was_err == FALSE) {
int rc = local_conn->cmds->set_connection_dnotify(local_conn, attrd_cib_connection_destroy);
if (rc != pcmk_ok) {
crm_err("Could not set dnotify callback");
was_err = TRUE;
}
}
if (was_err == FALSE) {
if (pcmk_ok !=
local_conn->cmds->add_notify_callback(local_conn, T_CIB_REPLACE_NOTIFY,
do_cib_replaced)) {
crm_err("Could not set CIB notification callback");
was_err = TRUE;
}
if (was_err == FALSE) {
if (pcmk_ok != local_conn->cmds->add_notify_callback(local_conn, T_CIB_DIFF_NOTIFY, attrd_cib_updated_cb)) {
crm_err("Could not set CIB notification callback (update)");
was_err = TRUE;
}
}
attrd_config_read = mainloop_add_trigger(G_PRIORITY_HIGH, attrd_read_options, NULL);
/* Reading of cib(Alert section) after the start */
mainloop_set_trigger(attrd_config_read);
}
if (was_err) {
crm_err("Aborting startup");
crm_exit(DAEMON_RESPAWN_STOP);
}
the_cib = local_conn;
crm_info("Sending full refresh now that we're connected to the cib");
g_hash_table_foreach(attr_hash, local_update_for_hash_entry, NULL);
return FALSE;
}
int
main(int argc, char **argv)
{
int flag = 0;
int argerr = 0;
crm_cluster_t cluster;
gboolean was_err = FALSE;
qb_ipcs_connection_t *c = NULL;
qb_ipcs_service_t *ipcs = NULL;
crm_log_init(T_ATTRD, LOG_NOTICE, TRUE, FALSE, argc, argv, FALSE);
mainloop_add_signal(SIGTERM, attrd_shutdown);
while ((flag = getopt(argc, argv, OPTARGS)) != EOF) {
switch (flag) {
case 'V':
crm_bump_log_level(argc, argv);
break;
case 'h': /* Help message */
usage(T_ATTRD, EX_OK);
break;
default:
++argerr;
break;
}
}
if (optind > argc) {
++argerr;
}
if (argerr) {
usage(T_ATTRD, EX_USAGE);
}
attr_hash = g_hash_table_new_full(crm_str_hash, g_str_equal, NULL, free_hash_entry);
crm_info("Starting up");
if (was_err == FALSE) {
#if SUPPORT_COROSYNC
if (is_openais_cluster()) {
cluster.destroy = attrd_cs_destroy;
cluster.cpg.cpg_deliver_fn = attrd_cs_dispatch;
cluster.cpg.cpg_confchg_fn = pcmk_cpg_membership;
}
#endif
#if SUPPORT_HEARTBEAT
if (is_heartbeat_cluster()) {
cluster.hb_conn = NULL;
cluster.hb_dispatch = attrd_ha_callback;
cluster.destroy = attrd_ha_connection_destroy;
}
#endif
if (FALSE == crm_cluster_connect(&cluster)) {
crm_err("HA Signon failed");
was_err = TRUE;
}
attrd_uname = cluster.uname;
attrd_uuid = cluster.uuid;
attrd_nodeid = cluster.nodeid;
#if SUPPORT_HEARTBEAT
attrd_cluster_conn = cluster.hb_conn;
#endif
}
crm_info("Cluster connection active");
if (was_err == FALSE) {
attrd_init_ipc(&ipcs, attrd_ipc_dispatch);
}
crm_info("Accepting attribute updates");
attrd_init_mainloop();
if (0 == g_timeout_add_full(G_PRIORITY_LOW + 1, 5000, cib_connect, NULL, NULL)) {
crm_info("Adding timer failed");
was_err = TRUE;
}
if (was_err) {
crm_err("Aborting startup");
return 100;
}
crm_notice("Starting mainloop...");
attrd_run_mainloop();
crm_notice("Exiting...");
#if SUPPORT_HEARTBEAT
if (is_heartbeat_cluster()) {
attrd_cluster_conn->llc_ops->signoff(attrd_cluster_conn, TRUE);
attrd_cluster_conn->llc_ops->delete(attrd_cluster_conn);
}
#endif
c = qb_ipcs_connection_first_get(ipcs);
while (c != NULL) {
qb_ipcs_connection_t *last = c;
c = qb_ipcs_connection_next_get(ipcs, 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);
}
qb_ipcs_destroy(ipcs);
attrd_lrmd_disconnect();
attrd_cib_disconnect();
g_hash_table_destroy(attr_hash);
free(attrd_uuid);
return crm_exit(pcmk_ok);
}
struct attrd_callback_s {
char *attr;
char *value;
};
/*!
* \internal
* \brief Free an attrd callback structure
*/
static void
free_attrd_callback(void *user_data)
{
struct attrd_callback_s *data = user_data;
free(data->attr);
free(data->value);
free(data);
}
static void
attrd_cib_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
{
attr_hash_entry_t *hash_entry = NULL;
struct attrd_callback_s *data = user_data;
if (data->value == NULL && rc == -ENXIO) {
rc = pcmk_ok;
} else if (call_id < 0) {
crm_warn("Update %s=%s failed: %s", data->attr, data->value, pcmk_strerror(call_id));
return;
}
switch (rc) {
case pcmk_ok:
crm_debug("Update %d for %s=%s passed", call_id, data->attr, data->value);
hash_entry = g_hash_table_lookup(attr_hash, data->attr);
if (hash_entry) {
free(hash_entry->stored_value);
hash_entry->stored_value = NULL;
if (data->value != NULL) {
hash_entry->stored_value = strdup(data->value);
}
}
break;
case -pcmk_err_diff_failed: /* When an attr changes while the CIB is syncing */
case -ETIME: /* When an attr changes while there is a DC election */
case -ENXIO: /* When an attr changes while the CIB is syncing a
* newer config from a node that just came up
*/
crm_warn("Update %d for %s=%s failed: %s",
call_id, data->attr, data->value, pcmk_strerror(rc));
break;
default:
crm_err("Update %d for %s=%s failed: %s",
call_id, data->attr, data->value, pcmk_strerror(rc));
}
}
void
attrd_perform_update(attr_hash_entry_t * hash_entry)
{
int rc = pcmk_ok;
struct attrd_callback_s *data = NULL;
const char *user_name = NULL;
if (hash_entry == NULL) {
return;
} else if (the_cib == NULL) {
crm_info("Delaying operation %s=%s: cib not connected", hash_entry->id,
crm_str(hash_entry->value));
return;
}
#if ENABLE_ACL
if (hash_entry->user) {
user_name = hash_entry->user;
crm_trace("Performing request from user '%s'", hash_entry->user);
}
#endif
if (hash_entry->value == NULL) {
/* delete the attr */
rc = delete_attr_delegate(the_cib, cib_none, hash_entry->section, attrd_uuid, NULL,
hash_entry->set, hash_entry->uuid, hash_entry->id, NULL, FALSE,
user_name);
if (rc >= 0 && hash_entry->stored_value) {
crm_notice("Sent delete %d: node=%s, attr=%s, id=%s, set=%s, section=%s",
rc, attrd_uuid, hash_entry->id,
hash_entry->uuid ? hash_entry->uuid : "<n/a>", hash_entry->set,
hash_entry->section);
} else if (rc < 0 && rc != -ENXIO) {
crm_notice
("Delete operation failed: node=%s, attr=%s, id=%s, set=%s, section=%s: %s (%d)",
attrd_uuid, hash_entry->id, hash_entry->uuid ? hash_entry->uuid : "<n/a>",
hash_entry->set, hash_entry->section, pcmk_strerror(rc), rc);
} else {
crm_trace("Sent delete %d: node=%s, attr=%s, id=%s, set=%s, section=%s",
rc, attrd_uuid, hash_entry->id,
hash_entry->uuid ? hash_entry->uuid : "<n/a>", hash_entry->set,
hash_entry->section);
}
} else {
/* send update */
rc = update_attr_delegate(the_cib, cib_none, hash_entry->section,
attrd_uuid, NULL, hash_entry->set, hash_entry->uuid,
hash_entry->id, hash_entry->value, FALSE, user_name, NULL);
if (rc < 0) {
crm_notice("Could not update %s=%s: %s (%d)", hash_entry->id,
hash_entry->value, pcmk_strerror(rc), rc);
} else if (safe_str_neq(hash_entry->value, hash_entry->stored_value)) {
crm_notice("Sent update %d: %s=%s", rc, hash_entry->id, hash_entry->value);
} else {
crm_trace("Sent update %d: %s=%s", rc, hash_entry->id, hash_entry->value);
}
}
attrd_send_attribute_alert(attrd_uname, attrd_nodeid,
hash_entry->id, hash_entry->value);
data = calloc(1, sizeof(struct attrd_callback_s));
data->attr = strdup(hash_entry->id);
if (hash_entry->value != NULL) {
data->value = strdup(hash_entry->value);
}
register_cib_callback(rc, data, attrd_cib_callback, free_attrd_callback);
return;
}
/*!
* \internal
* \brief Expand attribute values that use "++" or "+="
*
* \param[in] value Attribute value to expand
* \param[in] old_value Previous value of attribute
*
* \return Newly allocated string with expanded value, or NULL if not expanded
*/
static char *
expand_attr_value(const char *value, const char *old_value)
{
char *expanded = NULL;
if (attrd_value_needs_expansion(value)) {
expanded = crm_itoa(attrd_expand_value(value, old_value));
}
return expanded;
}
/*!
* \internal
* \brief Update a single node attribute for this node
*
* \param[in] msg XML message with update
* \param[in,out] hash_entry Node attribute structure
*/
static void
update_local_attr(xmlNode *msg, attr_hash_entry_t *hash_entry)
{
const char *value = crm_element_value(msg, F_ATTRD_VALUE);
char *expanded = NULL;
if (hash_entry->uuid == NULL) {
const char *key = crm_element_value(msg, F_ATTRD_KEY);
if (key) {
hash_entry->uuid = strdup(key);
}
}
crm_debug("Request to update %s (%s) to %s from %s (stored: %s)",
hash_entry->id, (hash_entry->uuid? hash_entry->uuid : "no uuid"),
value, hash_entry->value, hash_entry->stored_value);
if (safe_str_eq(value, hash_entry->value)
&& safe_str_eq(value, hash_entry->stored_value)) {
crm_trace("Ignoring non-change");
return;
} else if (value) {
expanded = expand_attr_value(value, hash_entry->value);
if (expanded) {
crm_info("Expanded %s=%s to %s", hash_entry->id, value, expanded);
value = expanded;
}
}
if (safe_str_eq(value, hash_entry->value) && hash_entry->timer_id) {
/* We're already waiting to set this value */
free(expanded);
return;
}
free(hash_entry->value);
hash_entry->value = NULL;
if (value != NULL) {
hash_entry->value = (expanded? expanded : strdup(value));
crm_debug("New value of %s is %s", hash_entry->id, value);
}
stop_attrd_timer(hash_entry);
if (hash_entry->timeout > 0) {
hash_entry->timer_id = g_timeout_add(hash_entry->timeout, attrd_timer_callback, hash_entry);
} else {
attrd_trigger_update(hash_entry);
}
}
/*!
* \internal
* \brief Log the result of a CIB operation for a remote attribute
*
* \param[in] msg ignored
* \param[in] id CIB operation ID
* \param[in] rc CIB operation result
* \param[in] output ignored
* \param[in] data User-friendly string describing operation
*/
static void
remote_attr_callback(xmlNode *msg, int id, int rc, xmlNode *output, void *data)
{
if (rc == pcmk_ok) {
crm_debug("%s succeeded " CRM_XS " call=%d", (char *) data, id);
} else {
crm_notice("%s failed: %s " CRM_XS " call=%d rc=%d",
(char *) data, pcmk_strerror(rc), id, rc);
}
}
/*!
* \internal
* \brief Update a Pacemaker Remote node attribute via CIB only
*
* \param[in] host Pacemaker Remote node name
* \param[in] name Attribute name
* \param[in] value New attribute value
* \param[in] section CIB section to update (defaults to status if NULL)
* \param[in] user_name User to perform operation as
*
* \note Legacy attrd does not track remote node attributes, so such requests
* are only sent to the CIB. This means that dampening is ignored, and
* updates for the same attribute submitted to different nodes cannot be
* reliably ordered. This is not ideal, but allows remote nodes to
* be supported, and should be acceptable in practice.
*/
static void
update_remote_attr(const char *host, const char *name, const char *value,
const char *section, const char *user_name)
{
int rc = pcmk_ok;
char *desc;
if (value == NULL) {
desc = crm_strdup_printf("Delete of %s in %s for %s",
name, section, host);
} else {
desc = crm_strdup_printf("Update of %s=%s in %s for %s",
name, value, section, host);
}
if (name == NULL) {
rc = -EINVAL;
} else if (the_cib == NULL) {
rc = -ENOTCONN;
}
if (rc != pcmk_ok) {
remote_attr_callback(NULL, rc, rc, NULL, desc);
free(desc);
return;
}
if (value == NULL) {
rc = delete_attr_delegate(the_cib, cib_none, section,
host, NULL, NULL, NULL, name, NULL,
FALSE, user_name);
} else {
rc = update_attr_delegate(the_cib, cib_none, section,
host, NULL, NULL, NULL, name, value,
FALSE, user_name, "remote");
}
attrd_send_attribute_alert(host, 0, name, (value? value : ""));
crm_trace("%s submitted as CIB call %d", desc, rc);
register_cib_callback(rc, desc, remote_attr_callback, free);
}
/*!
* \internal
* \brief Handle a client request to clear failures
*
* \param[in] msg XML of request
*
* \note Handling is according to the host specified in the request:
* NULL: Relay to all cluster nodes (which do local_clear_failure())
* and also handle all remote nodes here, using remote_clear_failure();
* Our uname: Handle here, using local_clear_failure();
* Known peer: Relay to that peer, which (via process_xml_message() then
* attrd_local_callback()) comes back here as previous case;
* Unknown peer: Handle here as remote node, using remote_clear_failure()
*/
static void
attrd_client_clear_failure(xmlNode *msg)
{
const char *host = crm_element_value(msg, F_ATTRD_HOST);
if (host == NULL) {
/* Clear failure on all cluster nodes */
crm_notice("Broadcasting request to clear failure on all hosts");
send_cluster_message(NULL, crm_msg_attrd, msg, FALSE);
/* Clear failure on all remote nodes */
remote_clear_failure(msg);
} else if (safe_str_eq(host, attrd_uname)) {
local_clear_failure(msg);
} else {
int is_remote = FALSE;
crm_node_t *peer = crm_find_peer(0, host);
crm_element_value_int(msg, F_ATTRD_IS_REMOTE, &is_remote);
if (is_remote || (peer == NULL)) {
/* If request is not for a known cluster node, assume remote */
remote_clear_failure(msg);
} else {
/* Relay request to proper node */
crm_notice("Relaying request to clear failure to %s", host);
send_cluster_message(peer, crm_msg_attrd, msg, FALSE);
}
}
}
void
attrd_local_callback(xmlNode * msg)
{
attr_hash_entry_t *hash_entry = NULL;
const char *from = crm_element_value(msg, F_ORIG);
const char *op = crm_element_value(msg, F_ATTRD_TASK);
const char *attr = crm_element_value(msg, F_ATTRD_ATTRIBUTE);
const char *pattern = crm_element_value(msg, F_ATTRD_REGEX);
const char *value = crm_element_value(msg, F_ATTRD_VALUE);
const char *host = crm_element_value(msg, F_ATTRD_HOST);
int is_remote = FALSE;
crm_element_value_int(msg, F_ATTRD_IS_REMOTE, &is_remote);
if (safe_str_eq(op, ATTRD_OP_REFRESH)) {
crm_notice("Sending full refresh (origin=%s)", from);
g_hash_table_foreach(attr_hash, update_for_hash_entry, NULL);
return;
} else if (safe_str_eq(op, ATTRD_OP_PEER_REMOVE)) {
if (host) {
crm_notice("Broadcasting removal of peer %s", host);
send_cluster_message(NULL, crm_msg_attrd, msg, FALSE);
}
return;
} else if (safe_str_eq(op, ATTRD_OP_CLEAR_FAILURE)) {
attrd_client_clear_failure(msg);
return;
} else if (op && safe_str_neq(op, ATTRD_OP_UPDATE)) {
crm_notice("Ignoring unsupported %s request from %s", op, from);
return;
}
/* Handle requests for Pacemaker Remote nodes specially */
if (host && is_remote) {
const char *section = crm_element_value(msg, F_ATTRD_SECTION);
const char *user_name = crm_element_value(msg, F_ATTRD_USER);
if (section == NULL) {
section = XML_CIB_TAG_STATUS;
}
if ((attr == NULL) && (pattern != NULL)) {
/* Attribute(s) specified by regular expression */
/* @TODO query, iterate and update_remote_attr() for matches? */
crm_notice("Update of %s for %s failed: regular expressions "
"are not supported with Pacemaker Remote nodes",
pattern, host);
} else {
/* Single attribute specified by exact name */
update_remote_attr(host, attr, value, section, user_name);
}
return;
}
/* Redirect requests for another cluster node to that node */
if (host != NULL && safe_str_neq(host, attrd_uname)) {
send_cluster_message(crm_get_peer(0, host), crm_msg_attrd, msg, FALSE);
return;
}
if (attr != NULL) {
/* Single attribute specified by exact name */
crm_debug("%s message from %s: %s=%s", op, from, attr, crm_str(value));
hash_entry = find_hash_entry(msg);
if (hash_entry != NULL) {
update_local_attr(msg, hash_entry);
}
} else if (pattern != NULL) {
/* Attribute(s) specified by regular expression */
regex_t regex;
GHashTableIter iter;
if (regcomp(&regex, pattern, REG_EXTENDED|REG_NOSUB)) {
crm_err("Update from %s failed: invalid pattern %s",
from, pattern);
return;
}
crm_debug("%s message from %s: %s=%s",
op, from, pattern, crm_str(value));
g_hash_table_iter_init(&iter, attr_hash);
while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &hash_entry)) {
int rc = regexec(&regex, hash_entry->id, 0, NULL, 0);
if (rc == 0) {
crm_trace("Attribute %s matches %s", hash_entry->id, pattern);
update_local_attr(msg, hash_entry);
}
}
regfree(&regex);
} else {
crm_info("Ignoring message with no attribute name or expression");
}
}
gboolean
attrd_timer_callback(void *user_data)
{
stop_attrd_timer(user_data);
attrd_trigger_update(user_data);
return TRUE; /* Always return true, removed cleanly by stop_attrd_timer() */
}
gboolean
attrd_trigger_update(attr_hash_entry_t * hash_entry)
{
xmlNode *msg = NULL;
/* send HA message to everyone */
crm_notice("Sending flush op to all hosts for: %s (%s)",
hash_entry->id, crm_str(hash_entry->value));
log_hash_entry(LOG_DEBUG_2, hash_entry, "Sending flush op to all hosts for:");
msg = create_xml_node(NULL, __FUNCTION__);
crm_xml_add(msg, F_TYPE, T_ATTRD);
crm_xml_add(msg, F_ORIG, attrd_uname);
crm_xml_add(msg, F_ATTRD_TASK, "flush");
crm_xml_add(msg, F_ATTRD_ATTRIBUTE, hash_entry->id);
crm_xml_add(msg, F_ATTRD_SET, hash_entry->set);
crm_xml_add(msg, F_ATTRD_SECTION, hash_entry->section);
crm_xml_add(msg, F_ATTRD_DAMPEN, hash_entry->dampen);
crm_xml_add(msg, F_ATTRD_VALUE, hash_entry->value);
#if ENABLE_ACL
if (hash_entry->user) {
crm_xml_add(msg, F_ATTRD_USER, hash_entry->user);
}
#endif
if (hash_entry->timeout <= 0) {
crm_xml_add(msg, F_ATTRD_IGNORE_LOCALLY, hash_entry->value);
attrd_perform_update(hash_entry);
}
send_cluster_message(NULL, crm_msg_attrd, msg, FALSE);
free_xml(msg);
return TRUE;
}
diff --git a/attrd/main.c b/attrd/main.c
index 030738fa9b..a5ecef150b 100644
--- a/attrd/main.c
+++ b/attrd/main.c
@@ -1,352 +1,351 @@
/*
* Copyright (C) 2013 Andrew Beekhof <andrew@beekhof.net>
*
* 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
*/
#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/cluster/internal.h>
#include <crm/cluster/election.h>
-#include <crm/common/alerts_internal.h>
#include <crm/common/xml.h>
#include <crm/attrd.h>
#include <internal.h>
lrmd_t *the_lrmd = NULL;
crm_cluster_t *attrd_cluster = NULL;
election_t *writer = NULL;
int attrd_error = pcmk_ok;
crm_trigger_t *attrd_config_read = NULL;
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 Corosync service!");
attrd_error = ECONNRESET;
attrd_shutdown(0);
}
}
static void
attrd_cib_replaced_cb(const char *event, xmlNode * msg)
{
crm_notice("Updating all attributes after %s event", event);
if(election_state(writer) == election_won) {
write_attributes(TRUE, FALSE);
}
}
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_err("Lost connection to CIB service!");
attrd_error = ECONNRESET;
attrd_shutdown(0);
}
return;
}
static cib_t *
attrd_cib_connect(int max_retry)
{
int rc = -ENOTCONN;
static int attempts = 0;
cib_t *connection = cib_new();
if(connection == NULL) {
return NULL;
}
do {
if(attempts > 0) {
sleep(attempts);
}
attempts++;
crm_debug("CIB signon attempt %d", attempts);
rc = connection->cmds->signon(connection, T_ATTRD, cib_command);
} while(rc != pcmk_ok && attempts < max_retry);
if (rc != pcmk_ok) {
crm_err("Signon to CIB failed: %s (%d)", pcmk_strerror(rc), rc);
goto cleanup;
}
crm_info("Connected to the CIB after %d attempts", attempts);
rc = connection->cmds->set_connection_dnotify(connection, attrd_cib_destroy_cb);
if (rc != pcmk_ok) {
crm_err("Could not set disconnection callback");
goto cleanup;
}
rc = connection->cmds->add_notify_callback(connection, T_CIB_REPLACE_NOTIFY, attrd_cib_replaced_cb);
if(rc != pcmk_ok) {
crm_err("Could not set CIB notification callback");
goto cleanup;
}
rc = connection->cmds->add_notify_callback(connection, T_CIB_DIFF_NOTIFY, attrd_cib_updated_cb);
if (rc != pcmk_ok) {
crm_err("Could not set CIB notification callback (update)");
goto cleanup;
}
return connection;
cleanup:
connection->cmds->signoff(connection);
cib_delete(connection);
return 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;
}
/* *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 rc = pcmk_ok;
int flag = 0;
int index = 0;
int argerr = 0;
qb_ipcs_service_t *ipcs = 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, EX_OK);
break;
default:
++argerr;
break;
}
}
if (optind > argc) {
++argerr;
}
if (argerr) {
crm_help('?', EX_USAGE);
}
crm_log_init(T_ATTRD, LOG_INFO, TRUE, FALSE, argc, argv, FALSE);
crm_info("Starting up");
attributes = g_hash_table_new_full(crm_str_hash, g_str_equal, NULL, free_attribute);
attrd_cluster = malloc(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");
rc = DAEMON_RESPAWN_STOP;
goto done;
}
crm_info("Cluster connection active");
writer = election_init(T_ATTRD, attrd_cluster->uname, 120000, attrd_election_cb);
attrd_init_ipc(&ipcs, attrd_ipc_dispatch);
crm_info("Accepting attribute updates");
the_cib = attrd_cib_connect(10);
if (the_cib == NULL) {
rc = DAEMON_RESPAWN_STOP;
goto done;
}
crm_info("CIB connection active");
attrd_config_read = mainloop_add_trigger(G_PRIORITY_HIGH, attrd_read_options, NULL);
/* Reading of cib(Alert section) after the start */
mainloop_set_trigger(attrd_config_read);
attrd_run_mainloop();
done:
crm_info("Shutting down attribute manager");
election_fini(writer);
if (ipcs) {
crm_client_disconnect_all(ipcs);
qb_ipcs_destroy(ipcs);
g_hash_table_destroy(attributes);
}
attrd_lrmd_disconnect();
attrd_cib_disconnect();
if(attrd_error) {
return crm_exit(attrd_error);
}
return crm_exit(rc);
}
diff --git a/crmd/Makefile.am b/crmd/Makefile.am
index 5d60a25b67..a230a70081 100644
--- a/crmd/Makefile.am
+++ b/crmd/Makefile.am
@@ -1,58 +1,58 @@
#
# Copyright (C) 2004 Andrew Beekhof
#
# 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 program 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 program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
include $(top_srcdir)/Makefile.common
halibdir = $(CRM_DAEMON_DIR)
## binary progs
halib_PROGRAMS = crmd
## SOURCES
noinst_HEADERS = crmd_alerts.h crmd_callbacks.h crmd_fsa.h crmd.h \
crmd_lrm.h crmd_messages.h crmd_utils.h fsa_defines.h \
fsa_matrix.h fsa_proto.h membership.h te_callbacks.h \
- tengine.h throttle.h
+ tengine.h throttle.h crmd_metadata.h
crmd_CFLAGS = $(CFLAGS_HARDENED_EXE)
crmd_LDFLAGS = $(LDFLAGS_HARDENED_EXE)
crmd_LDADD = $(top_builddir)/lib/fencing/libstonithd.la \
$(top_builddir)/lib/transition/libtransitioner.la \
$(top_builddir)/lib/pengine/libpe_rules.la \
$(top_builddir)/lib/cib/libcib.la \
$(top_builddir)/lib/cluster/libcrmcluster.la \
$(top_builddir)/lib/common/libcrmcommon.la \
$(top_builddir)/lib/services/libcrmservice.la \
$(top_builddir)/lib/lrmd/liblrmd.la \
$(CLUSTERLIBS)
-crmd_SOURCES = main.c crmd.c corosync.c crmd_alerts.c \
+crmd_SOURCES = main.c crmd.c corosync.c crmd_metadata.c \
fsa.c control.c messages.c membership.c callbacks.c attrd.c \
election.c join_client.c join_dc.c subsystems.c throttle.c \
cib.c pengine.c tengine.c lrm.c lrm_state.c remote_lrmd_ra.c \
utils.c misc.c te_events.c te_actions.c te_utils.c te_callbacks.c
if BUILD_HEARTBEAT_SUPPORT
crmd_SOURCES += heartbeat.c
endif
if BUILD_XML_HELP
man7_MANS = crmd.7
endif
CLEANFILES = $(man7_MANS)
diff --git a/crmd/control.c b/crmd/control.c
index 63d8116b50..813a0a1f5b 100644
--- a/crmd/control.c
+++ b/crmd/control.c
@@ -1,1167 +1,1165 @@
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* 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
*/
#include <crm_internal.h>
#include <sys/param.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/pengine/rules.h>
#include <crm/cluster/internal.h>
#include <crm/cluster/election.h>
#include <crm/common/ipcs.h>
#include <crmd.h>
#include <crmd_fsa.h>
#include <fsa_proto.h>
#include <crmd_messages.h>
#include <crmd_callbacks.h>
#include <crmd_lrm.h>
#include <crmd_alerts.h>
+#include <crmd_metadata.h>
#include <tengine.h>
#include <throttle.h>
#include <sys/types.h>
#include <sys/stat.h>
qb_ipcs_service_t *ipcs = NULL;
extern gboolean crm_connect_corosync(crm_cluster_t * cluster);
extern void crmd_ha_connection_destroy(gpointer user_data);
void crm_shutdown(int nsig);
gboolean crm_read_options(gpointer user_data);
gboolean fsa_has_quorum = FALSE;
crm_trigger_t *fsa_source = NULL;
crm_trigger_t *config_read = NULL;
bool no_quorum_suicide_escalation = FALSE;
static gboolean
election_timeout_popped(gpointer data)
{
/* Not everyone voted */
crm_info("Election failed: Declaring ourselves the winner");
register_fsa_input(C_TIMER_POPPED, I_ELECTION_DC, NULL);
return FALSE;
}
/* A_HA_CONNECT */
void
do_ha_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)
{
gboolean registered = FALSE;
static crm_cluster_t *cluster = NULL;
if (cluster == NULL) {
cluster = calloc(1, sizeof(crm_cluster_t));
}
if (action & A_HA_DISCONNECT) {
crm_cluster_disconnect(cluster);
crm_info("Disconnected from the cluster");
set_bit(fsa_input_register, R_HA_DISCONNECTED);
}
if (action & A_HA_CONNECT) {
crm_set_status_callback(&peer_update_callback);
crm_set_autoreap(FALSE);
if (is_openais_cluster()) {
#if SUPPORT_COROSYNC
registered = crm_connect_corosync(cluster);
#endif
} else if (is_heartbeat_cluster()) {
#if SUPPORT_HEARTBEAT
cluster->destroy = crmd_ha_connection_destroy;
cluster->hb_dispatch = crmd_ha_msg_callback;
registered = crm_cluster_connect(cluster);
fsa_cluster_conn = cluster->hb_conn;
crm_trace("Be informed of Node Status changes");
if (registered &&
fsa_cluster_conn->llc_ops->set_nstatus_callback(fsa_cluster_conn,
crmd_ha_status_callback,
fsa_cluster_conn) != HA_OK) {
crm_err("Cannot set nstatus callback: %s",
fsa_cluster_conn->llc_ops->errmsg(fsa_cluster_conn));
registered = FALSE;
}
crm_trace("Be informed of CRM Client Status changes");
if (registered &&
fsa_cluster_conn->llc_ops->set_cstatus_callback(fsa_cluster_conn,
crmd_client_status_callback,
fsa_cluster_conn) != HA_OK) {
crm_err("Cannot set cstatus callback: %s",
fsa_cluster_conn->llc_ops->errmsg(fsa_cluster_conn));
registered = FALSE;
}
if (registered) {
crm_trace("Requesting an initial dump of CRMD client_status");
fsa_cluster_conn->llc_ops->client_status(fsa_cluster_conn, NULL, CRM_SYSTEM_CRMD,
-1);
}
#endif
}
fsa_election = election_init(NULL, cluster->uname, 60000/*60s*/, election_timeout_popped);
fsa_our_uname = cluster->uname;
fsa_our_uuid = cluster->uuid;
if(cluster->uuid == NULL) {
crm_err("Could not obtain local uuid");
registered = FALSE;
}
if (registered == FALSE) {
set_bit(fsa_input_register, R_HA_DISCONNECTED);
register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
return;
}
populate_cib_nodes(node_update_none, __FUNCTION__);
clear_bit(fsa_input_register, R_HA_DISCONNECTED);
crm_info("Connected to the cluster");
}
if (action & ~(A_HA_CONNECT | A_HA_DISCONNECT)) {
crm_err("Unexpected action %s in %s", fsa_action2string(action), __FUNCTION__);
}
}
static bool
need_spawn_pengine_from_crmd(void)
{
static int result = -1;
if (result != -1)
return result;
if (!is_heartbeat_cluster()) {
result = 0;
return result;
}
/* NULL, or "strange" value: rather spawn from here. */
result = TRUE;
crm_str_to_boolean(daemon_option("crmd_spawns_pengine"), &result);
return result;
}
/* A_SHUTDOWN */
void
do_shutdown(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)
{
/* just in case */
set_bit(fsa_input_register, R_SHUTDOWN);
if (need_spawn_pengine_from_crmd()) {
if (is_set(fsa_input_register, pe_subsystem->flag_connected)) {
crm_info("Terminating the %s", pe_subsystem->name);
if (stop_subsystem(pe_subsystem, TRUE) == FALSE) {
/* It's gone ... */
crm_err("Faking %s exit", pe_subsystem->name);
clear_bit(fsa_input_register, pe_subsystem->flag_connected);
} else {
crm_info("Waiting for subsystems to exit");
crmd_fsa_stall(FALSE);
}
}
crm_info("All subsystems stopped, continuing");
}
if (stonith_api) {
/* Prevent it from coming up again */
clear_bit(fsa_input_register, R_ST_REQUIRED);
crm_info("Disconnecting STONITH...");
stonith_api->cmds->disconnect(stonith_api);
}
}
/* A_SHUTDOWN_REQ */
void
do_shutdown_req(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)
{
xmlNode *msg = NULL;
set_bit(fsa_input_register, R_SHUTDOWN);
crm_info("Sending shutdown request to all peers (DC is %s)",
(fsa_our_dc? fsa_our_dc : "not set"));
msg = create_request(CRM_OP_SHUTDOWN_REQ, NULL, NULL, CRM_SYSTEM_CRMD, CRM_SYSTEM_CRMD, NULL);
/* set_bit(fsa_input_register, R_STAYDOWN); */
if (send_cluster_message(NULL, crm_msg_crmd, msg, TRUE) == FALSE) {
register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
}
free_xml(msg);
}
extern crm_ipc_t *attrd_ipc;
extern char *max_generation_from;
extern xmlNode *max_generation_xml;
extern GHashTable *resource_history;
extern GHashTable *voted;
extern char *te_client_id;
extern regex_t *version_format_regex;
void log_connected_client(gpointer key, gpointer value, gpointer user_data);
void
log_connected_client(gpointer key, gpointer value, gpointer user_data)
{
crm_client_t *client = value;
crm_err("%s is still connected at exit", crm_client_name(client));
}
int
crmd_fast_exit(int rc)
{
if (is_set(fsa_input_register, R_STAYDOWN)) {
crm_warn("Inhibiting respawn "CRM_XS" remapping exit code %d to %d",
rc, DAEMON_RESPAWN_STOP);
rc = DAEMON_RESPAWN_STOP;
}
if (rc == pcmk_ok && is_set(fsa_input_register, R_IN_RECOVERY)) {
crm_err("Could not recover from internal error");
rc = pcmk_err_generic;
}
return crm_exit(rc);
}
int
crmd_exit(int rc)
{
GListPtr gIter = NULL;
GMainLoop *mloop = crmd_mainloop;
static bool in_progress = FALSE;
if(in_progress && rc == 0) {
crm_debug("Exit is already in progress");
return rc;
} else if(in_progress) {
crm_notice("Error during shutdown process, terminating now with status %d: %s",
rc, pcmk_strerror(rc));
crm_write_blackbox(SIGTRAP, NULL);
crmd_fast_exit(rc);
}
in_progress = TRUE;
crm_trace("Preparing to exit: %d", rc);
/* Suppress secondary errors resulting from us disconnecting everything */
set_bit(fsa_input_register, R_HA_DISCONNECTED);
/* Close all IPC servers and clients to ensure any and all shared memory files are cleaned up */
if(ipcs) {
crm_trace("Closing IPC server");
mainloop_del_ipc_server(ipcs);
ipcs = NULL;
}
if (attrd_ipc) {
crm_trace("Closing attrd connection");
crm_ipc_close(attrd_ipc);
crm_ipc_destroy(attrd_ipc);
attrd_ipc = NULL;
}
if (pe_subsystem && pe_subsystem->client && pe_subsystem->client->ipcs) {
crm_trace("Disconnecting Policy Engine");
qb_ipcs_disconnect(pe_subsystem->client->ipcs);
}
if(stonith_api) {
crm_trace("Disconnecting fencing API");
clear_bit(fsa_input_register, R_ST_REQUIRED);
stonith_api->cmds->free(stonith_api); stonith_api = NULL;
}
if (rc == pcmk_ok && crmd_mainloop == NULL) {
crm_debug("No mainloop detected");
rc = EPROTO;
}
/* On an error, just get out.
*
* Otherwise, make the effort to have mainloop exit gracefully so
* that it (mostly) cleans up after itself and valgrind has less
* to report on - allowing real errors stand out
*/
if(rc != pcmk_ok) {
crm_notice("Forcing immediate exit with status %d: %s",
rc, pcmk_strerror(rc));
crm_write_blackbox(SIGTRAP, NULL);
return crmd_fast_exit(rc);
}
/* Clean up as much memory as possible for valgrind */
for (gIter = fsa_message_queue; gIter != NULL; gIter = gIter->next) {
fsa_data_t *fsa_data = gIter->data;
crm_info("Dropping %s: [ state=%s cause=%s origin=%s ]",
fsa_input2string(fsa_data->fsa_input),
fsa_state2string(fsa_state),
fsa_cause2string(fsa_data->fsa_cause), fsa_data->origin);
delete_fsa_input(fsa_data);
}
clear_bit(fsa_input_register, R_MEMBERSHIP);
g_list_free(fsa_message_queue); fsa_message_queue = NULL;
free(pe_subsystem); pe_subsystem = NULL;
free(te_subsystem); te_subsystem = NULL;
free(cib_subsystem); cib_subsystem = NULL;
- if (version_format_regex) {
- regfree(version_format_regex);
- free(version_format_regex);
- }
+ metadata_cache_fini();
election_fini(fsa_election);
fsa_election = NULL;
/* Tear down the CIB connection, but don't free it yet -- it could be used
* when we drain the mainloop later.
*/
cib_free_callbacks(fsa_cib_conn);
fsa_cib_conn->cmds->signoff(fsa_cib_conn);
verify_stopped(fsa_state, LOG_WARNING);
clear_bit(fsa_input_register, R_LRM_CONNECTED);
lrm_state_destroy_all();
/* This basically will not work, since mainloop has a reference to it */
mainloop_destroy_trigger(fsa_source); fsa_source = NULL;
mainloop_destroy_trigger(config_read); config_read = NULL;
mainloop_destroy_trigger(stonith_reconnect); stonith_reconnect = NULL;
mainloop_destroy_trigger(transition_trigger); transition_trigger = NULL;
crm_client_cleanup();
crm_peer_destroy();
crm_timer_stop(transition_timer);
crm_timer_stop(integration_timer);
crm_timer_stop(finalization_timer);
crm_timer_stop(election_trigger);
election_timeout_stop(fsa_election);
crm_timer_stop(shutdown_escalation_timer);
crm_timer_stop(wait_timer);
crm_timer_stop(recheck_timer);
free(transition_timer); transition_timer = NULL;
free(integration_timer); integration_timer = NULL;
free(finalization_timer); finalization_timer = NULL;
free(election_trigger); election_trigger = NULL;
free(shutdown_escalation_timer); shutdown_escalation_timer = NULL;
free(wait_timer); wait_timer = NULL;
free(recheck_timer); recheck_timer = NULL;
free(fsa_our_dc_version); fsa_our_dc_version = NULL;
free(fsa_our_uname); fsa_our_uname = NULL;
free(fsa_our_uuid); fsa_our_uuid = NULL;
free(fsa_our_dc); fsa_our_dc = NULL;
free(fsa_cluster_name); fsa_cluster_name = NULL;
free(te_uuid); te_uuid = NULL;
free(te_client_id); te_client_id = NULL;
free(fsa_pe_ref); fsa_pe_ref = NULL;
free(failed_stop_offset); failed_stop_offset = NULL;
free(failed_start_offset); failed_start_offset = NULL;
free(max_generation_from); max_generation_from = NULL;
free_xml(max_generation_xml); max_generation_xml = NULL;
mainloop_destroy_signal(SIGPIPE);
mainloop_destroy_signal(SIGUSR1);
mainloop_destroy_signal(SIGTERM);
mainloop_destroy_signal(SIGTRAP);
/* leave SIGCHLD engaged as we might still want to drain some service-actions */
if (mloop) {
GMainContext *ctx = g_main_loop_get_context(crmd_mainloop);
/* Don't re-enter this block */
crmd_mainloop = NULL;
/* no signals on final draining anymore */
mainloop_destroy_signal(SIGCHLD);
crm_trace("Draining mainloop %d %d", g_main_loop_is_running(mloop), g_main_context_pending(ctx));
{
int lpc = 0;
while((g_main_context_pending(ctx) && lpc < 10)) {
lpc++;
crm_trace("Iteration %d", lpc);
g_main_context_dispatch(ctx);
}
}
crm_trace("Closing mainloop %d %d", g_main_loop_is_running(mloop), g_main_context_pending(ctx));
g_main_loop_quit(mloop);
#if SUPPORT_HEARTBEAT
/* Do this only after g_main_loop_quit().
*
* This interface was broken (incomplete) since it was introduced.
* ->delete() does cleanup and free most of it, but it does not
* actually remove and destroy the corresponding GSource, so the next
* prepare/check iteratioin would find a corrupt (because partially
* freed) GSource, and segfault.
*
* Apparently one was supposed to store the GSource as returned by
* G_main_add_ll_cluster(), and g_source_destroy() that "by hand".
*
* But no-one ever did this, not even in the old hb code when this was
* introduced.
*
* Note that fsa_cluster_conn was set as an "alias" to cluster->hb_conn
* in do_ha_control() right after crm_cluster_connect(), and only
* happens to still point at that object, because do_ha_control() does
* not reset it to NULL after crm_cluster_disconnect() above does
* reset cluster->hb_conn to NULL.
* Not sure if that's something to cleanup, too.
*
* I'll try to fix this up in heartbeat proper, so ->delete
* will actually remove, and destroy, and unref, and free this thing.
* Doing so after g_main_loop_quit() is valid with both old,
* and eventually fixed heartbeat.
*
* If we introduce the "by hand" destroy/remove/unref,
* this may break again once heartbeat is fixed :-(
*
* -- Lars Ellenberg
*/
if (fsa_cluster_conn) {
crm_trace("Deleting heartbeat api object");
fsa_cluster_conn->llc_ops->delete(fsa_cluster_conn);
fsa_cluster_conn = NULL;
}
#endif
/* Won't do anything yet, since we're inside it now */
g_main_loop_unref(mloop);
crm_trace("Done %d", rc);
} else {
mainloop_destroy_signal(SIGCHLD);
}
cib_delete(fsa_cib_conn);
fsa_cib_conn = NULL;
throttle_fini();
/* Graceful */
return rc;
}
/* A_EXIT_0, A_EXIT_1 */
void
do_exit(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)
{
int exit_code = pcmk_ok;
int log_level = LOG_INFO;
const char *exit_type = "gracefully";
if (action & A_EXIT_1) {
/* exit_code = pcmk_err_generic; */
log_level = LOG_ERR;
exit_type = "forcefully";
exit_code = pcmk_err_generic;
}
verify_stopped(cur_state, LOG_ERR);
do_crm_log(log_level, "Performing %s - %s exiting the CRMd",
fsa_action2string(action), exit_type);
crm_info("[%s] stopped (%d)", crm_system_name, exit_code);
crmd_exit(exit_code);
}
static void sigpipe_ignore(int nsig) { return; }
/* A_STARTUP */
void
do_startup(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)
{
int was_error = 0;
crm_debug("Registering Signal Handlers");
mainloop_add_signal(SIGTERM, crm_shutdown);
mainloop_add_signal(SIGPIPE, sigpipe_ignore);
fsa_source = mainloop_add_trigger(G_PRIORITY_HIGH, crm_fsa_trigger, NULL);
config_read = mainloop_add_trigger(G_PRIORITY_HIGH, crm_read_options, NULL);
transition_trigger = mainloop_add_trigger(G_PRIORITY_LOW, te_graph_trigger, NULL);
crm_debug("Creating CIB and LRM objects");
fsa_cib_conn = cib_new();
lrm_state_init_local();
/* set up the timers */
transition_timer = calloc(1, sizeof(fsa_timer_t));
integration_timer = calloc(1, sizeof(fsa_timer_t));
finalization_timer = calloc(1, sizeof(fsa_timer_t));
election_trigger = calloc(1, sizeof(fsa_timer_t));
shutdown_escalation_timer = calloc(1, sizeof(fsa_timer_t));
wait_timer = calloc(1, sizeof(fsa_timer_t));
recheck_timer = calloc(1, sizeof(fsa_timer_t));
if (election_trigger != NULL) {
election_trigger->source_id = 0;
election_trigger->period_ms = -1;
election_trigger->fsa_input = I_DC_TIMEOUT;
election_trigger->callback = crm_timer_popped;
election_trigger->repeat = FALSE;
} else {
was_error = TRUE;
}
if (transition_timer != NULL) {
transition_timer->source_id = 0;
transition_timer->period_ms = -1;
transition_timer->fsa_input = I_PE_CALC;
transition_timer->callback = crm_timer_popped;
transition_timer->repeat = FALSE;
} else {
was_error = TRUE;
}
if (integration_timer != NULL) {
integration_timer->source_id = 0;
integration_timer->period_ms = -1;
integration_timer->fsa_input = I_INTEGRATED;
integration_timer->callback = crm_timer_popped;
integration_timer->repeat = FALSE;
} else {
was_error = TRUE;
}
if (finalization_timer != NULL) {
finalization_timer->source_id = 0;
finalization_timer->period_ms = -1;
finalization_timer->fsa_input = I_FINALIZED;
finalization_timer->callback = crm_timer_popped;
finalization_timer->repeat = FALSE;
/* for possible enabling... a bug in the join protocol left
* a slave in S_PENDING while we think it's in S_NOT_DC
*
* raising I_FINALIZED put us into a transition loop which is
* never resolved.
* in this loop we continually send probes which the node
* NACK's because it's in S_PENDING
*
* if we have nodes where heartbeat is active but the
* CRM is not... then this will be handled in the
* integration phase
*/
finalization_timer->fsa_input = I_ELECTION;
} else {
was_error = TRUE;
}
if (shutdown_escalation_timer != NULL) {
shutdown_escalation_timer->source_id = 0;
shutdown_escalation_timer->period_ms = -1;
shutdown_escalation_timer->fsa_input = I_STOP;
shutdown_escalation_timer->callback = crm_timer_popped;
shutdown_escalation_timer->repeat = FALSE;
} else {
was_error = TRUE;
}
if (wait_timer != NULL) {
wait_timer->source_id = 0;
wait_timer->period_ms = 2000;
wait_timer->fsa_input = I_NULL;
wait_timer->callback = crm_timer_popped;
wait_timer->repeat = FALSE;
} else {
was_error = TRUE;
}
if (recheck_timer != NULL) {
recheck_timer->source_id = 0;
recheck_timer->period_ms = -1;
recheck_timer->fsa_input = I_PE_CALC;
recheck_timer->callback = crm_timer_popped;
recheck_timer->repeat = FALSE;
} else {
was_error = TRUE;
}
/* set up the sub systems */
cib_subsystem = calloc(1, sizeof(struct crm_subsystem_s));
te_subsystem = calloc(1, sizeof(struct crm_subsystem_s));
pe_subsystem = calloc(1, sizeof(struct crm_subsystem_s));
if (cib_subsystem != NULL) {
cib_subsystem->pid = -1;
cib_subsystem->name = CRM_SYSTEM_CIB;
cib_subsystem->flag_connected = R_CIB_CONNECTED;
cib_subsystem->flag_required = R_CIB_REQUIRED;
} else {
was_error = TRUE;
}
if (te_subsystem != NULL) {
te_subsystem->pid = -1;
te_subsystem->name = CRM_SYSTEM_TENGINE;
te_subsystem->flag_connected = R_TE_CONNECTED;
te_subsystem->flag_required = R_TE_REQUIRED;
} else {
was_error = TRUE;
}
if (pe_subsystem != NULL) {
pe_subsystem->pid = -1;
pe_subsystem->path = CRM_DAEMON_DIR;
pe_subsystem->name = CRM_SYSTEM_PENGINE;
pe_subsystem->command = CRM_DAEMON_DIR "/" CRM_SYSTEM_PENGINE;
pe_subsystem->args = NULL;
pe_subsystem->flag_connected = R_PE_CONNECTED;
pe_subsystem->flag_required = R_PE_REQUIRED;
} else {
was_error = TRUE;
}
if (was_error == FALSE && need_spawn_pengine_from_crmd()) {
if (start_subsystem(pe_subsystem) == FALSE) {
was_error = TRUE;
}
}
if (was_error) {
register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
}
}
static int32_t
crmd_ipc_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid)
{
crm_trace("Connection %p", c);
if (crm_client_new(c, uid, gid) == NULL) {
return -EIO;
}
return 0;
}
static void
crmd_ipc_created(qb_ipcs_connection_t * c)
{
crm_trace("Connection %p", c);
}
static int32_t
crmd_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 *msg = crm_ipcs_recv(client, data, size, &id, &flags);
crm_trace("Invoked: %s", crm_client_name(client));
crm_ipcs_send_ack(client, id, flags, "ack", __FUNCTION__, __LINE__);
if (msg == NULL) {
return 0;
}
#if ENABLE_ACL
CRM_ASSERT(client->user != NULL);
crm_acl_get_set_user(msg, F_CRM_USER, client->user);
#endif
crm_trace("Processing msg from %s", crm_client_name(client));
crm_log_xml_trace(msg, "CRMd[inbound]");
crm_xml_add(msg, F_CRM_SYS_FROM, client->id);
if (crmd_authorize_message(msg, client, NULL)) {
route_message(C_IPC_MESSAGE, msg);
}
trigger_fsa(fsa_source);
free_xml(msg);
return 0;
}
static int32_t
crmd_ipc_closed(qb_ipcs_connection_t * c)
{
crm_client_t *client = crm_client_get(c);
struct crm_subsystem_s *the_subsystem = NULL;
if (client == NULL) {
return 0;
}
crm_trace("Connection %p", c);
if (client->userdata == NULL) {
crm_trace("Client hadn't registered with us yet");
} else if (strcasecmp(CRM_SYSTEM_PENGINE, client->userdata) == 0) {
the_subsystem = pe_subsystem;
} else if (strcasecmp(CRM_SYSTEM_TENGINE, client->userdata) == 0) {
the_subsystem = te_subsystem;
} else if (strcasecmp(CRM_SYSTEM_CIB, client->userdata) == 0) {
the_subsystem = cib_subsystem;
}
if (the_subsystem != NULL) {
the_subsystem->source = NULL;
the_subsystem->client = NULL;
crm_info("Received HUP from %s:[%d]", the_subsystem->name, the_subsystem->pid);
} else {
/* else that was a transient client */
crm_trace("Received HUP from transient client");
}
crm_trace("Disconnecting client %s (%p)", crm_client_name(client), client);
free(client->userdata);
crm_client_destroy(client);
trigger_fsa(fsa_source);
return 0;
}
static void
crmd_ipc_destroy(qb_ipcs_connection_t * c)
{
crm_trace("Connection %p", c);
crmd_ipc_closed(c);
}
/* A_STOP */
void
do_stop(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_trace("Closing IPC server");
mainloop_del_ipc_server(ipcs); ipcs = NULL;
register_fsa_input(C_FSA_INTERNAL, I_TERMINATE, NULL);
}
/* A_STARTED */
void
do_started(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)
{
static struct qb_ipcs_service_handlers crmd_callbacks = {
.connection_accept = crmd_ipc_accept,
.connection_created = crmd_ipc_created,
.msg_process = crmd_ipc_dispatch,
.connection_closed = crmd_ipc_closed,
.connection_destroyed = crmd_ipc_destroy
};
if (cur_state != S_STARTING) {
crm_err("Start cancelled... %s", fsa_state2string(cur_state));
return;
} else if (is_set(fsa_input_register, R_MEMBERSHIP) == FALSE) {
crm_info("Delaying start, no membership data (%.16llx)", R_MEMBERSHIP);
crmd_fsa_stall(TRUE);
return;
} else if (is_set(fsa_input_register, R_LRM_CONNECTED) == FALSE) {
crm_info("Delaying start, LRM not connected (%.16llx)", R_LRM_CONNECTED);
crmd_fsa_stall(TRUE);
return;
} else if (is_set(fsa_input_register, R_CIB_CONNECTED) == FALSE) {
crm_info("Delaying start, CIB not connected (%.16llx)", R_CIB_CONNECTED);
crmd_fsa_stall(TRUE);
return;
} else if (is_set(fsa_input_register, R_READ_CONFIG) == FALSE) {
crm_info("Delaying start, Config not read (%.16llx)", R_READ_CONFIG);
crmd_fsa_stall(TRUE);
return;
} else if (is_set(fsa_input_register, R_PEER_DATA) == FALSE) {
/* try reading from HA */
crm_info("Delaying start, No peer data (%.16llx)", R_PEER_DATA);
#if SUPPORT_HEARTBEAT
if (is_heartbeat_cluster()) {
HA_Message *msg = NULL;
crm_trace("Looking for a HA message");
msg = fsa_cluster_conn->llc_ops->readmsg(fsa_cluster_conn, 0);
if (msg != NULL) {
crm_trace("There was a HA message");
ha_msg_del(msg);
}
}
#endif
crmd_fsa_stall(TRUE);
return;
}
crm_debug("Init server comms");
ipcs = crmd_ipc_server_init(&crmd_callbacks);
if (ipcs == NULL) {
crm_err("Failed to create IPC server: shutting down and inhibiting respawn");
register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
}
if (stonith_reconnect == NULL) {
int dummy;
stonith_reconnect = mainloop_add_trigger(G_PRIORITY_LOW, te_connect_stonith, &dummy);
}
set_bit(fsa_input_register, R_ST_REQUIRED);
mainloop_set_trigger(stonith_reconnect);
crm_notice("The local CRM is operational");
clear_bit(fsa_input_register, R_STARTING);
register_fsa_input(msg_data->fsa_cause, I_PENDING, NULL);
}
/* A_RECOVER */
void
do_recover(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)
{
set_bit(fsa_input_register, R_IN_RECOVERY);
crm_warn("Fast-tracking shutdown in response to errors");
register_fsa_input(C_FSA_INTERNAL, I_TERMINATE, NULL);
}
/* *INDENT-OFF* */
pe_cluster_option crmd_opts[] = {
/* name, old-name, validate, values, default, short description, long description */
{ "dc-version", NULL, "string", NULL, "none", NULL,
"Version of Pacemaker on the cluster's DC.",
"Includes the hash which identifies the exact changeset it was built from. Used for diagnostic purposes."
},
{ "cluster-infrastructure", NULL, "string", NULL, "heartbeat", NULL,
"The messaging stack on which Pacemaker is currently running.",
"Used for informational and diagnostic purposes." },
{ XML_CONFIG_ATTR_DC_DEADTIME, "dc_deadtime", "time", NULL, "20s", &check_time,
"How long to wait for a response from other nodes during startup.",
"The \"correct\" value will depend on the speed/load of your network and the type of switches used."
},
{ XML_CONFIG_ATTR_RECHECK, "cluster_recheck_interval", "time",
"Zero disables polling. Positive values are an interval in seconds (unless other SI units are specified. eg. 5min)",
"15min", &check_timer,
"Polling interval for time based changes to options, resource parameters and constraints.",
"The Cluster is primarily event driven, however the configuration can have elements that change based on time."
" To ensure these changes take effect, we can optionally poll the cluster's status for changes."
},
#ifdef RHEL7_COMPAT
/* These options were superseded by the alerts feature and now are just an
* alternate interface to it. It was never released upstream, but was
* released in RHEL 7, so we allow it to be enabled at compile-time by
* defining RHEL7_COMPAT.
*/
{ "notification-agent", NULL, "string", NULL, "/dev/null", &check_script,
"Deprecated",
"Use alert path in alerts section instead"
},
{ "notification-recipient", NULL, "string", NULL, "", NULL,
"Deprecated",
"Use recipient value in alerts section instead"
},
#endif
{ "load-threshold", NULL, "percentage", NULL, "80%", &check_utilization,
"The maximum amount of system resources that should be used by nodes in the cluster",
"The cluster will slow down its recovery process when the amount of system resources used"
" (currently CPU) approaches this limit",
},
{ "node-action-limit", NULL, "integer", NULL, "0", &check_number,
"The maximum number of jobs that can be scheduled per node. Defaults to 2x cores"},
{ XML_CONFIG_ATTR_ELECTION_FAIL, "election_timeout", "time", NULL, "2min", &check_timer,
"*** Advanced Use Only ***.", "If need to adjust this value, it probably indicates the presence of a bug."
},
{ XML_CONFIG_ATTR_FORCE_QUIT, "shutdown_escalation", "time", NULL, "20min", &check_timer,
"*** Advanced Use Only ***.", "If need to adjust this value, it probably indicates the presence of a bug."
},
{ "crmd-integration-timeout", NULL, "time", NULL, "3min", &check_timer,
"*** Advanced Use Only ***.", "If need to adjust this value, it probably indicates the presence of a bug."
},
{ "crmd-finalization-timeout", NULL, "time", NULL, "30min", &check_timer,
"*** Advanced Use Only ***.", "If you need to adjust this value, it probably indicates the presence of a bug."
},
{ "crmd-transition-delay", NULL, "time", NULL, "0s", &check_timer,
"*** Advanced Use Only ***\n"
"Enabling this option will slow down cluster recovery under all conditions",
"Delay cluster recovery for the configured interval to allow for additional/related events to occur.\n"
"Useful if your configuration is sensitive to the order in which ping updates arrive."
},
{ "stonith-watchdog-timeout", NULL, "time", NULL, NULL, &check_sbd_timeout,
"How long to wait before we can assume nodes are safely down", NULL
},
{ "stonith-max-attempts",NULL,"integer",NULL,"10",&check_positive_number,
"How many times stonith can fail before it will no longer be attempted on a target"
},
{ "no-quorum-policy", "no_quorum_policy", "enum", "stop, freeze, ignore, suicide", "stop", &check_quorum, NULL, NULL },
#if SUPPORT_PLUGIN
{ XML_ATTR_EXPECTED_VOTES, NULL, "integer", NULL, "2", &check_number, "The number of nodes expected to be in the cluster", "Used to calculate quorum in openais based clusters." },
#endif
};
/* *INDENT-ON* */
void
crmd_metadata(void)
{
config_metadata("CRM Daemon", "1.0",
"CRM Daemon Options",
"This is a fake resource that details the options that can be configured for the CRM Daemon.",
crmd_opts, DIMOF(crmd_opts));
}
static void
verify_crmd_options(GHashTable * options)
{
verify_all_options(options, crmd_opts, DIMOF(crmd_opts));
}
static const char *
crmd_pref(GHashTable * options, const char *name)
{
return get_cluster_pref(options, crmd_opts, DIMOF(crmd_opts), name);
}
static void
config_query_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
{
const char *value = NULL;
GHashTable *config_hash = NULL;
crm_time_t *now = crm_time_new(NULL);
xmlNode *crmconfig = NULL;
xmlNode *alerts = NULL;
if (rc != pcmk_ok) {
fsa_data_t *msg_data = NULL;
crm_err("Local CIB query resulted in an error: %s", pcmk_strerror(rc));
register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
if (rc == -EACCES || rc == -pcmk_err_schema_validation) {
crm_err("The cluster is mis-configured - shutting down and staying down");
set_bit(fsa_input_register, R_STAYDOWN);
}
goto bail;
}
crmconfig = output;
if ((crmconfig) &&
(crm_element_name(crmconfig)) &&
(strcmp(crm_element_name(crmconfig), XML_CIB_TAG_CRMCONFIG) != 0)) {
crmconfig = first_named_child(crmconfig, XML_CIB_TAG_CRMCONFIG);
}
if (!crmconfig) {
fsa_data_t *msg_data = NULL;
crm_err("Local CIB query for " XML_CIB_TAG_CRMCONFIG " section failed");
register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
goto bail;
}
crm_debug("Call %d : Parsing CIB options", call_id);
config_hash = crm_str_table_new();
unpack_instance_attributes(crmconfig, crmconfig, XML_CIB_TAG_PROPSET, NULL, config_hash,
CIB_OPTIONS_FIRST, FALSE, now);
verify_crmd_options(config_hash);
#ifdef RHEL7_COMPAT
{
const char *script = crmd_pref(config_hash, "notification-agent");
const char *recip = crmd_pref(config_hash, "notification-recipient");
pe_enable_legacy_alerts(script, recip);
}
#endif
value = crmd_pref(config_hash, XML_CONFIG_ATTR_DC_DEADTIME);
election_trigger->period_ms = crm_get_msec(value);
value = crmd_pref(config_hash, "node-action-limit"); /* Also checks migration-limit */
throttle_update_job_max(value);
value = crmd_pref(config_hash, "load-threshold");
if(value) {
throttle_set_load_target(strtof(value, NULL) / 100.0);
}
value = crmd_pref(config_hash, "no-quorum-policy");
if (safe_str_eq(value, "suicide") && pcmk_locate_sbd()) {
no_quorum_suicide_escalation = TRUE;
}
value = crmd_pref(config_hash,"stonith-max-attempts");
update_stonith_max_attempts(value);
value = crmd_pref(config_hash, XML_CONFIG_ATTR_FORCE_QUIT);
shutdown_escalation_timer->period_ms = crm_get_msec(value);
/* How long to declare an election over - even if not everyone voted */
crm_debug("Shutdown escalation occurs after: %dms", shutdown_escalation_timer->period_ms);
value = crmd_pref(config_hash, XML_CONFIG_ATTR_ELECTION_FAIL);
election_timeout_set_period(fsa_election, crm_get_msec(value));
value = crmd_pref(config_hash, XML_CONFIG_ATTR_RECHECK);
recheck_timer->period_ms = crm_get_msec(value);
crm_debug("Checking for expired actions every %dms", recheck_timer->period_ms);
value = crmd_pref(config_hash, "crmd-transition-delay");
transition_timer->period_ms = crm_get_msec(value);
value = crmd_pref(config_hash, "crmd-integration-timeout");
integration_timer->period_ms = crm_get_msec(value);
value = crmd_pref(config_hash, "crmd-finalization-timeout");
finalization_timer->period_ms = crm_get_msec(value);
#if SUPPORT_COROSYNC
if (is_classic_ais_cluster()) {
value = crmd_pref(config_hash, XML_ATTR_EXPECTED_VOTES);
crm_debug("Sending expected-votes=%s to corosync", value);
send_cluster_text(crm_class_quorum, value, TRUE, NULL, crm_msg_ais);
}
#endif
free(fsa_cluster_name);
fsa_cluster_name = NULL;
value = g_hash_table_lookup(config_hash, "cluster-name");
if (value) {
fsa_cluster_name = strdup(value);
}
alerts = first_named_child(output, XML_CIB_TAG_ALERTS);
crmd_unpack_alerts(alerts);
set_bit(fsa_input_register, R_READ_CONFIG);
crm_trace("Triggering FSA: %s", __FUNCTION__);
mainloop_set_trigger(fsa_source);
g_hash_table_destroy(config_hash);
bail:
crm_time_free(now);
}
gboolean
crm_read_options(gpointer user_data)
{
int call_id =
fsa_cib_conn->cmds->query(fsa_cib_conn,
"//" XML_CIB_TAG_CRMCONFIG " | //" XML_CIB_TAG_ALERTS,
NULL, cib_xpath | cib_scope_local);
fsa_register_cib_callback(call_id, FALSE, NULL, config_query_callback);
crm_trace("Querying the CIB... call %d", call_id);
return TRUE;
}
/* A_READCONFIG */
void
do_read_config(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)
{
throttle_init();
mainloop_set_trigger(config_read);
}
void
crm_shutdown(int nsig)
{
if (crmd_mainloop != NULL && g_main_is_running(crmd_mainloop)) {
if (is_set(fsa_input_register, R_SHUTDOWN)) {
crm_err("Escalating the shutdown");
register_fsa_input_before(C_SHUTDOWN, I_ERROR, NULL);
} else {
set_bit(fsa_input_register, R_SHUTDOWN);
register_fsa_input(C_SHUTDOWN, I_SHUTDOWN, NULL);
if (shutdown_escalation_timer->period_ms < 1) {
const char *value = crmd_pref(NULL, XML_CONFIG_ATTR_FORCE_QUIT);
int msec = crm_get_msec(value);
crm_debug("Using default shutdown escalation: %dms", msec);
shutdown_escalation_timer->period_ms = msec;
}
/* can't rely on this... */
crm_notice("Shutting down cluster resource manager " CRM_XS
" limit=%dms", shutdown_escalation_timer->period_ms);
crm_timer_start(shutdown_escalation_timer);
}
} else {
crm_info("exit from shutdown");
crmd_exit(pcmk_ok);
}
}
diff --git a/crmd/crmd_alerts.c b/crmd/crmd_alerts.c
deleted file mode 100644
index d14cb5c1ca..0000000000
--- a/crmd/crmd_alerts.c
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2015 Andrew Beekhof <andrew@beekhof.net>
- *
- * 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
- */
-
-#include <crm_internal.h>
-#include <crm/crm.h>
-#include <crm/msg_xml.h>
-#include <crm/lrmd_alerts_internal.h>
-#include "crmd_lrm.h"
-#include "crmd_alerts.h"
-#include "crmd_messages.h"
-#include <crm/common/alerts_internal.h>
-#include <crm/common/iso8601_internal.h>
-#include <crm/pengine/rules_internal.h>
-
-static GListPtr crmd_alert_list = NULL;
-
-void
-crmd_unpack_alerts(xmlNode *alerts)
-{
- pe_free_alert_list(crmd_alert_list);
- crmd_alert_list = pe_unpack_alerts(alerts);
-}
-
-void
-crmd_alert_node_event(crm_node_t *node)
-{
- lrmd_send_node_alert(crmd_alert_list, crmd_local_lrmd_conn, node->uname,
- node->id, node->state);
-}
-
-void
-crmd_alert_fencing_op(stonith_event_t * e)
-{
- char *desc = crm_strdup_printf("Operation %s of %s by %s for %s@%s: %s (ref=%s)",
- e->action, e->target,
- (e->executioner? e->executioner : "<no-one>"),
- e->client_origin, e->origin,
- pcmk_strerror(e->result), e->id);
-
- lrmd_send_fencing_alert(crmd_alert_list, crmd_local_lrmd_conn, e->target,
- e->operation, desc, e->result);
- free(desc);
-}
-
-void
-crmd_alert_resource_op(const char *node, lrmd_event_data_t * op)
-{
- lrmd_send_resource_alert(crmd_alert_list, crmd_local_lrmd_conn, node, op);
-}
diff --git a/crmd/crmd_lrm.h b/crmd/crmd_lrm.h
index 1866c4e606..266d40de61 100644
--- a/crmd/crmd_lrm.h
+++ b/crmd/crmd_lrm.h
@@ -1,184 +1,172 @@
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* 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
*/
#include <crmd_messages.h>
+#include <crmd_metadata.h>
extern gboolean verify_stopped(enum crmd_fsa_state cur_state, int log_level);
extern void lrm_clear_last_failure(const char *rsc_id, const char *node_name,
const char *operation, int interval);
void lrm_op_callback(lrmd_event_data_t * op);
lrmd_t *crmd_local_lrmd_conn(void);
typedef struct resource_history_s {
char *id;
uint32_t last_callid;
lrmd_rsc_info_t rsc;
lrmd_event_data_t *last;
lrmd_event_data_t *failed;
GList *recurring_op_list;
/* Resources must be stopped using the same
* parameters they were started with. This hashtable
* holds the parameters that should be used for the next stop
* cmd on this resource. */
GHashTable *stop_params;
} rsc_history_t;
void history_free(gpointer data);
/* TDOD - Replace this with lrmd_event_data_t */
struct recurring_op_s {
int call_id;
int interval;
gboolean remove;
gboolean cancelled;
unsigned int start_time;
char *rsc_id;
char *op_type;
char *op_key;
char *user_data;
GHashTable *params;
};
typedef struct lrm_state_s {
const char *node_name;
/* reserved for lrm_state.c usage only */
void *conn;
/* reserved for remote_lrmd_ra.c usage only */
void *remote_ra_data;
GHashTable *resource_history;
GHashTable *pending_ops;
GHashTable *deletion_ops;
GHashTable *rsc_info_cache;
+ GHashTable *metadata_cache; // key = class[:provider]:agent, value = ra_metadata_s
int num_lrm_register_fails;
-
- GHashTable *metadata_cache;
} lrm_state_t;
struct pending_deletion_op_s {
char *rsc;
ha_msg_input_t *input;
};
/*!
* \brief Is this the local ipc connection to the lrmd
*/
gboolean
lrm_state_is_local(lrm_state_t *lrm_state);
/*!
* \brief Clear all state information from a single state entry.
* \note It sometimes useful to save metadata cache when it won't go stale.
* \note This does not close the lrmd connection
*/
void lrm_state_reset_tables(lrm_state_t * lrm_state, gboolean reset_metadata);
GList *lrm_state_get_list(void);
/*!
* \brief Initiate internal state tables
*/
gboolean lrm_state_init_local(void);
/*!
* \brief Destroy all state entries and internal state tables
*/
void lrm_state_destroy_all(void);
/*!
* \brief Create lrmd connection entry.
*/
lrm_state_t *lrm_state_create(const char *node_name);
/*!
* \brief Destroy lrmd connection keyed of node name
*/
void lrm_state_destroy(const char *node_name);
/*!
* \brief Find lrm_state data by node name
*/
lrm_state_t *lrm_state_find(const char *node_name);
/*!
* \brief Either find or create a new entry
*/
lrm_state_t *lrm_state_find_or_create(const char *node_name);
-/*!
- * \brief Update metadata cache
- * \retval metadata XML on success
- * \retval NULL on failure
- */
-xmlNode *lrm_state_update_rsc_metadata(lrm_state_t *lrm_state, lrmd_rsc_info_t *rsc, const char *metadata_str);
-
-/*!
- * \brief Supply cached metadata XML based on resource's class:provider:type
- */
-xmlNode *lrm_state_get_rsc_metadata(lrm_state_t *lrm_state, lrmd_rsc_info_t *rsc);
-
/*!
* The functions below are wrappers for the lrmd api calls the crmd
* uses. These wrapper functions allow us to treat the crmd's remote
* lrmd connection resources the same as regular resources. Internally
* Regular resources go to the lrmd, and remote connection resources are
* handled locally in the crmd.
*/
void lrm_state_disconnect_only(lrm_state_t * lrm_state);
void lrm_state_disconnect(lrm_state_t * lrm_state);
int lrm_state_ipc_connect(lrm_state_t * lrm_state);
int lrm_state_remote_connect_async(lrm_state_t * lrm_state, const char *server, int port,
int timeout);
int lrm_state_is_connected(lrm_state_t * lrm_state);
int lrm_state_poke_connection(lrm_state_t * lrm_state);
int lrm_state_get_metadata(lrm_state_t * lrm_state,
const char *class,
const char *provider,
const char *agent, char **output, enum lrmd_call_options options);
int lrm_state_cancel(lrm_state_t * lrm_state, const char *rsc_id, const char *action, int interval);
int lrm_state_exec(lrm_state_t * lrm_state, const char *rsc_id, const char *action, const char *userdata, int interval, /* ms */
int timeout, /* ms */
int start_delay, /* ms */
lrmd_key_value_t * params);
lrmd_rsc_info_t *lrm_state_get_rsc_info(lrm_state_t * lrm_state,
const char *rsc_id, enum lrmd_call_options options);
int lrm_state_register_rsc(lrm_state_t * lrm_state,
const char *rsc_id,
const char *class,
const char *provider, const char *agent, enum lrmd_call_options options);
int lrm_state_unregister_rsc(lrm_state_t * lrm_state,
const char *rsc_id, enum lrmd_call_options options);
/*! These functions are used to manage the remote lrmd connection resources */
void remote_lrm_op_callback(lrmd_event_data_t * op);
gboolean is_remote_lrmd_ra(const char *agent, const char *provider, const char *id);
lrmd_rsc_info_t *remote_ra_get_rsc_info(lrm_state_t * lrm_state, const char *rsc_id);
int remote_ra_cancel(lrm_state_t * lrm_state, const char *rsc_id, const char *action, int interval);
int remote_ra_exec(lrm_state_t * lrm_state, const char *rsc_id, const char *action, const char *userdata, int interval, /* ms */
int timeout, /* ms */
int start_delay, /* ms */
lrmd_key_value_t * params);
void remote_ra_cleanup(lrm_state_t * lrm_state);
void remote_ra_fail(const char *node_name);
void remote_ra_process_pseudo(xmlNode *xml);
gboolean remote_ra_is_in_maintenance(lrm_state_t * lrm_state);
void remote_ra_process_maintenance_nodes(xmlNode *xml);
gboolean process_lrm_event(lrm_state_t * lrm_state, lrmd_event_data_t * op, struct recurring_op_s *pending);
diff --git a/crmd/crmd_metadata.c b/crmd/crmd_metadata.c
new file mode 100644
index 0000000000..f208c03778
--- /dev/null
+++ b/crmd/crmd_metadata.c
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2017 Andrew Beekhof <andrew@beekhof.net>
+ *
+ * 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 <glib.h>
+#include <regex.h>
+
+#include <crm/crm.h>
+#include <crm/lrmd.h>
+
+#include "crmd_lrm.h"
+
+/* If we can't successfully get the version from the agent, use this */
+#define DEFAULT_RA_VERSION "0.1"
+
+static regex_t *version_format_regex = NULL;
+
+static void
+ra_param_free(void *param)
+{
+ if (param) {
+ struct ra_param_s *p = (struct ra_param_s *) param;
+
+ if (p->rap_name) {
+ free(p->rap_name);
+ }
+ free(param);
+ }
+}
+
+static void
+metadata_free(void *metadata)
+{
+ if (metadata) {
+ struct ra_metadata_s *md = (struct ra_metadata_s *) metadata;
+
+ if (md->ra_version) {
+ free(md->ra_version);
+ }
+ g_list_free_full(md->ra_params, ra_param_free);
+ free(metadata);
+ }
+}
+
+GHashTable *
+metadata_cache_new()
+{
+ return g_hash_table_new_full(crm_str_hash, g_str_equal, g_hash_destroy_str,
+ metadata_free);
+}
+
+void
+metadata_cache_free(GHashTable *mdc)
+{
+ if (mdc) {
+ crm_trace("Destroying metadata cache with %d members", g_hash_table_size(mdc));
+ g_hash_table_destroy(mdc);
+ }
+}
+
+void
+metadata_cache_reset(GHashTable *mdc)
+{
+ if (mdc) {
+ crm_trace("Resetting metadata cache with %d members",
+ g_hash_table_size(mdc));
+ g_hash_table_remove_all(mdc);
+ }
+}
+
+static gboolean
+valid_version_format(const char *version)
+{
+ if (version == NULL) {
+ return FALSE;
+ }
+
+ if (version_format_regex == NULL) {
+ /* The OCF standard allows free-form versioning, but for our purposes of
+ * versioned resource and operation attributes, we constrain it to
+ * dot-separated numbers. Agents are still free to use other schemes,
+ * but we can't determine attributes based on them.
+ */
+ const char *regex_string = "^[[:digit:]]+([.][[:digit:]]+)*$";
+
+ version_format_regex = calloc(1, sizeof(regex_t));
+ regcomp(version_format_regex, regex_string, REG_EXTENDED | REG_NOSUB);
+
+ /* If our regex doesn't compile, it's a bug on our side, so CRM_CHECK()
+ * will give us a core dump to catch it. Pretend the version is OK
+ * because we don't want our mistake to break versioned attributes
+ * (which should only ever happen in a development branch anyway).
+ */
+ CRM_CHECK(version_format_regex != NULL, return TRUE);
+ }
+
+ return regexec(version_format_regex, version, 0, NULL, 0) == 0;
+}
+
+void
+metadata_cache_fini()
+{
+ if (version_format_regex) {
+ regfree(version_format_regex);
+ free(version_format_regex);
+ version_format_regex = NULL;
+ }
+}
+
+static char *
+ra_version_from_xml(xmlNode *metadata_xml, const lrmd_rsc_info_t *rsc)
+{
+ const char *version = crm_element_value(metadata_xml, XML_ATTR_VERSION);
+
+ if (version == NULL) {
+ crm_debug("Metadata for %s:%s:%s does not specify a version",
+ rsc->class, rsc->provider, rsc->type);
+ version = DEFAULT_RA_VERSION;
+
+ } else if (!valid_version_format(version)) {
+ crm_notice("%s:%s:%s metadata version has unrecognized format",
+ rsc->class, rsc->provider, rsc->type);
+ version = DEFAULT_RA_VERSION;
+
+ } else {
+ crm_debug("Metadata for %s:%s:%s has version %s",
+ rsc->class, rsc->provider, rsc->type, version);
+ }
+ return strdup(version);
+}
+
+static struct ra_param_s *
+ra_param_from_xml(xmlNode *param_xml, struct ra_metadata_s *md)
+{
+ const char *param_name = crm_element_value(param_xml, "name");
+ const char *value;
+ struct ra_param_s *p;
+
+ p = calloc(1, sizeof(struct ra_param_s));
+ if (p == NULL) {
+ crm_crit("Could not allocate memory for resource metadata");
+ return NULL;
+ }
+
+ p->rap_name = strdup(param_name);
+ if (p->rap_name == NULL) {
+ crm_crit("Could not allocate memory for resource metadata");
+ free(p);
+ return NULL;
+ }
+
+ /* Currently, we abuse "unique" to indicate reloadability (as the
+ * inverse of unique). This is nonstandard and should eventually be
+ * replaced once the OCF standard is updated with something better.
+ */
+ value = crm_element_value(param_xml, "unique");
+ if (!crm_is_true(value)) {
+ set_bit(p->rap_flags, ra_param_reloadable);
+ }
+
+ value = crm_element_value(param_xml, "private");
+ if (crm_is_true(value)) {
+ set_bit(p->rap_flags, ra_param_private);
+ set_bit(md->ra_flags, ra_uses_private);
+ }
+ return p;
+}
+
+struct ra_metadata_s *
+metadata_cache_update(GHashTable *mdc, lrmd_rsc_info_t *rsc,
+ const char *metadata_str)
+{
+ char *key = NULL;
+ xmlNode *metadata = NULL;
+ xmlNode *match = NULL;
+ struct ra_metadata_s *md = NULL;
+
+ CRM_CHECK(mdc && rsc && metadata_str, return NULL);
+
+ key = crm_generate_ra_key(rsc->class, rsc->provider, rsc->type);
+ if (!key) {
+ crm_crit("Could not allocate memory for resource metadata");
+ goto err;
+ }
+
+ metadata = string2xml(metadata_str);
+ if (!metadata) {
+ crm_err("Metadata for %s:%s:%s is not valid XML",
+ rsc->class, rsc->provider, rsc->type);
+ goto err;
+ }
+
+ md = calloc(1, sizeof(struct ra_metadata_s));
+ if (md == NULL) {
+ crm_crit("Could not allocate memory for resource metadata");
+ goto err;
+ }
+
+ md->ra_version = ra_version_from_xml(metadata, rsc);
+
+ // Check supported actions
+ match = first_named_child(metadata, "actions");
+ for (match = first_named_child(match, "action"); match != NULL;
+ match = crm_next_same_xml(match)) {
+
+ const char *action_name = crm_element_value(match, "name");
+
+ if (safe_str_eq(action_name, "reload")) {
+ set_bit(md->ra_flags, ra_supports_reload);
+ break; // since this is the only action we currently care about
+ }
+ }
+
+ // Build a parameter list
+ match = first_named_child(metadata, "parameters");
+ for (match = first_named_child(match, "parameter"); match != NULL;
+ match = crm_next_same_xml(match)) {
+
+ const char *param_name = crm_element_value(match, "name");
+
+ if (param_name == NULL) {
+ crm_warn("Metadata for %s:%s:%s has parameter without a name",
+ rsc->class, rsc->provider, rsc->type);
+ } else {
+ struct ra_param_s *p = ra_param_from_xml(match, md);
+
+ if (p == NULL) {
+ goto err;
+ }
+ md->ra_params = g_list_prepend(md->ra_params, p);
+ }
+ }
+
+ g_hash_table_replace(mdc, key, md);
+ return md;
+
+err:
+ free(key);
+ free_xml(metadata);
+ metadata_free(md);
+ return NULL;
+}
+
+struct ra_metadata_s *
+metadata_cache_get(GHashTable *mdc, lrmd_rsc_info_t *rsc)
+{
+ char *key = NULL;
+ struct ra_metadata_s *metadata = NULL;
+
+ CRM_CHECK(mdc && rsc, return NULL);
+ key = crm_generate_ra_key(rsc->class, rsc->provider, rsc->type);
+ if (key) {
+ metadata = g_hash_table_lookup(mdc, key);
+ free(key);
+ }
+ return metadata;
+}
diff --git a/crmd/crmd_metadata.h b/crmd/crmd_metadata.h
new file mode 100644
index 0000000000..16e2f21da1
--- /dev/null
+++ b/crmd/crmd_metadata.h
@@ -0,0 +1,55 @@
+#ifndef CRMD_METADATA_H
+#define CRMD_METADATA_H
+
+/*
+ * Copyright (C) 2017 Andrew Beekhof <andrew@beekhof.net>
+ *
+ * This source code is licensed under the GNU General Public License version 2
+ * or later (GPLv2+) WITHOUT ANY WARRANTY.
+ */
+
+enum ra_flags_e {
+ ra_supports_reload = 0x01,
+ ra_uses_private = 0x02,
+};
+
+enum ra_param_flags_e {
+ ra_param_reloadable = 0x01,
+ ra_param_private = 0x02,
+};
+
+struct ra_param_s {
+ char *rap_name;
+ uint32_t rap_flags; // bitmask of ra_param_flags_s
+};
+
+struct ra_metadata_s {
+ char *ra_version;
+ GList *ra_params; // ra_param_s
+ uint32_t ra_flags; // bitmask of ra_flags_e
+};
+
+GHashTable *metadata_cache_new(void);
+void metadata_cache_free(GHashTable *mdc);
+void metadata_cache_reset(GHashTable *mdc);
+void metadata_cache_fini(void);
+
+struct ra_metadata_s *metadata_cache_update(GHashTable *mdc,
+ lrmd_rsc_info_t *rsc,
+ const char *metadata_str);
+struct ra_metadata_s *metadata_cache_get(GHashTable *mdc, lrmd_rsc_info_t *rsc);
+
+static inline const char *
+ra_param_flag2text(enum ra_param_flags_e flag)
+{
+ switch (flag) {
+ case ra_param_reloadable:
+ return "reloadable";
+ case ra_param_private:
+ return "private";
+ default:
+ return "unknown";
+ }
+}
+
+#endif
diff --git a/crmd/lrm.c b/crmd/lrm.c
index 0e47a63c9b..5b80056b19 100644
--- a/crmd/lrm.c
+++ b/crmd/lrm.c
@@ -1,2664 +1,2570 @@
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* 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
*/
#include <crm_internal.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <crm/crm.h>
#include <crm/services.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crmd.h>
#include <crmd_fsa.h>
#include <crmd_messages.h>
#include <crmd_callbacks.h>
#include <crmd_lrm.h>
#include <regex.h>
#include <crm/pengine/rules.h>
#define START_DELAY_THRESHOLD 5 * 60 * 1000
#define MAX_LRM_REG_FAILS 30
#define s_if_plural(i) (((i) == 1)? "" : "s")
struct delete_event_s {
int rc;
const char *rsc;
lrm_state_t *lrm_state;
};
static gboolean is_rsc_active(lrm_state_t * lrm_state, const char *rsc_id);
static gboolean build_active_RAs(lrm_state_t * lrm_state, xmlNode * rsc_list);
static gboolean stop_recurring_actions(gpointer key, gpointer value, gpointer user_data);
static int delete_rsc_status(lrm_state_t * lrm_state, const char *rsc_id, int call_options,
const char *user_name);
static lrmd_event_data_t *construct_op(lrm_state_t * lrm_state, xmlNode * rsc_op,
const char *rsc_id, const char *operation);
static void do_lrm_rsc_op(lrm_state_t * lrm_state, lrmd_rsc_info_t * rsc, const char *operation,
xmlNode * msg, xmlNode * request);
void send_direct_ack(const char *to_host, const char *to_sys,
lrmd_rsc_info_t * rsc, lrmd_event_data_t * op, const char *rsc_id);
static gboolean lrm_state_verify_stopped(lrm_state_t * lrm_state, enum crmd_fsa_state cur_state,
int log_level);
static int do_update_resource(const char *node_name, lrmd_rsc_info_t * rsc, lrmd_event_data_t * op);
static void
lrm_connection_destroy(void)
{
if (is_set(fsa_input_register, R_LRM_CONNECTED)) {
crm_crit("LRM Connection failed");
register_fsa_input(C_FSA_INTERNAL, I_ERROR, NULL);
clear_bit(fsa_input_register, R_LRM_CONNECTED);
} else {
crm_info("LRM Connection disconnected");
}
}
static char *
make_stop_id(const char *rsc, int call_id)
{
char *op_id = NULL;
op_id = calloc(1, strlen(rsc) + 34);
if (op_id != NULL) {
snprintf(op_id, strlen(rsc) + 34, "%s:%d", rsc, call_id);
}
return op_id;
}
static void
copy_instance_keys(gpointer key, gpointer value, gpointer user_data)
{
if (strstr(key, CRM_META "_") == NULL) {
g_hash_table_replace(user_data, strdup((const char *)key), strdup((const char *)value));
}
}
static void
copy_meta_keys(gpointer key, gpointer value, gpointer user_data)
{
if (strstr(key, CRM_META "_") != NULL) {
g_hash_table_replace(user_data, strdup((const char *)key), strdup((const char *)value));
}
}
/*!
* \internal
* \brief Remove a recurring operation from a resource's history
*
* \param[in,out] history Resource history to modify
* \param[in] op Operation to remove
*
* \return TRUE if the operation was found and removed, FALSE otherwise
*/
static gboolean
history_remove_recurring_op(rsc_history_t *history, const lrmd_event_data_t *op)
{
GList *iter;
for (iter = history->recurring_op_list; iter != NULL; iter = iter->next) {
lrmd_event_data_t *existing = iter->data;
if ((op->interval == existing->interval)
&& crm_str_eq(op->rsc_id, existing->rsc_id, TRUE)
&& safe_str_eq(op->op_type, existing->op_type)) {
history->recurring_op_list = g_list_delete_link(history->recurring_op_list, iter);
lrmd_free_event(existing);
return TRUE;
}
}
return FALSE;
}
/*!
* \internal
* \brief Free all recurring operations in resource history
*
* \param[in,out] history Resource history to modify
*/
static void
history_free_recurring_ops(rsc_history_t *history)
{
GList *iter;
for (iter = history->recurring_op_list; iter != NULL; iter = iter->next) {
lrmd_free_event(iter->data);
}
g_list_free(history->recurring_op_list);
history->recurring_op_list = NULL;
}
/*!
* \internal
* \brief Free resource history
*
* \param[in,out] history Resource history to free
*/
void
history_free(gpointer data)
{
rsc_history_t *history = (rsc_history_t*)data;
if (history->stop_params) {
g_hash_table_destroy(history->stop_params);
}
/* Don't need to free history->rsc.id because it's set to history->id */
free(history->rsc.type);
free(history->rsc.class);
free(history->rsc.provider);
lrmd_free_event(history->failed);
lrmd_free_event(history->last);
free(history->id);
history_free_recurring_ops(history);
free(history);
}
static void
update_history_cache(lrm_state_t * lrm_state, lrmd_rsc_info_t * rsc, lrmd_event_data_t * op)
{
int target_rc = 0;
rsc_history_t *entry = NULL;
if (op->rsc_deleted) {
crm_debug("Purged history for '%s' after %s", op->rsc_id, op->op_type);
delete_rsc_status(lrm_state, op->rsc_id, cib_quorum_override, NULL);
return;
}
if (safe_str_eq(op->op_type, RSC_NOTIFY)) {
return;
}
crm_debug("Updating history for '%s' with %s op", op->rsc_id, op->op_type);
entry = g_hash_table_lookup(lrm_state->resource_history, op->rsc_id);
if (entry == NULL && rsc) {
entry = calloc(1, sizeof(rsc_history_t));
entry->id = strdup(op->rsc_id);
g_hash_table_insert(lrm_state->resource_history, entry->id, entry);
entry->rsc.id = entry->id;
entry->rsc.type = strdup(rsc->type);
entry->rsc.class = strdup(rsc->class);
if (rsc->provider) {
entry->rsc.provider = strdup(rsc->provider);
} else {
entry->rsc.provider = NULL;
}
} else if (entry == NULL) {
crm_info("Resource %s no longer exists, not updating cache", op->rsc_id);
return;
}
entry->last_callid = op->call_id;
target_rc = rsc_op_expected_rc(op);
if (op->op_status == PCMK_LRM_OP_CANCELLED) {
if (op->interval > 0) {
crm_trace("Removing cancelled recurring op: %s_%s_%d", op->rsc_id, op->op_type,
op->interval);
history_remove_recurring_op(entry, op);
return;
} else {
crm_trace("Skipping %s_%s_%d rc=%d, status=%d", op->rsc_id, op->op_type, op->interval,
op->rc, op->op_status);
}
} else if (did_rsc_op_fail(op, target_rc)) {
/* Store failed monitors here, otherwise the block below will cause them
* to be forgotten when a stop happens.
*/
if (entry->failed) {
lrmd_free_event(entry->failed);
}
entry->failed = lrmd_copy_event(op);
} else if (op->interval == 0) {
if (entry->last) {
lrmd_free_event(entry->last);
}
entry->last = lrmd_copy_event(op);
if (op->params &&
(safe_str_eq(CRMD_ACTION_START, op->op_type) ||
safe_str_eq("reload", op->op_type) ||
safe_str_eq(CRMD_ACTION_STATUS, op->op_type))) {
if (entry->stop_params) {
g_hash_table_destroy(entry->stop_params);
}
entry->stop_params = crm_str_table_new();
g_hash_table_foreach(op->params, copy_instance_keys, entry->stop_params);
}
}
if (op->interval > 0) {
/* Ensure there are no duplicates */
history_remove_recurring_op(entry, op);
crm_trace("Adding recurring op: %s_%s_%d", op->rsc_id, op->op_type, op->interval);
entry->recurring_op_list = g_list_prepend(entry->recurring_op_list, lrmd_copy_event(op));
} else if (entry->recurring_op_list && safe_str_eq(op->op_type, RSC_STATUS) == FALSE) {
crm_trace("Dropping %d recurring ops because of: %s_%s_%d",
g_list_length(entry->recurring_op_list), op->rsc_id,
op->op_type, op->interval);
history_free_recurring_ops(entry);
}
}
/*!
* \internal
* \brief Send a direct OK ack for a resource task
*
* \param[in] lrm_state LRM connection
* \param[in] input Input message being ack'ed
* \param[in] rsc_id ID of affected resource
* \param[in] rsc Affected resource (if available)
* \param[in] task Operation task being ack'ed
* \param[in] ack_host Name of host to send ack to
* \param[in] ack_sys IPC system name to ack
*/
static void
send_task_ok_ack(lrm_state_t *lrm_state, ha_msg_input_t *input,
const char *rsc_id, lrmd_rsc_info_t *rsc, const char *task,
const char *ack_host, const char *ack_sys)
{
lrmd_event_data_t *op = construct_op(lrm_state, input->xml, rsc_id, task);
CRM_ASSERT(op != NULL);
op->rc = PCMK_OCF_OK;
op->op_status = PCMK_LRM_OP_DONE;
send_direct_ack(ack_host, ack_sys, rsc, op, rsc_id);
lrmd_free_event(op);
}
void
lrm_op_callback(lrmd_event_data_t * op)
{
const char *nodename = NULL;
lrm_state_t *lrm_state = NULL;
CRM_CHECK(op != NULL, return);
/* determine the node name for this connection. */
nodename = op->remote_nodename ? op->remote_nodename : fsa_our_uname;
if (op->type == lrmd_event_disconnect && (safe_str_eq(nodename, fsa_our_uname))) {
/* if this is the local lrmd ipc connection, set the right bits in the
* crmd when the connection goes down */
lrm_connection_destroy();
return;
} else if (op->type != lrmd_event_exec_complete) {
/* we only need to process execution results */
return;
}
lrm_state = lrm_state_find(nodename);
CRM_ASSERT(lrm_state != NULL);
process_lrm_event(lrm_state, op, NULL);
}
/* A_LRM_CONNECT */
void
do_lrm_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)
{
/* This only pertains to local lrmd connections. Remote connections are handled as
* resources within the pengine. Connecting and disconnecting from remote lrmd instances
* handled differently than the local. */
lrm_state_t *lrm_state = NULL;
if(fsa_our_uname == NULL) {
return; /* Nothing to do */
}
lrm_state = lrm_state_find_or_create(fsa_our_uname);
if (lrm_state == NULL) {
register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
return;
}
if (action & A_LRM_DISCONNECT) {
if (lrm_state_verify_stopped(lrm_state, cur_state, LOG_INFO) == FALSE) {
if (action == A_LRM_DISCONNECT) {
crmd_fsa_stall(FALSE);
return;
}
}
clear_bit(fsa_input_register, R_LRM_CONNECTED);
crm_info("Disconnecting from the LRM");
lrm_state_disconnect(lrm_state);
lrm_state_reset_tables(lrm_state, FALSE);
crm_notice("Disconnected from the LRM");
}
if (action & A_LRM_CONNECT) {
int ret = pcmk_ok;
crm_debug("Connecting to the LRM");
ret = lrm_state_ipc_connect(lrm_state);
if (ret != pcmk_ok) {
if (lrm_state->num_lrm_register_fails < MAX_LRM_REG_FAILS) {
crm_warn("Failed to connect to the LRM %d time%s (%d max)",
lrm_state->num_lrm_register_fails,
s_if_plural(lrm_state->num_lrm_register_fails),
MAX_LRM_REG_FAILS);
crm_timer_start(wait_timer);
crmd_fsa_stall(FALSE);
return;
}
}
if (ret != pcmk_ok) {
crm_err("Failed to connect to the LRM the max allowed %d time%s",
lrm_state->num_lrm_register_fails,
s_if_plural(lrm_state->num_lrm_register_fails));
register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
return;
}
set_bit(fsa_input_register, R_LRM_CONNECTED);
crm_info("LRM connection established");
}
if (action & ~(A_LRM_CONNECT | A_LRM_DISCONNECT)) {
crm_err("Unexpected action %s in %s", fsa_action2string(action), __FUNCTION__);
}
}
-/*!
- * \internal
- * \brief Get the local lrmd connection
- *
- * \return Local lrmd connection, or NULL if not connected
- */
-lrmd_t *
-crmd_local_lrmd_conn()
-{
- lrm_state_t *lrm_state = lrm_state_find(fsa_our_uname);
-
- if (lrm_state == NULL) {
- return NULL;
- }
- return (lrmd_t *) (lrm_state->conn);
-}
-
static gboolean
lrm_state_verify_stopped(lrm_state_t * lrm_state, enum crmd_fsa_state cur_state, int log_level)
{
int counter = 0;
gboolean rc = TRUE;
const char *when = "lrm disconnect";
GHashTableIter gIter;
const char *key = NULL;
rsc_history_t *entry = NULL;
struct recurring_op_s *pending = NULL;
crm_debug("Checking for active resources before exit");
if (cur_state == S_TERMINATE) {
log_level = LOG_ERR;
when = "shutdown";
} else if (is_set(fsa_input_register, R_SHUTDOWN)) {
when = "shutdown... waiting";
}
if (lrm_state->pending_ops && lrm_state_is_connected(lrm_state) == TRUE) {
guint removed = g_hash_table_foreach_remove(
lrm_state->pending_ops, stop_recurring_actions, lrm_state);
guint nremaining = g_hash_table_size(lrm_state->pending_ops);
if (removed || nremaining) {
crm_notice("Stopped %u recurring operation%s at %s (%u remaining)",
removed, s_if_plural(removed), when, nremaining);
}
}
if (lrm_state->pending_ops) {
g_hash_table_iter_init(&gIter, lrm_state->pending_ops);
while (g_hash_table_iter_next(&gIter, NULL, (void **)&pending)) {
/* Ignore recurring actions in the shutdown calculations */
if (pending->interval == 0) {
counter++;
}
}
}
if (counter > 0) {
do_crm_log(log_level, "%d pending LRM operation%s at %s",
counter, s_if_plural(counter), when);
if (cur_state == S_TERMINATE || !is_set(fsa_input_register, R_SENT_RSC_STOP)) {
g_hash_table_iter_init(&gIter, lrm_state->pending_ops);
while (g_hash_table_iter_next(&gIter, (gpointer*)&key, (gpointer*)&pending)) {
do_crm_log(log_level, "Pending action: %s (%s)", key, pending->op_key);
}
} else {
rc = FALSE;
}
return rc;
}
if (lrm_state->resource_history == NULL) {
return rc;
}
if (is_set(fsa_input_register, R_SHUTDOWN)) {
/* At this point we're not waiting, we're just shutting down */
when = "shutdown";
}
counter = 0;
g_hash_table_iter_init(&gIter, lrm_state->resource_history);
while (g_hash_table_iter_next(&gIter, NULL, (gpointer*)&entry)) {
if (is_rsc_active(lrm_state, entry->id) == FALSE) {
continue;
}
counter++;
if (log_level == LOG_ERR) {
crm_info("Found %s active at %s", entry->id, when);
} else {
crm_trace("Found %s active at %s", entry->id, when);
}
if (lrm_state->pending_ops) {
GHashTableIter hIter;
g_hash_table_iter_init(&hIter, lrm_state->pending_ops);
while (g_hash_table_iter_next(&hIter, (gpointer*)&key, (gpointer*)&pending)) {
if (crm_str_eq(entry->id, pending->rsc_id, TRUE)) {
crm_notice("%sction %s (%s) incomplete at %s",
pending->interval == 0 ? "A" : "Recurring a",
key, pending->op_key, when);
}
}
}
}
if (counter) {
crm_err("%d resource%s active at %s",
counter, (counter == 1)? " was" : "s were", when);
}
return rc;
}
static char *
-build_parameter_list(lrmd_event_data_t *op, xmlNode *metadata, xmlNode *result,
- const char *criteria, bool target, bool invert_for_xml)
+build_parameter_list(const lrmd_event_data_t *op,
+ const struct ra_metadata_s *metadata,
+ xmlNode *result, enum ra_param_flags_e param_type)
{
int len = 0;
int max = 0;
char *list = NULL;
+ GList *iter = NULL;
- xmlNode *param = NULL;
- xmlNode *params = NULL;
-
+ /* Newer resource agents support the "private" parameter attribute to
+ * indicate sensitive parameters. For backward compatibility with older
+ * agents, this list is used if the agent doesn't specify any as "private".
+ */
const char *secure_terms[] = {
"password",
"passwd",
"user",
};
- if(safe_str_eq("private", criteria)) {
- /* It will take time for the agents to be updated
- * Check for some common terms
- */
+ if (is_not_set(metadata->ra_flags, ra_uses_private)
+ && (param_type == ra_param_private)) {
+
max = DIMOF(secure_terms);
}
- params = find_xml_node(metadata, "parameters", TRUE);
- for (param = __xml_first_child(params); param != NULL; param = __xml_next(param)) {
- if (crm_str_eq((const char *)param->name, "parameter", TRUE)) {
- bool accept = FALSE;
- const char *name = crm_element_value(param, "name");
- const char *value = crm_element_value(param, criteria);
-
- if(max && value) {
- /* Turn off the compatibility logic once an agent has been updated to know about 'private' */
- max = 0;
- }
-
- if (name == NULL) {
- crm_err("Invalid parameter in %s metadata", op->rsc_id);
-
- } else if(target == crm_is_true(value)) {
- accept = TRUE;
-
- } else if(max) {
- int lpc = 0;
- bool found = FALSE;
+ for (iter = metadata->ra_params; iter != NULL; iter = iter->next) {
+ struct ra_param_s *param = (struct ra_param_s *) iter->data;
+ bool accept = FALSE;
- for(lpc = 0; found == FALSE && lpc < max; lpc++) {
- if(safe_str_eq(secure_terms[lpc], name)) {
- found = TRUE;
- }
- }
+ if (is_set(param->rap_flags, param_type)) {
+ accept = TRUE;
- if(found == target) {
+ } else if (max) {
+ for (int lpc = 0; lpc < max; lpc++) {
+ if (safe_str_eq(secure_terms[lpc], param->rap_name)) {
accept = TRUE;
+ break;
}
}
+ }
- if(accept) {
- int start = len;
-
- crm_trace("Attr %s is %s%s", name, target?"":"not ", criteria);
+ if (accept) {
+ int start = len;
- len += strlen(name) + 2;
- list = realloc_safe(list, len + 1);
- sprintf(list + start, " %s ", name);
+ crm_trace("Attr %s is %s", param->rap_name, ra_param_flag2text(param_type));
- } else {
- crm_trace("Rejecting %s for %s", name, criteria);
- }
+ len += strlen(param->rap_name) + 2; // include spaces around
+ list = realloc_safe(list, len + 1); // include null terminator
- if(invert_for_xml) {
- crm_trace("Inverting %s match for %s xml", name, criteria);
- accept = !accept;
- }
+ // spaces before and after make parsing simpler
+ sprintf(list + start, " %s ", param->rap_name);
- if(result && accept) {
- value = g_hash_table_lookup(op->params, name);
- if(value != NULL) {
- crm_trace("Adding attr %s=%s to the xml result", name, value);
- crm_xml_add(result, name, value);
- }
- }
+ } else {
+ crm_trace("Rejecting %s for %s", param->rap_name, ra_param_flag2text(param_type));
}
- }
- return list;
-}
-
-static bool
-resource_supports_action(xmlNode *metadata, const char *name)
-{
- const char *value = NULL;
-
- xmlNode *action = NULL;
- xmlNode *actions = NULL;
+ if (result && accept) {
+ const char *v = g_hash_table_lookup(op->params, param->rap_name);
- actions = find_xml_node(metadata, "actions", TRUE);
- for (action = __xml_first_child(actions); action != NULL; action = __xml_next(action)) {
- if (crm_str_eq((const char *)action->name, "action", TRUE)) {
- value = crm_element_value(action, "name");
- if (safe_str_eq(name, value)) {
- return TRUE;
+ if (v != NULL) {
+ crm_trace("Adding attr %s=%s to the xml result", param->rap_name, v);
+ crm_xml_add(result, param->rap_name, v);
}
}
}
- return FALSE;
+ return list;
}
static void
-append_restart_list(lrmd_event_data_t *op, xmlNode *metadata, xmlNode * update, const char *version)
+append_restart_list(lrmd_event_data_t *op, struct ra_metadata_s *metadata,
+ xmlNode *update, const char *version)
{
char *list = NULL;
char *digest = NULL;
xmlNode *restart = NULL;
CRM_LOG_ASSERT(op->params != NULL);
if (op->interval > 0) {
/* monitors are not reloadable */
return;
}
- if(resource_supports_action(metadata, "reload")) {
+ if (is_set(metadata->ra_flags, ra_supports_reload)) {
restart = create_xml_node(NULL, XML_TAG_PARAMS);
/* Any parameters with unique="1" should be added into the "op-force-restart" list. */
- list = build_parameter_list(op, metadata, restart, "unique", TRUE, FALSE);
+ list = build_parameter_list(op, metadata, restart, ra_param_reloadable);
} else {
/* Resource does not support reloads */
return;
}
digest = calculate_operation_digest(restart, version);
/* Add "op-force-restart" and "op-restart-digest" to indicate the resource supports reload,
* no matter if it actually supports any parameters with unique="1"). */
crm_xml_add(update, XML_LRM_ATTR_OP_RESTART, list? list: "");
crm_xml_add(update, XML_LRM_ATTR_RESTART_DIGEST, digest);
crm_trace("%s: %s, %s", op->rsc_id, digest, list);
crm_log_xml_trace(restart, "restart digest source");
free_xml(restart);
free(digest);
free(list);
}
static void
-append_secure_list(lrmd_event_data_t *op, xmlNode *metadata, xmlNode * update, const char *version)
+append_secure_list(lrmd_event_data_t *op, struct ra_metadata_s *metadata,
+ xmlNode *update, const char *version)
{
char *list = NULL;
char *digest = NULL;
xmlNode *secure = NULL;
CRM_LOG_ASSERT(op->params != NULL);
/*
* To keep XML_LRM_ATTR_OP_SECURE short, we want it to contain the
* secure parameters but XML_LRM_ATTR_SECURE_DIGEST to be based on
* the insecure ones
*/
secure = create_xml_node(NULL, XML_TAG_PARAMS);
- list = build_parameter_list(op, metadata, secure, "private", TRUE, TRUE);
+ list = build_parameter_list(op, metadata, secure, ra_param_private);
if (list != NULL) {
digest = calculate_operation_digest(secure, version);
crm_xml_add(update, XML_LRM_ATTR_OP_SECURE, list);
crm_xml_add(update, XML_LRM_ATTR_SECURE_DIGEST, digest);
crm_trace("%s: %s, %s", op->rsc_id, digest, list);
crm_log_xml_trace(secure, "secure digest source");
} else {
crm_trace("%s: no secure parameters", op->rsc_id);
}
free_xml(secure);
free(digest);
free(list);
}
-regex_t *version_format_regex = NULL;
-
-static gboolean
-valid_version_format(const char *version)
-{
- if (version == NULL) {
- return FALSE;
- }
-
- if (version_format_regex == NULL) {
- const char *regex_string = "^[[:digit:]]+([.][[:digit:]]+)*$";
- version_format_regex = calloc(1, sizeof(regex_t));
- regcomp(version_format_regex, regex_string, REG_EXTENDED | REG_NOSUB);
-
- CRM_CHECK(version_format_regex != NULL, return TRUE);
- }
-
- return regexec(version_format_regex, version, 0, NULL, 0) == 0;
-}
-
-static const char*
-get_ra_version(lrmd_rsc_info_t *rsc, xmlNode *metadata)
-{
- static const char *default_version = "0.1";
- const char *version = NULL;
- xmlNode *version_xml = NULL;
-
- if (!metadata) {
- return default_version;
- }
-
- version_xml = get_xpath_object("/resource-agent/@version", metadata, LOG_TRACE);
- if (!version_xml) {
- if (rsc) {
- crm_debug("Resource agent %s:%s:%s (for %s) doesn't specify a version number",
- rsc->class, rsc->provider, rsc->type, rsc->id);
- }
- return default_version;
- }
-
- version = crm_element_value(version_xml, XML_ATTR_VERSION);
- if (!valid_version_format(version)) {
- if (rsc) {
- crm_notice("Resource agent version for %s (%s:%s:%s) has unrecognized format",
- rsc->id, rsc->class, rsc->provider, rsc->type);
- }
- return default_version;
- }
-
- if (rsc) {
- crm_trace("Resource agent version for %s (%s:%s:%s) is %s",
- rsc->id, rsc->class, rsc->provider, rsc->type, version);
- }
-
- return version;
-}
-
static gboolean
build_operation_update(xmlNode * parent, lrmd_rsc_info_t * rsc, lrmd_event_data_t * op,
const char *node_name, const char *src)
{
int target_rc = 0;
xmlNode *xml_op = NULL;
- xmlNode *metadata = NULL;
- const char *ra_version = NULL;
+ struct ra_metadata_s *metadata = NULL;
const char *caller_version = NULL;
lrm_state_t *lrm_state = NULL;
if (op == NULL) {
return FALSE;
}
target_rc = rsc_op_expected_rc(op);
/* there is a small risk in formerly mixed clusters that it will
* be sub-optimal.
*
* however with our upgrade policy, the update we send should
* still be completely supported anyway
*/
caller_version = g_hash_table_lookup(op->params, XML_ATTR_CRM_VERSION);
CRM_LOG_ASSERT(caller_version != NULL);
if(caller_version == NULL) {
caller_version = CRM_FEATURE_SET;
}
crm_trace("Building %s operation update with originator version: %s", op->rsc_id, caller_version);
xml_op = create_operation_update(parent, op, caller_version, target_rc, fsa_our_uname, src, LOG_DEBUG);
if (xml_op == NULL) {
return TRUE;
}
- if (rsc == NULL || op->params == NULL || crm_str_eq(CRMD_ACTION_STOP, op->op_type, TRUE) ||
- safe_str_eq(op->op_type, CRMD_ACTION_METADATA)) {
- /* Stopped resources don't need the digest logic */
- /* As well as meta-data query results */
- crm_trace("No digests needed for %s %p %p %s", op->rsc_id, op->params, rsc, op->op_type);
+ if ((rsc == NULL) || (op == NULL) || (op->params == NULL)
+ || !crm_op_needs_metadata(rsc->class, op->op_type)) {
+
+ crm_trace("No digests needed for %s action on %s (params=%p rsc=%p)",
+ op->op_type, op->rsc_id, op->params, rsc);
return TRUE;
}
lrm_state = lrm_state_find(node_name);
if (lrm_state == NULL) {
crm_warn("Cannot calculate digests for operation %s_%s_%d because we have no LRM connection to %s",
op->rsc_id, op->op_type, op->interval, node_name);
return TRUE;
}
- metadata = lrm_state_get_rsc_metadata(lrm_state, rsc);
- if (metadata == NULL && lrm_state_is_local(lrm_state)) {
- char *metadata_str = NULL;
- int rc = lrm_state_get_metadata(lrm_state, rsc->class, rsc->provider, rsc->type, &metadata_str, 0);
+ metadata = metadata_cache_get(lrm_state->metadata_cache, rsc);
+ if (metadata == NULL) {
+ if (lrm_state_is_local(lrm_state)) {
+ char *metadata_str = NULL;
+
+ /* Do a synchronous local execution for local agents.
+ * TODO: We really should do async via lrmd.
+ */
+ int rc = lrm_state_get_metadata(lrm_state, rsc->class,
+ rsc->provider, rsc->type,
+ &metadata_str, 0);
+
+ if (rc != pcmk_ok) {
+ crm_warn("Failed to get metadata for %s (%s:%s:%s)",
+ rsc->id, rsc->class, rsc->provider, rsc->type);
+ return TRUE;
+ }
- if (rc != pcmk_ok) {
- crm_warn("Cannot get metadata for %s (%s:%s:%s)", rsc->id, rsc->class, rsc->provider, rsc->type);
+ metadata = metadata_cache_update(lrm_state->metadata_cache, rsc, metadata_str);
+ free(metadata_str);
+ if (metadata == NULL) {
+ crm_warn("Failed to update metadata for %s (%s:%s:%s)",
+ rsc->id, rsc->class, rsc->provider, rsc->type);
+ return TRUE;
+ }
+ } else {
+ /* TODO Do async execution via lrmd */
+ crm_warn("Cannot get remote metadata for %s (%s:%s:%s)",
+ rsc->id, rsc->class, rsc->provider, rsc->type);
return TRUE;
}
-
- metadata = lrm_state_update_rsc_metadata(lrm_state, rsc, metadata_str);
- free(metadata_str);
}
- if (metadata == NULL) {
- crm_warn("Cannot update metadata for %s (%s:%s:%s)", rsc->id, rsc->class, rsc->provider, rsc->type);
- return TRUE;
- }
-
- ra_version = get_ra_version(rsc, metadata);
- crm_xml_add(xml_op, XML_ATTR_RA_VERSION, ra_version);
+ crm_xml_add(xml_op, XML_ATTR_RA_VERSION, metadata->ra_version);
crm_trace("Including additional digests for %s::%s:%s", rsc->class, rsc->provider, rsc->type);
append_restart_list(op, metadata, xml_op, caller_version);
append_secure_list(op, metadata, xml_op, caller_version);
return TRUE;
}
static gboolean
is_rsc_active(lrm_state_t * lrm_state, const char *rsc_id)
{
rsc_history_t *entry = NULL;
entry = g_hash_table_lookup(lrm_state->resource_history, rsc_id);
if (entry == NULL || entry->last == NULL) {
return FALSE;
}
crm_trace("Processing %s: %s.%d=%d",
rsc_id, entry->last->op_type, entry->last->interval, entry->last->rc);
if (entry->last->rc == PCMK_OCF_OK && safe_str_eq(entry->last->op_type, CRMD_ACTION_STOP)) {
return FALSE;
} else if (entry->last->rc == PCMK_OCF_OK
&& safe_str_eq(entry->last->op_type, CRMD_ACTION_MIGRATE)) {
/* a stricter check is too complex...
* leave that to the PE
*/
return FALSE;
} else if (entry->last->rc == PCMK_OCF_NOT_RUNNING) {
return FALSE;
} else if (entry->last->interval == 0 && entry->last->rc == PCMK_OCF_NOT_CONFIGURED) {
/* Badly configured resources can't be reliably stopped */
return FALSE;
}
return TRUE;
}
static gboolean
build_active_RAs(lrm_state_t * lrm_state, xmlNode * rsc_list)
{
GHashTableIter iter;
rsc_history_t *entry = NULL;
g_hash_table_iter_init(&iter, lrm_state->resource_history);
while (g_hash_table_iter_next(&iter, NULL, (void **)&entry)) {
GList *gIter = NULL;
xmlNode *xml_rsc = create_xml_node(rsc_list, XML_LRM_TAG_RESOURCE);
crm_xml_add(xml_rsc, XML_ATTR_ID, entry->id);
crm_xml_add(xml_rsc, XML_ATTR_TYPE, entry->rsc.type);
crm_xml_add(xml_rsc, XML_AGENT_ATTR_CLASS, entry->rsc.class);
crm_xml_add(xml_rsc, XML_AGENT_ATTR_PROVIDER, entry->rsc.provider);
if (entry->last && entry->last->params) {
const char *container = g_hash_table_lookup(entry->last->params, CRM_META"_"XML_RSC_ATTR_CONTAINER);
if (container) {
crm_trace("Resource %s is a part of container resource %s", entry->id, container);
crm_xml_add(xml_rsc, XML_RSC_ATTR_CONTAINER, container);
}
}
build_operation_update(xml_rsc, &(entry->rsc), entry->failed, lrm_state->node_name, __FUNCTION__);
build_operation_update(xml_rsc, &(entry->rsc), entry->last, lrm_state->node_name, __FUNCTION__);
for (gIter = entry->recurring_op_list; gIter != NULL; gIter = gIter->next) {
build_operation_update(xml_rsc, &(entry->rsc), gIter->data, lrm_state->node_name, __FUNCTION__);
}
}
return FALSE;
}
static xmlNode *
do_lrm_query_internal(lrm_state_t *lrm_state, int update_flags)
{
xmlNode *xml_state = NULL;
xmlNode *xml_data = NULL;
xmlNode *rsc_list = NULL;
crm_node_t *peer = NULL;
peer = crm_get_peer_full(0, lrm_state->node_name, CRM_GET_PEER_ANY);
CRM_CHECK(peer != NULL, return NULL);
xml_state = create_node_state_update(peer, update_flags, NULL,
__FUNCTION__);
xml_data = create_xml_node(xml_state, XML_CIB_TAG_LRM);
crm_xml_add(xml_data, XML_ATTR_ID, peer->uuid);
rsc_list = create_xml_node(xml_data, XML_LRM_TAG_RESOURCES);
/* Build a list of active (not always running) resources */
build_active_RAs(lrm_state, rsc_list);
crm_log_xml_trace(xml_state, "Current state of the LRM");
return xml_state;
}
xmlNode *
do_lrm_query(gboolean is_replace, const char *node_name)
{
lrm_state_t *lrm_state = lrm_state_find(node_name);
xmlNode *xml_state;
if (!lrm_state) {
crm_err("Could not query lrm state for lrmd node %s", node_name);
return NULL;
}
xml_state = do_lrm_query_internal(lrm_state,
node_update_cluster|node_update_peer);
/* In case this function is called to generate a join confirmation to
* send to the DC, force the current and expected join state to member.
* This isn't necessary for newer DCs but is backward compatible.
*/
crm_xml_add(xml_state, XML_NODE_JOIN_STATE, CRMD_JOINSTATE_MEMBER);
crm_xml_add(xml_state, XML_NODE_EXPECTED, CRMD_JOINSTATE_MEMBER);
return xml_state;
}
static void
notify_deleted(lrm_state_t * lrm_state, ha_msg_input_t * input, const char *rsc_id, int rc)
{
lrmd_event_data_t *op = NULL;
const char *from_sys = crm_element_value(input->msg, F_CRM_SYS_FROM);
const char *from_host = crm_element_value(input->msg, F_CRM_HOST_FROM);
crm_info("Notifying %s on %s that %s was%s deleted",
from_sys, (from_host? from_host : "localhost"), rsc_id,
((rc == pcmk_ok)? "" : " not"));
op = construct_op(lrm_state, input->xml, rsc_id, CRMD_ACTION_DELETE);
CRM_ASSERT(op != NULL);
if (rc == pcmk_ok) {
op->op_status = PCMK_LRM_OP_DONE;
op->rc = PCMK_OCF_OK;
} else {
op->op_status = PCMK_LRM_OP_ERROR;
op->rc = PCMK_OCF_UNKNOWN_ERROR;
}
send_direct_ack(from_host, from_sys, NULL, op, rsc_id);
lrmd_free_event(op);
if (safe_str_neq(from_sys, CRM_SYSTEM_TENGINE)) {
/* this isn't expected - trigger a new transition */
time_t now = time(NULL);
char *now_s = crm_itoa(now);
crm_debug("Triggering a refresh after %s deleted %s from the LRM", from_sys, rsc_id);
update_attr_delegate(fsa_cib_conn, cib_none, XML_CIB_TAG_CRMCONFIG, NULL, NULL, NULL, NULL,
"last-lrm-refresh", now_s, FALSE, NULL, NULL);
free(now_s);
}
}
static gboolean
lrm_remove_deleted_rsc(gpointer key, gpointer value, gpointer user_data)
{
struct delete_event_s *event = user_data;
struct pending_deletion_op_s *op = value;
if (crm_str_eq(event->rsc, op->rsc, TRUE)) {
notify_deleted(event->lrm_state, op->input, event->rsc, event->rc);
return TRUE;
}
return FALSE;
}
static gboolean
lrm_remove_deleted_op(gpointer key, gpointer value, gpointer user_data)
{
const char *rsc = user_data;
struct recurring_op_s *pending = value;
if (crm_str_eq(rsc, pending->rsc_id, TRUE)) {
crm_info("Removing op %s:%d for deleted resource %s",
pending->op_key, pending->call_id, rsc);
return TRUE;
}
return FALSE;
}
/*
* Remove the rsc from the CIB
*
* Avoids refreshing the entire LRM section of this host
*/
#define rsc_template "//"XML_CIB_TAG_STATE"[@uname='%s']//"XML_LRM_TAG_RESOURCE"[@id='%s']"
static int
delete_rsc_status(lrm_state_t * lrm_state, const char *rsc_id, int call_options,
const char *user_name)
{
char *rsc_xpath = NULL;
int max = 0;
int rc = pcmk_ok;
CRM_CHECK(rsc_id != NULL, return -ENXIO);
max = strlen(rsc_template) + strlen(rsc_id) + strlen(lrm_state->node_name) + 1;
rsc_xpath = calloc(1, max);
snprintf(rsc_xpath, max, rsc_template, lrm_state->node_name, rsc_id);
rc = cib_internal_op(fsa_cib_conn, CIB_OP_DELETE, NULL, rsc_xpath,
NULL, NULL, call_options | cib_xpath, user_name);
free(rsc_xpath);
return rc;
}
static void
delete_rsc_entry(lrm_state_t * lrm_state, ha_msg_input_t * input, const char *rsc_id,
GHashTableIter * rsc_gIter, int rc, const char *user_name)
{
struct delete_event_s event;
CRM_CHECK(rsc_id != NULL, return);
if (rc == pcmk_ok) {
char *rsc_id_copy = strdup(rsc_id);
if (rsc_gIter)
g_hash_table_iter_remove(rsc_gIter);
else
g_hash_table_remove(lrm_state->resource_history, rsc_id_copy);
crm_debug("sync: Sending delete op for %s", rsc_id_copy);
delete_rsc_status(lrm_state, rsc_id_copy, cib_quorum_override, user_name);
g_hash_table_foreach_remove(lrm_state->pending_ops, lrm_remove_deleted_op, rsc_id_copy);
free(rsc_id_copy);
}
if (input) {
notify_deleted(lrm_state, input, rsc_id, rc);
}
event.rc = rc;
event.rsc = rsc_id;
event.lrm_state = lrm_state;
g_hash_table_foreach_remove(lrm_state->deletion_ops, lrm_remove_deleted_rsc, &event);
}
/*!
* \internal
* \brief Erase an LRM history entry from the CIB, given the operation data
*
* \param[in] lrm_state LRM state of the desired node
* \param[in] op Operation whose history should be deleted
*/
static void
erase_lrm_history_by_op(lrm_state_t *lrm_state, lrmd_event_data_t *op)
{
xmlNode *xml_top = NULL;
CRM_CHECK(op != NULL, return);
xml_top = create_xml_node(NULL, XML_LRM_TAG_RSC_OP);
crm_xml_add_int(xml_top, XML_LRM_ATTR_CALLID, op->call_id);
crm_xml_add(xml_top, XML_ATTR_TRANSITION_KEY, op->user_data);
if (op->interval > 0) {
char *op_id = generate_op_key(op->rsc_id, op->op_type, op->interval);
/* Avoid deleting last_failure too (if it was a result of this recurring op failing) */
crm_xml_add(xml_top, XML_ATTR_ID, op_id);
free(op_id);
}
crm_debug("Erasing LRM resource history for %s_%s_%d (call=%d)",
op->rsc_id, op->op_type, op->interval, op->call_id);
fsa_cib_conn->cmds->delete(fsa_cib_conn, XML_CIB_TAG_STATUS, xml_top,
cib_quorum_override);
crm_log_xml_trace(xml_top, "op:cancel");
free_xml(xml_top);
}
/* Define xpath to find LRM resource history entry by node and resource */
#define XPATH_HISTORY \
"/" XML_TAG_CIB "/" XML_CIB_TAG_STATUS \
"/" XML_CIB_TAG_STATE "[@" XML_ATTR_UNAME "='%s']" \
"/" XML_CIB_TAG_LRM "/" XML_LRM_TAG_RESOURCES \
"/" XML_LRM_TAG_RESOURCE "[@" XML_ATTR_ID "='%s']" \
"/" XML_LRM_TAG_RSC_OP
/* ... and also by operation key */
#define XPATH_HISTORY_ID XPATH_HISTORY \
"[@" XML_ATTR_ID "='%s']"
/* ... and also by operation key and operation call ID */
#define XPATH_HISTORY_CALL XPATH_HISTORY \
"[@" XML_ATTR_ID "='%s' and @" XML_LRM_ATTR_CALLID "='%d']"
/* ... and also by operation key and original operation key */
#define XPATH_HISTORY_ORIG XPATH_HISTORY \
"[@" XML_ATTR_ID "='%s' and @" XML_LRM_ATTR_TASK_KEY "='%s']"
/*!
* \internal
* \brief Erase an LRM history entry from the CIB, given operation identifiers
*
* \param[in] lrm_state LRM state of the node to clear history for
* \param[in] rsc_id Name of resource to clear history for
* \param[in] key Operation key of operation to clear history for
* \param[in] orig_op If specified, delete only if it has this original op
* \param[in] call_id If specified, delete entry only if it has this call ID
*/
static void
erase_lrm_history_by_id(lrm_state_t *lrm_state, const char *rsc_id,
const char *key, const char *orig_op, int call_id)
{
char *op_xpath = NULL;
CRM_CHECK((rsc_id != NULL) && (key != NULL), return);
if (call_id > 0) {
op_xpath = crm_strdup_printf(XPATH_HISTORY_CALL,
lrm_state->node_name, rsc_id, key,
call_id);
} else if (orig_op) {
op_xpath = crm_strdup_printf(XPATH_HISTORY_ORIG,
lrm_state->node_name, rsc_id, key,
orig_op);
} else {
op_xpath = crm_strdup_printf(XPATH_HISTORY_ID,
lrm_state->node_name, rsc_id, key);
}
crm_debug("Erasing LRM resource history for %s on %s (call=%d)",
key, rsc_id, call_id);
fsa_cib_conn->cmds->delete(fsa_cib_conn, op_xpath, NULL,
cib_quorum_override | cib_xpath);
free(op_xpath);
}
static inline gboolean
last_failed_matches_op(rsc_history_t *entry, const char *op, int interval)
{
if (entry == NULL) {
return FALSE;
}
if (op == NULL) {
return TRUE;
}
return (safe_str_eq(op, entry->failed->op_type)
&& (interval == entry->failed->interval));
}
/*!
* \internal
* \brief Clear a resource's last failure
*
* Erase a resource's last failure on a particular node from both the
* LRM resource history in the CIB, and the resource history remembered
* for the LRM state.
*
* \param[in] rsc_id Resource name
* \param[in] node_name Node name
* \param[in] operation If specified, only clear if matching this operation
* \param[in] interval If operation is specified, it has this interval in ms
*/
void
lrm_clear_last_failure(const char *rsc_id, const char *node_name,
const char *operation, int interval)
{
char *op_key = NULL;
char *orig_op_key = NULL;
lrm_state_t *lrm_state = NULL;
lrm_state = lrm_state_find(node_name);
if (lrm_state == NULL) {
return;
}
/* Erase from CIB */
op_key = generate_op_key(rsc_id, "last_failure", 0);
if (operation) {
orig_op_key = generate_op_key(rsc_id, operation, interval);
}
erase_lrm_history_by_id(lrm_state, rsc_id, op_key, orig_op_key, 0);
free(op_key);
free(orig_op_key);
/* Remove from memory */
if (lrm_state->resource_history) {
rsc_history_t *entry = g_hash_table_lookup(lrm_state->resource_history,
rsc_id);
if (last_failed_matches_op(entry, operation, interval)) {
lrmd_free_event(entry->failed);
entry->failed = NULL;
}
}
}
/* Returns: gboolean - cancellation is in progress */
static gboolean
cancel_op(lrm_state_t * lrm_state, const char *rsc_id, const char *key, int op, gboolean remove)
{
int rc = pcmk_ok;
char *local_key = NULL;
struct recurring_op_s *pending = NULL;
CRM_CHECK(op != 0, return FALSE);
CRM_CHECK(rsc_id != NULL, return FALSE);
if (key == NULL) {
local_key = make_stop_id(rsc_id, op);
key = local_key;
}
pending = g_hash_table_lookup(lrm_state->pending_ops, key);
if (pending) {
if (remove && pending->remove == FALSE) {
pending->remove = TRUE;
crm_debug("Scheduling %s for removal", key);
}
if (pending->cancelled) {
crm_debug("Operation %s already cancelled", key);
free(local_key);
return FALSE;
}
pending->cancelled = TRUE;
} else {
crm_info("No pending op found for %s", key);
free(local_key);
return FALSE;
}
crm_debug("Cancelling op %d for %s (%s)", op, rsc_id, key);
rc = lrm_state_cancel(lrm_state, pending->rsc_id, pending->op_type, pending->interval);
if (rc == pcmk_ok) {
crm_debug("Op %d for %s (%s): cancelled", op, rsc_id, key);
free(local_key);
return TRUE;
}
crm_debug("Op %d for %s (%s): Nothing to cancel", op, rsc_id, key);
/* The caller needs to make sure the entry is
* removed from the pending_ops list
*
* Usually by returning TRUE inside the worker function
* supplied to g_hash_table_foreach_remove()
*
* Not removing the entry from pending_ops will block
* the node from shutting down
*/
free(local_key);
return FALSE;
}
struct cancel_data {
gboolean done;
gboolean remove;
const char *key;
lrmd_rsc_info_t *rsc;
lrm_state_t *lrm_state;
};
static gboolean
cancel_action_by_key(gpointer key, gpointer value, gpointer user_data)
{
gboolean remove = FALSE;
struct cancel_data *data = user_data;
struct recurring_op_s *op = (struct recurring_op_s *)value;
if (crm_str_eq(op->op_key, data->key, TRUE)) {
data->done = TRUE;
remove = !cancel_op(data->lrm_state, data->rsc->id, key, op->call_id, data->remove);
}
return remove;
}
static gboolean
cancel_op_key(lrm_state_t * lrm_state, lrmd_rsc_info_t * rsc, const char *key, gboolean remove)
{
guint removed = 0;
struct cancel_data data;
CRM_CHECK(rsc != NULL, return FALSE);
CRM_CHECK(key != NULL, return FALSE);
data.key = key;
data.rsc = rsc;
data.done = FALSE;
data.remove = remove;
data.lrm_state = lrm_state;
removed = g_hash_table_foreach_remove(lrm_state->pending_ops, cancel_action_by_key, &data);
crm_trace("Removed %u op cache entries, new size: %u",
removed, g_hash_table_size(lrm_state->pending_ops));
return data.done;
}
static lrmd_rsc_info_t *
get_lrm_resource(lrm_state_t * lrm_state, xmlNode * resource, xmlNode * op_msg, gboolean do_create)
{
lrmd_rsc_info_t *rsc = NULL;
const char *id = ID(resource);
const char *type = crm_element_value(resource, XML_ATTR_TYPE);
const char *class = crm_element_value(resource, XML_AGENT_ATTR_CLASS);
const char *provider = crm_element_value(resource, XML_AGENT_ATTR_PROVIDER);
const char *long_id = crm_element_value(resource, XML_ATTR_ID_LONG);
crm_trace("Retrieving %s from the LRM.", id);
CRM_CHECK(id != NULL, return NULL);
rsc = lrm_state_get_rsc_info(lrm_state, id, 0);
if (!rsc && long_id) {
rsc = lrm_state_get_rsc_info(lrm_state, long_id, 0);
}
if (!rsc && do_create) {
CRM_CHECK(class != NULL, return NULL);
CRM_CHECK(type != NULL, return NULL);
crm_trace("Adding rsc %s before operation", id);
lrm_state_register_rsc(lrm_state, id, class, provider, type, lrmd_opt_drop_recurring);
rsc = lrm_state_get_rsc_info(lrm_state, id, 0);
if (!rsc) {
fsa_data_t *msg_data = NULL;
crm_err("Could not add resource %s to LRM %s", id, lrm_state->node_name);
/* only register this as a internal error if this involves the local
* lrmd. Otherwise we're likely dealing with an unresponsive remote-node
* which is not a FSA failure. */
if (lrm_state_is_local(lrm_state) == TRUE) {
register_fsa_error(C_FSA_INTERNAL, I_FAIL, NULL);
}
}
}
return rsc;
}
static void
delete_resource(lrm_state_t * lrm_state,
const char *id,
lrmd_rsc_info_t * rsc,
GHashTableIter * gIter,
const char *sys,
const char *host,
const char *user,
ha_msg_input_t * request,
gboolean unregister)
{
int rc = pcmk_ok;
crm_info("Removing resource %s for %s (%s) on %s", id, sys, user ? user : "internal", host);
if (rsc && unregister) {
rc = lrm_state_unregister_rsc(lrm_state, id, 0);
}
if (rc == pcmk_ok) {
crm_trace("Resource '%s' deleted", id);
} else if (rc == -EINPROGRESS) {
crm_info("Deletion of resource '%s' pending", id);
if (request) {
struct pending_deletion_op_s *op = NULL;
char *ref = crm_element_value_copy(request->msg, XML_ATTR_REFERENCE);
op = calloc(1, sizeof(struct pending_deletion_op_s));
op->rsc = strdup(rsc->id);
op->input = copy_ha_msg_input(request);
g_hash_table_insert(lrm_state->deletion_ops, ref, op);
}
return;
} else {
crm_warn("Deletion of resource '%s' for %s (%s) on %s failed: %d",
id, sys, user ? user : "internal", host, rc);
}
delete_rsc_entry(lrm_state, request, id, gIter, rc, user);
}
static int
get_fake_call_id(lrm_state_t *lrm_state, const char *rsc_id)
{
int call_id = 999999999;
rsc_history_t *entry = NULL;
if(lrm_state) {
entry = g_hash_table_lookup(lrm_state->resource_history, rsc_id);
}
/* Make sure the call id is greater than the last successful operation,
* otherwise the failure will not result in a possible recovery of the resource
* as it could appear the failure occurred before the successful start */
if (entry) {
call_id = entry->last_callid + 1;
}
if (call_id < 0) {
call_id = 1;
}
return call_id;
}
static void
force_reprobe(lrm_state_t *lrm_state, const char *from_sys, const char *from_host, const char *user_name, gboolean is_remote_node)
{
GHashTableIter gIter;
rsc_history_t *entry = NULL;
crm_info("clearing resource history on node %s", lrm_state->node_name);
g_hash_table_iter_init(&gIter, lrm_state->resource_history);
while (g_hash_table_iter_next(&gIter, NULL, (void **)&entry)) {
/* only unregister the resource during a reprobe if it is not a remote connection
* resource. otherwise unregistering the connection will terminate remote-node
* membership */
gboolean unregister = TRUE;
if (is_remote_lrmd_ra(NULL, NULL, entry->id)) {
lrm_state_t *remote_lrm_state = lrm_state_find(entry->id);
if (remote_lrm_state) {
/* when forcing a reprobe, make sure to clear remote node before
* clearing the remote node's connection resource */
force_reprobe(remote_lrm_state, from_sys, from_host, user_name, TRUE);
}
unregister = FALSE;
}
delete_resource(lrm_state, entry->id, &entry->rsc, &gIter, from_sys, from_host,
user_name, NULL, unregister);
}
/* Now delete the copy in the CIB */
erase_status_tag(lrm_state->node_name, XML_CIB_TAG_LRM, cib_scope_local);
/* And finally, _delete_ the value in attrd
* Setting it to FALSE results in the PE sending us back here again
*/
update_attrd(lrm_state->node_name, CRM_OP_PROBED, NULL, user_name, is_remote_node);
}
static void
synthesize_lrmd_failure(lrm_state_t *lrm_state, xmlNode *action, int rc)
{
lrmd_event_data_t *op = NULL;
const char *operation = crm_element_value(action, XML_LRM_ATTR_TASK);
const char *target_node = crm_element_value(action, XML_LRM_ATTR_TARGET);
xmlNode *xml_rsc = find_xml_node(action, XML_CIB_TAG_RESOURCE, TRUE);
if(xml_rsc == NULL) {
/* @TODO Should we do something else, like direct ack? */
crm_info("Skipping %s=%d on %s (%p): no resource",
crm_element_value(action, XML_LRM_ATTR_TASK_KEY), rc, target_node, lrm_state);
return;
} else if(operation == NULL) {
/* This probably came from crm_resource -C, nothing to do */
crm_info("Skipping %s=%d on %s (%p): no operation",
crm_element_value(action, XML_ATTR_TRANSITION_KEY), rc, target_node, lrm_state);
return;
}
op = construct_op(lrm_state, action, ID(xml_rsc), operation);
CRM_ASSERT(op != NULL);
op->call_id = get_fake_call_id(lrm_state, op->rsc_id);
if(safe_str_eq(operation, RSC_NOTIFY)) {
/* Notifications can't fail yet */
op->op_status = PCMK_LRM_OP_DONE;
op->rc = PCMK_OCF_OK;
} else {
op->op_status = PCMK_LRM_OP_ERROR;
op->rc = rc;
}
op->t_run = time(NULL);
op->t_rcchange = op->t_run;
crm_info("Faking result %d for %s_%s_%d on %s (%p)", op->rc, op->rsc_id, op->op_type, op->interval, target_node, lrm_state);
if(lrm_state) {
process_lrm_event(lrm_state, op, NULL);
} else {
lrmd_rsc_info_t rsc;
rsc.id = strdup(op->rsc_id);
rsc.type = crm_element_value_copy(xml_rsc, XML_ATTR_TYPE);
rsc.class = crm_element_value_copy(xml_rsc, XML_AGENT_ATTR_CLASS);
rsc.provider = crm_element_value_copy(xml_rsc, XML_AGENT_ATTR_PROVIDER);
do_update_resource(target_node, &rsc, op);
free(rsc.id);
free(rsc.type);
free(rsc.class);
free(rsc.provider);
}
lrmd_free_event(op);
}
/* A_LRM_INVOKE */
void
do_lrm_invoke(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)
{
gboolean create_rsc = TRUE;
lrm_state_t *lrm_state = NULL;
const char *crm_op = NULL;
const char *from_sys = NULL;
const char *from_host = NULL;
const char *operation = NULL;
ha_msg_input_t *input = fsa_typed_data(fsa_dt_ha_msg);
const char *user_name = NULL;
const char *target_node = NULL;
gboolean is_remote_node = FALSE;
gboolean crm_rsc_delete = FALSE;
if (input->xml != NULL) {
/* Remote node operations are routed here to their remote connections */
target_node = crm_element_value(input->xml, XML_LRM_ATTR_TARGET);
}
if (target_node == NULL) {
target_node = fsa_our_uname;
} else if (safe_str_neq(target_node, fsa_our_uname)) {
is_remote_node = TRUE;
}
lrm_state = lrm_state_find(target_node);
if (lrm_state == NULL && is_remote_node) {
crm_err("Failing action because remote node %s has no connection to cluster node %s",
target_node, fsa_our_uname);
/* The action must be recorded here and in the CIB as failed */
synthesize_lrmd_failure(NULL, input->xml, PCMK_OCF_CONNECTION_DIED);
return;
}
CRM_ASSERT(lrm_state != NULL);
#if ENABLE_ACL
user_name = crm_acl_get_set_user(input->msg, F_CRM_USER, NULL);
crm_trace("LRM command from user '%s'", user_name);
#endif
crm_op = crm_element_value(input->msg, F_CRM_TASK);
from_sys = crm_element_value(input->msg, F_CRM_SYS_FROM);
if (safe_str_neq(from_sys, CRM_SYSTEM_TENGINE)) {
from_host = crm_element_value(input->msg, F_CRM_HOST_FROM);
}
crm_trace("LRM command from: %s", from_sys);
if (safe_str_eq(crm_op, CRM_OP_LRM_DELETE)) {
/* remember this delete op came from crm_resource */
crm_rsc_delete = TRUE;
operation = CRMD_ACTION_DELETE;
} else if (safe_str_eq(crm_op, CRM_OP_LRM_REFRESH)) {
operation = CRM_OP_LRM_REFRESH;
} else if (safe_str_eq(crm_op, CRM_OP_LRM_FAIL)) {
lrmd_event_data_t *op = NULL;
lrmd_rsc_info_t *rsc = NULL;
xmlNode *xml_rsc = find_xml_node(input->xml, XML_CIB_TAG_RESOURCE, TRUE);
CRM_CHECK(xml_rsc != NULL, return);
/* The lrmd can not fail a resource, it does not understand the
* concept of success or failure in relation to a resource, it simply
* executes operations and reports the results. We determine what a failure is.
* Because of this, if we want to fail a resource we have to fake what we
* understand a failure to look like.
*
* To do this we create a fake lrmd operation event for the resource
* we want to fail. We then pass that event to the lrmd client callback
* so it will be processed as if it actually came from the lrmd. */
op = construct_op(lrm_state, input->xml, ID(xml_rsc), "asyncmon");
CRM_ASSERT(op != NULL);
free((char *)op->user_data);
op->user_data = NULL;
op->call_id = get_fake_call_id(lrm_state, op->rsc_id);
op->interval = 0;
op->op_status = PCMK_LRM_OP_DONE;
op->rc = PCMK_OCF_UNKNOWN_ERROR;
op->t_run = time(NULL);
op->t_rcchange = op->t_run;
#if ENABLE_ACL
if (user_name && is_privileged(user_name) == FALSE) {
crm_err("%s does not have permission to fail %s", user_name, ID(xml_rsc));
send_direct_ack(from_host, from_sys, NULL, op, ID(xml_rsc));
lrmd_free_event(op);
return;
}
#endif
rsc = get_lrm_resource(lrm_state, xml_rsc, input->xml, create_rsc);
if (rsc) {
crm_info("Failing resource %s...", rsc->id);
process_lrm_event(lrm_state, op, NULL);
op->op_status = PCMK_LRM_OP_DONE;
op->rc = PCMK_OCF_OK;
lrmd_free_rsc_info(rsc);
} else {
crm_info("Cannot find/create resource in order to fail it...");
crm_log_xml_warn(input->msg, "bad input");
}
send_direct_ack(from_host, from_sys, NULL, op, ID(xml_rsc));
lrmd_free_event(op);
return;
} else if (input->xml != NULL) {
operation = crm_element_value(input->xml, XML_LRM_ATTR_TASK);
}
if (safe_str_eq(crm_op, CRM_OP_LRM_REFRESH)) {
int rc = pcmk_ok;
xmlNode *fragment = do_lrm_query_internal(lrm_state, node_update_all);
fsa_cib_update(XML_CIB_TAG_STATUS, fragment, cib_quorum_override, rc, user_name);
crm_info("Forced a local LRM refresh: call=%d", rc);
if (safe_str_neq(CRM_SYSTEM_CRMD, from_sys)) {
xmlNode *reply = create_request(
CRM_OP_INVOKE_LRM, fragment,
from_host, from_sys, CRM_SYSTEM_LRMD, fsa_our_uuid);
crm_debug("ACK'ing refresh from %s (%s)", from_sys, from_host);
if (relay_message(reply, TRUE) == FALSE) {
crm_log_xml_err(reply, "Unable to route reply");
}
free_xml(reply);
}
free_xml(fragment);
} else if (safe_str_eq(crm_op, CRM_OP_LRM_QUERY)) {
xmlNode *data = do_lrm_query_internal(lrm_state, node_update_all);
xmlNode *reply = create_reply(input->msg, data);
if (relay_message(reply, TRUE) == FALSE) {
crm_err("Unable to route reply");
crm_log_xml_err(reply, "reply");
}
free_xml(reply);
free_xml(data);
} else if (safe_str_eq(operation, CRM_OP_PROBED)) {
update_attrd(lrm_state->node_name, CRM_OP_PROBED, XML_BOOLEAN_TRUE, user_name, is_remote_node);
} else if (safe_str_eq(operation, CRM_OP_REPROBE) || safe_str_eq(crm_op, CRM_OP_REPROBE)) {
crm_notice("Forcing the status of all resources to be redetected");
force_reprobe(lrm_state, from_sys, from_host, user_name, is_remote_node);
if (safe_str_neq(CRM_SYSTEM_PENGINE, from_sys)
&& safe_str_neq(CRM_SYSTEM_TENGINE, from_sys)) {
xmlNode *reply = create_request(
CRM_OP_INVOKE_LRM, NULL,
from_host, from_sys, CRM_SYSTEM_LRMD, fsa_our_uuid);
crm_debug("ACK'ing re-probe from %s (%s)", from_sys, from_host);
if (relay_message(reply, TRUE) == FALSE) {
crm_log_xml_err(reply, "Unable to route reply");
}
free_xml(reply);
}
} else if (operation != NULL) {
lrmd_rsc_info_t *rsc = NULL;
xmlNode *params = NULL;
xmlNode *xml_rsc = find_xml_node(input->xml, XML_CIB_TAG_RESOURCE, TRUE);
CRM_CHECK(xml_rsc != NULL, return);
params = find_xml_node(input->xml, XML_TAG_ATTRS, TRUE);
if (safe_str_eq(operation, CRMD_ACTION_DELETE)) {
create_rsc = FALSE;
}
if(lrm_state_is_connected(lrm_state) == FALSE) {
synthesize_lrmd_failure(lrm_state, input->xml, PCMK_OCF_CONNECTION_DIED);
return;
}
rsc = get_lrm_resource(lrm_state, xml_rsc, input->xml, create_rsc);
if (rsc == NULL && create_rsc) {
crm_err("Invalid resource definition for %s", ID(xml_rsc));
crm_log_xml_warn(input->msg, "bad input");
/* if the operation couldn't complete because we can't register
* the resource, return a generic error */
synthesize_lrmd_failure(lrm_state, input->xml, PCMK_OCF_NOT_CONFIGURED);
} else if (rsc == NULL) {
crm_notice("Not creating resource for a %s event: %s", operation, ID(input->xml));
delete_rsc_entry(lrm_state, input, ID(xml_rsc), NULL, pcmk_ok, user_name);
/* Deleting something that does not exist is a success */
send_task_ok_ack(lrm_state, input, ID(xml_rsc), NULL, operation,
from_host, from_sys);
} else if (safe_str_eq(operation, CRMD_ACTION_CANCEL)) {
char *op_key = NULL;
char *meta_key = NULL;
int call = 0;
const char *call_id = NULL;
const char *op_task = NULL;
const char *op_interval = NULL;
gboolean in_progress = FALSE;
CRM_CHECK(params != NULL, crm_log_xml_warn(input->xml, "Bad command");
lrmd_free_rsc_info(rsc); return);
meta_key = crm_meta_name(XML_LRM_ATTR_INTERVAL);
op_interval = crm_element_value(params, meta_key);
free(meta_key);
meta_key = crm_meta_name(XML_LRM_ATTR_TASK);
op_task = crm_element_value(params, meta_key);
free(meta_key);
meta_key = crm_meta_name(XML_LRM_ATTR_CALLID);
call_id = crm_element_value(params, meta_key);
free(meta_key);
CRM_CHECK(op_task != NULL, crm_log_xml_warn(input->xml, "Bad command");
lrmd_free_rsc_info(rsc); return);
CRM_CHECK(op_interval != NULL, crm_log_xml_warn(input->xml, "Bad command");
lrmd_free_rsc_info(rsc); return);
op_key = generate_op_key(rsc->id, op_task, crm_parse_int(op_interval, "0"));
crm_debug("PE requested op %s (call=%s) be cancelled",
op_key, call_id ? call_id : "NA");
call = crm_parse_int(call_id, "0");
if (call == 0) {
/* the normal case when the PE cancels a recurring op */
in_progress = cancel_op_key(lrm_state, rsc, op_key, TRUE);
} else {
/* the normal case when the PE cancels an orphan op */
in_progress = cancel_op(lrm_state, rsc->id, NULL, call, TRUE);
}
/* Acknowledge the cancellation operation if it's for a remote connection resource */
if (in_progress == FALSE || is_remote_lrmd_ra(NULL, NULL, rsc->id)) {
char *op_id = make_stop_id(rsc->id, call);
if (is_remote_lrmd_ra(NULL, NULL, rsc->id) == FALSE) {
crm_info("Nothing known about operation %d for %s", call, op_key);
}
erase_lrm_history_by_id(lrm_state, rsc->id, op_key, NULL, call);
send_task_ok_ack(lrm_state, input, rsc->id, rsc, op_task,
from_host, from_sys);
/* needed at least for cancellation of a remote operation */
g_hash_table_remove(lrm_state->pending_ops, op_id);
free(op_id);
} else {
/* No ack is needed since abcdaa8, but peers with older versions
* in a rolling upgrade need one. We didn't bump the feature set
* at that commit, so we can only compare against the previous
* CRM version (3.0.8). If any peers have feature set 3.0.9 but
* not abcdaa8, they will time out waiting for the ack (no
* released versions of Pacemaker are affected).
*/
const char *peer_version = crm_element_value(params, XML_ATTR_CRM_VERSION);
if (compare_version(peer_version, "3.0.8") <= 0) {
crm_info("Sending compatibility ack for %s cancellation to %s (CRM version %s)",
op_key, from_host, peer_version);
send_task_ok_ack(lrm_state, input, rsc->id, rsc, op_task,
from_host, from_sys);
}
}
free(op_key);
} else if (safe_str_eq(operation, CRMD_ACTION_DELETE)) {
gboolean unregister = TRUE;
#if ENABLE_ACL
int cib_rc = delete_rsc_status(lrm_state, rsc->id, cib_dryrun | cib_sync_call, user_name);
if (cib_rc != pcmk_ok) {
lrmd_event_data_t *op = NULL;
crm_err
("Attempted deletion of resource status '%s' from CIB for %s (user=%s) on %s failed: (rc=%d) %s",
rsc->id, from_sys, user_name ? user_name : "unknown", from_host, cib_rc,
pcmk_strerror(cib_rc));
op = construct_op(lrm_state, input->xml, rsc->id, operation);
op->op_status = PCMK_LRM_OP_ERROR;
if (cib_rc == -EACCES) {
op->rc = PCMK_OCF_INSUFFICIENT_PRIV;
} else {
op->rc = PCMK_OCF_UNKNOWN_ERROR;
}
send_direct_ack(from_host, from_sys, NULL, op, rsc->id);
lrmd_free_event(op);
lrmd_free_rsc_info(rsc);
return;
}
#endif
if (crm_rsc_delete == TRUE && is_remote_lrmd_ra(NULL, NULL, rsc->id)) {
unregister = FALSE;
}
delete_resource(lrm_state, rsc->id, rsc, NULL, from_sys, from_host, user_name, input, unregister);
} else {
do_lrm_rsc_op(lrm_state, rsc, operation, input->xml, input->msg);
}
lrmd_free_rsc_info(rsc);
} else {
crm_err("Cannot perform operation %s of unknown type", crm_str(crm_op));
register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
}
}
static lrmd_event_data_t *
construct_op(lrm_state_t * lrm_state, xmlNode * rsc_op, const char *rsc_id, const char *operation)
{
lrmd_event_data_t *op = NULL;
const char *op_delay = NULL;
const char *op_timeout = NULL;
const char *op_interval = NULL;
GHashTable *params = NULL;
const char *transition = NULL;
CRM_ASSERT(rsc_id != NULL);
op = calloc(1, sizeof(lrmd_event_data_t));
op->type = lrmd_event_exec_complete;
op->op_type = strdup(operation);
op->op_status = PCMK_LRM_OP_PENDING;
op->rc = -1;
op->rsc_id = strdup(rsc_id);
op->interval = 0;
op->timeout = 0;
op->start_delay = 0;
if (rsc_op == NULL) {
CRM_LOG_ASSERT(safe_str_eq(CRMD_ACTION_STOP, operation));
op->user_data = NULL;
/* the stop_all_resources() case
* by definition there is no DC (or they'd be shutting
* us down).
* So we should put our version here.
*/
op->params = crm_str_table_new();
g_hash_table_insert(op->params, strdup(XML_ATTR_CRM_VERSION), strdup(CRM_FEATURE_SET));
crm_trace("Constructed %s op for %s", operation, rsc_id);
return op;
}
params = xml2list(rsc_op);
g_hash_table_remove(params, CRM_META "_op_target_rc");
op_delay = crm_meta_value(params, XML_OP_ATTR_START_DELAY);
op_timeout = crm_meta_value(params, XML_ATTR_TIMEOUT);
op_interval = crm_meta_value(params, XML_LRM_ATTR_INTERVAL);
op->interval = crm_parse_int(op_interval, "0");
op->timeout = crm_parse_int(op_timeout, "0");
op->start_delay = crm_parse_int(op_delay, "0");
- if (safe_str_neq(op->op_type, RSC_METADATA) && !is_remote_lrmd_ra(NULL, NULL, rsc_id)) {
+ // Resolve any versioned parameters
+ if (safe_str_neq(op->op_type, RSC_METADATA)
+ && safe_str_neq(op->op_type, CRMD_ACTION_DELETE)
+ && !is_remote_lrmd_ra(NULL, NULL, rsc_id)) {
+
+ // Resource info *should* already be cached, so we don't get lrmd call
lrmd_rsc_info_t *rsc = lrm_state_get_rsc_info(lrm_state, rsc_id, 0);
- xmlNode *metadata = lrm_state_get_rsc_metadata(lrm_state, rsc);
+ struct ra_metadata_s *metadata;
+ metadata = metadata_cache_get(lrm_state->metadata_cache, rsc);
if (metadata) {
- const char *ra_version = get_ra_version(rsc, metadata);
- xmlNode *versioned_attrs = NULL;
+ xmlNode *versioned_attrs = NULL;
GHashTable *hash = NULL;
char *key = NULL;
char *value = NULL;
GHashTableIter iter;
versioned_attrs = first_named_child(rsc_op, XML_TAG_OP_VER_ATTRS);
- hash = pe_unpack_versioned_parameters(versioned_attrs, ra_version);
+ hash = pe_unpack_versioned_parameters(versioned_attrs, metadata->ra_version);
g_hash_table_iter_init(&iter, hash);
while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value)) {
g_hash_table_iter_steal(&iter);
g_hash_table_replace(params, key, value);
// providing meta-names for instance_attributes is only for backward compatibility,
// and will be removed in a future release
g_hash_table_replace(params, crm_meta_name(key), strdup(value));
}
g_hash_table_destroy(hash);
versioned_attrs = first_named_child(rsc_op, XML_TAG_OP_VER_META);
- hash = pe_unpack_versioned_parameters(versioned_attrs, ra_version);
+ hash = pe_unpack_versioned_parameters(versioned_attrs, metadata->ra_version);
g_hash_table_iter_init(&iter, hash);
while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value)) {
g_hash_table_replace(params, crm_meta_name(key), strdup(value));
if (safe_str_eq(key, XML_ATTR_TIMEOUT)) {
op->timeout = crm_parse_int(value, "0");
} else if (safe_str_eq(key, XML_OP_ATTR_START_DELAY)) {
op->start_delay = crm_parse_int(value, "0");
}
}
g_hash_table_destroy(hash);
versioned_attrs = first_named_child(rsc_op, XML_TAG_RSC_VER_ATTRS);
- hash = pe_unpack_versioned_parameters(versioned_attrs, ra_version);
+ hash = pe_unpack_versioned_parameters(versioned_attrs, metadata->ra_version);
g_hash_table_iter_init(&iter, hash);
while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value)) {
g_hash_table_iter_steal(&iter);
g_hash_table_replace(params, key, value);
}
g_hash_table_destroy(hash);
}
lrmd_free_rsc_info(rsc);
}
if (safe_str_neq(operation, RSC_STOP)) {
op->params = params;
} else {
rsc_history_t *entry = g_hash_table_lookup(lrm_state->resource_history, rsc_id);
/* If we do not have stop parameters cached, use
* whatever we are given */
if (!entry || !entry->stop_params) {
op->params = params;
} else {
/* Copy the cached parameter list so that we stop the resource
* with the old attributes, not the new ones */
op->params = crm_str_table_new();
g_hash_table_foreach(params, copy_meta_keys, op->params);
g_hash_table_foreach(entry->stop_params, copy_instance_keys, op->params);
g_hash_table_destroy(params);
params = NULL;
}
}
/* sanity */
if (op->interval < 0) {
op->interval = 0;
}
if (op->timeout <= 0) {
op->timeout = op->interval;
}
if (op->start_delay < 0) {
op->start_delay = 0;
}
transition = crm_element_value(rsc_op, XML_ATTR_TRANSITION_KEY);
CRM_CHECK(transition != NULL, return op);
op->user_data = strdup(transition);
if (op->interval != 0) {
if (safe_str_eq(operation, CRMD_ACTION_START)
|| safe_str_eq(operation, CRMD_ACTION_STOP)) {
crm_err("Start and Stop actions cannot have an interval: %d", op->interval);
op->interval = 0;
}
}
crm_trace("Constructed %s op for %s: interval=%d", operation, rsc_id, op->interval);
return op;
}
void
send_direct_ack(const char *to_host, const char *to_sys,
lrmd_rsc_info_t * rsc, lrmd_event_data_t * op, const char *rsc_id)
{
xmlNode *reply = NULL;
xmlNode *update, *iter;
crm_node_t *peer = NULL;
CRM_CHECK(op != NULL, return);
if (op->rsc_id == NULL) {
CRM_ASSERT(rsc_id != NULL);
op->rsc_id = strdup(rsc_id);
}
if (to_sys == NULL) {
to_sys = CRM_SYSTEM_TENGINE;
}
peer = crm_get_peer(0, fsa_our_uname);
update = create_node_state_update(peer, node_update_none, NULL,
__FUNCTION__);
iter = create_xml_node(update, XML_CIB_TAG_LRM);
crm_xml_add(iter, XML_ATTR_ID, fsa_our_uuid);
iter = create_xml_node(iter, XML_LRM_TAG_RESOURCES);
iter = create_xml_node(iter, XML_LRM_TAG_RESOURCE);
crm_xml_add(iter, XML_ATTR_ID, op->rsc_id);
build_operation_update(iter, rsc, op, fsa_our_uname, __FUNCTION__);
reply = create_request(CRM_OP_INVOKE_LRM, update, to_host, to_sys, CRM_SYSTEM_LRMD, NULL);
crm_log_xml_trace(update, "ACK Update");
crm_debug("ACK'ing resource op %s_%s_%d from %s: %s",
op->rsc_id, op->op_type, op->interval, op->user_data,
crm_element_value(reply, XML_ATTR_REFERENCE));
if (relay_message(reply, TRUE) == FALSE) {
crm_log_xml_err(reply, "Unable to route reply");
}
free_xml(update);
free_xml(reply);
}
gboolean
verify_stopped(enum crmd_fsa_state cur_state, int log_level)
{
gboolean res = TRUE;
GList *lrm_state_list = lrm_state_get_list();
GList *state_entry;
for (state_entry = lrm_state_list; state_entry != NULL; state_entry = state_entry->next) {
lrm_state_t *lrm_state = state_entry->data;
if (!lrm_state_verify_stopped(lrm_state, cur_state, log_level)) {
/* keep iterating through all even when false is returned */
res = FALSE;
}
}
set_bit(fsa_input_register, R_SENT_RSC_STOP);
g_list_free(lrm_state_list); lrm_state_list = NULL;
return res;
}
struct stop_recurring_action_s {
lrmd_rsc_info_t *rsc;
lrm_state_t *lrm_state;
};
static gboolean
stop_recurring_action_by_rsc(gpointer key, gpointer value, gpointer user_data)
{
gboolean remove = FALSE;
struct stop_recurring_action_s *event = user_data;
struct recurring_op_s *op = (struct recurring_op_s *)value;
if (op->interval != 0 && crm_str_eq(op->rsc_id, event->rsc->id, TRUE)) {
crm_debug("Cancelling op %d for %s (%s)", op->call_id, op->rsc_id, (char*)key);
remove = !cancel_op(event->lrm_state, event->rsc->id, key, op->call_id, FALSE);
}
return remove;
}
static gboolean
stop_recurring_actions(gpointer key, gpointer value, gpointer user_data)
{
gboolean remove = FALSE;
lrm_state_t *lrm_state = user_data;
struct recurring_op_s *op = (struct recurring_op_s *)value;
if (op->interval != 0) {
crm_info("Cancelling op %d for %s (%s)", op->call_id, op->rsc_id, key);
remove = !cancel_op(lrm_state, op->rsc_id, key, op->call_id, FALSE);
}
return remove;
}
static void
record_pending_op(const char *node_name, lrmd_rsc_info_t *rsc, lrmd_event_data_t *op)
{
CRM_CHECK(node_name != NULL, return);
CRM_CHECK(rsc != NULL, return);
CRM_CHECK(op != NULL, return);
if (op->op_type == NULL
|| safe_str_eq(op->op_type, CRMD_ACTION_CANCEL)
|| safe_str_eq(op->op_type, CRMD_ACTION_DELETE)) {
return;
}
if (op->params == NULL) {
return;
} else {
const char *record_pending = crm_meta_value(op->params, XML_OP_ATTR_PENDING);
if (record_pending == NULL || crm_is_true(record_pending) == FALSE) {
return;
}
}
op->call_id = -1;
op->op_status = PCMK_LRM_OP_PENDING;
op->rc = PCMK_OCF_UNKNOWN;
op->t_run = time(NULL);
op->t_rcchange = op->t_run;
/* write a "pending" entry to the CIB, inhibit notification */
crm_debug("Recording pending op %s_%s_%d on %s in the CIB",
op->rsc_id, op->op_type, op->interval, node_name);
do_update_resource(node_name, rsc, op);
}
static void
do_lrm_rsc_op(lrm_state_t * lrm_state, lrmd_rsc_info_t * rsc, const char *operation, xmlNode * msg,
xmlNode * request)
{
int call_id = 0;
char *op_id = NULL;
lrmd_event_data_t *op = NULL;
lrmd_key_value_t *params = NULL;
fsa_data_t *msg_data = NULL;
const char *transition = NULL;
gboolean stop_recurring = FALSE;
bool send_nack = FALSE;
CRM_CHECK(rsc != NULL, return);
CRM_CHECK(operation != NULL, return);
if (msg != NULL) {
transition = crm_element_value(msg, XML_ATTR_TRANSITION_KEY);
if (transition == NULL) {
crm_log_xml_err(msg, "Missing transition number");
}
}
op = construct_op(lrm_state, msg, rsc->id, operation);
CRM_CHECK(op != NULL, return);
if (is_remote_lrmd_ra(NULL, NULL, rsc->id)
&& op->interval == 0
&& strcmp(operation, CRMD_ACTION_MIGRATE) == 0) {
/* pcmk remote connections are a special use case.
* We never ever want to stop monitoring a connection resource until
* the entire migration has completed. If the connection is unexpectedly
* severed, even during a migration, this is an event we must detect.*/
stop_recurring = FALSE;
} else if (op->interval == 0
&& strcmp(operation, CRMD_ACTION_STATUS) != 0
&& strcmp(operation, CRMD_ACTION_NOTIFY) != 0) {
/* stop any previous monitor operations before changing the resource state */
stop_recurring = TRUE;
}
if (stop_recurring == TRUE) {
guint removed = 0;
struct stop_recurring_action_s data;
data.rsc = rsc;
data.lrm_state = lrm_state;
removed = g_hash_table_foreach_remove(
lrm_state->pending_ops, stop_recurring_action_by_rsc, &data);
if (removed) {
crm_debug("Stopped %u recurring operation%s in preparation for %s_%s_%d",
removed, s_if_plural(removed), rsc->id, operation, op->interval);
}
}
/* now do the op */
crm_info("Performing key=%s op=%s_%s_%d", transition, rsc->id, operation, op->interval);
if (is_set(fsa_input_register, R_SHUTDOWN) && safe_str_eq(operation, RSC_START)) {
register_fsa_input(C_SHUTDOWN, I_SHUTDOWN, NULL);
send_nack = TRUE;
} else if (fsa_state != S_NOT_DC
&& fsa_state != S_POLICY_ENGINE /* Recalculating */
&& fsa_state != S_TRANSITION_ENGINE
&& safe_str_neq(operation, "fail")
&& safe_str_neq(operation, CRMD_ACTION_STOP)) {
send_nack = TRUE;
}
if(send_nack) {
crm_notice("Discarding attempt to perform action %s on %s in state %s (shutdown=%s)",
operation, rsc->id, fsa_state2string(fsa_state),
is_set(fsa_input_register, R_SHUTDOWN)?"true":"false");
op->rc = CRM_DIRECT_NACK_RC;
op->op_status = PCMK_LRM_OP_ERROR;
send_direct_ack(NULL, NULL, rsc, op, rsc->id);
lrmd_free_event(op);
free(op_id);
return;
}
record_pending_op(lrm_state->node_name, rsc, op);
op_id = generate_op_key(rsc->id, op->op_type, op->interval);
if (op->interval > 0) {
/* cancel it so we can then restart it without conflict */
cancel_op_key(lrm_state, rsc, op_id, FALSE);
}
if (op->params) {
char *key = NULL;
char *value = NULL;
GHashTableIter iter;
g_hash_table_iter_init(&iter, op->params);
while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) {
params = lrmd_key_value_add(params, key, value);
}
}
call_id = lrm_state_exec(lrm_state,
rsc->id,
op->op_type,
op->user_data, op->interval, op->timeout, op->start_delay, params);
if (call_id <= 0 && lrm_state_is_local(lrm_state)) {
crm_err("Operation %s on %s failed: %d", operation, rsc->id, call_id);
register_fsa_error(C_FSA_INTERNAL, I_FAIL, NULL);
} else if (call_id <= 0) {
crm_err("Operation %s on resource %s failed to execute on remote node %s: %d", operation, rsc->id, lrm_state->node_name, call_id);
op->call_id = get_fake_call_id(lrm_state, rsc->id);
op->op_status = PCMK_LRM_OP_DONE;
op->rc = PCMK_OCF_UNKNOWN_ERROR;
op->t_run = time(NULL);
op->t_rcchange = op->t_run;
process_lrm_event(lrm_state, op, NULL);
} else {
/* record all operations so we can wait
* for them to complete during shutdown
*/
char *call_id_s = make_stop_id(rsc->id, call_id);
struct recurring_op_s *pending = NULL;
pending = calloc(1, sizeof(struct recurring_op_s));
crm_trace("Recording pending op: %d - %s %s", call_id, op_id, call_id_s);
pending->call_id = call_id;
pending->interval = op->interval;
pending->op_type = strdup(operation);
pending->op_key = strdup(op_id);
pending->rsc_id = strdup(rsc->id);
pending->start_time = time(NULL);
pending->user_data = strdup(op->user_data);
g_hash_table_replace(lrm_state->pending_ops, call_id_s, pending);
if (op->interval > 0 && op->start_delay > START_DELAY_THRESHOLD) {
char *uuid = NULL;
int dummy = 0, target_rc = 0;
crm_info("Faking confirmation of %s: execution postponed for over 5 minutes", op_id);
decode_transition_key(op->user_data, &uuid, &dummy, &dummy, &target_rc);
free(uuid);
op->rc = target_rc;
op->op_status = PCMK_LRM_OP_DONE;
send_direct_ack(NULL, NULL, rsc, op, rsc->id);
}
pending->params = op->params;
op->params = NULL;
}
free(op_id);
lrmd_free_event(op);
return;
}
int last_resource_update = 0;
static void
cib_rsc_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
{
switch (rc) {
case pcmk_ok:
case -pcmk_err_diff_failed:
case -pcmk_err_diff_resync:
crm_trace("Resource update %d complete: rc=%d", call_id, rc);
break;
default:
crm_warn("Resource update %d failed: (rc=%d) %s", call_id, rc, pcmk_strerror(rc));
}
if (call_id == last_resource_update) {
last_resource_update = 0;
trigger_fsa(fsa_source);
}
}
static int
do_update_resource(const char *node_name, lrmd_rsc_info_t * rsc, lrmd_event_data_t * op)
{
/*
<status>
<nodes_status id=uname>
<lrm>
<lrm_resources>
<lrm_resource id=...>
</...>
*/
int rc = pcmk_ok;
xmlNode *update, *iter = NULL;
int call_opt = crmd_cib_smart_opt();
const char *uuid = NULL;
CRM_CHECK(op != NULL, return 0);
iter = create_xml_node(iter, XML_CIB_TAG_STATUS);
update = iter;
iter = create_xml_node(iter, XML_CIB_TAG_STATE);
if (safe_str_eq(node_name, fsa_our_uname)) {
uuid = fsa_our_uuid;
} else {
/* remote nodes uuid and uname are equal */
uuid = node_name;
crm_xml_add(iter, XML_NODE_IS_REMOTE, "true");
}
CRM_LOG_ASSERT(uuid != NULL);
if(uuid == NULL) {
rc = -EINVAL;
goto done;
}
crm_xml_add(iter, XML_ATTR_UUID, uuid);
crm_xml_add(iter, XML_ATTR_UNAME, node_name);
crm_xml_add(iter, XML_ATTR_ORIGIN, __FUNCTION__);
iter = create_xml_node(iter, XML_CIB_TAG_LRM);
crm_xml_add(iter, XML_ATTR_ID, uuid);
iter = create_xml_node(iter, XML_LRM_TAG_RESOURCES);
iter = create_xml_node(iter, XML_LRM_TAG_RESOURCE);
crm_xml_add(iter, XML_ATTR_ID, op->rsc_id);
build_operation_update(iter, rsc, op, node_name, __FUNCTION__);
if (rsc) {
const char *container = NULL;
crm_xml_add(iter, XML_ATTR_TYPE, rsc->type);
crm_xml_add(iter, XML_AGENT_ATTR_CLASS, rsc->class);
crm_xml_add(iter, XML_AGENT_ATTR_PROVIDER, rsc->provider);
if (op->params) {
container = g_hash_table_lookup(op->params, CRM_META"_"XML_RSC_ATTR_CONTAINER);
}
if (container) {
crm_trace("Resource %s is a part of container resource %s", op->rsc_id, container);
crm_xml_add(iter, XML_RSC_ATTR_CONTAINER, container);
}
} else {
crm_warn("Resource %s no longer exists in the lrmd", op->rsc_id);
send_direct_ack(NULL, NULL, rsc, op, op->rsc_id);
goto cleanup;
}
crm_log_xml_trace(update, __FUNCTION__);
/* make it an asynchronous call and be done with it
*
* Best case:
* the resource state will be discovered during
* the next signup or election.
*
* Bad case:
* we are shutting down and there is no DC at the time,
* but then why were we shutting down then anyway?
* (probably because of an internal error)
*
* Worst case:
* we get shot for having resources "running" when the really weren't
*
* the alternative however means blocking here for too long, which
* isn't acceptable
*/
fsa_cib_update(XML_CIB_TAG_STATUS, update, call_opt, rc, NULL);
if (rc > 0) {
last_resource_update = rc;
}
done:
/* the return code is a call number, not an error code */
crm_trace("Sent resource state update message: %d for %s=%d on %s", rc,
op->op_type, op->interval, op->rsc_id);
fsa_register_cib_callback(rc, FALSE, NULL, cib_rsc_callback);
cleanup:
free_xml(update);
return rc;
}
void
do_lrm_event(long long action,
enum crmd_fsa_cause cause,
enum crmd_fsa_state cur_state, enum crmd_fsa_input cur_input, fsa_data_t * msg_data)
{
CRM_CHECK(FALSE, return);
}
static char *
unescape_newlines(const char *string)
{
char *pch = NULL;
char *ret = NULL;
static const char *escaped_newline = "\\n";
if (!string) {
return NULL;
}
ret = strdup(string);
pch = strstr(ret, escaped_newline);
while (pch != NULL) {
strncpy(pch, "\n ", 2);
pch = strstr(pch, escaped_newline);
}
return ret;
}
gboolean
process_lrm_event(lrm_state_t * lrm_state, lrmd_event_data_t * op, struct recurring_op_s *pending)
{
char *op_id = NULL;
char *op_key = NULL;
int update_id = 0;
gboolean remove = FALSE;
gboolean removed = FALSE;
lrmd_rsc_info_t *rsc = NULL;
CRM_CHECK(op != NULL, return FALSE);
CRM_CHECK(op->rsc_id != NULL, return FALSE);
op_id = make_stop_id(op->rsc_id, op->call_id);
op_key = generate_op_key(op->rsc_id, op->op_type, op->interval);
rsc = lrm_state_get_rsc_info(lrm_state, op->rsc_id, 0);
if(pending == NULL) {
remove = TRUE;
pending = g_hash_table_lookup(lrm_state->pending_ops, op_id);
}
if (op->op_status == PCMK_LRM_OP_ERROR) {
switch(op->rc) {
case PCMK_OCF_NOT_RUNNING:
case PCMK_OCF_RUNNING_MASTER:
case PCMK_OCF_DEGRADED:
case PCMK_OCF_DEGRADED_MASTER:
/* Leave it up to the TE/PE to decide if this is an error */
op->op_status = PCMK_LRM_OP_DONE;
break;
default:
/* Nothing to do */
break;
}
}
if (op->op_status != PCMK_LRM_OP_CANCELLED) {
if (safe_str_eq(op->op_type, RSC_NOTIFY) || safe_str_eq(op->op_type, RSC_METADATA)) {
/* Keep notify and meta-data ops out of the CIB */
send_direct_ack(NULL, NULL, NULL, op, op->rsc_id);
} else {
update_id = do_update_resource(lrm_state->node_name, rsc, op);
}
} else if (op->interval == 0) {
/* This will occur when "crm resource cleanup" is called while actions are in-flight */
crm_err("Op %s (call=%d): Cancelled", op_key, op->call_id);
send_direct_ack(NULL, NULL, NULL, op, op->rsc_id);
} else if (pending == NULL) {
/* We don't need to do anything for cancelled ops
* that are not in our pending op list. There are no
* transition actions waiting on these operations. */
} else if (op->user_data == NULL) {
/* At this point we have a pending entry, but no transition
* key present in the user_data field. report this */
crm_err("Op %s (call=%d): No user data", op_key, op->call_id);
} else if (pending->remove) {
/* The tengine canceled this op, we have been waiting for the cancel to finish. */
erase_lrm_history_by_op(lrm_state, op);
} else if (pending && op->rsc_deleted) {
/* The tengine initiated this op, but it was cancelled outside of the
* tengine's control during a resource cleanup/re-probe request. The tengine
* must be alerted that this operation completed, otherwise the tengine
* will continue waiting for this update to occur until it is timed out.
* We don't want this update going to the cib though, so use a direct ack. */
crm_trace("Op %s (call=%d): cancelled due to rsc deletion", op_key, op->call_id);
send_direct_ack(NULL, NULL, NULL, op, op->rsc_id);
} else {
/* Before a stop is called, no need to direct ack */
crm_trace("Op %s (call=%d): no delete event required", op_key, op->call_id);
}
if(remove == FALSE) {
/* The caller will do this afterwards, but keep the logging consistent */
removed = TRUE;
} else if ((op->interval == 0) && g_hash_table_remove(lrm_state->pending_ops, op_id)) {
removed = TRUE;
crm_trace("Op %s (call=%d, stop-id=%s, remaining=%u): Confirmed",
op_key, op->call_id, op_id, g_hash_table_size(lrm_state->pending_ops));
} else if(op->interval != 0 && op->op_status == PCMK_LRM_OP_CANCELLED) {
removed = TRUE;
g_hash_table_remove(lrm_state->pending_ops, op_id);
}
switch (op->op_status) {
case PCMK_LRM_OP_CANCELLED:
crm_info("Result of %s operation for %s on %s: %s "
CRM_XS " call=%d key=%s confirmed=%s",
crm_action_str(op->op_type, op->interval),
op->rsc_id, lrm_state->node_name,
services_lrm_status_str(op->op_status),
op->call_id, op_key, (removed? "true" : "false"));
break;
case PCMK_LRM_OP_DONE:
do_crm_log(op->interval?LOG_INFO:LOG_NOTICE,
"Result of %s operation for %s on %s: %d (%s) "
CRM_XS " call=%d key=%s confirmed=%s cib-update=%d",
crm_action_str(op->op_type, op->interval),
op->rsc_id, lrm_state->node_name,
op->rc, services_ocf_exitcode_str(op->rc),
op->call_id, op_key, (removed? "true" : "false"),
update_id);
break;
case PCMK_LRM_OP_TIMEOUT:
crm_err("Result of %s operation for %s on %s: %s "
CRM_XS " call=%d key=%s timeout=%dms",
crm_action_str(op->op_type, op->interval),
op->rsc_id, lrm_state->node_name,
services_lrm_status_str(op->op_status),
op->call_id, op_key, op->timeout);
break;
default:
crm_err("Result of %s operation for %s on %s: %s "
CRM_XS " call=%d key=%s confirmed=%s status=%d cib-update=%d",
crm_action_str(op->op_type, op->interval),
op->rsc_id, lrm_state->node_name,
services_lrm_status_str(op->op_status), op->call_id, op_key,
(removed? "true" : "false"), op->op_status, update_id);
}
if (op->output) {
char *prefix =
crm_strdup_printf("%s-%s_%s_%d:%d", lrm_state->node_name, op->rsc_id, op->op_type, op->interval, op->call_id);
if (op->rc) {
crm_log_output(LOG_NOTICE, prefix, op->output);
} else {
crm_log_output(LOG_DEBUG, prefix, op->output);
}
free(prefix);
}
if (safe_str_neq(op->op_type, RSC_METADATA)) {
crmd_alert_resource_op(lrm_state->node_name, op);
} else if (op->rc == PCMK_OCF_OK) {
char *metadata = unescape_newlines(op->output);
- lrm_state_update_rsc_metadata(lrm_state, rsc, metadata);
+ metadata_cache_update(lrm_state->metadata_cache, rsc, metadata);
free(metadata);
}
if (op->rsc_deleted) {
crm_info("Deletion of resource '%s' complete after %s", op->rsc_id, op_key);
delete_rsc_entry(lrm_state, NULL, op->rsc_id, NULL, pcmk_ok, NULL);
}
/* If a shutdown was escalated while operations were pending,
* then the FSA will be stalled right now... allow it to continue
*/
mainloop_set_trigger(fsa_source);
update_history_cache(lrm_state, rsc, op);
lrmd_free_rsc_info(rsc);
free(op_key);
free(op_id);
return TRUE;
}
diff --git a/crmd/lrm_state.c b/crmd/lrm_state.c
index 31adbf49e7..972584502a 100644
--- a/crmd/lrm_state.c
+++ b/crmd/lrm_state.c
@@ -1,793 +1,797 @@
/*
* Copyright (C) 2012 David Vossel <davidvossel@gmail.com>
*
* 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
*/
#include <crm_internal.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/iso8601.h>
#include <crmd.h>
#include <crmd_fsa.h>
#include <crmd_messages.h>
#include <crmd_callbacks.h>
#include <crmd_lrm.h>
+#include <crmd_alerts.h>
#include <crm/pengine/rules.h>
+#include <crm/pengine/rules_internal.h>
#include <crm/transition.h>
+#include <crm/lrmd_alerts_internal.h>
GHashTable *lrm_state_table = NULL;
extern GHashTable *proxy_table;
int lrmd_internal_proxy_send(lrmd_t * lrmd, xmlNode *msg);
void lrmd_internal_set_proxy_callback(lrmd_t * lrmd, void *userdata, void (*callback)(lrmd_t *lrmd, void *userdata, xmlNode *msg));
static void
free_rsc_info(gpointer value)
{
lrmd_rsc_info_t *rsc_info = value;
lrmd_free_rsc_info(rsc_info);
}
static void
free_deletion_op(gpointer value)
{
struct pending_deletion_op_s *op = value;
free(op->rsc);
delete_ha_msg_input(op->input);
free(op);
}
static void
free_recurring_op(gpointer value)
{
struct recurring_op_s *op = (struct recurring_op_s *)value;
free(op->user_data);
free(op->rsc_id);
free(op->op_type);
free(op->op_key);
if (op->params) {
g_hash_table_destroy(op->params);
}
free(op);
}
static gboolean
fail_pending_op(gpointer key, gpointer value, gpointer user_data)
{
lrmd_event_data_t event = { 0, };
lrm_state_t *lrm_state = user_data;
struct recurring_op_s *op = (struct recurring_op_s *)value;
crm_trace("Pre-emptively failing %s_%s_%d on %s (call=%s, %s)",
op->rsc_id, op->op_type, op->interval,
lrm_state->node_name, (char*)key, op->user_data);
event.type = lrmd_event_exec_complete;
event.rsc_id = op->rsc_id;
event.op_type = op->op_type;
event.user_data = op->user_data;
event.timeout = 0;
event.interval = op->interval;
event.rc = PCMK_OCF_CONNECTION_DIED;
event.op_status = PCMK_LRM_OP_ERROR;
event.t_run = op->start_time;
event.t_rcchange = op->start_time;
event.call_id = op->call_id;
event.remote_nodename = lrm_state->node_name;
event.params = op->params;
process_lrm_event(lrm_state, &event, op);
return TRUE;
}
gboolean
lrm_state_is_local(lrm_state_t *lrm_state)
{
if (lrm_state == NULL || fsa_our_uname == NULL) {
return FALSE;
}
if (strcmp(lrm_state->node_name, fsa_our_uname) != 0) {
return FALSE;
}
return TRUE;
}
lrm_state_t *
lrm_state_create(const char *node_name)
{
lrm_state_t *state = NULL;
if (!node_name) {
crm_err("No node name given for lrm state object");
return NULL;
}
state = calloc(1, sizeof(lrm_state_t));
if (!state) {
return NULL;
}
state->node_name = strdup(node_name);
state->rsc_info_cache = g_hash_table_new_full(crm_str_hash,
g_str_equal, NULL, free_rsc_info);
state->deletion_ops = g_hash_table_new_full(crm_str_hash,
g_str_equal, g_hash_destroy_str, free_deletion_op);
state->pending_ops = g_hash_table_new_full(crm_str_hash,
g_str_equal, g_hash_destroy_str, free_recurring_op);
state->resource_history = g_hash_table_new_full(crm_str_hash,
g_str_equal, NULL, history_free);
- state->metadata_cache = g_hash_table_new_full(crm_str_hash, g_str_equal,
- g_hash_destroy_str, crm_destroy_xml);
+ state->metadata_cache = metadata_cache_new();
g_hash_table_insert(lrm_state_table, (char *)state->node_name, state);
return state;
}
void
lrm_state_destroy(const char *node_name)
{
g_hash_table_remove(lrm_state_table, node_name);
}
static gboolean
remote_proxy_remove_by_node(gpointer key, gpointer value, gpointer user_data)
{
remote_proxy_t *proxy = value;
const char *node_name = user_data;
if (safe_str_eq(node_name, proxy->node_name)) {
return TRUE;
}
return FALSE;
}
static void
internal_lrm_state_destroy(gpointer data)
{
lrm_state_t *lrm_state = data;
if (!lrm_state) {
return;
}
crm_trace("Destroying proxy table %s with %d members", lrm_state->node_name, g_hash_table_size(proxy_table));
g_hash_table_foreach_remove(proxy_table, remote_proxy_remove_by_node, (char *) lrm_state->node_name);
remote_ra_cleanup(lrm_state);
lrmd_api_delete(lrm_state->conn);
if (lrm_state->rsc_info_cache) {
crm_trace("Destroying rsc info cache with %d members", g_hash_table_size(lrm_state->rsc_info_cache));
g_hash_table_destroy(lrm_state->rsc_info_cache);
}
if (lrm_state->resource_history) {
crm_trace("Destroying history op cache with %d members", g_hash_table_size(lrm_state->resource_history));
g_hash_table_destroy(lrm_state->resource_history);
}
if (lrm_state->deletion_ops) {
crm_trace("Destroying deletion op cache with %d members", g_hash_table_size(lrm_state->deletion_ops));
g_hash_table_destroy(lrm_state->deletion_ops);
}
if (lrm_state->pending_ops) {
crm_trace("Destroying pending op cache with %d members", g_hash_table_size(lrm_state->pending_ops));
g_hash_table_destroy(lrm_state->pending_ops);
}
- if (lrm_state->metadata_cache) {
- crm_trace("Destroying metadata cache with %d members", g_hash_table_size(lrm_state->metadata_cache));
- g_hash_table_destroy(lrm_state->metadata_cache);
- }
+ metadata_cache_free(lrm_state->metadata_cache);
free((char *)lrm_state->node_name);
free(lrm_state);
}
void
lrm_state_reset_tables(lrm_state_t * lrm_state, gboolean reset_metadata)
{
if (lrm_state->resource_history) {
crm_trace("Re-setting history op cache with %d members",
g_hash_table_size(lrm_state->resource_history));
g_hash_table_remove_all(lrm_state->resource_history);
}
if (lrm_state->deletion_ops) {
crm_trace("Re-setting deletion op cache with %d members",
g_hash_table_size(lrm_state->deletion_ops));
g_hash_table_remove_all(lrm_state->deletion_ops);
}
if (lrm_state->pending_ops) {
crm_trace("Re-setting pending op cache with %d members",
g_hash_table_size(lrm_state->pending_ops));
g_hash_table_remove_all(lrm_state->pending_ops);
}
if (lrm_state->rsc_info_cache) {
crm_trace("Re-setting rsc info cache with %d members",
g_hash_table_size(lrm_state->rsc_info_cache));
g_hash_table_remove_all(lrm_state->rsc_info_cache);
}
- if (reset_metadata && lrm_state->metadata_cache) {
- crm_trace("Re-setting metadata cache with %d members",
- g_hash_table_size(lrm_state->metadata_cache));
- g_hash_table_remove_all(lrm_state->metadata_cache);
+ if (reset_metadata) {
+ metadata_cache_reset(lrm_state->metadata_cache);
}
}
-static gboolean
-has_cached_metadata_for(lrmd_rsc_info_t *rsc, const char *node_name)
-{
- lrm_state_t *lrm_state;
-
- CRM_CHECK((rsc != NULL) && (node_name != NULL), return FALSE);
-
- lrm_state = lrm_state_find(node_name);
- if (lrm_state == NULL) {
- crm_debug("Metadata check requested for %s but we've never connected to it",
- node_name);
- return FALSE;
- }
-
- return lrm_state_get_rsc_metadata(lrm_state, rsc) != NULL;
-}
-
gboolean
lrm_state_init_local(void)
{
if (lrm_state_table) {
return TRUE;
}
lrm_state_table =
g_hash_table_new_full(crm_strcase_hash, crm_strcase_equal, NULL, internal_lrm_state_destroy);
if (!lrm_state_table) {
return FALSE;
}
proxy_table =
g_hash_table_new_full(crm_strcase_hash, crm_strcase_equal, NULL, remote_proxy_free);
if (!proxy_table) {
g_hash_table_destroy(lrm_state_table);
lrm_state_table = NULL;
return FALSE;
}
- crm_register_cache_check_fn(&has_cached_metadata_for);
-
return TRUE;
}
void
lrm_state_destroy_all(void)
{
- crm_unregister_cache_check_fn();
-
if (lrm_state_table) {
crm_trace("Destroying state table with %d members", g_hash_table_size(lrm_state_table));
g_hash_table_destroy(lrm_state_table); lrm_state_table = NULL;
}
if(proxy_table) {
crm_trace("Destroying proxy table with %d members", g_hash_table_size(proxy_table));
g_hash_table_destroy(proxy_table); proxy_table = NULL;
}
}
lrm_state_t *
lrm_state_find(const char *node_name)
{
if (!node_name) {
return NULL;
}
return g_hash_table_lookup(lrm_state_table, node_name);
}
lrm_state_t *
lrm_state_find_or_create(const char *node_name)
{
lrm_state_t *lrm_state;
lrm_state = g_hash_table_lookup(lrm_state_table, node_name);
if (!lrm_state) {
lrm_state = lrm_state_create(node_name);
}
return lrm_state;
}
GList *
lrm_state_get_list(void)
{
return g_hash_table_get_values(lrm_state_table);
}
static remote_proxy_t *
find_connected_proxy_by_node(const char * node_name)
{
GHashTableIter gIter;
remote_proxy_t *proxy = NULL;
CRM_CHECK(proxy_table != NULL, return NULL);
g_hash_table_iter_init(&gIter, proxy_table);
while (g_hash_table_iter_next(&gIter, NULL, (gpointer *) &proxy)) {
if (proxy->source
&& safe_str_eq(node_name, proxy->node_name)) {
return proxy;
}
}
return NULL;
}
static void
remote_proxy_disconnect_by_node(const char * node_name)
{
remote_proxy_t *proxy = NULL;
CRM_CHECK(proxy_table != NULL, return);
while ((proxy = find_connected_proxy_by_node(node_name)) != NULL) {
/* mainloop_del_ipc_client() eventually calls remote_proxy_disconnected()
* , which removes the entry from proxy_table.
* Do not do this in a g_hash_table_iter_next() loop. */
if (proxy->source) {
mainloop_del_ipc_client(proxy->source);
}
}
return;
}
void
lrm_state_disconnect_only(lrm_state_t * lrm_state)
{
int removed = 0;
if (!lrm_state->conn) {
return;
}
crm_trace("Disconnecting %s", lrm_state->node_name);
remote_proxy_disconnect_by_node(lrm_state->node_name);
((lrmd_t *) lrm_state->conn)->cmds->disconnect(lrm_state->conn);
if (is_not_set(fsa_input_register, R_SHUTDOWN)) {
removed = g_hash_table_foreach_remove(lrm_state->pending_ops, fail_pending_op, lrm_state);
crm_trace("Synthesized %d operation failures for %s", removed, lrm_state->node_name);
}
}
void
lrm_state_disconnect(lrm_state_t * lrm_state)
{
if (!lrm_state->conn) {
return;
}
lrm_state_disconnect_only(lrm_state);
lrmd_api_delete(lrm_state->conn);
lrm_state->conn = NULL;
}
int
lrm_state_is_connected(lrm_state_t * lrm_state)
{
if (!lrm_state->conn) {
return FALSE;
}
return ((lrmd_t *) lrm_state->conn)->cmds->is_connected(lrm_state->conn);
}
int
lrm_state_poke_connection(lrm_state_t * lrm_state)
{
if (!lrm_state->conn) {
return -1;
}
return ((lrmd_t *) lrm_state->conn)->cmds->poke_connection(lrm_state->conn);
}
int
lrm_state_ipc_connect(lrm_state_t * lrm_state)
{
int ret;
if (!lrm_state->conn) {
lrm_state->conn = lrmd_api_new();
((lrmd_t *) lrm_state->conn)->cmds->set_callback(lrm_state->conn, lrm_op_callback);
}
ret = ((lrmd_t *) lrm_state->conn)->cmds->connect(lrm_state->conn, CRM_SYSTEM_CRMD, NULL);
if (ret != pcmk_ok) {
lrm_state->num_lrm_register_fails++;
} else {
lrm_state->num_lrm_register_fails = 0;
}
return ret;
}
static remote_proxy_t *
crmd_remote_proxy_new(lrmd_t *lrmd, const char *node_name, const char *session_id, const char *channel)
{
static struct ipc_client_callbacks proxy_callbacks = {
.dispatch = remote_proxy_dispatch,
.destroy = remote_proxy_disconnected
};
remote_proxy_t *proxy = remote_proxy_new(lrmd, &proxy_callbacks, node_name,
session_id, channel);
return proxy;
}
gboolean
crmd_is_proxy_session(const char *session)
{
return g_hash_table_lookup(proxy_table, session) ? TRUE : FALSE;
}
void
crmd_proxy_send(const char *session, xmlNode *msg)
{
remote_proxy_t *proxy = g_hash_table_lookup(proxy_table, session);
lrm_state_t *lrm_state = NULL;
if (!proxy) {
return;
}
crm_log_xml_trace(msg, "to-proxy");
lrm_state = lrm_state_find(proxy->node_name);
if (lrm_state) {
crm_trace("Sending event to %.8s on %s", proxy->session_id, proxy->node_name);
remote_proxy_relay_event(proxy, msg);
}
}
static void
crmd_proxy_dispatch(const char *session, xmlNode *msg)
{
crm_log_xml_trace(msg, "CRMd-PROXY[inbound]");
crm_xml_add(msg, F_CRM_SYS_FROM, session);
if (crmd_authorize_message(msg, NULL, session)) {
route_message(C_IPC_MESSAGE, msg);
}
trigger_fsa(fsa_source);
}
static void
remote_config_check(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
{
if (rc != pcmk_ok) {
crm_err("Query resulted in an error: %s", pcmk_strerror(rc));
if (rc == -EACCES || rc == -pcmk_err_schema_validation) {
crm_err("The cluster is mis-configured - shutting down and staying down");
}
} else {
lrmd_t * lrmd = (lrmd_t *)user_data;
crm_time_t *now = crm_time_new(NULL);
GHashTable *config_hash = crm_str_table_new();
crm_debug("Call %d : Parsing CIB options", call_id);
unpack_instance_attributes(
output, output, XML_CIB_TAG_PROPSET, NULL, config_hash, CIB_OPTIONS_FIRST, FALSE, now);
/* Now send it to the remote peer */
remote_proxy_check(lrmd, config_hash);
g_hash_table_destroy(config_hash);
crm_time_free(now);
}
}
static void
crmd_remote_proxy_cb(lrmd_t *lrmd, void *userdata, xmlNode *msg)
{
lrm_state_t *lrm_state = userdata;
const char *session = crm_element_value(msg, F_LRMD_IPC_SESSION);
remote_proxy_t *proxy = g_hash_table_lookup(proxy_table, session);
const char *op = crm_element_value(msg, F_LRMD_IPC_OP);
if (safe_str_eq(op, LRMD_IPC_OP_NEW)) {
const char *channel = crm_element_value(msg, F_LRMD_IPC_IPC_SERVER);
proxy = crmd_remote_proxy_new(lrmd, lrm_state->node_name, session, channel);
if (proxy != NULL) {
/* Look up stonith-watchdog-timeout and send to the remote peer for validation */
int rc = fsa_cib_conn->cmds->query(fsa_cib_conn, XML_CIB_TAG_CRMCONFIG, NULL, cib_scope_local);
fsa_cib_conn->cmds->register_callback_full(fsa_cib_conn, rc, 10, FALSE, lrmd,
"remote_config_check", remote_config_check, NULL);
}
} else if (safe_str_eq(op, LRMD_IPC_OP_SHUTDOWN_REQ)) {
char *now_s = NULL;
time_t now = time(NULL);
crm_notice("%s requested shutdown of its remote connection",
lrm_state->node_name);
if (!remote_ra_is_in_maintenance(lrm_state)) {
now_s = crm_itoa(now);
update_attrd(lrm_state->node_name, XML_CIB_ATTR_SHUTDOWN, now_s, NULL, TRUE);
free(now_s);
remote_proxy_ack_shutdown(lrmd);
crm_warn("Reconnection attempts to %s may result in failures that must be cleared",
lrm_state->node_name);
} else {
remote_proxy_nack_shutdown(lrmd);
crm_notice("Remote resource for %s is not managed so no ordered shutdown happening",
lrm_state->node_name);
}
return;
} else if (safe_str_eq(op, LRMD_IPC_OP_REQUEST) && proxy && proxy->is_local) {
/* this is for the crmd, which we are, so don't try
* and connect/send to ourselves over ipc. instead
* do it directly.
*/
int flags = 0;
xmlNode *request = get_message_xml(msg, F_LRMD_IPC_MSG);
CRM_CHECK(request != NULL, return);
#if ENABLE_ACL
CRM_CHECK(lrm_state->node_name, return);
crm_xml_add(request, XML_ACL_TAG_ROLE, "pacemaker-remote");
crm_acl_get_set_user(request, F_LRMD_IPC_USER, lrm_state->node_name);
#endif
crmd_proxy_dispatch(session, request);
crm_element_value_int(msg, F_LRMD_IPC_MSG_FLAGS, &flags);
if (flags & crm_ipc_client_response) {
int msg_id = 0;
xmlNode *op_reply = create_xml_node(NULL, "ack");
crm_xml_add(op_reply, "function", __FUNCTION__);
crm_xml_add_int(op_reply, "line", __LINE__);
crm_element_value_int(msg, F_LRMD_IPC_MSG_ID, &msg_id);
remote_proxy_relay_response(proxy, op_reply, msg_id);
free_xml(op_reply);
}
} else {
remote_proxy_cb(lrmd, lrm_state->node_name, msg);
}
}
int
lrm_state_remote_connect_async(lrm_state_t * lrm_state, const char *server, int port,
int timeout_ms)
{
int ret;
if (!lrm_state->conn) {
lrm_state->conn = lrmd_remote_api_new(lrm_state->node_name, server, port);
if (!lrm_state->conn) {
return -1;
}
((lrmd_t *) lrm_state->conn)->cmds->set_callback(lrm_state->conn, remote_lrm_op_callback);
lrmd_internal_set_proxy_callback(lrm_state->conn, lrm_state, crmd_remote_proxy_cb);
}
crm_trace("initiating remote connection to %s at %d with timeout %d", server, port, timeout_ms);
ret =
((lrmd_t *) lrm_state->conn)->cmds->connect_async(lrm_state->conn, lrm_state->node_name,
timeout_ms);
if (ret != pcmk_ok) {
lrm_state->num_lrm_register_fails++;
} else {
lrm_state->num_lrm_register_fails = 0;
}
return ret;
}
int
lrm_state_get_metadata(lrm_state_t * lrm_state,
const char *class,
const char *provider,
const char *agent, char **output, enum lrmd_call_options options)
{
if (!lrm_state->conn) {
return -ENOTCONN;
}
-
- /* Optimize this... only retrieve metadata from local lrmd connection. Perhaps consider
- * caching result. */
return ((lrmd_t *) lrm_state->conn)->cmds->get_metadata(lrm_state->conn, class, provider, agent,
output, options);
}
int
lrm_state_cancel(lrm_state_t * lrm_state, const char *rsc_id, const char *action, int interval)
{
if (!lrm_state->conn) {
return -ENOTCONN;
}
/* Figure out a way to make this async?
* NOTICE: Currently it's synced and directly acknowledged in do_lrm_invoke(). */
if (is_remote_lrmd_ra(NULL, NULL, rsc_id)) {
return remote_ra_cancel(lrm_state, rsc_id, action, interval);
}
return ((lrmd_t *) lrm_state->conn)->cmds->cancel(lrm_state->conn, rsc_id, action, interval);
}
lrmd_rsc_info_t *
lrm_state_get_rsc_info(lrm_state_t * lrm_state, const char *rsc_id, enum lrmd_call_options options)
{
lrmd_rsc_info_t *rsc = NULL;
if (!lrm_state->conn) {
return NULL;
}
if (is_remote_lrmd_ra(NULL, NULL, rsc_id)) {
return remote_ra_get_rsc_info(lrm_state, rsc_id);
}
rsc = g_hash_table_lookup(lrm_state->rsc_info_cache, rsc_id);
if (rsc == NULL) {
/* only contact the lrmd if we don't already have a cached rsc info */
rsc = ((lrmd_t *) lrm_state->conn)->cmds->get_rsc_info(lrm_state->conn, rsc_id, options);
if (rsc == NULL) {
return NULL;
}
/* cache the result */
g_hash_table_insert(lrm_state->rsc_info_cache, rsc->id, rsc);
}
return lrmd_copy_rsc_info(rsc);
}
int
lrm_state_exec(lrm_state_t * lrm_state, const char *rsc_id, const char *action, const char *userdata, int interval, /* ms */
int timeout, /* ms */
int start_delay, /* ms */
lrmd_key_value_t * params)
{
if (!lrm_state->conn) {
lrmd_key_value_freeall(params);
return -ENOTCONN;
}
if (is_remote_lrmd_ra(NULL, NULL, rsc_id)) {
return remote_ra_exec(lrm_state,
rsc_id, action, userdata, interval, timeout, start_delay, params);
}
return ((lrmd_t *) lrm_state->conn)->cmds->exec(lrm_state->conn,
rsc_id,
action,
userdata,
interval,
timeout,
start_delay,
lrmd_opt_notify_changes_only, params);
}
int
lrm_state_register_rsc(lrm_state_t * lrm_state,
const char *rsc_id,
const char *class,
const char *provider, const char *agent, enum lrmd_call_options options)
{
if (!lrm_state->conn) {
return -ENOTCONN;
}
/* optimize this... this function is a synced round trip from client to daemon.
* The crmd/lrm.c code path should be re-factored to allow the register of resources
* to be performed async. The lrmd client api needs to make an async version
* of register available. */
if (is_remote_lrmd_ra(agent, provider, NULL)) {
return lrm_state_find_or_create(rsc_id) ? pcmk_ok : -1;
}
return ((lrmd_t *) lrm_state->conn)->cmds->register_rsc(lrm_state->conn, rsc_id, class,
provider, agent, options);
}
int
lrm_state_unregister_rsc(lrm_state_t * lrm_state,
const char *rsc_id, enum lrmd_call_options options)
{
if (!lrm_state->conn) {
return -ENOTCONN;
}
/* optimize this... this function is a synced round trip from client to daemon.
* The crmd/lrm.c code path that uses this function should always treat it as an
* async operation. The lrmd client api needs to make an async version unreg available. */
if (is_remote_lrmd_ra(NULL, NULL, rsc_id)) {
lrm_state_destroy(rsc_id);
return pcmk_ok;
}
g_hash_table_remove(lrm_state->rsc_info_cache, rsc_id);
return ((lrmd_t *) lrm_state->conn)->cmds->unregister_rsc(lrm_state->conn, rsc_id, options);
}
-xmlNode *
-lrm_state_update_rsc_metadata(lrm_state_t *lrm_state, lrmd_rsc_info_t *rsc, const char *metadata_str)
+/*
+ * functions for sending alerts via local LRMD connection
+ */
+
+static GListPtr crmd_alert_list = NULL;
+
+void
+crmd_unpack_alerts(xmlNode *alerts)
{
- char *key = NULL;
- xmlNode *metadata = NULL;
+ pe_free_alert_list(crmd_alert_list);
+ crmd_alert_list = pe_unpack_alerts(alerts);
+}
- CRM_CHECK(lrm_state && rsc && metadata_str, return NULL);
+void
+crmd_alert_node_event(crm_node_t *node)
+{
+ lrm_state_t *lrm_state;
- key = crm_generate_ra_key(rsc->class, rsc->provider, rsc->type);
- if (!key) {
- return NULL;
+ if (crmd_alert_list == NULL) {
+ return;
}
- metadata = string2xml(metadata_str);
- if (!metadata) {
- crm_err("Metadata for %s (%s:%s:%s) is not valid XML", rsc->id, rsc->class, rsc->provider, rsc->type);
- free(key);
- return NULL;
+ lrm_state = lrm_state_find(fsa_our_uname);
+ if (lrm_state == NULL) {
+ return;
}
- g_hash_table_replace(lrm_state->metadata_cache, key, metadata);
-
- return metadata;
+ lrmd_send_node_alert((lrmd_t *) lrm_state->conn, crmd_alert_list,
+ node->uname, node->id, node->state);
}
-xmlNode *
-lrm_state_get_rsc_metadata(lrm_state_t *lrm_state, lrmd_rsc_info_t *rsc)
+void
+crmd_alert_fencing_op(stonith_event_t * e)
{
- char *key = NULL;
- xmlNode *metadata = NULL;
+ char *desc;
+ lrm_state_t *lrm_state;
- CRM_CHECK(lrm_state && rsc, return NULL);
+ if (crmd_alert_list == NULL) {
+ return;
+ }
- key = crm_generate_ra_key(rsc->class, rsc->provider, rsc->type);
- if (!key) {
- return NULL;
+ lrm_state = lrm_state_find(fsa_our_uname);
+ if (lrm_state == NULL) {
+ return;
+ }
+
+ desc = crm_strdup_printf("Operation %s of %s by %s for %s@%s: %s (ref=%s)",
+ e->action, e->target,
+ (e->executioner? e->executioner : "<no-one>"),
+ e->client_origin, e->origin,
+ pcmk_strerror(e->result), e->id);
+
+ lrmd_send_fencing_alert((lrmd_t *) lrm_state->conn, crmd_alert_list,
+ e->target, e->operation, desc, e->result);
+ free(desc);
+}
+
+void
+crmd_alert_resource_op(const char *node, lrmd_event_data_t * op)
+{
+ lrm_state_t *lrm_state;
+
+ if (crmd_alert_list == NULL) {
+ return;
}
- metadata = g_hash_table_lookup(lrm_state->metadata_cache, key);
- free(key);
+ lrm_state = lrm_state_find(fsa_our_uname);
+ if (lrm_state == NULL) {
+ return;
+ }
- return metadata;
+ lrmd_send_resource_alert((lrmd_t *) lrm_state->conn, crmd_alert_list, node,
+ op);
}
diff --git a/include/crm/common/util.h b/include/crm/common/util.h
index 04f7c3148c..e8b0dfe493 100644
--- a/include/crm/common/util.h
+++ b/include/crm/common/util.h
@@ -1,176 +1,180 @@
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* 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_COMMON_UTIL__H
# define CRM_COMMON_UTIL__H
/**
* \file
* \brief Utility functions
* \ingroup core
*/
# include <sys/types.h>
# include <stdlib.h>
# include <stdbool.h>
# include <limits.h>
# include <signal.h>
# include <sysexits.h>
# include <glib.h>
# include <crm/lrmd.h>
# if SUPPORT_HEARTBEAT
# include <heartbeat.h>
# else
# define NORMALNODE "normal"
# define ACTIVESTATUS "active"/* fully functional, and all links are up */
# define DEADSTATUS "dead"
/* Status of non-working link or machine */
# define PINGSTATUS "ping"
/* Status of a working ping node */
# define JOINSTATUS "join"
/* Status when an api client joins */
# define LEAVESTATUS "leave"
/* Status when an api client leaves */
# define ONLINESTATUS "online"/* Status of an online client */
# define OFFLINESTATUS "offline"
/* Status of an offline client */
# endif
/* public string functions (from strings.c) */
char *crm_itoa_stack(int an_int, char *buf, size_t len);
char *crm_itoa(int an_int);
gboolean crm_is_true(const char *s);
int crm_str_to_boolean(const char *s, int *ret);
int crm_parse_int(const char *text, const char *default_text);
char * crm_strip_trailing_newline(char *str);
gboolean crm_str_eq(const char *a, const char *b, gboolean use_case);
gboolean safe_str_neq(const char *a, const char *b);
guint crm_strcase_hash(gconstpointer v);
guint g_str_hash_traditional(gconstpointer v);
# define safe_str_eq(a, b) crm_str_eq(a, b, FALSE)
# define crm_str_hash g_str_hash_traditional
/* used with hash tables where case does not matter */
static inline gboolean
crm_strcase_equal(gconstpointer a, gconstpointer b)
{
return crm_str_eq((const char *) a, (const char *) b, FALSE);
}
/*!
* \brief Create hash table with dynamically allocated string keys/values
*
* \return Newly hash table
* \note It is the caller's responsibility to free the result, using
* g_hash_table_destroy().
*/
static inline GHashTable *
crm_str_table_new()
{
return g_hash_table_new_full(crm_str_hash, g_str_equal, free, free);
}
/*!
* \brief Create hash table with case-insensitive dynamically allocated string keys/values
*
* \return Newly hash table
* \note It is the caller's responsibility to free the result, using
* g_hash_table_destroy().
*/
static inline GHashTable *
crm_strcase_table_new()
{
return g_hash_table_new_full(crm_strcase_hash, crm_strcase_equal, free, free);
}
GHashTable *crm_str_table_dup(GHashTable *old_table);
# define crm_atoi(text, default_text) crm_parse_int(text, default_text)
/* public I/O functions (from io.c) */
void crm_build_path(const char *path_c, mode_t mode);
long long crm_get_msec(const char *input);
unsigned long long crm_get_interval(const char *input);
int char2score(const char *score);
char *score2char(int score);
char *score2char_stack(int score, char *buf, size_t len);
+/* public operation functions (from operations.c) */
+gboolean parse_op_key(const char *key, char **rsc_id, char **op_type,
+ int *interval);
+gboolean decode_transition_key(const char *key, char **uuid, int *action,
+ int *transition_id, int *target_rc);
+gboolean decode_transition_magic(const char *magic, char **uuid,
+ int *transition_id, int *action_id,
+ int *op_status, int *op_rc, int *target_rc);
+int rsc_op_expected_rc(lrmd_event_data_t *event);
+gboolean did_rsc_op_fail(lrmd_event_data_t *event, int target_rc);
+bool crm_op_needs_metadata(const char *rsc_class, const char *op);
+
int compare_version(const char *version1, const char *version2);
-gboolean parse_op_key(const char *key, char **rsc_id, char **op_type, int *interval);
-gboolean decode_transition_key(const char *key, char **uuid, int *action, int *transition_id,
- int *target_rc);
-gboolean decode_transition_magic(const char *magic, char **uuid, int *transition_id, int *action_id,
- int *op_status, int *op_rc, int *target_rc);
/* coverity[+kill] */
void crm_abort(const char *file, const char *function, int line,
const char *condition, gboolean do_core, gboolean do_fork);
static inline gboolean
is_not_set(long long word, long long bit)
{
return ((word & bit) == 0);
}
static inline gboolean
is_set(long long word, long long bit)
{
return ((word & bit) == bit);
}
static inline gboolean
is_set_any(long long word, long long bit)
{
return ((word & bit) != 0);
}
static inline guint
crm_hash_table_size(GHashTable * hashtable)
{
if (hashtable == NULL) {
return 0;
}
return g_hash_table_size(hashtable);
}
char *crm_meta_name(const char *field);
const char *crm_meta_value(GHashTable * hash, const char *field);
-int rsc_op_expected_rc(lrmd_event_data_t * event);
-gboolean did_rsc_op_fail(lrmd_event_data_t * event, int target_rc);
-
char *crm_md5sum(const char *buffer);
char *crm_generate_uuid(void);
bool crm_is_daemon_name(const char *name);
int crm_user_lookup(const char *name, uid_t * uid, gid_t * gid);
#ifdef HAVE_GNUTLS_GNUTLS_H
void crm_gnutls_global_init(void);
#endif
int crm_exit(int rc);
bool pcmk_acl_required(const char *user);
char *crm_generate_ra_key(const char *class, const char *provider, const char *type);
#endif
diff --git a/include/crm/lrmd.h b/include/crm/lrmd.h
index 45296a6183..a1adf936a0 100644
--- a/include/crm/lrmd.h
+++ b/include/crm/lrmd.h
@@ -1,499 +1,514 @@
/*
* Copyright (c) 2012 David Vossel <davidvossel@gmail.com>
*
* This library 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.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser 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 LRMD__H
# define LRMD__H
/**
* \file
* \brief Local Resource Manager
* \ingroup lrmd
*/
#include <stdbool.h>
#include <crm/services.h>
typedef struct lrmd_s lrmd_t;
typedef struct lrmd_key_value_s {
char *key;
char *value;
struct lrmd_key_value_s *next;
} lrmd_key_value_t;
#define LRMD_PROTOCOL_VERSION "1.1"
/* *INDENT-OFF* */
#define DEFAULT_REMOTE_KEY_LOCATION "/etc/pacemaker/authkey"
#define ALT_REMOTE_KEY_LOCATION "/etc/corosync/authkey"
#define DEFAULT_REMOTE_PORT 3121
#define DEFAULT_REMOTE_USERNAME "lrmd"
#define F_LRMD_OPERATION "lrmd_op"
#define F_LRMD_CLIENTNAME "lrmd_clientname"
#define F_LRMD_IS_IPC_PROVIDER "lrmd_is_ipc_provider"
#define F_LRMD_CLIENTID "lrmd_clientid"
#define F_LRMD_PROTOCOL_VERSION "lrmd_protocol_version"
#define F_LRMD_REMOTE_MSG_TYPE "lrmd_remote_msg_type"
#define F_LRMD_REMOTE_MSG_ID "lrmd_remote_msg_id"
#define F_LRMD_CALLBACK_TOKEN "lrmd_async_id"
#define F_LRMD_CALLID "lrmd_callid"
#define F_LRMD_CANCEL_CALLID "lrmd_cancel_callid"
#define F_LRMD_CALLOPTS "lrmd_callopt"
#define F_LRMD_CALLDATA "lrmd_calldata"
#define F_LRMD_RC "lrmd_rc"
#define F_LRMD_EXEC_RC "lrmd_exec_rc"
#define F_LRMD_OP_STATUS "lrmd_exec_op_status"
#define F_LRMD_TIMEOUT "lrmd_timeout"
#define F_LRMD_WATCHDOG "lrmd_watchdog"
#define F_LRMD_CLASS "lrmd_class"
#define F_LRMD_PROVIDER "lrmd_provider"
#define F_LRMD_TYPE "lrmd_type"
#define F_LRMD_ORIGIN "lrmd_origin"
#define F_LRMD_RSC_RUN_TIME "lrmd_run_time"
#define F_LRMD_RSC_RCCHANGE_TIME "lrmd_rcchange_time"
#define F_LRMD_RSC_EXEC_TIME "lrmd_exec_time"
#define F_LRMD_RSC_QUEUE_TIME "lrmd_queue_time"
#define F_LRMD_RSC_ID "lrmd_rsc_id"
#define F_LRMD_RSC_ACTION "lrmd_rsc_action"
#define F_LRMD_RSC_USERDATA_STR "lrmd_rsc_userdata_str"
#define F_LRMD_RSC_OUTPUT "lrmd_rsc_output"
#define F_LRMD_RSC_EXIT_REASON "lrmd_rsc_exit_reason"
#define F_LRMD_RSC_START_DELAY "lrmd_rsc_start_delay"
#define F_LRMD_RSC_INTERVAL "lrmd_rsc_interval"
#define F_LRMD_RSC_METADATA "lrmd_rsc_metadata_res"
#define F_LRMD_RSC_DELETED "lrmd_rsc_deleted"
#define F_LRMD_RSC "lrmd_rsc"
#define F_LRMD_ALERT_ID "lrmd_alert_id"
#define F_LRMD_ALERT_PATH "lrmd_alert_path"
#define F_LRMD_ALERT "lrmd_alert"
#define LRMD_OP_RSC_CHK_REG "lrmd_rsc_check_register"
#define LRMD_OP_RSC_REG "lrmd_rsc_register"
#define LRMD_OP_RSC_EXEC "lrmd_rsc_exec"
#define LRMD_OP_RSC_CANCEL "lrmd_rsc_cancel"
#define LRMD_OP_RSC_UNREG "lrmd_rsc_unregister"
#define LRMD_OP_RSC_INFO "lrmd_rsc_info"
#define LRMD_OP_RSC_METADATA "lrmd_rsc_metadata"
#define LRMD_OP_POKE "lrmd_rsc_poke"
#define LRMD_OP_NEW_CLIENT "lrmd_rsc_new_client"
#define LRMD_OP_CHECK "lrmd_check"
#define LRMD_OP_ALERT_EXEC "lrmd_alert_exec"
#define LRMD_IPC_OP_NEW "new"
#define LRMD_IPC_OP_DESTROY "destroy"
#define LRMD_IPC_OP_EVENT "event"
#define LRMD_IPC_OP_REQUEST "request"
#define LRMD_IPC_OP_RESPONSE "response"
#define LRMD_IPC_OP_SHUTDOWN_REQ "shutdown_req"
#define LRMD_IPC_OP_SHUTDOWN_ACK "shutdown_ack"
#define LRMD_IPC_OP_SHUTDOWN_NACK "shutdown_nack"
#define F_LRMD_IPC_OP "lrmd_ipc_op"
#define F_LRMD_IPC_IPC_SERVER "lrmd_ipc_server"
#define F_LRMD_IPC_SESSION "lrmd_ipc_session"
#define F_LRMD_IPC_CLIENT "lrmd_ipc_client"
#define F_LRMD_IPC_PROXY_NODE "lrmd_ipc_proxy_node"
#define F_LRMD_IPC_USER "lrmd_ipc_user"
#define F_LRMD_IPC_MSG "lrmd_ipc_msg"
#define F_LRMD_IPC_MSG_ID "lrmd_ipc_msg_id"
#define F_LRMD_IPC_MSG_FLAGS "lrmd_ipc_msg_flags"
#define T_LRMD "lrmd"
#define T_LRMD_REPLY "lrmd_reply"
#define T_LRMD_NOTIFY "lrmd_notify"
#define T_LRMD_IPC_PROXY "lrmd_ipc_proxy"
/* *INDENT-ON* */
/*!
* \brief Create a new local lrmd connection
*/
lrmd_t *lrmd_api_new(void);
/*!
* \brief Create a new remote lrmd connection using tls backend
*
* \param nodename name of remote node identified with this connection
* \param server name of server to connect to
* \param port port number to connect to
*
* \note nodename and server may be the same value.
*/
lrmd_t *lrmd_remote_api_new(const char *nodename, const char *server, int port);
/*!
* \brief Use after lrmd_poll returns 1 to read and dispatch a message
*
* \param[in,out] lrmd lrmd connection object
*
* \return TRUE if connection is still up, FALSE if disconnected
*/
bool lrmd_dispatch(lrmd_t * lrmd);
/*!
* \brief Poll for a specified timeout period to determine if a message
* is ready for dispatch.
* \retval 1 msg is ready
* \retval 0 timeout occurred
* \retval negative error code
*/
int lrmd_poll(lrmd_t * lrmd, int timeout);
/*!
* \brief Destroy lrmd object
*/
void lrmd_api_delete(lrmd_t * lrmd);
lrmd_key_value_t *lrmd_key_value_add(lrmd_key_value_t * kvp, const char *key, const char *value);
/* *INDENT-OFF* */
/* Reserved for future use */
enum lrmd_call_options {
lrmd_opt_none = 0x00000000,
/* lrmd_opt_sync_call = 0x00000001, //Not implemented, patches welcome. */
/*! Only notify the client originating a exec() the results */
lrmd_opt_notify_orig_only = 0x00000002,
/*! Drop recurring operations initiated by a client when client disconnects.
* This call_option is only valid when registering a resource. When used
* remotely with the pacemaker_remote daemon, this option means that recurring
* operations will be dropped once all the remote connections disconnect. */
lrmd_opt_drop_recurring = 0x00000003,
/*! Only send out notifications for recurring operations whenthe result changes */
lrmd_opt_notify_changes_only = 0x00000004,
};
enum lrmd_callback_event {
lrmd_event_register,
lrmd_event_unregister,
lrmd_event_exec_complete,
lrmd_event_disconnect,
lrmd_event_connect,
lrmd_event_poke,
lrmd_event_new_client,
};
/* *INDENT-ON* */
typedef struct lrmd_event_data_s {
/*! Type of event, register, unregister, call_completed... */
enum lrmd_callback_event type;
/*! The resource this event occurred on. */
const char *rsc_id;
/*! The action performed, start, stop, monitor... */
const char *op_type;
/*! The userdata string given do exec() api function */
const char *user_data;
/*! The client api call id associated with this event */
int call_id;
/*! The operation's timeout period in ms. */
int timeout;
/*! The operation's recurring interval in ms. */
int interval;
/*! The operation's start delay value in ms. */
int start_delay;
/*! This operation that just completed is on a deleted rsc. */
int rsc_deleted;
/*! The executed ra return code mapped to OCF */
enum ocf_exitcode rc;
/*! The lrmd status returned for exec_complete events */
int op_status;
/*! stdout from resource agent operation */
const char *output;
/*! Timestamp of when op ran */
unsigned int t_run;
/*! Timestamp of last rc change */
unsigned int t_rcchange;
/*! Time in length op took to execute */
unsigned int exec_time;
/*! Time in length spent in queue */
unsigned int queue_time;
/*! int connection result. Used for connection and poke events */
int connection_rc;
/* This is a GHashTable containing the
* parameters given to the operation */
void *params;
/*! client node name associated with this connection
* (used to match actions to the proper client when there are multiple)
*/
const char *remote_nodename;
/*! exit failure reason string from resource agent operation */
const char *exit_reason;
} lrmd_event_data_t;
lrmd_event_data_t *lrmd_copy_event(lrmd_event_data_t * event);
void lrmd_free_event(lrmd_event_data_t * event);
typedef struct lrmd_rsc_info_s {
char *id;
char *type;
char *class;
char *provider;
} lrmd_rsc_info_t;
lrmd_rsc_info_t *lrmd_copy_rsc_info(lrmd_rsc_info_t * rsc_info);
void lrmd_free_rsc_info(lrmd_rsc_info_t * rsc_info);
typedef void (*lrmd_event_callback) (lrmd_event_data_t * event);
typedef struct lrmd_list_s {
const char *val;
struct lrmd_list_s *next;
} lrmd_list_t;
void lrmd_list_freeall(lrmd_list_t * head);
void lrmd_key_value_freeall(lrmd_key_value_t * head);
typedef struct lrmd_api_operations_s {
/*!
* \brief Connect from the lrmd.
*
* \retval 0, success
* \retval negative error code on failure
*/
int (*connect) (lrmd_t * lrmd, const char *client_name, int *fd);
/*!
* \brief Establish an connection to lrmd, don't block while connecting.
* \note this function requires the use of mainloop.
*
* \note The is returned using the event callback.
* \note When this function returns 0, the callback will be invoked
* to report the final result of the connect.
* \retval 0, connect in progress, wait for event callback
* \retval -1, failure.
*/
int (*connect_async) (lrmd_t * lrmd, const char *client_name, int timeout /*ms */ );
/*!
* \brief Is connected to lrmd daemon?
*
* \retval 0, false
* \retval 1, true
*/
int (*is_connected) (lrmd_t * lrmd);
/*!
* \brief Poke lrmd connection to verify it is still capable of serving requests
* \note The response comes in the form of a poke event to the callback.
*
* \retval 0, wait for response in callback
* \retval -1, connection failure, callback may not be invoked
*/
int (*poke_connection) (lrmd_t * lrmd);
/*!
* \brief Disconnect from the lrmd.
*
* \retval 0, success
* \retval negative error code on failure
*/
int (*disconnect) (lrmd_t * lrmd);
/*!
* \brief Register a resource with the lrmd.
*
* \note Synchronous, guaranteed to occur in daemon before function returns.
*
* \retval 0, success
* \retval negative error code on failure
*/
int (*register_rsc) (lrmd_t * lrmd,
const char *rsc_id,
const char *class,
const char *provider, const char *agent, enum lrmd_call_options options);
/*!
* \brief Retrieve registration info for a rsc
*
* \retval info on success
* \retval NULL on failure
*/
lrmd_rsc_info_t *(*get_rsc_info) (lrmd_t * lrmd,
const char *rsc_id, enum lrmd_call_options options);
/*!
* \brief Unregister a resource from the lrmd.
*
* \note All pending and recurring operations will be cancelled
* automatically.
*
* \note Synchronous, guaranteed to occur in daemon before function returns.
*
* \retval 0, success
* \retval -1, success, but operations are currently executing on the rsc which will
* return once they are completed.
* \retval negative error code on failure
*
*/
int (*unregister_rsc) (lrmd_t * lrmd, const char *rsc_id, enum lrmd_call_options options);
/*!
* \brief Sets the callback to receive lrmd events on.
*/
void (*set_callback) (lrmd_t * lrmd, lrmd_event_callback callback);
/*!
* \brief Issue a command on a resource
*
* \note Asynchronous, command is queued in daemon on function return, but
* execution of command is not synced.
*
* \note Operations on individual resources are guaranteed to occur
* in the order the client api calls them in.
*
* \note Operations between different resources are not guaranteed
* to occur in any specific order in relation to one another
* regardless of what order the client api is called in.
* \retval call_id to track async event result on success
* \retval negative error code on failure
*/
int (*exec) (lrmd_t * lrmd, const char *rsc_id, const char *action, const char *userdata, /* userdata string given back in event notification */
int interval, /* ms */
int timeout, /* ms */
int start_delay, /* ms */
enum lrmd_call_options options, lrmd_key_value_t * params); /* ownership of params is given up to api here */
/*!
* \brief Cancel a recurring command.
*
* \note Synchronous, guaranteed to occur in daemon before function returns.
*
* \note The cancel is completed async from this call.
* We can be guaranteed the cancel has completed once
* the callback receives an exec_complete event with
* the lrmd_op_status signifying that the operation is
* cancelled.
* \note For each resource, cancel operations and exec operations
* are processed in the order they are received.
* It is safe to assume that for a single resource, a cancel
* will occur in the lrmd before an exec if the client's cancel
* api call occurs before the exec api call.
*
* It is not however safe to assume any operation on one resource will
* occur before an operation on another resource regardless of
* the order the client api is called in.
*
* \retval 0, cancel command sent.
* \retval negative error code on failure
*/
int (*cancel) (lrmd_t * lrmd, const char *rsc_id, const char *action, int interval);
/*!
- * \brief Get the metadata documentation for a resource.
+ * \brief Get resource metadata for a specified resource agent
*
- * \note Value is returned in output. Output must be freed when set
+ * \param[in] lrmd LRMD connection (unused)
+ * \param[in] class Resource agent class
+ * \param[in] provider Resource agent provider
+ * \param[in] agent Resource agent type
+ * \param[out] output Metadata will be stored here (must not be NULL)
+ * \param[in] options Options to use with any LRMD API calls (unused)
+ *
+ * \note Caller is responsible for freeing output. This call is currently
+ * always synchronous (blocking), and always done directly by the
+ * library (not via the LRMD connection). This means that it is based
+ * on the local host environment, even if the lrmd connection is to a
+ * remote node, so (for most resource agent classes) this will fail if
+ * the agent is not installed locally. This also means that, if an
+ * external agent must be executed, it will be executed by the
+ * caller's user, not the lrmd's.
+ * \todo Add a metadata call to the LRMD API and let the server handle this.
*
* \retval lrmd_ok success
* \retval negative error code on failure
*/
int (*get_metadata) (lrmd_t * lrmd,
const char *class,
const char *provider,
const char *agent, char **output, enum lrmd_call_options options);
/*!
* \brief Retrieve a list of installed resource agents.
*
* \note if class is not provided, all known agents will be returned
* \note list must be freed using lrmd_list_freeall()
*
* \retval num items in list on success
* \retval negative error code on failure
*/
int (*list_agents) (lrmd_t * lrmd, lrmd_list_t ** agents, const char *class,
const char *provider);
/*!
* \brief Retrieve a list of resource agent providers
*
* \note When the agent is provided, only the agent's provider will be returned
* \note When no agent is supplied, all providers will be returned.
* \note List must be freed using lrmd_list_freeall()
*
* \retval num items in list on success
* \retval negative error code on failure
*/
int (*list_ocf_providers) (lrmd_t * lrmd, const char *agent, lrmd_list_t ** providers);
/*!
* \brief Retrieve a list of standards supported by this machine/installation
*
* \note List must be freed using lrmd_list_freeall()
*
* \retval num items in list on success
* \retval negative error code on failure
*/
int (*list_standards) (lrmd_t * lrmd, lrmd_list_t ** standards);
/*!
* \brief Execute an alert agent
*
* \note Asynchronous, command is queued in daemon on function return, but
* execution of command is not synced.
*
* \note Operations on individual alerts are guaranteed to occur
* in the order the client api calls them in.
*
* \note Operations between different alerts are not guaranteed
* to occur in any specific order in relation to one another
* regardless of what order the client api is called in.
* \retval call_id to track async event result on success
* \retval negative error code on failure
*/
int (*exec_alert) (lrmd_t *lrmd, const char *alert_id,
const char *alert_path, int timeout, /* ms */
lrmd_key_value_t *params); /* ownership of params is given up to api here */
} lrmd_api_operations_t;
struct lrmd_s {
lrmd_api_operations_t *cmds;
void *private;
};
static inline const char *
lrmd_event_type2str(enum lrmd_callback_event type)
{
switch (type) {
case lrmd_event_register:
return "register";
case lrmd_event_unregister:
return "unregister";
case lrmd_event_exec_complete:
return "exec_complete";
case lrmd_event_disconnect:
return "disconnect";
case lrmd_event_connect:
return "connect";
case lrmd_event_poke:
return "poke";
case lrmd_event_new_client:
return "new_client";
}
return "unknown";
}
#endif
diff --git a/include/crm/lrmd_alerts_internal.h b/include/crm/lrmd_alerts_internal.h
index cdd345dc09..43c00b1d70 100644
--- a/include/crm/lrmd_alerts_internal.h
+++ b/include/crm/lrmd_alerts_internal.h
@@ -1,39 +1,36 @@
/*
* Copyright (C) 2015 Andrew Beekhof <andrew@beekhof.net>
*
* 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 LRMD_ALERT_INTERNAL_H
#define LRMD_ALERT_INTERNAL_H
#include <glib.h>
#include <crm/lrmd.h>
-int lrmd_send_attribute_alert(GList *alert_list,
- lrmd_t *(*lrmd_connect_func)(void),
+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 lrmd_send_node_alert(GList *alert_list, lrmd_t *(*lrmd_connect_func)(void),
+int lrmd_send_node_alert(lrmd_t *lrmd, GList *alert_list,
const char *node, uint32_t nodeid, const char *state);
-int lrmd_send_fencing_alert(GList *alert_list,
- lrmd_t *(*lrmd_connect_func)(void),
+int lrmd_send_fencing_alert(lrmd_t *lrmd, GList *alert_list,
const char *target, const char *task,
const char *desc, int op_rc);
-int lrmd_send_resource_alert(GList *alert_list,
- lrmd_t *(*lrmd_connect_func)(void),
+int lrmd_send_resource_alert(lrmd_t *lrmd, GList *alert_list,
const char *node, lrmd_event_data_t *op);
#endif
diff --git a/include/crm/pengine/status.h b/include/crm/pengine/status.h
index cfb7d9704f..7c490791f5 100644
--- a/include/crm/pengine/status.h
+++ b/include/crm/pengine/status.h
@@ -1,485 +1,494 @@
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* 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 PENGINE_STATUS__H
# define PENGINE_STATUS__H
# include <glib.h>
# include <stdbool.h>
# include <crm/common/iso8601.h>
# include <crm/pengine/common.h>
typedef struct node_s pe_node_t;
typedef struct node_s node_t;
typedef struct pe_action_s action_t;
typedef struct pe_action_s pe_action_t;
typedef struct resource_s resource_t;
typedef struct ticket_s ticket_t;
typedef enum no_quorum_policy_e {
no_quorum_freeze,
no_quorum_stop,
no_quorum_ignore,
no_quorum_suicide
} no_quorum_policy_t;
enum node_type {
node_ping,
node_member,
node_remote
};
enum pe_restart {
pe_restart_restart,
pe_restart_ignore
};
enum pe_find {
pe_find_renamed = 0x001,
pe_find_clone = 0x004,
pe_find_current = 0x008,
pe_find_inactive = 0x010,
};
# define pe_flag_have_quorum 0x00000001ULL
# define pe_flag_symmetric_cluster 0x00000002ULL
# define pe_flag_is_managed_default 0x00000004ULL
# define pe_flag_maintenance_mode 0x00000008ULL
# define pe_flag_stonith_enabled 0x00000010ULL
# define pe_flag_have_stonith_resource 0x00000020ULL
# define pe_flag_enable_unfencing 0x00000040ULL
# define pe_flag_concurrent_fencing 0x00000080ULL
# define pe_flag_stop_rsc_orphans 0x00000100ULL
# define pe_flag_stop_action_orphans 0x00000200ULL
# define pe_flag_stop_everything 0x00000400ULL
# define pe_flag_start_failure_fatal 0x00001000ULL
# define pe_flag_remove_after_stop 0x00002000ULL
# define pe_flag_startup_probes 0x00010000ULL
# define pe_flag_have_status 0x00020000ULL
# define pe_flag_have_remote_nodes 0x00040000ULL
# define pe_flag_quick_location 0x00100000ULL
# define pe_flag_sanitized 0x00200000ULL
typedef struct pe_working_set_s {
xmlNode *input;
crm_time_t *now;
/* options extracted from the input */
char *dc_uuid;
node_t *dc_node;
const char *stonith_action;
const char *placement_strategy;
unsigned long long flags;
int stonith_timeout;
int default_resource_stickiness;
no_quorum_policy_t no_quorum_policy;
GHashTable *config_hash;
GHashTable *tickets;
GHashTable *singletons; /* Actions for which there can be only one - ie. fence nodeX */
GListPtr nodes;
GListPtr resources;
GListPtr placement_constraints;
GListPtr ordering_constraints;
GListPtr colocation_constraints;
GListPtr ticket_constraints;
GListPtr actions;
xmlNode *failed;
xmlNode *op_defaults;
xmlNode *rsc_defaults;
/* stats */
int num_synapse;
int max_valid_nodes;
int order_id;
int action_id;
/* final output */
xmlNode *graph;
GHashTable *template_rsc_sets;
const char *localhost;
GHashTable *tags;
int blocked_resources;
int disabled_resources;
} pe_working_set_t;
struct node_shared_s {
const char *id;
const char *uname;
/* @TODO convert these flags (and the ones at the end) into a bitfield */
gboolean online;
gboolean standby;
gboolean standby_onfail;
gboolean pending;
gboolean unclean;
gboolean unseen;
gboolean shutdown;
gboolean expected_up;
gboolean is_dc;
int num_resources;
GListPtr running_rsc; /* resource_t* */
GListPtr allocated_rsc; /* resource_t* */
resource_t *remote_rsc;
GHashTable *attrs; /* char* => char* */
enum node_type type;
GHashTable *utilization;
/*! cache of calculated rsc digests for this node. */
GHashTable *digest_cache;
gboolean maintenance;
gboolean rsc_discovery_enabled;
gboolean remote_requires_reset;
gboolean remote_was_fenced;
gboolean remote_maintenance; /* what the remote-rsc is thinking */
gboolean unpacked;
};
struct node_s {
int weight;
gboolean fixed;
int count;
struct node_shared_s *details;
int rsc_discover_mode;
};
# include <crm/pengine/complex.h>
# define pe_rsc_orphan 0x00000001ULL
# define pe_rsc_managed 0x00000002ULL
# define pe_rsc_block 0x00000004ULL /* Further operations are prohibited due to failure policy */
# define pe_rsc_orphan_container_filler 0x00000008ULL
# define pe_rsc_notify 0x00000010ULL
# define pe_rsc_unique 0x00000020ULL
# define pe_rsc_fence_device 0x00000040ULL
# define pe_rsc_provisional 0x00000100ULL
# define pe_rsc_allocating 0x00000200ULL
# define pe_rsc_merging 0x00000400ULL
# define pe_rsc_munging 0x00000800ULL
# define pe_rsc_try_reload 0x00001000ULL
# define pe_rsc_reload 0x00002000ULL
# define pe_rsc_allow_remote_remotes 0x00004000ULL
# define pe_rsc_failed 0x00010000ULL
# define pe_rsc_shutdown 0x00020000ULL
# define pe_rsc_runnable 0x00040000ULL
# define pe_rsc_start_pending 0x00080000ULL
# define pe_rsc_starting 0x00100000ULL
# define pe_rsc_stopping 0x00200000ULL
# define pe_rsc_migrating 0x00400000ULL
# define pe_rsc_allow_migrate 0x00800000ULL
# define pe_rsc_failure_ignored 0x01000000ULL
# define pe_rsc_unexpectedly_running 0x02000000ULL
# define pe_rsc_maintenance 0x04000000ULL
# define pe_rsc_needs_quorum 0x10000000ULL
# define pe_rsc_needs_fencing 0x20000000ULL
# define pe_rsc_needs_unfencing 0x40000000ULL
# define pe_rsc_have_unfencing 0x80000000ULL /* obsolete (not set or used by cluster) */
enum pe_graph_flags {
pe_graph_none = 0x00000,
pe_graph_updated_first = 0x00001,
pe_graph_updated_then = 0x00002,
pe_graph_disable = 0x00004,
};
/* *INDENT-OFF* */
enum pe_action_flags {
pe_action_pseudo = 0x00001,
pe_action_runnable = 0x00002,
pe_action_optional = 0x00004,
pe_action_print_always = 0x00008,
pe_action_have_node_attrs = 0x00010,
pe_action_failure_is_fatal = 0x00020, /* no longer used, here for API compatibility */
pe_action_implied_by_stonith = 0x00040,
pe_action_migrate_runnable = 0x00080,
pe_action_dumped = 0x00100,
pe_action_processed = 0x00200,
pe_action_clear = 0x00400,
pe_action_dangle = 0x00800,
/* This action requires one or more of its dependencies to be runnable.
* We use this to clear the runnable flag before checking dependencies.
*/
pe_action_requires_any = 0x01000,
pe_action_reschedule = 0x02000,
pe_action_tracking = 0x04000,
};
/* *INDENT-ON* */
struct resource_s {
char *id;
char *clone_name;
xmlNode *xml;
xmlNode *orig_xml;
xmlNode *ops_xml;
resource_t *parent;
void *variant_opaque;
enum pe_obj_types variant;
resource_object_functions_t *fns;
resource_alloc_functions_t *cmds;
enum rsc_recovery_type recovery_type;
enum pe_restart restart_type;
int priority;
int stickiness;
int sort_index;
int failure_timeout;
int effective_priority;
int migration_threshold;
gboolean is_remote_node;
unsigned long long flags;
GListPtr rsc_cons_lhs; /* rsc_colocation_t* */
GListPtr rsc_cons; /* rsc_colocation_t* */
GListPtr rsc_location; /* rsc_to_node_t* */
GListPtr actions; /* action_t* */
GListPtr rsc_tickets; /* rsc_ticket* */
node_t *allocated_to;
GListPtr running_on; /* node_t* */
GHashTable *known_on; /* node_t* */
GHashTable *allowed_nodes; /* node_t* */
enum rsc_role_e role;
enum rsc_role_e next_role;
GHashTable *meta;
GHashTable *parameters;
GHashTable *utilization;
GListPtr children; /* resource_t* */
GListPtr dangling_migrations; /* node_t* */
node_t *partial_migration_target;
node_t *partial_migration_source;
resource_t *container;
GListPtr fillers;
char *pending_task;
const char *isolation_wrapper;
gboolean exclusive_discover;
int remote_reconnect_interval;
pe_working_set_t *cluster;
xmlNode *versioned_parameters;
};
+// Used as action->action_details if action->rsc is not NULL
+typedef struct pe_rsc_action_details_s {
+ xmlNode *versioned_parameters;
+ xmlNode *versioned_meta;
+} pe_rsc_action_details_t;
+
struct pe_action_s {
int id;
int priority;
resource_t *rsc;
node_t *node;
xmlNode *op_entry;
char *task;
char *uuid;
char *cancel_task;
enum pe_action_flags flags;
enum rsc_start_requirement needs;
enum action_fail_response on_fail;
enum rsc_role_e fail_role;
action_t *pre_notify;
action_t *pre_notified;
action_t *post_notify;
action_t *post_notified;
int seen_count;
GHashTable *meta;
GHashTable *extra;
/*
* These two varables are associated with the constraint logic
* that involves first having one or more actions runnable before
* then allowing this action to execute.
*
* These varables are used with features such as 'clone-min' which
* requires at minimum X number of cloned instances to be running
* before an order dependency can run. Another option that uses
* this is 'require-all=false' in ordering constrants. This option
* says "only require one instance of a resource to start before
* allowing dependencies to start" -- basically, require-all=false is
* the same as clone-min=1.
*/
/* current number of known runnable actions in the before list. */
int runnable_before;
/* the number of "before" runnable actions required for this action
* to be considered runnable */
int required_runnable_before;
GListPtr actions_before; /* action_wrapper_t* */
GListPtr actions_after; /* action_wrapper_t* */
- xmlNode *versioned_parameters;
- xmlNode *versioned_meta;
+ /* Some of the above fields could be moved to the details,
+ * except for API backward compatibility.
+ */
+ void *action_details; // varies by type of action
};
struct ticket_s {
char *id;
gboolean granted;
time_t last_granted;
gboolean standby;
GHashTable *state;
};
typedef struct tag_s {
char *id;
GListPtr refs;
} tag_t;
enum pe_link_state {
pe_link_not_dumped,
pe_link_dumped,
pe_link_dup,
};
/* *INDENT-OFF* */
enum pe_ordering {
pe_order_none = 0x0, /* deleted */
pe_order_optional = 0x1, /* pure ordering, nothing implied */
pe_order_apply_first_non_migratable = 0x2, /* Only apply this constraint's ordering if first is not migratable. */
pe_order_implies_first = 0x10, /* If 'then' is required, ensure 'first' is too */
pe_order_implies_then = 0x20, /* If 'first' is required, ensure 'then' is too */
pe_order_implies_first_master = 0x40, /* Imply 'first' is required when 'then' is required and then's rsc holds Master role. */
/* first requires then to be both runnable and migrate runnable. */
pe_order_implies_first_migratable = 0x80,
pe_order_runnable_left = 0x100, /* 'then' requires 'first' to be runnable */
pe_order_pseudo_left = 0x200, /* 'then' can only be pseudo if 'first' is runnable */
pe_order_implies_then_on_node = 0x400, /* If 'first' is required on 'nodeX',
* ensure instances of 'then' on 'nodeX' are too.
* Only really useful if 'then' is a clone and 'first' is not
*/
pe_order_restart = 0x1000, /* 'then' is runnable if 'first' is optional or runnable */
pe_order_stonith_stop = 0x2000, /* only applies if the action is non-pseudo */
pe_order_serialize_only = 0x4000, /* serialize */
pe_order_same_node = 0x8000, /* applies only if 'first' and 'then' are on same node */
pe_order_implies_first_printed = 0x10000, /* Like ..implies_first but only ensures 'first' is printed, not mandatory */
pe_order_implies_then_printed = 0x20000, /* Like ..implies_then but only ensures 'then' is printed, not mandatory */
pe_order_asymmetrical = 0x100000, /* Indicates asymmetrical one way ordering constraint. */
pe_order_load = 0x200000, /* Only relevant if... */
pe_order_one_or_more = 0x400000, /* 'then' is runnable only if one or more of its dependencies are too */
pe_order_anti_colocation = 0x800000,
pe_order_preserve = 0x1000000, /* Hack for breaking user ordering constraints with container resources */
pe_order_trace = 0x4000000, /* test marker */
};
/* *INDENT-ON* */
typedef struct action_wrapper_s action_wrapper_t;
struct action_wrapper_s {
enum pe_ordering type;
enum pe_link_state state;
action_t *action;
};
const char *rsc_printable_id(resource_t *rsc);
gboolean cluster_status(pe_working_set_t * data_set);
void set_working_set_defaults(pe_working_set_t * data_set);
void cleanup_calculations(pe_working_set_t * data_set);
resource_t *pe_find_resource(GListPtr rsc_list, const char *id_rh);
node_t *pe_find_node(GListPtr node_list, const char *uname);
node_t *pe_find_node_id(GListPtr node_list, const char *id);
node_t *pe_find_node_any(GListPtr node_list, const char *id, const char *uname);
GListPtr find_operations(const char *rsc, const char *node, gboolean active_filter,
pe_working_set_t * data_set);
+pe_rsc_action_details_t *pe_rsc_action_details(pe_action_t *action);
/*!
* \brief Check whether a resource is any clone type
*
* \param[in] rsc Resource to check
*
* \return TRUE if resource is clone, FALSE otherwise
*/
static inline bool
pe_rsc_is_clone(resource_t *rsc)
{
return rsc && ((rsc->variant == pe_clone) || (rsc->variant == pe_master));
}
/*!
* \brief Check whether a resource is a globally unique clone
*
* \param[in] rsc Resource to check
*
* \return TRUE if resource is unique clone, FALSE otherwise
*/
static inline bool
pe_rsc_is_unique_clone(resource_t *rsc)
{
return pe_rsc_is_clone(rsc) && is_set(rsc->flags, pe_rsc_unique);
}
/*!
* \brief Check whether a resource is an anonymous clone
*
* \param[in] rsc Resource to check
*
* \return TRUE if resource is anonymous clone, FALSE otherwise
*/
static inline bool
pe_rsc_is_anon_clone(resource_t *rsc)
{
return pe_rsc_is_clone(rsc) && is_not_set(rsc->flags, pe_rsc_unique);
}
#endif
diff --git a/include/crm/transition.h b/include/crm/transition.h
index 44cb7ddd65..f064c68e26 100644
--- a/include/crm/transition.h
+++ b/include/crm/transition.h
@@ -1,165 +1,162 @@
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* 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_TRANSITION__H
# define CRM_TRANSITION__H
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
typedef enum {
action_type_pseudo,
action_type_rsc,
action_type_crm
} action_type_e;
typedef struct te_timer_s crm_action_timer_t;
typedef struct crm_graph_s crm_graph_t;
typedef struct synapse_s {
int id;
int priority;
gboolean ready;
gboolean failed;
gboolean executed;
gboolean confirmed;
GListPtr actions; /* crm_action_t* */
GListPtr inputs; /* crm_action_t* */
crm_graph_t *graph;
} synapse_t;
typedef struct crm_action_s {
int id;
int timeout;
int interval;
GHashTable *params;
action_type_e type;
crm_action_timer_t *timer;
synapse_t *synapse;
gboolean sent_update; /* sent to the CIB */
gboolean executed; /* sent to the CRM */
gboolean confirmed;
gboolean failed;
gboolean can_fail;
xmlNode *xml;
char *task;
lrmd_rsc_info_t *rsc_info;
} crm_action_t;
/* @COMPAT: This enum has deprecated. It has apparently never been used in a
* Pacemaker release, but it is kept for API backward compatibility.
*/
enum timer_reason {
timeout_action,
timeout_action_warn,
timeout_abort,
};
struct te_timer_s {
int source_id;
int timeout;
enum timer_reason reason; /* @COMPAT: unused, API backward compatibility */
crm_action_t *action;
};
/* order matters here */
enum transition_action {
tg_done,
tg_stop,
tg_restart,
tg_shutdown,
};
struct crm_graph_s {
int id;
char *source;
int abort_priority;
gboolean complete;
const char *abort_reason;
enum transition_action completion_action;
int num_actions;
int num_synapses;
int batch_limit;
int network_delay;
int stonith_timeout;
int transition_timeout;
int fired;
int pending;
int skipped;
int completed;
int incomplete;
GListPtr synapses; /* synpase_t* */
int migration_limit;
int max_action_id;
};
typedef struct crm_graph_functions_s {
gboolean(*pseudo) (crm_graph_t * graph, crm_action_t * action);
gboolean(*rsc) (crm_graph_t * graph, crm_action_t * action);
gboolean(*crmd) (crm_graph_t * graph, crm_action_t * action);
gboolean(*stonith) (crm_graph_t * graph, crm_action_t * action);
gboolean(*allowed) (crm_graph_t * graph, crm_action_t * action);
} crm_graph_functions_t;
enum transition_status {
transition_active,
transition_pending, /* active but no actions performed this time */
transition_complete,
transition_stopped,
transition_terminated,
transition_action_failed,
transition_failed,
};
void set_default_graph_functions(void);
void set_graph_functions(crm_graph_functions_t * fns);
crm_graph_t *unpack_graph(xmlNode * xml_graph, const char *reference);
int run_graph(crm_graph_t * graph);
gboolean update_graph(crm_graph_t * graph, crm_action_t * action);
void destroy_graph(crm_graph_t * graph);
const char *transition_status(enum transition_status state);
void print_graph(unsigned int log_level, crm_graph_t * graph);
void print_action(int log_level, const char *prefix, crm_action_t * action);
bool update_abort_priority(crm_graph_t * graph, int priority,
enum transition_action action, const char *abort_reason);
const char *actiontype2text(action_type_e type);
lrmd_event_data_t *convert_graph_action(xmlNode * resource, crm_action_t * action, int status,
int rc);
-void crm_register_cache_check_fn(gboolean (*fn)(lrmd_rsc_info_t *rsc, const char *node_name));
-void crm_unregister_cache_check_fn(void);
-
#endif
diff --git a/include/crm_internal.h b/include/crm_internal.h
index e40b4ba625..acfca0778d 100644
--- a/include/crm_internal.h
+++ b/include/crm_internal.h
@@ -1,390 +1,388 @@
/* crm_internal.h */
/*
* Copyright (C) 2006 - 2008
* Andrew Beekhof <andrew@beekhof.net>
*
* 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 program 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 Lesser 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_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>
/* Dynamic loading of libraries */
void *find_library_function(void **handle, const char *lib, const char *fn, int fatal);
void *convert_const_pointer(const void *ptr);
/* For ACLs */
char *uid2username(uid_t uid);
void determine_request_user(const char *user, xmlNode * request, const char *field);
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);
int crm_help(char cmd, int 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);
gboolean check_sbd_timeout(const char *value);
-/* Shared PE/crmd functionality */
-void filter_action_parameters(xmlNode * param_set, const char *version);
-
-/* Resource operation updates */
-xmlNode *create_operation_update(xmlNode * parent, lrmd_event_data_t * event,
- const char * caller_version, int target_rc, const char * node,
- const char * origin, int level);
-
/* char2score */
extern int node_score_red;
extern int node_score_green;
extern int node_score_yellow;
extern int node_score_infinity;
/* Assorted convenience functions */
int crm_pid_active(long pid, const char *daemon);
void crm_make_daemon(const char *name, gboolean daemonize, const char *pidfile);
+/* from operations.c */
char *generate_op_key(const char *rsc_id, const char *op_type, int interval);
char *generate_notify_key(const char *rsc_id, const char *notify_type, const char *op_type);
char *generate_transition_magic_v202(const char *transition_key, int op_status);
char *generate_transition_magic(const char *transition_key, int op_status, int op_rc);
char *generate_transition_key(int action, int transition_id, int target_rc, const char *node);
+void filter_action_parameters(xmlNode *param_set, const char *version);
+xmlNode *create_operation_update(xmlNode *parent, lrmd_event_data_t *event,
+ const char *caller_version, int target_rc,
+ const char *node, const char *origin,
+ int level);
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);
/*! remote tcp/tls helper functions */
typedef struct crm_remote_s crm_remote_t;
int crm_remote_send(crm_remote_t * remote, xmlNode * msg);
int crm_remote_ready(crm_remote_t * remote, int total_timeout /*ms */ );
gboolean crm_remote_recv(crm_remote_t * remote, int total_timeout /*ms */ , int *disconnected);
xmlNode *crm_remote_parse_buffer(crm_remote_t * remote);
int crm_remote_tcp_connect(const char *host, int port);
int crm_remote_tcp_connect_async(const char *host, int port, int timeout, /*ms */
int *timer_id, void *userdata, void (*callback) (void *userdata, int sock));
int crm_remote_accept(int ssock);
void crm_sockaddr2str(void *sa, char *s);
# ifdef HAVE_GNUTLS_GNUTLS_H
/*!
* \internal
* \brief Initiate the client handshake after establishing the tcp socket.
* \note This is a blocking function, it will block until the entire handshake
* is complete or until the timeout period is reached.
* \retval 0 success
* \retval negative, failure
*/
int crm_initiate_client_tls_handshake(crm_remote_t * remote, int timeout_ms);
/*!
* \internal
* \brief Create client or server session for anon DH encryption credentials
* \param sock, the socket the session will use for transport
* \param type, GNUTLS_SERVER or GNUTLS_CLIENT
* \param credentials, gnutls_anon_server_credentials_t or gnutls_anon_client_credentials_t
*
* \retval gnutls_session_t * on success
* \retval NULL on failure
*/
void *crm_create_anon_tls_session(int sock, int type, void *credentials);
/*!
* \internal
* \brief Create client or server session for PSK credentials
* \param sock, the socket the session will use for transport
* \param type, GNUTLS_SERVER or GNUTLS_CLIENT
* \param credentials, gnutls_psk_server_credentials_t or gnutls_osk_client_credentials_t
*
* \retval gnutls_session_t * on success
* \retval NULL on failure
*/
void *create_psk_tls_session(int csock, int type, void *credentials);
# endif
# define REMOTE_MSG_TERMINATOR "\r\n\r\n"
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);
void sysrq_init(void);
pid_t pcmk_locate_sbd(void);
long crm_pidfile_inuse(const char *filename, long mypid, const char *daemon);
long crm_read_pidfile(const char *filename);
# 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 attrd_channel T_ATTRD
# 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_IGNORE_LOCALLY "attr_ignore_locally"
# 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"
/* 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"
# if SUPPORT_COROSYNC
# if CS_USES_LIBQB
# 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
# include <corosync/corodefs.h>
# include <corosync/coroipcc.h>
# include <corosync/coroipc_types.h>
typedef coroipc_request_header_t cs_ipc_header_request_t;
typedef coroipc_response_header_t cs_ipc_header_response_t;
# endif
# 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/Makefile.am b/lib/common/Makefile.am
index d8b593943a..77e50d5f94 100644
--- a/lib/common/Makefile.am
+++ b/lib/common/Makefile.am
@@ -1,49 +1,50 @@
#
# Copyright (C) 2004 Andrew Beekhof
#
# 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 program 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 program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
include $(top_srcdir)/Makefile.common
AM_CPPFLAGS += -I$(top_builddir)/lib/gnu -I$(top_srcdir)/lib/gnu
## libraries
lib_LTLIBRARIES = libcrmcommon.la
# Can't use -Wcast-qual here because glib insists on pretending things are const
# when they're not and thus we need the crm_element_value_const() hack
# s390 needs -fPIC
# s390-suse-linux/bin/ld: .libs/ipc.o: relocation R_390_PC32DBL against `__stack_chk_fail@@GLIBC_2.4' can not be used when making a shared object; recompile with -fPIC
CFLAGS = $(CFLAGS_COPY:-Wcast-qual=) -fPIC
libcrmcommon_la_LDFLAGS = -version-info 10:0:7
libcrmcommon_la_CFLAGS = $(CFLAGS_HARDENED_LIB)
libcrmcommon_la_LDFLAGS += $(LDFLAGS_HARDENED_LIB)
libcrmcommon_la_LIBADD = @LIBADD_DL@ $(GNUTLSLIBS)
libcrmcommon_la_SOURCES = compat.c digest.c ipc.c io.c procfs.c utils.c xml.c \
iso8601.c remote.c mainloop.c logging.c watchdog.c \
- schemas.c strings.c xpath.c attrd_client.c alerts.c
+ schemas.c strings.c xpath.c attrd_client.c alerts.c \
+ operations.c
if BUILD_CIBSECRETS
libcrmcommon_la_SOURCES += cib_secrets.c
endif
libcrmcommon_la_SOURCES += $(top_builddir)/lib/gnu/md5.c
clean-generic:
rm -f *.log *.debug *.xml *~
diff --git a/lib/common/operations.c b/lib/common/operations.c
new file mode 100644
index 0000000000..0f6bee6d15
--- /dev/null
+++ b/lib/common/operations.c
@@ -0,0 +1,637 @@
+/*
+ * Copyright (C) 2004-2017 Andrew Beekhof <andrew@beekhof.net>
+ *
+ * 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 <stdlib.h>
+#include <ctype.h>
+
+#include <crm/crm.h>
+#include <crm/lrmd.h>
+#include <crm/msg_xml.h>
+#include <crm/common/xml.h>
+#include <crm/common/util.h>
+
+/*!
+ * \brief Generate an operation key
+ *
+ * \param[in] rsc_id ID of resource being operated on
+ * \param[in] op_type Operation name
+ * \param[in] interval Operation interval
+ *
+ * \return Newly allocated memory containing operation key as string
+ *
+ * \note It is the caller's responsibility to free() the result.
+ */
+char *
+generate_op_key(const char *rsc_id, const char *op_type, int interval)
+{
+ CRM_ASSERT(rsc_id != NULL);
+ CRM_ASSERT(op_type != NULL);
+ CRM_ASSERT(interval >= 0);
+ return crm_strdup_printf("%s_%s_%d", rsc_id, op_type, interval);
+}
+
+gboolean
+parse_op_key(const char *key, char **rsc_id, char **op_type, int *interval)
+{
+ char *notify = NULL;
+ char *mutable_key = NULL;
+ char *mutable_key_ptr = NULL;
+ int len = 0, offset = 0, ch = 0;
+
+ CRM_CHECK(key != NULL, return FALSE);
+
+ *interval = 0;
+ len = strlen(key);
+ offset = len - 1;
+
+ crm_trace("Source: %s", key);
+
+ while (offset > 0 && isdigit(key[offset])) {
+ int digits = len - offset;
+
+ ch = key[offset] - '0';
+ CRM_CHECK(ch < 10, return FALSE);
+ CRM_CHECK(ch >= 0, return FALSE);
+ while (digits > 1) {
+ digits--;
+ ch = ch * 10;
+ }
+ *interval += ch;
+ offset--;
+ }
+
+ crm_trace(" Interval: %d", *interval);
+ CRM_CHECK(key[offset] == '_', return FALSE);
+
+ mutable_key = strdup(key);
+ mutable_key[offset] = 0;
+ offset--;
+
+ while (offset > 0 && key[offset] != '_') {
+ offset--;
+ }
+
+ CRM_CHECK(key[offset] == '_', free(mutable_key);
+ return FALSE);
+
+ mutable_key_ptr = mutable_key + offset + 1;
+
+ crm_trace(" Action: %s", mutable_key_ptr);
+
+ *op_type = strdup(mutable_key_ptr);
+
+ mutable_key[offset] = 0;
+ offset--;
+
+ CRM_CHECK(mutable_key != mutable_key_ptr, free(mutable_key);
+ return FALSE);
+
+ notify = strstr(mutable_key, "_post_notify");
+ if (notify && safe_str_eq(notify, "_post_notify")) {
+ notify[0] = 0;
+ }
+
+ notify = strstr(mutable_key, "_pre_notify");
+ if (notify && safe_str_eq(notify, "_pre_notify")) {
+ notify[0] = 0;
+ }
+
+ crm_trace(" Resource: %s", mutable_key);
+ *rsc_id = mutable_key;
+
+ return TRUE;
+}
+
+char *
+generate_notify_key(const char *rsc_id, const char *notify_type, const char *op_type)
+{
+ int len = 12;
+ char *op_id = NULL;
+
+ CRM_CHECK(rsc_id != NULL, return NULL);
+ CRM_CHECK(op_type != NULL, return NULL);
+ CRM_CHECK(notify_type != NULL, return NULL);
+
+ len += strlen(op_type);
+ len += strlen(rsc_id);
+ len += strlen(notify_type);
+ if(len > 0) {
+ op_id = malloc(len);
+ }
+ if (op_id != NULL) {
+ sprintf(op_id, "%s_%s_notify_%s_0", rsc_id, notify_type, op_type);
+ }
+ return op_id;
+}
+
+char *
+generate_transition_magic_v202(const char *transition_key, int op_status)
+{
+ int len = 80;
+ char *fail_state = NULL;
+
+ CRM_CHECK(transition_key != NULL, return NULL);
+
+ len += strlen(transition_key);
+
+ fail_state = malloc(len);
+ if (fail_state != NULL) {
+ snprintf(fail_state, len, "%d:%s", op_status, transition_key);
+ }
+ return fail_state;
+}
+
+char *
+generate_transition_magic(const char *transition_key, int op_status, int op_rc)
+{
+ int len = 80;
+ char *fail_state = NULL;
+
+ CRM_CHECK(transition_key != NULL, return NULL);
+
+ len += strlen(transition_key);
+
+ fail_state = malloc(len);
+ if (fail_state != NULL) {
+ snprintf(fail_state, len, "%d:%d;%s", op_status, op_rc, transition_key);
+ }
+ return fail_state;
+}
+
+gboolean
+decode_transition_magic(const char *magic, char **uuid, int *transition_id, int *action_id,
+ int *op_status, int *op_rc, int *target_rc)
+{
+ int res = 0;
+ char *key = NULL;
+ gboolean result = TRUE;
+
+ CRM_CHECK(magic != NULL, return FALSE);
+ CRM_CHECK(op_rc != NULL, return FALSE);
+ CRM_CHECK(op_status != NULL, return FALSE);
+
+ key = calloc(1, strlen(magic) + 1);
+ res = sscanf(magic, "%d:%d;%s", op_status, op_rc, key);
+ if (res != 3) {
+ crm_warn("Only found %d items in: '%s'", res, magic);
+ free(key);
+ return FALSE;
+ }
+
+ CRM_CHECK(decode_transition_key(key, uuid, transition_id, action_id, target_rc), result = FALSE);
+
+ free(key);
+ return result;
+}
+
+char *
+generate_transition_key(int transition_id, int action_id, int target_rc, const char *node)
+{
+ int len = 40;
+ char *fail_state = NULL;
+
+ CRM_CHECK(node != NULL, return NULL);
+
+ len += strlen(node);
+
+ fail_state = malloc(len);
+ if (fail_state != NULL) {
+ snprintf(fail_state, len, "%d:%d:%d:%-*s", action_id, transition_id, target_rc, 36, node);
+ }
+ return fail_state;
+}
+
+gboolean
+decode_transition_key(const char *key, char **uuid, int *transition_id, int *action_id,
+ int *target_rc)
+{
+ int res = 0;
+ gboolean done = FALSE;
+
+ CRM_CHECK(uuid != NULL, return FALSE);
+ CRM_CHECK(target_rc != NULL, return FALSE);
+ CRM_CHECK(action_id != NULL, return FALSE);
+ CRM_CHECK(transition_id != NULL, return FALSE);
+
+ *uuid = calloc(1, 37);
+ res = sscanf(key, "%d:%d:%d:%36s", action_id, transition_id, target_rc, *uuid);
+ switch (res) {
+ case 4:
+ /* Post Pacemaker 0.6 */
+ done = TRUE;
+ break;
+ case 3:
+ case 2:
+ /* this can be tricky - the UUID might start with an integer */
+
+ /* Until Pacemaker 0.6 */
+ done = TRUE;
+ *target_rc = -1;
+ res = sscanf(key, "%d:%d:%36s", action_id, transition_id, *uuid);
+ if (res == 2) {
+ *action_id = -1;
+ res = sscanf(key, "%d:%36s", transition_id, *uuid);
+ CRM_CHECK(res == 2, done = FALSE);
+
+ } else if (res != 3) {
+ CRM_CHECK(res == 3, done = FALSE);
+ }
+ break;
+
+ case 1:
+ /* Prior to Heartbeat 2.0.8 */
+ done = TRUE;
+ *action_id = -1;
+ *target_rc = -1;
+ res = sscanf(key, "%d:%36s", transition_id, *uuid);
+ CRM_CHECK(res == 2, done = FALSE);
+ break;
+ default:
+ crm_crit("Unhandled sscanf result (%d) for %s", res, key);
+ }
+
+ if (strlen(*uuid) != 36) {
+ crm_warn("Bad UUID (%s) in sscanf result (%d) for %s", *uuid, res, key);
+ }
+
+ if (done == FALSE) {
+ crm_err("Cannot decode '%s' rc=%d", key, res);
+
+ free(*uuid);
+ *uuid = NULL;
+ *target_rc = -1;
+ *action_id = -1;
+ *transition_id = -1;
+ }
+
+ return done;
+}
+
+void
+filter_action_parameters(xmlNode * param_set, const char *version)
+{
+ char *key = NULL;
+ char *timeout = NULL;
+ char *interval = NULL;
+
+ const char *attr_filter[] = {
+ XML_ATTR_ID,
+ XML_ATTR_CRM_VERSION,
+ XML_LRM_ATTR_OP_DIGEST,
+ XML_LRM_ATTR_TARGET,
+ XML_LRM_ATTR_TARGET_UUID,
+ "pcmk_external_ip"
+ };
+
+ gboolean do_delete = FALSE;
+ int lpc = 0;
+ static int meta_len = 0;
+
+ if (meta_len == 0) {
+ meta_len = strlen(CRM_META);
+ }
+
+ if (param_set == NULL) {
+ return;
+ }
+
+ for (lpc = 0; lpc < DIMOF(attr_filter); lpc++) {
+ xml_remove_prop(param_set, attr_filter[lpc]);
+ }
+
+ key = crm_meta_name(XML_LRM_ATTR_INTERVAL);
+ interval = crm_element_value_copy(param_set, key);
+ free(key);
+
+ key = crm_meta_name(XML_ATTR_TIMEOUT);
+ timeout = crm_element_value_copy(param_set, key);
+
+ if (param_set) {
+ xmlAttrPtr xIter = param_set->properties;
+
+ while (xIter) {
+ const char *prop_name = (const char *)xIter->name;
+
+ xIter = xIter->next;
+ do_delete = FALSE;
+ if (strncasecmp(prop_name, CRM_META, meta_len) == 0) {
+ do_delete = TRUE;
+ }
+
+ if (do_delete) {
+ xml_remove_prop(param_set, prop_name);
+ }
+ }
+ }
+
+ if (crm_get_msec(interval) > 0 && compare_version(version, "1.0.8") > 0) {
+ /* Re-instate the operation's timeout value */
+ if (timeout != NULL) {
+ crm_xml_add(param_set, key, timeout);
+ }
+ }
+
+ free(interval);
+ free(timeout);
+ free(key);
+}
+
+#define FAKE_TE_ID "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+static void
+append_digest(lrmd_event_data_t * op, xmlNode * update, const char *version, const char *magic,
+ int level)
+{
+ /* this will enable us to later determine that the
+ * resource's parameters have changed and we should force
+ * a restart
+ */
+ char *digest = NULL;
+ xmlNode *args_xml = NULL;
+
+ if (op->params == NULL) {
+ return;
+ }
+
+ args_xml = create_xml_node(NULL, XML_TAG_PARAMS);
+ g_hash_table_foreach(op->params, hash2field, args_xml);
+ filter_action_parameters(args_xml, version);
+ digest = calculate_operation_digest(args_xml, version);
+
+#if 0
+ if (level < get_crm_log_level()
+ && op->interval == 0 && crm_str_eq(op->op_type, CRMD_ACTION_START, TRUE)) {
+ char *digest_source = dump_xml_unformatted(args_xml);
+
+ do_crm_log(level, "Calculated digest %s for %s (%s). Source: %s\n",
+ digest, ID(update), magic, digest_source);
+ free(digest_source);
+ }
+#endif
+ crm_xml_add(update, XML_LRM_ATTR_OP_DIGEST, digest);
+
+ free_xml(args_xml);
+ free(digest);
+}
+
+int
+rsc_op_expected_rc(lrmd_event_data_t * op)
+{
+ int rc = 0;
+
+ if (op && op->user_data) {
+ int dummy = 0;
+ char *uuid = NULL;
+
+ decode_transition_key(op->user_data, &uuid, &dummy, &dummy, &rc);
+ free(uuid);
+ }
+ return rc;
+}
+
+gboolean
+did_rsc_op_fail(lrmd_event_data_t * op, int target_rc)
+{
+ switch (op->op_status) {
+ case PCMK_LRM_OP_CANCELLED:
+ case PCMK_LRM_OP_PENDING:
+ return FALSE;
+ break;
+
+ case PCMK_LRM_OP_NOTSUPPORTED:
+ case PCMK_LRM_OP_TIMEOUT:
+ case PCMK_LRM_OP_ERROR:
+ return TRUE;
+ break;
+
+ default:
+ if (target_rc != op->rc) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+xmlNode *
+create_operation_update(xmlNode * parent, lrmd_event_data_t * op, const char * caller_version,
+ int target_rc, const char * node, const char * origin, int level)
+{
+ char *key = NULL;
+ char *magic = NULL;
+ char *op_id = NULL;
+ char *op_id_additional = NULL;
+ char *local_user_data = NULL;
+ const char *exit_reason = NULL;
+
+ xmlNode *xml_op = NULL;
+ const char *task = NULL;
+ gboolean dc_munges_migrate_ops = (compare_version(caller_version, "3.0.3") < 0);
+ gboolean dc_needs_unique_ops = (compare_version(caller_version, "3.0.6") < 0);
+
+ CRM_CHECK(op != NULL, return NULL);
+ do_crm_log(level, "%s: Updating resource %s after %s op %s (interval=%d)",
+ origin, op->rsc_id, op->op_type, services_lrm_status_str(op->op_status),
+ op->interval);
+
+ crm_trace("DC version: %s", caller_version);
+
+ task = op->op_type;
+ /* remap the task name under various scenarios
+ * this makes life easier for the PE when trying determine the current state
+ */
+ if (crm_str_eq(task, "reload", TRUE)) {
+ if (op->op_status == PCMK_LRM_OP_DONE) {
+ task = CRMD_ACTION_START;
+ } else {
+ task = CRMD_ACTION_STATUS;
+ }
+
+ } else if (dc_munges_migrate_ops && crm_str_eq(task, CRMD_ACTION_MIGRATE, TRUE)) {
+ /* if the migrate_from fails it will have enough info to do the right thing */
+ if (op->op_status == PCMK_LRM_OP_DONE) {
+ task = CRMD_ACTION_STOP;
+ } else {
+ task = CRMD_ACTION_STATUS;
+ }
+
+ } else if (dc_munges_migrate_ops
+ && op->op_status == PCMK_LRM_OP_DONE
+ && crm_str_eq(task, CRMD_ACTION_MIGRATED, TRUE)) {
+ task = CRMD_ACTION_START;
+ }
+
+ key = generate_op_key(op->rsc_id, task, op->interval);
+ if (dc_needs_unique_ops && op->interval > 0) {
+ op_id = strdup(key);
+
+ } else if (crm_str_eq(task, CRMD_ACTION_NOTIFY, TRUE)) {
+ const char *n_type = crm_meta_value(op->params, "notify_type");
+ const char *n_task = crm_meta_value(op->params, "notify_operation");
+
+ CRM_LOG_ASSERT(n_type != NULL);
+ CRM_LOG_ASSERT(n_task != NULL);
+ op_id = generate_notify_key(op->rsc_id, n_type, n_task);
+
+ /* these are not yet allowed to fail */
+ op->op_status = PCMK_LRM_OP_DONE;
+ op->rc = 0;
+
+ } else if (did_rsc_op_fail(op, target_rc)) {
+ op_id = generate_op_key(op->rsc_id, "last_failure", 0);
+ if (op->interval == 0) {
+ /* Ensure 'last' gets updated too in case recording-pending="true" */
+ op_id_additional = generate_op_key(op->rsc_id, "last", 0);
+ }
+ exit_reason = op->exit_reason;
+
+ } else if (op->interval > 0) {
+ op_id = strdup(key);
+
+ } else {
+ op_id = generate_op_key(op->rsc_id, "last", 0);
+ }
+
+ again:
+ xml_op = find_entity(parent, XML_LRM_TAG_RSC_OP, op_id);
+ if (xml_op == NULL) {
+ xml_op = create_xml_node(parent, XML_LRM_TAG_RSC_OP);
+ }
+
+ if (op->user_data == NULL) {
+ crm_debug("Generating fake transition key for:"
+ " %s_%s_%d %d from %s",
+ op->rsc_id, op->op_type, op->interval, op->call_id, origin);
+ local_user_data = generate_transition_key(-1, op->call_id, target_rc, FAKE_TE_ID);
+ op->user_data = local_user_data;
+ }
+
+ if(magic == NULL) {
+ magic = generate_transition_magic(op->user_data, op->op_status, op->rc);
+ }
+
+ crm_xml_add(xml_op, XML_ATTR_ID, op_id);
+ crm_xml_add(xml_op, XML_LRM_ATTR_TASK_KEY, key);
+ crm_xml_add(xml_op, XML_LRM_ATTR_TASK, task);
+ crm_xml_add(xml_op, XML_ATTR_ORIGIN, origin);
+ crm_xml_add(xml_op, XML_ATTR_CRM_VERSION, caller_version);
+ crm_xml_add(xml_op, XML_ATTR_TRANSITION_KEY, op->user_data);
+ crm_xml_add(xml_op, XML_ATTR_TRANSITION_MAGIC, magic);
+ crm_xml_add(xml_op, XML_LRM_ATTR_EXIT_REASON, exit_reason);
+ crm_xml_add(xml_op, XML_LRM_ATTR_TARGET, node); /* For context during triage */
+
+ crm_xml_add_int(xml_op, XML_LRM_ATTR_CALLID, op->call_id);
+ crm_xml_add_int(xml_op, XML_LRM_ATTR_RC, op->rc);
+ crm_xml_add_int(xml_op, XML_LRM_ATTR_OPSTATUS, op->op_status);
+ crm_xml_add_int(xml_op, XML_LRM_ATTR_INTERVAL, op->interval);
+
+ if (compare_version("2.1", caller_version) <= 0) {
+ if (op->t_run || op->t_rcchange || op->exec_time || op->queue_time) {
+ crm_trace("Timing data (%s_%s_%d): last=%u change=%u exec=%u queue=%u",
+ op->rsc_id, op->op_type, op->interval,
+ op->t_run, op->t_rcchange, op->exec_time, op->queue_time);
+
+ if (op->interval == 0) {
+ /* The values are the same for non-recurring ops */
+ crm_xml_add_int(xml_op, XML_RSC_OP_LAST_RUN, op->t_run);
+ crm_xml_add_int(xml_op, XML_RSC_OP_LAST_CHANGE, op->t_run);
+
+ } else if(op->t_rcchange) {
+ /* last-run is not accurate for recurring ops */
+ crm_xml_add_int(xml_op, XML_RSC_OP_LAST_CHANGE, op->t_rcchange);
+
+ } else {
+ /* ...but is better than nothing otherwise */
+ crm_xml_add_int(xml_op, XML_RSC_OP_LAST_CHANGE, op->t_run);
+ }
+
+ crm_xml_add_int(xml_op, XML_RSC_OP_T_EXEC, op->exec_time);
+ crm_xml_add_int(xml_op, XML_RSC_OP_T_QUEUE, op->queue_time);
+ }
+ }
+
+ if (crm_str_eq(op->op_type, CRMD_ACTION_MIGRATE, TRUE)
+ || crm_str_eq(op->op_type, CRMD_ACTION_MIGRATED, TRUE)) {
+ /*
+ * Record migrate_source and migrate_target always for migrate ops.
+ */
+ const char *name = XML_LRM_ATTR_MIGRATE_SOURCE;
+
+ crm_xml_add(xml_op, name, crm_meta_value(op->params, name));
+
+ name = XML_LRM_ATTR_MIGRATE_TARGET;
+ crm_xml_add(xml_op, name, crm_meta_value(op->params, name));
+ }
+
+ append_digest(op, xml_op, caller_version, magic, LOG_DEBUG);
+
+ if (op_id_additional) {
+ free(op_id);
+ op_id = op_id_additional;
+ op_id_additional = NULL;
+ goto again;
+ }
+
+ if (local_user_data) {
+ free(local_user_data);
+ op->user_data = NULL;
+ }
+ free(magic);
+ free(op_id);
+ free(key);
+ return xml_op;
+}
+
+/*!
+ * \brief Check whether an operation requires resource agent meta-data
+ *
+ * \param[in] rsc_class Resource agent class (or NULL to skip class check)
+ * \param[in] op Operation action (or NULL to skip op check)
+ *
+ * \return TRUE if operation needs meta-data, FALSE otherwise
+ * \note At least one of rsc_class and op must be specified.
+ */
+bool
+crm_op_needs_metadata(const char *rsc_class, const char *op)
+{
+ /* Agent meta-data is used to determine whether a reload is possible, and to
+ * evaluate versioned parameters -- so if this op is not relevant to those
+ * features, we don't need the meta-data.
+ */
+
+ CRM_CHECK(rsc_class || op, return FALSE);
+
+ if (rsc_class
+ && strcmp(rsc_class, PCMK_RESOURCE_CLASS_OCF)
+ && strcmp(rsc_class, PCMK_RESOURCE_CLASS_STONITH)) {
+
+ /* Meta-data is only needed for resource classes that use parameters */
+ return FALSE;
+ }
+
+ /* Meta-data is only needed for these actions */
+ if (op
+ && strcmp(op, CRMD_ACTION_START)
+ && strcmp(op, CRMD_ACTION_STATUS)
+ && strcmp(op, CRMD_ACTION_PROMOTE)
+ && strcmp(op, CRMD_ACTION_DEMOTE)
+ && strcmp(op, CRMD_ACTION_RELOAD)
+ && strcmp(op, CRMD_ACTION_MIGRATE)
+ && strcmp(op, CRMD_ACTION_MIGRATED)
+ && strcmp(op, CRMD_ACTION_NOTIFY)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/lib/common/utils.c b/lib/common/utils.c
index d97bbbf222..9bb5bab415 100644
--- a/lib/common/utils.c
+++ b/lib/common/utils.c
@@ -1,2027 +1,1456 @@
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* This library 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.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser 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
*/
#include <crm_internal.h>
#include <dlfcn.h>
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
-#include <sys/param.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
-#include <ctype.h>
#include <pwd.h>
#include <time.h>
#include <libgen.h>
#include <signal.h>
#include <qb/qbdefs.h>
#include <crm/crm.h>
-#include <crm/lrmd.h>
#include <crm/services.h>
#include <crm/msg_xml.h>
#include <crm/cib/internal.h>
#include <crm/common/xml.h>
#include <crm/common/util.h>
#include <crm/common/ipc.h>
#include <crm/common/iso8601.h>
#include <crm/common/mainloop.h>
#include <libxml2/libxml/relaxng.h>
#ifndef MAXLINE
# define MAXLINE 512
#endif
#ifdef HAVE_GETOPT_H
# include <getopt.h>
#endif
#ifndef PW_BUFFER_LEN
# define PW_BUFFER_LEN 500
#endif
CRM_TRACE_INIT_DATA(common);
gboolean crm_config_error = FALSE;
gboolean crm_config_warning = FALSE;
char *crm_system_name = NULL;
int node_score_red = 0;
int node_score_green = 0;
int node_score_yellow = 0;
int node_score_infinity = INFINITY;
static struct crm_option *crm_long_options = NULL;
static const char *crm_app_description = NULL;
static char *crm_short_options = NULL;
static const char *crm_app_usage = NULL;
int
crm_exit(int rc)
{
mainloop_cleanup();
#if HAVE_LIBXML2
crm_trace("cleaning up libxml");
crm_xml_cleanup();
#endif
crm_trace("exit %d", rc);
qb_log_fini();
free(crm_short_options);
free(crm_system_name);
exit(ABS(rc)); /* Always exit with a positive value so that it can be passed to crm_error
*
* Otherwise the system wraps it around and people
* have to jump through hoops figuring out what the
* error was
*/
return rc; /* Can never happen, but allows return crm_exit(rc)
* where "return rc" was used previously - which
* keeps compilers happy.
*/
}
gboolean
check_time(const char *value)
{
if (crm_get_msec(value) < 5000) {
return FALSE;
}
return TRUE;
}
gboolean
check_timer(const char *value)
{
if (crm_get_msec(value) < 0) {
return FALSE;
}
return TRUE;
}
gboolean
check_boolean(const char *value)
{
int tmp = FALSE;
if (crm_str_to_boolean(value, &tmp) != 1) {
return FALSE;
}
return TRUE;
}
gboolean
check_number(const char *value)
{
errno = 0;
if (value == NULL) {
return FALSE;
} else if (safe_str_eq(value, MINUS_INFINITY_S)) {
} else if (safe_str_eq(value, INFINITY_S)) {
} else {
crm_int_helper(value, NULL);
}
if (errno != 0) {
return FALSE;
}
return TRUE;
}
gboolean
check_positive_number(const char* value)
{
if (safe_str_eq(value, INFINITY_S) || (crm_int_helper(value, NULL))) {
return TRUE;
}
return FALSE;
}
gboolean
check_quorum(const char *value)
{
if (safe_str_eq(value, "stop")) {
return TRUE;
} else if (safe_str_eq(value, "freeze")) {
return TRUE;
} else if (safe_str_eq(value, "ignore")) {
return TRUE;
} else if (safe_str_eq(value, "suicide")) {
return TRUE;
}
return FALSE;
}
gboolean
check_script(const char *value)
{
struct stat st;
if(safe_str_eq(value, "/dev/null")) {
return TRUE;
}
if(stat(value, &st) != 0) {
crm_err("Script %s does not exist", value);
return FALSE;
}
if(S_ISREG(st.st_mode) == 0) {
crm_err("Script %s is not a regular file", value);
return FALSE;
}
if( (st.st_mode & (S_IXUSR | S_IXGRP )) == 0) {
crm_err("Script %s is not executable", value);
return FALSE;
}
return TRUE;
}
gboolean
check_utilization(const char *value)
{
char *end = NULL;
long number = strtol(value, &end, 10);
if(end && end[0] != '%') {
return FALSE;
} else if(number < 0) {
return FALSE;
}
return TRUE;
}
int
char2score(const char *score)
{
int score_f = 0;
if (score == NULL) {
} else if (safe_str_eq(score, MINUS_INFINITY_S)) {
score_f = -node_score_infinity;
} else if (safe_str_eq(score, INFINITY_S)) {
score_f = node_score_infinity;
} else if (safe_str_eq(score, "+" INFINITY_S)) {
score_f = node_score_infinity;
} else if (safe_str_eq(score, "red")) {
score_f = node_score_red;
} else if (safe_str_eq(score, "yellow")) {
score_f = node_score_yellow;
} else if (safe_str_eq(score, "green")) {
score_f = node_score_green;
} else {
score_f = crm_parse_int(score, NULL);
if (score_f > 0 && score_f > node_score_infinity) {
score_f = node_score_infinity;
} else if (score_f < 0 && score_f < -node_score_infinity) {
score_f = -node_score_infinity;
}
}
return score_f;
}
char *
score2char_stack(int score, char *buf, size_t len)
{
if (score >= node_score_infinity) {
strncpy(buf, INFINITY_S, 9);
} else if (score <= -node_score_infinity) {
strncpy(buf, MINUS_INFINITY_S , 10);
} else {
return crm_itoa_stack(score, buf, len);
}
return buf;
}
char *
score2char(int score)
{
if (score >= node_score_infinity) {
return strdup(INFINITY_S);
} else if (score <= -node_score_infinity) {
return strdup("-" INFINITY_S);
}
return crm_itoa(score);
}
const char *
cluster_option(GHashTable * options, gboolean(*validate) (const char *),
const char *name, const char *old_name, const char *def_value)
{
const char *value = NULL;
CRM_ASSERT(name != NULL);
if (options != NULL) {
value = g_hash_table_lookup(options, name);
}
if (value == NULL && old_name && options != NULL) {
value = g_hash_table_lookup(options, old_name);
if (value != NULL) {
crm_config_warn("Using deprecated name '%s' for"
" cluster option '%s'", old_name, name);
g_hash_table_insert(options, strdup(name), strdup(value));
value = g_hash_table_lookup(options, old_name);
}
}
if (value == NULL) {
crm_trace("Using default value '%s' for cluster option '%s'", def_value, name);
if (options == NULL) {
return def_value;
} else if(def_value == NULL) {
return def_value;
}
g_hash_table_insert(options, strdup(name), strdup(def_value));
value = g_hash_table_lookup(options, name);
}
if (validate && validate(value) == FALSE) {
crm_config_err("Value '%s' for cluster option '%s' is invalid."
" Defaulting to %s", value, name, def_value);
g_hash_table_replace(options, strdup(name), strdup(def_value));
value = g_hash_table_lookup(options, name);
}
return value;
}
const char *
get_cluster_pref(GHashTable * options, pe_cluster_option * option_list, int len, const char *name)
{
int lpc = 0;
const char *value = NULL;
gboolean found = FALSE;
for (lpc = 0; lpc < len; lpc++) {
if (safe_str_eq(name, option_list[lpc].name)) {
found = TRUE;
value = cluster_option(options,
option_list[lpc].is_valid,
option_list[lpc].name,
option_list[lpc].alt_name, option_list[lpc].default_value);
}
}
CRM_CHECK(found, crm_err("No option named: %s", name));
return value;
}
void
config_metadata(const char *name, const char *version, const char *desc_short,
const char *desc_long, pe_cluster_option * option_list, int len)
{
int lpc = 0;
fprintf(stdout, "<?xml version=\"1.0\"?>"
"<!DOCTYPE resource-agent SYSTEM \"ra-api-1.dtd\">\n"
"<resource-agent name=\"%s\">\n"
" <version>%s</version>\n"
" <longdesc lang=\"en\">%s</longdesc>\n"
" <shortdesc lang=\"en\">%s</shortdesc>\n"
" <parameters>\n", name, version, desc_long, desc_short);
for (lpc = 0; lpc < len; lpc++) {
if (option_list[lpc].description_long == NULL && option_list[lpc].description_short == NULL) {
continue;
}
fprintf(stdout, " <parameter name=\"%s\" unique=\"0\">\n"
" <shortdesc lang=\"en\">%s</shortdesc>\n"
" <content type=\"%s\" default=\"%s\"/>\n"
" <longdesc lang=\"en\">%s%s%s</longdesc>\n"
" </parameter>\n",
option_list[lpc].name,
option_list[lpc].description_short,
option_list[lpc].type,
option_list[lpc].default_value,
option_list[lpc].description_long ? option_list[lpc].
description_long : option_list[lpc].description_short,
option_list[lpc].values ? " Allowed values: " : "",
option_list[lpc].values ? option_list[lpc].values : "");
}
fprintf(stdout, " </parameters>\n</resource-agent>\n");
}
void
verify_all_options(GHashTable * options, pe_cluster_option * option_list, int len)
{
int lpc = 0;
for (lpc = 0; lpc < len; lpc++) {
cluster_option(options,
option_list[lpc].is_valid,
option_list[lpc].name,
option_list[lpc].alt_name, option_list[lpc].default_value);
}
}
char *
generate_hash_key(const char *crm_msg_reference, const char *sys)
{
char *hash_key = crm_concat(sys ? sys : "none", crm_msg_reference, '_');
crm_trace("created hash key: (%s)", hash_key);
return hash_key;
}
int
crm_user_lookup(const char *name, uid_t * uid, gid_t * gid)
{
int rc = pcmk_ok;
char *buffer = NULL;
struct passwd pwd;
struct passwd *pwentry = NULL;
buffer = calloc(1, PW_BUFFER_LEN);
rc = getpwnam_r(name, &pwd, buffer, PW_BUFFER_LEN, &pwentry);
if (pwentry) {
if (uid) {
*uid = pwentry->pw_uid;
}
if (gid) {
*gid = pwentry->pw_gid;
}
crm_trace("User %s has uid=%d gid=%d", name, pwentry->pw_uid, pwentry->pw_gid);
} else {
rc = rc? -rc : -EINVAL;
crm_info("User %s lookup: %s", name, pcmk_strerror(rc));
}
free(buffer);
return rc;
}
static int
crm_version_helper(const char *text, char **end_text)
{
int atoi_result = -1;
CRM_ASSERT(end_text != NULL);
errno = 0;
if (text != NULL && text[0] != 0) {
atoi_result = (int)strtol(text, end_text, 10);
if (errno == EINVAL) {
crm_err("Conversion of '%s' %c failed", text, text[0]);
atoi_result = -1;
}
}
return atoi_result;
}
/*
* version1 < version2 : -1
* version1 = version2 : 0
* version1 > version2 : 1
*/
int
compare_version(const char *version1, const char *version2)
{
int rc = 0;
int lpc = 0;
char *ver1_copy = NULL, *ver2_copy = NULL;
char *rest1 = NULL, *rest2 = NULL;
if (version1 == NULL && version2 == NULL) {
return 0;
} else if (version1 == NULL) {
return -1;
} else if (version2 == NULL) {
return 1;
}
ver1_copy = strdup(version1);
ver2_copy = strdup(version2);
rest1 = ver1_copy;
rest2 = ver2_copy;
while (1) {
int digit1 = 0;
int digit2 = 0;
lpc++;
if (rest1 == rest2) {
break;
}
if (rest1 != NULL) {
digit1 = crm_version_helper(rest1, &rest1);
}
if (rest2 != NULL) {
digit2 = crm_version_helper(rest2, &rest2);
}
if (digit1 < digit2) {
rc = -1;
break;
} else if (digit1 > digit2) {
rc = 1;
break;
}
if (rest1 != NULL && rest1[0] == '.') {
rest1++;
}
if (rest1 != NULL && rest1[0] == 0) {
rest1 = NULL;
}
if (rest2 != NULL && rest2[0] == '.') {
rest2++;
}
if (rest2 != NULL && rest2[0] == 0) {
rest2 = NULL;
}
}
free(ver1_copy);
free(ver2_copy);
if (rc == 0) {
crm_trace("%s == %s (%d)", version1, version2, lpc);
} else if (rc < 0) {
crm_trace("%s < %s (%d)", version1, version2, lpc);
} else if (rc > 0) {
crm_trace("%s > %s (%d)", version1, version2, lpc);
}
return rc;
}
gboolean do_stderr = FALSE;
#ifndef NUMCHARS
# define NUMCHARS "0123456789."
#endif
#ifndef WHITESPACE
# define WHITESPACE " \t\n\r\f"
#endif
unsigned long long
crm_get_interval(const char *input)
{
unsigned long long msec = 0;
if (input == NULL) {
return msec;
} else if (input[0] != 'P') {
long long tmp = crm_get_msec(input);
if(tmp > 0) {
msec = tmp;
}
} else {
crm_time_t *interval = crm_time_parse_duration(input);
msec = 1000 * crm_time_get_seconds(interval);
crm_time_free(interval);
}
return msec;
}
long long
crm_get_msec(const char *input)
{
const char *cp = input;
const char *units;
long long multiplier = 1000;
long long divisor = 1;
long long msec = -1;
char *end_text = NULL;
/* double dret; */
if (input == NULL) {
return msec;
}
cp += strspn(cp, WHITESPACE);
units = cp + strspn(cp, NUMCHARS);
units += strspn(units, WHITESPACE);
if (strchr(NUMCHARS, *cp) == NULL) {
return msec;
}
if (strncasecmp(units, "ms", 2) == 0 || strncasecmp(units, "msec", 4) == 0) {
multiplier = 1;
divisor = 1;
} else if (strncasecmp(units, "us", 2) == 0 || strncasecmp(units, "usec", 4) == 0) {
multiplier = 1;
divisor = 1000;
} else if (strncasecmp(units, "s", 1) == 0 || strncasecmp(units, "sec", 3) == 0) {
multiplier = 1000;
divisor = 1;
} else if (strncasecmp(units, "m", 1) == 0 || strncasecmp(units, "min", 3) == 0) {
multiplier = 60 * 1000;
divisor = 1;
} else if (strncasecmp(units, "h", 1) == 0 || strncasecmp(units, "hr", 2) == 0) {
multiplier = 60 * 60 * 1000;
divisor = 1;
} else if (*units != EOS && *units != '\n' && *units != '\r') {
return msec;
}
msec = crm_int_helper(cp, &end_text);
if (msec > LLONG_MAX/multiplier) {
/* arithmetics overflow while multiplier/divisor mutually exclusive */
return LLONG_MAX;
}
msec *= multiplier;
msec /= divisor;
/* dret += 0.5; */
/* msec = (long long)dret; */
return msec;
}
-/*!
- * \brief Generate an operation key
- *
- * \param[in] rsc_id ID of resource being operated on
- * \param[in] op_type Operation name
- * \param[in] interval Operation interval
- *
- * \return Newly allocated memory containing operation key as string
- *
- * \note It is the caller's responsibility to free() the result.
- */
-char *
-generate_op_key(const char *rsc_id, const char *op_type, int interval)
-{
- CRM_ASSERT(rsc_id != NULL);
- CRM_ASSERT(op_type != NULL);
- CRM_ASSERT(interval >= 0);
- return crm_strdup_printf("%s_%s_%d", rsc_id, op_type, interval);
-}
-
-gboolean
-parse_op_key(const char *key, char **rsc_id, char **op_type, int *interval)
-{
- char *notify = NULL;
- char *mutable_key = NULL;
- char *mutable_key_ptr = NULL;
- int len = 0, offset = 0, ch = 0;
-
- CRM_CHECK(key != NULL, return FALSE);
-
- *interval = 0;
- len = strlen(key);
- offset = len - 1;
-
- crm_trace("Source: %s", key);
-
- while (offset > 0 && isdigit(key[offset])) {
- int digits = len - offset;
-
- ch = key[offset] - '0';
- CRM_CHECK(ch < 10, return FALSE);
- CRM_CHECK(ch >= 0, return FALSE);
- while (digits > 1) {
- digits--;
- ch = ch * 10;
- }
- *interval += ch;
- offset--;
- }
-
- crm_trace(" Interval: %d", *interval);
- CRM_CHECK(key[offset] == '_', return FALSE);
-
- mutable_key = strdup(key);
- mutable_key[offset] = 0;
- offset--;
-
- while (offset > 0 && key[offset] != '_') {
- offset--;
- }
-
- CRM_CHECK(key[offset] == '_', free(mutable_key);
- return FALSE);
-
- mutable_key_ptr = mutable_key + offset + 1;
-
- crm_trace(" Action: %s", mutable_key_ptr);
-
- *op_type = strdup(mutable_key_ptr);
-
- mutable_key[offset] = 0;
- offset--;
-
- CRM_CHECK(mutable_key != mutable_key_ptr, free(mutable_key);
- return FALSE);
-
- notify = strstr(mutable_key, "_post_notify");
- if (notify && safe_str_eq(notify, "_post_notify")) {
- notify[0] = 0;
- }
-
- notify = strstr(mutable_key, "_pre_notify");
- if (notify && safe_str_eq(notify, "_pre_notify")) {
- notify[0] = 0;
- }
-
- crm_trace(" Resource: %s", mutable_key);
- *rsc_id = mutable_key;
-
- return TRUE;
-}
-
-char *
-generate_notify_key(const char *rsc_id, const char *notify_type, const char *op_type)
-{
- int len = 12;
- char *op_id = NULL;
-
- CRM_CHECK(rsc_id != NULL, return NULL);
- CRM_CHECK(op_type != NULL, return NULL);
- CRM_CHECK(notify_type != NULL, return NULL);
-
- len += strlen(op_type);
- len += strlen(rsc_id);
- len += strlen(notify_type);
- if(len > 0) {
- op_id = malloc(len);
- }
- if (op_id != NULL) {
- sprintf(op_id, "%s_%s_notify_%s_0", rsc_id, notify_type, op_type);
- }
- return op_id;
-}
-
-char *
-generate_transition_magic_v202(const char *transition_key, int op_status)
-{
- int len = 80;
- char *fail_state = NULL;
-
- CRM_CHECK(transition_key != NULL, return NULL);
-
- len += strlen(transition_key);
-
- fail_state = malloc(len);
- if (fail_state != NULL) {
- snprintf(fail_state, len, "%d:%s", op_status, transition_key);
- }
- return fail_state;
-}
-
-char *
-generate_transition_magic(const char *transition_key, int op_status, int op_rc)
-{
- int len = 80;
- char *fail_state = NULL;
-
- CRM_CHECK(transition_key != NULL, return NULL);
-
- len += strlen(transition_key);
-
- fail_state = malloc(len);
- if (fail_state != NULL) {
- snprintf(fail_state, len, "%d:%d;%s", op_status, op_rc, transition_key);
- }
- return fail_state;
-}
-
-gboolean
-decode_transition_magic(const char *magic, char **uuid, int *transition_id, int *action_id,
- int *op_status, int *op_rc, int *target_rc)
-{
- int res = 0;
- char *key = NULL;
- gboolean result = TRUE;
-
- CRM_CHECK(magic != NULL, return FALSE);
- CRM_CHECK(op_rc != NULL, return FALSE);
- CRM_CHECK(op_status != NULL, return FALSE);
-
- key = calloc(1, strlen(magic) + 1);
- res = sscanf(magic, "%d:%d;%s", op_status, op_rc, key);
- if (res != 3) {
- crm_warn("Only found %d items in: '%s'", res, magic);
- free(key);
- return FALSE;
- }
-
- CRM_CHECK(decode_transition_key(key, uuid, transition_id, action_id, target_rc), result = FALSE);
-
- free(key);
- return result;
-}
-
-char *
-generate_transition_key(int transition_id, int action_id, int target_rc, const char *node)
-{
- int len = 40;
- char *fail_state = NULL;
-
- CRM_CHECK(node != NULL, return NULL);
-
- len += strlen(node);
-
- fail_state = malloc(len);
- if (fail_state != NULL) {
- snprintf(fail_state, len, "%d:%d:%d:%-*s", action_id, transition_id, target_rc, 36, node);
- }
- return fail_state;
-}
-
-gboolean
-decode_transition_key(const char *key, char **uuid, int *transition_id, int *action_id,
- int *target_rc)
-{
- int res = 0;
- gboolean done = FALSE;
-
- CRM_CHECK(uuid != NULL, return FALSE);
- CRM_CHECK(target_rc != NULL, return FALSE);
- CRM_CHECK(action_id != NULL, return FALSE);
- CRM_CHECK(transition_id != NULL, return FALSE);
-
- *uuid = calloc(1, 37);
- res = sscanf(key, "%d:%d:%d:%36s", action_id, transition_id, target_rc, *uuid);
- switch (res) {
- case 4:
- /* Post Pacemaker 0.6 */
- done = TRUE;
- break;
- case 3:
- case 2:
- /* this can be tricky - the UUID might start with an integer */
-
- /* Until Pacemaker 0.6 */
- done = TRUE;
- *target_rc = -1;
- res = sscanf(key, "%d:%d:%36s", action_id, transition_id, *uuid);
- if (res == 2) {
- *action_id = -1;
- res = sscanf(key, "%d:%36s", transition_id, *uuid);
- CRM_CHECK(res == 2, done = FALSE);
-
- } else if (res != 3) {
- CRM_CHECK(res == 3, done = FALSE);
- }
- break;
-
- case 1:
- /* Prior to Heartbeat 2.0.8 */
- done = TRUE;
- *action_id = -1;
- *target_rc = -1;
- res = sscanf(key, "%d:%36s", transition_id, *uuid);
- CRM_CHECK(res == 2, done = FALSE);
- break;
- default:
- crm_crit("Unhandled sscanf result (%d) for %s", res, key);
- }
-
- if (strlen(*uuid) != 36) {
- crm_warn("Bad UUID (%s) in sscanf result (%d) for %s", *uuid, res, key);
- }
-
- if (done == FALSE) {
- crm_err("Cannot decode '%s' rc=%d", key, res);
-
- free(*uuid);
- *uuid = NULL;
- *target_rc = -1;
- *action_id = -1;
- *transition_id = -1;
- }
-
- return done;
-}
-
-void
-filter_action_parameters(xmlNode * param_set, const char *version)
-{
- char *key = NULL;
- char *timeout = NULL;
- char *interval = NULL;
-
- const char *attr_filter[] = {
- XML_ATTR_ID,
- XML_ATTR_CRM_VERSION,
- XML_LRM_ATTR_OP_DIGEST,
- XML_LRM_ATTR_TARGET,
- XML_LRM_ATTR_TARGET_UUID,
- "pcmk_external_ip"
- };
-
- gboolean do_delete = FALSE;
- int lpc = 0;
- static int meta_len = 0;
-
- if (meta_len == 0) {
- meta_len = strlen(CRM_META);
- }
-
- if (param_set == NULL) {
- return;
- }
-
- for (lpc = 0; lpc < DIMOF(attr_filter); lpc++) {
- xml_remove_prop(param_set, attr_filter[lpc]);
- }
-
- key = crm_meta_name(XML_LRM_ATTR_INTERVAL);
- interval = crm_element_value_copy(param_set, key);
- free(key);
-
- key = crm_meta_name(XML_ATTR_TIMEOUT);
- timeout = crm_element_value_copy(param_set, key);
-
- if (param_set) {
- xmlAttrPtr xIter = param_set->properties;
-
- while (xIter) {
- const char *prop_name = (const char *)xIter->name;
-
- xIter = xIter->next;
- do_delete = FALSE;
- if (strncasecmp(prop_name, CRM_META, meta_len) == 0) {
- do_delete = TRUE;
- }
-
- if (do_delete) {
- xml_remove_prop(param_set, prop_name);
- }
- }
- }
-
- if (crm_get_msec(interval) > 0 && compare_version(version, "1.0.8") > 0) {
- /* Re-instate the operation's timeout value */
- if (timeout != NULL) {
- crm_xml_add(param_set, key, timeout);
- }
- }
-
- free(interval);
- free(timeout);
- free(key);
-}
-
extern bool crm_is_daemon;
/* coverity[+kill] */
void
crm_abort(const char *file, const char *function, int line,
const char *assert_condition, gboolean do_core, gboolean do_fork)
{
int rc = 0;
int pid = 0;
int status = 0;
/* Implied by the parent's error logging below */
/* crm_write_blackbox(0); */
if(crm_is_daemon == FALSE) {
/* This is a command line tool - do not fork */
/* crm_add_logfile(NULL); * Record it to a file? */
crm_enable_stderr(TRUE); /* Make sure stderr is enabled so we can tell the caller */
do_fork = FALSE; /* Just crash if needed */
}
if (do_core == FALSE) {
crm_err("%s: Triggered assert at %s:%d : %s", function, file, line, assert_condition);
return;
} else if (do_fork) {
pid = fork();
} else {
crm_err("%s: Triggered fatal assert at %s:%d : %s", function, file, line, assert_condition);
}
if (pid == -1) {
crm_crit("%s: Cannot create core for non-fatal assert at %s:%d : %s",
function, file, line, assert_condition);
return;
} else if(pid == 0) {
/* Child process */
abort();
return;
}
/* Parent process */
crm_err("%s: Forked child %d to record non-fatal assert at %s:%d : %s",
function, pid, file, line, assert_condition);
crm_write_blackbox(SIGTRAP, NULL);
do {
rc = waitpid(pid, &status, 0);
if(rc == pid) {
return; /* Job done */
}
} while(errno == EINTR);
if (errno == ECHILD) {
/* crm_mon does this */
crm_trace("Cannot wait on forked child %d - SIGCHLD is probably set to SIG_IGN", pid);
return;
}
crm_perror(LOG_ERR, "Cannot wait on forked child %d", pid);
}
int
crm_pid_active(long pid, const char *daemon)
{
static int have_proc_pid = 0;
if(have_proc_pid == 0) {
char proc_path[PATH_MAX], exe_path[PATH_MAX];
/* check to make sure pid hasn't been reused by another process */
snprintf(proc_path, sizeof(proc_path), "/proc/%lu/exe", (long unsigned int)getpid());
have_proc_pid = 1;
if(readlink(proc_path, exe_path, PATH_MAX - 1) < 0) {
have_proc_pid = -1;
}
}
if (pid <= 0) {
return -1;
} else if (kill(pid, 0) < 0 && errno == ESRCH) {
return 0;
} else if(daemon == NULL || have_proc_pid == -1) {
return 1;
} else {
int rc = 0;
char proc_path[PATH_MAX], exe_path[PATH_MAX], myexe_path[PATH_MAX];
/* check to make sure pid hasn't been reused by another process */
snprintf(proc_path, sizeof(proc_path), "/proc/%lu/exe", pid);
rc = readlink(proc_path, exe_path, PATH_MAX - 1);
if (rc < 0 && errno == EACCES) {
crm_perror(LOG_INFO, "Could not read from %s", proc_path);
return 1;
} else if (rc < 0) {
crm_perror(LOG_ERR, "Could not read from %s", proc_path);
return 0;
}
exe_path[rc] = 0;
if(daemon[0] != '/') {
rc = snprintf(myexe_path, sizeof(proc_path), CRM_DAEMON_DIR"/%s", daemon);
myexe_path[rc] = 0;
} else {
rc = snprintf(myexe_path, sizeof(proc_path), "%s", daemon);
myexe_path[rc] = 0;
}
if (strcmp(exe_path, myexe_path) == 0) {
return 1;
}
}
return 0;
}
#define LOCKSTRLEN 11
long
crm_read_pidfile(const char *filename)
{
int fd;
struct stat sbuf;
long pid = -ENOENT;
char buf[LOCKSTRLEN + 1];
if ((fd = open(filename, O_RDONLY)) < 0) {
goto bail;
}
if (fstat(fd, &sbuf) >= 0 && sbuf.st_size < LOCKSTRLEN) {
sleep(2); /* if someone was about to create one,
* give'm a sec to do so
*/
}
if (read(fd, buf, sizeof(buf)) < 1) {
goto bail;
}
if (sscanf(buf, "%lu", &pid) > 0) {
if (pid <= 0) {
pid = -ESRCH;
} else {
crm_trace("Got pid %lu from %s\n", pid, filename);
}
}
bail:
if (fd >= 0) {
close(fd);
}
return pid;
}
long
crm_pidfile_inuse(const char *filename, long mypid, const char *daemon)
{
long pid = crm_read_pidfile(filename);
if (pid < 2) {
/* Invalid pid */
pid = -ENOENT;
unlink(filename);
} else if (mypid && pid == mypid) {
/* In use by us */
pid = pcmk_ok;
} else if (crm_pid_active(pid, daemon) == FALSE) {
/* Contains a stale value */
unlink(filename);
pid = -ENOENT;
} else if (mypid && pid != mypid) {
/* locked by existing process - give up */
pid = -EEXIST;
}
return pid;
}
static int
crm_lock_pidfile(const char *filename, const char *name)
{
long mypid = 0;
int fd = 0, rc = 0;
char buf[LOCKSTRLEN + 1];
mypid = (unsigned long)getpid();
rc = crm_pidfile_inuse(filename, 0, name);
if (rc == -ENOENT) {
/* exists but the process is not active */
} else if (rc != pcmk_ok) {
/* locked by existing process - give up */
return rc;
}
if ((fd = open(filename, O_CREAT | O_WRONLY | O_EXCL, 0644)) < 0) {
/* Hmmh, why did we fail? Anyway, nothing we can do about it */
return -errno;
}
snprintf(buf, sizeof(buf), "%*lu\n", LOCKSTRLEN - 1, mypid);
rc = write(fd, buf, LOCKSTRLEN);
close(fd);
if (rc != LOCKSTRLEN) {
crm_perror(LOG_ERR, "Incomplete write to %s", filename);
return -errno;
}
return crm_pidfile_inuse(filename, mypid, name);
}
void
crm_make_daemon(const char *name, gboolean daemonize, const char *pidfile)
{
int rc;
long pid;
const char *devnull = "/dev/null";
if (daemonize == FALSE) {
return;
}
/* Check before we even try... */
rc = crm_pidfile_inuse(pidfile, 1, name);
if(rc < pcmk_ok && rc != -ENOENT) {
pid = crm_read_pidfile(pidfile);
crm_err("%s: already running [pid %ld in %s]", name, pid, pidfile);
printf("%s: already running [pid %ld in %s]\n", name, pid, pidfile);
crm_exit(rc);
}
pid = fork();
if (pid < 0) {
fprintf(stderr, "%s: could not start daemon\n", name);
crm_perror(LOG_ERR, "fork");
crm_exit(EINVAL);
} else if (pid > 0) {
crm_exit(pcmk_ok);
}
rc = crm_lock_pidfile(pidfile, name);
if(rc < pcmk_ok) {
crm_err("Could not lock '%s' for %s: %s (%d)", pidfile, name, pcmk_strerror(rc), rc);
printf("Could not lock '%s' for %s: %s (%d)\n", pidfile, name, pcmk_strerror(rc), rc);
crm_exit(rc);
}
umask(S_IWGRP | S_IWOTH | S_IROTH);
close(STDIN_FILENO);
(void)open(devnull, O_RDONLY); /* Stdin: fd 0 */
close(STDOUT_FILENO);
(void)open(devnull, O_WRONLY); /* Stdout: fd 1 */
close(STDERR_FILENO);
(void)open(devnull, O_WRONLY); /* Stderr: fd 2 */
}
char *
crm_meta_name(const char *field)
{
int lpc = 0;
int max = 0;
char *crm_name = NULL;
CRM_CHECK(field != NULL, return NULL);
crm_name = crm_concat(CRM_META, field, '_');
/* Massage the names so they can be used as shell variables */
max = strlen(crm_name);
for (; lpc < max; lpc++) {
switch (crm_name[lpc]) {
case '-':
crm_name[lpc] = '_';
break;
}
}
return crm_name;
}
const char *
crm_meta_value(GHashTable * hash, const char *field)
{
char *key = NULL;
const char *value = NULL;
key = crm_meta_name(field);
if (key) {
value = g_hash_table_lookup(hash, key);
free(key);
}
return value;
}
static struct option *
crm_create_long_opts(struct crm_option *long_options)
{
struct option *long_opts = NULL;
#ifdef HAVE_GETOPT_H
int index = 0, lpc = 0;
/*
* A previous, possibly poor, choice of '?' as the short form of --help
* means that getopt_long() returns '?' for both --help and for "unknown option"
*
* This dummy entry allows us to differentiate between the two in crm_get_option()
* and exit with the correct error code
*/
long_opts = realloc_safe(long_opts, (index + 1) * sizeof(struct option));
long_opts[index].name = "__dummmy__";
long_opts[index].has_arg = 0;
long_opts[index].flag = 0;
long_opts[index].val = '_';
index++;
for (lpc = 0; long_options[lpc].name != NULL; lpc++) {
if (long_options[lpc].name[0] == '-') {
continue;
}
long_opts = realloc_safe(long_opts, (index + 1) * sizeof(struct option));
/*fprintf(stderr, "Creating %d %s = %c\n", index,
* long_options[lpc].name, long_options[lpc].val); */
long_opts[index].name = long_options[lpc].name;
long_opts[index].has_arg = long_options[lpc].has_arg;
long_opts[index].flag = long_options[lpc].flag;
long_opts[index].val = long_options[lpc].val;
index++;
}
/* Now create the list terminator */
long_opts = realloc_safe(long_opts, (index + 1) * sizeof(struct option));
long_opts[index].name = NULL;
long_opts[index].has_arg = 0;
long_opts[index].flag = 0;
long_opts[index].val = 0;
#endif
return long_opts;
}
void
crm_set_options(const char *short_options, const char *app_usage, struct crm_option *long_options,
const char *app_desc)
{
if (short_options) {
crm_short_options = strdup(short_options);
} else if (long_options) {
int lpc = 0;
int opt_string_len = 0;
char *local_short_options = NULL;
for (lpc = 0; long_options[lpc].name != NULL; lpc++) {
if (long_options[lpc].val && long_options[lpc].val != '-' && long_options[lpc].val < UCHAR_MAX) {
local_short_options = realloc_safe(local_short_options, opt_string_len + 4);
local_short_options[opt_string_len++] = long_options[lpc].val;
/* getopt(3) says: Two colons mean an option takes an optional arg; */
if (long_options[lpc].has_arg == optional_argument) {
local_short_options[opt_string_len++] = ':';
}
if (long_options[lpc].has_arg >= required_argument) {
local_short_options[opt_string_len++] = ':';
}
local_short_options[opt_string_len] = 0;
}
}
crm_short_options = local_short_options;
crm_trace("Generated short option string: '%s'", local_short_options);
}
if (long_options) {
crm_long_options = long_options;
}
if (app_desc) {
crm_app_description = app_desc;
}
if (app_usage) {
crm_app_usage = app_usage;
}
}
int
crm_get_option(int argc, char **argv, int *index)
{
return crm_get_option_long(argc, argv, index, NULL);
}
int
crm_get_option_long(int argc, char **argv, int *index, const char **longname)
{
#ifdef HAVE_GETOPT_H
static struct option *long_opts = NULL;
if (long_opts == NULL && crm_long_options) {
long_opts = crm_create_long_opts(crm_long_options);
}
*index = 0;
if (long_opts) {
int flag = getopt_long(argc, argv, crm_short_options, long_opts, index);
switch (flag) {
case 0:
if (long_opts[*index].val) {
return long_opts[*index].val;
} else if (longname) {
*longname = long_opts[*index].name;
} else {
crm_notice("Unhandled option --%s", long_opts[*index].name);
return flag;
}
case -1: /* End of option processing */
break;
case ':':
crm_trace("Missing argument");
crm_help('?', 1);
break;
case '?':
crm_help('?', *index ? 0 : 1);
break;
}
return flag;
}
#endif
if (crm_short_options) {
return getopt(argc, argv, crm_short_options);
}
return -1;
}
int
crm_help(char cmd, int exit_code)
{
int i = 0;
FILE *stream = (exit_code ? stderr : stdout);
if (cmd == 'v' || cmd == '$') {
fprintf(stream, "Pacemaker %s\n", PACEMAKER_VERSION);
fprintf(stream, "Written by Andrew Beekhof\n");
goto out;
}
if (cmd == '!') {
fprintf(stream, "Pacemaker %s (Build: %s): %s\n", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES);
goto out;
}
fprintf(stream, "%s - %s\n", crm_system_name, crm_app_description);
if (crm_app_usage) {
fprintf(stream, "Usage: %s %s\n", crm_system_name, crm_app_usage);
}
if (crm_long_options) {
fprintf(stream, "Options:\n");
for (i = 0; crm_long_options[i].name != NULL; i++) {
if (crm_long_options[i].flags & pcmk_option_hidden) {
} else if (crm_long_options[i].flags & pcmk_option_paragraph) {
fprintf(stream, "%s\n\n", crm_long_options[i].desc);
} else if (crm_long_options[i].flags & pcmk_option_example) {
fprintf(stream, "\t#%s\n\n", crm_long_options[i].desc);
} else if (crm_long_options[i].val == '-' && crm_long_options[i].desc) {
fprintf(stream, "%s\n", crm_long_options[i].desc);
} else {
/* is val printable as char ? */
if (crm_long_options[i].val && crm_long_options[i].val <= UCHAR_MAX) {
fprintf(stream, " -%c,", crm_long_options[i].val);
} else {
fputs(" ", stream);
}
fprintf(stream, " --%s%s\t%s\n", crm_long_options[i].name,
crm_long_options[i].has_arg == optional_argument ? "[=value]" :
crm_long_options[i].has_arg == required_argument ? "=value" : "",
crm_long_options[i].desc ? crm_long_options[i].desc : "");
}
}
} else if (crm_short_options) {
fprintf(stream, "Usage: %s - %s\n", crm_system_name, crm_app_description);
for (i = 0; crm_short_options[i] != 0; i++) {
int has_arg = no_argument /* 0 */;
if (crm_short_options[i + 1] == ':') {
if (crm_short_options[i + 2] == ':')
has_arg = optional_argument /* 2 */;
else
has_arg = required_argument /* 1 */;
}
fprintf(stream, " -%c %s\n", crm_short_options[i],
has_arg == optional_argument ? "[value]" :
has_arg == required_argument ? "{value}" : "");
i += has_arg;
}
}
fprintf(stream, "\nReport bugs to %s\n", PACKAGE_BUGREPORT);
out:
return crm_exit(exit_code);
}
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)
{
*ipcs_ro = mainloop_add_ipc_server(cib_channel_ro, QB_IPC_NATIVE, ro_cb);
*ipcs_rw = mainloop_add_ipc_server(cib_channel_rw, QB_IPC_NATIVE, rw_cb);
*ipcs_shm = mainloop_add_ipc_server(cib_channel_shm, QB_IPC_SHM, rw_cb);
if (*ipcs_ro == NULL || *ipcs_rw == NULL || *ipcs_shm == NULL) {
crm_err("Failed to create cib servers: exiting and inhibiting respawn.");
crm_warn("Verify pacemaker and pacemaker_remote are not both enabled.");
crm_exit(DAEMON_RESPAWN_STOP);
}
}
void cib_ipc_servers_destroy(qb_ipcs_service_t *ipcs_ro,
qb_ipcs_service_t *ipcs_rw,
qb_ipcs_service_t *ipcs_shm)
{
qb_ipcs_destroy(ipcs_ro);
qb_ipcs_destroy(ipcs_rw);
qb_ipcs_destroy(ipcs_shm);
}
qb_ipcs_service_t *
crmd_ipc_server_init(struct qb_ipcs_service_handlers *cb)
{
return mainloop_add_ipc_server(CRM_SYSTEM_CRMD, QB_IPC_NATIVE, cb);
}
void
attrd_ipc_server_init(qb_ipcs_service_t **ipcs, struct qb_ipcs_service_handlers *cb)
{
*ipcs = mainloop_add_ipc_server(T_ATTRD, QB_IPC_NATIVE, cb);
if (*ipcs == NULL) {
crm_err("Failed to create attrd servers: exiting and inhibiting respawn.");
crm_warn("Verify pacemaker and pacemaker_remote are not both enabled.");
crm_exit(DAEMON_RESPAWN_STOP);
}
}
void
stonith_ipc_server_init(qb_ipcs_service_t **ipcs, struct qb_ipcs_service_handlers *cb)
{
*ipcs = mainloop_add_ipc_server("stonith-ng", QB_IPC_NATIVE, cb);
if (*ipcs == NULL) {
crm_err("Failed to create stonith-ng servers: exiting and inhibiting respawn.");
crm_warn("Verify pacemaker and pacemaker_remote are not both enabled.");
crm_exit(DAEMON_RESPAWN_STOP);
}
}
-#define FAKE_TE_ID "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
-static void
-append_digest(lrmd_event_data_t * op, xmlNode * update, const char *version, const char *magic,
- int level)
-{
- /* this will enable us to later determine that the
- * resource's parameters have changed and we should force
- * a restart
- */
- char *digest = NULL;
- xmlNode *args_xml = NULL;
-
- if (op->params == NULL) {
- return;
- }
-
- args_xml = create_xml_node(NULL, XML_TAG_PARAMS);
- g_hash_table_foreach(op->params, hash2field, args_xml);
- filter_action_parameters(args_xml, version);
- digest = calculate_operation_digest(args_xml, version);
-
-#if 0
- if (level < get_crm_log_level()
- && op->interval == 0 && crm_str_eq(op->op_type, CRMD_ACTION_START, TRUE)) {
- char *digest_source = dump_xml_unformatted(args_xml);
-
- do_crm_log(level, "Calculated digest %s for %s (%s). Source: %s\n",
- digest, ID(update), magic, digest_source);
- free(digest_source);
- }
-#endif
- crm_xml_add(update, XML_LRM_ATTR_OP_DIGEST, digest);
-
- free_xml(args_xml);
- free(digest);
-}
-
-int
-rsc_op_expected_rc(lrmd_event_data_t * op)
-{
- int rc = 0;
-
- if (op && op->user_data) {
- int dummy = 0;
- char *uuid = NULL;
-
- decode_transition_key(op->user_data, &uuid, &dummy, &dummy, &rc);
- free(uuid);
- }
- return rc;
-}
-
-gboolean
-did_rsc_op_fail(lrmd_event_data_t * op, int target_rc)
-{
- switch (op->op_status) {
- case PCMK_LRM_OP_CANCELLED:
- case PCMK_LRM_OP_PENDING:
- return FALSE;
- break;
-
- case PCMK_LRM_OP_NOTSUPPORTED:
- case PCMK_LRM_OP_TIMEOUT:
- case PCMK_LRM_OP_ERROR:
- return TRUE;
- break;
-
- default:
- if (target_rc != op->rc) {
- return TRUE;
- }
- }
-
- return FALSE;
-}
-
-xmlNode *
-create_operation_update(xmlNode * parent, lrmd_event_data_t * op, const char * caller_version,
- int target_rc, const char * node, const char * origin, int level)
-{
- char *key = NULL;
- char *magic = NULL;
- char *op_id = NULL;
- char *op_id_additional = NULL;
- char *local_user_data = NULL;
- const char *exit_reason = NULL;
-
- xmlNode *xml_op = NULL;
- const char *task = NULL;
- gboolean dc_munges_migrate_ops = (compare_version(caller_version, "3.0.3") < 0);
- gboolean dc_needs_unique_ops = (compare_version(caller_version, "3.0.6") < 0);
-
- CRM_CHECK(op != NULL, return NULL);
- do_crm_log(level, "%s: Updating resource %s after %s op %s (interval=%d)",
- origin, op->rsc_id, op->op_type, services_lrm_status_str(op->op_status),
- op->interval);
-
- crm_trace("DC version: %s", caller_version);
-
- task = op->op_type;
- /* remap the task name under various scenarios
- * this makes life easier for the PE when trying determine the current state
- */
- if (crm_str_eq(task, "reload", TRUE)) {
- if (op->op_status == PCMK_LRM_OP_DONE) {
- task = CRMD_ACTION_START;
- } else {
- task = CRMD_ACTION_STATUS;
- }
-
- } else if (dc_munges_migrate_ops && crm_str_eq(task, CRMD_ACTION_MIGRATE, TRUE)) {
- /* if the migrate_from fails it will have enough info to do the right thing */
- if (op->op_status == PCMK_LRM_OP_DONE) {
- task = CRMD_ACTION_STOP;
- } else {
- task = CRMD_ACTION_STATUS;
- }
-
- } else if (dc_munges_migrate_ops
- && op->op_status == PCMK_LRM_OP_DONE
- && crm_str_eq(task, CRMD_ACTION_MIGRATED, TRUE)) {
- task = CRMD_ACTION_START;
- }
-
- key = generate_op_key(op->rsc_id, task, op->interval);
- if (dc_needs_unique_ops && op->interval > 0) {
- op_id = strdup(key);
-
- } else if (crm_str_eq(task, CRMD_ACTION_NOTIFY, TRUE)) {
- const char *n_type = crm_meta_value(op->params, "notify_type");
- const char *n_task = crm_meta_value(op->params, "notify_operation");
-
- CRM_LOG_ASSERT(n_type != NULL);
- CRM_LOG_ASSERT(n_task != NULL);
- op_id = generate_notify_key(op->rsc_id, n_type, n_task);
-
- /* these are not yet allowed to fail */
- op->op_status = PCMK_LRM_OP_DONE;
- op->rc = 0;
-
- } else if (did_rsc_op_fail(op, target_rc)) {
- op_id = generate_op_key(op->rsc_id, "last_failure", 0);
- if (op->interval == 0) {
- /* Ensure 'last' gets updated too in case recording-pending="true" */
- op_id_additional = generate_op_key(op->rsc_id, "last", 0);
- }
- exit_reason = op->exit_reason;
-
- } else if (op->interval > 0) {
- op_id = strdup(key);
-
- } else {
- op_id = generate_op_key(op->rsc_id, "last", 0);
- }
-
- again:
- xml_op = find_entity(parent, XML_LRM_TAG_RSC_OP, op_id);
- if (xml_op == NULL) {
- xml_op = create_xml_node(parent, XML_LRM_TAG_RSC_OP);
- }
-
- if (op->user_data == NULL) {
- crm_debug("Generating fake transition key for:"
- " %s_%s_%d %d from %s",
- op->rsc_id, op->op_type, op->interval, op->call_id, origin);
- local_user_data = generate_transition_key(-1, op->call_id, target_rc, FAKE_TE_ID);
- op->user_data = local_user_data;
- }
-
- if(magic == NULL) {
- magic = generate_transition_magic(op->user_data, op->op_status, op->rc);
- }
-
- crm_xml_add(xml_op, XML_ATTR_ID, op_id);
- crm_xml_add(xml_op, XML_LRM_ATTR_TASK_KEY, key);
- crm_xml_add(xml_op, XML_LRM_ATTR_TASK, task);
- crm_xml_add(xml_op, XML_ATTR_ORIGIN, origin);
- crm_xml_add(xml_op, XML_ATTR_CRM_VERSION, caller_version);
- crm_xml_add(xml_op, XML_ATTR_TRANSITION_KEY, op->user_data);
- crm_xml_add(xml_op, XML_ATTR_TRANSITION_MAGIC, magic);
- crm_xml_add(xml_op, XML_LRM_ATTR_EXIT_REASON, exit_reason);
- crm_xml_add(xml_op, XML_LRM_ATTR_TARGET, node); /* For context during triage */
-
- crm_xml_add_int(xml_op, XML_LRM_ATTR_CALLID, op->call_id);
- crm_xml_add_int(xml_op, XML_LRM_ATTR_RC, op->rc);
- crm_xml_add_int(xml_op, XML_LRM_ATTR_OPSTATUS, op->op_status);
- crm_xml_add_int(xml_op, XML_LRM_ATTR_INTERVAL, op->interval);
-
- if (compare_version("2.1", caller_version) <= 0) {
- if (op->t_run || op->t_rcchange || op->exec_time || op->queue_time) {
- crm_trace("Timing data (%s_%s_%d): last=%u change=%u exec=%u queue=%u",
- op->rsc_id, op->op_type, op->interval,
- op->t_run, op->t_rcchange, op->exec_time, op->queue_time);
-
- if (op->interval == 0) {
- /* The values are the same for non-recurring ops */
- crm_xml_add_int(xml_op, XML_RSC_OP_LAST_RUN, op->t_run);
- crm_xml_add_int(xml_op, XML_RSC_OP_LAST_CHANGE, op->t_run);
-
- } else if(op->t_rcchange) {
- /* last-run is not accurate for recurring ops */
- crm_xml_add_int(xml_op, XML_RSC_OP_LAST_CHANGE, op->t_rcchange);
-
- } else {
- /* ...but is better than nothing otherwise */
- crm_xml_add_int(xml_op, XML_RSC_OP_LAST_CHANGE, op->t_run);
- }
-
- crm_xml_add_int(xml_op, XML_RSC_OP_T_EXEC, op->exec_time);
- crm_xml_add_int(xml_op, XML_RSC_OP_T_QUEUE, op->queue_time);
- }
- }
-
- if (crm_str_eq(op->op_type, CRMD_ACTION_MIGRATE, TRUE)
- || crm_str_eq(op->op_type, CRMD_ACTION_MIGRATED, TRUE)) {
- /*
- * Record migrate_source and migrate_target always for migrate ops.
- */
- const char *name = XML_LRM_ATTR_MIGRATE_SOURCE;
-
- crm_xml_add(xml_op, name, crm_meta_value(op->params, name));
-
- name = XML_LRM_ATTR_MIGRATE_TARGET;
- crm_xml_add(xml_op, name, crm_meta_value(op->params, name));
- }
-
- append_digest(op, xml_op, caller_version, magic, LOG_DEBUG);
-
- if (op_id_additional) {
- free(op_id);
- op_id = op_id_additional;
- op_id_additional = NULL;
- goto again;
- }
-
- if (local_user_data) {
- free(local_user_data);
- op->user_data = NULL;
- }
- free(magic);
- free(op_id);
- free(key);
- return xml_op;
-}
-
bool
pcmk_acl_required(const char *user)
{
#if ENABLE_ACL
if(user == NULL || strlen(user) == 0) {
crm_trace("no user set");
return FALSE;
} else if (strcmp(user, CRM_DAEMON_USER) == 0) {
return FALSE;
} else if (strcmp(user, "root") == 0) {
return FALSE;
}
crm_trace("acls required for %s", user);
return TRUE;
#else
crm_trace("acls not supported");
return FALSE;
#endif
}
#if ENABLE_ACL
char *
uid2username(uid_t uid)
{
struct passwd *pwent = getpwuid(uid);
if (pwent == NULL) {
crm_perror(LOG_ERR, "Cannot get password entry of uid: %d", uid);
return NULL;
} else {
return strdup(pwent->pw_name);
}
}
const char *
crm_acl_get_set_user(xmlNode * request, const char *field, const char *peer_user)
{
/* field is only checked for backwards compatibility */
static const char *effective_user = NULL;
const char *requested_user = NULL;
const char *user = NULL;
if(effective_user == NULL) {
effective_user = uid2username(geteuid());
}
requested_user = crm_element_value(request, XML_ACL_TAG_USER);
if(requested_user == NULL) {
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;
}
/* Yes, pointer comparision */
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;
}
void
determine_request_user(const char *user, xmlNode * request, const char *field)
{
/* Get our internal validation out of the way first */
CRM_CHECK(user != NULL && request != NULL && field != NULL, return);
/* If our peer is a privileged user, we might be doing something on behalf of someone else */
if (is_privileged(user) == FALSE) {
/* We're not a privileged user, set or overwrite any existing value for $field */
crm_xml_replace(request, field, user);
} else if (crm_element_value(request, field) == NULL) {
/* Even if we're privileged, make sure there is always a value set */
crm_xml_replace(request, field, user);
/* } else { Legal delegation */
}
crm_trace("Processing msg as user '%s'", crm_element_value(request, field));
}
#endif
void *
find_library_function(void **handle, const char *lib, const char *fn, gboolean fatal)
{
char *error;
void *a_function;
if (*handle == NULL) {
*handle = dlopen(lib, RTLD_LAZY);
}
if (!(*handle)) {
crm_err("%sCould not open %s: %s", fatal ? "Fatal: " : "", lib, dlerror());
if (fatal) {
crm_exit(DAEMON_RESPAWN_STOP);
}
return NULL;
}
a_function = dlsym(*handle, fn);
if (a_function == NULL) {
error = dlerror();
crm_err("%sCould not find %s in %s: %s", fatal ? "Fatal: " : "", fn, lib, error);
if (fatal) {
crm_exit(DAEMON_RESPAWN_STOP);
}
}
return a_function;
}
void *
convert_const_pointer(const void *ptr)
{
/* Worst function ever */
return (void *)ptr;
}
#ifdef HAVE_UUID_UUID_H
# include <uuid/uuid.h>
#endif
char *
crm_generate_uuid(void)
{
unsigned char uuid[16];
char *buffer = malloc(37); /* Including NUL byte */
uuid_generate(uuid);
uuid_unparse(uuid, buffer);
return buffer;
}
/*!
* \brief Check whether a string represents a cluster daemon name
*
* \param[in] name String to check
*
* \return TRUE if name is standard client name used by daemons, FALSE otherwise
*/
bool
crm_is_daemon_name(const char *name)
{
return (name &&
(!strcmp(name, CRM_SYSTEM_CRMD)
|| !strcmp(name, CRM_SYSTEM_STONITHD)
|| !strcmp(name, T_ATTRD)
|| !strcmp(name, CRM_SYSTEM_CIB)
|| !strcmp(name, CRM_SYSTEM_MCP)
|| !strcmp(name, CRM_SYSTEM_DC)
|| !strcmp(name, CRM_SYSTEM_TENGINE)
|| !strcmp(name, CRM_SYSTEM_LRMD)));
}
#include <md5.h>
char *
crm_md5sum(const char *buffer)
{
int lpc = 0, len = 0;
char *digest = NULL;
unsigned char raw_digest[MD5_DIGEST_SIZE];
if (buffer == NULL) {
buffer = "";
}
len = strlen(buffer);
crm_trace("Beginning digest of %d bytes", len);
digest = malloc(2 * MD5_DIGEST_SIZE + 1);
if(digest) {
md5_buffer(buffer, len, raw_digest);
for (lpc = 0; lpc < MD5_DIGEST_SIZE; lpc++) {
sprintf(digest + (2 * lpc), "%02x", raw_digest[lpc]);
}
digest[(2 * MD5_DIGEST_SIZE)] = 0;
crm_trace("Digest %s.", digest);
} else {
crm_err("Could not create digest");
}
return digest;
}
#ifdef HAVE_GNUTLS_GNUTLS_H
void
crm_gnutls_global_init(void)
{
signal(SIGPIPE, SIG_IGN);
gnutls_global_init();
}
#endif
char *
crm_generate_ra_key(const char *class, const char *provider, const char *type)
{
if (!class && !provider && !type) {
return NULL;
}
- return crm_strdup_printf("%s:%s:%s", class ? class : "", provider ? provider : "", type ? type : "");
+ return crm_strdup_printf("%s%s%s:%s",
+ (class? class : ""),
+ (provider? ":" : ""), (provider? provider : ""),
+ (type? type : ""));
}
diff --git a/lib/lrmd/lrmd_alerts.c b/lib/lrmd/lrmd_alerts.c
index 487e48c470..ae78c77c6c 100644
--- a/lib/lrmd/lrmd_alerts.c
+++ b/lib/lrmd/lrmd_alerts.c
@@ -1,379 +1,367 @@
/*
* Copyright (c) 2015 David Vossel <davidvossel@gmail.com>
*
* This library 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.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser 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
*
*/
#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_alerts_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,
const char *value)
{
const char **key;
if (value == NULL) {
value = "";
}
for (key = crm_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,
int value)
{
char *value_s = crm_itoa(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)
{
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] alert_list Alerts to execute
- * \param[in] lrmd_connect_func Function that returns an LRMD connection
- * \param[in] kind Type of event that is being alerted for
- * \param[in] attr_name If crm_alert_attribute, the attribute name
- * \param[in,out] params Environment variables to pass to agents
+ * \param[in] lrmd LRMD 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,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(GList *alert_list, lrmd_t *(*lrmd_connect_func)(void),
- enum crm_alert_flags kind, const char *attr_name,
- lrmd_key_value_t *params)
+exec_alert_list(lrmd_t *lrmd, GList *alert_list, enum crm_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);
crm_time_hr_t *now = NULL;
params = alert_key2param(params, CRM_alert_kind, kind_s);
params = alert_key2param(params, CRM_alert_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);
lrmd_key_value_t *copy_params = NULL;
lrmd_key_value_t *head = NULL;
- lrmd_t *lrmd_conn = 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)
&& !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) {
now = crm_time_hr_new(NULL);
}
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,
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,
timestamp);
free(timestamp);
}
}
copy_params = alert_envvar2params(copy_params, entry);
- lrmd_conn = (*lrmd_connect_func)();
- if (lrmd_conn == NULL) {
- crm_warn("Cannot send alerts: No LRMD connection");
- any_failure = TRUE;
- goto done;
- }
-
- rc = lrmd_conn->cmds->exec_alert(lrmd_conn, entry->id, entry->path,
- entry->timeout, copy_params);
+ 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;
}
}
-done:
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] alert_list List of alert agents to execute
- * \param[in] lrmd_connect_func Function that returns an LRMD connection
- * \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
+ * \param[in] lrmd LRMD 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(GList *alert_list, lrmd_t *(*lrmd_connect_func)(void),
+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 (alert_list == NULL) {
- return pcmk_ok;
+ 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);
- rc = exec_alert_list(alert_list, lrmd_connect_func, crm_alert_attribute,
- attr_name, params);
+ rc = exec_alert_list(lrmd, alert_list, crm_alert_attribute, attr_name,
+ params);
lrmd_key_value_freeall(params);
return rc;
}
/*!
* \internal
* \brief Send an alert for a node membership event
*
- * \param[in] alert_list List of alert agents to execute
- * \param[in] lrmd_connect_func Function that returns an LRMD connection
- * \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
+ * \param[in] lrmd LRMD 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(GList *alert_list, lrmd_t *(*lrmd_connect_func)(void),
+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 (alert_list == NULL) {
- return pcmk_ok;
+ 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);
- rc = exec_alert_list(alert_list, lrmd_connect_func, crm_alert_node, NULL,
- params);
+ rc = exec_alert_list(lrmd, alert_list, crm_alert_node, NULL, params);
lrmd_key_value_freeall(params);
return rc;
}
/*!
* \internal
* \brief Send an alert for a fencing event
*
- * \param[in] alert_list List of alert agents to execute
- * \param[in] lrmd_connect_func Function that returns an LRMD connection
- * \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
+ * \param[in] lrmd LRMD 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(GList *alert_list, lrmd_t *(*lrmd_connect_func)(void),
+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 (alert_list == NULL) {
- return pcmk_ok;
+ 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);
- rc = exec_alert_list(alert_list, lrmd_connect_func, crm_alert_fencing,
- NULL, params);
+ rc = exec_alert_list(lrmd, alert_list, crm_alert_fencing, NULL, params);
lrmd_key_value_freeall(params);
return rc;
}
/*!
* \internal
* \brief Send an alert for a resource operation
*
- * \param[in] alert_list List of alert agents to execute
- * \param[in] lrmd_connect_func Function that returns an LRMD connection
- * \param[in] node Name of node that executed operation
- * \param[in] op Resource operation
+ * \param[in] lrmd LRMD 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(GList *alert_list, lrmd_t *(*lrmd_connect_func)(void),
+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 (alert_list == NULL) {
- return pcmk_ok;
+ if (lrmd == NULL) {
+ return -2;
}
target_rc = rsc_op_expected_rc(op);
if ((op->interval == 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_int(params, CRM_alert_interval, op->interval);
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);
if (op->op_status == PCMK_LRM_OP_DONE) {
params = alert_key2param(params, CRM_alert_desc, services_ocf_exitcode_str(op->rc));
} else {
params = alert_key2param(params, CRM_alert_desc, services_lrm_status_str(op->op_status));
}
- rc = exec_alert_list(alert_list, lrmd_connect_func, crm_alert_resource,
- NULL, params);
+ rc = exec_alert_list(lrmd, alert_list, crm_alert_resource, NULL, params);
lrmd_key_value_freeall(params);
return rc;
}
diff --git a/lib/lrmd/lrmd_client.c b/lib/lrmd/lrmd_client.c
index ef6932f893..72aacb7de9 100644
--- a/lib/lrmd/lrmd_client.c
+++ b/lib/lrmd/lrmd_client.c
@@ -1,2261 +1,1913 @@
/*
* Copyright (c) 2012 David Vossel <davidvossel@gmail.com>
*
* This library 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.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser 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
*
*/
#include <crm_internal.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <glib.h>
#include <dirent.h>
#include <crm/crm.h>
#include <crm/lrmd.h>
#include <crm/services.h>
#include <crm/common/mainloop.h>
#include <crm/common/ipcs.h>
#include <crm/msg_xml.h>
#include <crm/stonith-ng.h>
#ifdef HAVE_GNUTLS_GNUTLS_H
# undef KEYFILE
# include <gnutls/gnutls.h>
#endif
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <netdb.h>
#define MAX_TLS_RECV_WAIT 10000
CRM_TRACE_INIT_DATA(lrmd);
static int lrmd_api_disconnect(lrmd_t * lrmd);
static int lrmd_api_is_connected(lrmd_t * lrmd);
/* IPC proxy functions */
int lrmd_internal_proxy_send(lrmd_t * lrmd, xmlNode *msg);
static void lrmd_internal_proxy_dispatch(lrmd_t *lrmd, xmlNode *msg);
void lrmd_internal_set_proxy_callback(lrmd_t * lrmd, void *userdata, void (*callback)(lrmd_t *lrmd, void *userdata, xmlNode *msg));
#ifdef HAVE_GNUTLS_GNUTLS_H
# define LRMD_CLIENT_HANDSHAKE_TIMEOUT 5000 /* 5 seconds */
gnutls_psk_client_credentials_t psk_cred_s;
int lrmd_tls_set_key(gnutls_datum_t * key);
static void lrmd_tls_disconnect(lrmd_t * lrmd);
static int global_remote_msg_id = 0;
int lrmd_tls_send_msg(crm_remote_t * session, xmlNode * msg, uint32_t id, const char *msg_type);
static void lrmd_tls_connection_destroy(gpointer userdata);
#endif
typedef struct lrmd_private_s {
enum client_type type;
char *token;
mainloop_io_t *source;
/* IPC parameters */
crm_ipc_t *ipc;
crm_remote_t *remote;
/* Extra TLS parameters */
char *remote_nodename;
#ifdef HAVE_GNUTLS_GNUTLS_H
char *server;
int port;
gnutls_psk_client_credentials_t psk_cred_c;
/* while the async connection is occurring, this is the id
* of the connection timeout timer. */
int async_timer;
int sock;
/* since tls requires a round trip across the network for a
* request/reply, there are times where we just want to be able
* to send a request from the client and not wait around (or even care
* about) what the reply is. */
int expected_late_replies;
GList *pending_notify;
crm_trigger_t *process_notify;
#endif
lrmd_event_callback callback;
/* Internal IPC proxy msg passing for remote guests */
void (*proxy_callback)(lrmd_t *lrmd, void *userdata, xmlNode *msg);
void *proxy_callback_userdata;
char *peer_version;
} lrmd_private_t;
static lrmd_list_t *
lrmd_list_add(lrmd_list_t * head, const char *value)
{
lrmd_list_t *p, *end;
p = calloc(1, sizeof(lrmd_list_t));
p->val = strdup(value);
end = head;
while (end && end->next) {
end = end->next;
}
if (end) {
end->next = p;
} else {
head = p;
}
return head;
}
void
lrmd_list_freeall(lrmd_list_t * head)
{
lrmd_list_t *p;
while (head) {
char *val = (char *)head->val;
p = head->next;
free(val);
free(head);
head = p;
}
}
lrmd_key_value_t *
lrmd_key_value_add(lrmd_key_value_t * head, const char *key, const char *value)
{
lrmd_key_value_t *p, *end;
p = calloc(1, sizeof(lrmd_key_value_t));
p->key = strdup(key);
p->value = strdup(value);
end = head;
while (end && end->next) {
end = end->next;
}
if (end) {
end->next = p;
} else {
head = p;
}
return head;
}
void
lrmd_key_value_freeall(lrmd_key_value_t * head)
{
lrmd_key_value_t *p;
while (head) {
p = head->next;
free(head->key);
free(head->value);
free(head);
head = p;
}
}
lrmd_event_data_t *
lrmd_copy_event(lrmd_event_data_t * event)
{
lrmd_event_data_t *copy = NULL;
copy = calloc(1, sizeof(lrmd_event_data_t));
/* This will get all the int values.
* we just have to be careful not to leave any
* dangling pointers to strings. */
memcpy(copy, event, sizeof(lrmd_event_data_t));
copy->rsc_id = event->rsc_id ? strdup(event->rsc_id) : NULL;
copy->op_type = event->op_type ? strdup(event->op_type) : NULL;
copy->user_data = event->user_data ? strdup(event->user_data) : NULL;
copy->output = event->output ? strdup(event->output) : NULL;
copy->exit_reason = event->exit_reason ? strdup(event->exit_reason) : NULL;
copy->remote_nodename = event->remote_nodename ? strdup(event->remote_nodename) : NULL;
copy->params = crm_str_table_dup(event->params);
return copy;
}
void
lrmd_free_event(lrmd_event_data_t * event)
{
if (!event) {
return;
}
/* free gives me grief if i try to cast */
free((char *)event->rsc_id);
free((char *)event->op_type);
free((char *)event->user_data);
free((char *)event->output);
free((char *)event->exit_reason);
free((char *)event->remote_nodename);
if (event->params) {
g_hash_table_destroy(event->params);
}
free(event);
}
static int
lrmd_dispatch_internal(lrmd_t * lrmd, xmlNode * msg)
{
const char *type;
const char *proxy_session = crm_element_value(msg, F_LRMD_IPC_SESSION);
lrmd_private_t *native = lrmd->private;
lrmd_event_data_t event = { 0, };
if (proxy_session != NULL) {
/* this is proxy business */
lrmd_internal_proxy_dispatch(lrmd, msg);
return 1;
} else if (!native->callback) {
/* no callback set */
crm_trace("notify event received but client has not set callback");
return 1;
}
event.remote_nodename = native->remote_nodename;
type = crm_element_value(msg, F_LRMD_OPERATION);
crm_element_value_int(msg, F_LRMD_CALLID, &event.call_id);
event.rsc_id = crm_element_value(msg, F_LRMD_RSC_ID);
if (crm_str_eq(type, LRMD_OP_RSC_REG, TRUE)) {
event.type = lrmd_event_register;
} else if (crm_str_eq(type, LRMD_OP_RSC_UNREG, TRUE)) {
event.type = lrmd_event_unregister;
} else if (crm_str_eq(type, LRMD_OP_RSC_EXEC, TRUE)) {
crm_element_value_int(msg, F_LRMD_TIMEOUT, &event.timeout);
crm_element_value_int(msg, F_LRMD_RSC_INTERVAL, &event.interval);
crm_element_value_int(msg, F_LRMD_RSC_START_DELAY, &event.start_delay);
crm_element_value_int(msg, F_LRMD_EXEC_RC, (int *)&event.rc);
crm_element_value_int(msg, F_LRMD_OP_STATUS, &event.op_status);
crm_element_value_int(msg, F_LRMD_RSC_DELETED, &event.rsc_deleted);
crm_element_value_int(msg, F_LRMD_RSC_RUN_TIME, (int *)&event.t_run);
crm_element_value_int(msg, F_LRMD_RSC_RCCHANGE_TIME, (int *)&event.t_rcchange);
crm_element_value_int(msg, F_LRMD_RSC_EXEC_TIME, (int *)&event.exec_time);
crm_element_value_int(msg, F_LRMD_RSC_QUEUE_TIME, (int *)&event.queue_time);
event.op_type = crm_element_value(msg, F_LRMD_RSC_ACTION);
event.user_data = crm_element_value(msg, F_LRMD_RSC_USERDATA_STR);
event.output = crm_element_value(msg, F_LRMD_RSC_OUTPUT);
event.exit_reason = crm_element_value(msg, F_LRMD_RSC_EXIT_REASON);
event.type = lrmd_event_exec_complete;
event.params = xml2list(msg);
} else if (crm_str_eq(type, LRMD_OP_NEW_CLIENT, TRUE)) {
event.type = lrmd_event_new_client;
} else if (crm_str_eq(type, LRMD_OP_POKE, TRUE)) {
event.type = lrmd_event_poke;
} else {
return 1;
}
crm_trace("op %s notify event received", type);
native->callback(&event);
if (event.params) {
g_hash_table_destroy(event.params);
}
return 1;
}
static int
lrmd_ipc_dispatch(const char *buffer, ssize_t length, gpointer userdata)
{
lrmd_t *lrmd = userdata;
lrmd_private_t *native = lrmd->private;
xmlNode *msg;
int rc;
if (!native->callback) {
/* no callback set */
return 1;
}
msg = string2xml(buffer);
rc = lrmd_dispatch_internal(lrmd, msg);
free_xml(msg);
return rc;
}
#ifdef HAVE_GNUTLS_GNUTLS_H
static void
lrmd_free_xml(gpointer userdata)
{
free_xml((xmlNode *) userdata);
}
static int
lrmd_tls_connected(lrmd_t * lrmd)
{
lrmd_private_t *native = lrmd->private;
if (native->remote->tls_session) {
return TRUE;
}
return FALSE;
}
static int
lrmd_tls_dispatch(gpointer userdata)
{
lrmd_t *lrmd = userdata;
lrmd_private_t *native = lrmd->private;
xmlNode *xml = NULL;
int rc = 0;
int disconnected = 0;
if (lrmd_tls_connected(lrmd) == FALSE) {
crm_trace("tls dispatch triggered after disconnect");
return 0;
}
crm_trace("tls_dispatch triggered");
/* First check if there are any pending notifies to process that came
* while we were waiting for replies earlier. */
if (native->pending_notify) {
GList *iter = NULL;
crm_trace("Processing pending notifies");
for (iter = native->pending_notify; iter; iter = iter->next) {
lrmd_dispatch_internal(lrmd, iter->data);
}
g_list_free_full(native->pending_notify, lrmd_free_xml);
native->pending_notify = NULL;
}
/* Next read the current buffer and see if there are any messages to handle. */
rc = crm_remote_ready(native->remote, 0);
if (rc == 0) {
/* nothing to read, see if any full messages are already in buffer. */
xml = crm_remote_parse_buffer(native->remote);
} else if (rc < 0) {
disconnected = 1;
} else {
crm_remote_recv(native->remote, -1, &disconnected);
xml = crm_remote_parse_buffer(native->remote);
}
while (xml) {
const char *msg_type = crm_element_value(xml, F_LRMD_REMOTE_MSG_TYPE);
if (safe_str_eq(msg_type, "notify")) {
lrmd_dispatch_internal(lrmd, xml);
} else if (safe_str_eq(msg_type, "reply")) {
if (native->expected_late_replies > 0) {
native->expected_late_replies--;
} else {
int reply_id = 0;
crm_element_value_int(xml, F_LRMD_CALLID, &reply_id);
/* if this happens, we want to know about it */
crm_err("Got outdated reply %d", reply_id);
}
}
free_xml(xml);
xml = crm_remote_parse_buffer(native->remote);
}
if (disconnected) {
crm_info("Server disconnected while reading remote server msg.");
lrmd_tls_disconnect(lrmd);
return 0;
}
return 1;
}
#endif
/* Not used with mainloop */
int
lrmd_poll(lrmd_t * lrmd, int timeout)
{
lrmd_private_t *native = lrmd->private;
switch (native->type) {
case CRM_CLIENT_IPC:
return crm_ipc_ready(native->ipc);
#ifdef HAVE_GNUTLS_GNUTLS_H
case CRM_CLIENT_TLS:
if (native->pending_notify) {
return 1;
}
return crm_remote_ready(native->remote, 0);
#endif
default:
crm_err("Unsupported connection type: %d", native->type);
}
return 0;
}
/* Not used with mainloop */
bool
lrmd_dispatch(lrmd_t * lrmd)
{
lrmd_private_t *private = NULL;
CRM_ASSERT(lrmd != NULL);
private = lrmd->private;
switch (private->type) {
case CRM_CLIENT_IPC:
while (crm_ipc_ready(private->ipc)) {
if (crm_ipc_read(private->ipc) > 0) {
const char *msg = crm_ipc_buffer(private->ipc);
lrmd_ipc_dispatch(msg, strlen(msg), lrmd);
}
}
break;
#ifdef HAVE_GNUTLS_GNUTLS_H
case CRM_CLIENT_TLS:
lrmd_tls_dispatch(lrmd);
break;
#endif
default:
crm_err("Unsupported connection type: %d", private->type);
}
if (lrmd_api_is_connected(lrmd) == FALSE) {
crm_err("Connection closed");
return FALSE;
}
return TRUE;
}
static xmlNode *
lrmd_create_op(const char *token, const char *op, xmlNode *data, int timeout,
enum lrmd_call_options options)
{
xmlNode *op_msg = create_xml_node(NULL, "lrmd_command");
CRM_CHECK(op_msg != NULL, return NULL);
CRM_CHECK(token != NULL, return NULL);
crm_xml_add(op_msg, F_XML_TAGNAME, "lrmd_command");
crm_xml_add(op_msg, F_TYPE, T_LRMD);
crm_xml_add(op_msg, F_LRMD_CALLBACK_TOKEN, token);
crm_xml_add(op_msg, F_LRMD_OPERATION, op);
crm_xml_add_int(op_msg, F_LRMD_TIMEOUT, timeout);
crm_xml_add_int(op_msg, F_LRMD_CALLOPTS, options);
if (data != NULL) {
add_message_xml(op_msg, F_LRMD_CALLDATA, data);
}
crm_trace("Created lrmd %s command with call options %.8lx (%d)",
op, (long)options, options);
return op_msg;
}
static void
lrmd_ipc_connection_destroy(gpointer userdata)
{
lrmd_t *lrmd = userdata;
lrmd_private_t *native = lrmd->private;
crm_info("IPC connection destroyed");
/* Prevent these from being cleaned up in lrmd_api_disconnect() */
native->ipc = NULL;
native->source = NULL;
if (native->callback) {
lrmd_event_data_t event = { 0, };
event.type = lrmd_event_disconnect;
event.remote_nodename = native->remote_nodename;
native->callback(&event);
}
}
#ifdef HAVE_GNUTLS_GNUTLS_H
static void
lrmd_tls_connection_destroy(gpointer userdata)
{
lrmd_t *lrmd = userdata;
lrmd_private_t *native = lrmd->private;
crm_info("TLS connection destroyed");
if (native->remote->tls_session) {
gnutls_bye(*native->remote->tls_session, GNUTLS_SHUT_RDWR);
gnutls_deinit(*native->remote->tls_session);
gnutls_free(native->remote->tls_session);
}
if (native->psk_cred_c) {
gnutls_psk_free_client_credentials(native->psk_cred_c);
}
if (native->sock) {
close(native->sock);
}
if (native->process_notify) {
mainloop_destroy_trigger(native->process_notify);
native->process_notify = NULL;
}
if (native->pending_notify) {
g_list_free_full(native->pending_notify, lrmd_free_xml);
native->pending_notify = NULL;
}
free(native->remote->buffer);
native->remote->buffer = NULL;
native->source = 0;
native->sock = 0;
native->psk_cred_c = NULL;
native->remote->tls_session = NULL;
native->sock = 0;
if (native->callback) {
lrmd_event_data_t event = { 0, };
event.remote_nodename = native->remote_nodename;
event.type = lrmd_event_disconnect;
native->callback(&event);
}
return;
}
int
lrmd_tls_send_msg(crm_remote_t * session, xmlNode * msg, uint32_t id, const char *msg_type)
{
int rc = -1;
crm_xml_add_int(msg, F_LRMD_REMOTE_MSG_ID, id);
crm_xml_add(msg, F_LRMD_REMOTE_MSG_TYPE, msg_type);
rc = crm_remote_send(session, msg);
if (rc < 0) {
crm_err("Failed to send remote lrmd tls msg, rc = %d", rc);
return rc;
}
return rc;
}
static xmlNode *
lrmd_tls_recv_reply(lrmd_t * lrmd, int total_timeout, int expected_reply_id, int *disconnected)
{
lrmd_private_t *native = lrmd->private;
xmlNode *xml = NULL;
time_t start = time(NULL);
const char *msg_type = NULL;
int reply_id = 0;
int remaining_timeout = 0;
/* A timeout of 0 here makes no sense. We have to wait a period of time
* for the response to come back. If -1 or 0, default to 10 seconds. */
if (total_timeout <= 0 || total_timeout > MAX_TLS_RECV_WAIT) {
total_timeout = MAX_TLS_RECV_WAIT;
}
while (!xml) {
xml = crm_remote_parse_buffer(native->remote);
if (!xml) {
/* read some more off the tls buffer if we still have time left. */
if (remaining_timeout) {
remaining_timeout = total_timeout - ((time(NULL) - start) * 1000);
} else {
remaining_timeout = total_timeout;
}
if (remaining_timeout <= 0) {
crm_err("Never received the expected reply during the timeout period, disconnecting.");
*disconnected = TRUE;
return NULL;
}
crm_remote_recv(native->remote, remaining_timeout, disconnected);
xml = crm_remote_parse_buffer(native->remote);
if (!xml) {
crm_err("Unable to receive expected reply, disconnecting.");
*disconnected = TRUE;
return NULL;
} else if (*disconnected) {
return NULL;
}
}
CRM_ASSERT(xml != NULL);
crm_element_value_int(xml, F_LRMD_REMOTE_MSG_ID, &reply_id);
msg_type = crm_element_value(xml, F_LRMD_REMOTE_MSG_TYPE);
if (!msg_type) {
crm_err("Empty msg type received while waiting for reply");
free_xml(xml);
xml = NULL;
} else if (safe_str_eq(msg_type, "notify")) {
/* got a notify while waiting for reply, trigger the notify to be processed later */
crm_info("queueing notify");
native->pending_notify = g_list_append(native->pending_notify, xml);
if (native->process_notify) {
crm_info("notify trigger set.");
mainloop_set_trigger(native->process_notify);
}
xml = NULL;
} else if (safe_str_neq(msg_type, "reply")) {
/* msg isn't a reply, make some noise */
crm_err("Expected a reply, got %s", msg_type);
free_xml(xml);
xml = NULL;
} else if (reply_id != expected_reply_id) {
if (native->expected_late_replies > 0) {
native->expected_late_replies--;
} else {
crm_err("Got outdated reply, expected id %d got id %d", expected_reply_id, reply_id);
}
free_xml(xml);
xml = NULL;
}
}
if (native->remote->buffer && native->process_notify) {
mainloop_set_trigger(native->process_notify);
}
return xml;
}
static int
lrmd_tls_send(lrmd_t * lrmd, xmlNode * msg)
{
int rc = 0;
lrmd_private_t *native = lrmd->private;
global_remote_msg_id++;
if (global_remote_msg_id <= 0) {
global_remote_msg_id = 1;
}
rc = lrmd_tls_send_msg(native->remote, msg, global_remote_msg_id, "request");
if (rc <= 0) {
crm_err("Remote lrmd send failed, disconnecting");
lrmd_tls_disconnect(lrmd);
return -ENOTCONN;
}
return pcmk_ok;
}
static int
lrmd_tls_send_recv(lrmd_t * lrmd, xmlNode * msg, int timeout, xmlNode ** reply)
{
int rc = 0;
int disconnected = 0;
xmlNode *xml = NULL;
if (lrmd_tls_connected(lrmd) == FALSE) {
return -1;
}
rc = lrmd_tls_send(lrmd, msg);
if (rc < 0) {
return rc;
}
xml = lrmd_tls_recv_reply(lrmd, timeout, global_remote_msg_id, &disconnected);
if (disconnected) {
crm_err("Remote lrmd server disconnected while waiting for reply with id %d. ",
global_remote_msg_id);
lrmd_tls_disconnect(lrmd);
rc = -ENOTCONN;
} else if (!xml) {
crm_err("Remote lrmd never received reply for request id %d. timeout: %dms ",
global_remote_msg_id, timeout);
rc = -ECOMM;
}
if (reply) {
*reply = xml;
} else {
free_xml(xml);
}
return rc;
}
#endif
static int
lrmd_send_xml(lrmd_t * lrmd, xmlNode * msg, int timeout, xmlNode ** reply)
{
int rc = -1;
lrmd_private_t *native = lrmd->private;
switch (native->type) {
case CRM_CLIENT_IPC:
rc = crm_ipc_send(native->ipc, msg, crm_ipc_client_response, timeout, reply);
break;
#ifdef HAVE_GNUTLS_GNUTLS_H
case CRM_CLIENT_TLS:
rc = lrmd_tls_send_recv(lrmd, msg, timeout, reply);
break;
#endif
default:
crm_err("Unsupported connection type: %d", native->type);
}
return rc;
}
static int
lrmd_send_xml_no_reply(lrmd_t * lrmd, xmlNode * msg)
{
int rc = -1;
lrmd_private_t *native = lrmd->private;
switch (native->type) {
case CRM_CLIENT_IPC:
rc = crm_ipc_send(native->ipc, msg, crm_ipc_flags_none, 0, NULL);
break;
#ifdef HAVE_GNUTLS_GNUTLS_H
case CRM_CLIENT_TLS:
rc = lrmd_tls_send(lrmd, msg);
if (rc == pcmk_ok) {
/* we don't want to wait around for the reply, but
* since the request/reply protocol needs to behave the same
* as libqb, a reply will eventually come later anyway. */
native->expected_late_replies++;
}
break;
#endif
default:
crm_err("Unsupported connection type: %d", native->type);
}
return rc;
}
static int
lrmd_api_is_connected(lrmd_t * lrmd)
{
lrmd_private_t *native = lrmd->private;
switch (native->type) {
case CRM_CLIENT_IPC:
return crm_ipc_connected(native->ipc);
break;
#ifdef HAVE_GNUTLS_GNUTLS_H
case CRM_CLIENT_TLS:
return lrmd_tls_connected(lrmd);
break;
#endif
default:
crm_err("Unsupported connection type: %d", native->type);
}
return 0;
}
/*!
* \internal
* \brief Send a prepared API command to the lrmd server
*
* \param[in] lrmd Existing connection to the lrmd server
* \param[in] op Name of API command to send
* \param[in] data Command data XML to add to the sent command
* \param[out] output_data If expecting a reply, it will be stored here
* \param[in] timeout Timeout in milliseconds (if 0, defaults to 1000);
* will be added to the command XML
* \param[in] call_options Call options to pass to server when sending
* \param[in] expect_reply If TRUE, wait for a reply from the server;
* must be TRUE for IPC (as opposed to TLS) clients
*
* \return pcmk_ok on success, -errno on error
*/
static int
lrmd_send_command(lrmd_t *lrmd, const char *op, xmlNode *data,
xmlNode **output_data, int timeout,
enum lrmd_call_options options, gboolean expect_reply)
{
int rc = pcmk_ok;
lrmd_private_t *native = lrmd->private;
xmlNode *op_msg = NULL;
xmlNode *op_reply = NULL;
if (!lrmd_api_is_connected(lrmd)) {
return -ENOTCONN;
}
if (op == NULL) {
crm_err("No operation specified");
return -EINVAL;
}
CRM_CHECK(native->token != NULL,;
);
crm_trace("sending %s op to lrmd", op);
op_msg = lrmd_create_op(native->token, op, data, timeout, options);
if (op_msg == NULL) {
return -EINVAL;
}
if (expect_reply) {
rc = lrmd_send_xml(lrmd, op_msg, timeout, &op_reply);
} else {
rc = lrmd_send_xml_no_reply(lrmd, op_msg);
goto done;
}
if (rc < 0) {
crm_perror(LOG_ERR, "Couldn't perform %s operation (timeout=%d): %d", op, timeout, rc);
rc = -ECOMM;
goto done;
} else if(op_reply == NULL) {
rc = -ENOMSG;
goto done;
}
rc = pcmk_ok;
crm_trace("%s op reply received", op);
if (crm_element_value_int(op_reply, F_LRMD_RC, &rc) != 0) {
rc = -ENOMSG;
goto done;
}
crm_log_xml_trace(op_reply, "Reply");
if (output_data) {
*output_data = op_reply;
op_reply = NULL; /* Prevent subsequent free */
}
done:
if (lrmd_api_is_connected(lrmd) == FALSE) {
crm_err("LRMD disconnected");
}
free_xml(op_msg);
free_xml(op_reply);
return rc;
}
static int
lrmd_api_poke_connection(lrmd_t * lrmd)
{
int rc;
lrmd_private_t *native = lrmd->private;
xmlNode *data = create_xml_node(NULL, F_LRMD_RSC);
crm_xml_add(data, F_LRMD_ORIGIN, __FUNCTION__);
rc = lrmd_send_command(lrmd, LRMD_OP_POKE, data, NULL, 0, 0, native->type == CRM_CLIENT_IPC ? TRUE : FALSE);
free_xml(data);
return rc < 0 ? rc : pcmk_ok;
}
int
remote_proxy_check(lrmd_t * lrmd, GHashTable *hash)
{
int rc;
const char *value;
lrmd_private_t *native = lrmd->private;
xmlNode *data = create_xml_node(NULL, F_LRMD_OPERATION);
crm_xml_add(data, F_LRMD_ORIGIN, __FUNCTION__);
value = g_hash_table_lookup(hash, "stonith-watchdog-timeout");
crm_xml_add(data, F_LRMD_WATCHDOG, value);
rc = lrmd_send_command(lrmd, LRMD_OP_CHECK, data, NULL, 0, 0, native->type == CRM_CLIENT_IPC ? TRUE : FALSE);
free_xml(data);
return rc < 0 ? rc : pcmk_ok;
}
static int
lrmd_handshake(lrmd_t * lrmd, const char *name)
{
int rc = pcmk_ok;
lrmd_private_t *native = lrmd->private;
xmlNode *reply = NULL;
xmlNode *hello = create_xml_node(NULL, "lrmd_command");
crm_xml_add(hello, F_TYPE, T_LRMD);
crm_xml_add(hello, F_LRMD_OPERATION, CRM_OP_REGISTER);
crm_xml_add(hello, F_LRMD_CLIENTNAME, name);
crm_xml_add(hello, F_LRMD_PROTOCOL_VERSION, LRMD_PROTOCOL_VERSION);
/* advertise that we are a proxy provider */
if (native->proxy_callback) {
crm_xml_add(hello, F_LRMD_IS_IPC_PROVIDER, "true");
}
rc = lrmd_send_xml(lrmd, hello, -1, &reply);
if (rc < 0) {
crm_perror(LOG_DEBUG, "Couldn't complete registration with the lrmd API: %d", rc);
rc = -ECOMM;
} else if (reply == NULL) {
crm_err("Did not receive registration reply");
rc = -EPROTO;
} else {
const char *version = crm_element_value(reply, F_LRMD_PROTOCOL_VERSION);
const char *msg_type = crm_element_value(reply, F_LRMD_OPERATION);
const char *tmp_ticket = crm_element_value(reply, F_LRMD_CLIENTID);
crm_element_value_int(reply, F_LRMD_RC, &rc);
if (rc == -EPROTO) {
crm_err("LRMD protocol mismatch client version %s, server version %s",
LRMD_PROTOCOL_VERSION, version);
crm_log_xml_err(reply, "Protocol Error");
} else if (safe_str_neq(msg_type, CRM_OP_REGISTER)) {
crm_err("Invalid registration message: %s", msg_type);
crm_log_xml_err(reply, "Bad reply");
rc = -EPROTO;
} else if (tmp_ticket == NULL) {
crm_err("No registration token provided");
crm_log_xml_err(reply, "Bad reply");
rc = -EPROTO;
} else {
crm_trace("Obtained registration token: %s", tmp_ticket);
native->token = strdup(tmp_ticket);
native->peer_version = strdup(version?version:"1.0"); /* Included since 1.1 */
rc = pcmk_ok;
}
}
free_xml(reply);
free_xml(hello);
if (rc != pcmk_ok) {
lrmd_api_disconnect(lrmd);
}
return rc;
}
static int
lrmd_ipc_connect(lrmd_t * lrmd, int *fd)
{
int rc = pcmk_ok;
lrmd_private_t *native = lrmd->private;
static struct ipc_client_callbacks lrmd_callbacks = {
.dispatch = lrmd_ipc_dispatch,
.destroy = lrmd_ipc_connection_destroy
};
crm_info("Connecting to lrmd");
if (fd) {
/* No mainloop */
native->ipc = crm_ipc_new(CRM_SYSTEM_LRMD, 0);
if (native->ipc && crm_ipc_connect(native->ipc)) {
*fd = crm_ipc_get_fd(native->ipc);
} else if (native->ipc) {
crm_perror(LOG_ERR, "Connection to local resource manager failed");
rc = -ENOTCONN;
}
} else {
native->source = mainloop_add_ipc_client(CRM_SYSTEM_LRMD, G_PRIORITY_HIGH, 0, lrmd, &lrmd_callbacks);
native->ipc = mainloop_get_ipc_client(native->source);
}
if (native->ipc == NULL) {
crm_debug("Could not connect to the LRMD API");
rc = -ENOTCONN;
}
return rc;
}
#ifdef HAVE_GNUTLS_GNUTLS_H
static int
set_key(gnutls_datum_t * key, const char *location)
{
FILE *stream;
int read_len = 256;
int cur_len = 0;
int buf_len = read_len;
static char *key_cache = NULL;
static size_t key_cache_len = 0;
static time_t key_cache_updated;
if (location == NULL) {
return -1;
}
if (key_cache) {
time_t now = time(NULL);
if ((now - key_cache_updated) < 60) {
key->data = gnutls_malloc(key_cache_len + 1);
key->size = key_cache_len;
memcpy(key->data, key_cache, key_cache_len);
crm_debug("using cached LRMD key");
return 0;
} else {
key_cache_len = 0;
key_cache_updated = 0;
free(key_cache);
key_cache = NULL;
crm_debug("clearing lrmd key cache");
}
}
stream = fopen(location, "r");
if (!stream) {
return -1;
}
key->data = gnutls_malloc(read_len);
while (!feof(stream)) {
int next;
if (cur_len == buf_len) {
buf_len = cur_len + read_len;
key->data = gnutls_realloc(key->data, buf_len);
}
next = fgetc(stream);
if (next == EOF && feof(stream)) {
break;
}
key->data[cur_len] = next;
cur_len++;
}
fclose(stream);
key->size = cur_len;
if (!cur_len) {
gnutls_free(key->data);
key->data = 0;
return -1;
}
if (!key_cache) {
key_cache = calloc(1, key->size + 1);
memcpy(key_cache, key->data, key->size);
key_cache_len = key->size;
key_cache_updated = time(NULL);
}
return 0;
}
int
lrmd_tls_set_key(gnutls_datum_t * key)
{
int rc = 0;
const char *specific_location = getenv("PCMK_authkey_location");
if (set_key(key, specific_location) == 0) {
crm_debug("Using custom authkey location %s", specific_location);
return 0;
} else if (specific_location) {
crm_err("No valid lrmd remote key found at %s, trying default location", specific_location);
}
if (set_key(key, DEFAULT_REMOTE_KEY_LOCATION) != 0) {
rc = set_key(key, ALT_REMOTE_KEY_LOCATION);
}
if (rc) {
crm_err("No valid lrmd remote key found at %s", DEFAULT_REMOTE_KEY_LOCATION);
return -1;
}
return rc;
}
static void
lrmd_gnutls_global_init(void)
{
static int gnutls_init = 0;
if (!gnutls_init) {
crm_gnutls_global_init();
}
gnutls_init = 1;
}
#endif
static void
report_async_connection_result(lrmd_t * lrmd, int rc)
{
lrmd_private_t *native = lrmd->private;
if (native->callback) {
lrmd_event_data_t event = { 0, };
event.type = lrmd_event_connect;
event.remote_nodename = native->remote_nodename;
event.connection_rc = rc;
native->callback(&event);
}
}
#ifdef HAVE_GNUTLS_GNUTLS_H
static void
lrmd_tcp_connect_cb(void *userdata, int sock)
{
lrmd_t *lrmd = userdata;
lrmd_private_t *native = lrmd->private;
char name[256] = { 0, };
static struct mainloop_fd_callbacks lrmd_tls_callbacks = {
.dispatch = lrmd_tls_dispatch,
.destroy = lrmd_tls_connection_destroy,
};
int rc = sock;
gnutls_datum_t psk_key = { NULL, 0 };
native->async_timer = 0;
if (rc < 0) {
lrmd_tls_connection_destroy(lrmd);
crm_info("remote lrmd connect to %s at port %d failed", native->server, native->port);
report_async_connection_result(lrmd, rc);
return;
}
/* TODO continue with tls stuff now that tcp connect passed. make this async as well soon
* to avoid all blocking code in the client. */
native->sock = sock;
if (lrmd_tls_set_key(&psk_key) != 0) {
lrmd_tls_connection_destroy(lrmd);
return;
}
gnutls_psk_allocate_client_credentials(&native->psk_cred_c);
gnutls_psk_set_client_credentials(native->psk_cred_c, DEFAULT_REMOTE_USERNAME, &psk_key, GNUTLS_PSK_KEY_RAW);
gnutls_free(psk_key.data);
native->remote->tls_session = create_psk_tls_session(sock, GNUTLS_CLIENT, native->psk_cred_c);
if (crm_initiate_client_tls_handshake(native->remote, LRMD_CLIENT_HANDSHAKE_TIMEOUT) != 0) {
crm_warn("Client tls handshake failed for server %s:%d. Disconnecting", native->server,
native->port);
gnutls_deinit(*native->remote->tls_session);
gnutls_free(native->remote->tls_session);
native->remote->tls_session = NULL;
lrmd_tls_connection_destroy(lrmd);
report_async_connection_result(lrmd, -1);
return;
}
crm_info("Remote lrmd client TLS connection established with server %s:%d", native->server,
native->port);
snprintf(name, 128, "remote-lrmd-%s:%d", native->server, native->port);
native->process_notify = mainloop_add_trigger(G_PRIORITY_HIGH, lrmd_tls_dispatch, lrmd);
native->source =
mainloop_add_fd(name, G_PRIORITY_HIGH, native->sock, lrmd, &lrmd_tls_callbacks);
rc = lrmd_handshake(lrmd, name);
report_async_connection_result(lrmd, rc);
return;
}
static int
lrmd_tls_connect_async(lrmd_t * lrmd, int timeout /*ms */ )
{
int rc = -1;
int sock = 0;
int timer_id = 0;
lrmd_private_t *native = lrmd->private;
lrmd_gnutls_global_init();
sock = crm_remote_tcp_connect_async(native->server, native->port, timeout, &timer_id, lrmd,
lrmd_tcp_connect_cb);
if (sock != -1) {
native->sock = sock;
rc = 0;
native->async_timer = timer_id;
}
return rc;
}
static int
lrmd_tls_connect(lrmd_t * lrmd, int *fd)
{
static struct mainloop_fd_callbacks lrmd_tls_callbacks = {
.dispatch = lrmd_tls_dispatch,
.destroy = lrmd_tls_connection_destroy,
};
lrmd_private_t *native = lrmd->private;
int sock;
gnutls_datum_t psk_key = { NULL, 0 };
lrmd_gnutls_global_init();
sock = crm_remote_tcp_connect(native->server, native->port);
if (sock < 0) {
crm_warn("Could not establish remote lrmd connection to %s", native->server);
lrmd_tls_connection_destroy(lrmd);
return -ENOTCONN;
}
native->sock = sock;
if (lrmd_tls_set_key(&psk_key) != 0) {
lrmd_tls_connection_destroy(lrmd);
return -1;
}
gnutls_psk_allocate_client_credentials(&native->psk_cred_c);
gnutls_psk_set_client_credentials(native->psk_cred_c, DEFAULT_REMOTE_USERNAME, &psk_key, GNUTLS_PSK_KEY_RAW);
gnutls_free(psk_key.data);
native->remote->tls_session = create_psk_tls_session(sock, GNUTLS_CLIENT, native->psk_cred_c);
if (crm_initiate_client_tls_handshake(native->remote, LRMD_CLIENT_HANDSHAKE_TIMEOUT) != 0) {
crm_err("Session creation for %s:%d failed", native->server, native->port);
gnutls_deinit(*native->remote->tls_session);
gnutls_free(native->remote->tls_session);
native->remote->tls_session = NULL;
lrmd_tls_connection_destroy(lrmd);
return -1;
}
crm_info("Remote lrmd client TLS connection established with server %s:%d", native->server,
native->port);
if (fd) {
*fd = sock;
} else {
char name[256] = { 0, };
snprintf(name, 128, "remote-lrmd-%s:%d", native->server, native->port);
native->process_notify = mainloop_add_trigger(G_PRIORITY_HIGH, lrmd_tls_dispatch, lrmd);
native->source =
mainloop_add_fd(name, G_PRIORITY_HIGH, native->sock, lrmd, &lrmd_tls_callbacks);
}
return pcmk_ok;
}
#endif
static int
lrmd_api_connect(lrmd_t * lrmd, const char *name, int *fd)
{
int rc = -ENOTCONN;
lrmd_private_t *native = lrmd->private;
switch (native->type) {
case CRM_CLIENT_IPC:
rc = lrmd_ipc_connect(lrmd, fd);
break;
#ifdef HAVE_GNUTLS_GNUTLS_H
case CRM_CLIENT_TLS:
rc = lrmd_tls_connect(lrmd, fd);
break;
#endif
default:
crm_err("Unsupported connection type: %d", native->type);
}
if (rc == pcmk_ok) {
rc = lrmd_handshake(lrmd, name);
}
return rc;
}
static int
lrmd_api_connect_async(lrmd_t * lrmd, const char *name, int timeout)
{
int rc = 0;
lrmd_private_t *native = lrmd->private;
if (!native->callback) {
crm_err("Async connect not possible, no lrmd client callback set.");
return -1;
}
switch (native->type) {
case CRM_CLIENT_IPC:
/* fake async connection with ipc. it should be fast
* enough that we gain very little from async */
rc = lrmd_api_connect(lrmd, name, NULL);
if (!rc) {
report_async_connection_result(lrmd, rc);
}
break;
#ifdef HAVE_GNUTLS_GNUTLS_H
case CRM_CLIENT_TLS:
rc = lrmd_tls_connect_async(lrmd, timeout);
if (rc) {
/* connection failed, report rc now */
report_async_connection_result(lrmd, rc);
}
break;
#endif
default:
crm_err("Unsupported connection type: %d", native->type);
}
return rc;
}
static void
lrmd_ipc_disconnect(lrmd_t * lrmd)
{
lrmd_private_t *native = lrmd->private;
if (native->source != NULL) {
/* Attached to mainloop */
mainloop_del_ipc_client(native->source);
native->source = NULL;
native->ipc = NULL;
} else if (native->ipc) {
/* Not attached to mainloop */
crm_ipc_t *ipc = native->ipc;
native->ipc = NULL;
crm_ipc_close(ipc);
crm_ipc_destroy(ipc);
}
}
#ifdef HAVE_GNUTLS_GNUTLS_H
static void
lrmd_tls_disconnect(lrmd_t * lrmd)
{
lrmd_private_t *native = lrmd->private;
if (native->remote->tls_session) {
gnutls_bye(*native->remote->tls_session, GNUTLS_SHUT_RDWR);
gnutls_deinit(*native->remote->tls_session);
gnutls_free(native->remote->tls_session);
native->remote->tls_session = 0;
}
if (native->async_timer) {
g_source_remove(native->async_timer);
native->async_timer = 0;
}
if (native->source != NULL) {
/* Attached to mainloop */
mainloop_del_ipc_client(native->source);
native->source = NULL;
} else if (native->sock) {
close(native->sock);
native->sock = 0;
}
if (native->pending_notify) {
g_list_free_full(native->pending_notify, lrmd_free_xml);
native->pending_notify = NULL;
}
}
#endif
static int
lrmd_api_disconnect(lrmd_t * lrmd)
{
lrmd_private_t *native = lrmd->private;
crm_info("Disconnecting from %d lrmd service", native->type);
switch (native->type) {
case CRM_CLIENT_IPC:
lrmd_ipc_disconnect(lrmd);
break;
#ifdef HAVE_GNUTLS_GNUTLS_H
case CRM_CLIENT_TLS:
lrmd_tls_disconnect(lrmd);
break;
#endif
default:
crm_err("Unsupported connection type: %d", native->type);
}
free(native->token);
native->token = NULL;
free(native->peer_version);
native->peer_version = NULL;
return 0;
}
static int
lrmd_api_register_rsc(lrmd_t * lrmd,
const char *rsc_id,
const char *class,
const char *provider, const char *type, enum lrmd_call_options options)
{
int rc = pcmk_ok;
xmlNode *data = NULL;
if (!class || !type || !rsc_id) {
return -EINVAL;
}
if (safe_str_eq(class, PCMK_RESOURCE_CLASS_OCF) && !provider) {
return -EINVAL;
}
data = create_xml_node(NULL, F_LRMD_RSC);
crm_xml_add(data, F_LRMD_ORIGIN, __FUNCTION__);
crm_xml_add(data, F_LRMD_RSC_ID, rsc_id);
crm_xml_add(data, F_LRMD_CLASS, class);
crm_xml_add(data, F_LRMD_PROVIDER, provider);
crm_xml_add(data, F_LRMD_TYPE, type);
rc = lrmd_send_command(lrmd, LRMD_OP_RSC_REG, data, NULL, 0, options, TRUE);
free_xml(data);
return rc;
}
static int
lrmd_api_unregister_rsc(lrmd_t * lrmd, const char *rsc_id, enum lrmd_call_options options)
{
int rc = pcmk_ok;
xmlNode *data = create_xml_node(NULL, F_LRMD_RSC);
crm_xml_add(data, F_LRMD_ORIGIN, __FUNCTION__);
crm_xml_add(data, F_LRMD_RSC_ID, rsc_id);
rc = lrmd_send_command(lrmd, LRMD_OP_RSC_UNREG, data, NULL, 0, options, TRUE);
free_xml(data);
return rc;
}
lrmd_rsc_info_t *
lrmd_copy_rsc_info(lrmd_rsc_info_t * rsc_info)
{
lrmd_rsc_info_t *copy = NULL;
copy = calloc(1, sizeof(lrmd_rsc_info_t));
copy->id = strdup(rsc_info->id);
copy->type = strdup(rsc_info->type);
copy->class = strdup(rsc_info->class);
if (rsc_info->provider) {
copy->provider = strdup(rsc_info->provider);
}
return copy;
}
void
lrmd_free_rsc_info(lrmd_rsc_info_t * rsc_info)
{
if (!rsc_info) {
return;
}
free(rsc_info->id);
free(rsc_info->type);
free(rsc_info->class);
free(rsc_info->provider);
free(rsc_info);
}
static lrmd_rsc_info_t *
lrmd_api_get_rsc_info(lrmd_t * lrmd, const char *rsc_id, enum lrmd_call_options options)
{
lrmd_rsc_info_t *rsc_info = NULL;
xmlNode *data = create_xml_node(NULL, F_LRMD_RSC);
xmlNode *output = NULL;
const char *class = NULL;
const char *provider = NULL;
const char *type = NULL;
crm_xml_add(data, F_LRMD_ORIGIN, __FUNCTION__);
crm_xml_add(data, F_LRMD_RSC_ID, rsc_id);
lrmd_send_command(lrmd, LRMD_OP_RSC_INFO, data, &output, 0, options, TRUE);
free_xml(data);
if (!output) {
return NULL;
}
class = crm_element_value(output, F_LRMD_CLASS);
provider = crm_element_value(output, F_LRMD_PROVIDER);
type = crm_element_value(output, F_LRMD_TYPE);
if (!class || !type) {
free_xml(output);
return NULL;
} else if (safe_str_eq(class, PCMK_RESOURCE_CLASS_OCF) && !provider) {
free_xml(output);
return NULL;
}
rsc_info = calloc(1, sizeof(lrmd_rsc_info_t));
rsc_info->id = strdup(rsc_id);
rsc_info->class = strdup(class);
if (provider) {
rsc_info->provider = strdup(provider);
}
rsc_info->type = strdup(type);
free_xml(output);
return rsc_info;
}
static void
lrmd_api_set_callback(lrmd_t * lrmd, lrmd_event_callback callback)
{
lrmd_private_t *native = lrmd->private;
native->callback = callback;
}
void
lrmd_internal_set_proxy_callback(lrmd_t * lrmd, void *userdata, void (*callback)(lrmd_t *lrmd, void *userdata, xmlNode *msg))
{
lrmd_private_t *native = lrmd->private;
native->proxy_callback = callback;
native->proxy_callback_userdata = userdata;
}
void
lrmd_internal_proxy_dispatch(lrmd_t *lrmd, xmlNode *msg)
{
lrmd_private_t *native = lrmd->private;
if (native->proxy_callback) {
crm_log_xml_trace(msg, "PROXY_INBOUND");
native->proxy_callback(lrmd, native->proxy_callback_userdata, msg);
}
}
int
lrmd_internal_proxy_send(lrmd_t * lrmd, xmlNode *msg)
{
if (lrmd == NULL) {
return -ENOTCONN;
}
crm_xml_add(msg, F_LRMD_OPERATION, CRM_OP_IPC_FWD);
crm_log_xml_trace(msg, "PROXY_OUTBOUND");
return lrmd_send_xml_no_reply(lrmd, msg);
}
static int
stonith_get_metadata(const char *provider, const char *type, char **output)
{
int rc = pcmk_ok;
stonith_t *stonith_api = stonith_api_new();
if(stonith_api) {
stonith_api->cmds->metadata(stonith_api, st_opt_sync_call, type, provider, output, 0);
stonith_api->cmds->free(stonith_api);
}
if (*output == NULL) {
rc = -EIO;
}
return rc;
}
-#define lsb_metadata_template \
- "<?xml version='1.0'?>\n" \
- "<!DOCTYPE resource-agent SYSTEM 'ra-api-1.dtd'>\n" \
- "<resource-agent name='%s' version='0.1'>\n" \
- " <version>1.0</version>\n" \
- " <longdesc lang='en'>\n" \
- " %s\n" \
- " </longdesc>\n" \
- " <shortdesc lang='en'>%s</shortdesc>\n" \
- " <parameters>\n" \
- " </parameters>\n" \
- " <actions>\n" \
- " <action name='meta-data' timeout='5' />\n" \
- " <action name='start' timeout='15' />\n" \
- " <action name='stop' timeout='15' />\n" \
- " <action name='status' timeout='15' />\n" \
- " <action name='restart' timeout='15' />\n" \
- " <action name='force-reload' timeout='15' />\n" \
- " <action name='monitor' timeout='15' interval='15' />\n" \
- " </actions>\n" \
- " <special tag='LSB'>\n" \
- " <Provides>%s</Provides>\n" \
- " <Required-Start>%s</Required-Start>\n" \
- " <Required-Stop>%s</Required-Stop>\n" \
- " <Should-Start>%s</Should-Start>\n" \
- " <Should-Stop>%s</Should-Stop>\n" \
- " <Default-Start>%s</Default-Start>\n" \
- " <Default-Stop>%s</Default-Stop>\n" \
- " </special>\n" \
- "</resource-agent>\n"
-
-#define LSB_INITSCRIPT_INFOBEGIN_TAG "### BEGIN INIT INFO"
-#define LSB_INITSCRIPT_INFOEND_TAG "### END INIT INFO"
-#define PROVIDES "# Provides:"
-#define REQ_START "# Required-Start:"
-#define REQ_STOP "# Required-Stop:"
-#define SHLD_START "# Should-Start:"
-#define SHLD_STOP "# Should-Stop:"
-#define DFLT_START "# Default-Start:"
-#define DFLT_STOP "# Default-Stop:"
-#define SHORT_DSCR "# Short-Description:"
-#define DESCRIPTION "# Description:"
-
-#define lsb_meta_helper_free_value(m) \
- do { \
- if ((m) != NULL) { \
- xmlFree(m); \
- (m) = NULL; \
- } \
- } while(0)
-
-/*!
- * \internal
- * \brief Grab an LSB header value
- *
- * \param[in] line Line read from LSB init script
- * \param[in,out] value If not set, will be set to XML-safe copy of value
- * \param[in] prefix Set value if line starts with this pattern
- *
- * \return TRUE if value was set, FALSE otherwise
- */
-static inline gboolean
-lsb_meta_helper_get_value(const char *line, char **value, const char *prefix)
-{
- if (!*value && !strncasecmp(line, prefix, strlen(prefix))) {
- *value = (char *)xmlEncodeEntitiesReentrant(NULL, BAD_CAST line+strlen(prefix));
- return TRUE;
- }
- return FALSE;
-}
-
static int
-lsb_get_metadata(const char *type, char **output)
+lrmd_api_get_metadata(lrmd_t * lrmd,
+ const char *class,
+ const char *provider,
+ const char *type, char **output, enum lrmd_call_options options)
{
- char ra_pathname[PATH_MAX] = { 0, };
- FILE *fp;
- char buffer[1024];
- char *provides = NULL;
- char *req_start = NULL;
- char *req_stop = NULL;
- char *shld_start = NULL;
- char *shld_stop = NULL;
- char *dflt_start = NULL;
- char *dflt_stop = NULL;
- char *s_dscrpt = NULL;
- char *xml_l_dscrpt = NULL;
- int offset = 0;
- int max = 2048;
- char description[max];
-
- if(type[0] == '/') {
- snprintf(ra_pathname, sizeof(ra_pathname), "%s", type);
- } else {
- snprintf(ra_pathname, sizeof(ra_pathname), "%s/%s", LSB_ROOT_DIR, type);
- }
-
- crm_trace("Looking into %s", ra_pathname);
- if (!(fp = fopen(ra_pathname, "r"))) {
- return -errno;
- }
-
- /* Enter into the lsb-compliant comment block */
- while (fgets(buffer, sizeof(buffer), fp)) {
-
- /* Now suppose each of the following eight arguments contain only one line */
- if (lsb_meta_helper_get_value(buffer, &provides, PROVIDES)) {
- continue;
- }
- if (lsb_meta_helper_get_value(buffer, &req_start, REQ_START)) {
- continue;
- }
- if (lsb_meta_helper_get_value(buffer, &req_stop, REQ_STOP)) {
- continue;
- }
- if (lsb_meta_helper_get_value(buffer, &shld_start, SHLD_START)) {
- continue;
- }
- if (lsb_meta_helper_get_value(buffer, &shld_stop, SHLD_STOP)) {
- continue;
- }
- if (lsb_meta_helper_get_value(buffer, &dflt_start, DFLT_START)) {
- continue;
- }
- if (lsb_meta_helper_get_value(buffer, &dflt_stop, DFLT_STOP)) {
- continue;
- }
- if (lsb_meta_helper_get_value(buffer, &s_dscrpt, SHORT_DSCR)) {
- continue;
- }
-
- /* Long description may cross multiple lines */
- if (offset == 0 && (0 == strncasecmp(buffer, DESCRIPTION, strlen(DESCRIPTION)))) {
- /* Between # and keyword, more than one space, or a tab
- * character, indicates the continuation line.
- *
- * Extracted from LSB init script standard
- */
- while (fgets(buffer, sizeof(buffer), fp)) {
- if (!strncmp(buffer, "# ", 3) || !strncmp(buffer, "#\t", 2)) {
- buffer[0] = ' ';
- offset += snprintf(description+offset, max-offset, "%s", buffer);
-
- } else {
- fputs(buffer, fp);
- break; /* Long description ends */
- }
- }
- continue;
- }
-
- if (xml_l_dscrpt == NULL && offset > 0) {
- xml_l_dscrpt = (char *)xmlEncodeEntitiesReentrant(NULL, BAD_CAST(description));
- }
-
- if (!strncasecmp(buffer, LSB_INITSCRIPT_INFOEND_TAG, strlen(LSB_INITSCRIPT_INFOEND_TAG))) {
- /* Get to the out border of LSB comment block */
- break;
- }
- if (buffer[0] != '#') {
- break; /* Out of comment block in the beginning */
- }
- }
- fclose(fp);
-
- *output = crm_strdup_printf(lsb_metadata_template, type,
- (xml_l_dscrpt == NULL) ? type : xml_l_dscrpt,
- (s_dscrpt == NULL) ? type : s_dscrpt, (provides == NULL) ? "" : provides,
- (req_start == NULL) ? "" : req_start, (req_stop == NULL) ? "" : req_stop,
- (shld_start == NULL) ? "" : shld_start, (shld_stop == NULL) ? "" : shld_stop,
- (dflt_start == NULL) ? "" : dflt_start, (dflt_stop == NULL) ? "" : dflt_stop);
-
- lsb_meta_helper_free_value(xml_l_dscrpt);
- lsb_meta_helper_free_value(s_dscrpt);
- lsb_meta_helper_free_value(provides);
- lsb_meta_helper_free_value(req_start);
- lsb_meta_helper_free_value(req_stop);
- lsb_meta_helper_free_value(shld_start);
- lsb_meta_helper_free_value(shld_stop);
- lsb_meta_helper_free_value(dflt_start);
- lsb_meta_helper_free_value(dflt_stop);
-
- crm_trace("Created fake metadata: %llu",
- (unsigned long long) strlen(*output));
- return pcmk_ok;
-}
+ svc_action_t *action;
-#if SUPPORT_NAGIOS
-static int
-nagios_get_metadata(const char *type, char **output)
-{
- int rc = pcmk_ok;
- FILE *file_strm = NULL;
- int start = 0, length = 0, read_len = 0;
- char *metadata_file = NULL;
- int len = 36;
-
- len += strlen(NAGIOS_METADATA_DIR);
- len += strlen(type);
- metadata_file = calloc(1, len);
- CRM_CHECK(metadata_file != NULL, return -ENOMEM);
-
- sprintf(metadata_file, "%s/%s.xml", NAGIOS_METADATA_DIR, type);
- file_strm = fopen(metadata_file, "r");
- if (file_strm == NULL) {
- crm_err("Metadata file %s does not exist", metadata_file);
- free(metadata_file);
- return -EIO;
+ if (!class || !type) {
+ return -EINVAL;
}
- /* see how big the file is */
- start = ftell(file_strm);
- fseek(file_strm, 0L, SEEK_END);
- length = ftell(file_strm);
- fseek(file_strm, 0L, start);
-
- CRM_ASSERT(length >= 0);
- CRM_ASSERT(start == ftell(file_strm));
-
- if (length <= 0) {
- crm_info("%s was not valid", metadata_file);
- free(*output);
- *output = NULL;
- rc = -EIO;
-
- } else {
- crm_trace("Reading %d bytes from file", length);
- *output = calloc(1, (length + 1));
- read_len = fread(*output, 1, length, file_strm);
- if (read_len != length) {
- crm_err("Calculated and read bytes differ: %d vs. %d", length, read_len);
- free(*output);
- *output = NULL;
- rc = -EIO;
- }
+ if (safe_str_eq(class, PCMK_RESOURCE_CLASS_STONITH)) {
+ return stonith_get_metadata(provider, type, output);
}
- fclose(file_strm);
- free(metadata_file);
- return rc;
-}
-#endif
-
-#if SUPPORT_HEARTBEAT
-/* strictly speaking, support for class=heartbeat style scripts
- * does not require "heartbeat support" to be enabled.
- * But since those scripts are part of the "heartbeat" package usually,
- * and are very unlikely to be present in any other deployment,
- * I leave it inside this ifdef.
- *
- * Yes, I know, these are legacy and should die,
- * or at least be rewritten to be a proper OCF style agent.
- * But they exist, and custom scripts following these rules do, too.
- *
- * Taken from the old "glue" lrmd, see
- * http://hg.linux-ha.org/glue/file/0a7add1d9996/lib/plugins/lrm/raexechb.c#l49
- * http://hg.linux-ha.org/glue/file/0a7add1d9996/lib/plugins/lrm/raexechb.c#l393
- */
-
-static const char hb_metadata_template[] =
-"<?xml version='1.0'?>\n"
-"<!DOCTYPE resource-agent SYSTEM 'ra-api-1.dtd'>\n"
-"<resource-agent name='%s' version='0.1'>\n"
-"<version>1.0</version>\n"
-"<longdesc lang='en'>\n"
-"%s"
-"</longdesc>\n"
-"<shortdesc lang='en'>%s</shortdesc>\n"
-"<parameters>\n"
-"<parameter name='1' unique='1' required='0'>\n"
-"<longdesc lang='en'>\n"
-"This argument will be passed as the first argument to the "
-"heartbeat resource agent (assuming it supports one)\n"
-"</longdesc>\n"
-"<shortdesc lang='en'>argv[1]</shortdesc>\n"
-"<content type='string' default=' ' />\n"
-"</parameter>\n"
-"<parameter name='2' unique='1' required='0'>\n"
-"<longdesc lang='en'>\n"
-"This argument will be passed as the second argument to the "
-"heartbeat resource agent (assuming it supports one)\n"
-"</longdesc>\n"
-"<shortdesc lang='en'>argv[2]</shortdesc>\n"
-"<content type='string' default=' ' />\n"
-"</parameter>\n"
-"<parameter name='3' unique='1' required='0'>\n"
-"<longdesc lang='en'>\n"
-"This argument will be passed as the third argument to the "
-"heartbeat resource agent (assuming it supports one)\n"
-"</longdesc>\n"
-"<shortdesc lang='en'>argv[3]</shortdesc>\n"
-"<content type='string' default=' ' />\n"
-"</parameter>\n"
-"<parameter name='4' unique='1' required='0'>\n"
-"<longdesc lang='en'>\n"
-"This argument will be passed as the fourth argument to the "
-"heartbeat resource agent (assuming it supports one)\n"
-"</longdesc>\n"
-"<shortdesc lang='en'>argv[4]</shortdesc>\n"
-"<content type='string' default=' ' />\n"
-"</parameter>\n"
-"<parameter name='5' unique='1' required='0'>\n"
-"<longdesc lang='en'>\n"
-"This argument will be passed as the fifth argument to the "
-"heartbeat resource agent (assuming it supports one)\n"
-"</longdesc>\n"
-"<shortdesc lang='en'>argv[5]</shortdesc>\n"
-"<content type='string' default=' ' />\n"
-"</parameter>\n"
-"</parameters>\n"
-"<actions>\n"
-"<action name='start' timeout='15' />\n"
-"<action name='stop' timeout='15' />\n"
-"<action name='status' timeout='15' />\n"
-"<action name='monitor' timeout='15' interval='15' start-delay='15' />\n"
-"<action name='meta-data' timeout='5' />\n"
-"</actions>\n"
-"<special tag='heartbeat'>\n"
-"</special>\n"
-"</resource-agent>\n";
-
-static int
-heartbeat_get_metadata(const char *type, char **output)
-{
- *output = crm_strdup_printf(hb_metadata_template, type, type, type);
- crm_trace("Created fake metadata: %llu",
- (unsigned long long) strlen(*output));
- return pcmk_ok;
-}
-#endif
-
-static int
-generic_get_metadata(const char *standard, const char *provider, const char *type, char **output)
-{
- svc_action_t *action;
-
- action = resources_action_create(type, standard, provider, type,
- "meta-data", 0, 30000, NULL, 0);
+ action = resources_action_create(type, class, provider, type,
+ "meta-data", 0,
+ CRMD_METADATA_CALL_TIMEOUT, NULL, 0);
if (action == NULL) {
- crm_err("Unable to retrieve meta-data for %s:%s:%s", standard, provider, type);
+ crm_err("Unable to retrieve meta-data for %s:%s:%s", class, provider, type);
services_action_free(action);
return -EINVAL;
}
if (!(services_action_sync(action))) {
- crm_err("Failed to retrieve meta-data for %s:%s:%s", standard, provider, type);
+ crm_err("Failed to retrieve meta-data for %s:%s:%s", class, provider, type);
services_action_free(action);
return -EIO;
}
if (!action->stdout_data) {
- crm_err("Failed to receive meta-data for %s:%s:%s", standard, provider, type);
+ crm_err("Failed to receive meta-data for %s:%s:%s", class, provider, type);
services_action_free(action);
return -EIO;
}
*output = strdup(action->stdout_data);
services_action_free(action);
return pcmk_ok;
}
-static int
-lrmd_api_get_metadata(lrmd_t * lrmd,
- const char *class,
- const char *provider,
- const char *type, char **output, enum lrmd_call_options options)
-{
- if (!class || !type) {
- return -EINVAL;
- }
-
- if (safe_str_eq(class, PCMK_RESOURCE_CLASS_SERVICE)) {
- class = resources_find_service_class(type);
- }
-
- if (safe_str_eq(class, PCMK_RESOURCE_CLASS_STONITH)) {
- return stonith_get_metadata(provider, type, output);
- } else if (safe_str_eq(class, PCMK_RESOURCE_CLASS_LSB)) {
- return lsb_get_metadata(type, output);
-#if SUPPORT_NAGIOS
- } else if (safe_str_eq(class, PCMK_RESOURCE_CLASS_NAGIOS)) {
- return nagios_get_metadata(type, output);
-#endif
-#if SUPPORT_HEARTBEAT
- } else if (safe_str_eq(class, PCMK_RESOURCE_CLASS_HB)) {
- return heartbeat_get_metadata(type, output);
-#endif
- }
- return generic_get_metadata(class, provider, type, output);
-}
-
static int
lrmd_api_exec(lrmd_t * lrmd, const char *rsc_id, const char *action, const char *userdata, int interval, /* ms */
int timeout, /* ms */
int start_delay, /* ms */
enum lrmd_call_options options, lrmd_key_value_t * params)
{
int rc = pcmk_ok;
xmlNode *data = create_xml_node(NULL, F_LRMD_RSC);
xmlNode *args = create_xml_node(data, XML_TAG_ATTRS);
lrmd_key_value_t *tmp = NULL;
crm_xml_add(data, F_LRMD_ORIGIN, __FUNCTION__);
crm_xml_add(data, F_LRMD_RSC_ID, rsc_id);
crm_xml_add(data, F_LRMD_RSC_ACTION, action);
crm_xml_add(data, F_LRMD_RSC_USERDATA_STR, userdata);
crm_xml_add_int(data, F_LRMD_RSC_INTERVAL, interval);
crm_xml_add_int(data, F_LRMD_TIMEOUT, timeout);
crm_xml_add_int(data, F_LRMD_RSC_START_DELAY, start_delay);
for (tmp = params; tmp; tmp = tmp->next) {
hash2smartfield((gpointer) tmp->key, (gpointer) tmp->value, args);
}
rc = lrmd_send_command(lrmd, LRMD_OP_RSC_EXEC, data, NULL, timeout, options, TRUE);
free_xml(data);
lrmd_key_value_freeall(params);
return rc;
}
/* timeout is in ms */
static int
lrmd_api_exec_alert(lrmd_t *lrmd, const char *alert_id, const char *alert_path,
int timeout, lrmd_key_value_t *params)
{
int rc = pcmk_ok;
xmlNode *data = create_xml_node(NULL, F_LRMD_ALERT);
xmlNode *args = create_xml_node(data, XML_TAG_ATTRS);
lrmd_key_value_t *tmp = NULL;
crm_xml_add(data, F_LRMD_ORIGIN, __FUNCTION__);
crm_xml_add(data, F_LRMD_ALERT_ID, alert_id);
crm_xml_add(data, F_LRMD_ALERT_PATH, alert_path);
crm_xml_add_int(data, F_LRMD_TIMEOUT, timeout);
for (tmp = params; tmp; tmp = tmp->next) {
hash2smartfield((gpointer) tmp->key, (gpointer) tmp->value, args);
}
rc = lrmd_send_command(lrmd, LRMD_OP_ALERT_EXEC, data, NULL, timeout,
lrmd_opt_notify_orig_only, TRUE);
free_xml(data);
lrmd_key_value_freeall(params);
return rc;
}
static int
lrmd_api_cancel(lrmd_t * lrmd, const char *rsc_id, const char *action, int interval)
{
int rc = pcmk_ok;
xmlNode *data = create_xml_node(NULL, F_LRMD_RSC);
crm_xml_add(data, F_LRMD_ORIGIN, __FUNCTION__);
crm_xml_add(data, F_LRMD_RSC_ACTION, action);
crm_xml_add(data, F_LRMD_RSC_ID, rsc_id);
crm_xml_add_int(data, F_LRMD_RSC_INTERVAL, interval);
rc = lrmd_send_command(lrmd, LRMD_OP_RSC_CANCEL, data, NULL, 0, 0, TRUE);
free_xml(data);
return rc;
}
static int
list_stonith_agents(lrmd_list_t ** resources)
{
int rc = 0;
stonith_t *stonith_api = stonith_api_new();
stonith_key_value_t *stonith_resources = NULL;
stonith_key_value_t *dIter = NULL;
if(stonith_api) {
stonith_api->cmds->list_agents(stonith_api, st_opt_sync_call, NULL, &stonith_resources, 0);
stonith_api->cmds->free(stonith_api);
}
for (dIter = stonith_resources; dIter; dIter = dIter->next) {
rc++;
if (resources) {
*resources = lrmd_list_add(*resources, dIter->value);
}
}
stonith_key_value_freeall(stonith_resources, 1, 0);
return rc;
}
static int
lrmd_api_list_agents(lrmd_t * lrmd, lrmd_list_t ** resources, const char *class,
const char *provider)
{
int rc = 0;
if (safe_str_eq(class, PCMK_RESOURCE_CLASS_STONITH)) {
rc += list_stonith_agents(resources);
} else {
GListPtr gIter = NULL;
GList *agents = resources_list_agents(class, provider);
for (gIter = agents; gIter != NULL; gIter = gIter->next) {
*resources = lrmd_list_add(*resources, (const char *)gIter->data);
rc++;
}
g_list_free_full(agents, free);
if (!class) {
rc += list_stonith_agents(resources);
}
}
if (rc == 0) {
crm_notice("No agents found for class %s", class);
rc = -EPROTONOSUPPORT;
}
return rc;
}
static int
does_provider_have_agent(const char *agent, const char *provider, const char *class)
{
int found = 0;
GList *agents = NULL;
GListPtr gIter2 = NULL;
agents = resources_list_agents(class, provider);
for (gIter2 = agents; gIter2 != NULL; gIter2 = gIter2->next) {
if (safe_str_eq(agent, gIter2->data)) {
found = 1;
}
}
g_list_free_full(agents, free);
return found;
}
static int
lrmd_api_list_ocf_providers(lrmd_t * lrmd, const char *agent, lrmd_list_t ** providers)
{
int rc = pcmk_ok;
char *provider = NULL;
GList *ocf_providers = NULL;
GListPtr gIter = NULL;
ocf_providers = resources_list_providers(PCMK_RESOURCE_CLASS_OCF);
for (gIter = ocf_providers; gIter != NULL; gIter = gIter->next) {
provider = gIter->data;
if (!agent || does_provider_have_agent(agent, provider,
PCMK_RESOURCE_CLASS_OCF)) {
*providers = lrmd_list_add(*providers, (const char *)gIter->data);
rc++;
}
}
g_list_free_full(ocf_providers, free);
return rc;
}
static int
lrmd_api_list_standards(lrmd_t * lrmd, lrmd_list_t ** supported)
{
int rc = 0;
GList *standards = NULL;
GListPtr gIter = NULL;
standards = resources_list_standards();
for (gIter = standards; gIter != NULL; gIter = gIter->next) {
*supported = lrmd_list_add(*supported, (const char *)gIter->data);
rc++;
}
if (list_stonith_agents(NULL) > 0) {
*supported = lrmd_list_add(*supported, PCMK_RESOURCE_CLASS_STONITH);
rc++;
}
g_list_free_full(standards, free);
return rc;
}
lrmd_t *
lrmd_api_new(void)
{
lrmd_t *new_lrmd = NULL;
lrmd_private_t *pvt = NULL;
new_lrmd = calloc(1, sizeof(lrmd_t));
pvt = calloc(1, sizeof(lrmd_private_t));
pvt->remote = calloc(1, sizeof(crm_remote_t));
new_lrmd->cmds = calloc(1, sizeof(lrmd_api_operations_t));
pvt->type = CRM_CLIENT_IPC;
new_lrmd->private = pvt;
new_lrmd->cmds->connect = lrmd_api_connect;
new_lrmd->cmds->connect_async = lrmd_api_connect_async;
new_lrmd->cmds->is_connected = lrmd_api_is_connected;
new_lrmd->cmds->poke_connection = lrmd_api_poke_connection;
new_lrmd->cmds->disconnect = lrmd_api_disconnect;
new_lrmd->cmds->register_rsc = lrmd_api_register_rsc;
new_lrmd->cmds->unregister_rsc = lrmd_api_unregister_rsc;
new_lrmd->cmds->get_rsc_info = lrmd_api_get_rsc_info;
new_lrmd->cmds->set_callback = lrmd_api_set_callback;
new_lrmd->cmds->get_metadata = lrmd_api_get_metadata;
new_lrmd->cmds->exec = lrmd_api_exec;
new_lrmd->cmds->cancel = lrmd_api_cancel;
new_lrmd->cmds->list_agents = lrmd_api_list_agents;
new_lrmd->cmds->list_ocf_providers = lrmd_api_list_ocf_providers;
new_lrmd->cmds->list_standards = lrmd_api_list_standards;
new_lrmd->cmds->exec_alert = lrmd_api_exec_alert;
return new_lrmd;
}
lrmd_t *
lrmd_remote_api_new(const char *nodename, const char *server, int port)
{
#ifdef HAVE_GNUTLS_GNUTLS_H
lrmd_t *new_lrmd = lrmd_api_new();
lrmd_private_t *native = new_lrmd->private;
if (!nodename && !server) {
lrmd_api_delete(new_lrmd);
return NULL;
}
native->type = CRM_CLIENT_TLS;
native->remote_nodename = nodename ? strdup(nodename) : strdup(server);
native->server = server ? strdup(server) : strdup(nodename);
native->port = port;
if (native->port == 0) {
const char *remote_port_str = getenv("PCMK_remote_port");
native->port = remote_port_str ? atoi(remote_port_str) : DEFAULT_REMOTE_PORT;
}
return new_lrmd;
#else
crm_err("GNUTLS is not enabled for this build, remote LRMD client can not be created");
return NULL;
#endif
}
void
lrmd_api_delete(lrmd_t * lrmd)
{
if (!lrmd) {
return;
}
lrmd->cmds->disconnect(lrmd); /* no-op if already disconnected */
free(lrmd->cmds);
if (lrmd->private) {
lrmd_private_t *native = lrmd->private;
#ifdef HAVE_GNUTLS_GNUTLS_H
free(native->server);
#endif
free(native->remote_nodename);
free(native->remote);
free(native->token);
free(native->peer_version);
}
free(lrmd->private);
free(lrmd);
}
diff --git a/lib/pengine/utils.c b/lib/pengine/utils.c
index 7c29e7bcb0..25137030cd 100644
--- a/lib/pengine/utils.c
+++ b/lib/pengine/utils.c
@@ -1,2064 +1,2107 @@
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* This library 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.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser 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
*/
#include <crm_internal.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/common/util.h>
#include <glib.h>
#include <crm/pengine/rules.h>
#include <crm/pengine/internal.h>
pe_working_set_t *pe_dataset = NULL;
extern xmlNode *get_object_root(const char *object_type, xmlNode * the_root);
void print_str_str(gpointer key, gpointer value, gpointer user_data);
gboolean ghash_free_str_str(gpointer key, gpointer value, gpointer user_data);
void unpack_operation(action_t * action, xmlNode * xml_obj, resource_t * container,
pe_working_set_t * data_set);
static xmlNode *find_rsc_op_entry_helper(resource_t * rsc, const char *key,
gboolean include_disabled);
+pe_rsc_action_details_t *
+pe_rsc_action_details(pe_action_t *action)
+{
+ pe_rsc_action_details_t *details;
+
+ CRM_CHECK(action != NULL, return NULL);
+
+ if (action->action_details == NULL) {
+ action->action_details = calloc(1, sizeof(pe_rsc_action_details_t));
+ CRM_CHECK(action->action_details != NULL, return NULL);
+ }
+
+ details = (pe_rsc_action_details_t *) action->action_details;
+ if (details->versioned_parameters == NULL) {
+ details->versioned_parameters = create_xml_node(NULL,
+ XML_TAG_OP_VER_ATTRS);
+ }
+ if (details->versioned_meta == NULL) {
+ details->versioned_meta = create_xml_node(NULL, XML_TAG_OP_VER_META);
+ }
+ return details;
+}
+
+static void
+pe_free_rsc_action_details(pe_action_t *action)
+{
+ pe_rsc_action_details_t *details;
+
+ if ((action == NULL) || (action->action_details == NULL)) {
+ return;
+ }
+
+ details = (pe_rsc_action_details_t *) action->action_details;
+
+ if (details->versioned_parameters) {
+ free_xml(details->versioned_parameters);
+ }
+ if (details->versioned_meta) {
+ free_xml(details->versioned_meta);
+ }
+
+ action->action_details = NULL;
+}
+
/*!
* \internal
* \brief Check whether we can fence a particular node
*
* \param[in] data_set Working set for cluster
* \param[in] node Name of node to check
*
* \return TRUE if node can be fenced, FALSE otherwise
*
* \note This function should only be called for cluster nodes and baremetal
* remote nodes; guest nodes are fenced by stopping their container
* resource, so fence execution requirements do not apply to them.
*/
bool pe_can_fence(pe_working_set_t * data_set, node_t *node)
{
if(is_not_set(data_set->flags, pe_flag_stonith_enabled)) {
return FALSE; /* Turned off */
} else if (is_not_set(data_set->flags, pe_flag_have_stonith_resource)) {
return FALSE; /* No devices */
} else if (is_set(data_set->flags, pe_flag_have_quorum)) {
return TRUE;
} else if (data_set->no_quorum_policy == no_quorum_ignore) {
return TRUE;
} else if(node == NULL) {
return FALSE;
} else if(node->details->online) {
crm_notice("We can fence %s without quorum because they're in our membership", node->details->uname);
return TRUE;
}
crm_trace("Cannot fence %s", node->details->uname);
return FALSE;
}
node_t *
node_copy(const node_t *this_node)
{
node_t *new_node = NULL;
CRM_CHECK(this_node != NULL, return NULL);
new_node = calloc(1, sizeof(node_t));
CRM_ASSERT(new_node != NULL);
crm_trace("Copying %p (%s) to %p", this_node, this_node->details->uname, new_node);
new_node->rsc_discover_mode = this_node->rsc_discover_mode;
new_node->weight = this_node->weight;
new_node->fixed = this_node->fixed;
new_node->details = this_node->details;
return new_node;
}
/* any node in list1 or list2 and not in the other gets a score of -INFINITY */
void
node_list_exclude(GHashTable * hash, GListPtr list, gboolean merge_scores)
{
GHashTable *result = hash;
node_t *other_node = NULL;
GListPtr gIter = list;
GHashTableIter iter;
node_t *node = NULL;
g_hash_table_iter_init(&iter, hash);
while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
other_node = pe_find_node_id(list, node->details->id);
if (other_node == NULL) {
node->weight = -INFINITY;
} else if (merge_scores) {
node->weight = merge_weights(node->weight, other_node->weight);
}
}
for (; gIter != NULL; gIter = gIter->next) {
node_t *node = (node_t *) gIter->data;
other_node = pe_hash_table_lookup(result, node->details->id);
if (other_node == NULL) {
node_t *new_node = node_copy(node);
new_node->weight = -INFINITY;
g_hash_table_insert(result, (gpointer) new_node->details->id, new_node);
}
}
}
GHashTable *
node_hash_from_list(GListPtr list)
{
GListPtr gIter = list;
GHashTable *result = g_hash_table_new_full(crm_str_hash, g_str_equal, NULL, g_hash_destroy_str);
for (; gIter != NULL; gIter = gIter->next) {
node_t *node = (node_t *) gIter->data;
node_t *n = node_copy(node);
g_hash_table_insert(result, (gpointer) n->details->id, n);
}
return result;
}
GListPtr
node_list_dup(GListPtr list1, gboolean reset, gboolean filter)
{
GListPtr result = NULL;
GListPtr gIter = list1;
for (; gIter != NULL; gIter = gIter->next) {
node_t *new_node = NULL;
node_t *this_node = (node_t *) gIter->data;
if (filter && this_node->weight < 0) {
continue;
}
new_node = node_copy(this_node);
if (reset) {
new_node->weight = 0;
}
if (new_node != NULL) {
result = g_list_prepend(result, new_node);
}
}
return result;
}
gint
sort_node_uname(gconstpointer a, gconstpointer b)
{
const node_t *node_a = a;
const node_t *node_b = b;
return strcmp(node_a->details->uname, node_b->details->uname);
}
void
dump_node_scores_worker(int level, const char *file, const char *function, int line,
resource_t * rsc, const char *comment, GHashTable * nodes)
{
GHashTable *hash = nodes;
GHashTableIter iter;
node_t *node = NULL;
if (rsc) {
hash = rsc->allowed_nodes;
}
if (rsc && is_set(rsc->flags, pe_rsc_orphan)) {
/* Don't show the allocation scores for orphans */
return;
}
if (level == 0) {
char score[128];
int len = sizeof(score);
/* For now we want this in sorted order to keep the regression tests happy */
GListPtr gIter = NULL;
GListPtr list = g_hash_table_get_values(hash);
list = g_list_sort(list, sort_node_uname);
gIter = list;
for (; gIter != NULL; gIter = gIter->next) {
node_t *node = (node_t *) gIter->data;
/* This function is called a whole lot, use stack allocated score */
score2char_stack(node->weight, score, len);
if (rsc) {
printf("%s: %s allocation score on %s: %s\n",
comment, rsc->id, node->details->uname, score);
} else {
printf("%s: %s = %s\n", comment, node->details->uname, score);
}
}
g_list_free(list);
} else if (hash) {
char score[128];
int len = sizeof(score);
g_hash_table_iter_init(&iter, hash);
while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
/* This function is called a whole lot, use stack allocated score */
score2char_stack(node->weight, score, len);
if (rsc) {
do_crm_log_alias(LOG_TRACE, file, function, line,
"%s: %s allocation score on %s: %s", comment, rsc->id,
node->details->uname, score);
} else {
do_crm_log_alias(LOG_TRACE, file, function, line + 1, "%s: %s = %s", comment,
node->details->uname, score);
}
}
}
if (rsc && rsc->children) {
GListPtr gIter = NULL;
gIter = rsc->children;
for (; gIter != NULL; gIter = gIter->next) {
resource_t *child = (resource_t *) gIter->data;
dump_node_scores_worker(level, file, function, line, child, comment, nodes);
}
}
}
static void
append_dump_text(gpointer key, gpointer value, gpointer user_data)
{
char **dump_text = user_data;
int len = 0;
char *new_text = NULL;
len = strlen(*dump_text) + strlen(" ") + strlen(key) + strlen("=") + strlen(value) + 1;
new_text = calloc(1, len);
sprintf(new_text, "%s %s=%s", *dump_text, (char *)key, (char *)value);
free(*dump_text);
*dump_text = new_text;
}
void
dump_node_capacity(int level, const char *comment, node_t * node)
{
int len = 0;
char *dump_text = NULL;
len = strlen(comment) + strlen(": ") + strlen(node->details->uname) + strlen(" capacity:") + 1;
dump_text = calloc(1, len);
sprintf(dump_text, "%s: %s capacity:", comment, node->details->uname);
g_hash_table_foreach(node->details->utilization, append_dump_text, &dump_text);
if (level == 0) {
fprintf(stdout, "%s\n", dump_text);
} else {
crm_trace("%s", dump_text);
}
free(dump_text);
}
void
dump_rsc_utilization(int level, const char *comment, resource_t * rsc, node_t * node)
{
int len = 0;
char *dump_text = NULL;
len = strlen(comment) + strlen(": ") + strlen(rsc->id) + strlen(" utilization on ")
+ strlen(node->details->uname) + strlen(":") + 1;
dump_text = calloc(1, len);
sprintf(dump_text, "%s: %s utilization on %s:", comment, rsc->id, node->details->uname);
g_hash_table_foreach(rsc->utilization, append_dump_text, &dump_text);
if (level == 0) {
fprintf(stdout, "%s\n", dump_text);
} else {
crm_trace("%s", dump_text);
}
free(dump_text);
}
gint
sort_rsc_index(gconstpointer a, gconstpointer b)
{
const resource_t *resource1 = (const resource_t *)a;
const resource_t *resource2 = (const resource_t *)b;
if (a == NULL && b == NULL) {
return 0;
}
if (a == NULL) {
return 1;
}
if (b == NULL) {
return -1;
}
if (resource1->sort_index > resource2->sort_index) {
return -1;
}
if (resource1->sort_index < resource2->sort_index) {
return 1;
}
return 0;
}
gint
sort_rsc_priority(gconstpointer a, gconstpointer b)
{
const resource_t *resource1 = (const resource_t *)a;
const resource_t *resource2 = (const resource_t *)b;
if (a == NULL && b == NULL) {
return 0;
}
if (a == NULL) {
return 1;
}
if (b == NULL) {
return -1;
}
if (resource1->priority > resource2->priority) {
return -1;
}
if (resource1->priority < resource2->priority) {
return 1;
}
return 0;
}
action_t *
custom_action(resource_t * rsc, char *key, const char *task,
node_t * on_node, gboolean optional, gboolean save_action,
pe_working_set_t * data_set)
{
action_t *action = NULL;
GListPtr possible_matches = NULL;
CRM_CHECK(key != NULL, return NULL);
CRM_CHECK(task != NULL, free(key); return NULL);
if (save_action && rsc != NULL) {
possible_matches = find_actions(rsc->actions, key, on_node);
} else if(save_action) {
#if 0
action = g_hash_table_lookup(data_set->singletons, key);
#else
/* More expensive but takes 'node' into account */
possible_matches = find_actions(data_set->actions, key, on_node);
#endif
}
if(data_set->singletons == NULL) {
data_set->singletons = g_hash_table_new_full(crm_str_hash, g_str_equal, NULL, NULL);
}
if (possible_matches != NULL) {
if (g_list_length(possible_matches) > 1) {
pe_warn("Action %s for %s on %s exists %d times",
task, rsc ? rsc->id : "<NULL>",
on_node ? on_node->details->uname : "<NULL>", g_list_length(possible_matches));
}
action = g_list_nth_data(possible_matches, 0);
pe_rsc_trace(rsc, "Found existing action (%d) %s for %s on %s",
action->id, task, rsc ? rsc->id : "<NULL>",
on_node ? on_node->details->uname : "<NULL>");
g_list_free(possible_matches);
}
if (action == NULL) {
if (save_action) {
pe_rsc_trace(rsc, "Creating%s action %d: %s for %s on %s %d",
optional ? "" : " mandatory", data_set->action_id, key,
rsc ? rsc->id : "<NULL>", on_node ? on_node->details->uname : "<NULL>", optional);
}
action = calloc(1, sizeof(action_t));
if (save_action) {
action->id = data_set->action_id++;
} else {
action->id = 0;
}
action->rsc = rsc;
CRM_ASSERT(task != NULL);
action->task = strdup(task);
if (on_node) {
action->node = node_copy(on_node);
}
action->uuid = strdup(key);
pe_set_action_bit(action, pe_action_runnable);
if (optional) {
pe_rsc_trace(rsc, "Set optional on %s", action->uuid);
pe_set_action_bit(action, pe_action_optional);
} else {
pe_clear_action_bit(action, pe_action_optional);
pe_rsc_trace(rsc, "Unset optional on %s", action->uuid);
}
/*
Implied by calloc()...
action->actions_before = NULL;
action->actions_after = NULL;
action->pseudo = FALSE;
action->dumped = FALSE;
action->processed = FALSE;
action->seen_count = 0;
*/
action->extra = crm_str_table_new();
action->meta = crm_str_table_new();
- action->versioned_parameters = create_xml_node(NULL, XML_TAG_OP_VER_ATTRS);
-
- action->versioned_meta = create_xml_node(NULL, XML_TAG_OP_VER_META);
-
if (save_action) {
data_set->actions = g_list_prepend(data_set->actions, action);
if(rsc == NULL) {
g_hash_table_insert(data_set->singletons, action->uuid, action);
}
}
if (rsc != NULL) {
action->op_entry = find_rsc_op_entry_helper(rsc, key, TRUE);
unpack_operation(action, action->op_entry, rsc->container, data_set);
if (save_action) {
rsc->actions = g_list_prepend(rsc->actions, action);
}
}
if (save_action) {
pe_rsc_trace(rsc, "Action %d created", action->id);
}
}
if (optional == FALSE) {
pe_rsc_trace(rsc, "Unset optional on %s", action->uuid);
pe_clear_action_bit(action, pe_action_optional);
}
if (rsc != NULL) {
enum action_tasks a_task = text2task(action->task);
int warn_level = LOG_TRACE;
if (save_action) {
warn_level = LOG_WARNING;
}
if (is_set(action->flags, pe_action_have_node_attrs) == FALSE
&& action->node != NULL && action->op_entry != NULL) {
pe_set_action_bit(action, pe_action_have_node_attrs);
unpack_instance_attributes(data_set->input, action->op_entry, XML_TAG_ATTR_SETS,
action->node->details->attrs,
action->extra, NULL, FALSE, data_set->now);
}
if (is_set(action->flags, pe_action_pseudo)) {
/* leave untouched */
} else if (action->node == NULL) {
pe_rsc_trace(rsc, "Unset runnable on %s", action->uuid);
pe_clear_action_bit(action, pe_action_runnable);
} else if (is_not_set(rsc->flags, pe_rsc_managed)
&& g_hash_table_lookup(action->meta, XML_LRM_ATTR_INTERVAL) == NULL) {
crm_debug("Action %s (unmanaged)", action->uuid);
pe_rsc_trace(rsc, "Set optional on %s", action->uuid);
pe_set_action_bit(action, pe_action_optional);
/* action->runnable = FALSE; */
} else if (action->node->details->online == FALSE
&& (!is_container_remote_node(action->node) || action->node->details->remote_requires_reset)) {
pe_clear_action_bit(action, pe_action_runnable);
do_crm_log(warn_level, "Action %s on %s is unrunnable (offline)",
action->uuid, action->node->details->uname);
if (is_set(action->rsc->flags, pe_rsc_managed)
&& save_action && a_task == stop_rsc
&& action->node->details->unclean == FALSE) {
pe_fence_node(data_set, action->node, "resource actions are unrunnable");
}
} else if (action->node->details->pending) {
pe_clear_action_bit(action, pe_action_runnable);
do_crm_log(warn_level, "Action %s on %s is unrunnable (pending)",
action->uuid, action->node->details->uname);
} else if (action->needs == rsc_req_nothing) {
pe_rsc_trace(rsc, "Action %s does not require anything", action->uuid);
pe_set_action_bit(action, pe_action_runnable);
#if 0
/*
* No point checking this
* - if we don't have quorum we can't stonith anyway
*/
} else if (action->needs == rsc_req_stonith) {
crm_trace("Action %s requires only stonith", action->uuid);
action->runnable = TRUE;
#endif
} else if (is_set(data_set->flags, pe_flag_have_quorum) == FALSE
&& data_set->no_quorum_policy == no_quorum_stop) {
pe_clear_action_bit(action, pe_action_runnable);
crm_debug("%s\t%s (cancelled : quorum)", action->node->details->uname, action->uuid);
} else if (is_set(data_set->flags, pe_flag_have_quorum) == FALSE
&& data_set->no_quorum_policy == no_quorum_freeze) {
pe_rsc_trace(rsc, "Check resource is already active: %s %s %s %s", rsc->id, action->uuid, role2text(rsc->next_role), role2text(rsc->role));
if (rsc->fns->active(rsc, TRUE) == FALSE || rsc->next_role > rsc->role) {
pe_clear_action_bit(action, pe_action_runnable);
pe_rsc_debug(rsc, "%s\t%s (cancelled : quorum freeze)",
action->node->details->uname, action->uuid);
}
} else {
pe_rsc_trace(rsc, "Action %s is runnable", action->uuid);
pe_set_action_bit(action, pe_action_runnable);
}
if (save_action) {
switch (a_task) {
case stop_rsc:
set_bit(rsc->flags, pe_rsc_stopping);
break;
case start_rsc:
clear_bit(rsc->flags, pe_rsc_starting);
if (is_set(action->flags, pe_action_runnable)) {
set_bit(rsc->flags, pe_rsc_starting);
}
break;
default:
break;
}
}
}
free(key);
return action;
}
static const char *
unpack_operation_on_fail(action_t * action)
{
const char *value = g_hash_table_lookup(action->meta, XML_OP_ATTR_ON_FAIL);
if (safe_str_eq(action->task, CRMD_ACTION_STOP) && safe_str_eq(value, "standby")) {
crm_config_err("on-fail=standby is not allowed for stop actions: %s", action->rsc->id);
return NULL;
} else if (safe_str_eq(action->task, CRMD_ACTION_DEMOTE) && !value) {
/* demote on_fail defaults to master monitor value if present */
xmlNode *operation = NULL;
const char *name = NULL;
const char *role = NULL;
const char *on_fail = NULL;
const char *interval = NULL;
const char *enabled = NULL;
CRM_CHECK(action->rsc != NULL, return NULL);
for (operation = __xml_first_child(action->rsc->ops_xml);
operation && !value; operation = __xml_next_element(operation)) {
if (!crm_str_eq((const char *)operation->name, "op", TRUE)) {
continue;
}
name = crm_element_value(operation, "name");
role = crm_element_value(operation, "role");
on_fail = crm_element_value(operation, XML_OP_ATTR_ON_FAIL);
enabled = crm_element_value(operation, "enabled");
interval = crm_element_value(operation, XML_LRM_ATTR_INTERVAL);
if (!on_fail) {
continue;
} else if (enabled && !crm_is_true(enabled)) {
continue;
} else if (safe_str_neq(name, "monitor") || safe_str_neq(role, "Master")) {
continue;
} else if (crm_get_interval(interval) <= 0) {
continue;
}
value = on_fail;
}
}
return value;
}
static xmlNode *
find_min_interval_mon(resource_t * rsc, gboolean include_disabled)
{
int number = 0;
int min_interval = -1;
const char *name = NULL;
const char *value = NULL;
const char *interval = NULL;
xmlNode *op = NULL;
xmlNode *operation = NULL;
for (operation = __xml_first_child(rsc->ops_xml); operation != NULL;
operation = __xml_next_element(operation)) {
if (crm_str_eq((const char *)operation->name, "op", TRUE)) {
name = crm_element_value(operation, "name");
interval = crm_element_value(operation, XML_LRM_ATTR_INTERVAL);
value = crm_element_value(operation, "enabled");
if (!include_disabled && value && crm_is_true(value) == FALSE) {
continue;
}
if (safe_str_neq(name, RSC_STATUS)) {
continue;
}
number = crm_get_interval(interval);
if (number < 0) {
continue;
}
if (min_interval < 0 || number < min_interval) {
min_interval = number;
op = operation;
}
}
}
return op;
}
static int
unpack_start_delay(const char *value, GHashTable *meta)
{
int start_delay = 0;
if (value != NULL) {
start_delay = crm_get_msec(value);
if (start_delay < 0) {
start_delay = 0;
}
if (meta) {
g_hash_table_replace(meta, strdup(XML_OP_ATTR_START_DELAY), crm_itoa(start_delay));
}
}
return start_delay;
}
static int
unpack_interval_origin(const char *value, GHashTable *meta, xmlNode *xml_obj,
unsigned long long interval, crm_time_t *now)
{
int start_delay = 0;
if (interval > 0 && value) {
crm_time_t *origin = crm_time_new(value);
if (origin && now) {
crm_time_t *delay = NULL;
int rc = crm_time_compare(origin, now);
long long delay_s = 0;
int interval_s = (interval / 1000);
crm_trace("Origin: %s, interval: %d", value, interval_s);
/* If 'origin' is in the future, find the most recent "multiple" that occurred in the past */
while(rc > 0) {
crm_time_add_seconds(origin, -interval_s);
rc = crm_time_compare(origin, now);
}
/* Now find the first "multiple" that occurs after 'now' */
while (rc < 0) {
crm_time_add_seconds(origin, interval_s);
rc = crm_time_compare(origin, now);
}
delay = crm_time_calculate_duration(origin, now);
crm_time_log(LOG_TRACE, "origin", origin,
crm_time_log_date | crm_time_log_timeofday |
crm_time_log_with_timezone);
crm_time_log(LOG_TRACE, "now", now,
crm_time_log_date | crm_time_log_timeofday |
crm_time_log_with_timezone);
crm_time_log(LOG_TRACE, "delay", delay, crm_time_log_duration);
delay_s = crm_time_get_seconds(delay);
CRM_CHECK(delay_s >= 0, delay_s = 0);
start_delay = delay_s * 1000;
if (xml_obj) {
crm_info("Calculated a start delay of %llds for %s", delay_s, ID(xml_obj));
}
if (meta) {
g_hash_table_replace(meta, strdup(XML_OP_ATTR_START_DELAY),
crm_itoa(start_delay));
}
crm_time_free(origin);
crm_time_free(delay);
} else if (!origin && xml_obj) {
crm_config_err("Operation %s contained an invalid " XML_OP_ATTR_ORIGIN ": %s",
ID(xml_obj), value);
}
}
return start_delay;
}
static int
unpack_timeout(const char *value, action_t *action, xmlNode *xml_obj,
unsigned long long interval, GHashTable *config_hash)
{
int timeout = 0;
if (value == NULL && xml_obj == NULL && action &&
safe_str_eq(action->task, RSC_STATUS) && interval == 0) {
xmlNode *min_interval_mon = find_min_interval_mon(action->rsc, FALSE);
if (min_interval_mon) {
value = crm_element_value(min_interval_mon, XML_ATTR_TIMEOUT);
pe_rsc_trace(action->rsc,
"\t%s uses the timeout value '%s' from the minimum interval monitor",
action->uuid, value);
}
}
if (value == NULL && config_hash) {
value = pe_pref(config_hash, "default-action-timeout");
}
timeout = crm_get_msec(value);
if (timeout < 0) {
timeout = 0;
}
return timeout;
}
static void
unpack_versioned_meta(xmlNode *versioned_meta, xmlNode *xml_obj, unsigned long long interval, crm_time_t *now)
{
xmlNode *attrs = NULL;
xmlNode *attr = NULL;
for (attrs = __xml_first_child(versioned_meta); attrs != NULL; attrs = __xml_next_element(attrs)) {
for (attr = __xml_first_child(attrs); attr != NULL; attr = __xml_next_element(attr)) {
const char *name = crm_element_value(attr, XML_NVPAIR_ATTR_NAME);
const char *value = crm_element_value(attr, XML_NVPAIR_ATTR_VALUE);
if (safe_str_eq(name, XML_OP_ATTR_START_DELAY)) {
int start_delay = unpack_start_delay(value, NULL);
crm_xml_add_int(attr, XML_NVPAIR_ATTR_VALUE, start_delay);
} else if (safe_str_eq(name, XML_OP_ATTR_ORIGIN)) {
int start_delay = unpack_interval_origin(value, NULL, xml_obj, interval, now);
crm_xml_add(attr, XML_NVPAIR_ATTR_NAME, XML_OP_ATTR_START_DELAY);
crm_xml_add_int(attr, XML_NVPAIR_ATTR_VALUE, start_delay);
} else if (safe_str_eq(name, XML_ATTR_TIMEOUT)) {
int timeout = unpack_timeout(value, NULL, NULL, 0, NULL);
crm_xml_add_int(attr, XML_NVPAIR_ATTR_VALUE, timeout);
}
}
}
}
void
unpack_operation(action_t * action, xmlNode * xml_obj, resource_t * container,
pe_working_set_t * data_set)
{
unsigned long long interval = 0;
int timeout = 0;
char *value_ms = NULL;
const char *value = NULL;
const char *field = NULL;
+ pe_rsc_action_details_t *rsc_details = NULL;
CRM_CHECK(action->rsc != NULL, return);
unpack_instance_attributes(data_set->input, data_set->op_defaults, XML_TAG_META_SETS, NULL,
action->meta, NULL, FALSE, data_set->now);
if (xml_obj) {
xmlAttrPtr xIter = NULL;
for (xIter = xml_obj->properties; xIter; xIter = xIter->next) {
const char *prop_name = (const char *)xIter->name;
const char *prop_value = crm_element_value(xml_obj, prop_name);
g_hash_table_replace(action->meta, strdup(prop_name), strdup(prop_value));
}
}
unpack_instance_attributes(data_set->input, xml_obj, XML_TAG_META_SETS,
NULL, action->meta, NULL, FALSE, data_set->now);
unpack_instance_attributes(data_set->input, xml_obj, XML_TAG_ATTR_SETS,
NULL, action->meta, NULL, FALSE, data_set->now);
+ rsc_details = pe_rsc_action_details(action);
pe_unpack_versioned_attributes(data_set->input, xml_obj, XML_TAG_ATTR_SETS, NULL,
- action->versioned_parameters, data_set->now);
-
+ rsc_details->versioned_parameters, data_set->now);
pe_unpack_versioned_attributes(data_set->input, xml_obj, XML_TAG_META_SETS, NULL,
- action->versioned_meta, data_set->now);
+ rsc_details->versioned_meta, data_set->now);
g_hash_table_remove(action->meta, "id");
field = XML_LRM_ATTR_INTERVAL;
value = g_hash_table_lookup(action->meta, field);
if (value != NULL) {
interval = crm_get_interval(value);
if (interval > 0) {
value_ms = crm_itoa(interval);
g_hash_table_replace(action->meta, strdup(field), value_ms);
} else {
g_hash_table_remove(action->meta, field);
}
}
/* @COMPAT data sets < 1.1.10 ("requires" on start action not resource) */
value = g_hash_table_lookup(action->meta, "requires");
if (safe_str_neq(action->task, RSC_START)
&& safe_str_neq(action->task, RSC_PROMOTE)) {
action->needs = rsc_req_nothing;
value = "nothing (not start/promote)";
} else if (safe_str_eq(value, "nothing")) {
action->needs = rsc_req_nothing;
} else if (safe_str_eq(value, "quorum")) {
action->needs = rsc_req_quorum;
} else if (safe_str_eq(value, "unfencing")) {
action->needs = rsc_req_stonith;
set_bit(action->rsc->flags, pe_rsc_needs_unfencing);
if (is_not_set(data_set->flags, pe_flag_stonith_enabled)) {
crm_notice("%s requires unfencing but fencing is disabled", action->rsc->id);
}
} else if (is_set(data_set->flags, pe_flag_stonith_enabled)
&& safe_str_eq(value, "fencing")) {
action->needs = rsc_req_stonith;
if (is_not_set(data_set->flags, pe_flag_stonith_enabled)) {
crm_notice("%s requires fencing but fencing is disabled", action->rsc->id);
}
/* @COMPAT end compatibility code */
} else if (is_set(action->rsc->flags, pe_rsc_needs_fencing)) {
action->needs = rsc_req_stonith;
value = "fencing (resource)";
} else if (is_set(action->rsc->flags, pe_rsc_needs_quorum)) {
action->needs = rsc_req_quorum;
value = "quorum (resource)";
} else {
action->needs = rsc_req_nothing;
value = "nothing (resource)";
}
pe_rsc_trace(action->rsc, "\tAction %s requires: %s", action->task, value);
value = unpack_operation_on_fail(action);
if (value == NULL) {
} else if (safe_str_eq(value, "block")) {
action->on_fail = action_fail_block;
g_hash_table_insert(action->meta, strdup(XML_OP_ATTR_ON_FAIL), strdup("block"));
} else if (safe_str_eq(value, "fence")) {
action->on_fail = action_fail_fence;
value = "node fencing";
if (is_set(data_set->flags, pe_flag_stonith_enabled) == FALSE) {
crm_config_err("Specifying on_fail=fence and" " stonith-enabled=false makes no sense");
action->on_fail = action_fail_stop;
action->fail_role = RSC_ROLE_STOPPED;
value = "stop resource";
}
} else if (safe_str_eq(value, "standby")) {
action->on_fail = action_fail_standby;
value = "node standby";
} else if (safe_str_eq(value, "ignore")
|| safe_str_eq(value, "nothing")) {
action->on_fail = action_fail_ignore;
value = "ignore";
} else if (safe_str_eq(value, "migrate")) {
action->on_fail = action_fail_migrate;
value = "force migration";
} else if (safe_str_eq(value, "stop")) {
action->on_fail = action_fail_stop;
action->fail_role = RSC_ROLE_STOPPED;
value = "stop resource";
} else if (safe_str_eq(value, "restart")) {
action->on_fail = action_fail_recover;
value = "restart (and possibly migrate)";
} else if (safe_str_eq(value, "restart-container")) {
if (container) {
action->on_fail = action_fail_restart_container;
value = "restart container (and possibly migrate)";
} else {
value = NULL;
}
} else {
pe_err("Resource %s: Unknown failure type (%s)", action->rsc->id, value);
value = NULL;
}
/* defaults */
if (value == NULL && container) {
action->on_fail = action_fail_restart_container;
value = "restart container (and possibly migrate) (default)";
/* for baremetal remote nodes, ensure that any failure that results in
* dropping an active connection to a remote node results in fencing of
* the remote node.
*
* There are only two action failures that don't result in fencing.
* 1. probes - probe failures are expected.
* 2. start - a start failure indicates that an active connection does not already
* exist. The user can set op on-fail=fence if they really want to fence start
* failures. */
} else if (((value == NULL) || !is_set(action->rsc->flags, pe_rsc_managed)) &&
(is_rsc_baremetal_remote_node(action->rsc, data_set) &&
!(safe_str_eq(action->task, CRMD_ACTION_STATUS) && interval == 0) &&
(safe_str_neq(action->task, CRMD_ACTION_START)))) {
if (!is_set(action->rsc->flags, pe_rsc_managed)) {
action->on_fail = action_fail_stop;
action->fail_role = RSC_ROLE_STOPPED;
value = "stop unmanaged baremetal remote node (enforcing default)";
} else {
if (is_set(data_set->flags, pe_flag_stonith_enabled)) {
value = "fence baremetal remote node (default)";
} else {
value = "recover baremetal remote node connection (default)";
}
if (action->rsc->remote_reconnect_interval) {
action->fail_role = RSC_ROLE_STOPPED;
}
action->on_fail = action_fail_reset_remote;
}
} else if (value == NULL && safe_str_eq(action->task, CRMD_ACTION_STOP)) {
if (is_set(data_set->flags, pe_flag_stonith_enabled)) {
action->on_fail = action_fail_fence;
value = "resource fence (default)";
} else {
action->on_fail = action_fail_block;
value = "resource block (default)";
}
} else if (value == NULL) {
action->on_fail = action_fail_recover;
value = "restart (and possibly migrate) (default)";
}
pe_rsc_trace(action->rsc, "\t%s failure handling: %s", action->task, value);
value = NULL;
if (xml_obj != NULL) {
value = g_hash_table_lookup(action->meta, "role_after_failure");
}
if (value != NULL && action->fail_role == RSC_ROLE_UNKNOWN) {
action->fail_role = text2role(value);
}
/* defaults */
if (action->fail_role == RSC_ROLE_UNKNOWN) {
if (safe_str_eq(action->task, CRMD_ACTION_PROMOTE)) {
action->fail_role = RSC_ROLE_SLAVE;
} else {
action->fail_role = RSC_ROLE_STARTED;
}
}
pe_rsc_trace(action->rsc, "\t%s failure results in: %s", action->task,
role2text(action->fail_role));
field = XML_OP_ATTR_START_DELAY;
value = g_hash_table_lookup(action->meta, XML_OP_ATTR_START_DELAY);
if (value) {
unpack_start_delay(value, action->meta);
} else {
value = g_hash_table_lookup(action->meta, XML_OP_ATTR_ORIGIN);
unpack_interval_origin(value, action->meta, xml_obj, interval, data_set->now);
}
field = XML_ATTR_TIMEOUT;
value = g_hash_table_lookup(action->meta, field);
timeout = unpack_timeout(value, action, xml_obj, interval, data_set->config_hash);
g_hash_table_replace(action->meta, strdup(XML_ATTR_TIMEOUT), crm_itoa(timeout));
- unpack_versioned_meta(action->versioned_meta, xml_obj, interval, data_set->now);
+ unpack_versioned_meta(rsc_details->versioned_meta, xml_obj, interval,
+ data_set->now);
}
static xmlNode *
find_rsc_op_entry_helper(resource_t * rsc, const char *key, gboolean include_disabled)
{
unsigned long long number = 0;
gboolean do_retry = TRUE;
char *local_key = NULL;
const char *name = NULL;
const char *value = NULL;
const char *interval = NULL;
char *match_key = NULL;
xmlNode *op = NULL;
xmlNode *operation = NULL;
retry:
for (operation = __xml_first_child(rsc->ops_xml); operation != NULL;
operation = __xml_next_element(operation)) {
if (crm_str_eq((const char *)operation->name, "op", TRUE)) {
name = crm_element_value(operation, "name");
interval = crm_element_value(operation, XML_LRM_ATTR_INTERVAL);
value = crm_element_value(operation, "enabled");
if (!include_disabled && value && crm_is_true(value) == FALSE) {
continue;
}
number = crm_get_interval(interval);
match_key = generate_op_key(rsc->id, name, number);
if (safe_str_eq(key, match_key)) {
op = operation;
}
free(match_key);
if (rsc->clone_name) {
match_key = generate_op_key(rsc->clone_name, name, number);
if (safe_str_eq(key, match_key)) {
op = operation;
}
free(match_key);
}
if (op != NULL) {
free(local_key);
return op;
}
}
}
free(local_key);
if (do_retry == FALSE) {
return NULL;
}
do_retry = FALSE;
if (strstr(key, CRMD_ACTION_MIGRATE) || strstr(key, CRMD_ACTION_MIGRATED)) {
local_key = generate_op_key(rsc->id, "migrate", 0);
key = local_key;
goto retry;
} else if (strstr(key, "_notify_")) {
local_key = generate_op_key(rsc->id, "notify", 0);
key = local_key;
goto retry;
}
return NULL;
}
xmlNode *
find_rsc_op_entry(resource_t * rsc, const char *key)
{
return find_rsc_op_entry_helper(rsc, key, FALSE);
}
void
print_node(const char *pre_text, node_t * node, gboolean details)
{
if (node == NULL) {
crm_trace("%s%s: <NULL>", pre_text == NULL ? "" : pre_text, pre_text == NULL ? "" : ": ");
return;
}
CRM_ASSERT(node->details);
crm_trace("%s%s%sNode %s: (weight=%d, fixed=%s)",
pre_text == NULL ? "" : pre_text,
pre_text == NULL ? "" : ": ",
node->details->online ? "" : "Unavailable/Unclean ",
node->details->uname, node->weight, node->fixed ? "True" : "False");
if (details) {
char *pe_mutable = strdup("\t\t");
GListPtr gIter = node->details->running_rsc;
crm_trace("\t\t===Node Attributes");
g_hash_table_foreach(node->details->attrs, print_str_str, pe_mutable);
free(pe_mutable);
crm_trace("\t\t=== Resources");
for (; gIter != NULL; gIter = gIter->next) {
resource_t *rsc = (resource_t *) gIter->data;
print_resource(LOG_DEBUG_4, "\t\t", rsc, FALSE);
}
}
}
/*
* Used by the HashTable for-loop
*/
void
print_str_str(gpointer key, gpointer value, gpointer user_data)
{
crm_trace("%s%s %s ==> %s",
user_data == NULL ? "" : (char *)user_data,
user_data == NULL ? "" : ": ", (char *)key, (char *)value);
}
void
print_resource(int log_level, const char *pre_text, resource_t * rsc, gboolean details)
{
long options = pe_print_log | pe_print_pending;
if (rsc == NULL) {
do_crm_log(log_level - 1, "%s%s: <NULL>",
pre_text == NULL ? "" : pre_text, pre_text == NULL ? "" : ": ");
return;
}
if (details) {
options |= pe_print_details;
}
rsc->fns->print(rsc, pre_text, options, &log_level);
}
void
pe_free_action(action_t * action)
{
if (action == NULL) {
return;
}
g_list_free_full(action->actions_before, free); /* action_wrapper_t* */
g_list_free_full(action->actions_after, free); /* action_wrapper_t* */
if (action->extra) {
g_hash_table_destroy(action->extra);
}
if (action->meta) {
g_hash_table_destroy(action->meta);
}
- if (action->versioned_parameters) {
- free_xml(action->versioned_parameters);
- }
- if (action->versioned_meta) {
- free_xml(action->versioned_meta);
+ if (action->rsc) {
+ pe_free_rsc_action_details(action);
}
free(action->cancel_task);
free(action->task);
free(action->uuid);
free(action->node);
free(action);
}
GListPtr
find_recurring_actions(GListPtr input, node_t * not_on_node)
{
const char *value = NULL;
GListPtr result = NULL;
GListPtr gIter = input;
CRM_CHECK(input != NULL, return NULL);
for (; gIter != NULL; gIter = gIter->next) {
action_t *action = (action_t *) gIter->data;
value = g_hash_table_lookup(action->meta, XML_LRM_ATTR_INTERVAL);
if (value == NULL) {
/* skip */
} else if (safe_str_eq(value, "0")) {
/* skip */
} else if (safe_str_eq(CRMD_ACTION_CANCEL, action->task)) {
/* skip */
} else if (not_on_node == NULL) {
crm_trace("(null) Found: %s", action->uuid);
result = g_list_prepend(result, action);
} else if (action->node == NULL) {
/* skip */
} else if (action->node->details != not_on_node->details) {
crm_trace("Found: %s", action->uuid);
result = g_list_prepend(result, action);
}
}
return result;
}
enum action_tasks
get_complex_task(resource_t * rsc, const char *name, gboolean allow_non_atomic)
{
enum action_tasks task = text2task(name);
if (rsc == NULL) {
return task;
} else if (allow_non_atomic == FALSE || rsc->variant == pe_native) {
switch (task) {
case stopped_rsc:
case started_rsc:
case action_demoted:
case action_promoted:
crm_trace("Folding %s back into its atomic counterpart for %s", name, rsc->id);
return task - 1;
break;
default:
break;
}
}
return task;
}
action_t *
find_first_action(GListPtr input, const char *uuid, const char *task, node_t * on_node)
{
GListPtr gIter = NULL;
CRM_CHECK(uuid || task, return NULL);
for (gIter = input; gIter != NULL; gIter = gIter->next) {
action_t *action = (action_t *) gIter->data;
if (uuid != NULL && safe_str_neq(uuid, action->uuid)) {
continue;
} else if (task != NULL && safe_str_neq(task, action->task)) {
continue;
} else if (on_node == NULL) {
return action;
} else if (action->node == NULL) {
continue;
} else if (on_node->details == action->node->details) {
return action;
}
}
return NULL;
}
GListPtr
find_actions(GListPtr input, const char *key, const node_t *on_node)
{
GListPtr gIter = input;
GListPtr result = NULL;
CRM_CHECK(key != NULL, return NULL);
for (; gIter != NULL; gIter = gIter->next) {
action_t *action = (action_t *) gIter->data;
if (safe_str_neq(key, action->uuid)) {
crm_trace("%s does not match action %s", key, action->uuid);
continue;
} else if (on_node == NULL) {
crm_trace("Action %s matches (ignoring node)", key);
result = g_list_prepend(result, action);
} else if (action->node == NULL) {
crm_trace("Action %s matches (unallocated, assigning to %s)",
key, on_node->details->uname);
action->node = node_copy(on_node);
result = g_list_prepend(result, action);
} else if (on_node->details == action->node->details) {
crm_trace("Action %s on %s matches", key, on_node->details->uname);
result = g_list_prepend(result, action);
} else {
crm_trace("Action %s on node %s does not match requested node %s",
key, action->node->details->uname,
on_node->details->uname);
}
}
return result;
}
GListPtr
find_actions_exact(GListPtr input, const char *key, node_t * on_node)
{
GListPtr gIter = input;
GListPtr result = NULL;
CRM_CHECK(key != NULL, return NULL);
for (; gIter != NULL; gIter = gIter->next) {
action_t *action = (action_t *) gIter->data;
crm_trace("Matching %s against %s", key, action->uuid);
if (safe_str_neq(key, action->uuid)) {
crm_trace("Key mismatch: %s vs. %s", key, action->uuid);
continue;
} else if (on_node == NULL || action->node == NULL) {
crm_trace("on_node=%p, action->node=%p", on_node, action->node);
continue;
} else if (safe_str_eq(on_node->details->id, action->node->details->id)) {
result = g_list_prepend(result, action);
}
crm_trace("Node mismatch: %s vs. %s", on_node->details->id, action->node->details->id);
}
return result;
}
static void
resource_node_score(resource_t * rsc, node_t * node, int score, const char *tag)
{
node_t *match = NULL;
if (rsc->children) {
GListPtr gIter = rsc->children;
for (; gIter != NULL; gIter = gIter->next) {
resource_t *child_rsc = (resource_t *) gIter->data;
resource_node_score(child_rsc, node, score, tag);
}
}
pe_rsc_trace(rsc, "Setting %s for %s on %s: %d", tag, rsc->id, node->details->uname, score);
match = pe_hash_table_lookup(rsc->allowed_nodes, node->details->id);
if (match == NULL) {
match = node_copy(node);
g_hash_table_insert(rsc->allowed_nodes, (gpointer) match->details->id, match);
}
match->weight = merge_weights(match->weight, score);
}
void
resource_location(resource_t * rsc, node_t * node, int score, const char *tag,
pe_working_set_t * data_set)
{
if (node != NULL) {
resource_node_score(rsc, node, score, tag);
} else if (data_set != NULL) {
GListPtr gIter = data_set->nodes;
for (; gIter != NULL; gIter = gIter->next) {
node_t *node_iter = (node_t *) gIter->data;
resource_node_score(rsc, node_iter, score, tag);
}
} else {
GHashTableIter iter;
node_t *node_iter = NULL;
g_hash_table_iter_init(&iter, rsc->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (void **)&node_iter)) {
resource_node_score(rsc, node_iter, score, tag);
}
}
if (node == NULL && score == -INFINITY) {
if (rsc->allocated_to) {
crm_info("Deallocating %s from %s", rsc->id, rsc->allocated_to->details->uname);
free(rsc->allocated_to);
rsc->allocated_to = NULL;
}
}
}
#define sort_return(an_int, why) do { \
free(a_uuid); \
free(b_uuid); \
crm_trace("%s (%d) %c %s (%d) : %s", \
a_xml_id, a_call_id, an_int>0?'>':an_int<0?'<':'=', \
b_xml_id, b_call_id, why); \
return an_int; \
} while(0)
gint
sort_op_by_callid(gconstpointer a, gconstpointer b)
{
int a_call_id = -1;
int b_call_id = -1;
char *a_uuid = NULL;
char *b_uuid = NULL;
const xmlNode *xml_a = a;
const xmlNode *xml_b = b;
const char *a_xml_id = crm_element_value_const(xml_a, XML_ATTR_ID);
const char *b_xml_id = crm_element_value_const(xml_b, XML_ATTR_ID);
if (safe_str_eq(a_xml_id, b_xml_id)) {
/* We have duplicate lrm_rsc_op entries in the status
* section which is unliklely to be a good thing
* - we can handle it easily enough, but we need to get
* to the bottom of why it's happening.
*/
pe_err("Duplicate lrm_rsc_op entries named %s", a_xml_id);
sort_return(0, "duplicate");
}
crm_element_value_const_int(xml_a, XML_LRM_ATTR_CALLID, &a_call_id);
crm_element_value_const_int(xml_b, XML_LRM_ATTR_CALLID, &b_call_id);
if (a_call_id == -1 && b_call_id == -1) {
/* both are pending ops so it doesn't matter since
* stops are never pending
*/
sort_return(0, "pending");
} else if (a_call_id >= 0 && a_call_id < b_call_id) {
sort_return(-1, "call id");
} else if (b_call_id >= 0 && a_call_id > b_call_id) {
sort_return(1, "call id");
} else if (b_call_id >= 0 && a_call_id == b_call_id) {
/*
* The op and last_failed_op are the same
* Order on last-rc-change
*/
int last_a = -1;
int last_b = -1;
crm_element_value_const_int(xml_a, XML_RSC_OP_LAST_CHANGE, &last_a);
crm_element_value_const_int(xml_b, XML_RSC_OP_LAST_CHANGE, &last_b);
crm_trace("rc-change: %d vs %d", last_a, last_b);
if (last_a >= 0 && last_a < last_b) {
sort_return(-1, "rc-change");
} else if (last_b >= 0 && last_a > last_b) {
sort_return(1, "rc-change");
}
sort_return(0, "rc-change");
} else {
/* One of the inputs is a pending operation
* Attempt to use XML_ATTR_TRANSITION_MAGIC to determine its age relative to the other
*/
int a_id = -1;
int b_id = -1;
int dummy = -1;
const char *a_magic = crm_element_value_const(xml_a, XML_ATTR_TRANSITION_MAGIC);
const char *b_magic = crm_element_value_const(xml_b, XML_ATTR_TRANSITION_MAGIC);
CRM_CHECK(a_magic != NULL && b_magic != NULL, sort_return(0, "No magic"));
if(!decode_transition_magic(a_magic, &a_uuid, &a_id, &dummy, &dummy, &dummy, &dummy)) {
sort_return(0, "bad magic a");
}
if(!decode_transition_magic(b_magic, &b_uuid, &b_id, &dummy, &dummy, &dummy, &dummy)) {
sort_return(0, "bad magic b");
}
/* try to determine the relative age of the operation...
* some pending operations (ie. a start) may have been superseded
* by a subsequent stop
*
* [a|b]_id == -1 means it's a shutdown operation and _always_ comes last
*/
if (safe_str_neq(a_uuid, b_uuid) || a_id == b_id) {
/*
* some of the logic in here may be redundant...
*
* if the UUID from the TE doesn't match then one better
* be a pending operation.
* pending operations don't survive between elections and joins
* because we query the LRM directly
*/
if (b_call_id == -1) {
sort_return(-1, "transition + call");
} else if (a_call_id == -1) {
sort_return(1, "transition + call");
}
} else if ((a_id >= 0 && a_id < b_id) || b_id == -1) {
sort_return(-1, "transition");
} else if ((b_id >= 0 && a_id > b_id) || a_id == -1) {
sort_return(1, "transition");
}
}
/* we should never end up here */
CRM_CHECK(FALSE, sort_return(0, "default"));
}
time_t
get_effective_time(pe_working_set_t * data_set)
{
if(data_set) {
if (data_set->now == NULL) {
crm_trace("Recording a new 'now'");
data_set->now = crm_time_new(NULL);
}
return crm_time_get_seconds_since_epoch(data_set->now);
}
crm_trace("Defaulting to 'now'");
return time(NULL);
}
gboolean
get_target_role(resource_t * rsc, enum rsc_role_e * role)
{
enum rsc_role_e local_role = RSC_ROLE_UNKNOWN;
const char *value = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET_ROLE);
CRM_CHECK(role != NULL, return FALSE);
if (value == NULL || safe_str_eq("started", value)
|| safe_str_eq("default", value)) {
return FALSE;
}
local_role = text2role(value);
if (local_role == RSC_ROLE_UNKNOWN) {
crm_config_err("%s: Unknown value for %s: %s", rsc->id, XML_RSC_ATTR_TARGET_ROLE, value);
return FALSE;
} else if (local_role > RSC_ROLE_STARTED) {
if (uber_parent(rsc)->variant == pe_master) {
if (local_role > RSC_ROLE_SLAVE) {
/* This is what we'd do anyway, just leave the default to avoid messing up the placement algorithm */
return FALSE;
}
} else {
crm_config_err("%s is not part of a master/slave resource, a %s of '%s' makes no sense",
rsc->id, XML_RSC_ATTR_TARGET_ROLE, value);
return FALSE;
}
}
*role = local_role;
return TRUE;
}
gboolean
order_actions(action_t * lh_action, action_t * rh_action, enum pe_ordering order)
{
GListPtr gIter = NULL;
action_wrapper_t *wrapper = NULL;
GListPtr list = NULL;
if (order == pe_order_none) {
return FALSE;
}
if (lh_action == NULL || rh_action == NULL) {
return FALSE;
}
crm_trace("Ordering Action %s before %s", lh_action->uuid, rh_action->uuid);
/* Ensure we never create a dependency on ourselves... it's happened */
CRM_ASSERT(lh_action != rh_action);
/* Filter dups, otherwise update_action_states() has too much work to do */
gIter = lh_action->actions_after;
for (; gIter != NULL; gIter = gIter->next) {
action_wrapper_t *after = (action_wrapper_t *) gIter->data;
if (after->action == rh_action && (after->type & order)) {
return FALSE;
}
}
wrapper = calloc(1, sizeof(action_wrapper_t));
wrapper->action = rh_action;
wrapper->type = order;
list = lh_action->actions_after;
list = g_list_prepend(list, wrapper);
lh_action->actions_after = list;
wrapper = NULL;
/* order |= pe_order_implies_then; */
/* order ^= pe_order_implies_then; */
wrapper = calloc(1, sizeof(action_wrapper_t));
wrapper->action = lh_action;
wrapper->type = order;
list = rh_action->actions_before;
list = g_list_prepend(list, wrapper);
rh_action->actions_before = list;
return TRUE;
}
action_t *
get_pseudo_op(const char *name, pe_working_set_t * data_set)
{
action_t *op = NULL;
if(data_set->singletons) {
op = g_hash_table_lookup(data_set->singletons, name);
}
if (op == NULL) {
op = custom_action(NULL, strdup(name), name, NULL, TRUE, TRUE, data_set);
set_bit(op->flags, pe_action_pseudo);
set_bit(op->flags, pe_action_runnable);
}
return op;
}
void
destroy_ticket(gpointer data)
{
ticket_t *ticket = data;
if (ticket->state) {
g_hash_table_destroy(ticket->state);
}
free(ticket->id);
free(ticket);
}
ticket_t *
ticket_new(const char *ticket_id, pe_working_set_t * data_set)
{
ticket_t *ticket = NULL;
if (ticket_id == NULL || strlen(ticket_id) == 0) {
return NULL;
}
if (data_set->tickets == NULL) {
data_set->tickets =
g_hash_table_new_full(crm_str_hash, g_str_equal, g_hash_destroy_str, destroy_ticket);
}
ticket = g_hash_table_lookup(data_set->tickets, ticket_id);
if (ticket == NULL) {
ticket = calloc(1, sizeof(ticket_t));
if (ticket == NULL) {
crm_err("Cannot allocate ticket '%s'", ticket_id);
return NULL;
}
crm_trace("Creaing ticket entry for %s", ticket_id);
ticket->id = strdup(ticket_id);
ticket->granted = FALSE;
ticket->last_granted = -1;
ticket->standby = FALSE;
ticket->state = crm_str_table_new();
g_hash_table_insert(data_set->tickets, strdup(ticket->id), ticket);
}
return ticket;
}
static void
filter_parameters(xmlNode * param_set, const char *param_string, bool need_present)
{
int len = 0;
char *name = NULL;
char *match = NULL;
if (param_set == NULL) {
return;
}
if (param_set) {
xmlAttrPtr xIter = param_set->properties;
while (xIter) {
const char *prop_name = (const char *)xIter->name;
xIter = xIter->next;
name = NULL;
len = strlen(prop_name) + 3;
name = malloc(len);
if(name) {
sprintf(name, " %s ", prop_name);
name[len - 1] = 0;
match = strstr(param_string, name);
}
if (need_present && match == NULL) {
crm_trace("%s not found in %s", prop_name, param_string);
xml_remove_prop(param_set, prop_name);
} else if (need_present == FALSE && match) {
crm_trace("%s found in %s", prop_name, param_string);
xml_remove_prop(param_set, prop_name);
}
free(name);
}
}
}
bool fix_remote_addr(resource_t * rsc)
{
const char *name;
const char *value;
const char *attr_list[] = {
XML_ATTR_TYPE,
XML_AGENT_ATTR_CLASS,
XML_AGENT_ATTR_PROVIDER
};
const char *value_list[] = {
"remote",
"ocf",
"pacemaker"
};
if(rsc == NULL) {
return FALSE;
}
name = "addr";
value = g_hash_table_lookup(rsc->parameters, name);
if (safe_str_eq(value, "#uname") == FALSE) {
return FALSE;
}
for (int lpc = 0; lpc < DIMOF(attr_list); lpc++) {
name = attr_list[lpc];
value = crm_element_value(rsc->xml, attr_list[lpc]);
if (safe_str_eq(value, value_list[lpc]) == FALSE) {
return FALSE;
}
}
return TRUE;
}
static void
append_versioned_params(xmlNode *versioned_params, const char *ra_version, xmlNode *params)
{
GHashTable *hash = pe_unpack_versioned_parameters(versioned_params, ra_version);
char *key = NULL;
char *value = NULL;
GHashTableIter iter;
g_hash_table_iter_init(&iter, hash);
while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value)) {
crm_xml_add(params, key, value);
}
g_hash_table_destroy(hash);
}
op_digest_cache_t *
rsc_action_digest_cmp(resource_t * rsc, xmlNode * xml_op, node_t * node,
pe_working_set_t * data_set)
{
op_digest_cache_t *data = NULL;
GHashTable *local_rsc_params = NULL;
xmlNode *local_versioned_params = NULL;
action_t *action = NULL;
char *key = NULL;
int interval = 0;
const char *op_id = ID(xml_op);
const char *interval_s = crm_element_value(xml_op, XML_LRM_ATTR_INTERVAL);
const char *task = crm_element_value(xml_op, XML_LRM_ATTR_TASK);
const char *digest_all;
const char *digest_restart;
const char *secure_list;
const char *restart_list;
const char *op_version;
const char *ra_version;
CRM_ASSERT(node != NULL);
data = g_hash_table_lookup(node->details->digest_cache, op_id);
if (data) {
return data;
}
data = calloc(1, sizeof(op_digest_cache_t));
CRM_ASSERT(data != NULL);
digest_all = crm_element_value(xml_op, XML_LRM_ATTR_OP_DIGEST);
digest_restart = crm_element_value(xml_op, XML_LRM_ATTR_RESTART_DIGEST);
secure_list = crm_element_value(xml_op, XML_LRM_ATTR_OP_SECURE);
restart_list = crm_element_value(xml_op, XML_LRM_ATTR_OP_RESTART);
op_version = crm_element_value(xml_op, XML_ATTR_CRM_VERSION);
ra_version = crm_element_value(xml_op, XML_ATTR_RA_VERSION);
/* key is freed in custom_action */
interval = crm_parse_int(interval_s, "0");
key = generate_op_key(rsc->id, task, interval);
action = custom_action(rsc, key, task, node, TRUE, FALSE, data_set);
key = NULL;
local_rsc_params = crm_str_table_new();
get_rsc_attributes(local_rsc_params, rsc, node, data_set);
local_versioned_params = create_xml_node(NULL, XML_TAG_RSC_VER_ATTRS);
pe_get_versioned_attributes(local_versioned_params, rsc, node, data_set);
data->params_all = create_xml_node(NULL, XML_TAG_PARAMS);
if (fix_remote_addr(rsc)) {
// REMOTE_CONTAINER_HACK: Allow remote nodes that start containers with pacemaker remote inside
crm_xml_add(data->params_all, "addr", node->details->uname);
crm_trace("Fixing addr for %s on %s", rsc->id, node->details->uname);
}
g_hash_table_foreach(local_rsc_params, hash2field, data->params_all);
g_hash_table_foreach(action->extra, hash2field, data->params_all);
g_hash_table_foreach(rsc->parameters, hash2field, data->params_all);
g_hash_table_foreach(action->meta, hash2metafield, data->params_all);
append_versioned_params(local_versioned_params, ra_version, data->params_all);
append_versioned_params(rsc->versioned_parameters, ra_version, data->params_all);
- append_versioned_params(action->versioned_parameters, ra_version, data->params_all);
+ {
+ pe_rsc_action_details_t *details = pe_rsc_action_details(action);
+ append_versioned_params(details->versioned_parameters, ra_version,
+ data->params_all);
+ }
filter_action_parameters(data->params_all, op_version);
data->digest_all_calc = calculate_operation_digest(data->params_all, op_version);
if (secure_list && is_set(data_set->flags, pe_flag_sanitized)) {
data->params_secure = copy_xml(data->params_all);
if (secure_list) {
filter_parameters(data->params_secure, secure_list, FALSE);
}
data->digest_secure_calc = calculate_operation_digest(data->params_secure, op_version);
}
if (digest_restart) {
data->params_restart = copy_xml(data->params_all);
if (restart_list) {
filter_parameters(data->params_restart, restart_list, TRUE);
}
data->digest_restart_calc = calculate_operation_digest(data->params_restart, op_version);
}
data->rc = RSC_DIGEST_MATCH;
if (digest_restart && strcmp(data->digest_restart_calc, digest_restart) != 0) {
data->rc = RSC_DIGEST_RESTART;
} else if (digest_all == NULL) {
/* it is unknown what the previous op digest was */
data->rc = RSC_DIGEST_UNKNOWN;
} else if (strcmp(digest_all, data->digest_all_calc) != 0) {
data->rc = RSC_DIGEST_ALL;
}
g_hash_table_insert(node->details->digest_cache, strdup(op_id), data);
g_hash_table_destroy(local_rsc_params);
free_xml(local_versioned_params);
pe_free_action(action);
return data;
}
const char *rsc_printable_id(resource_t *rsc)
{
if (is_not_set(rsc->flags, pe_rsc_unique)) {
return ID(rsc->xml);
}
return rsc->id;
}
void
clear_bit_recursive(resource_t * rsc, unsigned long long flag)
{
GListPtr gIter = rsc->children;
clear_bit(rsc->flags, flag);
for (; gIter != NULL; gIter = gIter->next) {
resource_t *child_rsc = (resource_t *) gIter->data;
clear_bit_recursive(child_rsc, flag);
}
}
void
set_bit_recursive(resource_t * rsc, unsigned long long flag)
{
GListPtr gIter = rsc->children;
set_bit(rsc->flags, flag);
for (; gIter != NULL; gIter = gIter->next) {
resource_t *child_rsc = (resource_t *) gIter->data;
set_bit_recursive(child_rsc, flag);
}
}
action_t *
pe_fence_op(node_t * node, const char *op, bool optional, pe_working_set_t * data_set)
{
char *key = NULL;
action_t *stonith_op = NULL;
if(op == NULL) {
op = data_set->stonith_action;
}
key = crm_strdup_printf("%s-%s-%s", CRM_OP_FENCE, node->details->uname, op);
if(data_set->singletons) {
stonith_op = g_hash_table_lookup(data_set->singletons, key);
}
if(stonith_op == NULL) {
stonith_op = custom_action(NULL, key, CRM_OP_FENCE, node, optional, TRUE, data_set);
add_hash_param(stonith_op->meta, XML_LRM_ATTR_TARGET, node->details->uname);
add_hash_param(stonith_op->meta, XML_LRM_ATTR_TARGET_UUID, node->details->id);
add_hash_param(stonith_op->meta, "stonith_action", op);
} else {
free(key);
}
if(optional == FALSE) {
crm_trace("%s is no longer optional", stonith_op->uuid);
pe_clear_action_bit(stonith_op, pe_action_optional);
}
return stonith_op;
}
void
trigger_unfencing(
resource_t * rsc, node_t *node, const char *reason, action_t *dependency, pe_working_set_t * data_set)
{
if(is_not_set(data_set->flags, pe_flag_enable_unfencing)) {
/* No resources require it */
return;
} else if (rsc != NULL && is_not_set(rsc->flags, pe_rsc_fence_device)) {
/* Wasn't a stonith device */
return;
} else if(node
&& node->details->online
&& node->details->unclean == FALSE
&& node->details->shutdown == FALSE) {
action_t *unfence = pe_fence_op(node, "on", FALSE, data_set);
crm_notice("Unfencing %s: %s", node->details->uname, reason);
if(dependency) {
order_actions(unfence, dependency, pe_order_optional);
}
} else if(rsc) {
GHashTableIter iter;
g_hash_table_iter_init(&iter, rsc->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
if(node->details->online && node->details->unclean == FALSE && node->details->shutdown == FALSE) {
trigger_unfencing(rsc, node, reason, dependency, data_set);
}
}
}
}
gboolean
add_tag_ref(GHashTable * tags, const char * tag_name, const char * obj_ref)
{
tag_t *tag = NULL;
GListPtr gIter = NULL;
gboolean is_existing = FALSE;
CRM_CHECK(tags && tag_name && obj_ref, return FALSE);
tag = g_hash_table_lookup(tags, tag_name);
if (tag == NULL) {
tag = calloc(1, sizeof(tag_t));
if (tag == NULL) {
return FALSE;
}
tag->id = strdup(tag_name);
tag->refs = NULL;
g_hash_table_insert(tags, strdup(tag_name), tag);
}
for (gIter = tag->refs; gIter != NULL; gIter = gIter->next) {
const char *existing_ref = (const char *) gIter->data;
if (crm_str_eq(existing_ref, obj_ref, TRUE)){
is_existing = TRUE;
break;
}
}
if (is_existing == FALSE) {
tag->refs = g_list_append(tag->refs, strdup(obj_ref));
crm_trace("Added: tag=%s ref=%s", tag->id, obj_ref);
}
return TRUE;
}
diff --git a/lib/services/services.c b/lib/services/services.c
index bcaa705d32..8cf2e5a513 100644
--- a/lib/services/services.c
+++ b/lib/services/services.c
@@ -1,1054 +1,1451 @@
/*
* Copyright (C) 2010-2016 Andrew Beekhof <andrew@beekhof.net>
*
* 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 <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <dirent.h>
#include <fcntl.h>
#include <crm/crm.h>
#include <crm/common/mainloop.h>
#include <crm/services.h>
#include <crm/msg_xml.h>
#include "services_private.h"
#if SUPPORT_UPSTART
# include <upstart.h>
#endif
#if SUPPORT_SYSTEMD
# include <systemd.h>
#endif
/* TODO: Develop a rollover strategy */
static int operations = 0;
static GHashTable *recurring_actions = NULL;
/* ops waiting to run async because of conflicting active
* pending ops */
static GList *blocked_ops = NULL;
/* ops currently active (in-flight) */
static GList *inflight_ops = NULL;
static void handle_blocked_ops(void);
svc_action_t *
services_action_create(const char *name, const char *action, int interval, int timeout)
{
return resources_action_create(name, PCMK_RESOURCE_CLASS_LSB, NULL, name,
action, interval, timeout, NULL, 0);
}
const char *
resources_find_service_class(const char *agent)
{
/* Priority is:
* - lsb
* - systemd
* - upstart
*/
int rc = 0;
struct stat st;
char *path = NULL;
#ifdef LSB_ROOT_DIR
rc = asprintf(&path, "%s/%s", LSB_ROOT_DIR, agent);
if (rc > 0 && stat(path, &st) == 0) {
free(path);
return PCMK_RESOURCE_CLASS_LSB;
}
free(path);
#endif
#if SUPPORT_SYSTEMD
if (systemd_unit_exists(agent)) {
return PCMK_RESOURCE_CLASS_SYSTEMD;
}
#endif
#if SUPPORT_UPSTART
if (upstart_job_exists(agent)) {
return PCMK_RESOURCE_CLASS_UPSTART;
}
#endif
return NULL;
}
static inline void
init_recurring_actions(void)
{
if (recurring_actions == NULL) {
recurring_actions = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
NULL);
}
}
/*!
* \internal
* \brief Check whether op is in-flight systemd or upstart op
*
* \param[in] op Operation to check
*
* \return TRUE if op is in-flight systemd or upstart op
*/
static inline gboolean
inflight_systemd_or_upstart(svc_action_t *op)
{
return (safe_str_eq(op->standard, PCMK_RESOURCE_CLASS_SYSTEMD)
|| safe_str_eq(op->standard, PCMK_RESOURCE_CLASS_UPSTART))
&& (g_list_find(inflight_ops, op) != NULL);
}
/*!
* \internal
* \brief Expand "service" alias to an actual resource class
*
* \param[in] rsc Resource name (for logging only)
* \param[in] standard Resource class as configured
* \param[in] agent Agent name to look for
*
* \return Newly allocated string with actual resource class
*
* \note The caller is responsible for calling free() on the result.
*/
static char *
expand_resource_class(const char *rsc, const char *standard, const char *agent)
{
char *expanded_class = NULL;
if (strcasecmp(standard, PCMK_RESOURCE_CLASS_SERVICE) == 0) {
const char *found_class = resources_find_service_class(agent);
if (found_class) {
crm_debug("Found %s agent %s for %s", found_class, agent, rsc);
expanded_class = strdup(found_class);
} else {
crm_info("Assuming resource class lsb for agent %s for %s",
agent, rsc);
expanded_class = strdup(PCMK_RESOURCE_CLASS_LSB);
}
} else {
expanded_class = strdup(standard);
}
CRM_ASSERT(expanded_class);
return expanded_class;
}
svc_action_t *
resources_action_create(const char *name, const char *standard, const char *provider,
const char *agent, const char *action, int interval, int timeout,
GHashTable * params, enum svc_action_flags flags)
{
svc_action_t *op = NULL;
/*
* Do some up front sanity checks before we go off and
* build the svc_action_t instance.
*/
if (crm_strlen_zero(name)) {
crm_err("Cannot create operation without resource name");
goto return_error;
}
if (crm_strlen_zero(standard)) {
crm_err("Cannot create operation for %s without resource class", name);
goto return_error;
}
if (!strcasecmp(standard, PCMK_RESOURCE_CLASS_OCF)
&& crm_strlen_zero(provider)) {
crm_err("Cannot create OCF operation for %s without provider", name);
goto return_error;
}
if (crm_strlen_zero(agent)) {
crm_err("Cannot create operation for %s without agent name", name);
goto return_error;
}
if (crm_strlen_zero(action)) {
crm_err("Cannot create operation for %s without operation name", name);
goto return_error;
}
/*
* Sanity checks passed, proceed!
*/
op = calloc(1, sizeof(svc_action_t));
op->opaque = calloc(1, sizeof(svc_action_private_t));
op->rsc = strdup(name);
op->interval = interval;
op->timeout = timeout;
op->standard = expand_resource_class(name, standard, agent);
op->agent = strdup(agent);
op->sequence = ++operations;
op->flags = flags;
op->id = generate_op_key(name, action, interval);
if (safe_str_eq(action, "monitor") && (
#if SUPPORT_HEARTBEAT
safe_str_eq(op->standard, PCMK_RESOURCE_CLASS_HB) ||
#endif
safe_str_eq(op->standard, PCMK_RESOURCE_CLASS_LSB))) {
action = "status";
}
op->action = strdup(action);
if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_OCF) == 0) {
op->provider = strdup(provider);
op->params = params;
params = NULL;
if (asprintf(&op->opaque->exec, "%s/resource.d/%s/%s", OCF_ROOT_DIR, provider, agent) == -1) {
crm_err("Internal error: cannot create agent path");
goto return_error;
}
op->opaque->args[0] = strdup(op->opaque->exec);
op->opaque->args[1] = strdup(action);
} else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_LSB) == 0) {
if (op->agent[0] == '/') {
/* if given an absolute path, use that instead
* of tacking on the LSB_ROOT_DIR path to the front */
op->opaque->exec = strdup(op->agent);
} else if (asprintf(&op->opaque->exec, "%s/%s", LSB_ROOT_DIR, op->agent) == -1) {
crm_err("Internal error: cannot create agent path");
goto return_error;
}
op->opaque->args[0] = strdup(op->opaque->exec);
op->opaque->args[1] = strdup(op->action);
op->opaque->args[2] = NULL;
#if SUPPORT_HEARTBEAT
} else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_HB) == 0) {
int index;
int param_num;
char buf_tmp[20];
void *value_tmp;
if (op->agent[0] == '/') {
/* if given an absolute path, use that instead
* of tacking on the HB_RA_DIR path to the front */
op->opaque->exec = strdup(op->agent);
} else if (asprintf(&op->opaque->exec, "%s/%s", HB_RA_DIR, op->agent) == -1) {
crm_err("Internal error: cannot create agent path");
goto return_error;
}
op->opaque->args[0] = strdup(op->opaque->exec);
/* The "heartbeat" agent class only has positional arguments,
* which we keyed by their decimal position number. */
param_num = 1;
for (index = 1; index <= MAX_ARGC - 3; index++ ) {
snprintf(buf_tmp, sizeof(buf_tmp), "%d", index);
value_tmp = g_hash_table_lookup(params, buf_tmp);
if (value_tmp == NULL) {
/* maybe: strdup("") ??
* But the old lrmd did simply continue as well. */
continue;
}
op->opaque->args[param_num++] = strdup(value_tmp);
}
/* Add operation code as the last argument, */
/* and the teminating NULL pointer */
op->opaque->args[param_num++] = strdup(op->action);
op->opaque->args[param_num] = NULL;
#endif
#if SUPPORT_SYSTEMD
} else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_SYSTEMD) == 0) {
op->opaque->exec = strdup("systemd-dbus");
#endif
#if SUPPORT_UPSTART
} else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_UPSTART) == 0) {
op->opaque->exec = strdup("upstart-dbus");
#endif
#if SUPPORT_NAGIOS
} else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_NAGIOS) == 0) {
int index = 0;
if (op->agent[0] == '/') {
/* if given an absolute path, use that instead
* of tacking on the NAGIOS_PLUGIN_DIR path to the front */
op->opaque->exec = strdup(op->agent);
} else if (asprintf(&op->opaque->exec, "%s/%s", NAGIOS_PLUGIN_DIR, op->agent) == -1) {
crm_err("Internal error: cannot create agent path");
goto return_error;
}
op->opaque->args[0] = strdup(op->opaque->exec);
index = 1;
if (safe_str_eq(op->action, "monitor") && op->interval == 0) {
/* Invoke --version for a nagios probe */
op->opaque->args[index] = strdup("--version");
index++;
} else if (params) {
GHashTableIter iter;
char *key = NULL;
char *value = NULL;
static int args_size = sizeof(op->opaque->args) / sizeof(char *);
g_hash_table_iter_init(&iter, params);
while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value) &&
index <= args_size - 3) {
int len = 3;
char *long_opt = NULL;
if (safe_str_eq(key, XML_ATTR_CRM_VERSION) || strstr(key, CRM_META "_")) {
continue;
}
len += strlen(key);
long_opt = calloc(1, len);
sprintf(long_opt, "--%s", key);
long_opt[len - 1] = 0;
op->opaque->args[index] = long_opt;
op->opaque->args[index + 1] = strdup(value);
index += 2;
}
}
op->opaque->args[index] = NULL;
#endif
} else {
crm_err("Unknown resource standard: %s", op->standard);
services_action_free(op);
op = NULL;
}
if(params) {
g_hash_table_destroy(params);
}
return op;
return_error:
if(params) {
g_hash_table_destroy(params);
}
services_action_free(op);
return NULL;
}
svc_action_t *
services_action_create_generic(const char *exec, const char *args[])
{
svc_action_t *op;
unsigned int cur_arg;
op = calloc(1, sizeof(*op));
op->opaque = calloc(1, sizeof(svc_action_private_t));
op->opaque->exec = strdup(exec);
op->opaque->args[0] = strdup(exec);
for (cur_arg = 1; args && args[cur_arg - 1]; cur_arg++) {
op->opaque->args[cur_arg] = strdup(args[cur_arg - 1]);
if (cur_arg == DIMOF(op->opaque->args) - 1) {
crm_err("svc_action_t args list not long enough for '%s' execution request.", exec);
break;
}
}
return op;
}
/*!
* \brief Create an alert agent action
*
* \param[in] id Alert ID
* \param[in] exec Path to alert agent executable
* \param[in] timeout Action timeout
* \param[in] params Parameters to use with action
* \param[in] sequence Action sequence number
* \param[in] cb_data Data to pass to callback function
*
* \return New action on success, NULL on error
* \note It is the caller's responsibility to free cb_data.
* The caller should not free params explicitly.
*/
svc_action_t *
services_alert_create(const char *id, const char *exec, int timeout,
GHashTable *params, int sequence, void *cb_data)
{
svc_action_t *action = services_action_create_generic(exec, NULL);
CRM_ASSERT(action);
action->timeout = timeout;
action->id = strdup(id);
action->params = params;
action->sequence = sequence;
action->cb_data = cb_data;
return action;
}
/*!
* \brief Set the user and group that an action will execute as
*
* \param[in,out] action Action to modify
* \param[in] user Name of user to execute action as
* \param[in] group Name of group to execute action as
*
* \return pcmk_ok on success, -errno otherwise
*
* \note This will have no effect unless the process executing the action runs
* as root, and the action is not a systemd or upstart action.
* We could implement this for systemd by adding User= and Group= to
* [Service] in the override file, but that seems more likely to cause
* problems than be useful.
*/
int
services_action_user(svc_action_t *op, const char *user)
{
CRM_CHECK((op != NULL) && (user != NULL), return -EINVAL);
return crm_user_lookup(user, &(op->opaque->uid), &(op->opaque->gid));
}
static void
set_alert_env(gpointer key, gpointer value, gpointer user_data)
{
int rc;
if (value) {
rc = setenv(key, value, 1);
} else {
rc = unsetenv(key);
}
if (rc < 0) {
crm_perror(LOG_ERR, "setenv %s=%s",
(char*)key, (value? (char*)value : ""));
} else {
crm_trace("setenv %s=%s", (char*)key, (value? (char*)value : ""));
}
}
static void
unset_alert_env(gpointer key, gpointer value, gpointer user_data)
{
if (unsetenv(key) < 0) {
crm_perror(LOG_ERR, "unset %s", (char*)key);
} else {
crm_trace("unset %s", (char*)key);
}
}
/*!
* \brief Execute an alert agent action
*
* \param[in] action Action to execute
* \param[in] cb Function to call when action completes
*
* \return TRUE if the library will free action, FALSE otherwise
*
* \note If this function returns FALSE, it is the caller's responsibility to
* free the action with services_action_free().
*/
gboolean
services_alert_async(svc_action_t *action, void (*cb)(svc_action_t *op))
{
gboolean responsible;
action->synchronous = false;
action->opaque->callback = cb;
if (action->params) {
g_hash_table_foreach(action->params, set_alert_env, NULL);
}
responsible = services_os_action_execute(action);
if (action->params) {
g_hash_table_foreach(action->params, unset_alert_env, NULL);
}
return responsible;
}
#if SUPPORT_DBUS
/*!
* \internal
* \brief Update operation's pending DBus call, unreferencing old one if needed
*
* \param[in,out] op Operation to modify
* \param[in] pending Pending call to set
*/
void
services_set_op_pending(svc_action_t *op, DBusPendingCall *pending)
{
if (op->opaque->pending && (op->opaque->pending != pending)) {
if (pending) {
crm_info("Lost pending %s DBus call (%p)", op->id, op->opaque->pending);
} else {
crm_trace("Done with pending %s DBus call (%p)", op->id, op->opaque->pending);
}
dbus_pending_call_unref(op->opaque->pending);
}
op->opaque->pending = pending;
if (pending) {
crm_trace("Updated pending %s DBus call (%p)", op->id, pending);
} else {
crm_trace("Cleared pending %s DBus call", op->id);
}
}
#endif
void
services_action_cleanup(svc_action_t * op)
{
if(op->opaque == NULL) {
return;
}
#if SUPPORT_DBUS
if(op->opaque->timerid != 0) {
crm_trace("Removing timer for call %s to %s", op->action, op->rsc);
g_source_remove(op->opaque->timerid);
op->opaque->timerid = 0;
}
if(op->opaque->pending) {
crm_trace("Cleaning up pending dbus call %p %s for %s", op->opaque->pending, op->action, op->rsc);
if(dbus_pending_call_get_completed(op->opaque->pending)) {
crm_warn("Pending dbus call %s for %s did not complete", op->action, op->rsc);
}
dbus_pending_call_cancel(op->opaque->pending);
dbus_pending_call_unref(op->opaque->pending);
op->opaque->pending = NULL;
}
#endif
if (op->opaque->stderr_gsource) {
mainloop_del_fd(op->opaque->stderr_gsource);
op->opaque->stderr_gsource = NULL;
}
if (op->opaque->stdout_gsource) {
mainloop_del_fd(op->opaque->stdout_gsource);
op->opaque->stdout_gsource = NULL;
}
}
void
services_action_free(svc_action_t * op)
{
unsigned int i;
if (op == NULL) {
return;
}
/* The operation should be removed from all tracking lists by this point.
* If it's not, we have a bug somewhere, so bail. That may lead to a
* memory leak, but it's better than a use-after-free segmentation fault.
*/
CRM_CHECK(g_list_find(inflight_ops, op) == NULL, return);
CRM_CHECK(g_list_find(blocked_ops, op) == NULL, return);
CRM_CHECK((recurring_actions == NULL)
|| (g_hash_table_lookup(recurring_actions, op->id) == NULL),
return);
services_action_cleanup(op);
if (op->opaque->repeat_timer) {
g_source_remove(op->opaque->repeat_timer);
op->opaque->repeat_timer = 0;
}
free(op->id);
free(op->opaque->exec);
for (i = 0; i < DIMOF(op->opaque->args); i++) {
free(op->opaque->args[i]);
}
free(op->opaque);
free(op->rsc);
free(op->action);
free(op->standard);
free(op->agent);
free(op->provider);
free(op->stdout_data);
free(op->stderr_data);
if (op->params) {
g_hash_table_destroy(op->params);
op->params = NULL;
}
free(op);
}
gboolean
cancel_recurring_action(svc_action_t * op)
{
crm_info("Cancelling %s operation %s", op->standard, op->id);
if (recurring_actions) {
g_hash_table_remove(recurring_actions, op->id);
}
if (op->opaque->repeat_timer) {
g_source_remove(op->opaque->repeat_timer);
op->opaque->repeat_timer = 0;
}
return TRUE;
}
/*!
* \brief Cancel a recurring action
*
* \param[in] name Name of resource that operation is for
* \param[in] action Name of operation to cancel
* \param[in] interval Interval of operation to cancel
*
* \return TRUE if action was successfully cancelled, FALSE otherwise
*/
gboolean
services_action_cancel(const char *name, const char *action, int interval)
{
gboolean cancelled = FALSE;
char *id = generate_op_key(name, action, interval);
svc_action_t *op = NULL;
/* We can only cancel a recurring action */
init_recurring_actions();
op = g_hash_table_lookup(recurring_actions, id);
if (op == NULL) {
goto done;
}
/* Tell operation_finalize() not to reschedule the operation */
op->cancel = TRUE;
/* Stop tracking it as a recurring operation, and stop its timer */
cancel_recurring_action(op);
/* If the op has a PID, it's an in-flight child process, so kill it.
*
* Whether the kill succeeds or fails, the main loop will send the op to
* operation_finished() (and thus operation_finalize()) when the process
* goes away.
*/
if (op->pid != 0) {
crm_info("Terminating in-flight op %s (pid %d) early because it was cancelled",
id, op->pid);
cancelled = mainloop_child_kill(op->pid);
if (cancelled == FALSE) {
crm_err("Termination of %s (pid %d) failed", id, op->pid);
}
goto done;
}
/* In-flight systemd and upstart ops don't have a pid. The relevant handlers
* will call operation_finalize() when the operation completes.
* @TODO: Can we request early termination, maybe using
* dbus_pending_call_cancel()?
*/
if (inflight_systemd_or_upstart(op)) {
crm_info("Will cancel %s op %s when in-flight instance completes",
op->standard, op->id);
cancelled = FALSE;
goto done;
}
/* Otherwise, operation is not in-flight, just report as cancelled */
op->status = PCMK_LRM_OP_CANCELLED;
if (op->opaque->callback) {
op->opaque->callback(op);
}
blocked_ops = g_list_remove(blocked_ops, op);
services_action_free(op);
cancelled = TRUE;
done:
free(id);
return cancelled;
}
gboolean
services_action_kick(const char *name, const char *action, int interval /* ms */)
{
svc_action_t * op = NULL;
char *id = generate_op_key(name, action, interval);
init_recurring_actions();
op = g_hash_table_lookup(recurring_actions, id);
free(id);
if (op == NULL) {
return FALSE;
}
if (op->pid || inflight_systemd_or_upstart(op)) {
return TRUE;
} else {
if (op->opaque->repeat_timer) {
g_source_remove(op->opaque->repeat_timer);
op->opaque->repeat_timer = 0;
}
recurring_action_timer(op);
return TRUE;
}
}
/*!
* \internal
* \brief Add a new recurring operation, checking for duplicates
*
* \param[in] op Operation to add
*
* \return TRUE if duplicate found (and reschedule), FALSE otherwise
*/
static gboolean
handle_duplicate_recurring(svc_action_t * op)
{
svc_action_t * dup = NULL;
/* check for duplicates */
dup = g_hash_table_lookup(recurring_actions, op->id);
if (dup && (dup != op)) {
/* update user data */
if (op->opaque->callback) {
dup->opaque->callback = op->opaque->callback;
dup->cb_data = op->cb_data;
op->cb_data = NULL;
}
/* immediately execute the next interval */
if (dup->pid != 0) {
if (op->opaque->repeat_timer) {
g_source_remove(op->opaque->repeat_timer);
op->opaque->repeat_timer = 0;
}
recurring_action_timer(dup);
}
/* free the duplicate */
services_action_free(op);
return TRUE;
}
return FALSE;
}
inline static gboolean
action_exec_helper(svc_action_t * op)
{
/* Whether a/synchronous must be decided (op->synchronous) beforehand. */
if (op->standard
&& (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_UPSTART) == 0)) {
#if SUPPORT_UPSTART
return upstart_job_exec(op);
#endif
} else if (op->standard && strcasecmp(op->standard,
PCMK_RESOURCE_CLASS_SYSTEMD) == 0) {
#if SUPPORT_SYSTEMD
return systemd_unit_exec(op);
#endif
} else {
return services_os_action_execute(op);
}
/* The 'op' has probably been freed if the execution functions return TRUE
for the asynchronous 'op'. */
/* Avoid using the 'op' in here. */
return FALSE;
}
void
services_add_inflight_op(svc_action_t * op)
{
if (op == NULL) {
return;
}
CRM_ASSERT(op->synchronous == FALSE);
/* keep track of ops that are in-flight to avoid collisions in the same namespace */
if (op->rsc) {
inflight_ops = g_list_append(inflight_ops, op);
}
}
/*!
* \internal
* \brief Stop tracking an operation that completed
*
* \param[in] op Operation to stop tracking
*/
void
services_untrack_op(svc_action_t *op)
{
/* Op is no longer in-flight or blocked */
inflight_ops = g_list_remove(inflight_ops, op);
blocked_ops = g_list_remove(blocked_ops, op);
/* Op is no longer blocking other ops, so check if any need to run */
handle_blocked_ops();
}
gboolean
services_action_async(svc_action_t * op, void (*action_callback) (svc_action_t *))
{
op->synchronous = false;
if (action_callback) {
op->opaque->callback = action_callback;
}
if (op->interval > 0) {
init_recurring_actions();
if (handle_duplicate_recurring(op) == TRUE) {
/* entry rescheduled, dup freed */
/* exit early */
return TRUE;
}
g_hash_table_replace(recurring_actions, op->id, op);
}
if (op->rsc && is_op_blocked(op->rsc)) {
blocked_ops = g_list_append(blocked_ops, op);
return TRUE;
}
return action_exec_helper(op);
}
static gboolean processing_blocked_ops = FALSE;
gboolean
is_op_blocked(const char *rsc)
{
GList *gIter = NULL;
svc_action_t *op = NULL;
for (gIter = inflight_ops; gIter != NULL; gIter = gIter->next) {
op = gIter->data;
if (safe_str_eq(op->rsc, rsc)) {
return TRUE;
}
}
return FALSE;
}
static void
handle_blocked_ops(void)
{
GList *executed_ops = NULL;
GList *gIter = NULL;
svc_action_t *op = NULL;
gboolean res = FALSE;
if (processing_blocked_ops) {
/* avoid nested calling of this function */
return;
}
processing_blocked_ops = TRUE;
/* n^2 operation here, but blocked ops are incredibly rare. this list
* will be empty 99% of the time. */
for (gIter = blocked_ops; gIter != NULL; gIter = gIter->next) {
op = gIter->data;
if (is_op_blocked(op->rsc)) {
continue;
}
executed_ops = g_list_append(executed_ops, op);
res = action_exec_helper(op);
if (res == FALSE) {
op->status = PCMK_LRM_OP_ERROR;
/* this can cause this function to be called recursively
* which is why we have processing_blocked_ops static variable */
operation_finalize(op);
}
}
for (gIter = executed_ops; gIter != NULL; gIter = gIter->next) {
op = gIter->data;
blocked_ops = g_list_remove(blocked_ops, op);
}
g_list_free(executed_ops);
processing_blocked_ops = FALSE;
}
+#define lsb_metadata_template \
+ "<?xml version='1.0'?>\n" \
+ "<!DOCTYPE resource-agent SYSTEM 'ra-api-1.dtd'>\n" \
+ "<resource-agent name='%s' version='0.1'>\n" \
+ " <version>1.0</version>\n" \
+ " <longdesc lang='en'>\n" \
+ " %s\n" \
+ " </longdesc>\n" \
+ " <shortdesc lang='en'>%s</shortdesc>\n" \
+ " <parameters>\n" \
+ " </parameters>\n" \
+ " <actions>\n" \
+ " <action name='meta-data' timeout='5' />\n" \
+ " <action name='start' timeout='15' />\n" \
+ " <action name='stop' timeout='15' />\n" \
+ " <action name='status' timeout='15' />\n" \
+ " <action name='restart' timeout='15' />\n" \
+ " <action name='force-reload' timeout='15' />\n" \
+ " <action name='monitor' timeout='15' interval='15' />\n" \
+ " </actions>\n" \
+ " <special tag='LSB'>\n" \
+ " <Provides>%s</Provides>\n" \
+ " <Required-Start>%s</Required-Start>\n" \
+ " <Required-Stop>%s</Required-Stop>\n" \
+ " <Should-Start>%s</Should-Start>\n" \
+ " <Should-Stop>%s</Should-Stop>\n" \
+ " <Default-Start>%s</Default-Start>\n" \
+ " <Default-Stop>%s</Default-Stop>\n" \
+ " </special>\n" \
+ "</resource-agent>\n"
+
+#define LSB_INITSCRIPT_INFOBEGIN_TAG "### BEGIN INIT INFO"
+#define LSB_INITSCRIPT_INFOEND_TAG "### END INIT INFO"
+#define PROVIDES "# Provides:"
+#define REQ_START "# Required-Start:"
+#define REQ_STOP "# Required-Stop:"
+#define SHLD_START "# Should-Start:"
+#define SHLD_STOP "# Should-Stop:"
+#define DFLT_START "# Default-Start:"
+#define DFLT_STOP "# Default-Stop:"
+#define SHORT_DSCR "# Short-Description:"
+#define DESCRIPTION "# Description:"
+
+#define lsb_meta_helper_free_value(m) \
+ do { \
+ if ((m) != NULL) { \
+ xmlFree(m); \
+ (m) = NULL; \
+ } \
+ } while(0)
+
+/*!
+ * \internal
+ * \brief Grab an LSB header value
+ *
+ * \param[in] line Line read from LSB init script
+ * \param[in,out] value If not set, will be set to XML-safe copy of value
+ * \param[in] prefix Set value if line starts with this pattern
+ *
+ * \return TRUE if value was set, FALSE otherwise
+ */
+static inline gboolean
+lsb_meta_helper_get_value(const char *line, char **value, const char *prefix)
+{
+ if (!*value && !strncasecmp(line, prefix, strlen(prefix))) {
+ *value = (char *)xmlEncodeEntitiesReentrant(NULL, BAD_CAST line+strlen(prefix));
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static int
+lsb_get_metadata(const char *type, char **output)
+{
+ char ra_pathname[PATH_MAX] = { 0, };
+ FILE *fp;
+ char buffer[1024];
+ char *provides = NULL;
+ char *req_start = NULL;
+ char *req_stop = NULL;
+ char *shld_start = NULL;
+ char *shld_stop = NULL;
+ char *dflt_start = NULL;
+ char *dflt_stop = NULL;
+ char *s_dscrpt = NULL;
+ char *xml_l_dscrpt = NULL;
+ int offset = 0;
+ int max = 2048;
+ char description[max];
+
+ if (type[0] == '/') {
+ snprintf(ra_pathname, sizeof(ra_pathname), "%s", type);
+ } else {
+ snprintf(ra_pathname, sizeof(ra_pathname), "%s/%s",
+ LSB_ROOT_DIR, type);
+ }
+
+ crm_trace("Looking into %s", ra_pathname);
+ fp = fopen(ra_pathname, "r");
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ /* Enter into the LSB-compliant comment block */
+ while (fgets(buffer, sizeof(buffer), fp)) {
+
+ /* Assume each of the following eight arguments contain one line */
+ if (lsb_meta_helper_get_value(buffer, &provides, PROVIDES)) {
+ continue;
+ }
+ if (lsb_meta_helper_get_value(buffer, &req_start, REQ_START)) {
+ continue;
+ }
+ if (lsb_meta_helper_get_value(buffer, &req_stop, REQ_STOP)) {
+ continue;
+ }
+ if (lsb_meta_helper_get_value(buffer, &shld_start, SHLD_START)) {
+ continue;
+ }
+ if (lsb_meta_helper_get_value(buffer, &shld_stop, SHLD_STOP)) {
+ continue;
+ }
+ if (lsb_meta_helper_get_value(buffer, &dflt_start, DFLT_START)) {
+ continue;
+ }
+ if (lsb_meta_helper_get_value(buffer, &dflt_stop, DFLT_STOP)) {
+ continue;
+ }
+ if (lsb_meta_helper_get_value(buffer, &s_dscrpt, SHORT_DSCR)) {
+ continue;
+ }
+
+ /* Long description may cross multiple lines */
+ if ((offset == 0)
+ && !strncasecmp(buffer, DESCRIPTION, strlen(DESCRIPTION))) {
+ /* Between # and keyword, more than one space, or a tab
+ * character, indicates the continuation line.
+ *
+ * Extracted from LSB init script standard
+ */
+ while (fgets(buffer, sizeof(buffer), fp)) {
+ if (!strncmp(buffer, "# ", 3) || !strncmp(buffer, "#\t", 2)) {
+ buffer[0] = ' ';
+ offset += snprintf(description+offset, max-offset, "%s",
+ buffer);
+ } else {
+ fputs(buffer, fp);
+ break; /* Long description ends */
+ }
+ }
+ continue;
+ }
+
+ if ((xml_l_dscrpt == NULL) && (offset > 0)) {
+ xml_l_dscrpt = (char *)xmlEncodeEntitiesReentrant(NULL, BAD_CAST(description));
+ }
+
+ if (!strncasecmp(buffer, LSB_INITSCRIPT_INFOEND_TAG,
+ strlen(LSB_INITSCRIPT_INFOEND_TAG))) {
+ /* Get to the out border of LSB comment block */
+ break;
+ }
+ if (buffer[0] != '#') {
+ break; /* Out of comment block in the beginning */
+ }
+ }
+ fclose(fp);
+
+ *output = crm_strdup_printf(lsb_metadata_template, type,
+ (xml_l_dscrpt? xml_l_dscrpt : type),
+ (s_dscrpt? s_dscrpt : type),
+ (provides? provides : ""),
+ (req_start? req_start : ""),
+ (req_stop? req_stop : ""),
+ (shld_start? shld_start : ""),
+ (shld_stop? shld_stop : ""),
+ (dflt_start? dflt_start : ""),
+ (dflt_stop? dflt_stop : ""));
+
+ lsb_meta_helper_free_value(xml_l_dscrpt);
+ lsb_meta_helper_free_value(s_dscrpt);
+ lsb_meta_helper_free_value(provides);
+ lsb_meta_helper_free_value(req_start);
+ lsb_meta_helper_free_value(req_stop);
+ lsb_meta_helper_free_value(shld_start);
+ lsb_meta_helper_free_value(shld_stop);
+ lsb_meta_helper_free_value(dflt_start);
+ lsb_meta_helper_free_value(dflt_stop);
+
+ crm_trace("Created fake metadata: %llu",
+ (unsigned long long) strlen(*output));
+ return pcmk_ok;
+}
+
+#if SUPPORT_NAGIOS
+static int
+nagios_get_metadata(const char *type, char **output)
+{
+ int rc = pcmk_ok;
+ FILE *file_strm = NULL;
+ int start = 0, length = 0, read_len = 0;
+ char *metadata_file = NULL;
+ int len = 36;
+
+ len += strlen(NAGIOS_METADATA_DIR);
+ len += strlen(type);
+ metadata_file = calloc(1, len);
+ CRM_CHECK(metadata_file != NULL, return -ENOMEM);
+
+ sprintf(metadata_file, "%s/%s.xml", NAGIOS_METADATA_DIR, type);
+ file_strm = fopen(metadata_file, "r");
+ if (file_strm == NULL) {
+ crm_err("Metadata file %s does not exist", metadata_file);
+ free(metadata_file);
+ return -EIO;
+ }
+
+ /* see how big the file is */
+ start = ftell(file_strm);
+ fseek(file_strm, 0L, SEEK_END);
+ length = ftell(file_strm);
+ fseek(file_strm, 0L, start);
+
+ CRM_ASSERT(length >= 0);
+ CRM_ASSERT(start == ftell(file_strm));
+
+ if (length <= 0) {
+ crm_info("%s was not valid", metadata_file);
+ free(*output);
+ *output = NULL;
+ rc = -EIO;
+
+ } else {
+ crm_trace("Reading %d bytes from file", length);
+ *output = calloc(1, (length + 1));
+ read_len = fread(*output, 1, length, file_strm);
+ if (read_len != length) {
+ crm_err("Calculated and read bytes differ: %d vs. %d",
+ length, read_len);
+ free(*output);
+ *output = NULL;
+ rc = -EIO;
+ }
+ }
+
+ fclose(file_strm);
+ free(metadata_file);
+ return rc;
+}
+#endif
+
+#if SUPPORT_HEARTBEAT
+/* strictly speaking, support for class=heartbeat style scripts
+ * does not require "heartbeat support" to be enabled.
+ * But since those scripts are part of the "heartbeat" package usually,
+ * and are very unlikely to be present in any other deployment,
+ * I leave it inside this ifdef.
+ *
+ * Yes, I know, these are legacy and should die,
+ * or at least be rewritten to be a proper OCF style agent.
+ * But they exist, and custom scripts following these rules do, too.
+ *
+ * Taken from the old "glue" lrmd, see
+ * http://hg.linux-ha.org/glue/file/0a7add1d9996/lib/plugins/lrm/raexechb.c#l49
+ * http://hg.linux-ha.org/glue/file/0a7add1d9996/lib/plugins/lrm/raexechb.c#l393
+ */
+
+static const char hb_metadata_template[] =
+ "<?xml version='1.0'?>\n"
+ "<!DOCTYPE resource-agent SYSTEM 'ra-api-1.dtd'>\n"
+ "<resource-agent name='%s' version='0.1'>\n"
+ "<version>1.0</version>\n"
+ "<longdesc lang='en'>\n"
+ "%s"
+ "</longdesc>\n"
+ "<shortdesc lang='en'>%s</shortdesc>\n"
+ "<parameters>\n"
+ "<parameter name='1' unique='1' required='0'>\n"
+ "<longdesc lang='en'>\n"
+ "This argument will be passed as the first argument to the "
+ "heartbeat resource agent (assuming it supports one)\n"
+ "</longdesc>\n"
+ "<shortdesc lang='en'>argv[1]</shortdesc>\n"
+ "<content type='string' default=' ' />\n"
+ "</parameter>\n"
+ "<parameter name='2' unique='1' required='0'>\n"
+ "<longdesc lang='en'>\n"
+ "This argument will be passed as the second argument to the "
+ "heartbeat resource agent (assuming it supports one)\n"
+ "</longdesc>\n"
+ "<shortdesc lang='en'>argv[2]</shortdesc>\n"
+ "<content type='string' default=' ' />\n"
+ "</parameter>\n"
+ "<parameter name='3' unique='1' required='0'>\n"
+ "<longdesc lang='en'>\n"
+ "This argument will be passed as the third argument to the "
+ "heartbeat resource agent (assuming it supports one)\n"
+ "</longdesc>\n"
+ "<shortdesc lang='en'>argv[3]</shortdesc>\n"
+ "<content type='string' default=' ' />\n"
+ "</parameter>\n"
+ "<parameter name='4' unique='1' required='0'>\n"
+ "<longdesc lang='en'>\n"
+ "This argument will be passed as the fourth argument to the "
+ "heartbeat resource agent (assuming it supports one)\n"
+ "</longdesc>\n"
+ "<shortdesc lang='en'>argv[4]</shortdesc>\n"
+ "<content type='string' default=' ' />\n"
+ "</parameter>\n"
+ "<parameter name='5' unique='1' required='0'>\n"
+ "<longdesc lang='en'>\n"
+ "This argument will be passed as the fifth argument to the "
+ "heartbeat resource agent (assuming it supports one)\n"
+ "</longdesc>\n"
+ "<shortdesc lang='en'>argv[5]</shortdesc>\n"
+ "<content type='string' default=' ' />\n"
+ "</parameter>\n"
+ "</parameters>\n"
+ "<actions>\n"
+ "<action name='start' timeout='15' />\n"
+ "<action name='stop' timeout='15' />\n"
+ "<action name='status' timeout='15' />\n"
+ "<action name='monitor' timeout='15' interval='15' start-delay='15' />\n"
+ "<action name='meta-data' timeout='5' />\n"
+ "</actions>\n"
+ "<special tag='heartbeat'>\n"
+ "</special>\n"
+ "</resource-agent>\n";
+
+static int
+heartbeat_get_metadata(const char *type, char **output)
+{
+ *output = crm_strdup_printf(hb_metadata_template, type, type, type);
+ crm_trace("Created fake metadata: %llu",
+ (unsigned long long) strlen(*output));
+ return pcmk_ok;
+}
+#endif
+
+static gboolean
+action_get_metadata(svc_action_t *op)
+{
+ const char *class = op->standard;
+
+ if (op->agent == NULL) {
+ crm_err("meta-data requested without specifying agent");
+ return FALSE;
+ }
+
+ if (class == NULL) {
+ crm_err("meta-data requested for agent %s without specifying class",
+ op->agent);
+ return FALSE;
+ }
+
+ if (!strcmp(class, PCMK_RESOURCE_CLASS_SERVICE)) {
+ class = resources_find_service_class(op->agent);
+ }
+
+ if (class == NULL) {
+ crm_err("meta-data requested for %s, but could not determine class",
+ op->agent);
+ return FALSE;
+ }
+
+ if (safe_str_eq(class, PCMK_RESOURCE_CLASS_LSB)) {
+ return (lsb_get_metadata(op->agent, &op->stdout_data) >= 0);
+ }
+
+#if SUPPORT_NAGIOS
+ if (safe_str_eq(class, PCMK_RESOURCE_CLASS_NAGIOS)) {
+ return (nagios_get_metadata(op->agent, &op->stdout_data) >= 0);
+ }
+#endif
+
+#if SUPPORT_HEARTBEAT
+ if (safe_str_eq(class, PCMK_RESOURCE_CLASS_HB)) {
+ return (heartbeat_get_metadata(op->agent, &op->stdout_data) >= 0);
+ }
+#endif
+
+ return action_exec_helper(op);
+}
+
gboolean
services_action_sync(svc_action_t * op)
{
gboolean rc = TRUE;
if (op == NULL) {
crm_trace("No operation to execute");
return FALSE;
}
op->synchronous = true;
- rc = action_exec_helper(op);
- crm_trace(" > %s_%s_%d: %s = %d", op->rsc, op->action, op->interval, op->opaque->exec, op->rc);
+
+ if (safe_str_eq(op->action, "meta-data")) {
+ /* Synchronous meta-data operations are handled specially. Since most
+ * resource classes don't provide any meta-data, it has to be
+ * synthesized from available information about the agent.
+ *
+ * services_action_async() doesn't treat meta-data actions specially, so
+ * it will result in an error for classes that don't support the action.
+ */
+ rc = action_get_metadata(op);
+ } else {
+ rc = action_exec_helper(op);
+ }
+ crm_trace(" > %s_%s_%d: %s = %d",
+ op->rsc, op->action, op->interval, op->opaque->exec, op->rc);
if (op->stdout_data) {
crm_trace(" > stdout: %s", op->stdout_data);
}
if (op->stderr_data) {
crm_trace(" > stderr: %s", op->stderr_data);
}
return rc;
}
GList *
get_directory_list(const char *root, gboolean files, gboolean executable)
{
return services_os_get_directory_list(root, files, executable);
}
GList *
services_list(void)
{
return resources_list_agents(PCMK_RESOURCE_CLASS_LSB, NULL);
}
#if SUPPORT_HEARTBEAT
static GList *
resources_os_list_hb_agents(void)
{
return services_os_get_directory_list(HB_RA_DIR, TRUE, TRUE);
}
#endif
GList *
resources_list_standards(void)
{
GList *standards = NULL;
GList *agents = NULL;
standards = g_list_append(standards, strdup(PCMK_RESOURCE_CLASS_OCF));
standards = g_list_append(standards, strdup(PCMK_RESOURCE_CLASS_LSB));
standards = g_list_append(standards, strdup(PCMK_RESOURCE_CLASS_SERVICE));
#if SUPPORT_SYSTEMD
agents = systemd_unit_listall();
if (agents) {
standards = g_list_append(standards,
strdup(PCMK_RESOURCE_CLASS_SYSTEMD));
g_list_free_full(agents, free);
}
#endif
#if SUPPORT_UPSTART
agents = upstart_job_listall();
if (agents) {
standards = g_list_append(standards,
strdup(PCMK_RESOURCE_CLASS_UPSTART));
g_list_free_full(agents, free);
}
#endif
#if SUPPORT_NAGIOS
agents = resources_os_list_nagios_agents();
if (agents) {
standards = g_list_append(standards,
strdup(PCMK_RESOURCE_CLASS_NAGIOS));
g_list_free_full(agents, free);
}
#endif
#if SUPPORT_HEARTBEAT
standards = g_list_append(standards, strdup(PCMK_RESOURCE_CLASS_HB));
#endif
return standards;
}
GList *
resources_list_providers(const char *standard)
{
if (strcasecmp(standard, PCMK_RESOURCE_CLASS_OCF) == 0) {
return resources_os_list_ocf_providers();
}
return NULL;
}
GList *
resources_list_agents(const char *standard, const char *provider)
{
if ((standard == NULL)
|| (strcasecmp(standard, PCMK_RESOURCE_CLASS_SERVICE) == 0)) {
GList *tmp1;
GList *tmp2;
GList *result = resources_os_list_lsb_agents();
if (standard == NULL) {
tmp1 = result;
tmp2 = resources_os_list_ocf_agents(NULL);
if (tmp2) {
result = g_list_concat(tmp1, tmp2);
}
}
#if SUPPORT_SYSTEMD
tmp1 = result;
tmp2 = systemd_unit_listall();
if (tmp2) {
result = g_list_concat(tmp1, tmp2);
}
#endif
#if SUPPORT_UPSTART
tmp1 = result;
tmp2 = upstart_job_listall();
if (tmp2) {
result = g_list_concat(tmp1, tmp2);
}
#endif
return result;
} else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_OCF) == 0) {
return resources_os_list_ocf_agents(provider);
} else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_LSB) == 0) {
return resources_os_list_lsb_agents();
#if SUPPORT_HEARTBEAT
} else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_HB) == 0) {
return resources_os_list_hb_agents();
#endif
#if SUPPORT_SYSTEMD
} else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_SYSTEMD) == 0) {
return systemd_unit_listall();
#endif
#if SUPPORT_UPSTART
} else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_UPSTART) == 0) {
return upstart_job_listall();
#endif
#if SUPPORT_NAGIOS
} else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_NAGIOS) == 0) {
return resources_os_list_nagios_agents();
#endif
}
return NULL;
}
diff --git a/lib/transition/unpack.c b/lib/transition/unpack.c
index e9cc1785cf..dc93fdee15 100644
--- a/lib/transition/unpack.c
+++ b/lib/transition/unpack.c
@@ -1,611 +1,376 @@
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* This library 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.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser 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
*/
#include <crm_internal.h>
#include <sys/param.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/transition.h>
#include <sys/stat.h>
CRM_TRACE_INIT_DATA(transitioner);
-gboolean (*cache_check_fn)(lrmd_rsc_info_t *rsc, const char *node_name) = NULL;
-
static crm_action_t *
unpack_action(synapse_t * parent, xmlNode * xml_action)
{
crm_action_t *action = NULL;
const char *value = crm_element_value(xml_action, XML_ATTR_ID);
xmlNode *rsc_xml = NULL;
if (value == NULL) {
crm_err("Actions must have an id!");
crm_log_xml_trace(xml_action, "Action with missing id");
return NULL;
}
action = calloc(1, sizeof(crm_action_t));
if (action == NULL) {
crm_perror(LOG_CRIT, "Cannot unpack action");
crm_log_xml_trace(xml_action, "Lost action");
return NULL;
}
action->id = crm_parse_int(value, NULL);
if (action->id > parent->graph->max_action_id) {
parent->graph->max_action_id = action->id;
}
action->type = action_type_rsc;
action->xml = copy_xml(xml_action);
action->synapse = parent;
if (safe_str_eq(crm_element_name(action->xml), XML_GRAPH_TAG_RSC_OP)) {
action->type = action_type_rsc;
} else if (safe_str_eq(crm_element_name(action->xml), XML_GRAPH_TAG_PSEUDO_EVENT)) {
action->type = action_type_pseudo;
} else if (safe_str_eq(crm_element_name(action->xml), XML_GRAPH_TAG_CRM_EVENT)) {
action->type = action_type_crm;
}
action->params = xml2list(action->xml);
value = g_hash_table_lookup(action->params, "CRM_meta_timeout");
if (value != NULL) {
action->timeout = crm_parse_int(value, NULL);
}
/* Take start-delay into account for the timeout of the action timer */
value = g_hash_table_lookup(action->params, "CRM_meta_start_delay");
if (value != NULL) {
action->timeout += crm_parse_int(value, NULL);
}
value = g_hash_table_lookup(action->params, "CRM_meta_interval");
if (value != NULL) {
action->interval = crm_parse_int(value, NULL);
}
value = g_hash_table_lookup(action->params, "CRM_meta_can_fail");
if (value != NULL) {
crm_str_to_boolean(value, &(action->can_fail));
}
crm_trace("Action %d has timer set to %dms", action->id, action->timeout);
action->task = crm_element_value_copy(action->xml, XML_LRM_ATTR_TASK);
rsc_xml = find_xml_node(action->xml, XML_CIB_TAG_RESOURCE, FALSE);
if (!rsc_xml) {
return action;
}
value = crm_element_value(rsc_xml, XML_ATTR_ID_LONG);
if (value != NULL) {
g_hash_table_insert(action->params, strdup(XML_ATTR_ID_LONG), strdup(value));
}
action->rsc_info = calloc(1, sizeof(lrmd_rsc_info_t));
action->rsc_info->id = crm_element_value_copy(rsc_xml, XML_ATTR_ID);
action->rsc_info->class = crm_element_value_copy(rsc_xml, XML_AGENT_ATTR_CLASS);
action->rsc_info->provider = crm_element_value_copy(rsc_xml, XML_AGENT_ATTR_PROVIDER);
action->rsc_info->type = crm_element_value_copy(rsc_xml, XML_ATTR_TYPE);
return action;
}
static synapse_t *
unpack_synapse(crm_graph_t * new_graph, xmlNode * xml_synapse)
{
const char *value = NULL;
xmlNode *inputs = NULL;
xmlNode *action_set = NULL;
synapse_t *new_synapse = NULL;
CRM_CHECK(xml_synapse != NULL, return NULL);
crm_trace("looking in synapse %s", ID(xml_synapse));
new_synapse = calloc(1, sizeof(synapse_t));
new_synapse->id = crm_parse_int(ID(xml_synapse), NULL);
value = crm_element_value(xml_synapse, XML_CIB_ATTR_PRIORITY);
if (value != NULL) {
new_synapse->priority = crm_parse_int(value, NULL);
}
CRM_CHECK(new_synapse->id >= 0, free(new_synapse);
return NULL);
new_graph->num_synapses++;
new_synapse->graph = new_graph;
crm_trace("look for actions in synapse %s", crm_element_value(xml_synapse, XML_ATTR_ID));
for (action_set = __xml_first_child(xml_synapse); action_set != NULL;
action_set = __xml_next(action_set)) {
if (crm_str_eq((const char *)action_set->name, "action_set", TRUE)) {
xmlNode *action = NULL;
for (action = __xml_first_child(action_set); action != NULL;
action = __xml_next(action)) {
crm_action_t *new_action = unpack_action(new_synapse, action);
if (new_action == NULL) {
continue;
}
new_graph->num_actions++;
crm_trace("Adding action %d to synapse %d", new_action->id, new_synapse->id);
new_synapse->actions = g_list_append(new_synapse->actions, new_action);
}
}
}
crm_trace("look for inputs in synapse %s", ID(xml_synapse));
for (inputs = __xml_first_child(xml_synapse); inputs != NULL; inputs = __xml_next(inputs)) {
if (crm_str_eq((const char *)inputs->name, "inputs", TRUE)) {
xmlNode *trigger = NULL;
for (trigger = __xml_first_child(inputs); trigger != NULL;
trigger = __xml_next(trigger)) {
xmlNode *input = NULL;
for (input = __xml_first_child(trigger); input != NULL; input = __xml_next(input)) {
crm_action_t *new_input = unpack_action(new_synapse, input);
if (new_input == NULL) {
continue;
}
crm_trace("Adding input %d to synapse %d", new_input->id, new_synapse->id);
new_synapse->inputs = g_list_append(new_synapse->inputs, new_input);
}
}
}
}
return new_synapse;
}
-static char *
-generate_metadata_key(lrmd_rsc_info_t *rsc, const char *node)
-{
- char *ra_key = NULL;
- char *result = NULL;
-
- if (!rsc) {
- return NULL;
- }
-
- ra_key = crm_generate_ra_key(rsc->class, rsc->provider, rsc->type);
- result = crm_concat(ra_key, node, '_');
- free(ra_key);
-
- return result;
-}
-
-static gboolean
-has_injected_metadata_for(crm_action_t *action, GHashTable *injected_metadata)
-{
- const char *node = NULL;
- char *key = NULL;
- gboolean result = FALSE;
-
- CRM_CHECK(action, return FALSE);
-
- node = crm_meta_value(action->params, XML_LRM_ATTR_TARGET);
- key = generate_metadata_key(action->rsc_info, node);
-
- if (!key) {
- return FALSE;
- }
-
- result = g_hash_table_lookup(injected_metadata, key) != NULL;
- free(key);
-
- return result;
-}
-
-static gboolean
-has_cached_metadata_for(crm_action_t *action)
-{
- const char *node = NULL;
-
- CRM_CHECK(action, return FALSE);
-
- if (!action->rsc_info || !cache_check_fn) {
- return FALSE;
- }
-
- node = crm_meta_value(action->params, XML_LRM_ATTR_TARGET);
-
- return cache_check_fn(action->rsc_info, node);
-}
-
static void destroy_action(crm_action_t * action);
-static GListPtr
-prepend_probe_action(GListPtr actions, synapse_t *synapse)
-{
- GListPtr lpc;
-
- for (lpc = synapse->actions; lpc != NULL; lpc = lpc->next) {
- crm_action_t *action = (crm_action_t *) lpc->data;
-
- if (action->type == action_type_rsc && action->interval == 0 &&
- safe_str_eq(action->task, RSC_STATUS)) {
- actions = g_list_prepend(actions, action);
- }
- }
-
- return actions;
-}
-
-static GListPtr
-prepend_start_action(GListPtr actions, synapse_t *synapse)
-{
- GListPtr lpc;
-
- for (lpc = synapse->actions; lpc != NULL; lpc = lpc->next) {
- crm_action_t *action = (crm_action_t *) lpc->data;
-
- if (action->type == action_type_rsc && safe_str_eq(action->task, RSC_START)) {
- actions = g_list_prepend(actions, action);
- }
- }
-
- return actions;
-}
-
-static GListPtr
-prepend_other_action(GListPtr actions, synapse_t *synapse, GHashTable *injected)
-{
- GListPtr lpc;
-
- for (lpc = synapse->actions; lpc != NULL; lpc = lpc->next) {
- crm_action_t *action = (crm_action_t *) lpc->data;
-
- if (action->type == action_type_rsc && !has_cached_metadata_for(action) &&
- !has_injected_metadata_for(action, injected)) {
- actions = g_list_prepend(actions, action);
- }
- }
-
- return actions;
-}
-
-static void
-inject_metadata(crm_action_t *action, GHashTable *injected)
-{
- crm_action_t *new_input = NULL;
- synapse_t *new_synapse = NULL;
- xmlNode *synapse = NULL;
- xmlNode *action_set = NULL;
- xmlNode *op = NULL;
- xmlNode *rsc = NULL;
- xmlNode *attrs = NULL;
- char *key = NULL;
- const char *long_id = NULL;
- const char *node = NULL;
- const char *node_uuid = NULL;
- crm_graph_t *graph = NULL;
-
- CRM_CHECK(action, return);
-
- if (!action->rsc_info) {
- return;
- }
-
- graph = action->synapse->graph;
-
- key = generate_op_key(action->rsc_info->id, RSC_METADATA, 0);
-
- node = crm_meta_value(action->params, XML_LRM_ATTR_TARGET);
- node_uuid = crm_meta_value(action->params, XML_LRM_ATTR_TARGET_UUID);
-
- synapse = create_xml_node(NULL, "synapse");
- // graph->num_synapses will be incremented later when whe synapse is unpacked
- crm_xml_add_int(synapse, XML_ATTR_ID, graph->num_synapses);
-
- action_set = create_xml_node(synapse, "action_set");
-
- op = create_xml_node(action_set, XML_GRAPH_TAG_RSC_OP);
- crm_xml_add_int(op, XML_ATTR_ID, graph->max_action_id + 1);
- crm_xml_add(op, XML_LRM_ATTR_TASK, RSC_METADATA);
- crm_xml_add(op, XML_LRM_ATTR_TARGET, node);
- crm_xml_add(op, XML_LRM_ATTR_TARGET_UUID, node_uuid);
- crm_xml_add(op, XML_LRM_ATTR_TASK_KEY, key);
- free(key);
-
- rsc = create_xml_node(op, XML_CIB_TAG_RESOURCE);
- crm_xml_add(rsc, XML_ATTR_ID, action->rsc_info->id);
- crm_xml_add(rsc, XML_AGENT_ATTR_CLASS, action->rsc_info->class);
- crm_xml_add(rsc, XML_AGENT_ATTR_PROVIDER, action->rsc_info->provider);
- crm_xml_add(rsc, XML_ATTR_TYPE, action->rsc_info->type);
-
- long_id = g_hash_table_lookup(action->params, XML_ATTR_ID_LONG);
- if (long_id) {
- crm_xml_add(rsc, XML_ATTR_ID_LONG, long_id);
- }
-
- attrs = create_xml_node(op, XML_TAG_ATTRS);
- crm_xml_add_int(attrs, CRM_META "_" XML_ATTR_TIMEOUT, CRMD_METADATA_CALL_TIMEOUT);
- crm_xml_add(attrs, CRM_META "_" XML_LRM_ATTR_TARGET, node);
- crm_xml_add(attrs, CRM_META "_" XML_LRM_ATTR_TARGET_UUID, node_uuid);
- crm_xml_add(attrs, XML_ATTR_CRM_VERSION, g_hash_table_lookup(action->params, XML_ATTR_CRM_VERSION));
- crm_xml_add(attrs, "CRM_meta_on_fail", "ignore");
- crm_xml_add(attrs, "CRM_meta_can_fail", "true");
-
- new_input = unpack_action(action->synapse, op);
- if (!new_input) {
- free_xml(synapse);
- return;
- }
-
- new_synapse = unpack_synapse(graph, synapse);
- free_xml(synapse);
-
- if (!new_synapse) {
- destroy_action(new_input);
- return;
- }
-
- action->synapse->inputs = g_list_append(action->synapse->inputs, new_input);
-
- graph->synapses = g_list_prepend(graph->synapses, new_synapse);
-
- key = generate_metadata_key(action->rsc_info, node);
-
- if (!key) {
- return;
- }
-
- g_hash_table_replace(injected, key, key);
-}
-
-static void
-inject_metadata_for(GListPtr actions, GHashTable *injected)
-{
- GListPtr iter;
-
- for (iter = actions; iter != NULL; iter = iter->next) {
- crm_action_t *action = (crm_action_t *) iter->data;
-
- if (!has_injected_metadata_for(action, injected)) {
- inject_metadata(action, injected);
- }
- }
-}
-
crm_graph_t *
unpack_graph(xmlNode * xml_graph, const char *reference)
{
/*
<transition_graph>
<synapse>
<action_set>
<rsc_op id="2"
...
<inputs>
<rsc_op id="2"
...
*/
crm_graph_t *new_graph = NULL;
const char *t_id = NULL;
const char *time = NULL;
xmlNode *synapse = NULL;
- GListPtr sIter = NULL;
- GListPtr start_actions = NULL;
- GListPtr probe_actions = NULL;
- GListPtr other_actions = NULL;
- GHashTable *injected = NULL;
new_graph = calloc(1, sizeof(crm_graph_t));
new_graph->id = -1;
new_graph->abort_priority = 0;
new_graph->network_delay = -1;
new_graph->transition_timeout = -1;
new_graph->stonith_timeout = -1;
new_graph->completion_action = tg_done;
new_graph->max_action_id = 0;
if (reference) {
new_graph->source = strdup(reference);
} else {
new_graph->source = strdup("unknown");
}
if (xml_graph != NULL) {
t_id = crm_element_value(xml_graph, "transition_id");
CRM_CHECK(t_id != NULL, free(new_graph);
return NULL);
new_graph->id = crm_parse_int(t_id, "-1");
time = crm_element_value(xml_graph, "cluster-delay");
CRM_CHECK(time != NULL, free(new_graph);
return NULL);
new_graph->network_delay = crm_get_msec(time);
time = crm_element_value(xml_graph, "stonith-timeout");
if (time == NULL) {
new_graph->stonith_timeout = new_graph->network_delay;
} else {
new_graph->stonith_timeout = crm_get_msec(time);
}
t_id = crm_element_value(xml_graph, "batch-limit");
new_graph->batch_limit = crm_parse_int(t_id, "0");
t_id = crm_element_value(xml_graph, "migration-limit");
new_graph->migration_limit = crm_parse_int(t_id, "-1");
}
for (synapse = __xml_first_child(xml_graph); synapse != NULL; synapse = __xml_next(synapse)) {
if (crm_str_eq((const char *)synapse->name, "synapse", TRUE)) {
synapse_t *new_synapse = unpack_synapse(new_graph, synapse);
if (new_synapse != NULL) {
new_graph->synapses = g_list_append(new_graph->synapses, new_synapse);
- probe_actions = prepend_probe_action(probe_actions, new_synapse);
- start_actions = prepend_start_action(start_actions, new_synapse);
}
}
}
- injected = g_hash_table_new_full(crm_str_hash, g_str_equal, g_hash_destroy_str, NULL);
-
- inject_metadata_for(probe_actions, injected);
- inject_metadata_for(start_actions, injected);
-
- for (sIter = new_graph->synapses; sIter != NULL; sIter = sIter->next) {
- synapse_t *synapse = (synapse_t*) sIter->data;
-
- other_actions = prepend_other_action(other_actions, synapse, injected);
- }
-
- inject_metadata_for(other_actions, injected);
-
- g_hash_table_destroy(injected);
- g_list_free(start_actions);
- g_list_free(probe_actions);
- g_list_free(other_actions);
-
crm_debug("Unpacked transition %d: %d actions in %d synapses",
new_graph->id, new_graph->num_actions, new_graph->num_synapses);
return new_graph;
}
static void
destroy_action(crm_action_t * action)
{
if (action->timer && action->timer->source_id != 0) {
crm_warn("Cancelling timer for action %d (src=%d)", action->id, action->timer->source_id);
g_source_remove(action->timer->source_id);
}
if (action->params) {
g_hash_table_destroy(action->params);
}
free_xml(action->xml);
free(action->timer);
free(action->task);
lrmd_free_rsc_info(action->rsc_info);
free(action);
}
static void
destroy_synapse(synapse_t * synapse)
{
while (g_list_length(synapse->actions) > 0) {
crm_action_t *action = g_list_nth_data(synapse->actions, 0);
synapse->actions = g_list_remove(synapse->actions, action);
destroy_action(action);
}
while (g_list_length(synapse->inputs) > 0) {
crm_action_t *action = g_list_nth_data(synapse->inputs, 0);
synapse->inputs = g_list_remove(synapse->inputs, action);
destroy_action(action);
}
free(synapse);
}
void
destroy_graph(crm_graph_t * graph)
{
if (graph == NULL) {
return;
}
while (g_list_length(graph->synapses) > 0) {
synapse_t *synapse = g_list_nth_data(graph->synapses, 0);
graph->synapses = g_list_remove(graph->synapses, synapse);
destroy_synapse(synapse);
}
free(graph->source);
free(graph);
}
lrmd_event_data_t *
convert_graph_action(xmlNode * resource, crm_action_t * action, int status, int rc)
{
xmlNode *xop = NULL;
lrmd_event_data_t *op = NULL;
GHashTableIter iter;
const char *name = NULL;
const char *value = NULL;
xmlNode *action_resource = NULL;
CRM_CHECK(action != NULL, return NULL);
CRM_CHECK(action->type == action_type_rsc, return NULL);
action_resource = first_named_child(action->xml, XML_CIB_TAG_RESOURCE);
CRM_CHECK(action_resource != NULL, crm_log_xml_warn(action->xml, "Bad");
return NULL);
op = calloc(1, sizeof(lrmd_event_data_t));
op->rsc_id = strdup(ID(action_resource));
op->interval = action->interval;
op->op_type = strdup(action->task);
op->rc = rc;
op->op_status = status;
op->t_run = time(NULL);
op->t_rcchange = op->t_run;
op->params = g_hash_table_new_full(crm_str_hash, g_str_equal,
g_hash_destroy_str, g_hash_destroy_str);
g_hash_table_iter_init(&iter, action->params);
while (g_hash_table_iter_next(&iter, (void **)&name, (void **)&value)) {
if (safe_str_eq(name, XML_ATTR_ID_LONG)) {
continue;
}
g_hash_table_insert(op->params, strdup(name), strdup(value));
}
for (xop = __xml_first_child(resource); xop != NULL; xop = __xml_next(xop)) {
int tmp = 0;
crm_element_value_int(xop, XML_LRM_ATTR_CALLID, &tmp);
crm_debug("Got call_id=%d for %s", tmp, ID(resource));
if (tmp > op->call_id) {
op->call_id = tmp;
}
}
op->call_id++;
return op;
}
diff --git a/lib/transition/utils.c b/lib/transition/utils.c
index c662a5a658..d3199d9c21 100644
--- a/lib/transition/utils.c
+++ b/lib/transition/utils.c
@@ -1,306 +1,290 @@
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* This library 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.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser 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
*/
#include <crm_internal.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/transition.h>
/* #include <sys/param.h> */
/* */
extern crm_graph_functions_t *graph_fns;
-extern gboolean (*cache_check_fn)(lrmd_rsc_info_t *rsc, const char *node_name);
-
static gboolean
pseudo_action_dummy(crm_graph_t * graph, crm_action_t * action)
{
static int fail = -1;
if (fail < 0) {
char *fail_s = getenv("PE_fail");
if (fail_s) {
fail = crm_int_helper(fail_s, NULL);
} else {
fail = 0;
}
}
crm_trace("Dummy event handler: action %d executed", action->id);
if (action->id == fail) {
crm_err("Dummy event handler: pretending action %d failed", action->id);
action->failed = TRUE;
graph->abort_priority = INFINITY;
}
action->confirmed = TRUE;
update_graph(graph, action);
return TRUE;
}
crm_graph_functions_t default_fns = {
pseudo_action_dummy,
pseudo_action_dummy,
pseudo_action_dummy,
pseudo_action_dummy
};
void
set_default_graph_functions(void)
{
graph_fns = &default_fns;
}
void
set_graph_functions(crm_graph_functions_t * fns)
{
crm_info("Setting custom graph functions");
graph_fns = fns;
CRM_ASSERT(graph_fns != NULL);
CRM_ASSERT(graph_fns->rsc != NULL);
CRM_ASSERT(graph_fns->crmd != NULL);
CRM_ASSERT(graph_fns->pseudo != NULL);
CRM_ASSERT(graph_fns->stonith != NULL);
}
const char *
transition_status(enum transition_status state)
{
switch (state) {
case transition_active:
return "active";
case transition_pending:
return "pending";
case transition_complete:
return "complete";
case transition_stopped:
return "stopped";
case transition_terminated:
return "terminated";
case transition_action_failed:
return "failed (action)";
case transition_failed:
return "failed";
}
return "unknown";
}
const char *
actiontype2text(action_type_e type)
{
switch (type) {
case action_type_pseudo:
return "pseudo";
case action_type_rsc:
return "rsc";
case action_type_crm:
return "crm";
}
return "<unknown>";
}
static crm_action_t *
find_action(crm_graph_t * graph, int id)
{
GListPtr sIter = NULL;
if (graph == NULL) {
return NULL;
}
for (sIter = graph->synapses; sIter != NULL; sIter = sIter->next) {
GListPtr aIter = NULL;
synapse_t *synapse = (synapse_t *) sIter->data;
for (aIter = synapse->actions; aIter != NULL; aIter = aIter->next) {
crm_action_t *action = (crm_action_t *) aIter->data;
if (action->id == id) {
return action;
}
}
}
return NULL;
}
static void
print_synapse(unsigned int log_level, crm_graph_t * graph, synapse_t * synapse)
{
GListPtr lpc = NULL;
char *pending = NULL;
const char *state = "Pending";
if (synapse->failed) {
state = "Failed";
} else if (synapse->confirmed) {
state = "Completed";
} else if (synapse->executed) {
state = "In-flight";
} else if (synapse->ready) {
state = "Ready";
}
if (synapse->executed == FALSE) {
for (lpc = synapse->inputs; lpc != NULL; lpc = lpc->next) {
crm_action_t *input = (crm_action_t *) lpc->data;
const char *id_string = crm_element_value(input->xml, XML_ATTR_ID);
if (input->failed) {
pending = add_list_element(pending, id_string);
} else if (input->confirmed) {
/* Confirmed, skip */
} else if (find_action(graph, input->id)) {
/* In-flight or pending */
pending = add_list_element(pending, id_string);
}
}
}
for (lpc = synapse->actions; lpc != NULL; lpc = lpc->next) {
crm_action_t *action = (crm_action_t *) lpc->data;
const char *key = crm_element_value(action->xml, XML_LRM_ATTR_TASK_KEY);
const char *host = crm_element_value(action->xml, XML_LRM_ATTR_TARGET);
char *desc = crm_strdup_printf("%s %s op %s", state, actiontype2text(action->type), key);
do_crm_log(log_level,
"[Action %4d]: %-50s on %s (priority: %d, waiting: %s)",
action->id, desc, host ? host : "N/A",
synapse->priority, pending ? pending : "none");
free(desc);
}
if (synapse->executed == FALSE) {
for (lpc = synapse->inputs; lpc != NULL; lpc = lpc->next) {
crm_action_t *input = (crm_action_t *) lpc->data;
const char *key = crm_element_value(input->xml, XML_LRM_ATTR_TASK_KEY);
const char *host = crm_element_value(input->xml, XML_LRM_ATTR_TARGET);
if (find_action(graph, input->id) == NULL) {
if (host == NULL) {
do_crm_log(log_level, " * [Input %2d]: Unresolved dependency %s op %s",
input->id, actiontype2text(input->type), key);
} else {
do_crm_log(log_level, " * [Input %2d]: Unresolved dependency %s op %s on %s",
input->id, actiontype2text(input->type), key, host);
}
}
}
}
free(pending);
}
void
print_action(int log_level, const char *prefix, crm_action_t * action)
{
print_synapse(log_level, NULL, action->synapse);
}
void
print_graph(unsigned int log_level, crm_graph_t * graph)
{
GListPtr lpc = NULL;
if (graph == NULL || graph->num_actions == 0) {
if (log_level > LOG_DEBUG) {
crm_debug("Empty transition graph");
}
return;
}
do_crm_log(log_level, "Graph %d with %d actions:"
" batch-limit=%d jobs, network-delay=%dms",
graph->id, graph->num_actions,
graph->batch_limit, graph->network_delay);
for (lpc = graph->synapses; lpc != NULL; lpc = lpc->next) {
synapse_t *synapse = (synapse_t *) lpc->data;
print_synapse(log_level, graph, synapse);
}
}
static const char *
abort2text(enum transition_action abort_action)
{
switch (abort_action) {
case tg_done:
return "done";
case tg_stop:
return "stop";
case tg_restart:
return "restart";
case tg_shutdown:
return "shutdown";
}
return "unknown";
}
bool
update_abort_priority(crm_graph_t * graph, int priority,
enum transition_action action, const char *abort_reason)
{
bool change = FALSE;
if (graph == NULL) {
return change;
}
if (graph->abort_priority < priority) {
crm_debug("Abort priority upgraded from %d to %d", graph->abort_priority, priority);
graph->abort_priority = priority;
if (graph->abort_reason != NULL) {
crm_debug("'%s' abort superseded by %s", graph->abort_reason, abort_reason);
}
graph->abort_reason = abort_reason;
change = TRUE;
}
if (graph->completion_action < action) {
crm_debug("Abort action %s superseded by %s: %s",
abort2text(graph->completion_action), abort2text(action), abort_reason);
graph->completion_action = action;
change = TRUE;
}
return change;
}
-
-void
-crm_register_cache_check_fn(gboolean (*fn)(lrmd_rsc_info_t *rsc, const char *node_name))
-{
- CRM_ASSERT(fn != NULL);
-
- cache_check_fn = fn;
-}
-
-void
-crm_unregister_cache_check_fn(void)
-{
- cache_check_fn = NULL;
-}
diff --git a/lrmd/remote_ctl.c b/lrmd/remote_ctl.c
index 4afa337bc3..b105a58d03 100644
--- a/lrmd/remote_ctl.c
+++ b/lrmd/remote_ctl.c
@@ -1,395 +1,398 @@
/*
* Copyright (c) 2015 David Vossel <davidvossel@gmail.com>
*
* This library 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.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser 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
*
*/
#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/pengine/status.h>
#include <crm/cib.h>
#include <crm/lrmd.h>
extern GHashTable *proxy_table;
void lrmd_internal_set_proxy_callback(lrmd_t * lrmd, void *userdata, void (*callback)(lrmd_t *lrmd, void *userdata, xmlNode *msg));
/* *INDENT-OFF* */
static struct crm_option long_options[] = {
{"help", 0, 0, '?'},
{"verbose", 0, 0, 'V', "\t\tPrint out logs and events to screen"},
{"quiet", 0, 0, 'Q', "\t\tSuppress all output to screen"},
{"tls", 1, 0, 'S', "\t\tSet tls host to contact"},
{"tls-port", 1, 0, 'p', "\t\tUse custom tls port"},
{"node", 1, 0, 'n', "\tNode name to use for ipc proxy"},
{"api-call", 1, 0, 'c', "\tDirectly relates to lrmd api functions"},
{"-spacer-", 1, 0, '-', "\nParameters for api-call option"},
{"action", 1, 0, 'a'},
{"rsc-id", 1, 0, 'r'},
{"provider", 1, 0, 'P'},
{"class", 1, 0, 'C'},
{"type", 1, 0, 'T'},
{"timeout", 1, 0, 't'},
{"param-key", 1, 0, 'k'},
{"param-val", 1, 0, 'v'},
{"-spacer-", 1, 0, '-'},
{0, 0, 0, 0}
};
/* *INDENT-ON* */
static int wait_poke = 0;
static int exec_call_id = 0;
static gboolean client_start(gpointer user_data);
static void try_connect(void);
static struct {
int verbose;
int quiet;
int print;
int interval;
int timeout;
int port;
const char *node_name;
const char *api_call;
const char *rsc_id;
const char *provider;
const char *class;
const char *type;
const char *action;
const char *listen;
const char *tls_host;
lrmd_key_value_t *params;
} options;
GMainLoop *mainloop = NULL;
lrmd_t *lrmd_conn = NULL;
static void
client_exit(int rc)
{
lrmd_api_delete(lrmd_conn);
if (proxy_table) {
g_hash_table_destroy(proxy_table); proxy_table = NULL;
}
exit(rc);
}
static void
client_shutdown(int nsig)
{
lrmd_api_delete(lrmd_conn);
lrmd_conn = NULL;
}
static void
read_events(lrmd_event_data_t * event)
{
if (wait_poke && event->type == lrmd_event_poke) {
client_exit(PCMK_OCF_OK);
}
if ((event->call_id == exec_call_id) && (event->type == lrmd_event_exec_complete)) {
if (event->output) {
crm_info("%s", event->output);
}
if (event->exit_reason) {
fprintf(stderr, "%s%s\n", PCMK_OCF_REASON_PREFIX, event->exit_reason);
}
client_exit(event->rc);
}
}
static gboolean
timeout_err(gpointer data)
{
crm_err("timed out in remote_client");
client_exit(PCMK_OCF_TIMEOUT);
return FALSE;
}
static void
connection_events(lrmd_event_data_t * event)
{
int rc = event->connection_rc;
if (event->type != lrmd_event_connect) {
/* ignore */
return;
}
if (!rc) {
client_start(NULL);
return;
} else {
sleep(1);
try_connect();
}
}
static void
try_connect(void)
{
int tries = 10;
static int num_tries = 0;
int rc = 0;
lrmd_conn->cmds->set_callback(lrmd_conn, connection_events);
for (; num_tries < tries; num_tries++) {
rc = lrmd_conn->cmds->connect_async(lrmd_conn, "lrmd", 10000);
if (!rc) {
num_tries++;
return; /* we'll hear back in async callback */
}
sleep(1);
}
crm_err("Failed to connect to pacemaker remote.");
client_exit(PCMK_OCF_UNKNOWN_ERROR);
}
static gboolean
client_start(gpointer user_data)
{
int rc = 0;
if (!lrmd_conn->cmds->is_connected(lrmd_conn)) {
try_connect();
/* async connect -- this function will get called back into */
return 0;
}
lrmd_conn->cmds->set_callback(lrmd_conn, read_events);
if (safe_str_eq(options.api_call, "ipc_debug")) {
/* Do nothing, leave connection up just for debugging ipc proxy */
return 0;
}
if (options.timeout) {
g_timeout_add(options.timeout, timeout_err, NULL);
}
if (safe_str_eq(options.api_call, "metadata")) {
char *output = NULL;
+ /* This is broken when the agent is installed only in the
+ * remote environment and not on the cluster node handling the proxy.
+ */
rc = lrmd_conn->cmds->get_metadata(lrmd_conn,
options.class,
options.provider, options.type, &output, 0);
if (rc == pcmk_ok) {
printf("%s", output);
free(output);
client_exit(PCMK_OCF_OK);
}
client_exit(PCMK_OCF_UNKNOWN_ERROR);
} else if (safe_str_eq(options.api_call, "poke")) {
rc = lrmd_conn->cmds->poke_connection(lrmd_conn);
if (rc != pcmk_ok) {
client_exit(PCMK_OCF_UNKNOWN_ERROR);
}
wait_poke = 1;
} else {
lrmd_rsc_info_t *rsc_info = NULL;
rsc_info = lrmd_conn->cmds->get_rsc_info(lrmd_conn, options.rsc_id, 0);
if (rsc_info == NULL) {
rc = lrmd_conn->cmds->register_rsc(lrmd_conn, options.rsc_id,
options.class, options.provider, options.type, 0);
if (rc != 0){
crm_err("failed to register resource %s with pacemaker_remote. rc: %d", options.rsc_id, rc);
client_exit(1);
}
}
lrmd_free_rsc_info(rsc_info);
rc = lrmd_conn->cmds->exec(lrmd_conn,
options.rsc_id,
options.action,
NULL,
options.interval,
options.timeout,
0, 0, options.params);
if (rc > 0) {
exec_call_id = rc;
} else {
crm_err("execution of rsc %s failed. rc = %d", options.rsc_id, rc);
client_exit(PCMK_OCF_UNKNOWN_ERROR);
}
}
return 0;
}
static void
ctl_remote_proxy_cb(lrmd_t *lrmd, void *userdata, xmlNode *msg)
{
const char *op = crm_element_value(msg, F_LRMD_IPC_OP);
const char *session = crm_element_value(msg, F_LRMD_IPC_SESSION);
if (safe_str_eq(op, LRMD_IPC_OP_NEW)) {
const char *channel = crm_element_value(msg, F_LRMD_IPC_IPC_SERVER);
static struct ipc_client_callbacks proxy_callbacks = {
.dispatch = remote_proxy_dispatch,
.destroy = remote_proxy_disconnected
};
remote_proxy_new(lrmd, &proxy_callbacks, options.node_name, session, channel);
} else {
remote_proxy_cb(lrmd, options.node_name, msg);
}
}
int
main(int argc, char **argv)
{
int option_index = 0;
int argerr = 0;
int flag;
char *key = NULL;
char *val = NULL;
gboolean use_tls = FALSE;
crm_trigger_t *trig;
crm_set_options(NULL, "mode [options]", long_options,
"Inject commands into the lrmd and watch for events\n");
while (1) {
flag = crm_get_option(argc, argv, &option_index);
if (flag == -1)
break;
switch (flag) {
case '?':
crm_help(flag, EX_OK);
break;
case 'V':
options.verbose = 1;
break;
case 'Q':
options.quiet = 1;
options.verbose = 0;
break;
case 'n':
options.node_name = optarg;
break;
case 'c':
options.api_call = optarg;
break;
case 'a':
options.action = optarg;
break;
case 'r':
options.rsc_id = optarg;
break;
case 'P':
options.provider = optarg;
break;
case 'C':
options.class = optarg;
break;
case 'T':
options.type = optarg;
break;
case 't':
if(optarg) {
options.timeout = atoi(optarg);
}
break;
case 'k':
key = optarg;
if (key && val) {
options.params = lrmd_key_value_add(options.params, key, val);
key = val = NULL;
}
break;
case 'v':
val = optarg;
if (key && val) {
options.params = lrmd_key_value_add(options.params, key, val);
key = val = NULL;
}
break;
case 'S':
options.tls_host = optarg;
use_tls = TRUE;
break;
case 'p':
if(optarg) {
options.port = atoi(optarg);
}
use_tls = TRUE;
break;
default:
++argerr;
break;
}
}
if (argerr) {
crm_help('?', EX_USAGE);
}
if (optind > argc) {
++argerr;
}
crm_log_init("remote_client", LOG_INFO, FALSE, options.verbose ? TRUE : FALSE, argc, argv, FALSE);
/* if we can't perform an api_call or listen for events,
* there is nothing to do */
if (!options.api_call ) {
crm_err("Nothing to be done. Please specify 'api-call'");
return PCMK_OCF_UNKNOWN_ERROR;
}
if (!options.timeout ) {
options.timeout = 20000;
}
if (use_tls) {
if (options.node_name == NULL) {
crm_err("\"node\" option required when tls is in use.");
return PCMK_OCF_UNKNOWN_ERROR;
}
proxy_table =
g_hash_table_new_full(crm_strcase_hash, crm_strcase_equal, NULL, remote_proxy_free);
lrmd_conn = lrmd_remote_api_new(NULL, options.tls_host ? options.tls_host : "localhost", options.port);
lrmd_internal_set_proxy_callback(lrmd_conn, NULL, ctl_remote_proxy_cb);
} else {
lrmd_conn = lrmd_api_new();
}
trig = mainloop_add_trigger(G_PRIORITY_HIGH, client_start, NULL);
mainloop_set_trigger(trig);
mainloop_add_signal(SIGTERM, client_shutdown);
mainloop = g_main_new(FALSE);
g_main_run(mainloop);
client_exit(0);
return 0;
}
diff --git a/pengine/allocate.c b/pengine/allocate.c
index 3a883ad321..e3cb4ccb5d 100644
--- a/pengine/allocate.c
+++ b/pengine/allocate.c
@@ -1,2481 +1,2484 @@
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* 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
*/
#include <crm_internal.h>
#include <sys/param.h>
#include <crm/crm.h>
#include <crm/cib.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <glib.h>
#include <crm/pengine/status.h>
#include <pengine.h>
#include <allocate.h>
#include <utils.h>
CRM_TRACE_INIT_DATA(pe_allocate);
void set_alloc_actions(pe_working_set_t * data_set);
void migrate_reload_madness(pe_working_set_t * data_set);
extern void ReloadRsc(resource_t * rsc, node_t *node, pe_working_set_t * data_set);
extern gboolean DeleteRsc(resource_t * rsc, node_t * node, gboolean optional, pe_working_set_t * data_set);
static void apply_remote_node_ordering(pe_working_set_t *data_set);
static enum remote_connection_state get_remote_node_state(pe_node_t *node);
enum remote_connection_state
{
remote_state_unknown = 0,
remote_state_alive = 1,
remote_state_resting = 2,
remote_state_failed = 3,
remote_state_stopped = 4
};
resource_alloc_functions_t resource_class_alloc_functions[] = {
{
native_merge_weights,
native_color,
native_create_actions,
native_create_probe,
native_internal_constraints,
native_rsc_colocation_lh,
native_rsc_colocation_rh,
native_rsc_location,
native_action_flags,
native_update_actions,
native_expand,
native_append_meta,
},
{
group_merge_weights,
group_color,
group_create_actions,
native_create_probe,
group_internal_constraints,
group_rsc_colocation_lh,
group_rsc_colocation_rh,
group_rsc_location,
group_action_flags,
group_update_actions,
group_expand,
group_append_meta,
},
{
clone_merge_weights,
clone_color,
clone_create_actions,
clone_create_probe,
clone_internal_constraints,
clone_rsc_colocation_lh,
clone_rsc_colocation_rh,
clone_rsc_location,
clone_action_flags,
container_update_actions,
clone_expand,
clone_append_meta,
},
{
master_merge_weights,
master_color,
master_create_actions,
clone_create_probe,
master_internal_constraints,
clone_rsc_colocation_lh,
master_rsc_colocation_rh,
clone_rsc_location,
clone_action_flags,
container_update_actions,
clone_expand,
master_append_meta,
},
{
container_merge_weights,
container_color,
container_create_actions,
container_create_probe,
container_internal_constraints,
container_rsc_colocation_lh,
container_rsc_colocation_rh,
container_rsc_location,
container_action_flags,
container_update_actions,
container_expand,
container_append_meta,
}
};
gboolean
update_action_flags(action_t * action, enum pe_action_flags flags, const char *source, int line)
{
static unsigned long calls = 0;
gboolean changed = FALSE;
gboolean clear = is_set(flags, pe_action_clear);
enum pe_action_flags last = action->flags;
if (clear) {
action->flags = crm_clear_bit(source, line, action->uuid, action->flags, flags);
} else {
action->flags = crm_set_bit(source, line, action->uuid, action->flags, flags);
}
if (last != action->flags) {
calls++;
changed = TRUE;
/* Useful for tracking down _who_ changed a specific flag */
/* CRM_ASSERT(calls != 534); */
clear_bit(flags, pe_action_clear);
crm_trace("%s on %s: %sset flags 0x%.6x (was 0x%.6x, now 0x%.6x, %lu, %s)",
action->uuid, action->node ? action->node->details->uname : "[none]",
clear ? "un-" : "", flags, last, action->flags, calls, source);
}
return changed;
}
static gboolean
check_rsc_parameters(resource_t * rsc, node_t * node, xmlNode * rsc_entry,
gboolean active_here, pe_working_set_t * data_set)
{
int attr_lpc = 0;
gboolean force_restart = FALSE;
gboolean delete_resource = FALSE;
gboolean changed = FALSE;
const char *value = NULL;
const char *old_value = NULL;
const char *attr_list[] = {
XML_ATTR_TYPE,
XML_AGENT_ATTR_CLASS,
XML_AGENT_ATTR_PROVIDER
};
for (; attr_lpc < DIMOF(attr_list); attr_lpc++) {
value = crm_element_value(rsc->xml, attr_list[attr_lpc]);
old_value = crm_element_value(rsc_entry, attr_list[attr_lpc]);
if (value == old_value /* ie. NULL */
|| crm_str_eq(value, old_value, TRUE)) {
continue;
}
changed = TRUE;
trigger_unfencing(rsc, node, "Device definition changed", NULL, data_set);
if (active_here) {
force_restart = TRUE;
crm_notice("Forcing restart of %s on %s, %s changed: %s -> %s",
rsc->id, node->details->uname, attr_list[attr_lpc],
crm_str(old_value), crm_str(value));
}
}
if (force_restart) {
/* make sure the restart happens */
stop_action(rsc, node, FALSE);
set_bit(rsc->flags, pe_rsc_start_pending);
delete_resource = TRUE;
} else if (changed) {
delete_resource = TRUE;
}
return delete_resource;
}
static void
CancelXmlOp(resource_t * rsc, xmlNode * xml_op, node_t * active_node,
const char *reason, pe_working_set_t * data_set)
{
int interval = 0;
action_t *cancel = NULL;
char *key = NULL;
const char *task = NULL;
const char *call_id = NULL;
const char *interval_s = NULL;
CRM_CHECK(xml_op != NULL, return);
CRM_CHECK(active_node != NULL, return);
task = crm_element_value(xml_op, XML_LRM_ATTR_TASK);
call_id = crm_element_value(xml_op, XML_LRM_ATTR_CALLID);
interval_s = crm_element_value(xml_op, XML_LRM_ATTR_INTERVAL);
interval = crm_parse_int(interval_s, "0");
/* we need to reconstruct the key because of the way we used to construct resource IDs */
key = generate_op_key(rsc->id, task, interval);
crm_info("Action %s on %s will be stopped: %s",
key, active_node->details->uname, reason ? reason : "unknown");
/* TODO: This looks highly dangerous if we ever try to schedule 'key' too */
cancel = custom_action(rsc, strdup(key), RSC_CANCEL, active_node, FALSE, TRUE, data_set);
free(cancel->task);
free(cancel->cancel_task);
cancel->task = strdup(RSC_CANCEL);
cancel->cancel_task = strdup(task);
add_hash_param(cancel->meta, XML_LRM_ATTR_TASK, task);
add_hash_param(cancel->meta, XML_LRM_ATTR_CALLID, call_id);
add_hash_param(cancel->meta, XML_LRM_ATTR_INTERVAL, interval_s);
custom_action_order(rsc, stop_key(rsc), NULL, rsc, NULL, cancel, pe_order_optional, data_set);
free(key);
key = NULL;
}
static gboolean
check_action_definition(resource_t * rsc, node_t * active_node, xmlNode * xml_op,
pe_working_set_t * data_set)
{
char *key = NULL;
int interval = 0;
const char *interval_s = NULL;
const op_digest_cache_t *digest_data = NULL;
gboolean did_change = FALSE;
const char *task = crm_element_value(xml_op, XML_LRM_ATTR_TASK);
const char *op_version;
const char *digest_secure = NULL;
CRM_CHECK(active_node != NULL, return FALSE);
if (safe_str_eq(task, RSC_STOP)) {
return FALSE;
}
interval_s = crm_element_value(xml_op, XML_LRM_ATTR_INTERVAL);
interval = crm_parse_int(interval_s, "0");
if (interval > 0) {
xmlNode *op_match = NULL;
/* we need to reconstruct the key because of the way we used to construct resource IDs */
key = generate_op_key(rsc->id, task, interval);
pe_rsc_trace(rsc, "Checking parameters for %s", key);
op_match = find_rsc_op_entry(rsc, key);
if (op_match == NULL && is_set(data_set->flags, pe_flag_stop_action_orphans)) {
CancelXmlOp(rsc, xml_op, active_node, "orphan", data_set);
free(key);
return TRUE;
} else if (op_match == NULL) {
pe_rsc_debug(rsc, "Orphan action detected: %s on %s", key, active_node->details->uname);
free(key);
return TRUE;
}
free(key);
key = NULL;
}
crm_trace("Testing %s_%s_%d on %s",
rsc->id, task, interval, active_node->details->uname);
if (interval == 0 && safe_str_eq(task, RSC_STATUS)) {
/* Reload based on the start action not a probe */
task = RSC_START;
} else if (interval == 0 && safe_str_eq(task, RSC_MIGRATED)) {
/* Reload based on the start action not a migrate */
task = RSC_START;
} else if (interval == 0 && safe_str_eq(task, RSC_PROMOTE)) {
/* Reload based on the start action not a promote */
task = RSC_START;
}
op_version = crm_element_value(xml_op, XML_ATTR_CRM_VERSION);
digest_data = rsc_action_digest_cmp(rsc, xml_op, active_node, data_set);
if(is_set(data_set->flags, pe_flag_sanitized)) {
digest_secure = crm_element_value(xml_op, XML_LRM_ATTR_SECURE_DIGEST);
}
if(digest_data->rc != RSC_DIGEST_MATCH
&& digest_secure
&& digest_data->digest_secure_calc
&& strcmp(digest_data->digest_secure_calc, digest_secure) == 0) {
fprintf(stdout, "Only 'private' parameters to %s_%s_%d on %s changed: %s\n",
rsc->id, task, interval, active_node->details->uname,
crm_element_value(xml_op, XML_ATTR_TRANSITION_MAGIC));
} else if (digest_data->rc == RSC_DIGEST_RESTART) {
/* Changes that force a restart */
const char *digest_restart = crm_element_value(xml_op, XML_LRM_ATTR_RESTART_DIGEST);
did_change = TRUE;
key = generate_op_key(rsc->id, task, interval);
crm_log_xml_info(digest_data->params_restart, "params:restart");
pe_rsc_info(rsc, "Parameters to %s on %s changed: was %s vs. now %s (restart:%s) %s",
key, active_node->details->uname,
crm_str(digest_restart), digest_data->digest_restart_calc,
op_version, crm_element_value(xml_op, XML_ATTR_TRANSITION_MAGIC));
custom_action(rsc, key, task, NULL, FALSE, TRUE, data_set);
trigger_unfencing(rsc, NULL, "Device parameters changed", NULL, data_set);
} else if ((digest_data->rc == RSC_DIGEST_ALL) || (digest_data->rc == RSC_DIGEST_UNKNOWN)) {
/* Changes that can potentially be handled by a reload */
const char *digest_restart = crm_element_value(xml_op, XML_LRM_ATTR_RESTART_DIGEST);
const char *digest_all = crm_element_value(xml_op, XML_LRM_ATTR_OP_DIGEST);
did_change = TRUE;
trigger_unfencing(rsc, NULL, "Device parameters changed (reload)", NULL, data_set);
crm_log_xml_info(digest_data->params_all, "params:reload");
key = generate_op_key(rsc->id, task, interval);
pe_rsc_info(rsc, "Parameters to %s on %s changed: was %s vs. now %s (reload:%s) %s",
key, active_node->details->uname,
crm_str(digest_all), digest_data->digest_all_calc, op_version,
crm_element_value(xml_op, XML_ATTR_TRANSITION_MAGIC));
if (interval > 0) {
action_t *op = NULL;
#if 0
/* Always reload/restart the entire resource */
ReloadRsc(rsc, active_node, data_set);
#else
/* Re-sending the recurring op is sufficient - the old one will be cancelled automatically */
op = custom_action(rsc, key, task, active_node, TRUE, TRUE, data_set);
set_bit(op->flags, pe_action_reschedule);
#endif
} else if (digest_restart && rsc->isolation_wrapper == NULL && (uber_parent(rsc))->isolation_wrapper == NULL) {
pe_rsc_trace(rsc, "Reloading '%s' action for resource %s", task, rsc->id);
/* Reload this resource */
ReloadRsc(rsc, active_node, data_set);
free(key);
} else {
pe_rsc_trace(rsc, "Resource %s doesn't know how to reload", rsc->id);
/* Re-send the start/demote/promote op
* Recurring ops will be detected independently
*/
custom_action(rsc, key, task, NULL, FALSE, TRUE, data_set);
}
}
return did_change;
}
static void
check_actions_for(xmlNode * rsc_entry, resource_t * rsc, node_t * node, pe_working_set_t * data_set)
{
GListPtr gIter = NULL;
int offset = -1;
int interval = 0;
int stop_index = 0;
int start_index = 0;
const char *task = NULL;
const char *interval_s = NULL;
xmlNode *rsc_op = NULL;
GListPtr op_list = NULL;
GListPtr sorted_op_list = NULL;
gboolean is_probe = FALSE;
gboolean did_change = FALSE;
CRM_CHECK(node != NULL, return);
if (is_set(rsc->flags, pe_rsc_orphan)) {
resource_t *parent = uber_parent(rsc);
if(parent == NULL
|| pe_rsc_is_clone(parent) == FALSE
|| is_set(parent->flags, pe_rsc_unique)) {
pe_rsc_trace(rsc, "Skipping param check for %s and deleting: orphan", rsc->id);
DeleteRsc(rsc, node, FALSE, data_set);
} else {
pe_rsc_trace(rsc, "Skipping param check for %s (orphan clone)", rsc->id);
}
return;
} else if (pe_find_node_id(rsc->running_on, node->details->id) == NULL) {
if (check_rsc_parameters(rsc, node, rsc_entry, FALSE, data_set)) {
DeleteRsc(rsc, node, FALSE, data_set);
}
pe_rsc_trace(rsc, "Skipping param check for %s: no longer active on %s",
rsc->id, node->details->uname);
return;
}
pe_rsc_trace(rsc, "Processing %s on %s", rsc->id, node->details->uname);
if (check_rsc_parameters(rsc, node, rsc_entry, TRUE, data_set)) {
DeleteRsc(rsc, node, FALSE, data_set);
}
for (rsc_op = __xml_first_child(rsc_entry); rsc_op != NULL; rsc_op = __xml_next_element(rsc_op)) {
if (crm_str_eq((const char *)rsc_op->name, XML_LRM_TAG_RSC_OP, TRUE)) {
op_list = g_list_prepend(op_list, rsc_op);
}
}
sorted_op_list = g_list_sort(op_list, sort_op_by_callid);
calculate_active_ops(sorted_op_list, &start_index, &stop_index);
for (gIter = sorted_op_list; gIter != NULL; gIter = gIter->next) {
xmlNode *rsc_op = (xmlNode *) gIter->data;
offset++;
if (start_index < stop_index) {
/* stopped */
continue;
} else if (offset < start_index) {
/* action occurred prior to a start */
continue;
}
is_probe = FALSE;
did_change = FALSE;
task = crm_element_value(rsc_op, XML_LRM_ATTR_TASK);
interval_s = crm_element_value(rsc_op, XML_LRM_ATTR_INTERVAL);
interval = crm_parse_int(interval_s, "0");
if (interval == 0 && safe_str_eq(task, RSC_STATUS)) {
is_probe = TRUE;
}
if (interval > 0 &&
(is_set(rsc->flags, pe_rsc_maintenance) || node->details->maintenance)) {
CancelXmlOp(rsc, rsc_op, node, "maintenance mode", data_set);
} else if (is_probe || safe_str_eq(task, RSC_START) || safe_str_eq(task, RSC_PROMOTE) || interval > 0
|| safe_str_eq(task, RSC_MIGRATED)) {
did_change = check_action_definition(rsc, node, rsc_op, data_set);
}
if (did_change && get_failcount(node, rsc, NULL, data_set)) {
char *key = NULL;
action_t *action_clear = NULL;
key = generate_op_key(rsc->id, CRM_OP_CLEAR_FAILCOUNT, 0);
action_clear =
custom_action(rsc, key, CRM_OP_CLEAR_FAILCOUNT, node, FALSE, TRUE, data_set);
set_bit(action_clear->flags, pe_action_runnable);
crm_notice("Clearing failure of %s on %s "
"because action definition changed " CRM_XS " %s",
rsc->id, node->details->uname, action_clear->uuid);
}
}
g_list_free(sorted_op_list);
}
static GListPtr
find_rsc_list(GListPtr result, resource_t * rsc, const char *id, gboolean renamed_clones,
gboolean partial, pe_working_set_t * data_set)
{
GListPtr gIter = NULL;
gboolean match = FALSE;
if (id == NULL) {
return NULL;
} else if (rsc == NULL && data_set) {
for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
resource_t *child = (resource_t *) gIter->data;
result = find_rsc_list(result, child, id, renamed_clones, partial, NULL);
}
return result;
} else if (rsc == NULL) {
return NULL;
}
if (partial) {
if (strstr(rsc->id, id)) {
match = TRUE;
} else if (renamed_clones && rsc->clone_name && strstr(rsc->clone_name, id)) {
match = TRUE;
}
} else {
if (strcmp(rsc->id, id) == 0) {
match = TRUE;
} else if (renamed_clones && rsc->clone_name && strcmp(rsc->clone_name, id) == 0) {
match = TRUE;
}
}
if (match) {
result = g_list_prepend(result, rsc);
}
if (rsc->children) {
gIter = rsc->children;
for (; gIter != NULL; gIter = gIter->next) {
resource_t *child = (resource_t *) gIter->data;
result = find_rsc_list(result, child, id, renamed_clones, partial, NULL);
}
}
return result;
}
static void
check_actions(pe_working_set_t * data_set)
{
const char *id = NULL;
node_t *node = NULL;
xmlNode *lrm_rscs = NULL;
xmlNode *status = get_object_root(XML_CIB_TAG_STATUS, data_set->input);
xmlNode *node_state = NULL;
for (node_state = __xml_first_child(status); node_state != NULL;
node_state = __xml_next_element(node_state)) {
if (crm_str_eq((const char *)node_state->name, XML_CIB_TAG_STATE, TRUE)) {
id = crm_element_value(node_state, XML_ATTR_ID);
lrm_rscs = find_xml_node(node_state, XML_CIB_TAG_LRM, FALSE);
lrm_rscs = find_xml_node(lrm_rscs, XML_LRM_TAG_RESOURCES, FALSE);
node = pe_find_node_id(data_set->nodes, id);
if (node == NULL) {
continue;
/* Still need to check actions for a maintenance node to cancel existing monitor operations */
} else if (can_run_resources(node) == FALSE && node->details->maintenance == FALSE) {
crm_trace("Skipping param check for %s: can't run resources",
node->details->uname);
continue;
}
crm_trace("Processing node %s", node->details->uname);
if (node->details->online || is_set(data_set->flags, pe_flag_stonith_enabled)) {
xmlNode *rsc_entry = NULL;
for (rsc_entry = __xml_first_child(lrm_rscs); rsc_entry != NULL;
rsc_entry = __xml_next_element(rsc_entry)) {
if (crm_str_eq((const char *)rsc_entry->name, XML_LRM_TAG_RESOURCE, TRUE)) {
if (xml_has_children(rsc_entry)) {
GListPtr gIter = NULL;
GListPtr result = NULL;
const char *rsc_id = ID(rsc_entry);
CRM_CHECK(rsc_id != NULL, return);
result = find_rsc_list(NULL, NULL, rsc_id, TRUE, FALSE, data_set);
for (gIter = result; gIter != NULL; gIter = gIter->next) {
resource_t *rsc = (resource_t *) gIter->data;
if (rsc->variant != pe_native) {
continue;
}
check_actions_for(rsc_entry, rsc, node, data_set);
}
g_list_free(result);
}
}
}
}
}
}
}
static gboolean
apply_placement_constraints(pe_working_set_t * data_set)
{
GListPtr gIter = NULL;
crm_trace("Applying constraints...");
for (gIter = data_set->placement_constraints; gIter != NULL; gIter = gIter->next) {
rsc_to_node_t *cons = (rsc_to_node_t *) gIter->data;
cons->rsc_lh->cmds->rsc_location(cons->rsc_lh, cons);
}
return TRUE;
}
static gboolean
failcount_clear_action_exists(node_t * node, resource_t * rsc)
{
gboolean rc = FALSE;
char *key = generate_op_key(rsc->id, CRM_OP_CLEAR_FAILCOUNT, 0);
GListPtr list = find_actions_exact(rsc->actions, key, node);
if (list) {
rc = TRUE;
}
g_list_free(list);
free(key);
return rc;
}
/*!
* \internal
* \brief Force resource away if failures hit migration threshold
*
* \param[in,out] rsc Resource to check for failures
* \param[in,out] node Node to check for failures
* \param[in,out] data_set Cluster working set to update
*/
static void
check_migration_threshold(resource_t *rsc, node_t *node,
pe_working_set_t *data_set)
{
int fail_count, countdown;
resource_t *failed;
/* Migration threshold of 0 means never force away */
if (rsc->migration_threshold == 0) {
return;
}
/* If there are no failures, there's no need to force away */
fail_count = get_failcount_all(node, rsc, NULL, data_set);
if (fail_count <= 0) {
return;
}
/* How many more times recovery will be tried on this node */
countdown = QB_MAX(rsc->migration_threshold - fail_count, 0);
/* If failed resource has a parent, we'll force the parent away */
failed = rsc;
if (is_not_set(rsc->flags, pe_rsc_unique)) {
failed = uber_parent(rsc);
}
if (countdown == 0) {
resource_location(failed, node, -INFINITY, "__fail_limit__", data_set);
crm_warn("Forcing %s away from %s after %d failures (max=%d)",
failed->id, node->details->uname, fail_count,
rsc->migration_threshold);
} else {
crm_info("%s can fail %d more times on %s before being forced off",
failed->id, countdown, node->details->uname);
}
}
static void
common_apply_stickiness(resource_t * rsc, node_t * node, pe_working_set_t * data_set)
{
if (rsc->children) {
GListPtr gIter = rsc->children;
for (; gIter != NULL; gIter = gIter->next) {
resource_t *child_rsc = (resource_t *) gIter->data;
common_apply_stickiness(child_rsc, node, data_set);
}
return;
}
if (is_set(rsc->flags, pe_rsc_managed)
&& rsc->stickiness != 0 && g_list_length(rsc->running_on) == 1) {
node_t *current = pe_find_node_id(rsc->running_on, node->details->id);
node_t *match = pe_hash_table_lookup(rsc->allowed_nodes, node->details->id);
if (current == NULL) {
} else if (match != NULL || is_set(data_set->flags, pe_flag_symmetric_cluster)) {
resource_t *sticky_rsc = rsc;
resource_location(sticky_rsc, node, rsc->stickiness, "stickiness", data_set);
pe_rsc_debug(sticky_rsc, "Resource %s: preferring current location"
" (node=%s, weight=%d)", sticky_rsc->id,
node->details->uname, rsc->stickiness);
} else {
GHashTableIter iter;
node_t *nIter = NULL;
pe_rsc_debug(rsc, "Ignoring stickiness for %s: the cluster is asymmetric"
" and node %s is not explicitly allowed", rsc->id, node->details->uname);
g_hash_table_iter_init(&iter, rsc->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (void **)&nIter)) {
crm_err("%s[%s] = %d", rsc->id, nIter->details->uname, nIter->weight);
}
}
}
/* Check the migration threshold only if a failcount clear action
* has not already been placed for this resource on the node.
* There is no sense in potentially forcing the resource from this
* node if the failcount is being reset anyway. */
if (failcount_clear_action_exists(node, rsc) == FALSE) {
check_migration_threshold(rsc, node, data_set);
}
}
void
complex_set_cmds(resource_t * rsc)
{
GListPtr gIter = rsc->children;
rsc->cmds = &resource_class_alloc_functions[rsc->variant];
for (; gIter != NULL; gIter = gIter->next) {
resource_t *child_rsc = (resource_t *) gIter->data;
complex_set_cmds(child_rsc);
}
}
void
set_alloc_actions(pe_working_set_t * data_set)
{
GListPtr gIter = data_set->resources;
for (; gIter != NULL; gIter = gIter->next) {
resource_t *rsc = (resource_t *) gIter->data;
complex_set_cmds(rsc);
}
}
static void
calculate_system_health(gpointer gKey, gpointer gValue, gpointer user_data)
{
const char *key = (const char *)gKey;
const char *value = (const char *)gValue;
int *system_health = (int *)user_data;
if (!gKey || !gValue || !user_data) {
return;
}
/* Does it start with #health? */
if (0 == strncmp(key, "#health", 7)) {
int score;
/* Convert the value into an integer */
score = char2score(value);
/* Add it to the running total */
*system_health = merge_weights(score, *system_health);
}
}
static gboolean
apply_system_health(pe_working_set_t * data_set)
{
GListPtr gIter = NULL;
const char *health_strategy = pe_pref(data_set->config_hash, "node-health-strategy");
int base_health = 0;
if (health_strategy == NULL || safe_str_eq(health_strategy, "none")) {
/* Prevent any accidental health -> score translation */
node_score_red = 0;
node_score_yellow = 0;
node_score_green = 0;
return TRUE;
} else if (safe_str_eq(health_strategy, "migrate-on-red")) {
/* Resources on nodes which have health values of red are
* weighted away from that node.
*/
node_score_red = -INFINITY;
node_score_yellow = 0;
node_score_green = 0;
} else if (safe_str_eq(health_strategy, "only-green")) {
/* Resources on nodes which have health values of red or yellow
* are forced away from that node.
*/
node_score_red = -INFINITY;
node_score_yellow = -INFINITY;
node_score_green = 0;
} else if (safe_str_eq(health_strategy, "progressive")) {
/* Same as the above, but use the r/y/g scores provided by the user
* Defaults are provided by the pe_prefs table
* Also, custom health "base score" can be used
*/
base_health = crm_parse_int(pe_pref(data_set->config_hash, "node-health-base"), "0");
} else if (safe_str_eq(health_strategy, "custom")) {
/* Requires the admin to configure the rsc_location constaints for
* processing the stored health scores
*/
/* TODO: Check for the existence of appropriate node health constraints */
return TRUE;
} else {
crm_err("Unknown node health strategy: %s", health_strategy);
return FALSE;
}
crm_info("Applying automated node health strategy: %s", health_strategy);
for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
int system_health = base_health;
node_t *node = (node_t *) gIter->data;
/* Search through the node hash table for system health entries. */
g_hash_table_foreach(node->details->attrs, calculate_system_health, &system_health);
crm_info(" Node %s has an combined system health of %d",
node->details->uname, system_health);
/* If the health is non-zero, then create a new rsc2node so that the
* weight will be added later on.
*/
if (system_health != 0) {
GListPtr gIter2 = data_set->resources;
for (; gIter2 != NULL; gIter2 = gIter2->next) {
resource_t *rsc = (resource_t *) gIter2->data;
rsc2node_new(health_strategy, rsc, system_health, NULL, node, data_set);
}
}
}
return TRUE;
}
gboolean
stage0(pe_working_set_t * data_set)
{
xmlNode *cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, data_set->input);
if (data_set->input == NULL) {
return FALSE;
}
if (is_set(data_set->flags, pe_flag_have_status) == FALSE) {
crm_trace("Calculating status");
cluster_status(data_set);
}
set_alloc_actions(data_set);
apply_system_health(data_set);
unpack_constraints(cib_constraints, data_set);
return TRUE;
}
/*
* Check nodes for resources started outside of the LRM
*/
gboolean
probe_resources(pe_working_set_t * data_set)
{
action_t *probe_node_complete = NULL;
for (GListPtr gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
node_t *node = (node_t *) gIter->data;
const char *probed = g_hash_table_lookup(node->details->attrs, CRM_OP_PROBED);
if (is_container_remote_node(node)) {
/* TODO enable guest node probes once ordered probing is implemented */
continue;
} else if (node->details->online == FALSE && node->details->remote_rsc) {
+ // TODO figure out why this results in fence loop
+ /*
enum remote_connection_state state = get_remote_node_state(node);
if(state == remote_state_failed) {
pe_fence_node(data_set, node, "the connection is unrecoverable");
}
+ */
continue;
} else if(node->details->online == FALSE) {
continue;
} else if (node->details->unclean) {
continue;
} else if (node->details->rsc_discovery_enabled == FALSE) {
/* resource discovery is disabled for this node */
continue;
}
if (probed != NULL && crm_is_true(probed) == FALSE) {
action_t *probe_op = custom_action(NULL, crm_strdup_printf("%s-%s", CRM_OP_REPROBE, node->details->uname),
CRM_OP_REPROBE, node, FALSE, TRUE, data_set);
add_hash_param(probe_op->meta, XML_ATTR_TE_NOWAIT, XML_BOOLEAN_TRUE);
continue;
}
for (GListPtr gIter2 = data_set->resources; gIter2 != NULL; gIter2 = gIter2->next) {
resource_t *rsc = (resource_t *) gIter2->data;
rsc->cmds->create_probe(rsc, node, probe_node_complete, FALSE, data_set);
}
}
return TRUE;
}
static void
rsc_discover_filter(resource_t *rsc, node_t *node)
{
GListPtr gIter = rsc->children;
resource_t *top = uber_parent(rsc);
node_t *match;
if (rsc->exclusive_discover == FALSE && top->exclusive_discover == FALSE) {
return;
}
for (; gIter != NULL; gIter = gIter->next) {
resource_t *child_rsc = (resource_t *) gIter->data;
rsc_discover_filter(child_rsc, node);
}
match = g_hash_table_lookup(rsc->allowed_nodes, node->details->id);
if (match && match->rsc_discover_mode != discover_exclusive) {
match->weight = -INFINITY;
}
}
/*
* Count how many valid nodes we have (so we know the maximum number of
* colors we can resolve).
*
* Apply node constraints (ie. filter the "allowed_nodes" part of resources
*/
gboolean
stage2(pe_working_set_t * data_set)
{
GListPtr gIter = NULL;
crm_trace("Applying placement constraints");
gIter = data_set->nodes;
for (; gIter != NULL; gIter = gIter->next) {
node_t *node = (node_t *) gIter->data;
if (node == NULL) {
/* error */
} else if (node->weight >= 0.0 /* global weight */
&& node->details->online && node->details->type != node_ping) {
data_set->max_valid_nodes++;
}
}
apply_placement_constraints(data_set);
gIter = data_set->nodes;
for (; gIter != NULL; gIter = gIter->next) {
GListPtr gIter2 = NULL;
node_t *node = (node_t *) gIter->data;
gIter2 = data_set->resources;
for (; gIter2 != NULL; gIter2 = gIter2->next) {
resource_t *rsc = (resource_t *) gIter2->data;
common_apply_stickiness(rsc, node, data_set);
rsc_discover_filter(rsc, node);
}
}
return TRUE;
}
/*
* Create internal resource constraints before allocation
*/
gboolean
stage3(pe_working_set_t * data_set)
{
GListPtr gIter = data_set->resources;
for (; gIter != NULL; gIter = gIter->next) {
resource_t *rsc = (resource_t *) gIter->data;
rsc->cmds->internal_constraints(rsc, data_set);
}
return TRUE;
}
/*
* Check for orphaned or redefined actions
*/
gboolean
stage4(pe_working_set_t * data_set)
{
check_actions(data_set);
return TRUE;
}
static gint
sort_rsc_process_order(gconstpointer a, gconstpointer b, gpointer data)
{
int rc = 0;
int r1_weight = -INFINITY;
int r2_weight = -INFINITY;
const char *reason = "existence";
const GListPtr nodes = (GListPtr) data;
resource_t *resource1 = (resource_t *) convert_const_pointer(a);
resource_t *resource2 = (resource_t *) convert_const_pointer(b);
node_t *r1_node = NULL;
node_t *r2_node = NULL;
GListPtr gIter = NULL;
GHashTable *r1_nodes = NULL;
GHashTable *r2_nodes = NULL;
if (a == NULL && b == NULL) {
goto done;
}
if (a == NULL) {
return 1;
}
if (b == NULL) {
return -1;
}
reason = "priority";
r1_weight = resource1->priority;
r2_weight = resource2->priority;
if (r1_weight > r2_weight) {
rc = -1;
goto done;
}
if (r1_weight < r2_weight) {
rc = 1;
goto done;
}
reason = "no node list";
if (nodes == NULL) {
goto done;
}
r1_nodes =
rsc_merge_weights(resource1, resource1->id, NULL, NULL, 1,
pe_weights_forward | pe_weights_init);
dump_node_scores(LOG_TRACE, NULL, resource1->id, r1_nodes);
r2_nodes =
rsc_merge_weights(resource2, resource2->id, NULL, NULL, 1,
pe_weights_forward | pe_weights_init);
dump_node_scores(LOG_TRACE, NULL, resource2->id, r2_nodes);
/* Current location score */
reason = "current location";
r1_weight = -INFINITY;
r2_weight = -INFINITY;
if (resource1->running_on) {
r1_node = g_list_nth_data(resource1->running_on, 0);
r1_node = g_hash_table_lookup(r1_nodes, r1_node->details->id);
if (r1_node != NULL) {
r1_weight = r1_node->weight;
}
}
if (resource2->running_on) {
r2_node = g_list_nth_data(resource2->running_on, 0);
r2_node = g_hash_table_lookup(r2_nodes, r2_node->details->id);
if (r2_node != NULL) {
r2_weight = r2_node->weight;
}
}
if (r1_weight > r2_weight) {
rc = -1;
goto done;
}
if (r1_weight < r2_weight) {
rc = 1;
goto done;
}
reason = "score";
for (gIter = nodes; gIter != NULL; gIter = gIter->next) {
node_t *node = (node_t *) gIter->data;
r1_node = NULL;
r2_node = NULL;
r1_weight = -INFINITY;
if (r1_nodes) {
r1_node = g_hash_table_lookup(r1_nodes, node->details->id);
}
if (r1_node) {
r1_weight = r1_node->weight;
}
r2_weight = -INFINITY;
if (r2_nodes) {
r2_node = g_hash_table_lookup(r2_nodes, node->details->id);
}
if (r2_node) {
r2_weight = r2_node->weight;
}
if (r1_weight > r2_weight) {
rc = -1;
goto done;
}
if (r1_weight < r2_weight) {
rc = 1;
goto done;
}
}
done:
crm_trace("%s (%d) on %s %c %s (%d) on %s: %s",
resource1->id, r1_weight, r1_node ? r1_node->details->id : "n/a",
rc < 0 ? '>' : rc > 0 ? '<' : '=',
resource2->id, r2_weight, r2_node ? r2_node->details->id : "n/a", reason);
if (r1_nodes) {
g_hash_table_destroy(r1_nodes);
}
if (r2_nodes) {
g_hash_table_destroy(r2_nodes);
}
return rc;
}
static void
allocate_resources(pe_working_set_t * data_set)
{
GListPtr gIter = NULL;
if (is_set(data_set->flags, pe_flag_have_remote_nodes)) {
/* Force remote connection resources to be allocated first. This
* also forces any colocation dependencies to be allocated as well */
for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
resource_t *rsc = (resource_t *) gIter->data;
if (rsc->is_remote_node == FALSE) {
continue;
}
pe_rsc_trace(rsc, "Allocating: %s", rsc->id);
/* For remote node connection resources, always prefer the partial
* migration target during resource allocation, if the rsc is in the
* middle of a migration.
*/
rsc->cmds->allocate(rsc, rsc->partial_migration_target, data_set);
}
}
/* now do the rest of the resources */
for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
resource_t *rsc = (resource_t *) gIter->data;
if (rsc->is_remote_node == TRUE) {
continue;
}
pe_rsc_trace(rsc, "Allocating: %s", rsc->id);
rsc->cmds->allocate(rsc, NULL, data_set);
}
}
static void
cleanup_orphans(resource_t * rsc, pe_working_set_t * data_set)
{
GListPtr gIter = NULL;
if (is_set(data_set->flags, pe_flag_stop_rsc_orphans) == FALSE) {
return;
}
/* Don't recurse into ->children, those are just unallocated clone instances */
if(is_not_set(rsc->flags, pe_rsc_orphan)) {
return;
}
for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
node_t *node = (node_t *) gIter->data;
if (node->details->online && get_failcount(node, rsc, NULL, data_set)) {
char *key = generate_op_key(rsc->id, CRM_OP_CLEAR_FAILCOUNT, 0);
action_t *clear_op = custom_action(rsc, key, CRM_OP_CLEAR_FAILCOUNT,
node, FALSE, TRUE, data_set);
add_hash_param(clear_op->meta, XML_ATTR_TE_NOWAIT, XML_BOOLEAN_TRUE);
pe_rsc_info(rsc,
"Clearing failure of %s on %s because it is orphaned "
CRM_XS " %s",
rsc->id, node->details->uname, clear_op->uuid);
custom_action_order(rsc, NULL, clear_op,
rsc, generate_op_key(rsc->id, RSC_STOP, 0), NULL,
pe_order_optional, data_set);
}
}
}
gboolean
stage5(pe_working_set_t * data_set)
{
GListPtr gIter = NULL;
if (safe_str_neq(data_set->placement_strategy, "default")) {
GListPtr nodes = g_list_copy(data_set->nodes);
nodes = g_list_sort_with_data(nodes, sort_node_weight, NULL);
data_set->resources =
g_list_sort_with_data(data_set->resources, sort_rsc_process_order, nodes);
g_list_free(nodes);
}
gIter = data_set->nodes;
for (; gIter != NULL; gIter = gIter->next) {
node_t *node = (node_t *) gIter->data;
dump_node_capacity(show_utilization ? 0 : utilization_log_level, "Original", node);
}
crm_trace("Allocating services");
/* Take (next) highest resource, assign it and create its actions */
allocate_resources(data_set);
gIter = data_set->nodes;
for (; gIter != NULL; gIter = gIter->next) {
node_t *node = (node_t *) gIter->data;
dump_node_capacity(show_utilization ? 0 : utilization_log_level, "Remaining", node);
}
if (is_set(data_set->flags, pe_flag_startup_probes)) {
crm_trace("Calculating needed probes");
/* This code probably needs optimization
* ptest -x with 100 nodes, 100 clones and clone-max=100:
With probes:
ptest[14781]: 2010/09/27_17:56:46 notice: TRACE: do_calculations: pengine.c:258 Calculate cluster status
ptest[14781]: 2010/09/27_17:56:46 notice: TRACE: do_calculations: pengine.c:278 Applying placement constraints
ptest[14781]: 2010/09/27_17:56:47 notice: TRACE: do_calculations: pengine.c:285 Create internal constraints
ptest[14781]: 2010/09/27_17:56:47 notice: TRACE: do_calculations: pengine.c:292 Check actions
ptest[14781]: 2010/09/27_17:56:48 notice: TRACE: do_calculations: pengine.c:299 Allocate resources
ptest[14781]: 2010/09/27_17:56:48 notice: TRACE: stage5: allocate.c:881 Allocating services
ptest[14781]: 2010/09/27_17:56:49 notice: TRACE: stage5: allocate.c:894 Calculating needed probes
ptest[14781]: 2010/09/27_17:56:51 notice: TRACE: stage5: allocate.c:899 Creating actions
ptest[14781]: 2010/09/27_17:56:52 notice: TRACE: stage5: allocate.c:905 Creating done
ptest[14781]: 2010/09/27_17:56:52 notice: TRACE: do_calculations: pengine.c:306 Processing fencing and shutdown cases
ptest[14781]: 2010/09/27_17:56:52 notice: TRACE: do_calculations: pengine.c:313 Applying ordering constraints
36s
ptest[14781]: 2010/09/27_17:57:28 notice: TRACE: do_calculations: pengine.c:320 Create transition graph
Without probes:
ptest[14637]: 2010/09/27_17:56:21 notice: TRACE: do_calculations: pengine.c:258 Calculate cluster status
ptest[14637]: 2010/09/27_17:56:22 notice: TRACE: do_calculations: pengine.c:278 Applying placement constraints
ptest[14637]: 2010/09/27_17:56:22 notice: TRACE: do_calculations: pengine.c:285 Create internal constraints
ptest[14637]: 2010/09/27_17:56:22 notice: TRACE: do_calculations: pengine.c:292 Check actions
ptest[14637]: 2010/09/27_17:56:23 notice: TRACE: do_calculations: pengine.c:299 Allocate resources
ptest[14637]: 2010/09/27_17:56:23 notice: TRACE: stage5: allocate.c:881 Allocating services
ptest[14637]: 2010/09/27_17:56:24 notice: TRACE: stage5: allocate.c:899 Creating actions
ptest[14637]: 2010/09/27_17:56:25 notice: TRACE: stage5: allocate.c:905 Creating done
ptest[14637]: 2010/09/27_17:56:25 notice: TRACE: do_calculations: pengine.c:306 Processing fencing and shutdown cases
ptest[14637]: 2010/09/27_17:56:25 notice: TRACE: do_calculations: pengine.c:313 Applying ordering constraints
ptest[14637]: 2010/09/27_17:56:25 notice: TRACE: do_calculations: pengine.c:320 Create transition graph
*/
probe_resources(data_set);
}
crm_trace("Handle orphans");
for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
resource_t *rsc = (resource_t *) gIter->data;
cleanup_orphans(rsc, data_set);
}
crm_trace("Creating actions");
for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
resource_t *rsc = (resource_t *) gIter->data;
rsc->cmds->create_actions(rsc, data_set);
}
crm_trace("Creating done");
return TRUE;
}
static gboolean
is_managed(const resource_t * rsc)
{
GListPtr gIter = rsc->children;
if (is_set(rsc->flags, pe_rsc_managed)) {
return TRUE;
}
for (; gIter != NULL; gIter = gIter->next) {
resource_t *child_rsc = (resource_t *) gIter->data;
if (is_managed(child_rsc)) {
return TRUE;
}
}
return FALSE;
}
static gboolean
any_managed_resources(pe_working_set_t * data_set)
{
GListPtr gIter = data_set->resources;
for (; gIter != NULL; gIter = gIter->next) {
resource_t *rsc = (resource_t *) gIter->data;
if (is_managed(rsc)) {
return TRUE;
}
}
return FALSE;
}
/*!
* \internal
* \brief Create pseudo-op for guest node fence, and order relative to it
*
* \param[in] node Guest node to fence
* \param[in] done STONITH_DONE operation
* \param[in] data_set Working set of CIB state
*/
static void
fence_guest(pe_node_t *node, pe_action_t *done, pe_working_set_t *data_set)
{
resource_t *container = node->details->remote_rsc->container;
pe_action_t *stop = NULL;
pe_action_t *stonith_op = NULL;
/* The fence action is just a label; we don't do anything differently for
* off vs. reboot. We specify it explicitly, rather than let it default to
* cluster's default action, because we are not _initiating_ fencing -- we
* are creating a pseudo-event to describe fencing that is already occurring
* by other means (container recovery).
*/
const char *fence_action = "off";
/* Check whether guest's container resource is has any explicit stop or
* start (the stop may be implied by fencing of the guest's host).
*/
if (container) {
stop = find_first_action(container->actions, NULL, CRMD_ACTION_STOP, NULL);
if (find_first_action(container->actions, NULL, CRMD_ACTION_START, NULL)) {
fence_action = "reboot";
}
}
/* Create a fence pseudo-event, so we have an event to order actions
* against, and crmd can always detect it.
*/
stonith_op = pe_fence_op(node, fence_action, FALSE, data_set);
update_action_flags(stonith_op, pe_action_pseudo | pe_action_runnable,
__FUNCTION__, __LINE__);
/* We want to imply stops/demotes after the guest is stopped, not wait until
* it is restarted, so we always order pseudo-fencing after stop, not start
* (even though start might be closer to what is done for a real reboot).
*/
if (stop) {
order_actions(stop, stonith_op,
pe_order_runnable_left|pe_order_implies_then);
crm_info("Implying guest node %s is down (action %d) "
"after container %s is stopped (action %d)",
node->details->uname, stonith_op->id,
container->id, stop->id);
} else {
crm_info("Implying guest node %s is down (action %d) ",
node->details->uname, stonith_op->id);
}
/* @TODO: Order pseudo-fence after any (optional) fence of guest's host */
/* Order/imply other actions relative to pseudo-fence as with real fence */
stonith_constraints(node, stonith_op, data_set);
order_actions(stonith_op, done, pe_order_implies_then);
}
/*
* Create dependencies for stonith and shutdown operations
*/
gboolean
stage6(pe_working_set_t * data_set)
{
action_t *dc_down = NULL;
action_t *dc_fence = NULL;
action_t *stonith_op = NULL;
action_t *last_stonith = NULL;
gboolean integrity_lost = FALSE;
action_t *all_stopped = get_pseudo_op(ALL_STOPPED, data_set);
action_t *done = get_pseudo_op(STONITH_DONE, data_set);
gboolean need_stonith = TRUE;
GListPtr gIter;
GListPtr stonith_ops = NULL;
/* Remote ordering constraints need to happen prior to calculate
* fencing because it is one more place we will mark the node as
* dirty.
*
* A nice side-effect of doing it first is that we can remove a
* bunch of special logic from apply_*_ordering() because its
* already part of pe_fence_node()
*/
crm_trace("Creating remote ordering constraints");
apply_remote_node_ordering(data_set);
crm_trace("Processing fencing and shutdown cases");
if (any_managed_resources(data_set) == FALSE) {
crm_notice("Delaying fencing operations until there are resources to manage");
need_stonith = FALSE;
}
/* Check each node for stonith/shutdown */
for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
node_t *node = (node_t *) gIter->data;
/* Guest nodes are "fenced" by recovering their container resource,
* so handle them separately.
*/
if (is_container_remote_node(node)) {
if (node->details->remote_requires_reset && need_stonith) {
fence_guest(node, done, data_set);
}
continue;
}
stonith_op = NULL;
if (node->details->unclean
&& need_stonith && pe_can_fence(data_set, node)) {
pe_warn("Scheduling Node %s for STONITH", node->details->uname);
stonith_op = pe_fence_op(node, NULL, FALSE, data_set);
stonith_constraints(node, stonith_op, data_set);
if (node->details->is_dc) {
dc_down = stonith_op;
dc_fence = stonith_op;
} else if (is_set(data_set->flags, pe_flag_concurrent_fencing) == FALSE) {
if (last_stonith) {
order_actions(last_stonith, stonith_op, pe_order_optional);
}
last_stonith = stonith_op;
} else {
order_actions(stonith_op, done, pe_order_implies_then);
stonith_ops = g_list_append(stonith_ops, stonith_op);
}
} else if (node->details->online && node->details->shutdown &&
/* TODO define what a shutdown op means for a remote node.
* For now we do not send shutdown operations for remote nodes, but
* if we can come up with a good use for this in the future, we will. */
is_remote_node(node) == FALSE) {
action_t *down_op = NULL;
crm_notice("Scheduling Node %s for shutdown", node->details->uname);
down_op = custom_action(NULL, crm_strdup_printf("%s-%s", CRM_OP_SHUTDOWN, node->details->uname),
CRM_OP_SHUTDOWN, node, FALSE, TRUE, data_set);
shutdown_constraints(node, down_op, data_set);
add_hash_param(down_op->meta, XML_ATTR_TE_NOWAIT, XML_BOOLEAN_TRUE);
if (node->details->is_dc) {
dc_down = down_op;
}
}
if (node->details->unclean && stonith_op == NULL) {
integrity_lost = TRUE;
pe_warn("Node %s is unclean!", node->details->uname);
}
}
if (integrity_lost) {
if (is_set(data_set->flags, pe_flag_stonith_enabled) == FALSE) {
pe_warn("YOUR RESOURCES ARE NOW LIKELY COMPROMISED");
pe_err("ENABLE STONITH TO KEEP YOUR RESOURCES SAFE");
} else if (is_set(data_set->flags, pe_flag_have_quorum) == FALSE) {
crm_notice("Cannot fence unclean nodes until quorum is"
" attained (or no-quorum-policy is set to ignore)");
}
}
if (dc_down != NULL) {
GListPtr gIter = NULL;
crm_trace("Ordering shutdowns before %s on %s (DC)",
dc_down->task, dc_down->node->details->uname);
add_hash_param(dc_down->meta, XML_ATTR_TE_NOWAIT, XML_BOOLEAN_TRUE);
for (gIter = data_set->actions; gIter != NULL; gIter = gIter->next) {
action_t *node_stop = (action_t *) gIter->data;
if (safe_str_neq(CRM_OP_SHUTDOWN, node_stop->task)) {
continue;
} else if (node_stop->node->details->is_dc) {
continue;
}
crm_debug("Ordering shutdown on %s before %s on %s",
node_stop->node->details->uname,
dc_down->task, dc_down->node->details->uname);
order_actions(node_stop, dc_down, pe_order_optional);
}
if (last_stonith) {
if (dc_down != last_stonith) {
order_actions(last_stonith, dc_down, pe_order_optional);
}
} else {
GListPtr gIter2 = NULL;
for (gIter2 = stonith_ops; gIter2 != NULL; gIter2 = gIter2->next) {
stonith_op = (action_t *) gIter2->data;
if (dc_down != stonith_op) {
order_actions(stonith_op, dc_down, pe_order_optional);
}
}
}
}
if (dc_fence) {
order_actions(dc_down, done, pe_order_implies_then);
} else if (last_stonith) {
order_actions(last_stonith, done, pe_order_implies_then);
}
order_actions(done, all_stopped, pe_order_implies_then);
g_list_free(stonith_ops);
return TRUE;
}
/*
* Determine the sets of independent actions and the correct order for the
* actions in each set.
*
* Mark dependencies of un-runnable actions un-runnable
*
*/
static GListPtr
find_actions_by_task(GListPtr actions, resource_t * rsc, const char *original_key)
{
GListPtr list = NULL;
list = find_actions(actions, original_key, NULL);
if (list == NULL) {
/* we're potentially searching a child of the original resource */
char *key = NULL;
char *tmp = NULL;
char *task = NULL;
int interval = 0;
if (parse_op_key(original_key, &tmp, &task, &interval)) {
key = generate_op_key(rsc->id, task, interval);
/* crm_err("looking up %s instead of %s", key, original_key); */
/* slist_iter(action, action_t, actions, lpc, */
/* crm_err(" - %s", action->uuid)); */
list = find_actions(actions, key, NULL);
} else {
crm_err("search key: %s", original_key);
}
free(key);
free(tmp);
free(task);
}
return list;
}
static void
rsc_order_then(action_t * lh_action, resource_t * rsc, order_constraint_t * order)
{
GListPtr gIter = NULL;
GListPtr rh_actions = NULL;
action_t *rh_action = NULL;
enum pe_ordering type = order->type;
CRM_CHECK(rsc != NULL, return);
CRM_CHECK(order != NULL, return);
rh_action = order->rh_action;
crm_trace("Processing RH of ordering constraint %d", order->id);
if (rh_action != NULL) {
rh_actions = g_list_prepend(NULL, rh_action);
} else if (rsc != NULL) {
rh_actions = find_actions_by_task(rsc->actions, rsc, order->rh_action_task);
}
if (rh_actions == NULL) {
pe_rsc_trace(rsc, "No RH-Side (%s/%s) found for constraint..."
" ignoring", rsc->id, order->rh_action_task);
if (lh_action) {
pe_rsc_trace(rsc, "LH-Side was: %s", lh_action->uuid);
}
return;
}
if (lh_action && lh_action->rsc == rsc && is_set(lh_action->flags, pe_action_dangle)) {
pe_rsc_trace(rsc, "Detected dangling operation %s -> %s", lh_action->uuid,
order->rh_action_task);
clear_bit(type, pe_order_implies_then);
}
gIter = rh_actions;
for (; gIter != NULL; gIter = gIter->next) {
action_t *rh_action_iter = (action_t *) gIter->data;
if (lh_action) {
order_actions(lh_action, rh_action_iter, type);
} else if (type & pe_order_implies_then) {
update_action_flags(rh_action_iter, pe_action_runnable | pe_action_clear, __FUNCTION__, __LINE__);
crm_warn("Unrunnable %s 0x%.6x", rh_action_iter->uuid, type);
} else {
crm_warn("neither %s 0x%.6x", rh_action_iter->uuid, type);
}
}
g_list_free(rh_actions);
}
static void
rsc_order_first(resource_t * lh_rsc, order_constraint_t * order, pe_working_set_t * data_set)
{
GListPtr gIter = NULL;
GListPtr lh_actions = NULL;
action_t *lh_action = order->lh_action;
resource_t *rh_rsc = order->rh_rsc;
crm_trace("Processing LH of ordering constraint %d", order->id);
CRM_ASSERT(lh_rsc != NULL);
if (lh_action != NULL) {
lh_actions = g_list_prepend(NULL, lh_action);
} else if (lh_action == NULL) {
lh_actions = find_actions_by_task(lh_rsc->actions, lh_rsc, order->lh_action_task);
}
if (lh_actions == NULL && lh_rsc != rh_rsc) {
char *key = NULL;
char *rsc_id = NULL;
char *op_type = NULL;
int interval = 0;
parse_op_key(order->lh_action_task, &rsc_id, &op_type, &interval);
key = generate_op_key(lh_rsc->id, op_type, interval);
if (lh_rsc->fns->state(lh_rsc, TRUE) == RSC_ROLE_STOPPED && safe_str_eq(op_type, RSC_STOP)) {
free(key);
pe_rsc_trace(lh_rsc, "No LH-Side (%s/%s) found for constraint %d with %s - ignoring",
lh_rsc->id, order->lh_action_task, order->id, order->rh_action_task);
} else if (lh_rsc->fns->state(lh_rsc, TRUE) == RSC_ROLE_SLAVE && safe_str_eq(op_type, RSC_DEMOTE)) {
free(key);
pe_rsc_trace(lh_rsc, "No LH-Side (%s/%s) found for constraint %d with %s - ignoring",
lh_rsc->id, order->lh_action_task, order->id, order->rh_action_task);
} else {
pe_rsc_trace(lh_rsc, "No LH-Side (%s/%s) found for constraint %d with %s - creating",
lh_rsc->id, order->lh_action_task, order->id, order->rh_action_task);
lh_action = custom_action(lh_rsc, key, op_type, NULL, TRUE, TRUE, data_set);
lh_actions = g_list_prepend(NULL, lh_action);
}
free(op_type);
free(rsc_id);
}
gIter = lh_actions;
for (; gIter != NULL; gIter = gIter->next) {
action_t *lh_action_iter = (action_t *) gIter->data;
if (rh_rsc == NULL && order->rh_action) {
rh_rsc = order->rh_action->rsc;
}
if (rh_rsc) {
rsc_order_then(lh_action_iter, rh_rsc, order);
} else if (order->rh_action) {
order_actions(lh_action_iter, order->rh_action, order->type);
}
}
g_list_free(lh_actions);
}
extern gboolean update_action(action_t * action);
extern void update_colo_start_chain(action_t * action);
static int
is_recurring_action(action_t *action)
{
const char *interval_s = g_hash_table_lookup(action->meta, XML_LRM_ATTR_INTERVAL);
int interval = crm_parse_int(interval_s, "0");
if(interval > 0) {
return TRUE;
}
return FALSE;
}
static void
apply_container_ordering(action_t *action, pe_working_set_t *data_set)
{
/* VMs are also classified as containers for these purposes... in
* that they both involve a 'thing' running on a real or remote
* cluster node.
*
* This allows us to be smarter about the type and extent of
* recovery actions required in various scenarios
*/
resource_t *remote_rsc = NULL;
resource_t *container = NULL;
enum action_tasks task = text2task(action->task);
if (action->rsc == NULL) {
return;
}
CRM_ASSERT(action->node);
CRM_ASSERT(is_remote_node(action->node));
remote_rsc = action->node->details->remote_rsc;
CRM_ASSERT(remote_rsc);
container = remote_rsc->container;
CRM_ASSERT(container);
if(is_set(container->flags, pe_rsc_failed)) {
pe_fence_node(data_set, action->node, "container failed");
}
crm_trace("Order %s action %s relative to %s%s for %s%s",
action->task, action->uuid,
is_set(remote_rsc->flags, pe_rsc_failed)? "failed " : "",
remote_rsc->id,
is_set(container->flags, pe_rsc_failed)? "failed " : "",
container->id);
switch (task) {
case start_rsc:
case action_promote:
/* Force resource recovery if the container is recovered */
custom_action_order(container, generate_op_key(container->id, RSC_START, 0), NULL,
action->rsc, NULL, action,
pe_order_preserve | pe_order_implies_then | pe_order_runnable_left, data_set);
/* Wait for the connection resource to be up too */
custom_action_order(remote_rsc, generate_op_key(remote_rsc->id, RSC_START, 0), NULL,
action->rsc, NULL, action,
pe_order_preserve | pe_order_runnable_left, data_set);
break;
case stop_rsc:
if(is_set(container->flags, pe_rsc_failed)) {
/* When the container representing a guest node fails,
* the stop action for all the resources living in
* that container is implied by the container
* stopping. This is similar to how fencing operations
* work for cluster nodes.
*/
} else {
/* Otherwise, ensure the operation happens before the connection is brought down */
custom_action_order(action->rsc, NULL, action,
remote_rsc, generate_op_key(remote_rsc->id, RSC_STOP, 0), NULL,
pe_order_preserve, data_set);
}
break;
case action_demote:
if(is_set(container->flags, pe_rsc_failed)) {
/* Just like a stop, the demote is implied by the
* container having failed/stopped
*
* If we really wanted to we would order the demote
* after the stop, IFF the containers current role was
* stopped (otherwise we re-introduce an ordering
* loop)
*/
} else {
/* Otherwise, ensure the operation happens before the connection is brought down */
custom_action_order(action->rsc, NULL, action,
remote_rsc, generate_op_key(remote_rsc->id, RSC_STOP, 0), NULL,
pe_order_preserve, data_set);
}
break;
default:
/* Wait for the connection resource to be up */
if (is_recurring_action(action)) {
/* In case we ever get the recovery logic wrong, force
* recurring monitors to be restarted, even if just
* the connection was re-established
*/
if(task != no_action) {
custom_action_order(remote_rsc, generate_op_key(remote_rsc->id, RSC_START, 0), NULL,
action->rsc, NULL, action,
pe_order_preserve | pe_order_runnable_left | pe_order_implies_then, data_set);
}
} else {
custom_action_order(remote_rsc, generate_op_key(remote_rsc->id, RSC_START, 0), NULL,
action->rsc, NULL, action,
pe_order_preserve | pe_order_runnable_left, data_set);
}
break;
}
}
static enum remote_connection_state
get_remote_node_state(pe_node_t *node)
{
resource_t *remote_rsc = NULL;
node_t *cluster_node = NULL;
if(node == NULL) {
return remote_state_unknown;
}
remote_rsc = node->details->remote_rsc;
CRM_ASSERT(remote_rsc);
if(remote_rsc->running_on) {
cluster_node = remote_rsc->running_on->data;
}
/* If the cluster node the remote connection resource resides on
* is unclean or went offline, we can't process any operations
* on that remote node until after it starts elsewhere.
*/
if(remote_rsc->next_role == RSC_ROLE_STOPPED || remote_rsc->allocated_to == NULL) {
/* There is nowhere left to run the connection resource,
* and the resource is in a failed state (either directly
* or because it is located on a failed node).
*
* If there are any resources known to be active on it (stop),
* or if there are resources in an unknown state (probe), we
* must assume the worst and fence it.
*/
if (is_set(remote_rsc->flags, pe_rsc_failed)) {
return remote_state_failed;
} else if(cluster_node && cluster_node->details->unclean) {
return remote_state_failed;
} else {
return remote_state_stopped;
}
} else if (cluster_node == NULL) {
/* Connection is recoverable but not currently running anywhere, see if we can recover it first */
return remote_state_unknown;
} else if(cluster_node->details->unclean == TRUE
|| cluster_node->details->online == FALSE) {
/* Connection is running on a dead node, see if we can recover it first */
return remote_state_resting;
} else if (g_list_length(remote_rsc->running_on) > 1
&& remote_rsc->partial_migration_source
&& remote_rsc->partial_migration_target) {
/* We're in the middle of migrating a connection resource,
* wait until after the resource migrates before performing
* any actions.
*/
return remote_state_resting;
}
return remote_state_alive;
}
static void
apply_remote_ordering(action_t *action, pe_working_set_t *data_set)
{
resource_t *remote_rsc = NULL;
node_t *cluster_node = NULL;
enum action_tasks task = text2task(action->task);
enum remote_connection_state state = get_remote_node_state(action->node);
enum pe_ordering order_opts = pe_order_none;
if (action->rsc == NULL) {
return;
}
CRM_ASSERT(action->node);
CRM_ASSERT(is_remote_node(action->node));
remote_rsc = action->node->details->remote_rsc;
CRM_ASSERT(remote_rsc);
if(remote_rsc->running_on) {
cluster_node = remote_rsc->running_on->data;
}
crm_trace("Order %s action %s relative to %s%s (state %d)",
action->task, action->uuid,
is_set(remote_rsc->flags, pe_rsc_failed)? "failed " : "",
remote_rsc->id, state);
switch (task) {
case start_rsc:
case action_promote:
/* This as an internally generated constraint exempt from
* user constraint prohibitions, and this action isn't runnable
* if the connection start isn't runnable.
*/
order_opts = pe_order_preserve | pe_order_runnable_left;
if (state == remote_state_failed) {
/* Force recovery, by making this action required */
order_opts |= pe_order_implies_then;
}
/* Ensure connection is up before running this action */
custom_action_order(remote_rsc,
generate_op_key(remote_rsc->id, RSC_START, 0),
NULL, action->rsc, NULL, action, order_opts,
data_set);
break;
case stop_rsc:
/* Handle special case with remote node where stop actions need to be
* ordered after the connection resource starts somewhere else.
*/
if(state == remote_state_resting) {
/* Wait for the connection resource to be up and assume everything is as we left it */
custom_action_order(remote_rsc, generate_op_key(remote_rsc->id, RSC_START, 0), NULL,
action->rsc, NULL, action,
pe_order_preserve | pe_order_runnable_left, data_set);
} else {
if(state == remote_state_failed) {
/* We would only be here if the resource is
* running on the remote node. Since we have no
* way to stop it, it is necessary to fence the
* node.
*/
pe_fence_node(data_set, action->node, "resources are active and the connection is unrecoverable");
}
custom_action_order(action->rsc, NULL, action,
remote_rsc, generate_op_key(remote_rsc->id, RSC_STOP, 0), NULL,
pe_order_preserve | pe_order_implies_first, data_set);
}
break;
case action_demote:
/* Only order this demote relative to the connection start if the
* connection isn't being torn down. Otherwise, the demote would be
* blocked because the connection start would not be allowed.
*/
if(state == remote_state_resting || state == remote_state_unknown) {
custom_action_order(remote_rsc, generate_op_key(remote_rsc->id, RSC_START, 0), NULL,
action->rsc, NULL, action,
pe_order_preserve, data_set);
} /* Otherwise we can rely on the stop ordering */
break;
default:
/* Wait for the connection resource to be up */
if (is_recurring_action(action)) {
/* In case we ever get the recovery logic wrong, force
* recurring monitors to be restarted, even if just
* the connection was re-established
*/
custom_action_order(remote_rsc, generate_op_key(remote_rsc->id, RSC_START, 0), NULL,
action->rsc, NULL, action,
pe_order_preserve | pe_order_runnable_left | pe_order_implies_then, data_set);
} else {
if(task == monitor_rsc && state == remote_state_failed) {
/* We would only be here if we do not know the
* state of the resource on the remote node.
* Since we have no way to find out, it is
* necessary to fence the node.
*/
pe_fence_node(data_set, action->node, "resources are in an unknown state and the connection is unrecoverable");
}
if(cluster_node && state == remote_state_stopped) {
/* The connection is currently up, but is going
* down permanently.
*
* Make sure we check services are actually
* stopped _before_ we let the connection get
* closed
*/
custom_action_order(action->rsc, NULL, action,
remote_rsc, generate_op_key(remote_rsc->id, RSC_STOP, 0), NULL,
pe_order_preserve | pe_order_runnable_left, data_set);
} else {
custom_action_order(remote_rsc, generate_op_key(remote_rsc->id, RSC_START, 0), NULL,
action->rsc, NULL, action,
pe_order_preserve | pe_order_runnable_left, data_set);
}
}
break;
}
}
static void
apply_remote_node_ordering(pe_working_set_t *data_set)
{
if (is_set(data_set->flags, pe_flag_have_remote_nodes) == FALSE) {
return;
}
for (GListPtr gIter = data_set->actions; gIter != NULL; gIter = gIter->next) {
action_t *action = (action_t *) gIter->data;
if (action->rsc == NULL) {
continue;
}
/* Special case. */
if (action->rsc &&
action->rsc->is_remote_node &&
safe_str_eq(action->task, CRM_OP_CLEAR_FAILCOUNT)) {
/* If we are clearing the failcount of an actual remote node
* connection resource, then make sure this happens before allowing
* the connection to start if we are planning on starting the
* connection during this transition.
*/
custom_action_order(action->rsc,
NULL,
action,
action->rsc,
generate_op_key(action->rsc->id, RSC_START, 0),
NULL,
pe_order_optional,
data_set);
continue;
}
/* If the action occurs on a Pacemaker Remote node, create
* ordering constraints that guarantee the action occurs while the node
* is active (after start, before stop ... things like that).
*/
if (action->node == NULL ||
is_remote_node(action->node) == FALSE ||
action->node->details->remote_rsc == NULL ||
is_set(action->flags, pe_action_pseudo)) {
crm_trace("Nothing required for %s on %s", action->uuid, action->node?action->node->details->uname:"NA");
} else if(action->node->details->remote_rsc->container) {
crm_trace("Container ordering for %s", action->uuid);
apply_container_ordering(action, data_set);
} else {
crm_trace("Remote ordering for %s", action->uuid);
apply_remote_ordering(action, data_set);
}
}
}
static void
order_probes(pe_working_set_t * data_set)
{
#if 0
GListPtr gIter = NULL;
for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
resource_t *rsc = (resource_t *) gIter->data;
/* Given "A then B", we would prefer to wait for A to be
* started before probing B.
*
* If A was a filesystem on which the binaries and data for B
* lived, it would have been useful if the author of B's agent
* could assume that A is running before B.monitor will be
* called.
*
* However we can't _only_ probe once A is running, otherwise
* we'd not detect the state of B if A could not be started
* for some reason.
*
* In practice however, we cannot even do an opportunistic
* version of this because B may be moving:
*
* B.probe -> B.start
* B.probe -> B.stop
* B.stop -> B.start
* A.stop -> A.start
* A.start -> B.probe
*
* So far so good, but if we add the result of this code:
*
* B.stop -> A.stop
*
* Then we get a loop:
*
* B.probe -> B.stop -> A.stop -> A.start -> B.probe
*
* We could kill the 'B.probe -> B.stop' dependency, but that
* could mean stopping B "too" soon, because B.start must wait
* for the probes to complete.
*
* Another option is to allow it only if A is a non-unique
* clone with clone-max == node-max (since we'll never be
* moving it). However, we could still be stopping one
* instance at the same time as starting another.
* The complexity of checking for allowed conditions combined
* with the ever narrowing usecase suggests that this code
* should remain disabled until someone gets smarter.
*/
action_t *start = NULL;
GListPtr actions = NULL;
GListPtr probes = NULL;
char *key = NULL;
key = start_key(rsc);
actions = find_actions(rsc->actions, key, NULL);
free(key);
if (actions) {
start = actions->data;
g_list_free(actions);
}
if(start == NULL) {
crm_err("No start action for %s", rsc->id);
continue;
}
key = generate_op_key(rsc->id, CRMD_ACTION_STATUS, 0);
probes = find_actions(rsc->actions, key, NULL);
free(key);
for (actions = start->actions_before; actions != NULL; actions = actions->next) {
action_wrapper_t *before = (action_wrapper_t *) actions->data;
GListPtr pIter = NULL;
action_t *first = before->action;
resource_t *first_rsc = first->rsc;
if(first->required_runnable_before) {
GListPtr clone_actions = NULL;
for (clone_actions = first->actions_before; clone_actions != NULL; clone_actions = clone_actions->next) {
before = (action_wrapper_t *) clone_actions->data;
crm_trace("Testing %s -> %s (%p) for %s", first->uuid, before->action->uuid, before->action->rsc, start->uuid);
CRM_ASSERT(before->action->rsc);
first_rsc = before->action->rsc;
break;
}
} else if(safe_str_neq(first->task, RSC_START)) {
crm_trace("Not a start op %s for %s", first->uuid, start->uuid);
}
if(first_rsc == NULL) {
continue;
} else if(uber_parent(first_rsc) == uber_parent(start->rsc)) {
crm_trace("Same parent %s for %s", first_rsc->id, start->uuid);
continue;
} else if(FALSE && pe_rsc_is_clone(uber_parent(first_rsc)) == FALSE) {
crm_trace("Not a clone %s for %s", first_rsc->id, start->uuid);
continue;
}
crm_err("Appplying %s before %s %d", first->uuid, start->uuid, uber_parent(first_rsc)->variant);
for (pIter = probes; pIter != NULL; pIter = pIter->next) {
action_t *probe = (action_t *) pIter->data;
crm_err("Ordering %s before %s", first->uuid, probe->uuid);
order_actions(first, probe, pe_order_optional);
}
}
}
#endif
}
gboolean
stage7(pe_working_set_t * data_set)
{
GListPtr gIter = NULL;
crm_trace("Applying ordering constraints");
/* Don't ask me why, but apparently they need to be processed in
* the order they were created in... go figure
*
* Also g_list_append() has horrendous performance characteristics
* So we need to use g_list_prepend() and then reverse the list here
*/
data_set->ordering_constraints = g_list_reverse(data_set->ordering_constraints);
for (gIter = data_set->ordering_constraints; gIter != NULL; gIter = gIter->next) {
order_constraint_t *order = (order_constraint_t *) gIter->data;
resource_t *rsc = order->lh_rsc;
crm_trace("Applying ordering constraint: %d", order->id);
if (rsc != NULL) {
crm_trace("rsc_action-to-*");
rsc_order_first(rsc, order, data_set);
continue;
}
rsc = order->rh_rsc;
if (rsc != NULL) {
crm_trace("action-to-rsc_action");
rsc_order_then(order->lh_action, rsc, order);
} else {
crm_trace("action-to-action");
order_actions(order->lh_action, order->rh_action, order->type);
}
}
for (gIter = data_set->actions; gIter != NULL; gIter = gIter->next) {
action_t *action = (action_t *) gIter->data;
update_colo_start_chain(action);
}
crm_trace("Ordering probes");
order_probes(data_set);
crm_trace("Updating %d actions", g_list_length(data_set->actions));
for (gIter = data_set->actions; gIter != NULL; gIter = gIter->next) {
action_t *action = (action_t *) gIter->data;
update_action(action);
}
LogNodeActions(data_set, FALSE);
for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
resource_t *rsc = (resource_t *) gIter->data;
LogActions(rsc, data_set, FALSE);
}
return TRUE;
}
int transition_id = -1;
/*
* Create a dependency graph to send to the transitioner (via the CRMd)
*/
gboolean
stage8(pe_working_set_t * data_set)
{
GListPtr gIter = NULL;
const char *value = NULL;
transition_id++;
crm_trace("Creating transition graph %d.", transition_id);
data_set->graph = create_xml_node(NULL, XML_TAG_GRAPH);
value = pe_pref(data_set->config_hash, "cluster-delay");
crm_xml_add(data_set->graph, "cluster-delay", value);
value = pe_pref(data_set->config_hash, "stonith-timeout");
crm_xml_add(data_set->graph, "stonith-timeout", value);
crm_xml_add(data_set->graph, "failed-stop-offset", "INFINITY");
if (is_set(data_set->flags, pe_flag_start_failure_fatal)) {
crm_xml_add(data_set->graph, "failed-start-offset", "INFINITY");
} else {
crm_xml_add(data_set->graph, "failed-start-offset", "1");
}
value = pe_pref(data_set->config_hash, "batch-limit");
crm_xml_add(data_set->graph, "batch-limit", value);
crm_xml_add_int(data_set->graph, "transition_id", transition_id);
value = pe_pref(data_set->config_hash, "migration-limit");
if (crm_int_helper(value, NULL) > 0) {
crm_xml_add(data_set->graph, "migration-limit", value);
}
/* errors...
slist_iter(action, action_t, action_list, lpc,
if(action->optional == FALSE && action->runnable == FALSE) {
print_action("Ignoring", action, TRUE);
}
);
*/
gIter = data_set->resources;
for (; gIter != NULL; gIter = gIter->next) {
resource_t *rsc = (resource_t *) gIter->data;
pe_rsc_trace(rsc, "processing actions for rsc=%s", rsc->id);
rsc->cmds->expand(rsc, data_set);
}
crm_log_xml_trace(data_set->graph, "created resource-driven action list");
/* pseudo action to distribute list of nodes with maintenance state update */
add_maintenance_update(data_set);
/* catch any non-resource specific actions */
crm_trace("processing non-resource actions");
gIter = data_set->actions;
for (; gIter != NULL; gIter = gIter->next) {
action_t *action = (action_t *) gIter->data;
if (action->rsc
&& action->node
&& action->node->details->shutdown
&& is_not_set(action->rsc->flags, pe_rsc_maintenance)
&& is_not_set(action->flags, pe_action_optional)
&& is_not_set(action->flags, pe_action_runnable)
&& crm_str_eq(action->task, RSC_STOP, TRUE)
) {
/* Eventually we should just ignore the 'fence' case
* But for now it's the best way to detect (in CTS) when
* CIB resource updates are being lost
*/
if (is_set(data_set->flags, pe_flag_have_quorum)
|| data_set->no_quorum_policy == no_quorum_ignore) {
crm_crit("Cannot %s node '%s' because of %s:%s%s (%s)",
action->node->details->unclean ? "fence" : "shut down",
action->node->details->uname, action->rsc->id,
is_not_set(action->rsc->flags, pe_rsc_managed) ? " unmanaged" : " blocked",
is_set(action->rsc->flags, pe_rsc_failed) ? " failed" : "",
action->uuid);
}
}
graph_element_from_action(action, data_set);
}
crm_log_xml_trace(data_set->graph, "created generic action list");
crm_trace("Created transition graph %d.", transition_id);
return TRUE;
}
void
LogNodeActions(pe_working_set_t * data_set, gboolean terminal)
{
GListPtr gIter = NULL;
for (gIter = data_set->actions; gIter != NULL; gIter = gIter->next) {
char *node_name = NULL;
const char *task = NULL;
action_t *action = (action_t *) gIter->data;
if (action->rsc != NULL) {
continue;
}
if (is_container_remote_node(action->node)) {
node_name = crm_strdup_printf("%s (resource: %s)", action->node->details->uname, action->node->details->remote_rsc->container->id);
} else if(action->node) {
node_name = crm_strdup_printf("%s", action->node->details->uname);
}
if (safe_str_eq(action->task, CRM_OP_SHUTDOWN)) {
task = "Shutdown";
} else if (safe_str_eq(action->task, CRM_OP_FENCE)) {
task = "Fence";
}
if(task == NULL) {
/* Nothing to report */
} else if(terminal) {
printf(" * %s %s\n", task, node_name);
} else {
crm_notice(" * %s %s\n", task, node_name);
}
free(node_name);
}
}
void
cleanup_alloc_calculations(pe_working_set_t * data_set)
{
if (data_set == NULL) {
return;
}
crm_trace("deleting %d order cons: %p",
g_list_length(data_set->ordering_constraints), data_set->ordering_constraints);
pe_free_ordering(data_set->ordering_constraints);
data_set->ordering_constraints = NULL;
crm_trace("deleting %d node cons: %p",
g_list_length(data_set->placement_constraints), data_set->placement_constraints);
pe_free_rsc_to_node(data_set->placement_constraints);
data_set->placement_constraints = NULL;
crm_trace("deleting %d inter-resource cons: %p",
g_list_length(data_set->colocation_constraints), data_set->colocation_constraints);
g_list_free_full(data_set->colocation_constraints, free);
data_set->colocation_constraints = NULL;
crm_trace("deleting %d ticket deps: %p",
g_list_length(data_set->ticket_constraints), data_set->ticket_constraints);
g_list_free_full(data_set->ticket_constraints, free);
data_set->ticket_constraints = NULL;
cleanup_calculations(data_set);
}
diff --git a/pengine/graph.c b/pengine/graph.c
index 72a7dea8b9..a446fe5fbf 100644
--- a/pengine/graph.c
+++ b/pengine/graph.c
@@ -1,1652 +1,1656 @@
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* 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
*/
#include <crm_internal.h>
#include <sys/param.h>
#include <crm/crm.h>
#include <crm/cib.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <glib.h>
#include <allocate.h>
#include <utils.h>
void update_colo_start_chain(action_t * action);
gboolean rsc_update_action(action_t * first, action_t * then, enum pe_ordering type);
static enum pe_action_flags
get_action_flags(action_t * action, node_t * node)
{
enum pe_action_flags flags = action->flags;
if (action->rsc) {
flags = action->rsc->cmds->action_flags(action, NULL);
if (pe_rsc_is_clone(action->rsc) && node) {
/* We only care about activity on $node */
enum pe_action_flags clone_flags = action->rsc->cmds->action_flags(action, node);
/* Go to great lengths to ensure the correct value for pe_action_runnable...
*
* If we are a clone, then for _ordering_ constraints, it's only relevant
* if we are runnable _anywhere_.
*
* This only applies to _runnable_ though, and only for ordering constraints.
* If this function is ever used during colocation, then we'll need additional logic
*
* Not very satisfying, but it's logical and appears to work well.
*/
if (is_not_set(clone_flags, pe_action_runnable)
&& is_set(flags, pe_action_runnable)) {
pe_rsc_trace(action->rsc, "Fixing up runnable flag for %s", action->uuid);
set_bit(clone_flags, pe_action_runnable);
}
flags = clone_flags;
}
}
return flags;
}
static char *
convert_non_atomic_uuid(char *old_uuid, resource_t * rsc, gboolean allow_notify,
gboolean free_original)
{
int interval = 0;
char *uuid = NULL;
char *rid = NULL;
char *raw_task = NULL;
int task = no_action;
CRM_ASSERT(rsc);
pe_rsc_trace(rsc, "Processing %s", old_uuid);
if (old_uuid == NULL) {
return NULL;
} else if (strstr(old_uuid, "notify") != NULL) {
goto done; /* no conversion */
} else if (rsc->variant < pe_group) {
goto done; /* no conversion */
}
CRM_ASSERT(parse_op_key(old_uuid, &rid, &raw_task, &interval));
if (interval > 0) {
goto done; /* no conversion */
}
task = text2task(raw_task);
switch (task) {
case stop_rsc:
case start_rsc:
case action_notify:
case action_promote:
case action_demote:
break;
case stopped_rsc:
case started_rsc:
case action_notified:
case action_promoted:
case action_demoted:
task--;
break;
case monitor_rsc:
case shutdown_crm:
case stonith_node:
task = no_action;
break;
default:
crm_err("Unknown action: %s", raw_task);
task = no_action;
break;
}
if (task != no_action) {
if (is_set(rsc->flags, pe_rsc_notify) && allow_notify) {
uuid = generate_notify_key(rid, "confirmed-post", task2text(task + 1));
} else {
uuid = generate_op_key(rid, task2text(task + 1), 0);
}
pe_rsc_trace(rsc, "Converted %s -> %s", old_uuid, uuid);
}
done:
if (uuid == NULL) {
uuid = strdup(old_uuid);
}
if (free_original) {
free(old_uuid);
}
free(raw_task);
free(rid);
return uuid;
}
static action_t *
rsc_expand_action(action_t * action)
{
gboolean notify = FALSE;
action_t *result = action;
resource_t *rsc = action->rsc;
if (rsc == NULL) {
return action;
}
if ((rsc->parent == NULL)
|| (pe_rsc_is_clone(rsc) && (rsc->parent->variant == pe_container))) {
/* Only outermost resources have notification actions.
* The exception is those in bundles.
*/
notify = is_set(rsc->flags, pe_rsc_notify);
}
if (rsc->variant >= pe_group) {
/* Expand 'start' -> 'started' */
char *uuid = NULL;
uuid = convert_non_atomic_uuid(action->uuid, rsc, notify, FALSE);
if (uuid) {
pe_rsc_trace(rsc, "Converting %s to %s %d", action->uuid, uuid,
is_set(rsc->flags, pe_rsc_notify));
result = find_first_action(rsc->actions, uuid, NULL, NULL);
if (result == NULL) {
crm_err("Couldn't expand %s to %s in %s", action->uuid, uuid, rsc->id);
result = action;
}
free(uuid);
}
}
return result;
}
static enum pe_graph_flags
graph_update_action(action_t * first, action_t * then, node_t * node,
enum pe_action_flags first_flags, enum pe_action_flags then_flags,
enum pe_ordering type)
{
enum pe_graph_flags changed = pe_graph_none;
gboolean processed = FALSE;
/* TODO: Do as many of these in parallel as possible */
if (type & pe_order_implies_then) {
processed = TRUE;
if (then->rsc) {
changed |=
then->rsc->cmds->update_actions(first, then, node, first_flags & pe_action_optional,
pe_action_optional, pe_order_implies_then);
} else if (is_set(first_flags, pe_action_optional) == FALSE) {
if (update_action_flags(then, pe_action_optional | pe_action_clear, __FUNCTION__, __LINE__)) {
changed |= pe_graph_updated_then;
}
}
if (changed) {
pe_rsc_trace(then->rsc, "implies right: %s then %s: changed", first->uuid, then->uuid);
} else {
crm_trace("implies right: %s then %s %p", first->uuid, then->uuid, then->rsc);
}
}
if ((type & pe_order_restart) && then->rsc) {
enum pe_action_flags restart = (pe_action_optional | pe_action_runnable);
processed = TRUE;
changed |=
then->rsc->cmds->update_actions(first, then, node, first_flags, restart, pe_order_restart);
if (changed) {
pe_rsc_trace(then->rsc, "restart: %s then %s: changed", first->uuid, then->uuid);
} else {
crm_trace("restart: %s then %s", first->uuid, then->uuid);
}
}
if (type & pe_order_implies_first) {
processed = TRUE;
if (first->rsc) {
changed |=
first->rsc->cmds->update_actions(first, then, node, first_flags,
pe_action_optional, pe_order_implies_first);
} else if (is_set(first_flags, pe_action_optional) == FALSE) {
pe_rsc_trace(first->rsc, "first unrunnable: %s (%d) then %s (%d)",
first->uuid, is_set(first_flags, pe_action_optional),
then->uuid, is_set(then_flags, pe_action_optional));
if (update_action_flags(first, pe_action_runnable | pe_action_clear, __FUNCTION__, __LINE__)) {
changed |= pe_graph_updated_first;
}
}
if (changed) {
pe_rsc_trace(then->rsc, "implies left: %s then %s: changed", first->uuid, then->uuid);
} else {
crm_trace("implies left: %s (%d) then %s (%d)",
first->uuid, is_set(first_flags, pe_action_optional),
then->uuid, is_set(then_flags, pe_action_optional));
}
}
if (type & pe_order_implies_first_master) {
processed = TRUE;
if (then->rsc) {
changed |=
then->rsc->cmds->update_actions(first, then, node, first_flags & pe_action_optional,
pe_action_optional, pe_order_implies_first_master);
}
if (changed) {
pe_rsc_trace(then->rsc,
"implies left when right rsc is Master role: %s then %s: changed",
first->uuid, then->uuid);
} else {
crm_trace("implies left when right rsc is Master role: %s then %s", first->uuid,
then->uuid);
}
}
if (type & pe_order_one_or_more) {
processed = TRUE;
if (then->rsc) {
changed |=
then->rsc->cmds->update_actions(first, then, node, first_flags,
pe_action_runnable, pe_order_one_or_more);
} else if (is_set(first_flags, pe_action_runnable)) {
/* alright. a "first" action is considered runnable, incremente
* the 'runnable_before' counter */
then->runnable_before++;
/* if the runnable before count for then exceeds the required number
* of "before" runnable actions... mark then as runnable */
if (then->runnable_before >= then->required_runnable_before) {
if (update_action_flags(then, pe_action_runnable, __FUNCTION__, __LINE__)) {
changed |= pe_graph_updated_then;
}
}
}
if (changed) {
pe_rsc_trace(then->rsc, "runnable_one_or_more: %s then %s: changed", first->uuid,
then->uuid);
} else {
crm_trace("runnable_one_or_more: %s then %s", first->uuid, then->uuid);
}
}
if (type & pe_order_runnable_left) {
processed = TRUE;
if (then->rsc) {
changed |=
then->rsc->cmds->update_actions(first, then, node, first_flags,
pe_action_runnable, pe_order_runnable_left);
} else if (is_set(first_flags, pe_action_runnable) == FALSE) {
pe_rsc_trace(then->rsc, "then unrunnable: %s then %s", first->uuid, then->uuid);
if (update_action_flags(then, pe_action_runnable | pe_action_clear, __FUNCTION__, __LINE__)) {
changed |= pe_graph_updated_then;
}
}
if (changed) {
pe_rsc_trace(then->rsc, "runnable: %s then %s: changed", first->uuid, then->uuid);
} else {
crm_trace("runnable: %s then %s", first->uuid, then->uuid);
}
}
if (type & pe_order_implies_first_migratable) {
processed = TRUE;
if (then->rsc) {
changed |=
then->rsc->cmds->update_actions(first, then, node, first_flags,
pe_action_optional, pe_order_implies_first_migratable);
}
if (changed) {
pe_rsc_trace(then->rsc, "optional: %s then %s: changed", first->uuid, then->uuid);
} else {
crm_trace("optional: %s then %s", first->uuid, then->uuid);
}
}
if (type & pe_order_pseudo_left) {
processed = TRUE;
if (then->rsc) {
changed |=
then->rsc->cmds->update_actions(first, then, node, first_flags,
pe_action_optional, pe_order_pseudo_left);
}
if (changed) {
pe_rsc_trace(then->rsc, "optional: %s then %s: changed", first->uuid, then->uuid);
} else {
crm_trace("optional: %s then %s", first->uuid, then->uuid);
}
}
if (type & pe_order_optional) {
processed = TRUE;
if (then->rsc) {
changed |=
then->rsc->cmds->update_actions(first, then, node, first_flags,
pe_action_runnable, pe_order_optional);
}
if (changed) {
pe_rsc_trace(then->rsc, "optional: %s then %s: changed", first->uuid, then->uuid);
} else {
crm_trace("optional: %s then %s", first->uuid, then->uuid);
}
}
if (type & pe_order_asymmetrical) {
processed = TRUE;
if (then->rsc) {
changed |=
then->rsc->cmds->update_actions(first, then, node, first_flags,
pe_action_runnable, pe_order_asymmetrical);
}
if (changed) {
pe_rsc_trace(then->rsc, "asymmetrical: %s then %s: changed", first->uuid, then->uuid);
} else {
crm_trace("asymmetrical: %s then %s", first->uuid, then->uuid);
}
}
if ((first->flags & pe_action_runnable) && (type & pe_order_implies_then_printed)
&& (first_flags & pe_action_optional) == 0) {
processed = TRUE;
crm_trace("%s implies %s printed", first->uuid, then->uuid);
update_action_flags(then, pe_action_print_always, __FUNCTION__, __LINE__); /* don't care about changed */
}
if (is_set(type, pe_order_implies_first_printed) && is_set(then_flags, pe_action_optional) == FALSE) {
processed = TRUE;
crm_trace("%s implies %s printed", then->uuid, first->uuid);
update_action_flags(first, pe_action_print_always, __FUNCTION__, __LINE__); /* don't care about changed */
}
if ((type & pe_order_implies_then
|| type & pe_order_implies_first
|| type & pe_order_restart)
&& first->rsc
&& safe_str_eq(first->task, RSC_STOP)
&& is_not_set(first->rsc->flags, pe_rsc_managed)
&& is_set(first->rsc->flags, pe_rsc_block)
&& is_not_set(first->flags, pe_action_runnable)) {
if (update_action_flags(then, pe_action_runnable | pe_action_clear, __FUNCTION__, __LINE__)) {
changed |= pe_graph_updated_then;
}
if (changed) {
pe_rsc_trace(then->rsc, "unmanaged left: %s then %s: changed", first->uuid, then->uuid);
} else {
crm_trace("unmanaged left: %s then %s", first->uuid, then->uuid);
}
}
if (processed == FALSE) {
crm_trace("Constraint 0x%.6x not applicable", type);
}
return changed;
}
static void
mark_start_blocked(resource_t *rsc)
{
GListPtr gIter = rsc->actions;
for (; gIter != NULL; gIter = gIter->next) {
action_t *action = (action_t *) gIter->data;
if (safe_str_neq(action->task, RSC_START)) {
continue;
}
if (is_set(action->flags, pe_action_runnable)) {
clear_bit(action->flags, pe_action_runnable);
update_colo_start_chain(action);
update_action(action);
}
}
}
void
update_colo_start_chain(action_t *action)
{
GListPtr gIter = NULL;
resource_t *rsc = NULL;
if (is_not_set(action->flags, pe_action_runnable) && safe_str_eq(action->task, RSC_START)) {
rsc = uber_parent(action->rsc);
}
if (rsc == NULL || rsc->rsc_cons_lhs == NULL) {
return;
}
/* if rsc has children, all the children need to have start set to
* unrunnable before we follow the colo chain for the parent. */
for (gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
resource_t *child = (resource_t *)gIter->data;
action_t *start = find_first_action(child->actions, NULL, RSC_START, NULL);
if (start == NULL || is_set(start->flags, pe_action_runnable)) {
return;
}
}
for (gIter = rsc->rsc_cons_lhs; gIter != NULL; gIter = gIter->next) {
rsc_colocation_t *colocate_with = (rsc_colocation_t *)gIter->data;
if (colocate_with->score == INFINITY) {
mark_start_blocked(colocate_with->rsc_lh);
}
}
}
gboolean
update_action(action_t * then)
{
GListPtr lpc = NULL;
enum pe_graph_flags changed = pe_graph_none;
int last_flags = then->flags;
crm_trace("Processing %s (%s %s %s)",
then->uuid,
is_set(then->flags, pe_action_optional) ? "optional" : "required",
is_set(then->flags, pe_action_runnable) ? "runnable" : "unrunnable",
is_set(then->flags,
pe_action_pseudo) ? "pseudo" : then->node ? then->node->details->uname : "");
if (is_set(then->flags, pe_action_requires_any)) {
/* initialize current known runnable before actions to 0
* from here as graph_update_action is called for each of
* then's before actions, this number will increment as
* runnable 'first' actions are encountered */
then->runnable_before = 0;
/* for backwards compatibility with previous options that use
* the 'requires_any' flag, initialize required to 1 if it is
* not set. */
if (then->required_runnable_before == 0) {
then->required_runnable_before = 1;
}
clear_bit(then->flags, pe_action_runnable);
/* We are relying on the pe_order_one_or_more clause of
* graph_update_action(), called as part of the:
*
* 'if (first == other->action)'
*
* block below, to set this back if appropriate
*/
}
for (lpc = then->actions_before; lpc != NULL; lpc = lpc->next) {
action_wrapper_t *other = (action_wrapper_t *) lpc->data;
action_t *first = other->action;
node_t *then_node = then->node;
node_t *first_node = first->node;
enum pe_action_flags then_flags = 0;
enum pe_action_flags first_flags = 0;
if (first->rsc && first->rsc->variant == pe_group && safe_str_eq(first->task, RSC_START)) {
first_node = first->rsc->fns->location(first->rsc, NULL, FALSE);
if (first_node) {
crm_trace("First: Found node %s for %s", first_node->details->uname, first->uuid);
}
}
if (then->rsc && then->rsc->variant == pe_group && safe_str_eq(then->task, RSC_START)) {
then_node = then->rsc->fns->location(then->rsc, NULL, FALSE);
if (then_node) {
crm_trace("Then: Found node %s for %s", then_node->details->uname, then->uuid);
}
}
/* Disable constraint if it only applies when on same node, but isn't */
if (is_set(other->type, pe_order_same_node) && first_node && then_node
&& (first_node->details != then_node->details)) {
crm_trace("Disabled constraint %s on %s -> %s on %s",
other->action->uuid, first_node->details->uname,
then->uuid, then_node->details->uname);
other->type = pe_order_none;
continue;
}
clear_bit(changed, pe_graph_updated_first);
if (first->rsc && then->rsc && (first->rsc != then->rsc)
&& (is_parent(then->rsc, first->rsc) == FALSE)) {
first = rsc_expand_action(first);
}
if (first != other->action) {
crm_trace("Ordering %s after %s instead of %s", then->uuid, first->uuid,
other->action->uuid);
}
first_flags = get_action_flags(first, then_node);
then_flags = get_action_flags(then, first_node);
crm_trace("Checking %s (%s %s %s) against %s (%s %s %s) filter=0x%.6x type=0x%.6x",
then->uuid,
is_set(then_flags, pe_action_optional) ? "optional" : "required",
is_set(then_flags, pe_action_runnable) ? "runnable" : "unrunnable",
is_set(then_flags,
pe_action_pseudo) ? "pseudo" : then->node ? then->node->details->
uname : "", first->uuid, is_set(first_flags,
pe_action_optional) ? "optional" : "required",
is_set(first_flags, pe_action_runnable) ? "runnable" : "unrunnable",
is_set(first_flags,
pe_action_pseudo) ? "pseudo" : first->node ? first->node->details->
uname : "", first_flags, other->type);
if (first == other->action) {
/*
* 'first' was not expanded (ie. from 'start' to 'running'), which could mean it:
* - has no associated resource,
* - was a primitive,
* - was pre-expanded (ie. 'running' instead of 'start')
*
* The third argument here to graph_update_action() is a node which is used under two conditions:
* - Interleaving, in which case first->node and
* then->node are equal (and NULL)
* - If 'then' is a clone, to limit the scope of the
* constraint to instances on the supplied node
*
*/
int otype = other->type;
node_t *node = then->node;
if(is_set(otype, pe_order_implies_then_on_node)) {
/* Normally we want the _whole_ 'then' clone to
* restart if 'first' is restarted, so then->node is
* needed.
*
* However for unfencing, we want to limit this to
* instances on the same node as 'first' (the
* unfencing operation), so first->node is supplied.
*
* Swap the node, from then on we can can treat it
* like any other 'pe_order_implies_then'
*/
clear_bit(otype, pe_order_implies_then_on_node);
set_bit(otype, pe_order_implies_then);
node = first->node;
}
clear_bit(first_flags, pe_action_pseudo);
changed |= graph_update_action(first, then, node, first_flags, then_flags, otype);
/* 'first' was for a complex resource (clone, group, etc),
* create a new dependency if necessary
*/
} else if (order_actions(first, then, other->type)) {
/* This was the first time 'first' and 'then' were associated,
* start again to get the new actions_before list
*/
changed |= (pe_graph_updated_then | pe_graph_disable);
}
if (changed & pe_graph_disable) {
crm_trace("Disabled constraint %s -> %s in favor of %s -> %s",
other->action->uuid, then->uuid, first->uuid, then->uuid);
clear_bit(changed, pe_graph_disable);
other->type = pe_order_none;
}
if (changed & pe_graph_updated_first) {
GListPtr lpc2 = NULL;
crm_trace("Updated %s (first %s %s %s), processing dependents ",
first->uuid,
is_set(first->flags, pe_action_optional) ? "optional" : "required",
is_set(first->flags, pe_action_runnable) ? "runnable" : "unrunnable",
is_set(first->flags,
pe_action_pseudo) ? "pseudo" : first->node ? first->node->details->
uname : "");
for (lpc2 = first->actions_after; lpc2 != NULL; lpc2 = lpc2->next) {
action_wrapper_t *other = (action_wrapper_t *) lpc2->data;
update_action(other->action);
}
update_action(first);
}
}
if (is_set(then->flags, pe_action_requires_any)) {
if (last_flags != then->flags) {
changed |= pe_graph_updated_then;
} else {
clear_bit(changed, pe_graph_updated_then);
}
}
if (changed & pe_graph_updated_then) {
crm_trace("Updated %s (then %s %s %s), processing dependents ",
then->uuid,
is_set(then->flags, pe_action_optional) ? "optional" : "required",
is_set(then->flags, pe_action_runnable) ? "runnable" : "unrunnable",
is_set(then->flags,
pe_action_pseudo) ? "pseudo" : then->node ? then->node->details->
uname : "");
if (is_set(last_flags, pe_action_runnable) && is_not_set(then->flags, pe_action_runnable)) {
update_colo_start_chain(then);
}
update_action(then);
for (lpc = then->actions_after; lpc != NULL; lpc = lpc->next) {
action_wrapper_t *other = (action_wrapper_t *) lpc->data;
update_action(other->action);
}
}
return FALSE;
}
gboolean
shutdown_constraints(node_t * node, action_t * shutdown_op, pe_working_set_t * data_set)
{
/* add the stop to the before lists so it counts as a pre-req
* for the shutdown
*/
GListPtr lpc = NULL;
for (lpc = data_set->actions; lpc != NULL; lpc = lpc->next) {
action_t *action = (action_t *) lpc->data;
if (action->rsc == NULL || action->node == NULL) {
continue;
} else if (action->node->details != node->details) {
continue;
} else if (is_set(action->rsc->flags, pe_rsc_maintenance)) {
pe_rsc_trace(action->rsc, "Skipping %s: maintenance mode", action->uuid);
continue;
} else if (node->details->maintenance) {
pe_rsc_trace(action->rsc, "Skipping %s: node %s is in maintenance mode",
action->uuid, node->details->uname);
continue;
} else if (safe_str_neq(action->task, RSC_STOP)) {
continue;
} else if (is_not_set(action->rsc->flags, pe_rsc_managed)
&& is_not_set(action->rsc->flags, pe_rsc_block)) {
/*
* If another action depends on this one, we may still end up blocking
*/
pe_rsc_trace(action->rsc, "Skipping %s: unmanaged", action->uuid);
continue;
}
pe_rsc_trace(action->rsc, "Ordering %s before shutdown on %s", action->uuid,
node->details->uname);
pe_clear_action_bit(action, pe_action_optional);
custom_action_order(action->rsc, NULL, action,
NULL, strdup(CRM_OP_SHUTDOWN), shutdown_op,
pe_order_optional | pe_order_runnable_left, data_set);
}
return TRUE;
}
/*!
* \internal
* \brief Order all actions appropriately relative to a fencing operation
*
* Ensure start operations of affected resources are ordered after fencing,
* imply stop and demote operations of affected resources by marking them as
* pseudo-actions, etc.
*
* \param[in] node Node to be fenced
* \param[in] stonith_op Fencing operation
* \param[in,out] data_set Working set of cluster
*/
gboolean
stonith_constraints(node_t * node, action_t * stonith_op, pe_working_set_t * data_set)
{
GListPtr r = NULL;
CRM_CHECK(stonith_op != NULL, return FALSE);
for (r = data_set->resources; r != NULL; r = r->next) {
rsc_stonith_ordering((resource_t *) r->data, stonith_op, data_set);
}
return TRUE;
}
static node_t *
get_router_node(action_t *action)
{
node_t *began_on = NULL;
node_t *ended_on = NULL;
node_t *router_node = NULL;
if (safe_str_eq(action->task, CRM_OP_FENCE) || is_remote_node(action->node) == FALSE) {
return NULL;
}
CRM_ASSERT(action->node->details->remote_rsc != NULL);
if (action->node->details->remote_rsc->running_on) {
began_on = action->node->details->remote_rsc->running_on->data;
}
ended_on = action->node->details->remote_rsc->allocated_to;
/* if there is only one location to choose from,
* this is easy. Check for those conditions first */
if (!began_on || !ended_on) {
/* remote rsc is either shutting down or starting up */
return began_on ? began_on : ended_on;
} else if (began_on->details == ended_on->details) {
/* remote rsc didn't move nodes. */
return began_on;
}
/* If we have get here, we know the remote resource
* began on one node and is moving to another node.
*
* This means some actions will get routed through the cluster
* node the connection rsc began on, and others are routed through
* the cluster node the connection rsc ends up on.
*
* 1. stop, demote, migrate actions of resources living in the remote
* node _MUST_ occur _BEFORE_ the connection can move (these actions
* are all required before the remote rsc stop action can occur.) In
* this case, we know these actions have to be routed through the initial
* cluster node the connection resource lived on before the move takes place.
*
* 2. Everything else (start, promote, monitor, probe, refresh, clear failcount
* delete ....) must occur after the resource starts on the node it is
* moving to.
*/
/* 1. before connection rsc moves. */
if (safe_str_eq(action->task, "stop") ||
safe_str_eq(action->task, "demote") ||
safe_str_eq(action->task, "migrate_from") ||
safe_str_eq(action->task, "migrate_to")) {
router_node = began_on;
/* 2. after connection rsc moves. */
} else {
router_node = ended_on;
}
return router_node;
}
/*!
* \internal
* \brief Add an XML node tag for a specified ID
*
* \param[in] id Node UUID to add
* \param[in,out] xml Parent XML tag to add to
*/
static xmlNode*
add_node_to_xml_by_id(const char *id, xmlNode *xml)
{
xmlNode *node_xml;
node_xml = create_xml_node(xml, XML_CIB_TAG_NODE);
crm_xml_add(node_xml, XML_ATTR_UUID, id);
return node_xml;
}
/*!
* \internal
* \brief Add an XML node tag for a specified node
*
* \param[in] node Node to add
* \param[in,out] xml XML to add node to
*/
static void
add_node_to_xml(const node_t *node, void *xml)
{
add_node_to_xml_by_id(node->details->id, (xmlNode *) xml);
}
/*!
* \internal
* \brief Add XML with nodes that need an update of their maintenance state
*
* \param[in,out] xml Parent XML tag to add to
* \param[in] data_set Working set for cluster
*/
static int
add_maintenance_nodes(xmlNode *xml, const pe_working_set_t *data_set)
{
GListPtr gIter = NULL;
xmlNode *maintenance =
xml?create_xml_node(xml, XML_GRAPH_TAG_MAINTENANCE):NULL;
int count = 0;
for (gIter = data_set->nodes; gIter != NULL;
gIter = gIter->next) {
node_t *node = (node_t *) gIter->data;
struct node_shared_s *details = node->details;
if (!(is_remote_node(node))) {
continue; /* just remote nodes need to know atm */
}
if (details->maintenance != details->remote_maintenance) {
if (maintenance) {
crm_xml_add(
add_node_to_xml_by_id(node->details->id, maintenance),
XML_NODE_IS_MAINTENANCE, details->maintenance?"1":"0");
}
count++;
}
}
crm_trace("%s %d nodes to adjust maintenance-mode "
"to transition", maintenance?"Added":"Counted", count);
return count;
}
/*!
* \internal
* \brief Add pseudo action with nodes needing maintenance state update
*
* \param[in,out] data_set Working set for cluster
*/
void
add_maintenance_update(pe_working_set_t *data_set)
{
action_t *action = NULL;
if (add_maintenance_nodes(NULL, data_set)) {
crm_trace("adding maintenance state update pseudo action");
action = get_pseudo_op(CRM_OP_MAINTENANCE_NODES, data_set);
set_bit(action->flags, pe_action_print_always);
}
}
/*!
* \internal
* \brief Add XML with nodes that an action is expected to bring down
*
* If a specified action is expected to bring any nodes down, add an XML block
* with their UUIDs. When a node is lost, this allows the crmd to determine
* whether it was expected.
*
* \param[in,out] xml Parent XML tag to add to
* \param[in] action Action to check for downed nodes
* \param[in] data_set Working set for cluster
*/
static void
add_downed_nodes(xmlNode *xml, const action_t *action,
const pe_working_set_t *data_set)
{
CRM_CHECK(xml && action && action->node && data_set, return);
if (safe_str_eq(action->task, CRM_OP_SHUTDOWN)) {
/* Shutdown makes the action's node down */
xmlNode *downed = create_xml_node(xml, XML_GRAPH_TAG_DOWNED);
add_node_to_xml_by_id(action->node->details->id, downed);
} else if (safe_str_eq(action->task, CRM_OP_FENCE)) {
/* Fencing makes the action's node and any hosted guest nodes down */
const char *fence = g_hash_table_lookup(action->meta, "stonith_action");
if (safe_str_eq(fence, "off") || safe_str_eq(fence, "reboot")) {
xmlNode *downed = create_xml_node(xml, XML_GRAPH_TAG_DOWNED);
add_node_to_xml_by_id(action->node->details->id, downed);
pe_foreach_guest_node(data_set, action->node, add_node_to_xml, downed);
}
} else if (action->rsc && action->rsc->is_remote_node
&& safe_str_eq(action->task, CRMD_ACTION_STOP)) {
/* Stopping a remote connection resource makes connected node down,
* unless it's part of a migration
*/
GListPtr iter;
action_t *input;
gboolean migrating = FALSE;
for (iter = action->actions_before; iter != NULL; iter = iter->next) {
input = ((action_wrapper_t *) iter->data)->action;
if (input->rsc && safe_str_eq(action->rsc->id, input->rsc->id)
&& safe_str_eq(input->task, CRMD_ACTION_MIGRATED)) {
migrating = TRUE;
break;
}
}
if (!migrating) {
xmlNode *downed = create_xml_node(xml, XML_GRAPH_TAG_DOWNED);
add_node_to_xml_by_id(action->rsc->id, downed);
}
}
}
static xmlNode *
action2xml(action_t * action, gboolean as_input, pe_working_set_t *data_set)
{
gboolean needs_node_info = TRUE;
gboolean needs_maintenance_info = FALSE;
xmlNode *action_xml = NULL;
xmlNode *args_xml = NULL;
+ pe_rsc_action_details_t *rsc_details = NULL;
if (action == NULL) {
return NULL;
}
if (safe_str_eq(action->task, CRM_OP_FENCE)) {
/* All fences need node info; guest node fences are pseudo-events */
action_xml = create_xml_node(NULL,
is_set(action->flags, pe_action_pseudo)?
XML_GRAPH_TAG_PSEUDO_EVENT :
XML_GRAPH_TAG_CRM_EVENT);
} else if (safe_str_eq(action->task, CRM_OP_SHUTDOWN)) {
action_xml = create_xml_node(NULL, XML_GRAPH_TAG_CRM_EVENT);
} else if (safe_str_eq(action->task, CRM_OP_CLEAR_FAILCOUNT)) {
action_xml = create_xml_node(NULL, XML_GRAPH_TAG_CRM_EVENT);
} else if (safe_str_eq(action->task, CRM_OP_LRM_REFRESH)) {
action_xml = create_xml_node(NULL, XML_GRAPH_TAG_CRM_EVENT);
/* } else if(safe_str_eq(action->task, RSC_PROBED)) { */
/* action_xml = create_xml_node(NULL, XML_GRAPH_TAG_CRM_EVENT); */
} else if (is_set(action->flags, pe_action_pseudo)) {
if (safe_str_eq(action->task, CRM_OP_MAINTENANCE_NODES)) {
needs_maintenance_info = TRUE;
}
action_xml = create_xml_node(NULL, XML_GRAPH_TAG_PSEUDO_EVENT);
needs_node_info = FALSE;
} else {
action_xml = create_xml_node(NULL, XML_GRAPH_TAG_RSC_OP);
+ rsc_details = pe_rsc_action_details(action);
}
crm_xml_add_int(action_xml, XML_ATTR_ID, action->id);
crm_xml_add(action_xml, XML_LRM_ATTR_TASK, action->task);
if (action->rsc != NULL && action->rsc->clone_name != NULL) {
char *clone_key = NULL;
const char *interval_s = g_hash_table_lookup(action->meta, XML_LRM_ATTR_INTERVAL);
int interval = crm_parse_int(interval_s, "0");
if (safe_str_eq(action->task, RSC_NOTIFY)) {
const char *n_type = g_hash_table_lookup(action->meta, "notify_type");
const char *n_task = g_hash_table_lookup(action->meta, "notify_operation");
CRM_CHECK(n_type != NULL, crm_err("No notify type value found for %s", action->uuid));
CRM_CHECK(n_task != NULL,
crm_err("No notify operation value found for %s", action->uuid));
clone_key = generate_notify_key(action->rsc->clone_name, n_type, n_task);
} else if(action->cancel_task) {
clone_key = generate_op_key(action->rsc->clone_name, action->cancel_task, interval);
} else {
clone_key = generate_op_key(action->rsc->clone_name, action->task, interval);
}
CRM_CHECK(clone_key != NULL, crm_err("Could not generate a key for %s", action->uuid));
crm_xml_add(action_xml, XML_LRM_ATTR_TASK_KEY, clone_key);
crm_xml_add(action_xml, "internal_" XML_LRM_ATTR_TASK_KEY, action->uuid);
free(clone_key);
} else {
crm_xml_add(action_xml, XML_LRM_ATTR_TASK_KEY, action->uuid);
}
if (needs_node_info && action->node != NULL) {
node_t *router_node = get_router_node(action);
crm_xml_add(action_xml, XML_LRM_ATTR_TARGET, action->node->details->uname);
crm_xml_add(action_xml, XML_LRM_ATTR_TARGET_UUID, action->node->details->id);
if (router_node) {
crm_xml_add(action_xml, XML_LRM_ATTR_ROUTER_NODE, router_node->details->uname);
}
g_hash_table_insert(action->meta, strdup(XML_LRM_ATTR_TARGET), strdup(action->node->details->uname));
g_hash_table_insert(action->meta, strdup(XML_LRM_ATTR_TARGET_UUID), strdup(action->node->details->id));
}
/* No details if this action is only being listed in the inputs section */
if (as_input) {
return action_xml;
}
/* List affected resource */
if (action->rsc) {
if (is_set(action->flags, pe_action_pseudo) == FALSE) {
int lpc = 0;
xmlNode *rsc_xml = create_xml_node(action_xml, crm_element_name(action->rsc->xml));
const char *attr_list[] = {
XML_AGENT_ATTR_CLASS,
XML_AGENT_ATTR_PROVIDER,
XML_ATTR_TYPE
};
if (is_set(action->rsc->flags, pe_rsc_orphan) && action->rsc->clone_name) {
/* Do not use the 'instance free' name here as that
* might interfere with the instance we plan to keep.
* Ie. if there are more than two named /anonymous/
* instances on a given node, we need to make sure the
* command goes to the right one.
*
* Keep this block, even when everyone is using
* 'instance free' anonymous clone names - it means
* we'll do the right thing if anyone toggles the
* unique flag to 'off'
*/
crm_debug("Using orphan clone name %s instead of %s", action->rsc->id,
action->rsc->clone_name);
crm_xml_add(rsc_xml, XML_ATTR_ID, action->rsc->clone_name);
crm_xml_add(rsc_xml, XML_ATTR_ID_LONG, action->rsc->id);
} else if (is_not_set(action->rsc->flags, pe_rsc_unique)) {
const char *xml_id = ID(action->rsc->xml);
crm_debug("Using anonymous clone name %s for %s (aka. %s)", xml_id, action->rsc->id,
action->rsc->clone_name);
/* ID is what we'd like client to use
* ID_LONG is what they might know it as instead
*
* ID_LONG is only strictly needed /here/ during the
* transition period until all nodes in the cluster
* are running the new software /and/ have rebooted
* once (meaning that they've only ever spoken to a DC
* supporting this feature).
*
* If anyone toggles the unique flag to 'on', the
* 'instance free' name will correspond to an orphan
* and fall into the clause above instead
*/
crm_xml_add(rsc_xml, XML_ATTR_ID, xml_id);
if (action->rsc->clone_name && safe_str_neq(xml_id, action->rsc->clone_name)) {
crm_xml_add(rsc_xml, XML_ATTR_ID_LONG, action->rsc->clone_name);
} else {
crm_xml_add(rsc_xml, XML_ATTR_ID_LONG, action->rsc->id);
}
} else {
CRM_ASSERT(action->rsc->clone_name == NULL);
crm_xml_add(rsc_xml, XML_ATTR_ID, action->rsc->id);
}
for (lpc = 0; lpc < DIMOF(attr_list); lpc++) {
crm_xml_add(rsc_xml, attr_list[lpc],
g_hash_table_lookup(action->rsc->meta, attr_list[lpc]));
}
}
}
/* List any attributes in effect */
args_xml = create_xml_node(NULL, XML_TAG_ATTRS);
crm_xml_add(args_xml, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET);
g_hash_table_foreach(action->extra, hash2field, args_xml);
if (action->rsc != NULL && action->node) {
GHashTable *p = crm_str_table_new();
xmlNode *versioned_parameters = create_xml_node(NULL, XML_TAG_RSC_VER_ATTRS);
get_rsc_attributes(p, action->rsc, action->node, data_set);
g_hash_table_foreach(p, hash2smartfield, args_xml);
pe_get_versioned_attributes(versioned_parameters, action->rsc, action->node, data_set);
if (xml_has_children(versioned_parameters)) {
add_node_copy(action_xml, versioned_parameters);
}
g_hash_table_destroy(p);
free_xml(versioned_parameters);
} else if(action->rsc && action->rsc->variant <= pe_native) {
g_hash_table_foreach(action->rsc->parameters, hash2smartfield, args_xml);
if (xml_has_children(action->rsc->versioned_parameters)) {
add_node_copy(action_xml, action->rsc->versioned_parameters);
}
}
- if (xml_has_children(action->versioned_parameters)) {
- add_node_copy(action_xml, action->versioned_parameters);
- }
+ if (rsc_details) {
+ if (xml_has_children(rsc_details->versioned_parameters)) {
+ add_node_copy(action_xml, rsc_details->versioned_parameters);
+ }
- if (xml_has_children(action->versioned_meta)) {
- add_node_copy(action_xml, action->versioned_meta);
+ if (xml_has_children(rsc_details->versioned_meta)) {
+ add_node_copy(action_xml, rsc_details->versioned_meta);
+ }
}
g_hash_table_foreach(action->meta, hash2metafield, args_xml);
if (action->rsc != NULL) {
int isolated = 0;
const char *value = g_hash_table_lookup(action->rsc->meta, "external-ip");
resource_t *parent = action->rsc;
while (parent != NULL) {
isolated |= parent->isolation_wrapper ? 1 : 0;
parent->cmds->append_meta(parent, args_xml);
parent = parent->parent;
}
if (isolated && action->node) {
char *nodeattr = crm_meta_name(XML_RSC_ATTR_ISOLATION_HOST);
crm_xml_add(args_xml, nodeattr, action->node->details->uname);
free(nodeattr);
}
if(value) {
hash2smartfield((gpointer)"pcmk_external_ip", (gpointer)value, (gpointer)args_xml);
}
} else if (safe_str_eq(action->task, CRM_OP_FENCE) && action->node) {
/* Pass the node's attributes as meta-attributes.
*
* @TODO: Determine whether it is still necessary to do this. It was
* added in 33d99707, probably for the libfence-based implementation in
* c9a90bd, which is no longer used.
*/
g_hash_table_foreach(action->node->details->attrs, hash2metafield, args_xml);
}
sorted_xml(args_xml, action_xml, FALSE);
free_xml(args_xml);
/* List any nodes this action is expected to make down */
if (needs_node_info && (action->node != NULL)) {
add_downed_nodes(action_xml, action, data_set);
}
if (needs_maintenance_info) {
add_maintenance_nodes(action_xml, data_set);
}
crm_log_xml_trace(action_xml, "dumped action");
return action_xml;
}
static gboolean
should_dump_action(action_t * action)
{
CRM_CHECK(action != NULL, return FALSE);
if (is_set(action->flags, pe_action_dumped)) {
crm_trace("action %d (%s) was already dumped", action->id, action->uuid);
return FALSE;
} else if (is_set(action->flags, pe_action_pseudo) && safe_str_eq(action->task, CRM_OP_PROBED)) {
GListPtr lpc = NULL;
/* This is a horrible but convenient hack
*
* It mimimizes the number of actions with unsatisfied inputs
* (ie. not included in the graph)
*
* This in turn, means we can be more concise when printing
* aborted/incomplete graphs.
*
* It also makes it obvious which node is preventing
* probe_complete from running (presumably because it is only
* partially up)
*
* For these reasons we tolerate such perversions
*/
for (lpc = action->actions_after; lpc != NULL; lpc = lpc->next) {
action_wrapper_t *wrapper = (action_wrapper_t *) lpc->data;
if (is_not_set(wrapper->action->flags, pe_action_runnable)) {
/* Only interested in runnable operations */
} else if (safe_str_neq(wrapper->action->task, RSC_START)) {
/* Only interested in start operations */
} else if (is_set(wrapper->action->flags, pe_action_dumped)) {
crm_trace("action %d (%s) dependency of %s",
action->id, action->uuid, wrapper->action->uuid);
return TRUE;
} else if (should_dump_action(wrapper->action)) {
crm_trace("action %d (%s) dependency of %s",
action->id, action->uuid, wrapper->action->uuid);
return TRUE;
}
}
}
if (is_set(action->flags, pe_action_runnable) == FALSE) {
crm_trace("action %d (%s) was not runnable", action->id, action->uuid);
return FALSE;
} else if (is_set(action->flags, pe_action_optional)
&& is_set(action->flags, pe_action_print_always) == FALSE) {
crm_trace("action %d (%s) was optional", action->id, action->uuid);
return FALSE;
} else if (action->rsc != NULL && is_not_set(action->rsc->flags, pe_rsc_managed)) {
const char *interval = NULL;
interval = g_hash_table_lookup(action->meta, XML_LRM_ATTR_INTERVAL);
/* make sure probes and recurring monitors go through */
if (safe_str_neq(action->task, RSC_STATUS) && interval == NULL) {
crm_trace("action %d (%s) was for an unmanaged resource (%s)",
action->id, action->uuid, action->rsc->id);
return FALSE;
}
}
if (is_set(action->flags, pe_action_pseudo)
|| safe_str_eq(action->task, CRM_OP_FENCE)
|| safe_str_eq(action->task, CRM_OP_SHUTDOWN)) {
/* skip the next checks */
return TRUE;
}
if (action->node == NULL) {
pe_err("action %d (%s) was not allocated", action->id, action->uuid);
log_action(LOG_DEBUG, "Unallocated action", action, FALSE);
return FALSE;
} else if(is_container_remote_node(action->node) && action->node->details->remote_requires_reset == FALSE) {
crm_trace("Assuming action %s for %s will be runnable", action->uuid, action->node->details->uname);
} else if (action->node->details->online == FALSE) {
pe_err("action %d was (%s) scheduled for offline node", action->id, action->uuid);
log_action(LOG_DEBUG, "Action for offline node", action, FALSE);
return FALSE;
#if 0
/* but this would also affect resources that can be safely
* migrated before a fencing op
*/
} else if (action->node->details->unclean == FALSE) {
pe_err("action %d was (%s) scheduled for unclean node", action->id, action->uuid);
log_action(LOG_DEBUG, "Action for unclean node", action, FALSE);
return FALSE;
#endif
}
return TRUE;
}
/* lowest to highest */
static gint
sort_action_id(gconstpointer a, gconstpointer b)
{
const action_wrapper_t *action_wrapper2 = (const action_wrapper_t *)a;
const action_wrapper_t *action_wrapper1 = (const action_wrapper_t *)b;
if (a == NULL) {
return 1;
}
if (b == NULL) {
return -1;
}
if (action_wrapper1->action->id > action_wrapper2->action->id) {
return -1;
}
if (action_wrapper1->action->id < action_wrapper2->action->id) {
return 1;
}
return 0;
}
static gboolean
check_dump_input(int last_action, action_t * action, action_wrapper_t * wrapper)
{
int type = wrapper->type;
if (wrapper->state == pe_link_dumped) {
return TRUE;
} else if (wrapper->state == pe_link_dup) {
return FALSE;
}
type &= ~pe_order_implies_first_printed;
type &= ~pe_order_implies_then_printed;
type &= ~pe_order_optional;
if (is_not_set(type, pe_order_preserve)
&& action->rsc && action->rsc->fillers
&& wrapper->action->rsc && wrapper->action->node
&& wrapper->action->node->details->remote_rsc
&& (wrapper->action->node->details->remote_rsc->container == action->rsc)) {
/* This prevents user-defined ordering constraints between resources
* running in a guest node and the resource that defines that node.
*/
crm_warn("Invalid ordering constraint between %s and %s",
wrapper->action->rsc->id, action->rsc->id);
wrapper->type = pe_order_none;
return FALSE;
}
if (last_action == wrapper->action->id) {
crm_trace("Input (%d) %s duplicated for %s",
wrapper->action->id, wrapper->action->uuid, action->uuid);
wrapper->state = pe_link_dup;
return FALSE;
} else if (wrapper->type == pe_order_none) {
crm_trace("Input (%d) %s suppressed for %s",
wrapper->action->id, wrapper->action->uuid, action->uuid);
return FALSE;
} else if (is_set(wrapper->action->flags, pe_action_runnable) == FALSE
&& type == pe_order_none && safe_str_neq(wrapper->action->uuid, CRM_OP_PROBED)) {
crm_trace("Input (%d) %s optional (ordering) for %s",
wrapper->action->id, wrapper->action->uuid, action->uuid);
return FALSE;
} else if (is_set(wrapper->action->flags, pe_action_runnable) == FALSE
&& is_set(type, pe_order_one_or_more)) {
crm_trace("Input (%d) %s optional (one-or-more) for %s",
wrapper->action->id, wrapper->action->uuid, action->uuid);
return FALSE;
} else if (is_set(action->flags, pe_action_pseudo)
&& (wrapper->type & pe_order_stonith_stop)) {
crm_trace("Input (%d) %s suppressed for %s",
wrapper->action->id, wrapper->action->uuid, action->uuid);
return FALSE;
} else if ((wrapper->type & pe_order_implies_first_migratable) && (is_set(wrapper->action->flags, pe_action_runnable) == FALSE)) {
return FALSE;
} else if ((wrapper->type & pe_order_apply_first_non_migratable)
&& (is_set(wrapper->action->flags, pe_action_migrate_runnable))) {
return FALSE;
} else if ((wrapper->type == pe_order_optional)
&& crm_ends_with(wrapper->action->uuid, "_stop_0")
&& is_set(wrapper->action->flags, pe_action_migrate_runnable)) {
/* for optional only ordering, ordering is not preserved for
* a stop action that is actually involved with a migration. */
return FALSE;
} else if (wrapper->type == pe_order_load) {
crm_trace("check load filter %s.%s -> %s.%s",
wrapper->action->uuid,
wrapper->action->node ? wrapper->action->node->details->uname : "", action->uuid,
action->node ? action->node->details->uname : "");
if (action->rsc && safe_str_eq(action->task, RSC_MIGRATE)) {
/* Remove the orders like the following if not relevant:
* "load_stopped_node2" -> "rscA_migrate_to node1"
* which were created also from: pengine/native.c: MigrateRsc()
* order_actions(other, then, other_w->type);
*/
/* For migrate_to ops, we care about where it has been
* allocated to, not where the action will be executed
*/
if (wrapper->action->node == NULL || action->rsc->allocated_to == NULL
|| wrapper->action->node->details != action->rsc->allocated_to->details) {
/* Check if the actions are for the same node, ignore otherwise */
crm_trace("load filter - migrate");
wrapper->type = pe_order_none;
return FALSE;
}
} else if (wrapper->action->node == NULL || action->node == NULL
|| wrapper->action->node->details != action->node->details) {
/* Check if the actions are for the same node, ignore otherwise */
crm_trace("load filter - node");
wrapper->type = pe_order_none;
return FALSE;
} else if (is_set(wrapper->action->flags, pe_action_optional)) {
/* Check if the pre-req is optional, ignore if so */
crm_trace("load filter - optional");
wrapper->type = pe_order_none;
return FALSE;
}
} else if (wrapper->type == pe_order_anti_colocation) {
crm_trace("check anti-colocation filter %s.%s -> %s.%s",
wrapper->action->uuid,
wrapper->action->node ? wrapper->action->node->details->uname : "",
action->uuid,
action->node ? action->node->details->uname : "");
if (wrapper->action->node && action->node
&& wrapper->action->node->details != action->node->details) {
/* Check if the actions are for the same node, ignore otherwise */
crm_trace("anti-colocation filter - node");
wrapper->type = pe_order_none;
return FALSE;
} else if (is_set(wrapper->action->flags, pe_action_optional)) {
/* Check if the pre-req is optional, ignore if so */
crm_trace("anti-colocation filter - optional");
wrapper->type = pe_order_none;
return FALSE;
}
} else if (wrapper->action->rsc
&& wrapper->action->rsc != action->rsc
&& is_set(wrapper->action->rsc->flags, pe_rsc_failed)
&& is_not_set(wrapper->action->rsc->flags, pe_rsc_managed)
&& crm_ends_with(wrapper->action->uuid, "_stop_0")
&& action->rsc && pe_rsc_is_clone(action->rsc)) {
crm_warn("Ignoring requirement that %s complete before %s:"
" unmanaged failed resources cannot prevent clone shutdown",
wrapper->action->uuid, action->uuid);
return FALSE;
} else if (is_set(wrapper->action->flags, pe_action_dumped)
|| should_dump_action(wrapper->action)) {
crm_trace("Input (%d) %s should be dumped for %s", wrapper->action->id,
wrapper->action->uuid, action->uuid);
goto dump;
#if 0
} else if (is_set(wrapper->action->flags, pe_action_runnable)
&& is_set(wrapper->action->flags, pe_action_pseudo)
&& wrapper->action->rsc->variant != pe_native) {
crm_crit("Input (%d) %s should be dumped for %s",
wrapper->action->id, wrapper->action->uuid, action->uuid);
goto dump;
#endif
} else if (is_set(wrapper->action->flags, pe_action_optional) == TRUE
&& is_set(wrapper->action->flags, pe_action_print_always) == FALSE) {
crm_trace("Input (%d) %s optional for %s", wrapper->action->id,
wrapper->action->uuid, action->uuid);
crm_trace("Input (%d) %s n=%p p=%d r=%d o=%d a=%d f=0x%.6x",
wrapper->action->id, wrapper->action->uuid, wrapper->action->node,
is_set(wrapper->action->flags, pe_action_pseudo),
is_set(wrapper->action->flags, pe_action_runnable),
is_set(wrapper->action->flags, pe_action_optional),
is_set(wrapper->action->flags, pe_action_print_always), wrapper->type);
return FALSE;
}
dump:
return TRUE;
}
static gboolean
graph_has_loop(action_t * init_action, action_t * action, action_wrapper_t * wrapper)
{
GListPtr lpc = NULL;
gboolean has_loop = FALSE;
if (is_set(wrapper->action->flags, pe_action_tracking)) {
crm_trace("Breaking tracking loop: %s.%s -> %s.%s (0x%.6x)",
wrapper->action->uuid,
wrapper->action->node ? wrapper->action->node->details->uname : "",
action->uuid,
action->node ? action->node->details->uname : "",
wrapper->type);
return FALSE;
}
if (check_dump_input(-1, action, wrapper) == FALSE) {
return FALSE;
}
/* If there's any order like:
* "rscB_stop node2"-> "load_stopped_node2" -> "rscA_migrate_to node1"
* rscA is being migrated from node1 to node2,
* while rscB is being migrated from node2 to node1.
* There will be potential graph loop.
* Break the order "load_stopped_node2" -> "rscA_migrate_to node1".
*/
crm_trace("Checking graph loop: %s.%s -> %s.%s (0x%.6x)",
wrapper->action->uuid,
wrapper->action->node ? wrapper->action->node->details->uname : "",
action->uuid,
action->node ? action->node->details->uname : "",
wrapper->type);
if (wrapper->action == init_action) {
crm_debug("Found graph loop: %s.%s ->...-> %s.%s",
action->uuid,
action->node ? action->node->details->uname : "",
init_action->uuid,
init_action->node ? init_action->node->details->uname : "");
return TRUE;
}
set_bit(wrapper->action->flags, pe_action_tracking);
for (lpc = wrapper->action->actions_before; lpc != NULL; lpc = lpc->next) {
action_wrapper_t *wrapper_before = (action_wrapper_t *) lpc->data;
if (graph_has_loop(init_action, wrapper->action, wrapper_before)) {
has_loop = TRUE;
goto done;
}
}
done:
clear_bit(wrapper->action->flags, pe_action_tracking);
return has_loop;
}
static gboolean
should_dump_input(int last_action, action_t * action, action_wrapper_t * wrapper)
{
wrapper->state = pe_link_not_dumped;
if (check_dump_input(last_action, action, wrapper) == FALSE) {
return FALSE;
}
if (wrapper->type == pe_order_load
&& action->rsc
&& safe_str_eq(action->task, RSC_MIGRATE)) {
crm_trace("Checking graph loop - load migrate: %s.%s -> %s.%s",
wrapper->action->uuid,
wrapper->action->node ? wrapper->action->node->details->uname : "",
action->uuid,
action->node ? action->node->details->uname : "");
if (graph_has_loop(action, action, wrapper)) {
/* Remove the orders like the following if they are introducing any graph loops:
* "load_stopped_node2" -> "rscA_migrate_to node1"
* which were created also from: pengine/native.c: MigrateRsc()
* order_actions(other, then, other_w->type);
*/
crm_debug("Breaking graph loop - load migrate: %s.%s -> %s.%s",
wrapper->action->uuid,
wrapper->action->node ? wrapper->action->node->details->uname : "",
action->uuid,
action->node ? action->node->details->uname : "");
wrapper->type = pe_order_none;
return FALSE;
}
}
crm_trace("Input (%d) %s n=%p p=%d r=%d o=%d a=%d f=0x%.6x dumped for %s",
wrapper->action->id,
wrapper->action->uuid,
wrapper->action->node,
is_set(wrapper->action->flags, pe_action_pseudo),
is_set(wrapper->action->flags, pe_action_runnable),
is_set(wrapper->action->flags, pe_action_optional),
is_set(wrapper->action->flags, pe_action_print_always), wrapper->type, action->uuid);
return TRUE;
}
void
graph_element_from_action(action_t * action, pe_working_set_t * data_set)
{
GListPtr lpc = NULL;
int last_action = -1;
int synapse_priority = 0;
xmlNode *syn = NULL;
xmlNode *set = NULL;
xmlNode *in = NULL;
xmlNode *input = NULL;
xmlNode *xml_action = NULL;
if (should_dump_action(action) == FALSE) {
return;
}
set_bit(action->flags, pe_action_dumped);
syn = create_xml_node(data_set->graph, "synapse");
set = create_xml_node(syn, "action_set");
in = create_xml_node(syn, "inputs");
crm_xml_add_int(syn, XML_ATTR_ID, data_set->num_synapse);
data_set->num_synapse++;
if (action->rsc != NULL) {
synapse_priority = action->rsc->priority;
}
if (action->priority > synapse_priority) {
synapse_priority = action->priority;
}
if (synapse_priority > 0) {
crm_xml_add_int(syn, XML_CIB_ATTR_PRIORITY, synapse_priority);
}
xml_action = action2xml(action, FALSE, data_set);
add_node_nocopy(set, crm_element_name(xml_action), xml_action);
action->actions_before = g_list_sort(action->actions_before, sort_action_id);
for (lpc = action->actions_before; lpc != NULL; lpc = lpc->next) {
action_wrapper_t *wrapper = (action_wrapper_t *) lpc->data;
if (should_dump_input(last_action, action, wrapper) == FALSE) {
continue;
}
wrapper->state = pe_link_dumped;
CRM_CHECK(last_action < wrapper->action->id,;
);
last_action = wrapper->action->id;
input = create_xml_node(in, "trigger");
xml_action = action2xml(wrapper->action, TRUE, data_set);
add_node_nocopy(input, crm_element_name(xml_action), xml_action);
}
}
diff --git a/pengine/test10/remote-fence-unclean-3.dot b/pengine/test10/remote-fence-unclean-3.dot
index b32b77e3a7..14adaefdd5 100644
--- a/pengine/test10/remote-fence-unclean-3.dot
+++ b/pengine/test10/remote-fence-unclean-3.dot
@@ -1,19 +1,14 @@
digraph "g" {
-"all_stopped" -> "fence1_start_0 overcloud-controller-0" [ style = bold]
"all_stopped" [ style=bold color="green" fontcolor="orange"]
"fence1_monitor_0 overcloud-controller-0" -> "fence1_start_0 overcloud-controller-0" [ style = bold]
"fence1_monitor_0 overcloud-controller-0" [ style=bold color="green" fontcolor="black"]
"fence1_monitor_0 overcloud-controller-1" -> "fence1_start_0 overcloud-controller-0" [ style = bold]
"fence1_monitor_0 overcloud-controller-1" [ style=bold color="green" fontcolor="black"]
"fence1_monitor_0 overcloud-controller-2" -> "fence1_start_0 overcloud-controller-0" [ style = bold]
"fence1_monitor_0 overcloud-controller-2" [ style=bold color="green" fontcolor="black"]
"fence1_monitor_60000 overcloud-controller-0" [ style=bold color="green" fontcolor="black"]
"fence1_start_0 overcloud-controller-0" -> "fence1_monitor_60000 overcloud-controller-0" [ style = bold]
"fence1_start_0 overcloud-controller-0" [ style=bold color="green" fontcolor="black"]
"overcloud-novacompute-0_stop_0 overcloud-controller-0" -> "all_stopped" [ style = bold]
"overcloud-novacompute-0_stop_0 overcloud-controller-0" [ style=bold color="green" fontcolor="black"]
-"stonith 'reboot' overcloud-novacompute-0" -> "stonith_complete" [ style = bold]
-"stonith 'reboot' overcloud-novacompute-0" [ style=bold color="green" fontcolor="black"]
-"stonith_complete" -> "all_stopped" [ style = bold]
-"stonith_complete" [ style=bold color="green" fontcolor="orange"]
}
diff --git a/pengine/test10/remote-fence-unclean-3.exp b/pengine/test10/remote-fence-unclean-3.exp
index 2e341bdc36..64e5a622f7 100644
--- a/pengine/test10/remote-fence-unclean-3.exp
+++ b/pengine/test10/remote-fence-unclean-3.exp
@@ -1,114 +1,85 @@
<transition_graph cluster-delay="60s" stonith-timeout="60s" failed-stop-offset="INFINITY" failed-start-offset="INFINITY" transition_id="0">
<synapse id="0">
<action_set>
<rsc_op id="47" operation="monitor" operation_key="fence1_monitor_60000" on_node="overcloud-controller-0" on_node_uuid="1">
<primitive id="fence1" class="stonith" type="fence_xvm"/>
<attributes CRM_meta_interval="60000" CRM_meta_name="monitor" CRM_meta_on_node="overcloud-controller-0" CRM_meta_on_node_uuid="1" CRM_meta_timeout="20000" multicast_address="225.0.0.2"/>
</rsc_op>
</action_set>
<inputs>
<trigger>
<rsc_op id="46" operation="start" operation_key="fence1_start_0" on_node="overcloud-controller-0" on_node_uuid="1"/>
</trigger>
</inputs>
</synapse>
<synapse id="1">
<action_set>
<rsc_op id="46" operation="start" operation_key="fence1_start_0" on_node="overcloud-controller-0" on_node_uuid="1">
<primitive id="fence1" class="stonith" type="fence_xvm"/>
<attributes CRM_meta_on_node="overcloud-controller-0" CRM_meta_on_node_uuid="1" CRM_meta_timeout="20000" multicast_address="225.0.0.2"/>
</rsc_op>
</action_set>
<inputs>
- <trigger>
- <pseudo_event id="42" operation="all_stopped" operation_key="all_stopped"/>
- </trigger>
<trigger>
<rsc_op id="43" operation="monitor" operation_key="fence1_monitor_0" on_node="overcloud-controller-0" on_node_uuid="1"/>
</trigger>
<trigger>
<rsc_op id="44" operation="monitor" operation_key="fence1_monitor_0" on_node="overcloud-controller-1" on_node_uuid="2"/>
</trigger>
<trigger>
<rsc_op id="45" operation="monitor" operation_key="fence1_monitor_0" on_node="overcloud-controller-2" on_node_uuid="3"/>
</trigger>
</inputs>
</synapse>
<synapse id="2">
<action_set>
<rsc_op id="45" operation="monitor" operation_key="fence1_monitor_0" on_node="overcloud-controller-2" on_node_uuid="3">
<primitive id="fence1" class="stonith" type="fence_xvm"/>
<attributes CRM_meta_on_node="overcloud-controller-2" CRM_meta_on_node_uuid="3" CRM_meta_op_target_rc="7" CRM_meta_timeout="20000" multicast_address="225.0.0.2"/>
</rsc_op>
</action_set>
<inputs/>
</synapse>
<synapse id="3">
<action_set>
<rsc_op id="44" operation="monitor" operation_key="fence1_monitor_0" on_node="overcloud-controller-1" on_node_uuid="2">
<primitive id="fence1" class="stonith" type="fence_xvm"/>
<attributes CRM_meta_on_node="overcloud-controller-1" CRM_meta_on_node_uuid="2" CRM_meta_op_target_rc="7" CRM_meta_timeout="20000" multicast_address="225.0.0.2"/>
</rsc_op>
</action_set>
<inputs/>
</synapse>
<synapse id="4">
<action_set>
<rsc_op id="43" operation="monitor" operation_key="fence1_monitor_0" on_node="overcloud-controller-0" on_node_uuid="1">
<primitive id="fence1" class="stonith" type="fence_xvm"/>
<attributes CRM_meta_on_node="overcloud-controller-0" CRM_meta_on_node_uuid="1" CRM_meta_op_target_rc="7" CRM_meta_timeout="20000" multicast_address="225.0.0.2"/>
</rsc_op>
</action_set>
<inputs/>
</synapse>
<synapse id="5">
<action_set>
<rsc_op id="30" operation="stop" operation_key="overcloud-novacompute-0_stop_0" on_node="overcloud-controller-0" on_node_uuid="1">
<primitive id="overcloud-novacompute-0" class="ocf" provider="pacemaker" type="remote"/>
<attributes CRM_meta_name="stop" CRM_meta_on_node="overcloud-controller-0" CRM_meta_on_node_uuid="1" CRM_meta_timeout="60000" reconnect_interval="240"/>
<downed>
<node id="overcloud-novacompute-0"/>
</downed>
</rsc_op>
</action_set>
<inputs/>
</synapse>
<synapse id="6">
- <action_set>
- <crm_event id="185" operation="stonith" operation_key="stonith-overcloud-novacompute-0-reboot" on_node="overcloud-novacompute-0" on_node_uuid="overcloud-novacompute-0">
- <attributes CRM_meta_compute_role="true" CRM_meta_on_node="overcloud-novacompute-0" CRM_meta_on_node_uuid="overcloud-novacompute-0" CRM_meta_stonith_action="reboot" />
- <downed>
- <node id="overcloud-novacompute-0"/>
- </downed>
- </crm_event>
- </action_set>
- <inputs/>
- </synapse>
- <synapse id="7">
- <action_set>
- <pseudo_event id="184" operation="stonith_complete" operation_key="stonith_complete">
- <attributes />
- </pseudo_event>
- </action_set>
- <inputs>
- <trigger>
- <crm_event id="185" operation="stonith" operation_key="stonith-overcloud-novacompute-0-reboot" on_node="overcloud-novacompute-0" on_node_uuid="overcloud-novacompute-0"/>
- </trigger>
- </inputs>
- </synapse>
- <synapse id="8">
<action_set>
<pseudo_event id="42" operation="all_stopped" operation_key="all_stopped">
<attributes />
</pseudo_event>
</action_set>
<inputs>
<trigger>
<rsc_op id="30" operation="stop" operation_key="overcloud-novacompute-0_stop_0" on_node="overcloud-controller-0" on_node_uuid="1"/>
</trigger>
- <trigger>
- <pseudo_event id="184" operation="stonith_complete" operation_key="stonith_complete"/>
- </trigger>
</inputs>
</synapse>
</transition_graph>
diff --git a/pengine/test10/remote-fence-unclean-3.summary b/pengine/test10/remote-fence-unclean-3.summary
index ec24500635..ec54d8e481 100644
--- a/pengine/test10/remote-fence-unclean-3.summary
+++ b/pengine/test10/remote-fence-unclean-3.summary
@@ -1,85 +1,82 @@
Current cluster status:
Online: [ overcloud-controller-0 overcloud-controller-1 overcloud-controller-2 ]
RemoteOFFLINE: [ overcloud-novacompute-0 ]
Containers: [ galera-bundle-0:galera-bundle-docker-0 galera-bundle-1:galera-bundle-docker-1 galera-bundle-2:galera-bundle-docker-2 rabbitmq-bundle-0:rabbitmq-bundle-docker-0 rabbitmq-bundle-1:rabbitmq-bundle-docker-1 rabbitmq-bundle-2:rabbitmq-bundle-docker-2 redis-bundle-0:redis-bundle-docker-0 redis-bundle-1:redis-bundle-docker-1 redis-bundle-2:redis-bundle-docker-2 ]
fence1 (stonith:fence_xvm): Stopped
overcloud-novacompute-0 (ocf::pacemaker:remote): FAILED overcloud-controller-0
Docker container set: rabbitmq-bundle [192.168.24.1:8787/tripleoupstream/centos-binary-rabbitmq:latest]
rabbitmq-bundle-0 (ocf::heartbeat:rabbitmq-cluster): Started overcloud-controller-0
rabbitmq-bundle-1 (ocf::heartbeat:rabbitmq-cluster): Started overcloud-controller-1
rabbitmq-bundle-2 (ocf::heartbeat:rabbitmq-cluster): Started overcloud-controller-2
Docker container set: galera-bundle [192.168.24.1:8787/tripleoupstream/centos-binary-mariadb:latest]
galera-bundle-0 (ocf::heartbeat:galera): Master overcloud-controller-0
galera-bundle-1 (ocf::heartbeat:galera): Master overcloud-controller-1
galera-bundle-2 (ocf::heartbeat:galera): Master overcloud-controller-2
Docker container set: redis-bundle [192.168.24.1:8787/tripleoupstream/centos-binary-redis:latest]
redis-bundle-0 (ocf::heartbeat:redis): Master overcloud-controller-0
redis-bundle-1 (ocf::heartbeat:redis): Slave overcloud-controller-1
redis-bundle-2 (ocf::heartbeat:redis): Slave overcloud-controller-2
ip-192.168.24.9 (ocf::heartbeat:IPaddr2): Started overcloud-controller-0
ip-10.0.0.7 (ocf::heartbeat:IPaddr2): Started overcloud-controller-1
ip-172.16.2.4 (ocf::heartbeat:IPaddr2): Started overcloud-controller-2
ip-172.16.2.8 (ocf::heartbeat:IPaddr2): Started overcloud-controller-0
ip-172.16.1.9 (ocf::heartbeat:IPaddr2): Started overcloud-controller-1
ip-172.16.3.9 (ocf::heartbeat:IPaddr2): Started overcloud-controller-2
Docker container set: haproxy-bundle [192.168.24.1:8787/tripleoupstream/centos-binary-haproxy:latest]
haproxy-bundle-docker-0 (ocf::heartbeat:docker): Started overcloud-controller-0
haproxy-bundle-docker-1 (ocf::heartbeat:docker): Started overcloud-controller-1
haproxy-bundle-docker-2 (ocf::heartbeat:docker): Started overcloud-controller-2
Docker container: openstack-cinder-volume [192.168.24.1:8787/tripleoupstream/centos-binary-cinder-volume:latest]
openstack-cinder-volume-docker-0 (ocf::heartbeat:docker): Started overcloud-controller-0
Docker container: openstack-cinder-backup [192.168.24.1:8787/tripleoupstream/centos-binary-cinder-backup:latest]
openstack-cinder-backup-docker-0 (ocf::heartbeat:docker): Started overcloud-controller-1
Transition Summary:
- * Fence overcloud-novacompute-0
* Start fence1 (overcloud-controller-0)
* Stop overcloud-novacompute-0 (overcloud-controller-0)
Executing cluster transition:
* Resource action: fence1 monitor on overcloud-controller-2
* Resource action: fence1 monitor on overcloud-controller-1
* Resource action: fence1 monitor on overcloud-controller-0
* Resource action: overcloud-novacompute-0 stop on overcloud-controller-0
- * Fencing overcloud-novacompute-0 (reboot)
- * Pseudo action: stonith_complete
* Pseudo action: all_stopped
* Resource action: fence1 start on overcloud-controller-0
* Resource action: fence1 monitor=60000 on overcloud-controller-0
Revised cluster status:
Online: [ overcloud-controller-0 overcloud-controller-1 overcloud-controller-2 ]
RemoteOFFLINE: [ overcloud-novacompute-0 ]
Containers: [ galera-bundle-0:galera-bundle-docker-0 galera-bundle-1:galera-bundle-docker-1 galera-bundle-2:galera-bundle-docker-2 rabbitmq-bundle-0:rabbitmq-bundle-docker-0 rabbitmq-bundle-1:rabbitmq-bundle-docker-1 rabbitmq-bundle-2:rabbitmq-bundle-docker-2 redis-bundle-0:redis-bundle-docker-0 redis-bundle-1:redis-bundle-docker-1 redis-bundle-2:redis-bundle-docker-2 ]
fence1 (stonith:fence_xvm): Started overcloud-controller-0
overcloud-novacompute-0 (ocf::pacemaker:remote): Stopped
Docker container set: rabbitmq-bundle [192.168.24.1:8787/tripleoupstream/centos-binary-rabbitmq:latest]
rabbitmq-bundle-0 (ocf::heartbeat:rabbitmq-cluster): Started overcloud-controller-0
rabbitmq-bundle-1 (ocf::heartbeat:rabbitmq-cluster): Started overcloud-controller-1
rabbitmq-bundle-2 (ocf::heartbeat:rabbitmq-cluster): Started overcloud-controller-2
Docker container set: galera-bundle [192.168.24.1:8787/tripleoupstream/centos-binary-mariadb:latest]
galera-bundle-0 (ocf::heartbeat:galera): Master overcloud-controller-0
galera-bundle-1 (ocf::heartbeat:galera): Master overcloud-controller-1
galera-bundle-2 (ocf::heartbeat:galera): Master overcloud-controller-2
Docker container set: redis-bundle [192.168.24.1:8787/tripleoupstream/centos-binary-redis:latest]
redis-bundle-0 (ocf::heartbeat:redis): Master overcloud-controller-0
redis-bundle-1 (ocf::heartbeat:redis): Slave overcloud-controller-1
redis-bundle-2 (ocf::heartbeat:redis): Slave overcloud-controller-2
ip-192.168.24.9 (ocf::heartbeat:IPaddr2): Started overcloud-controller-0
ip-10.0.0.7 (ocf::heartbeat:IPaddr2): Started overcloud-controller-1
ip-172.16.2.4 (ocf::heartbeat:IPaddr2): Started overcloud-controller-2
ip-172.16.2.8 (ocf::heartbeat:IPaddr2): Started overcloud-controller-0
ip-172.16.1.9 (ocf::heartbeat:IPaddr2): Started overcloud-controller-1
ip-172.16.3.9 (ocf::heartbeat:IPaddr2): Started overcloud-controller-2
Docker container set: haproxy-bundle [192.168.24.1:8787/tripleoupstream/centos-binary-haproxy:latest]
haproxy-bundle-docker-0 (ocf::heartbeat:docker): Started overcloud-controller-0
haproxy-bundle-docker-1 (ocf::heartbeat:docker): Started overcloud-controller-1
haproxy-bundle-docker-2 (ocf::heartbeat:docker): Started overcloud-controller-2
Docker container: openstack-cinder-volume [192.168.24.1:8787/tripleoupstream/centos-binary-cinder-volume:latest]
openstack-cinder-volume-docker-0 (ocf::heartbeat:docker): Started overcloud-controller-0
Docker container: openstack-cinder-backup [192.168.24.1:8787/tripleoupstream/centos-binary-cinder-backup:latest]
openstack-cinder-backup-docker-0 (ocf::heartbeat:docker): Started overcloud-controller-1

File Metadata

Mime Type
text/x-diff
Expires
Tue, Jul 8, 6:21 PM (17 h, 9 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2002599
Default Alt Text
(777 KB)

Event Timeline