Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F7609808
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
115 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/crmd/te_callbacks.c b/crmd/te_callbacks.c
index ff0a664b7e..6c0670c96c 100644
--- a/crmd/te_callbacks.c
+++ b/crmd/te_callbacks.c
@@ -1,859 +1,852 @@
/*
* 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/stat.h>
#include <crm/crm.h>
#include <crm/common/xml.h>
#include <crm/msg_xml.h>
#include <tengine.h>
#include <te_callbacks.h>
#include <crmd_fsa.h>
#include <crm/cluster.h> /* For ONLINESTATUS etc */
void te_update_confirm(const char *event, xmlNode * msg);
extern char *te_uuid;
gboolean shuttingdown = FALSE;
crm_graph_t *transition_graph;
crm_trigger_t *transition_trigger = NULL;
static unsigned long int stonith_max_attempts = 10;
/* #define rsc_op_template "//"XML_TAG_DIFF_ADDED"//"XML_TAG_CIB"//"XML_CIB_TAG_STATE"[@uname='%s']"//"XML_LRM_TAG_RSC_OP"[@id='%s]" */
#define rsc_op_template "//"XML_TAG_DIFF_ADDED"//"XML_TAG_CIB"//"XML_LRM_TAG_RSC_OP"[@id='%s']"
static const char *
get_node_id(xmlNode * rsc_op)
{
xmlNode *node = rsc_op;
while (node != NULL && safe_str_neq(XML_CIB_TAG_STATE, TYPE(node))) {
node = node->parent;
}
CRM_CHECK(node != NULL, return NULL);
return ID(node);
}
void
update_stonith_max_attempts(const char* value)
{
if (safe_str_eq(value, INFINITY_S)) {
stonith_max_attempts = node_score_infinity;
}
else {
stonith_max_attempts = crm_int_helper(value, NULL);
}
}
static void
te_legacy_update_diff(const char *event, xmlNode * diff)
{
int lpc, max;
xmlXPathObject *xpathObj = NULL;
CRM_CHECK(diff != NULL, return);
xml_log_patchset(LOG_TRACE, __FUNCTION__, diff);
if (cib_config_changed(NULL, NULL, &diff)) {
abort_transition(INFINITY, tg_restart, "Non-status change", diff);
goto bail; /* configuration changed */
}
/* Tickets Attributes - Added/Updated */
xpathObj =
xpath_search(diff,
"//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED "//" XML_CIB_TAG_TICKETS);
if (numXpathResults(xpathObj) > 0) {
xmlNode *aborted = getXpathResult(xpathObj, 0);
abort_transition(INFINITY, tg_restart, "Ticket attribute: update", aborted);
goto bail;
}
freeXpathObject(xpathObj);
/* Tickets Attributes - Removed */
xpathObj =
xpath_search(diff,
"//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_REMOVED "//" XML_CIB_TAG_TICKETS);
if (numXpathResults(xpathObj) > 0) {
xmlNode *aborted = getXpathResult(xpathObj, 0);
abort_transition(INFINITY, tg_restart, "Ticket attribute: removal", aborted);
goto bail;
}
freeXpathObject(xpathObj);
/* Transient Attributes - Added/Updated */
xpathObj =
xpath_search(diff,
"//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED "//"
XML_TAG_TRANSIENT_NODEATTRS "//" XML_CIB_TAG_NVPAIR);
max = numXpathResults(xpathObj);
for (lpc = 0; lpc < max; lpc++) {
xmlNode *attr = getXpathResult(xpathObj, lpc);
const char *name = crm_element_value(attr, XML_NVPAIR_ATTR_NAME);
const char *value = NULL;
if (safe_str_eq(CRM_OP_PROBED, name)) {
value = crm_element_value(attr, XML_NVPAIR_ATTR_VALUE);
}
if (crm_is_true(value) == FALSE) {
abort_transition(INFINITY, tg_restart, "Transient attribute: update", attr);
crm_log_xml_trace(attr, "Abort");
goto bail;
}
}
freeXpathObject(xpathObj);
/* Transient Attributes - Removed */
xpathObj =
xpath_search(diff,
"//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_REMOVED "//"
XML_TAG_TRANSIENT_NODEATTRS);
if (numXpathResults(xpathObj) > 0) {
xmlNode *aborted = getXpathResult(xpathObj, 0);
abort_transition(INFINITY, tg_restart, "Transient attribute: removal", aborted);
goto bail;
}
freeXpathObject(xpathObj);
/*
* Check for and fast-track the processing of LRM refreshes
* In large clusters this can result in _huge_ speedups
*
* Unfortunately we can only do so when there are no pending actions
* Otherwise we could miss updates we're waiting for and stall
*
*/
xpathObj = NULL;
if (transition_graph->pending == 0) {
xpathObj =
xpath_search(diff,
"//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED "//"
XML_LRM_TAG_RESOURCE);
}
max = numXpathResults(xpathObj);
if (max > 1) {
/* Updates by, or in response to, TE actions will never contain updates
* for more than one resource at a time
*/
crm_debug("Detected LRM refresh - %d resources updated: Skipping all resource events", max);
crm_log_xml_trace(diff, "lrm-refresh");
abort_transition(INFINITY, tg_restart, "LRM Refresh", NULL);
goto bail;
}
freeXpathObject(xpathObj);
/* Process operation updates */
xpathObj =
xpath_search(diff,
"//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED "//" XML_LRM_TAG_RSC_OP);
if (numXpathResults(xpathObj)) {
/*
<status>
<node_state id="node1" state=CRMD_JOINSTATE_MEMBER exp_state="active">
<lrm>
<lrm_resources>
<rsc_state id="" rsc_id="rsc4" node_id="node1" rsc_state="stopped"/>
*/
int lpc = 0, max = numXpathResults(xpathObj);
for (lpc = 0; lpc < max; lpc++) {
xmlNode *rsc_op = getXpathResult(xpathObj, lpc);
const char *node = get_node_id(rsc_op);
process_graph_event(rsc_op, node);
}
}
freeXpathObject(xpathObj);
/* Detect deleted (as opposed to replaced or added) actions - eg. crm_resource -C */
xpathObj = xpath_search(diff, "//" XML_TAG_DIFF_REMOVED "//" XML_LRM_TAG_RSC_OP);
max = numXpathResults(xpathObj);
for (lpc = 0; lpc < max; lpc++) {
int path_max = 0;
const char *op_id = NULL;
char *rsc_op_xpath = NULL;
xmlXPathObject *op_match = NULL;
xmlNode *match = getXpathResult(xpathObj, lpc);
CRM_LOG_ASSERT(match != NULL);
if(match == NULL) { continue; };
op_id = ID(match);
path_max = strlen(rsc_op_template) + strlen(op_id) + 1;
rsc_op_xpath = calloc(1, path_max);
snprintf(rsc_op_xpath, path_max, rsc_op_template, op_id);
op_match = xpath_search(diff, rsc_op_xpath);
if (numXpathResults(op_match) == 0) {
/* Prevent false positives by matching cancelations too */
const char *node = get_node_id(match);
crm_action_t *cancelled = get_cancel_action(op_id, node);
if (cancelled == NULL) {
crm_debug("No match for deleted action %s (%s on %s)", rsc_op_xpath, op_id,
node);
abort_transition(INFINITY, tg_restart, "Resource op removal", match);
freeXpathObject(op_match);
free(rsc_op_xpath);
goto bail;
} else {
crm_debug("Deleted lrm_rsc_op %s on %s was for graph event %d",
op_id, node, cancelled->id);
}
}
freeXpathObject(op_match);
free(rsc_op_xpath);
}
bail:
freeXpathObject(xpathObj);
}
static void process_resource_updates(
const char *node, xmlNode *xml, xmlNode *change, const char *op, const char *xpath)
{
xmlNode *cIter = NULL;
xmlNode *rsc = NULL;
xmlNode *rsc_op = NULL;
int num_resources = 0;
if(xml == NULL) {
return;
} else if(strcmp((const char*)xml->name, XML_CIB_TAG_LRM) == 0) {
xml = first_named_child(xml, XML_LRM_TAG_RESOURCES);
crm_trace("Got %p in %s", xml, XML_CIB_TAG_LRM);
}
CRM_ASSERT(strcmp((const char*)xml->name, XML_LRM_TAG_RESOURCES) == 0);
for(cIter = xml->children; cIter; cIter = cIter->next) {
num_resources++;
}
if(num_resources > 1) {
/*
* Check for and fast-track the processing of LRM refreshes
* In large clusters this can result in _huge_ speedups
*
* Unfortunately we can only do so when there are no pending actions
* Otherwise we could miss updates we're waiting for and stall
*
*/
crm_debug("Detected LRM refresh - %d resources updated", num_resources);
crm_log_xml_trace(change, "lrm-refresh");
abort_transition(INFINITY, tg_restart, "LRM Refresh", NULL);
return;
}
for (rsc = __xml_first_child(xml); rsc != NULL; rsc = __xml_next(rsc)) {
crm_trace("Processing %s", ID(rsc));
for (rsc_op = __xml_first_child(rsc); rsc_op != NULL; rsc_op = __xml_next(rsc_op)) {
crm_trace("Processing %s", ID(rsc_op));
process_graph_event(rsc_op, node);
}
}
}
#define NODE_PATT "/lrm[@id="
static char *get_node_from_xpath(const char *xpath)
{
char *nodeid = NULL;
char *tmp = strstr(xpath, NODE_PATT);
if(tmp) {
tmp += strlen(NODE_PATT);
tmp += 1;
nodeid = strdup(tmp);
tmp = strstr(nodeid, "\'");
CRM_ASSERT(tmp);
tmp[0] = 0;
}
return nodeid;
}
static char *extract_node_uuid(const char *xpath)
{
char *mutable_path = strdup(xpath);
char *node_uuid = NULL;
char *search = NULL;
char *match = NULL;
match = strstr(mutable_path, "node_state[@id=\'");
if (match == NULL) {
free(mutable_path);
return NULL;
}
match += strlen("node_state[@id=\'");
search = strchr(match, '\'');
if (search == NULL) {
free(mutable_path);
return NULL;
}
search[0] = 0;
node_uuid = strdup(match);
free(mutable_path);
return node_uuid;
}
static void
abort_unless_down(const char *xpath, const char *op, xmlNode *change,
const char *reason)
{
char *node_uuid = NULL;
crm_action_t *down = NULL;
if(safe_str_neq(op, "delete")) {
abort_transition(INFINITY, tg_restart, reason, change);
return;
}
node_uuid = extract_node_uuid(xpath);
if(node_uuid == NULL) {
crm_err("Could not extract node ID from %s", xpath);
abort_transition(INFINITY, tg_restart, reason, change);
return;
}
down = match_down_event(node_uuid, TRUE);
if(down == NULL || down->executed == false) {
crm_trace("Not expecting %s to be down (%s)", node_uuid, xpath);
abort_transition(INFINITY, tg_restart, reason, change);
} else {
crm_trace("Expecting changes to %s (%s)", node_uuid, xpath);
}
free(node_uuid);
}
void
te_update_diff(const char *event, xmlNode * msg)
{
int rc = -EINVAL;
int format = 1;
xmlNode *change = NULL;
const char *op = NULL;
xmlNode *diff = NULL;
int p_add[] = { 0, 0, 0 };
int p_del[] = { 0, 0, 0 };
CRM_CHECK(msg != NULL, return);
crm_element_value_int(msg, F_CIB_RC, &rc);
if (transition_graph == NULL) {
crm_trace("No graph");
return;
} else if (rc < pcmk_ok) {
crm_trace("Filter rc=%d (%s)", rc, pcmk_strerror(rc));
return;
} else if (transition_graph->complete == TRUE
&& fsa_state != S_IDLE
&& fsa_state != S_TRANSITION_ENGINE && fsa_state != S_POLICY_ENGINE) {
crm_trace("Filter state=%s, complete=%d", fsa_state2string(fsa_state),
transition_graph->complete);
return;
}
op = crm_element_value(msg, F_CIB_OPERATION);
diff = get_message_xml(msg, F_CIB_UPDATE_RESULT);
xml_patch_versions(diff, p_add, p_del);
crm_debug("Processing (%s) diff: %d.%d.%d -> %d.%d.%d (%s)", op,
p_del[0], p_del[1], p_del[2], p_add[0], p_add[1], p_add[2],
fsa_state2string(fsa_state));
crm_element_value_int(diff, "format", &format);
switch(format) {
case 1:
te_legacy_update_diff(event, diff);
return;
case 2:
/* Cool, we know what to do here */
crm_log_xml_trace(diff, "Patch:Raw");
break;
default:
crm_warn("Unknown patch format: %d", format);
return;
}
for (change = __xml_first_child(diff); change != NULL; change = __xml_next(change)) {
const char *name = NULL;
const char *op = crm_element_value(change, XML_DIFF_OP);
const char *xpath = crm_element_value(change, XML_DIFF_PATH);
xmlNode *match = NULL;
const char *node = NULL;
if(op == NULL) {
continue;
} else if(strcmp(op, "create") == 0) {
match = change->children;
} else if(strcmp(op, "move") == 0) {
continue;
} else if(strcmp(op, "modify") == 0) {
match = first_named_child(change, XML_DIFF_RESULT);
if(match) {
match = match->children;
}
}
if(match) {
if (match->type == XML_COMMENT_NODE) {
crm_trace("Ignoring %s operation for comment at %s", op, xpath);
continue;
}
name = (const char *)match->name;
}
crm_trace("Handling %s operation for %s%s%s",
op, (xpath? xpath : "CIB"),
(name? " matched by " : ""), (name? name : ""));
if(xpath == NULL) {
/* Version field, ignore */
} else if(strstr(xpath, "/cib/configuration")) {
abort_transition(INFINITY, tg_restart, "Configuration change", change);
break; /* Won't be packaged with any resource operations we may be waiting for */
} else if(strstr(xpath, "/"XML_CIB_TAG_TICKETS) || safe_str_eq(name, XML_CIB_TAG_TICKETS)) {
abort_transition(INFINITY, tg_restart, "Ticket attribute change", change);
break; /* Won't be packaged with any resource operations we may be waiting for */
} else if(strstr(xpath, "/"XML_TAG_TRANSIENT_NODEATTRS"[") || safe_str_eq(name, XML_TAG_TRANSIENT_NODEATTRS)) {
abort_unless_down(xpath, op, change, "Transient attribute change");
break; /* Won't be packaged with any resource operations we may be waiting for */
} else if(strstr(xpath, "/"XML_LRM_TAG_RSC_OP"[") && safe_str_eq(op, "delete")) {
crm_action_t *cancel = NULL;
char *mutable_key = strdup(xpath);
char *key, *node_uuid;
/* Extract the part of xpath between last pair of single quotes */
key = strrchr(mutable_key, '\'');
if (key != NULL) {
*key = '\0';
key = strrchr(mutable_key, '\'');
}
if (key == NULL) {
crm_warn("Ignoring malformed CIB update (resource deletion)");
free(mutable_key);
continue;
}
++key;
node_uuid = extract_node_uuid(xpath);
cancel = get_cancel_action(key, node_uuid);
if (cancel == NULL) {
abort_transition(INFINITY, tg_restart, "Resource operation removal", change);
} else {
crm_info("Cancellation of %s on %s confirmed (%d)", key, node_uuid, cancel->id);
stop_te_timer(cancel->timer);
te_action_confirmed(cancel);
update_graph(transition_graph, cancel);
trigger_graph();
}
free(mutable_key);
free(node_uuid);
} else if(strstr(xpath, "/"XML_CIB_TAG_LRM"[") && safe_str_eq(op, "delete")) {
abort_unless_down(xpath, op, change, "Resource state removal");
} else if(strstr(xpath, "/"XML_CIB_TAG_STATE"[") && safe_str_eq(op, "delete")) {
abort_unless_down(xpath, op, change, "Node state removal");
} else if(name == NULL) {
crm_debug("No result for %s operation to %s", op, xpath);
CRM_ASSERT(strcmp(op, "delete") == 0 || strcmp(op, "move") == 0);
} else if(strcmp(name, XML_TAG_CIB) == 0) {
xmlNode *state = NULL;
xmlNode *status = first_named_child(match, XML_CIB_TAG_STATUS);
xmlNode *config = first_named_child(match, XML_CIB_TAG_CONFIGURATION);
for (state = __xml_first_child(status); state != NULL; state = __xml_next(state)) {
xmlNode *lrm = first_named_child(state, XML_CIB_TAG_LRM);
node = ID(state);
process_resource_updates(node, lrm, change, op, xpath);
}
if(config) {
abort_transition(INFINITY, tg_restart, "Non-status-only change", change);
}
} else if(strcmp(name, XML_CIB_TAG_STATUS) == 0) {
xmlNode *state = NULL;
for (state = __xml_first_child(match); state != NULL; state = __xml_next(state)) {
xmlNode *lrm = first_named_child(state, XML_CIB_TAG_LRM);
node = ID(state);
process_resource_updates(node, lrm, change, op, xpath);
}
} else if(strcmp(name, XML_CIB_TAG_STATE) == 0) {
xmlNode *lrm = first_named_child(match, XML_CIB_TAG_LRM);
node = ID(match);
process_resource_updates(node, lrm, change, op, xpath);
} else if(strcmp(name, XML_CIB_TAG_LRM) == 0) {
node = ID(match);
process_resource_updates(node, match, change, op, xpath);
} else if(strcmp(name, XML_LRM_TAG_RESOURCES) == 0) {
char *local_node = get_node_from_xpath(xpath);
process_resource_updates(local_node, match, change, op, xpath);
free(local_node);
} else if(strcmp(name, XML_LRM_TAG_RESOURCE) == 0) {
xmlNode *rsc_op;
char *local_node = get_node_from_xpath(xpath);
for (rsc_op = __xml_first_child(match); rsc_op != NULL; rsc_op = __xml_next(rsc_op)) {
process_graph_event(rsc_op, local_node);
}
free(local_node);
} else if(strcmp(name, XML_LRM_TAG_RSC_OP) == 0) {
char *local_node = get_node_from_xpath(xpath);
process_graph_event(match, local_node);
free(local_node);
} else {
crm_err("Ignoring %s operation for %s %p, %s", op, xpath, match, name);
}
}
}
gboolean
process_te_message(xmlNode * msg, xmlNode * xml_data)
{
const char *from = crm_element_value(msg, F_ORIG);
const char *sys_to = crm_element_value(msg, F_CRM_SYS_TO);
const char *sys_from = crm_element_value(msg, F_CRM_SYS_FROM);
const char *ref = crm_element_value(msg, F_CRM_REFERENCE);
const char *op = crm_element_value(msg, F_CRM_TASK);
const char *type = crm_element_value(msg, F_CRM_MSG_TYPE);
crm_trace("Processing %s (%s) message", op, ref);
crm_log_xml_trace(msg, "ipc");
if (op == NULL) {
/* error */
} else if (sys_to == NULL || strcasecmp(sys_to, CRM_SYSTEM_TENGINE) != 0) {
crm_trace("Bad sys-to %s", crm_str(sys_to));
return FALSE;
} else if (safe_str_eq(op, CRM_OP_INVOKE_LRM)
&& safe_str_eq(sys_from, CRM_SYSTEM_LRMD)
/* && safe_str_eq(type, XML_ATTR_RESPONSE) */
) {
xmlXPathObject *xpathObj = NULL;
crm_log_xml_trace(msg, "Processing (N)ACK");
crm_debug("Processing (N)ACK %s from %s", crm_element_value(msg, F_CRM_REFERENCE), from);
xpathObj = xpath_search(xml_data, "//" XML_LRM_TAG_RSC_OP);
if (numXpathResults(xpathObj)) {
int lpc = 0, max = numXpathResults(xpathObj);
for (lpc = 0; lpc < max; lpc++) {
xmlNode *rsc_op = getXpathResult(xpathObj, lpc);
const char *node = get_node_id(rsc_op);
process_graph_event(rsc_op, node);
}
freeXpathObject(xpathObj);
} else {
crm_log_xml_err(msg, "Invalid (N)ACK");
freeXpathObject(xpathObj);
return FALSE;
}
} else {
crm_err("Unknown command: %s::%s from %s", type, op, sys_from);
}
crm_trace("finished processing message");
return TRUE;
}
GHashTable *stonith_failures = NULL;
struct st_fail_rec {
int count;
- int last_rc;
};
gboolean
too_many_st_failures(void)
{
GHashTableIter iter;
const char *key = NULL;
struct st_fail_rec *value = NULL;
if (stonith_failures == NULL) {
return FALSE;
}
g_hash_table_iter_init(&iter, stonith_failures);
while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) {
if (value->count > stonith_max_attempts ) {
crm_warn("Too many failures to fence %s (%d), giving up", key, value->count);
return TRUE;
- } else if (value->last_rc == -ENODEV) {
- crm_warn("No devices found in cluster to fence %s, giving up", key);
- return TRUE;
}
}
return FALSE;
}
void
st_fail_count_reset(const char *target)
{
struct st_fail_rec *rec = NULL;
if (stonith_failures) {
rec = g_hash_table_lookup(stonith_failures, target);
}
if (rec) {
rec->count = 0;
- rec->last_rc = 0;
}
}
static void
st_fail_count_increment(const char *target, int rc)
{
struct st_fail_rec *rec = NULL;
if (stonith_failures == NULL) {
stonith_failures =
g_hash_table_new_full(crm_str_hash, g_str_equal, g_hash_destroy_str, free);
}
rec = g_hash_table_lookup(stonith_failures, target);
if (rec) {
rec->count++;
} else {
rec = malloc(sizeof(struct st_fail_rec));
if(rec == NULL) {
return;
}
rec->count = 1;
g_hash_table_insert(stonith_failures, strdup(target), rec);
}
- rec->last_rc = rc;
-
}
void
tengine_stonith_callback(stonith_t * stonith, stonith_callback_data_t * data)
{
char *uuid = NULL;
int target_rc = -1;
int stonith_id = -1;
int transition_id = -1;
crm_action_t *action = NULL;
int call_id = data->call_id;
int rc = data->rc;
char *userdata = data->userdata;
CRM_CHECK(userdata != NULL, return);
crm_notice("Stonith operation %d/%s: %s (%d)", call_id, (char *)userdata,
pcmk_strerror(rc), rc);
if (AM_I_DC == FALSE) {
return;
}
/* crm_info("call=%d, optype=%d, node_name=%s, result=%d, node_list=%s, action=%s", */
/* op->call_id, op->optype, op->node_name, op->op_result, */
/* (char *)op->node_list, op->private_data); */
/* filter out old STONITH actions */
CRM_CHECK(decode_transition_key(userdata, &uuid, &transition_id, &stonith_id, &target_rc),
crm_err("Invalid event detected");
goto bail;
);
if (transition_graph->complete || stonith_id < 0 || safe_str_neq(uuid, te_uuid)
|| transition_graph->id != transition_id) {
crm_info("Ignoring STONITH action initiated outside of the current transition");
goto bail;
}
action = get_action(stonith_id, FALSE);
if (action == NULL) {
crm_err("Stonith action not matched");
goto bail;
}
stop_te_timer(action->timer);
if (rc == pcmk_ok) {
const char *target = crm_element_value(action->xml, XML_LRM_ATTR_TARGET);
const char *uuid = crm_element_value(action->xml, XML_LRM_ATTR_TARGET_UUID);
const char *op = crm_meta_value(action->params, "stonith_action");
crm_debug("Stonith operation %d for %s passed", call_id, target);
if (action->confirmed == FALSE) {
te_action_confirmed(action);
if (action->sent_update == FALSE && safe_str_neq("on", op)) {
send_stonith_update(action, target, uuid);
action->sent_update = TRUE;
}
}
st_fail_count_reset(target);
} else {
const char *target = crm_element_value_const(action->xml, XML_LRM_ATTR_TARGET);
action->failed = TRUE;
crm_notice("Stonith operation %d for %s failed (%s): aborting transition.",
call_id, target, pcmk_strerror(rc));
abort_transition(INFINITY, tg_restart, "Stonith failed", NULL);
st_fail_count_increment(target, rc);
}
update_graph(transition_graph, action);
trigger_graph();
bail:
free(userdata);
free(uuid);
return;
}
void
cib_fencing_updated(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
{
if (rc < pcmk_ok) {
crm_err("Fencing update %d for %s: failed - %s (%d)",
call_id, (char *)user_data, pcmk_strerror(rc), rc);
crm_log_xml_warn(msg, "Failed update");
abort_transition(INFINITY, tg_shutdown, "CIB update failed", NULL);
} else {
crm_info("Fencing update %d for %s: complete", call_id, (char *)user_data);
}
}
void
cib_action_updated(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
{
if (rc < pcmk_ok) {
crm_err("Update %d FAILED: %s", call_id, pcmk_strerror(rc));
}
}
gboolean
action_timer_callback(gpointer data)
{
crm_action_timer_t *timer = NULL;
CRM_CHECK(data != NULL, return FALSE);
timer = (crm_action_timer_t *) data;
stop_te_timer(timer);
crm_warn("Timer popped (timeout=%d, abort_level=%d, complete=%s)",
timer->timeout,
transition_graph->abort_priority, transition_graph->complete ? "true" : "false");
CRM_CHECK(timer->action != NULL, return FALSE);
if (transition_graph->complete) {
crm_warn("Ignoring timeout while not in transition");
} else if (timer->reason == timeout_action_warn) {
print_action(LOG_WARNING, "Action missed its timeout: ", timer->action);
/* Don't check the FSA state
*
* We might also be in S_INTEGRATION or some other state waiting for this
* action so we can close the transition and continue
*/
} else {
/* fail the action */
gboolean send_update = TRUE;
const char *task = crm_element_value(timer->action->xml, XML_LRM_ATTR_TASK);
print_action(LOG_ERR, "Aborting transition, action lost: ", timer->action);
timer->action->failed = TRUE;
te_action_confirmed(timer->action);
abort_transition(INFINITY, tg_restart, "Action lost", NULL);
update_graph(transition_graph, timer->action);
trigger_graph();
if (timer->action->type != action_type_rsc) {
send_update = FALSE;
} else if (safe_str_eq(task, RSC_CANCEL)) {
/* we don't need to update the CIB with these */
send_update = FALSE;
}
if (send_update) {
cib_action_update(timer->action, PCMK_LRM_OP_TIMEOUT, PCMK_OCF_UNKNOWN_ERROR);
}
}
return FALSE;
}
diff --git a/lib/cib/cib_attrs.c b/lib/cib/cib_attrs.c
index a2b118f00c..0f5d5a718e 100644
--- a/lib/cib/cib_attrs.c
+++ b/lib/cib/cib_attrs.c
@@ -1,602 +1,592 @@
/*
* 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 <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/cib/internal.h>
#define attr_msg(level, fmt, args...) do { \
if(to_console) { \
printf(fmt"\n", ##args); \
} else { \
do_crm_log(level, fmt , ##args); \
} \
} while(0)
/* could also check for possible truncation */
#define attr_snprintf(_str, _offset, _limit, ...) do { \
_offset += snprintf(_str + _offset, \
(_limit > _offset) ? _limit - _offset : 0, \
__VA_ARGS__); \
} while(0)
extern int
find_nvpair_attr_delegate(cib_t * the_cib, const char *attr, const char *section,
const char *node_uuid, const char *attr_set_type, const char *set_name,
const char *attr_id, const char *attr_name, gboolean to_console,
char **value, const char *user_name)
{
int offset = 0;
static int xpath_max = 1024;
int rc = pcmk_ok;
char *xpath_string = NULL;
xmlNode *xml_search = NULL;
const char *set_type = NULL;
const char *node_type = NULL;
if (attr_set_type) {
set_type = attr_set_type;
} else {
set_type = XML_TAG_ATTR_SETS;
}
CRM_ASSERT(value != NULL);
*value = NULL;
if (safe_str_eq(section, XML_CIB_TAG_CRMCONFIG)) {
node_uuid = NULL;
set_type = XML_CIB_TAG_PROPSET;
} else if (safe_str_eq(section, XML_CIB_TAG_OPCONFIG)
|| safe_str_eq(section, XML_CIB_TAG_RSCCONFIG)) {
node_uuid = NULL;
set_type = XML_TAG_META_SETS;
} else if (safe_str_eq(section, XML_CIB_TAG_TICKETS)) {
node_uuid = NULL;
section = XML_CIB_TAG_STATUS;
node_type = XML_CIB_TAG_TICKETS;
} else if (node_uuid == NULL) {
return -EINVAL;
}
xpath_string = calloc(1, xpath_max);
if (xpath_string == NULL) {
crm_perror(LOG_CRIT, "Could not create xpath");
return -ENOMEM;
}
attr_snprintf(xpath_string, offset, xpath_max, "%.128s", get_object_path(section));
if (safe_str_eq(node_type, XML_CIB_TAG_TICKETS)) {
attr_snprintf(xpath_string, offset, xpath_max, "//%s", node_type);
} else if (node_uuid) {
const char *node_type = XML_CIB_TAG_NODE;
if (safe_str_eq(section, XML_CIB_TAG_STATUS)) {
node_type = XML_CIB_TAG_STATE;
set_type = XML_TAG_TRANSIENT_NODEATTRS;
}
attr_snprintf(xpath_string, offset, xpath_max, "//%s[@id='%s']", node_type,
node_uuid);
}
if (set_name) {
attr_snprintf(xpath_string, offset, xpath_max, "//%s[@id='%.128s']", set_type,
set_name);
} else {
attr_snprintf(xpath_string, offset, xpath_max, "//%s", set_type);
}
attr_snprintf(xpath_string, offset, xpath_max, "//nvpair[");
if (attr_id) {
attr_snprintf(xpath_string, offset, xpath_max, "@id='%s'", attr_id);
}
if (attr_name) {
if (attr_id) {
attr_snprintf(xpath_string, offset, xpath_max, " and ");
}
attr_snprintf(xpath_string, offset, xpath_max, "@name='%.128s'", attr_name);
}
attr_snprintf(xpath_string, offset, xpath_max, "]");
CRM_LOG_ASSERT(offset > 0);
rc = cib_internal_op(the_cib, CIB_OP_QUERY, NULL, xpath_string, NULL, &xml_search,
cib_sync_call | cib_scope_local | cib_xpath, user_name);
if (rc != pcmk_ok) {
crm_trace("Query failed for attribute %s (section=%s, node=%s, set=%s, xpath=%s): %s",
attr_name, section, crm_str(node_uuid), crm_str(set_name), xpath_string,
pcmk_strerror(rc));
goto done;
}
crm_log_xml_debug(xml_search, "Match");
if (xml_has_children(xml_search)) {
xmlNode *child = NULL;
rc = -ENOTUNIQ;
attr_msg(LOG_WARNING, "Multiple attributes match name=%s", attr_name);
for (child = __xml_first_child(xml_search); child != NULL; child = __xml_next(child)) {
attr_msg(LOG_INFO, " Value: %s \t(id=%s)",
crm_element_value(child, XML_NVPAIR_ATTR_VALUE), ID(child));
}
} else {
const char *tmp = crm_element_value(xml_search, attr);
if (tmp) {
*value = strdup(tmp);
}
}
done:
free(xpath_string);
free_xml(xml_search);
return rc;
}
int
update_attr_delegate(cib_t * the_cib, int call_options,
const char *section, const char *node_uuid, const char *set_type,
const char *set_name, const char *attr_id, const char *attr_name,
const char *attr_value, gboolean to_console, const char *user_name,
const char *node_type)
{
const char *tag = NULL;
int rc = pcmk_ok;
xmlNode *xml_top = NULL;
xmlNode *xml_obj = NULL;
char *local_attr_id = NULL;
char *local_set_name = NULL;
CRM_CHECK(section != NULL, return -EINVAL);
CRM_CHECK(attr_value != NULL, return -EINVAL);
CRM_CHECK(attr_name != NULL || attr_id != NULL, return -EINVAL);
rc = find_nvpair_attr_delegate(the_cib, XML_ATTR_ID, section, node_uuid, set_type, set_name,
attr_id, attr_name, to_console, &local_attr_id, user_name);
if (rc == pcmk_ok) {
attr_id = local_attr_id;
goto do_modify;
} else if (rc != -ENXIO) {
return rc;
/* } else if(attr_id == NULL) { */
/* return -EINVAL; */
} else {
crm_trace("%s does not exist, create it", attr_name);
if (safe_str_eq(section, XML_CIB_TAG_TICKETS)) {
node_uuid = NULL;
section = XML_CIB_TAG_STATUS;
node_type = XML_CIB_TAG_TICKETS;
xml_top = create_xml_node(xml_obj, XML_CIB_TAG_STATUS);
xml_obj = create_xml_node(xml_top, XML_CIB_TAG_TICKETS);
} else if (safe_str_eq(section, XML_CIB_TAG_NODES)) {
if (node_uuid == NULL) {
return -EINVAL;
}
if (safe_str_eq(node_type, "remote")) {
xml_top = create_xml_node(xml_obj, XML_CIB_TAG_NODES);
xml_obj = create_xml_node(xml_top, XML_CIB_TAG_NODE);
crm_xml_add(xml_obj, XML_ATTR_TYPE, "remote");
crm_xml_add(xml_obj, XML_ATTR_ID, node_uuid);
crm_xml_add(xml_obj, XML_ATTR_UNAME, node_uuid);
} else {
tag = XML_CIB_TAG_NODE;
}
} else if (safe_str_eq(section, XML_CIB_TAG_STATUS)) {
tag = XML_TAG_TRANSIENT_NODEATTRS;
if (node_uuid == NULL) {
return -EINVAL;
}
xml_top = create_xml_node(xml_obj, XML_CIB_TAG_STATE);
crm_xml_add(xml_top, XML_ATTR_ID, node_uuid);
xml_obj = xml_top;
} else {
tag = section;
node_uuid = NULL;
}
if (set_name == NULL) {
if (safe_str_eq(section, XML_CIB_TAG_CRMCONFIG)) {
local_set_name = strdup(CIB_OPTIONS_FIRST);
} else if (safe_str_eq(node_type, XML_CIB_TAG_TICKETS)) {
local_set_name = crm_concat(section, XML_CIB_TAG_TICKETS, '-');
} else if (node_uuid) {
local_set_name = crm_concat(section, node_uuid, '-');
if (set_type) {
char *tmp_set_name = local_set_name;
local_set_name = crm_concat(tmp_set_name, set_type, '-');
free(tmp_set_name);
}
} else {
local_set_name = crm_concat(section, "options", '-');
}
set_name = local_set_name;
}
if (attr_id == NULL) {
- int lpc = 0;
-
local_attr_id = crm_concat(set_name, attr_name, '-');
+ crm_xml_sanitize_id(local_attr_id);
attr_id = local_attr_id;
- /* Minimal attempt at sanitizing automatic IDs */
- for (lpc = 0; local_attr_id[lpc] != 0; lpc++) {
- switch (local_attr_id[lpc]) {
- case ':':
- case '#':
- local_attr_id[lpc] = '.';
- }
- }
-
} else if (attr_name == NULL) {
attr_name = attr_id;
}
crm_trace("Creating %s/%s", section, tag);
if (tag != NULL) {
xml_obj = create_xml_node(xml_obj, tag);
crm_xml_add(xml_obj, XML_ATTR_ID, node_uuid);
if (xml_top == NULL) {
xml_top = xml_obj;
}
}
if (node_uuid == NULL && safe_str_neq(node_type, XML_CIB_TAG_TICKETS)) {
if (safe_str_eq(section, XML_CIB_TAG_CRMCONFIG)) {
xml_obj = create_xml_node(xml_obj, XML_CIB_TAG_PROPSET);
} else {
xml_obj = create_xml_node(xml_obj, XML_TAG_META_SETS);
}
} else if (set_type) {
xml_obj = create_xml_node(xml_obj, set_type);
} else {
xml_obj = create_xml_node(xml_obj, XML_TAG_ATTR_SETS);
}
crm_xml_add(xml_obj, XML_ATTR_ID, set_name);
if (xml_top == NULL) {
xml_top = xml_obj;
}
}
do_modify:
xml_obj = create_xml_node(xml_obj, XML_CIB_TAG_NVPAIR);
if (xml_top == NULL) {
xml_top = xml_obj;
}
crm_xml_add(xml_obj, XML_ATTR_ID, attr_id);
crm_xml_add(xml_obj, XML_NVPAIR_ATTR_NAME, attr_name);
crm_xml_add(xml_obj, XML_NVPAIR_ATTR_VALUE, attr_value);
crm_log_xml_trace(xml_top, "update_attr");
rc = cib_internal_op(the_cib, CIB_OP_MODIFY, NULL, section, xml_top, NULL,
call_options | cib_quorum_override, user_name);
if (rc < pcmk_ok) {
attr_msg(LOG_ERR, "Error setting %s=%s (section=%s, set=%s): %s",
attr_name, attr_value, section, crm_str(set_name), pcmk_strerror(rc));
crm_log_xml_info(xml_top, "Update");
}
free(local_set_name);
free(local_attr_id);
free_xml(xml_top);
return rc;
}
int
read_attr_delegate(cib_t * the_cib,
const char *section, const char *node_uuid, const char *set_type,
const char *set_name, const char *attr_id, const char *attr_name,
char **attr_value, gboolean to_console, const char *user_name)
{
int rc = pcmk_ok;
CRM_ASSERT(attr_value != NULL);
CRM_CHECK(section != NULL, return -EINVAL);
CRM_CHECK(attr_name != NULL || attr_id != NULL, return -EINVAL);
*attr_value = NULL;
rc = find_nvpair_attr_delegate(the_cib, XML_NVPAIR_ATTR_VALUE, section, node_uuid, set_type,
set_name, attr_id, attr_name, to_console, attr_value, user_name);
if (rc != pcmk_ok) {
crm_trace("Query failed for attribute %s (section=%s, node=%s, set=%s): %s",
attr_name, section, crm_str(set_name), crm_str(node_uuid), pcmk_strerror(rc));
}
return rc;
}
int
delete_attr_delegate(cib_t * the_cib, int options,
const char *section, const char *node_uuid, const char *set_type,
const char *set_name, const char *attr_id, const char *attr_name,
const char *attr_value, gboolean to_console, const char *user_name)
{
int rc = pcmk_ok;
xmlNode *xml_obj = NULL;
char *local_attr_id = NULL;
CRM_CHECK(section != NULL, return -EINVAL);
CRM_CHECK(attr_name != NULL || attr_id != NULL, return -EINVAL);
if (attr_id == NULL) {
rc = find_nvpair_attr_delegate(the_cib, XML_ATTR_ID, section, node_uuid, set_type,
set_name, attr_id, attr_name, to_console, &local_attr_id,
user_name);
if (rc != pcmk_ok) {
return rc;
}
attr_id = local_attr_id;
}
xml_obj = create_xml_node(NULL, XML_CIB_TAG_NVPAIR);
crm_xml_add(xml_obj, XML_ATTR_ID, attr_id);
crm_xml_add(xml_obj, XML_NVPAIR_ATTR_NAME, attr_name);
crm_xml_add(xml_obj, XML_NVPAIR_ATTR_VALUE, attr_value);
rc = cib_internal_op(the_cib, CIB_OP_DELETE, NULL, section, xml_obj, NULL,
options | cib_quorum_override, user_name);
if (rc == pcmk_ok) {
attr_msg(LOG_DEBUG, "Deleted %s %s: id=%s%s%s%s%s\n",
section, node_uuid ? "attribute" : "option", local_attr_id,
set_name ? " set=" : "", set_name ? set_name : "",
attr_name ? " name=" : "", attr_name ? attr_name : "");
}
free(local_attr_id);
free_xml(xml_obj);
return rc;
}
/*!
* \internal
* \brief Parse node UUID from search result
*
* \param[in] result XML search result
* \param[out] uuid If non-NULL, where to store parsed UUID
* \param[out] is_remote If non-NULL, set TRUE if result is remote node
*
* \return pcmk_ok if UUID was successfully parsed, -ENXIO otherwise
*/
static int
get_uuid_from_result(xmlNode *result, char **uuid, int *is_remote)
{
int rc = -ENXIO;
const char *tag;
const char *parsed_uuid = NULL;
int parsed_is_remote = FALSE;
if (result == NULL) {
return rc;
}
/* If there are multiple results, the first is sufficient */
tag = (const char *) (result->name);
if (safe_str_eq(tag, "xpath-query")) {
result = __xml_first_child(result);
tag = (const char *) (result->name);
}
if (safe_str_eq(tag, XML_CIB_TAG_NODE)) {
/* Result is <node> tag from <nodes> section */
if (safe_str_eq(crm_element_value(result, XML_ATTR_TYPE), "remote")) {
parsed_uuid = crm_element_value(result, XML_ATTR_UNAME);
parsed_is_remote = TRUE;
} else {
parsed_uuid = ID(result);
parsed_is_remote = FALSE;
}
} else if (safe_str_eq(tag, XML_CIB_TAG_RESOURCE)) {
/* Result is <primitive> for ocf:pacemaker:remote resource */
parsed_uuid = ID(result);
parsed_is_remote = TRUE;
} else if (safe_str_eq(tag, XML_CIB_TAG_NVPAIR)) {
/* Result is remote-node parameter of <primitive> for guest node */
parsed_uuid = crm_element_value(result, XML_NVPAIR_ATTR_VALUE);
parsed_is_remote = TRUE;
} else if (safe_str_eq(tag, XML_CIB_TAG_STATE)) {
/* Result is <node_state> tag from <status> section */
parsed_uuid = crm_element_value(result, XML_ATTR_UNAME);
crm_element_value_int(result, F_ATTRD_IS_REMOTE, &parsed_is_remote);
}
if (parsed_uuid) {
if (uuid) {
*uuid = strdup(parsed_uuid);
}
if (is_remote) {
*is_remote = parsed_is_remote;
}
rc = pcmk_ok;
}
return rc;
}
/* Search string to find a node by name, as:
* - cluster or remote node in nodes section
* - remote node in resources section
* - guest node in resources section
* - orphaned remote node in status section
*/
#define XPATH_NODE \
"/" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION "/" XML_CIB_TAG_NODES \
"/" XML_CIB_TAG_NODE "[@" XML_ATTR_UNAME "='%s']" \
"|/" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION "/" XML_CIB_TAG_RESOURCES \
"/" XML_CIB_TAG_RESOURCE \
"[@class='ocf'][@provider='pacemaker'][@type='remote'][@id='%s']" \
"|/" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION "/" XML_CIB_TAG_RESOURCES \
"/" XML_CIB_TAG_RESOURCE "/" XML_TAG_META_SETS "/" XML_CIB_TAG_NVPAIR \
"[@name='" XML_RSC_ATTR_REMOTE_NODE "'][@value='%s']" \
"|/" XML_TAG_CIB "/" XML_CIB_TAG_STATUS "/" XML_CIB_TAG_STATE \
"[@" XML_NODE_IS_REMOTE "='true'][@" XML_ATTR_UUID "='%s']"
int
query_node_uuid(cib_t * the_cib, const char *uname, char **uuid, int *is_remote_node)
{
int rc = pcmk_ok;
char *xpath_string;
xmlNode *xml_search = NULL;
CRM_ASSERT(uname != NULL);
if (uuid) {
*uuid = NULL;
}
if (is_remote_node) {
*is_remote_node = FALSE;
}
xpath_string = crm_strdup_printf(XPATH_NODE, uname, uname, uname, uname);
if (cib_internal_op(the_cib, CIB_OP_QUERY, NULL, xpath_string, NULL,
&xml_search, cib_sync_call|cib_scope_local|cib_xpath,
NULL) == pcmk_ok) {
rc = get_uuid_from_result(xml_search, uuid, is_remote_node);
} else {
rc = -ENXIO;
}
free(xpath_string);
free_xml(xml_search);
if (rc != pcmk_ok) {
crm_debug("Could not map node name '%s' to a UUID: %s",
uname, pcmk_strerror(rc));
} else {
crm_info("Mapped node name '%s' to UUID %s", uname, (uuid? *uuid : ""));
}
return rc;
}
int
query_node_uname(cib_t * the_cib, const char *uuid, char **uname)
{
int rc = pcmk_ok;
xmlNode *a_child = NULL;
xmlNode *xml_obj = NULL;
xmlNode *fragment = NULL;
const char *child_name = NULL;
CRM_ASSERT(uname != NULL);
CRM_ASSERT(uuid != NULL);
rc = the_cib->cmds->query(the_cib, XML_CIB_TAG_NODES, &fragment,
cib_sync_call | cib_scope_local);
if (rc != pcmk_ok) {
return rc;
}
xml_obj = fragment;
CRM_CHECK(safe_str_eq(crm_element_name(xml_obj), XML_CIB_TAG_NODES), return -ENOMSG);
CRM_ASSERT(xml_obj != NULL);
crm_log_xml_trace(xml_obj, "Result section");
rc = -ENXIO;
*uname = NULL;
for (a_child = __xml_first_child(xml_obj); a_child != NULL; a_child = __xml_next(a_child)) {
if (crm_str_eq((const char *)a_child->name, XML_CIB_TAG_NODE, TRUE)) {
child_name = ID(a_child);
if (safe_str_eq(uuid, child_name)) {
child_name = crm_element_value(a_child, XML_ATTR_UNAME);
if (child_name != NULL) {
*uname = strdup(child_name);
rc = pcmk_ok;
}
break;
}
}
}
free_xml(fragment);
return rc;
}
int
set_standby(cib_t * the_cib, const char *uuid, const char *scope, const char *standby_value)
{
int rc = pcmk_ok;
char *attr_id = NULL;
CRM_CHECK(uuid != NULL, return -EINVAL);
CRM_CHECK(standby_value != NULL, return -EINVAL);
if (safe_str_eq(scope, "reboot") || safe_str_eq(scope, XML_CIB_TAG_STATUS)) {
scope = XML_CIB_TAG_STATUS;
attr_id = crm_strdup_printf("transient-standby-%.256s", uuid);
} else {
scope = XML_CIB_TAG_NODES;
attr_id = crm_strdup_printf("standby-%.256s", uuid);
}
rc = update_attr_delegate(the_cib, cib_sync_call, scope, uuid, NULL, NULL,
attr_id, "standby", standby_value, TRUE, NULL, NULL);
free(attr_id);
return rc;
}
diff --git a/lib/common/schemas.c b/lib/common/schemas.c
index f1a0238024..055d1410e0 100644
--- a/lib/common/schemas.c
+++ b/lib/common/schemas.c
@@ -1,937 +1,940 @@
/*
* Copyright (C) 2004-2016 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 <stdio.h>
#include <string.h>
#include <dirent.h>
#include <errno.h>
#include <math.h>
#include <sys/stat.h>
#if HAVE_LIBXML2
# include <libxml/relaxng.h>
#endif
#if HAVE_LIBXSLT
# include <libxslt/xslt.h>
# include <libxslt/transform.h>
#endif
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
typedef struct {
xmlRelaxNGPtr rng;
xmlRelaxNGValidCtxtPtr valid;
xmlRelaxNGParserCtxtPtr parser;
} relaxng_ctx_cache_t;
struct schema_s {
int type;
float version;
char *name;
char *location;
char *transform;
int after_transform;
void *cache;
};
static struct schema_s *known_schemas = NULL;
static int xml_schema_max = 0;
void
xml_log(int priority, const char *fmt, ...)
G_GNUC_PRINTF(2, 3);
void
xml_log(int priority, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
qb_log_from_external_source_va(__FUNCTION__, __FILE__, fmt, priority,
__LINE__, 0, ap);
va_end(ap);
}
static int
xml_latest_schema_index(void)
{
return xml_schema_max - 4;
}
static int
xml_minimum_schema_index(void)
{
static int best = 0;
if (best == 0) {
int lpc = 0;
float target = 0.0;
best = xml_latest_schema_index();
target = floor(known_schemas[best].version);
for (lpc = best; lpc > 0; lpc--) {
if (known_schemas[lpc].version < target) {
return best;
} else {
best = lpc;
}
}
best = xml_latest_schema_index();
}
return best;
}
const char *
xml_latest_schema(void)
{
return get_schema_name(xml_latest_schema_index());
}
static const char *
get_schema_root(void)
{
static const char *base = NULL;
if (base == NULL) {
base = getenv("PCMK_schema_directory");
}
if (base == NULL || strlen(base) == 0) {
base = CRM_DTD_DIRECTORY;
}
return base;
}
static char *
get_schema_path(const char *name, const char *file)
{
const char *base = get_schema_root();
if (file) {
return crm_strdup_printf("%s/%s", base, file);
}
return crm_strdup_printf("%s/%s.rng", base, name);
}
static int
schema_filter(const struct dirent *a)
{
int rc = 0;
float version = 0;
if (strstr(a->d_name, "pacemaker-") != a->d_name) {
/* crm_trace("%s - wrong prefix", a->d_name); */
} else if (!crm_ends_with(a->d_name, ".rng")) {
/* crm_trace("%s - wrong suffix", a->d_name); */
} else if (sscanf(a->d_name, "pacemaker-%f.rng", &version) == 0) {
/* crm_trace("%s - wrong format", a->d_name); */
} else if (strcmp(a->d_name, "pacemaker-1.1.rng") == 0) {
/* "-1.1" was used for what later became "-next" */
/* crm_trace("%s - hack", a->d_name); */
} else {
/* crm_debug("%s - candidate", a->d_name); */
rc = 1;
}
return rc;
}
static int
schema_sort(const struct dirent **a, const struct dirent **b)
{
int rc = 0;
float a_version = 0.0;
float b_version = 0.0;
sscanf(a[0]->d_name, "pacemaker-%f.rng", &a_version);
sscanf(b[0]->d_name, "pacemaker-%f.rng", &b_version);
if (a_version > b_version) {
rc = 1;
} else if(a_version < b_version) {
rc = -1;
}
/* crm_trace("%s (%f) vs. %s (%f) : %d", a[0]->d_name, a_version, b[0]->d_name, b_version, rc); */
return rc;
}
static void
__xml_schema_add(int type, float version, const char *name,
const char *location, const char *transform,
int after_transform)
{
int last = xml_schema_max;
xml_schema_max++;
known_schemas = realloc_safe(known_schemas,
xml_schema_max * sizeof(struct schema_s));
CRM_ASSERT(known_schemas != NULL);
memset(known_schemas+last, 0, sizeof(struct schema_s));
known_schemas[last].type = type;
known_schemas[last].after_transform = after_transform;
if (version > 0.0) {
known_schemas[last].version = version;
known_schemas[last].name = crm_strdup_printf("pacemaker-%.1f", version);
known_schemas[last].location = crm_strdup_printf("%s.rng", known_schemas[last].name);
} else {
char dummy[1024];
CRM_ASSERT(name);
CRM_ASSERT(location);
sscanf(name, "%[^-]-%f", dummy, &version);
known_schemas[last].version = version;
known_schemas[last].name = strdup(name);
known_schemas[last].location = strdup(location);
}
if (transform) {
known_schemas[last].transform = strdup(transform);
}
if (after_transform == 0) {
after_transform = xml_schema_max; /* upgrade is a one-way */
}
known_schemas[last].after_transform = after_transform;
if (known_schemas[last].after_transform < 0) {
crm_debug("Added supported schema %d: %s (%s)",
last, known_schemas[last].name, known_schemas[last].location);
} else if (known_schemas[last].transform) {
crm_debug("Added supported schema %d: %s (%s upgrades to %d with %s)",
last, known_schemas[last].name, known_schemas[last].location,
known_schemas[last].after_transform,
known_schemas[last].transform);
} else {
crm_debug("Added supported schema %d: %s (%s upgrades to %d)",
last, known_schemas[last].name, known_schemas[last].location,
known_schemas[last].after_transform);
}
}
/*!
* \internal
* \brief Load pacemaker schemas into cache
*/
void
crm_schema_init(void)
{
int lpc, max;
const char *base = get_schema_root();
struct dirent **namelist = NULL;
max = scandir(base, &namelist, schema_filter, schema_sort);
__xml_schema_add(1, 0.0, "pacemaker-0.6", "crm.dtd", "upgrade06.xsl", 3);
__xml_schema_add(1, 0.0, "transitional-0.6", "crm-transitional.dtd",
"upgrade06.xsl", 3);
__xml_schema_add(2, 0.0, "pacemaker-0.7", "pacemaker-1.0.rng", NULL, 0);
if (max < 0) {
crm_notice("scandir(%s) failed: %s (%d)", base, strerror(errno), errno);
} else {
for (lpc = 0; lpc < max; lpc++) {
int next = 0;
float version = 0.0;
char *transform = NULL;
sscanf(namelist[lpc]->d_name, "pacemaker-%f.rng", &version);
if ((lpc + 1) < max) {
float next_version = 0.0;
sscanf(namelist[lpc+1]->d_name, "pacemaker-%f.rng",
&next_version);
if (floor(version) < floor(next_version)) {
struct stat s;
char *xslt = NULL;
transform = crm_strdup_printf("upgrade-%.1f.xsl", version);
xslt = get_schema_path(NULL, transform);
if (stat(xslt, &s) != 0) {
crm_err("Transform %s not found", xslt);
free(xslt);
__xml_schema_add(2, version, NULL, NULL, NULL, -1);
break;
} else {
free(xslt);
}
}
} else {
next = -1;
}
__xml_schema_add(2, version, NULL, NULL, transform, next);
free(namelist[lpc]);
free(transform);
}
}
/* 1.1 was the old name for -next */
__xml_schema_add(2, 0.0, "pacemaker-1.1", "pacemaker-next.rng", NULL, 0);
__xml_schema_add(2, 0.0, "pacemaker-next", "pacemaker-next.rng", NULL, -1);
__xml_schema_add(0, 0.0, "none", "N/A", NULL, -1);
free(namelist);
}
static gboolean
validate_with_dtd(xmlDocPtr doc, gboolean to_logs, const char *dtd_file)
{
gboolean valid = TRUE;
xmlDtdPtr dtd = NULL;
xmlValidCtxtPtr cvp = NULL;
CRM_CHECK(doc != NULL, return FALSE);
CRM_CHECK(dtd_file != NULL, return FALSE);
dtd = xmlParseDTD(NULL, (const xmlChar *)dtd_file);
if (dtd == NULL) {
crm_err("Could not locate/parse DTD: %s", dtd_file);
return TRUE;
}
cvp = xmlNewValidCtxt();
if (cvp) {
if (to_logs) {
cvp->userData = (void *)LOG_ERR;
cvp->error = (xmlValidityErrorFunc) xml_log;
cvp->warning = (xmlValidityWarningFunc) xml_log;
} else {
cvp->userData = (void *)stderr;
cvp->error = (xmlValidityErrorFunc) fprintf;
cvp->warning = (xmlValidityWarningFunc) fprintf;
}
if (!xmlValidateDtd(cvp, doc, dtd)) {
valid = FALSE;
}
xmlFreeValidCtxt(cvp);
} else {
crm_err("Internal error: No valid context");
}
xmlFreeDtd(dtd);
return valid;
}
#if 0
static void
relaxng_invalid_stderr(void *userData, xmlErrorPtr error)
{
/*
Structure xmlError
struct _xmlError {
int domain : What part of the library raised this er
int code : The error code, e.g. an xmlParserError
char * message : human-readable informative error messag
xmlErrorLevel level : how consequent is the error
char * file : the filename
int line : the line number if available
char * str1 : extra string information
char * str2 : extra string information
char * str3 : extra string information
int int1 : extra number information
int int2 : column number of the error or 0 if N/A
void * ctxt : the parser context if available
void * node : the node in the tree
}
*/
crm_err("Structured error: line=%d, level=%d %s", error->line, error->level, error->message);
}
#endif
static gboolean
validate_with_relaxng(xmlDocPtr doc, gboolean to_logs, const char *relaxng_file,
relaxng_ctx_cache_t **cached_ctx)
{
int rc = 0;
gboolean valid = TRUE;
relaxng_ctx_cache_t *ctx = NULL;
CRM_CHECK(doc != NULL, return FALSE);
CRM_CHECK(relaxng_file != NULL, return FALSE);
if (cached_ctx && *cached_ctx) {
ctx = *cached_ctx;
} else {
crm_info("Creating RNG parser context");
ctx = calloc(1, sizeof(relaxng_ctx_cache_t));
xmlLoadExtDtdDefaultValue = 1;
ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file);
CRM_CHECK(ctx->parser != NULL, goto cleanup);
if (to_logs) {
xmlRelaxNGSetParserErrors(ctx->parser,
(xmlRelaxNGValidityErrorFunc) xml_log,
(xmlRelaxNGValidityWarningFunc) xml_log,
GUINT_TO_POINTER(LOG_ERR));
} else {
xmlRelaxNGSetParserErrors(ctx->parser,
(xmlRelaxNGValidityErrorFunc) fprintf,
(xmlRelaxNGValidityWarningFunc) fprintf,
stderr);
}
ctx->rng = xmlRelaxNGParse(ctx->parser);
CRM_CHECK(ctx->rng != NULL,
crm_err("Could not find/parse %s", relaxng_file);
goto cleanup);
ctx->valid = xmlRelaxNGNewValidCtxt(ctx->rng);
CRM_CHECK(ctx->valid != NULL, goto cleanup);
if (to_logs) {
xmlRelaxNGSetValidErrors(ctx->valid,
(xmlRelaxNGValidityErrorFunc) xml_log,
(xmlRelaxNGValidityWarningFunc) xml_log,
GUINT_TO_POINTER(LOG_ERR));
} else {
xmlRelaxNGSetValidErrors(ctx->valid,
(xmlRelaxNGValidityErrorFunc) fprintf,
(xmlRelaxNGValidityWarningFunc) fprintf,
stderr);
}
}
/* xmlRelaxNGSetValidStructuredErrors( */
/* valid, relaxng_invalid_stderr, valid); */
xmlLineNumbersDefault(1);
rc = xmlRelaxNGValidateDoc(ctx->valid, doc);
if (rc > 0) {
valid = FALSE;
} else if (rc < 0) {
crm_err("Internal libxml error during validation");
}
cleanup:
if (cached_ctx) {
*cached_ctx = ctx;
} else {
if (ctx->parser != NULL) {
xmlRelaxNGFreeParserCtxt(ctx->parser);
}
if (ctx->valid != NULL) {
xmlRelaxNGFreeValidCtxt(ctx->valid);
}
if (ctx->rng != NULL) {
xmlRelaxNGFree(ctx->rng);
}
free(ctx);
}
return valid;
}
/*!
* \internal
* \brief Clean up global memory associated with XML schemas
*/
void
crm_schema_cleanup(void)
{
int lpc;
relaxng_ctx_cache_t *ctx = NULL;
for (lpc = 0; lpc < xml_schema_max; lpc++) {
switch (known_schemas[lpc].type) {
case 0:
/* None */
break;
case 1:
/* DTD - Not cached */
break;
case 2:
/* RNG - Cached */
ctx = (relaxng_ctx_cache_t *) known_schemas[lpc].cache;
if (ctx == NULL) {
break;
}
if (ctx->parser != NULL) {
xmlRelaxNGFreeParserCtxt(ctx->parser);
}
if (ctx->valid != NULL) {
xmlRelaxNGFreeValidCtxt(ctx->valid);
}
if (ctx->rng != NULL) {
xmlRelaxNGFree(ctx->rng);
}
free(ctx);
known_schemas[lpc].cache = NULL;
break;
default:
break;
}
free(known_schemas[lpc].name);
free(known_schemas[lpc].location);
free(known_schemas[lpc].transform);
}
free(known_schemas);
known_schemas = NULL;
}
static gboolean
validate_with(xmlNode *xml, int method, gboolean to_logs)
{
xmlDocPtr doc = NULL;
gboolean valid = FALSE;
int type = 0;
char *file = NULL;
if (method < 0) {
return FALSE;
}
type = known_schemas[method].type;
if(type == 0) {
return TRUE;
}
CRM_CHECK(xml != NULL, return FALSE);
doc = getDocPtr(xml);
file = get_schema_path(known_schemas[method].name,
known_schemas[method].location);
crm_trace("Validating with: %s (type=%d)", crm_str(file), type);
switch (type) {
case 1:
valid = validate_with_dtd(doc, to_logs, file);
break;
case 2:
valid =
validate_with_relaxng(doc, to_logs, file,
(relaxng_ctx_cache_t **) & (known_schemas[method].cache));
break;
default:
crm_err("Unknown validator type: %d", type);
break;
}
free(file);
return valid;
}
static void
dump_file(const char *filename)
{
FILE *fp = NULL;
int ch, line = 0;
CRM_CHECK(filename != NULL, return);
fp = fopen(filename, "r");
- CRM_CHECK(fp != NULL, return);
+ if (fp == NULL) {
+ crm_perror(LOG_ERR, "Could not open %s for reading", filename);
+ return;
+ }
fprintf(stderr, "%4d ", ++line);
do {
ch = getc(fp);
if (ch == EOF) {
putc('\n', stderr);
break;
} else if (ch == '\n') {
fprintf(stderr, "\n%4d ", ++line);
} else {
putc(ch, stderr);
}
} while (1);
fclose(fp);
}
gboolean
validate_xml_verbose(xmlNode *xml_blob)
{
int fd = 0;
xmlDoc *doc = NULL;
xmlNode *xml = NULL;
gboolean rc = FALSE;
char *filename = strdup(CRM_STATE_DIR "/cib-invalid.XXXXXX");
CRM_CHECK(filename != NULL, return FALSE);
umask(S_IWGRP | S_IWOTH | S_IROTH);
fd = mkstemp(filename);
write_xml_fd(xml_blob, filename, fd, FALSE);
dump_file(filename);
doc = xmlParseFile(filename);
xml = xmlDocGetRootElement(doc);
rc = validate_xml(xml, NULL, FALSE);
free_xml(xml);
unlink(filename);
free(filename);
return rc;
}
gboolean
validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs)
{
int version = 0;
if (validation == NULL) {
validation = crm_element_value(xml_blob, XML_ATTR_VALIDATION);
}
if (validation == NULL) {
int lpc = 0;
bool valid = FALSE;
validation = crm_element_value(xml_blob, "ignore-dtd");
if (crm_is_true(validation)) {
/* Legacy compatibilty */
crm_xml_add(xml_blob, XML_ATTR_VALIDATION, "none");
return TRUE;
}
/* Work it out */
for (lpc = 0; lpc < xml_schema_max; lpc++) {
if (validate_with(xml_blob, lpc, FALSE)) {
valid = TRUE;
crm_xml_add(xml_blob, XML_ATTR_VALIDATION,
known_schemas[lpc].name);
crm_info("XML validated against %s", known_schemas[lpc].name);
if(known_schemas[lpc].after_transform == 0) {
break;
}
}
}
return valid;
}
version = get_schema_version(validation);
if (strcmp(validation, "none") == 0) {
return TRUE;
} else if (version < xml_schema_max) {
return validate_with(xml_blob, version, to_logs);
}
crm_err("Unknown validator: %s", validation);
return FALSE;
}
#if HAVE_LIBXSLT
static xmlNode *
apply_transformation(xmlNode *xml, const char *transform)
{
char *xform = NULL;
xmlNode *out = NULL;
xmlDocPtr res = NULL;
xmlDocPtr doc = NULL;
xsltStylesheet *xslt = NULL;
CRM_CHECK(xml != NULL, return FALSE);
doc = getDocPtr(xml);
xform = get_schema_path(NULL, transform);
xmlLoadExtDtdDefaultValue = 1;
xmlSubstituteEntitiesDefault(1);
xslt = xsltParseStylesheetFile((const xmlChar *)xform);
CRM_CHECK(xslt != NULL, goto cleanup);
res = xsltApplyStylesheet(xslt, doc, NULL);
CRM_CHECK(res != NULL, goto cleanup);
out = xmlDocGetRootElement(res);
cleanup:
if (xslt) {
xsltFreeStylesheet(xslt);
}
free(xform);
return out;
}
#endif
const char *
get_schema_name(int version)
{
if (version < 0 || version >= xml_schema_max) {
return "unknown";
}
return known_schemas[version].name;
}
int
get_schema_version(const char *name)
{
int lpc = 0;
if (name == NULL) {
name = "none";
}
for (; lpc < xml_schema_max; lpc++) {
if (safe_str_eq(name, known_schemas[lpc].name)) {
return lpc;
}
}
return -1;
}
/* set which validation to use */
int
update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform,
gboolean to_logs)
{
xmlNode *xml = NULL;
char *value = NULL;
int max_stable_schemas = xml_latest_schema_index();
int lpc = 0, match = -1, rc = pcmk_ok;
int next = -1; /* -1 denotes "inactive" value */
CRM_CHECK(best != NULL, return -EINVAL);
*best = 0;
CRM_CHECK(xml_blob != NULL, return -EINVAL);
CRM_CHECK(*xml_blob != NULL, return -EINVAL);
xml = *xml_blob;
value = crm_element_value_copy(xml, XML_ATTR_VALIDATION);
if (value != NULL) {
match = get_schema_version(value);
lpc = match;
if (lpc >= 0 && transform == FALSE) {
*best = lpc++;
} else if (lpc < 0) {
crm_debug("Unknown validation type");
lpc = 0;
}
}
if (match >= max_stable_schemas) {
/* nothing to do */
free(value);
*best = match;
return pcmk_ok;
}
while (lpc <= max_stable_schemas) {
crm_debug("Testing '%s' validation (%d of %d)",
known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>",
lpc, max_stable_schemas);
if (validate_with(xml, lpc, to_logs) == FALSE) {
if (next != -1) {
crm_info("Configuration not valid for schema: %s",
known_schemas[lpc].name);
next = -1;
} else {
crm_trace("%s validation failed",
known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>");
}
if (*best) {
/* we've satisfied the validation, no need to check further */
break;
}
rc = -pcmk_err_schema_validation;
} else {
if (next != -1) {
crm_debug("Configuration valid for schema: %s",
known_schemas[next].name);
next = -1;
}
rc = pcmk_ok;
}
if (rc == pcmk_ok) {
*best = lpc;
}
if (rc == pcmk_ok && transform) {
xmlNode *upgrade = NULL;
next = known_schemas[lpc].after_transform;
if (next <= lpc) {
/* There is no next version, or next would regress */
crm_trace("Stopping at %s", known_schemas[lpc].name);
break;
} else if (max > 0 && (lpc == max || next > max)) {
crm_trace("Upgrade limit reached at %s (lpc=%d, next=%d, max=%d)",
known_schemas[lpc].name, lpc, next, max);
break;
} else if (known_schemas[lpc].transform == NULL) {
crm_debug("%s-style configuration is also valid for %s",
known_schemas[lpc].name, known_schemas[next].name);
lpc = next;
} else {
crm_debug("Upgrading %s-style configuration to %s with %s",
known_schemas[lpc].name, known_schemas[next].name,
known_schemas[lpc].transform ? known_schemas[lpc].transform : "no-op");
#if HAVE_LIBXSLT
upgrade = apply_transformation(xml, known_schemas[lpc].transform);
#endif
if (upgrade == NULL) {
crm_err("Transformation %s failed",
known_schemas[lpc].transform);
rc = -pcmk_err_transform_failed;
} else if (validate_with(upgrade, next, to_logs)) {
crm_info("Transformation %s successful",
known_schemas[lpc].transform);
lpc = next;
*best = next;
free_xml(xml);
xml = upgrade;
rc = pcmk_ok;
} else {
crm_err("Transformation %s did not produce a valid configuration",
known_schemas[lpc].transform);
crm_log_xml_info(upgrade, "transform:bad");
free_xml(upgrade);
rc = -pcmk_err_schema_validation;
}
next = -1;
}
}
if (transform == FALSE || rc != pcmk_ok) {
/* we need some progress! */
lpc++;
}
}
if (*best > match && *best) {
crm_info("%s the configuration from %s to %s",
transform?"Transformed":"Upgraded",
value ? value : "<none>", known_schemas[*best].name);
crm_xml_add(xml, XML_ATTR_VALIDATION, known_schemas[*best].name);
}
*xml_blob = xml;
free(value);
return rc;
}
gboolean
cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
{
gboolean rc = TRUE;
const char *value = crm_element_value(*xml, XML_ATTR_VALIDATION);
char *const orig_value = strdup(value == NULL ? "(none)" : value);
int version = get_schema_version(value);
int orig_version = version;
int min_version = xml_minimum_schema_index();
if (version < min_version) {
xmlNode *converted = NULL;
converted = copy_xml(*xml);
update_validation(&converted, &version, 0, TRUE, to_logs);
value = crm_element_value(converted, XML_ATTR_VALIDATION);
if (version < min_version) {
if (version < orig_version || orig_version == -1) {
if (to_logs) {
crm_config_err("Your current configuration %s could not"
" validate with any schema in range [%s, %s],"
" cannot upgrade to %s.",
orig_value,
get_schema_name(orig_version),
xml_latest_schema(),
get_schema_name(min_version));
} else {
fprintf(stderr, "Your current configuration %s could not"
" validate with any schema in range [%s, %s],"
" cannot upgrade to %s.\n",
orig_value,
get_schema_name(orig_version),
xml_latest_schema(),
get_schema_name(min_version));
}
} else if (to_logs) {
crm_config_err("Your current configuration could only be upgraded to %s... "
"the minimum requirement is %s.", crm_str(value),
get_schema_name(min_version));
} else {
fprintf(stderr, "Your current configuration could only be upgraded to %s... "
"the minimum requirement is %s.\n",
crm_str(value), get_schema_name(min_version));
}
free_xml(converted);
converted = NULL;
rc = FALSE;
} else {
free_xml(*xml);
*xml = converted;
if (version < xml_latest_schema_index()) {
crm_config_warn("Your configuration was internally updated to %s... "
"which is acceptable but not the most recent",
get_schema_name(version));
} else if (to_logs) {
crm_info("Your configuration was internally updated to the latest version (%s)",
get_schema_name(version));
}
}
} else if (version >= get_schema_version("none")) {
if (to_logs) {
crm_config_warn("Configuration validation is currently disabled."
" It is highly encouraged and prevents many common cluster issues.");
} else {
fprintf(stderr, "Configuration validation is currently disabled."
" It is highly encouraged and prevents many common cluster issues.\n");
}
}
if (best_version) {
*best_version = version;
}
free(orig_value);
return rc;
}
diff --git a/lib/pengine/container.c b/lib/pengine/container.c
index b93d98f6e9..127b144e4f 100644
--- a/lib/pengine/container.c
+++ b/lib/pengine/container.c
@@ -1,978 +1,987 @@
/*
* 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/pengine/rules.h>
#include <crm/pengine/status.h>
#include <crm/pengine/internal.h>
#include <unpack.h>
#include <crm/msg_xml.h>
#define VARIANT_CONTAINER 1
#include "./variant.h"
void tuple_free(container_grouping_t *tuple);
static char *
next_ip(const char *last_ip)
{
unsigned int oct1 = 0;
unsigned int oct2 = 0;
unsigned int oct3 = 0;
unsigned int oct4 = 0;
int rc = sscanf(last_ip, "%u.%u.%u.%u", &oct1, &oct2, &oct3, &oct4);
if (rc != 4) {
/*@ TODO check for IPv6 */
return NULL;
} else if (oct3 > 253) {
return NULL;
} else if (oct4 > 253) {
++oct3;
oct4 = 1;
} else {
++oct4;
}
return crm_strdup_printf("%u.%u.%u.%u", oct1, oct2, oct3, oct4);
}
static int
allocate_ip(container_variant_data_t *data, container_grouping_t *tuple, char *buffer, int max)
{
if(data->ip_range_start == NULL) {
return 0;
} else if(data->ip_last) {
tuple->ipaddr = next_ip(data->ip_last);
} else {
tuple->ipaddr = strdup(data->ip_range_start);
}
data->ip_last = tuple->ipaddr;
#if 0
return snprintf(buffer, max, " --add-host=%s-%d:%s --link %s-docker-%d:%s-link-%d",
data->prefix, tuple->offset, tuple->ipaddr,
data->prefix, tuple->offset, data->prefix, tuple->offset);
#else
return snprintf(buffer, max, " --add-host=%s-%d:%s",
data->prefix, tuple->offset, tuple->ipaddr);
#endif
}
static xmlNode *
create_resource(const char *name, const char *provider, const char *kind)
{
xmlNode *rsc = create_xml_node(NULL, XML_CIB_TAG_RESOURCE);
crm_xml_add(rsc, XML_ATTR_ID, name);
crm_xml_add(rsc, XML_AGENT_ATTR_CLASS, "ocf");
crm_xml_add(rsc, XML_AGENT_ATTR_PROVIDER, provider);
crm_xml_add(rsc, XML_ATTR_TYPE, kind);
return rsc;
}
static void
create_nvp(xmlNode *parent, const char *name, const char *value)
{
xmlNode *xml_nvp = create_xml_node(parent, XML_CIB_TAG_NVPAIR);
crm_xml_set_id(xml_nvp, "%s-%s", ID(parent), name);
crm_xml_add(xml_nvp, XML_NVPAIR_ATTR_NAME, name);
crm_xml_add(xml_nvp, XML_NVPAIR_ATTR_VALUE, value);
}
static void
create_op(xmlNode *parent, const char *prefix, const char *task, const char *interval)
{
xmlNode *xml_op = create_xml_node(parent, "op");
crm_xml_set_id(xml_op, "%s-%s-%s", prefix, task, interval);
crm_xml_add(xml_op, XML_LRM_ATTR_INTERVAL, interval);
crm_xml_add(xml_op, "name", task);
}
/*!
* \internal
* \brief Check whether cluster can manage resource inside container
*
* \param[in] data Container variant data
*
* \return TRUE if networking configuration is acceptable, FALSE otherwise
*
* \note The resource is manageable if an IP range or control port has been
* specified. If a control port is used without an IP range, replicas per
* host must be 1.
*/
static bool
valid_network(container_variant_data_t *data)
{
if(data->ip_range_start) {
return TRUE;
}
if(data->control_port) {
if(data->replicas_per_host > 1) {
pe_err("Specifying the 'control-port' for %s requires 'replicas-per-host=1'", data->prefix);
data->replicas_per_host = 1;
/* @TODO to be sure: clear_bit(rsc->flags, pe_rsc_unique); */
}
return TRUE;
}
return FALSE;
}
static bool
create_ip_resource(
resource_t *parent, container_variant_data_t *data, container_grouping_t *tuple,
pe_working_set_t * data_set)
{
if(data->ip_range_start) {
char *id = NULL;
xmlNode *xml_ip = NULL;
xmlNode *xml_obj = NULL;
id = crm_strdup_printf("%s-ip-%s", data->prefix, tuple->ipaddr);
crm_xml_sanitize_id(id);
xml_ip = create_resource(id, "heartbeat", "IPaddr2");
free(id);
xml_obj = create_xml_node(xml_ip, XML_TAG_ATTR_SETS);
crm_xml_set_id(xml_obj, "%s-attributes-%d", data->prefix, tuple->offset);
create_nvp(xml_obj, "ip", tuple->ipaddr);
if(data->host_network) {
create_nvp(xml_obj, "nic", data->host_network);
}
if(data->host_netmask) {
create_nvp(xml_obj, "cidr_netmask", data->host_netmask);
} else {
create_nvp(xml_obj, "cidr_netmask", "32");
}
xml_obj = create_xml_node(xml_ip, "operations");
create_op(xml_obj, ID(xml_ip), "monitor", "60s");
// TODO: Other ops? Timeouts and intervals from underlying resource?
if (common_unpack(xml_ip, &tuple->ip, NULL, data_set) == false) {
return FALSE;
}
parent->children = g_list_append(parent->children, tuple->ip);
}
return TRUE;
}
static bool
create_docker_resource(
resource_t *parent, container_variant_data_t *data, container_grouping_t *tuple,
pe_working_set_t * data_set)
{
int offset = 0, max = 4096;
char *buffer = calloc(1, max+1);
int doffset = 0, dmax = 1024;
char *dbuffer = calloc(1, dmax+1);
char *id = NULL;
xmlNode *xml_docker = NULL;
xmlNode *xml_obj = NULL;
id = crm_strdup_printf("%s-docker-%d", data->prefix, tuple->offset);
crm_xml_sanitize_id(id);
xml_docker = create_resource(id, "heartbeat", "docker");
free(id);
xml_obj = create_xml_node(xml_docker, XML_TAG_ATTR_SETS);
crm_xml_set_id(xml_obj, "%s-attributes-%d", data->prefix, tuple->offset);
create_nvp(xml_obj, "image", data->image);
create_nvp(xml_obj, "allow_pull", "true");
create_nvp(xml_obj, "force_kill", "false");
create_nvp(xml_obj, "reuse", "false");
- offset += snprintf(buffer+offset, max-offset, "-h %s-%d --restart=no ",
- data->prefix, tuple->offset);
+ offset += snprintf(buffer+offset, max-offset, " --restart=no");
+
+ /* Set a container hostname only if we have an IP to map it to.
+ * The user can set -h or --uts=host themselves if they want a nicer
+ * name for logs, but this makes applications happy who need their
+ * hostname to match the IP they bind to.
+ */
+ if (data->ip_range_start != NULL) {
+ offset += snprintf(buffer+offset, max-offset, " -h %s-%d",
+ data->prefix, tuple->offset);
+ }
if(data->docker_network) {
// offset += snprintf(buffer+offset, max-offset, " --link-local-ip=%s", tuple->ipaddr);
offset += snprintf(buffer+offset, max-offset, " --net=%s", data->docker_network);
}
if(data->control_port) {
offset += snprintf(buffer+offset, max-offset, " -e PCMK_remote_port=%s", data->control_port);
} else {
offset += snprintf(buffer+offset, max-offset, " -e PCMK_remote_port=%d", DEFAULT_REMOTE_PORT);
}
for(GListPtr pIter = data->mounts; pIter != NULL; pIter = pIter->next) {
container_mount_t *mount = pIter->data;
if(mount->flags) {
char *source = crm_strdup_printf(
"%s/%s-%d", mount->source, data->prefix, tuple->offset);
if(doffset > 0) {
doffset += snprintf(dbuffer+doffset, dmax-doffset, ",");
}
doffset += snprintf(dbuffer+doffset, dmax-doffset, "%s", source);
offset += snprintf(buffer+offset, max-offset, " -v %s:%s", source, mount->target);
free(source);
} else {
offset += snprintf(buffer+offset, max-offset, " -v %s:%s", mount->source, mount->target);
}
if(mount->options) {
offset += snprintf(buffer+offset, max-offset, ":%s", mount->options);
}
}
for(GListPtr pIter = data->ports; pIter != NULL; pIter = pIter->next) {
container_port_t *port = pIter->data;
if(tuple->ipaddr) {
offset += snprintf(buffer+offset, max-offset, " -p %s:%s:%s",
tuple->ipaddr, port->source, port->target);
} else {
offset += snprintf(buffer+offset, max-offset, " -p %s:%s", port->source, port->target);
}
}
if(data->docker_run_options) {
offset += snprintf(buffer+offset, max-offset, " %s", data->docker_run_options);
}
if(data->docker_host_options) {
offset += snprintf(buffer+offset, max-offset, " %s", data->docker_host_options);
}
create_nvp(xml_obj, "run_opts", buffer);
free(buffer);
create_nvp(xml_obj, "mount_points", dbuffer);
free(dbuffer);
if(tuple->child) {
if(data->docker_run_command) {
create_nvp(xml_obj, "run_cmd", data->docker_run_command);
} else {
create_nvp(xml_obj, "run_cmd", SBIN_DIR"/pacemaker_remoted");
}
/* TODO: Allow users to specify their own?
*
* We just want to know if the container is alive, we'll
* monitor the child independently
*/
create_nvp(xml_obj, "monitor_cmd", "/bin/true");
/* } else if(child && data->untrusted) {
* Support this use-case?
*
* The ability to have resources started/stopped by us, but
* unable to set attributes, etc.
*
* Arguably better to control API access this with ACLs like
* "normal" remote nodes
*
* create_nvp(xml_obj, "run_cmd", "/usr/libexec/pacemaker/lrmd");
* create_nvp(xml_obj, "monitor_cmd", "/usr/libexec/pacemaker/lrmd_internal_ctl -c poke");
*/
} else {
if(data->docker_run_command) {
create_nvp(xml_obj, "run_cmd", data->docker_run_command);
}
/* TODO: Allow users to specify their own?
*
* We don't know what's in the container, so we just want
* to know if it is alive
*/
create_nvp(xml_obj, "monitor_cmd", "/bin/true");
}
xml_obj = create_xml_node(xml_docker, "operations");
create_op(xml_obj, ID(xml_docker), "monitor", "60s");
// TODO: Other ops? Timeouts and intervals from underlying resource?
if (common_unpack(xml_docker, &tuple->docker, NULL, data_set) == FALSE) {
return FALSE;
}
parent->children = g_list_append(parent->children, tuple->docker);
tuple->docker->parent = parent;
return TRUE;
}
static bool
create_remote_resource(
resource_t *parent, container_variant_data_t *data, container_grouping_t *tuple,
pe_working_set_t * data_set)
{
if (tuple->child && valid_network(data)) {
node_t *node = NULL;
xmlNode *xml_obj = NULL;
xmlNode *xml_remote = NULL;
char *nodeid = crm_strdup_printf("%s-%d", data->prefix, tuple->offset);
char *id = NULL;
if (remote_id_conflict(nodeid, data_set)) {
// The biggest hammer we have
id = crm_strdup_printf("pcmk-internal-%s-remote-%d", tuple->child->id, tuple->offset);
CRM_ASSERT(remote_id_conflict(id, data_set) == FALSE);
} else {
id = strdup(nodeid);
}
xml_remote = create_resource(id, "pacemaker", "remote");
free(id);
xml_obj = create_xml_node(xml_remote, "operations");
create_op(xml_obj, ID(xml_remote), "monitor", "60s");
xml_obj = create_xml_node(xml_remote, XML_TAG_ATTR_SETS);
crm_xml_set_id(xml_obj, "%s-attributes-%d", data->prefix, tuple->offset);
if(tuple->ipaddr) {
create_nvp(xml_obj, "addr", tuple->ipaddr);
} else {
create_nvp(xml_obj, "addr", "localhost");
}
if(data->control_port) {
create_nvp(xml_obj, "port", data->control_port);
} else {
create_nvp(xml_obj, "port", crm_itoa(DEFAULT_REMOTE_PORT));
}
xml_obj = create_xml_node(xml_remote, XML_TAG_META_SETS);
crm_xml_set_id(xml_obj, "%s-meta-%d", data->prefix, tuple->offset);
create_nvp(xml_obj, XML_OP_ATTR_ALLOW_MIGRATE, "false");
// Sets up node->details->remote_rsc->container == tuple->docker
create_nvp(xml_obj, XML_RSC_ATTR_CONTAINER, tuple->docker->id);
// TODO: Do this generically, eg with rsc->flags
// create_nvp(xml_obj, XML_RSC_ATTR_INTERNAL_RSC, "true"); // Suppress printing
// tuple->docker->fillers = g_list_append(tuple->docker->fillers, child);
// -INFINITY prevents anyone else from running here
node = pe_create_node(strdup(nodeid), nodeid, "remote", "-INFINITY",
data_set);
tuple->node = node_copy(node);
tuple->node->weight = 500;
nodeid = NULL;
id = NULL;
if (common_unpack(xml_remote, &tuple->remote, NULL, data_set) == FALSE) {
return FALSE;
}
tuple->node->details->remote_rsc = tuple->remote;
parent->children = g_list_append(parent->children, tuple->remote);
}
return TRUE;
}
static bool
create_container(
resource_t *parent, container_variant_data_t *data, container_grouping_t *tuple,
pe_working_set_t * data_set)
{
if(create_docker_resource(parent, data, tuple, data_set) == FALSE) {
return TRUE;
}
if(create_ip_resource(parent, data, tuple, data_set) == FALSE) {
return TRUE;
}
if(create_remote_resource(parent, data, tuple, data_set) == FALSE) {
return TRUE;
}
if(tuple->child && tuple->ipaddr) {
add_hash_param(tuple->child->meta, "external-ip", tuple->ipaddr);
}
return FALSE;
}
static void mount_free(container_mount_t *mount)
{
free(mount->source);
free(mount->target);
free(mount->options);
free(mount);
}
static void port_free(container_port_t *port)
{
free(port->source);
free(port->target);
free(port);
}
gboolean
container_unpack(resource_t * rsc, pe_working_set_t * data_set)
{
const char *value = NULL;
xmlNode *xml_obj = NULL;
xmlNode *xml_resource = NULL;
container_variant_data_t *container_data = NULL;
CRM_ASSERT(rsc != NULL);
pe_rsc_trace(rsc, "Processing resource %s...", rsc->id);
container_data = calloc(1, sizeof(container_variant_data_t));
rsc->variant_opaque = container_data;
container_data->prefix = strdup(rsc->id);
xml_obj = first_named_child(rsc->xml, "docker");
if(xml_obj == NULL) {
return FALSE;
}
value = crm_element_value(xml_obj, "masters");
container_data->masters = crm_parse_int(value, "0");
if (container_data->masters < 0) {
pe_err("'masters' for %s must be nonnegative integer, using 0",
rsc->id);
container_data->masters = 0;
}
value = crm_element_value(xml_obj, "replicas");
if ((value == NULL) && (container_data->masters > 0)) {
container_data->replicas = container_data->masters;
} else {
container_data->replicas = crm_parse_int(value, "1");
}
if (container_data->replicas < 1) {
pe_err("'replicas' for %s must be positive integer, using 1", rsc->id);
container_data->replicas = 1;
}
/*
* Communication between containers on the same host via the
* floating IPs only works if docker is started with:
* --userland-proxy=false --ip-masq=false
*/
value = crm_element_value(xml_obj, "replicas-per-host");
container_data->replicas_per_host = crm_parse_int(value, "1");
if (container_data->replicas_per_host < 1) {
pe_err("'replicas-per-host' for %s must be positive integer, using 1",
rsc->id);
container_data->replicas_per_host = 1;
}
if (container_data->replicas_per_host == 1) {
clear_bit(rsc->flags, pe_rsc_unique);
}
container_data->docker_run_command = crm_element_value_copy(xml_obj, "run-command");
container_data->docker_run_options = crm_element_value_copy(xml_obj, "options");
container_data->image = crm_element_value_copy(xml_obj, "image");
container_data->docker_network = crm_element_value_copy(xml_obj, "network");
xml_obj = first_named_child(rsc->xml, "network");
if(xml_obj) {
container_data->ip_range_start = crm_element_value_copy(xml_obj, "ip-range-start");
container_data->host_netmask = crm_element_value_copy(xml_obj, "host-netmask");
container_data->host_network = crm_element_value_copy(xml_obj, "host-interface");
container_data->control_port = crm_element_value_copy(xml_obj, "control-port");
for (xmlNode *xml_child = __xml_first_child_element(xml_obj); xml_child != NULL;
xml_child = __xml_next_element(xml_child)) {
container_port_t *port = calloc(1, sizeof(container_port_t));
port->source = crm_element_value_copy(xml_child, "port");
if(port->source == NULL) {
port->source = crm_element_value_copy(xml_child, "range");
} else {
port->target = crm_element_value_copy(xml_child, "internal-port");
}
if(port->source != NULL && strlen(port->source) > 0) {
if(port->target == NULL) {
port->target = strdup(port->source);
}
container_data->ports = g_list_append(container_data->ports, port);
} else {
pe_err("Invalid port directive %s", ID(xml_child));
port_free(port);
}
}
}
xml_obj = first_named_child(rsc->xml, "storage");
for (xmlNode *xml_child = __xml_first_child_element(xml_obj); xml_child != NULL;
xml_child = __xml_next_element(xml_child)) {
container_mount_t *mount = calloc(1, sizeof(container_mount_t));
mount->source = crm_element_value_copy(xml_child, "source-dir");
if(mount->source == NULL) {
mount->source = crm_element_value_copy(xml_child, "source-dir-root");
mount->flags = 1;
}
mount->target = crm_element_value_copy(xml_child, "target-dir");
mount->options = crm_element_value_copy(xml_child, "options");
if(mount->source && mount->target) {
container_data->mounts = g_list_append(container_data->mounts, mount);
} else {
pe_err("Invalid mount directive %s", ID(xml_child));
mount_free(mount);
}
}
xml_obj = first_named_child(rsc->xml, "primitive");
if (xml_obj && valid_network(container_data)) {
char *value = NULL;
xmlNode *xml_set = NULL;
if(container_data->masters > 0) {
xml_resource = create_xml_node(NULL, XML_CIB_TAG_MASTER);
} else {
xml_resource = create_xml_node(NULL, XML_CIB_TAG_INCARNATION);
}
crm_xml_set_id(xml_resource, "%s-%s", container_data->prefix, xml_resource->name);
xml_set = create_xml_node(xml_resource, XML_TAG_META_SETS);
crm_xml_set_id(xml_set, "%s-%s-meta", container_data->prefix, xml_resource->name);
create_nvp(xml_set, XML_RSC_ATTR_ORDERED, "true");
value = crm_itoa(container_data->replicas);
create_nvp(xml_set, XML_RSC_ATTR_INCARNATION_MAX, value);
free(value);
value = crm_itoa(container_data->replicas_per_host);
create_nvp(xml_set, XML_RSC_ATTR_INCARNATION_NODEMAX, value);
free(value);
if(container_data->replicas_per_host > 1) {
create_nvp(xml_set, XML_RSC_ATTR_UNIQUE, "true");
} else {
create_nvp(xml_set, XML_RSC_ATTR_UNIQUE, "false");
}
if(container_data->masters) {
value = crm_itoa(container_data->masters);
create_nvp(xml_set, XML_RSC_ATTR_MASTER_MAX, value);
free(value);
}
//crm_xml_add(xml_obj, XML_ATTR_ID, container_data->prefix);
add_node_copy(xml_resource, xml_obj);
} else if(xml_obj) {
pe_err("Cannot control %s inside %s without either ip-range-start or control-port",
rsc->id, ID(xml_obj));
return FALSE;
}
if(xml_resource) {
int lpc = 0;
GListPtr childIter = NULL;
resource_t *new_rsc = NULL;
container_mount_t *mount = NULL;
container_port_t *port = NULL;
int offset = 0, max = 1024;
char *buffer = NULL;
if (common_unpack(xml_resource, &new_rsc, rsc, data_set) == FALSE) {
pe_err("Failed unpacking resource %s", ID(rsc->xml));
if (new_rsc != NULL && new_rsc->fns != NULL) {
new_rsc->fns->free(new_rsc);
}
return FALSE;
}
container_data->child = new_rsc;
container_data->child->orig_xml = xml_obj; // Also the trigger for common_free()
// to free xml_resource as container_data->child->xml
mount = calloc(1, sizeof(container_mount_t));
mount->source = strdup(DEFAULT_REMOTE_KEY_LOCATION);
mount->target = strdup(DEFAULT_REMOTE_KEY_LOCATION);
mount->options = NULL;
mount->flags = 0;
container_data->mounts = g_list_append(container_data->mounts, mount);
mount = calloc(1, sizeof(container_mount_t));
mount->source = strdup(CRM_LOG_DIR "/bundles");
mount->target = strdup("/var/log");
mount->options = NULL;
mount->flags = 1;
container_data->mounts = g_list_append(container_data->mounts, mount);
port = calloc(1, sizeof(container_port_t));
if(container_data->control_port) {
port->source = strdup(container_data->control_port);
} else {
port->source = crm_itoa(DEFAULT_REMOTE_PORT);
}
port->target = strdup(port->source);
container_data->ports = g_list_append(container_data->ports, port);
buffer = calloc(1, max+1);
for(childIter = container_data->child->children; childIter != NULL; childIter = childIter->next) {
container_grouping_t *tuple = calloc(1, sizeof(container_grouping_t));
tuple->child = childIter->data;
tuple->offset = lpc++;
offset += allocate_ip(container_data, tuple, buffer+offset, max-offset);
container_data->tuples = g_list_append(container_data->tuples, tuple);
}
container_data->docker_host_options = buffer;
} else {
// Just a naked container, no pacemaker-remote
int offset = 0, max = 1024;
char *buffer = calloc(1, max+1);
for(int lpc = 0; lpc < container_data->replicas; lpc++) {
container_grouping_t *tuple = calloc(1, sizeof(container_grouping_t));
tuple->offset = lpc;
offset += allocate_ip(container_data, tuple, buffer+offset, max-offset);
container_data->tuples = g_list_append(container_data->tuples, tuple);
}
container_data->docker_host_options = buffer;
}
for (GListPtr gIter = container_data->tuples; gIter != NULL; gIter = gIter->next) {
container_grouping_t *tuple = (container_grouping_t *)gIter->data;
// TODO: Remove from list if create_container() returns TRUE
create_container(rsc, container_data, tuple, data_set);
}
if(container_data->child) {
rsc->children = g_list_append(rsc->children, container_data->child);
}
return TRUE;
}
static int
tuple_rsc_active(resource_t *rsc, gboolean all)
{
if (rsc) {
gboolean child_active = rsc->fns->active(rsc, all);
if (child_active && !all) {
return TRUE;
} else if (!child_active && all) {
return FALSE;
}
}
return -1;
}
gboolean
container_active(resource_t * rsc, gboolean all)
{
container_variant_data_t *container_data = NULL;
GListPtr iter = NULL;
get_container_variant_data(container_data, rsc);
for (iter = container_data->tuples; iter != NULL; iter = iter->next) {
container_grouping_t *tuple = (container_grouping_t *)(iter->data);
int rsc_active;
rsc_active = tuple_rsc_active(tuple->ip, all);
if (rsc_active >= 0) {
return (gboolean) rsc_active;
}
rsc_active = tuple_rsc_active(tuple->child, all);
if (rsc_active >= 0) {
return (gboolean) rsc_active;
}
rsc_active = tuple_rsc_active(tuple->docker, all);
if (rsc_active >= 0) {
return (gboolean) rsc_active;
}
rsc_active = tuple_rsc_active(tuple->remote, all);
if (rsc_active >= 0) {
return (gboolean) rsc_active;
}
}
/* If "all" is TRUE, we've already checked that no resources were inactive,
* so return TRUE; if "all" is FALSE, we didn't find any active resources,
* so return FALSE.
*/
return all;
}
resource_t *
find_container_child(const char *stem, resource_t * rsc, node_t *node)
{
container_variant_data_t *container_data = NULL;
resource_t *parent = uber_parent(rsc);
CRM_ASSERT(parent->parent);
parent = parent->parent;
get_container_variant_data(container_data, parent);
if (is_not_set(rsc->flags, pe_rsc_unique)) {
for (GListPtr gIter = container_data->tuples; gIter != NULL; gIter = gIter->next) {
container_grouping_t *tuple = (container_grouping_t *)gIter->data;
CRM_ASSERT(tuple);
if(tuple->node->details == node->details) {
rsc = tuple->child;
break;
}
}
}
if (rsc && safe_str_neq(stem, rsc->id)) {
free(rsc->clone_name);
rsc->clone_name = strdup(stem);
}
return rsc;
}
static void
print_rsc_in_list(resource_t *rsc, const char *pre_text, long options,
void *print_data)
{
if (rsc != NULL) {
if (options & pe_print_html) {
status_print("<li>");
}
rsc->fns->print(rsc, pre_text, options, print_data);
if (options & pe_print_html) {
status_print("</li>\n");
}
}
}
static void
container_print_xml(resource_t * rsc, const char *pre_text, long options, void *print_data)
{
container_variant_data_t *container_data = NULL;
char *child_text = NULL;
CRM_CHECK(rsc != NULL, return);
if (pre_text == NULL) {
pre_text = "";
}
child_text = crm_concat(pre_text, " ", ' ');
get_container_variant_data(container_data, rsc);
status_print("%s<bundle ", pre_text);
status_print("id=\"%s\" ", rsc->id);
status_print("type=\"docker\" ");
status_print("image=\"%s\" ", container_data->image);
status_print("unique=\"%s\" ", is_set(rsc->flags, pe_rsc_unique)? "true" : "false");
status_print("managed=\"%s\" ", is_set(rsc->flags, pe_rsc_managed) ? "true" : "false");
status_print("failed=\"%s\" ", is_set(rsc->flags, pe_rsc_failed) ? "true" : "false");
status_print(">\n");
for (GListPtr gIter = container_data->tuples; gIter != NULL; gIter = gIter->next) {
container_grouping_t *tuple = (container_grouping_t *)gIter->data;
CRM_ASSERT(tuple);
status_print("%s <replica id=\"%d\">\n", pre_text, tuple->offset);
print_rsc_in_list(tuple->ip, child_text, options, print_data);
print_rsc_in_list(tuple->child, child_text, options, print_data);
print_rsc_in_list(tuple->docker, child_text, options, print_data);
print_rsc_in_list(tuple->remote, child_text, options, print_data);
status_print("%s </replica>\n", pre_text);
}
status_print("%s</bundle>\n", pre_text);
free(child_text);
}
static void
tuple_print(container_grouping_t * tuple, const char *pre_text, long options, void *print_data)
{
node_t *node = NULL;
resource_t *rsc = tuple->child;
int offset = 0;
char buffer[LINE_MAX];
if(rsc == NULL) {
rsc = tuple->docker;
}
if(tuple->remote) {
offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", rsc_printable_id(tuple->remote));
} else {
offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", rsc_printable_id(tuple->docker));
}
if(tuple->ipaddr) {
offset += snprintf(buffer + offset, LINE_MAX - offset, " (%s)", tuple->ipaddr);
}
if(tuple->remote && tuple->remote->running_on != NULL) {
node = tuple->remote->running_on->data;
} else if (tuple->remote == NULL && rsc->running_on != NULL) {
node = rsc->running_on->data;
}
common_print(rsc, pre_text, buffer, node, options, print_data);
}
void
container_print(resource_t * rsc, const char *pre_text, long options, void *print_data)
{
container_variant_data_t *container_data = NULL;
char *child_text = NULL;
CRM_CHECK(rsc != NULL, return);
if (options & pe_print_xml) {
container_print_xml(rsc, pre_text, options, print_data);
return;
}
get_container_variant_data(container_data, rsc);
if (pre_text == NULL) {
pre_text = " ";
}
status_print("%sDocker container%s: %s [%s]%s%s\n",
pre_text, container_data->replicas>1?" set":"", rsc->id, container_data->image,
is_set(rsc->flags, pe_rsc_unique) ? " (unique)" : "",
is_set(rsc->flags, pe_rsc_managed) ? "" : " (unmanaged)");
if (options & pe_print_html) {
status_print("<br />\n<ul>\n");
}
for (GListPtr gIter = container_data->tuples; gIter != NULL; gIter = gIter->next) {
container_grouping_t *tuple = (container_grouping_t *)gIter->data;
CRM_ASSERT(tuple);
if (options & pe_print_html) {
status_print("<li>");
}
if(is_set(options, pe_print_clone_details)) {
child_text = crm_strdup_printf(" %s", pre_text);
if(g_list_length(container_data->tuples) > 1) {
status_print(" %sReplica[%d]\n", pre_text, tuple->offset);
}
if (options & pe_print_html) {
status_print("<br />\n<ul>\n");
}
print_rsc_in_list(tuple->ip, child_text, options, print_data);
print_rsc_in_list(tuple->docker, child_text, options, print_data);
print_rsc_in_list(tuple->remote, child_text, options, print_data);
print_rsc_in_list(tuple->child, child_text, options, print_data);
if (options & pe_print_html) {
status_print("</ul>\n");
}
} else {
child_text = crm_strdup_printf("%s ", pre_text);
tuple_print(tuple, child_text, options, print_data);
}
free(child_text);
if (options & pe_print_html) {
status_print("</li>\n");
}
}
if (options & pe_print_html) {
status_print("</ul>\n");
}
}
void
tuple_free(container_grouping_t *tuple)
{
if(tuple == NULL) {
return;
}
// TODO: Free tuple->node ?
if(tuple->ip) {
tuple->ip->fns->free(tuple->ip);
tuple->ip = NULL;
}
if(tuple->child) {
tuple->child->fns->free(tuple->child);
tuple->child = NULL;
}
if(tuple->docker) {
tuple->docker->fns->free(tuple->docker);
tuple->docker = NULL;
}
if(tuple->remote) {
tuple->remote->fns->free(tuple->remote);
tuple->remote = NULL;
}
free(tuple->ipaddr);
free(tuple);
}
void
container_free(resource_t * rsc)
{
container_variant_data_t *container_data = NULL;
CRM_CHECK(rsc != NULL, return);
get_container_variant_data(container_data, rsc);
pe_rsc_trace(rsc, "Freeing %s", rsc->id);
free(container_data->prefix);
free(container_data->image);
free(container_data->control_port);
free(container_data->host_network);
free(container_data->host_netmask);
free(container_data->ip_range_start);
free(container_data->docker_network);
free(container_data->docker_run_options);
free(container_data->docker_run_command);
free(container_data->docker_host_options);
g_list_free_full(container_data->tuples, (GDestroyNotify)tuple_free);
g_list_free_full(container_data->mounts, (GDestroyNotify)mount_free);
g_list_free_full(container_data->ports, (GDestroyNotify)port_free);
common_free(rsc);
}
enum rsc_role_e
container_resource_state(const resource_t * rsc, gboolean current)
{
enum rsc_role_e container_role = RSC_ROLE_UNKNOWN;
return container_role;
}
diff --git a/valgrind-pcmk.suppressions b/valgrind-pcmk.suppressions
index 48eec9e2af..33d3b06043 100644
--- a/valgrind-pcmk.suppressions
+++ b/valgrind-pcmk.suppressions
@@ -1,341 +1,340 @@
# Valgrind suppressions for Pacemaker testing
{
Valgrind bug
Memcheck:Addr8
fun:__strspn_sse42
fun:crm_get_msec
}
{
Bash leak
Memcheck:Leak
fun:malloc
fun:xmalloc
fun:*
fun:yyparse
fun:parse_command
fun:read_command
fun:reader_loop
fun:main
}
{
Another bash leak
Memcheck:Leak
fun:malloc
fun:xmalloc
fun:set_default_locale
fun:main
}
{
Bash leak in cli regression testing
Memcheck:Leak
- match-leak-kinds: definite
fun:malloc
fun:xmalloc
obj:/usr/bin/bash
fun:execute_command_internal
obj:/usr/bin/bash
fun:execute_command_internal
fun:parse_and_execute
fun:command_substitute
obj:/usr/bin/bash
obj:/usr/bin/bash
obj:/usr/bin/bash
fun:expand_string_assignment
obj:/usr/bin/bash
obj:/usr/bin/bash
obj:/usr/bin/bash
obj:/usr/bin/bash
fun:execute_command_internal
fun:execute_command_internal
fun:execute_command
fun:execute_command_internal
}
{
Ignore option parsing
Memcheck:Leak
fun:realloc
fun:crm_get_option_long
fun:main
}
{
dlopen internals
Memcheck:Leak
fun:calloc
fun:_dlerror_run
fun:dlopen*
fun:_log_so_walk_callback
fun:dl_iterate_phdr
fun:qb_log_init
}
{
Cman - Points to uninitialized bytes
Memcheck:Param
socketcall.sendmsg(msg)
fun:__sendmsg_nocancel
obj:*/libcman.so.3.0
obj:*/libcman.so.3.0
}
{
Cman - Who cares if unused bytes are uninitialized
Memcheck:Param
sendmsg(msg)
fun:__sendmsg_nocancel
obj:*/libcman.so.3.0
obj:*/libcman.so.3.0
}
{
Cman - Jump or move depends on uninitialized values
Memcheck:Cond
obj:*/libcman.so.3.0
obj:*/libcman.so.3.0
}
{
quarks - hashtable
Memcheck:Leak
fun:calloc
fun:g_malloc0
obj:*/libglib-*
fun:g_slice_alloc
fun:g_hash_table_new_full
fun:g_quark_from_static_string
}
{
quarks - hashtable 2
Memcheck:Leak
fun:malloc
fun:g_malloc
fun:g_slice_alloc
fun:g_hash_table_new_full
fun:g_quark_from_static_string
}
{
quarks - hashtable 3
Memcheck:Leak
fun:calloc
fun:g_malloc0
fun:g_hash_table_new_full
fun:g_quark_from_static_string
}
{
quarks - hashtable 4
Memcheck:Leak
fun:malloc
fun:realloc
fun:g_realloc
fun:g_quark_from_static_string
}
{
glib mainloop internals - default
Memcheck:Leak
fun:calloc
fun:g_malloc0
fun:g_main_context_new
fun:g_main_context_default
fun:g_main_loop_new
fun:main
}
{
glib mainloop internals - default
Memcheck:Leak
fun:malloc
fun:g_malloc
fun:g_slice_alloc
fun:*
fun:g_main_context_new
fun:g_main_context_default
fun:g_main_loop_new
fun:main
}
{
glib mainloop internals - default
Memcheck:Leak
fun:calloc
fun:g_malloc0
obj:*/libglib-2.*
fun:g_slice_alloc
fun:g_ptr_array_sized_new
fun:g_main_context_new
fun:g_main_context_default
fun:g_main_loop_new
}
{
glib mainloop internals - run
Memcheck:Leak
fun:calloc
fun:g_malloc0
fun:g_thread_self
fun:g_main_loop_run
}
{
glib mainloop internals - run
Memcheck:Leak
fun:malloc
fun:g_malloc
obj:*/libglib-2.*
fun:g_main_loop_run
}
{
glib mainloop internals - run
Memcheck:Leak
fun:malloc
fun:realloc
fun:g_realloc
obj:*/libglib-2.*
fun:g_ptr_array_add
fun:g_main_context_check
obj:*/libglib-2.*
fun:g_main_loop_run
}
{
glib mainloop internals - run
Memcheck:Leak
fun:malloc
fun:g_malloc
fun:g_slice_alloc
fun:g_slice_alloc0
obj:*/libglib-2.*
fun:g_main_context_dispatch
obj:*/libglib-2.*
fun:g_main_loop_run
}
{
glib mainloop internals - run
Memcheck:Leak
fun:malloc
fun:realloc
fun:g_realloc
obj:*/libglib-2.*
fun:g_array_set_size
fun:g_static_private_set
obj:*/libglib-2.*
fun:g_main_context_dispatch
obj:*/libglib-2.*
fun:g_main_loop_run
}
{
glib mainloop internals - run
Memcheck:Leak
fun:malloc
fun:g_malloc
fun:g_slice_alloc
fun:g_array_sized_new
fun:g_static_private_set
obj:*/libglib-2.*
fun:g_main_context_dispatch
obj:*/libglib-2.*
fun:g_main_loop_run
}
{
glib types
Memcheck:Leak
fun:malloc
fun:realloc
fun:g_realloc
obj:*/libgobject-*
fun:g_type_register_static
}
{
glib types 2
Memcheck:Leak
fun:realloc
fun:g_realloc
obj:*/libgobject-*
fun:g_type_register_static
fun:g_param_type_register_static
}
{
glib types 3
Memcheck:Leak
fun:calloc
fun:g_malloc0
obj:*/libgobject-*
fun:g_type_register_fundamental
}
{
glib types 4
Memcheck:Leak
fun:calloc
fun:g_malloc0
obj:*/libgobject-*
obj:*/libgobject-*
fun:g_type_register_fundamental
}
{
glib - the return
Memcheck:Leak
fun:calloc
fun:g_malloc0
obj:*/libgobject-*
obj:*/libgobject-*
fun:_dl_init
}
{
glib - seriously?
Memcheck:Leak
fun:calloc
fun:g_malloc0
obj:*/libgobject-*
obj:*/libgobject-*
obj:*/libgobject-*
fun:_dl_init
}
{
glib - this is not funny anymore
Memcheck:Leak
fun:malloc
fun:realloc
fun:g_realloc
obj:*/libgobject-*
fun:g_type_register_fundamental
}
{
glib - why do you hate me?
Memcheck:Leak
fun:calloc
fun:g_malloc0
obj:*/libgobject-*
obj:*/libgobject-*
fun:call_init.part.0
fun:_dl_init
}
{
dear glib - you suck at memory management
Memcheck:Leak
fun:calloc
fun:g_malloc0
obj:*/libgobject-*
obj:*/libgobject-*
obj:*/libgobject-*
fun:call_init.part.0
fun:_dl_init
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Oct 16, 12:17 AM (1 d, 20 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2530783
Default Alt Text
(115 KB)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment