diff --git a/attrd/commands.c b/attrd/commands.c
index dc488fe482..3deed8e65b 100644
--- a/attrd/commands.c
+++ b/attrd/commands.c
@@ -1,1192 +1,1193 @@
 /*
  * Copyright (C) 2013 Andrew Beekhof <andrew@beekhof.net>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public
  * License as published by the Free Software Foundation; either
  * version 2 of the License, or (at your option) any later version.
  *
  * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * General Public License for more details.
  *
  * You should have received a copy of the GNU General Public
  * License along with this library; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
 #include <crm_internal.h>
 
 #include <sys/types.h>
 #include <regex.h>
 #include <glib.h>
 
 #include <crm/msg_xml.h>
 #include <crm/cluster.h>
 #include <crm/cib.h>
 #include <crm/cluster/internal.h>
 #include <crm/cluster/election.h>
 #include <crm/cib/internal.h>
 
 #include <internal.h>
 
 /*
  * Legacy attrd (all pre-1.1.11 Pacemaker versions, plus all versions when used
  * with the no-longer-supported CMAN or corosync-plugin stacks) is unversioned.
  *
  * With atomic attrd, each attrd will send ATTRD_PROTOCOL_VERSION with every
  * peer request and reply. As of Pacemaker 2.0.0, at start-up each attrd will
  * also set a private attribute for itself with its version, so any attrd can
  * determine the minimum version supported by all peers.
  *
  * Protocol  Pacemaker  Significant changes
  * --------  ---------  -------------------
  *     1       1.1.11   ATTRD_OP_UPDATE (F_ATTRD_ATTRIBUTE only),
  *                      ATTRD_OP_PEER_REMOVE, ATTRD_OP_REFRESH, ATTRD_OP_FLUSH,
  *                      ATTRD_OP_SYNC, ATTRD_OP_SYNC_RESPONSE
  *     1       1.1.13   ATTRD_OP_UPDATE (with F_ATTR_REGEX), ATTRD_OP_QUERY
  *     1       1.1.15   ATTRD_OP_UPDATE_BOTH, ATTRD_OP_UPDATE_DELAY
  *     2       1.1.17   ATTRD_OP_CLEAR_FAILCOUNT
  */
 #define ATTRD_PROTOCOL_VERSION "2"
 
 int last_cib_op_done = 0;
 char *peer_writer = NULL;
 GHashTable *attributes = NULL;
 
 void write_attribute(attribute_t *a);
 void write_or_elect_attribute(attribute_t *a);
 void attrd_peer_update(crm_node_t *peer, xmlNode *xml, const char *host, bool filter);
 void attrd_peer_sync(crm_node_t *peer, xmlNode *xml);
 void attrd_peer_remove(const char *host, gboolean uncache, const char *source);
 
 static gboolean
 send_attrd_message(crm_node_t * node, xmlNode * data)
 {
     crm_xml_add(data, F_TYPE, T_ATTRD);
     crm_xml_add(data, F_ATTRD_VERSION, ATTRD_PROTOCOL_VERSION);
     crm_xml_add_int(data, F_ATTRD_WRITER, election_state(writer));
 
     return send_cluster_message(node, crm_msg_attrd, data, TRUE);
 }
 
 static gboolean
 attribute_timer_cb(gpointer data)
 {
     attribute_t *a = data;
     crm_trace("Dampen interval expired for %s in state %d", a->id, election_state(writer));
     write_or_elect_attribute(a);
     return FALSE;
 }
 
 static void
 free_attribute_value(gpointer data)
 {
     attribute_value_t *v = data;
 
     free(v->nodename);
     free(v->current);
     free(v->requested);
     free(v);
 }
 
 void
 free_attribute(gpointer data)
 {
     attribute_t *a = data;
     if(a) {
         free(a->id);
         free(a->set);
         free(a->uuid);
         free(a->user);
 
         mainloop_timer_del(a->timer);
         g_hash_table_destroy(a->values);
 
         free(a);
     }
 }
 
 static xmlNode *
 build_attribute_xml(
     xmlNode *parent, const char *name, const char *set, const char *uuid, unsigned int timeout_ms, const char *user,
     gboolean is_private, const char *peer, uint32_t peerid, const char *value)
 {
     xmlNode *xml = create_xml_node(parent, __FUNCTION__);
 
     crm_xml_add(xml, F_ATTRD_ATTRIBUTE, name);
     crm_xml_add(xml, F_ATTRD_SET, set);
     crm_xml_add(xml, F_ATTRD_KEY, uuid);
     crm_xml_add(xml, F_ATTRD_USER, user);
     crm_xml_add(xml, F_ATTRD_HOST, peer);
     crm_xml_add_int(xml, F_ATTRD_HOST_ID, peerid);
     crm_xml_add(xml, F_ATTRD_VALUE, value);
     crm_xml_add_int(xml, F_ATTRD_DAMPEN, timeout_ms/1000);
     crm_xml_add_int(xml, F_ATTRD_IS_PRIVATE, is_private);
 
     return xml;
 }
 
 static attribute_t *
 create_attribute(xmlNode *xml)
 {
     int dampen = 0;
     const char *value = crm_element_value(xml, F_ATTRD_DAMPEN);
     attribute_t *a = calloc(1, sizeof(attribute_t));
 
     a->id      = crm_element_value_copy(xml, F_ATTRD_ATTRIBUTE);
     a->set     = crm_element_value_copy(xml, F_ATTRD_SET);
     a->uuid    = crm_element_value_copy(xml, F_ATTRD_KEY);
     a->values = g_hash_table_new_full(crm_strcase_hash, crm_strcase_equal, NULL, free_attribute_value);
 
     crm_element_value_int(xml, F_ATTRD_IS_PRIVATE, &a->is_private);
 
 #if ENABLE_ACL
     crm_trace("Performing all %s operations as user '%s'", a->id, a->user);
     a->user = crm_element_value_copy(xml, F_ATTRD_USER);
 #endif
 
     if(value) {
         dampen = crm_get_msec(value);
         crm_trace("Created attribute %s with delay %dms (%s)", a->id, dampen, value);
     } else {
         crm_trace("Created attribute %s with no delay", a->id);
     }
 
     if(dampen > 0) {
         a->timeout_ms = dampen;
         a->timer = mainloop_timer_add(a->id, a->timeout_ms, FALSE, attribute_timer_cb, a);
     } else if (dampen < 0) {
 	crm_warn("Ignoring invalid delay %s for attribute %s", value, a->id);
     }
 
     g_hash_table_replace(attributes, a->id, a);
     return a;
 }
 
 /*!
  * \internal
  * \brief Respond to a client peer-remove request (i.e. propagate to all peers)
  *
  * \param[in] client_name Name of client that made request (for log messages)
  * \param[in] xml         Root of request XML
  *
  * \return void
  */
 void
 attrd_client_peer_remove(const char *client_name, xmlNode *xml)
 {
     const char *host = crm_element_value(xml, F_ATTRD_HOST);
 
     if (host) {
         crm_info("Client %s is requesting all values for %s be removed",
                  client_name, host);
         send_attrd_message(NULL, xml); /* ends up at attrd_peer_message() */
     } else {
         crm_info("Ignoring request by client %s to remove all peer values without specifying peer",
                  client_name);
     }
 }
 
 /*!
  * \internal
  * \brief Respond to a client update request
  *
  * \param[in] xml         Root of request XML
  *
  * \return void
  */
 void
 attrd_client_update(xmlNode *xml)
 {
     attribute_t *a = NULL;
     char *host = crm_element_value_copy(xml, F_ATTRD_HOST);
     const char *attr = crm_element_value(xml, F_ATTRD_ATTRIBUTE);
     const char *value = crm_element_value(xml, F_ATTRD_VALUE);
     const char *regex = crm_element_value(xml, F_ATTRD_REGEX);
 
     /* If a regex was specified, broadcast a message for each match */
     if ((attr == NULL) && regex) {
         GHashTableIter aIter;
         regex_t *r_patt = calloc(1, sizeof(regex_t));
 
         crm_debug("Setting %s to %s", regex, value);
         if (regcomp(r_patt, regex, REG_EXTENDED|REG_NOSUB)) {
             crm_err("Bad regex '%s' for update", regex);
 
         } else {
             g_hash_table_iter_init(&aIter, attributes);
             while (g_hash_table_iter_next(&aIter, (gpointer *) & attr, NULL)) {
                 int status = regexec(r_patt, attr, 0, NULL, 0);
 
                 if (status == 0) {
                     crm_trace("Matched %s with %s", attr, regex);
                     crm_xml_add(xml, F_ATTRD_ATTRIBUTE, attr);
                     send_attrd_message(NULL, xml);
                 }
             }
         }
 
         free(host);
         regfree(r_patt);
         free(r_patt);
         return;
 
     } else if (attr == NULL) {
         crm_err("Update request did not specify attribute or regular expression");
         free(host);
         return;
     }
 
     if (host == NULL) {
         crm_trace("Inferring host");
         host = strdup(attrd_cluster->uname);
         crm_xml_add(xml, F_ATTRD_HOST, host);
         crm_xml_add_int(xml, F_ATTRD_HOST_ID, attrd_cluster->nodeid);
     }
 
     a = g_hash_table_lookup(attributes, attr);
 
     /* If value was specified using ++ or += notation, expand to real value */
     if (value) {
         if (attrd_value_needs_expansion(value)) {
             int int_value;
             attribute_value_t *v = NULL;
 
             if (a) {
                 v = g_hash_table_lookup(a->values, host);
             }
             int_value = attrd_expand_value(value, (v? v->current : NULL));
 
             crm_info("Expanded %s=%s to %d", attr, value, int_value);
             crm_xml_add_int(xml, F_ATTRD_VALUE, int_value);
 
             /* Replacing the value frees the previous memory, so re-query it */
             value = crm_element_value(xml, F_ATTRD_VALUE);
         }
     }
 
     if ((peer_writer == NULL) && (election_state(writer) != election_in_progress)) {
         crm_info("Starting an election to determine the writer");
         election_vote(writer);
     }
 
     crm_debug("Broadcasting %s[%s] = %s%s", attr, host, value,
               ((election_state(writer) == election_won)? " (writer)" : ""));
 
     free(host);
 
     send_attrd_message(NULL, xml); /* ends up at attrd_peer_message() */
 }
 
 /*!
  * \internal
  * \brief Respond to client clear-failure request
  *
  * \param[in] xml         Request XML
  */
 void
 attrd_client_clear_failure(xmlNode *xml)
 {
 #if 0
     /* @TODO Track the minimum supported protocol version across all nodes,
      * then enable this more-efficient code.
      */
     if (compare_version("2", minimum_protocol_version) <= 0) {
         /* Propagate to all peers (including ourselves).
          * This ends up at attrd_peer_message().
          */
         send_attrd_message(NULL, xml);
         return;
     }
 #endif
 
     const char *rsc = crm_element_value(xml, F_ATTRD_RESOURCE);
     const char *op = crm_element_value(xml, F_ATTRD_OPERATION);
     const char *interval_s = crm_element_value(xml, F_ATTRD_INTERVAL);
 
     /* Map this to an update */
     crm_xml_add(xml, F_ATTRD_TASK, ATTRD_OP_UPDATE);
 
     /* Add regular expression matching desired attributes */
 
     if (rsc) {
         char *pattern;
 
         if (op == NULL) {
             pattern = crm_strdup_printf(ATTRD_RE_CLEAR_ONE, rsc);
 
         } else {
             int interval = crm_get_interval(interval_s);
 
             pattern = crm_strdup_printf(ATTRD_RE_CLEAR_OP,
                                         rsc, op, interval);
         }
 
         crm_xml_add(xml, F_ATTRD_REGEX, pattern);
         free(pattern);
 
     } else {
         crm_xml_add(xml, F_ATTRD_REGEX, ATTRD_RE_CLEAR_ALL);
     }
 
     /* Make sure attribute and value are not set, so we delete via regex */
     if (crm_element_value(xml, F_ATTRD_ATTRIBUTE)) {
         crm_xml_replace(xml, F_ATTRD_ATTRIBUTE, NULL);
     }
     if (crm_element_value(xml, F_ATTRD_VALUE)) {
         crm_xml_replace(xml, F_ATTRD_VALUE, NULL);
     }
 
     attrd_client_update(xml);
 }
 
 /*!
  * \internal
  * \brief Respond to a client refresh request (i.e. write out all attributes)
  *
  * \return void
  */
 void
 attrd_client_refresh(void)
 {
     GHashTableIter iter;
     attribute_t *a = NULL;
 
     /* 'refresh' forces a write of the current value of all attributes
      * Cancel any existing timers, we're writing it NOW
      */
     g_hash_table_iter_init(&iter, attributes);
     while (g_hash_table_iter_next(&iter, NULL, (gpointer *) & a)) {
         mainloop_timer_stop(a->timer);
     }
 
     crm_info("Updating all attributes");
     write_attributes(TRUE);
 }
 
 /*!
  * \internal
  * \brief Build the XML reply to a client query
  *
  * param[in] attr Name of requested attribute
  * param[in] host Name of requested host (or NULL for all hosts)
  *
  * \return New XML reply
  * \note Caller is responsible for freeing the resulting XML
  */
 static xmlNode *build_query_reply(const char *attr, const char *host)
 {
     xmlNode *reply = create_xml_node(NULL, __FUNCTION__);
     attribute_t *a;
 
     if (reply == NULL) {
         return NULL;
     }
     crm_xml_add(reply, F_TYPE, T_ATTRD);
     crm_xml_add(reply, F_ATTRD_VERSION, ATTRD_PROTOCOL_VERSION);
 
     /* If desired attribute exists, add its value(s) to the reply */
     a = g_hash_table_lookup(attributes, attr);
     if (a) {
         attribute_value_t *v;
         xmlNode *host_value;
 
         crm_xml_add(reply, F_ATTRD_ATTRIBUTE, attr);
 
         /* Allow caller to use "localhost" to refer to local node */
         if (safe_str_eq(host, "localhost")) {
             host = attrd_cluster->uname;
             crm_trace("Mapped localhost to %s", host);
         }
 
         /* If a specific node was requested, add its value */
         if (host) {
             v = g_hash_table_lookup(a->values, host);
             host_value = create_xml_node(reply, XML_CIB_TAG_NODE);
             if (host_value == NULL) {
                 free_xml(reply);
                 return NULL;
             }
             crm_xml_add(host_value, F_ATTRD_HOST, host);
             crm_xml_add(host_value, F_ATTRD_VALUE, (v? v->current : NULL));
 
         /* Otherwise, add all nodes' values */
         } else {
             GHashTableIter iter;
 
             g_hash_table_iter_init(&iter, a->values);
             while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &v)) {
                 host_value = create_xml_node(reply, XML_CIB_TAG_NODE);
                 if (host_value == NULL) {
                     free_xml(reply);
                     return NULL;
                 }
                 crm_xml_add(host_value, F_ATTRD_HOST, v->nodename);
                 crm_xml_add(host_value, F_ATTRD_VALUE, v->current);
             }
         }
     }
     return reply;
 }
 
 /*!
  * \internal
  * \brief Respond to a client query
  *
  * \param[in] client Who queried us
  * \param[in] query  Root of query XML
  *
  * \return void
  */
 void
 attrd_client_query(crm_client_t *client, uint32_t id, uint32_t flags, xmlNode *query)
 {
     const char *attr;
     const char *origin = crm_element_value(query, F_ORIG);
     ssize_t rc;
     xmlNode *reply;
 
     if (origin == NULL) {
         origin = "unknown client";
     }
     crm_debug("Query arrived from %s", origin);
 
     /* Request must specify attribute name to query */
     attr = crm_element_value(query, F_ATTRD_ATTRIBUTE);
     if (attr == NULL) {
         crm_warn("Ignoring malformed query from %s (no attribute name given)",
                  origin);
         return;
     }
 
     /* Build the XML reply */
     reply = build_query_reply(attr, crm_element_value(query, F_ATTRD_HOST));
     if (reply == NULL) {
         crm_err("Could not respond to query from %s: could not create XML reply",
                  origin);
         return;
     }
     crm_log_xml_trace(reply, "Reply");
 
     /* Send the reply to the client */
     client->request_id = 0;
     if ((rc = crm_ipcs_send(client, id, reply, flags)) < 0) {
         crm_err("Could not respond to query from %s: %s (%d)",
                 origin, pcmk_strerror(-rc), -rc);
     }
     free_xml(reply);
 }
 
 /*!
  * \internal
  * \brief Clear failure-related attributes
  *
  * \param[in] peer  Peer that sent clear request
  * \param[in] xml   Request XML
  */
 static void
 attrd_peer_clear_failure(crm_node_t *peer, xmlNode *xml)
 {
     const char *rsc = crm_element_value(xml, F_ATTRD_RESOURCE);
     const char *host = crm_element_value(xml, F_ATTRD_HOST);
     const char *op = crm_element_value(xml, F_ATTRD_OPERATION);
     const char *interval_s = crm_element_value(xml, F_ATTRD_INTERVAL);
     int interval = crm_get_interval(interval_s);
     char *attr = NULL;
     GHashTableIter iter;
     regex_t regex;
 
     if (attrd_failure_regex(&regex, rsc, op, interval) != pcmk_ok) {
         crm_info("Ignoring invalid request to clear failures for %s",
                  (rsc? rsc : "all resources"));
         return;
     }
 
     crm_xml_add(xml, F_ATTRD_TASK, ATTRD_OP_UPDATE);
 
     /* Make sure value is not set, so we delete */
     if (crm_element_value(xml, F_ATTRD_VALUE)) {
         crm_xml_replace(xml, F_ATTRD_VALUE, NULL);
     }
 
     g_hash_table_iter_init(&iter, attributes);
     while (g_hash_table_iter_next(&iter, (gpointer *) &attr, NULL)) {
         if (regexec(&regex, attr, 0, NULL, 0) == 0) {
             crm_trace("Matched %s when clearing %s",
                       attr, (rsc? rsc : "all resources"));
             crm_xml_add(xml, F_ATTRD_ATTRIBUTE, attr);
             attrd_peer_update(peer, xml, host, FALSE);
         }
     }
     regfree(&regex);
 }
 
 /*!
     \internal
     \brief Broadcast private attribute for local node with protocol version
 */
 void
 attrd_broadcast_protocol()
 {
     xmlNode *attrd_op = create_xml_node(NULL, __FUNCTION__);
 
     crm_xml_add(attrd_op, F_TYPE, T_ATTRD);
     crm_xml_add(attrd_op, F_ORIG, crm_system_name);
     crm_xml_add(attrd_op, F_ATTRD_TASK, ATTRD_OP_UPDATE);
     crm_xml_add(attrd_op, F_ATTRD_ATTRIBUTE, CRM_ATTR_PROTOCOL);
     crm_xml_add(attrd_op, F_ATTRD_VALUE, ATTRD_PROTOCOL_VERSION);
     crm_xml_add_int(attrd_op, F_ATTRD_IS_PRIVATE, 1);
     attrd_client_update(attrd_op);
+    free_xml(attrd_op);
 }
 
 void
 attrd_peer_message(crm_node_t *peer, xmlNode *xml)
 {
     int peer_state = 0;
     const char *op = crm_element_value(xml, F_ATTRD_TASK);
     const char *election_op = crm_element_value(xml, F_CRM_TASK);
     const char *host = crm_element_value(xml, F_ATTRD_HOST);
 
     if(election_op) {
         enum election_result rc = 0;
 
         crm_xml_add(xml, F_CRM_HOST_FROM, peer->uname);
         rc = election_count_vote(writer, xml, TRUE);
         switch(rc) {
             case election_start:
                 free(peer_writer);
                 peer_writer = NULL;
                 election_vote(writer);
                 break;
             case election_lost:
                 free(peer_writer);
                 peer_writer = strdup(peer->uname);
                 break;
             default:
                 election_check(writer);
                 break;
         }
         return;
     }
 
     crm_element_value_int(xml, F_ATTRD_WRITER, &peer_state);
     if(election_state(writer) == election_won
        && peer_state == election_won
        && safe_str_neq(peer->uname, attrd_cluster->uname)) {
         crm_notice("Detected another attribute writer: %s", peer->uname);
         election_vote(writer);
 
     } else if(peer_state == election_won) {
         if(peer_writer == NULL) {
             peer_writer = strdup(peer->uname);
             crm_notice("Recorded attribute writer: %s", peer->uname);
 
         } else if(safe_str_neq(peer->uname, peer_writer)) {
             crm_notice("Recorded new attribute writer: %s (was %s)", peer->uname, peer_writer);
             free(peer_writer);
             peer_writer = strdup(peer->uname);
         }
     }
 
     if (safe_str_eq(op, ATTRD_OP_UPDATE) || safe_str_eq(op, ATTRD_OP_UPDATE_BOTH) || safe_str_eq(op, ATTRD_OP_UPDATE_DELAY)) {
         attrd_peer_update(peer, xml, host, FALSE);
 
     } else if (safe_str_eq(op, ATTRD_OP_SYNC)) {
         attrd_peer_sync(peer, xml);
 
     } else if (safe_str_eq(op, ATTRD_OP_PEER_REMOVE)) {
         attrd_peer_remove(host, TRUE, peer->uname);
 
     } else if (safe_str_eq(op, ATTRD_OP_CLEAR_FAILURE)) {
         /* It is not currently possible to receive this as a peer command,
          * but will be, if we one day enable propagating this operation.
          */
         attrd_peer_clear_failure(peer, xml);
 
     } else if (safe_str_eq(op, ATTRD_OP_SYNC_RESPONSE)
               && safe_str_neq(peer->uname, attrd_cluster->uname)) {
         xmlNode *child = NULL;
 
         crm_info("Processing %s from %s", op, peer->uname);
         for (child = __xml_first_child(xml); child != NULL; child = __xml_next(child)) {
             host = crm_element_value(child, F_ATTRD_HOST);
             attrd_peer_update(peer, child, host, TRUE);
         }
     }
 }
 
 void
 attrd_peer_sync(crm_node_t *peer, xmlNode *xml)
 {
     GHashTableIter aIter;
     GHashTableIter vIter;
 
     attribute_t *a = NULL;
     attribute_value_t *v = NULL;
     xmlNode *sync = create_xml_node(NULL, __FUNCTION__);
 
     crm_xml_add(sync, F_ATTRD_TASK, ATTRD_OP_SYNC_RESPONSE);
 
     g_hash_table_iter_init(&aIter, attributes);
     while (g_hash_table_iter_next(&aIter, NULL, (gpointer *) & a)) {
         g_hash_table_iter_init(&vIter, a->values);
         while (g_hash_table_iter_next(&vIter, NULL, (gpointer *) & v)) {
             crm_debug("Syncing %s[%s] = %s to %s", a->id, v->nodename, v->current, peer?peer->uname:"everyone");
             build_attribute_xml(sync, a->id, a->set, a->uuid, a->timeout_ms, a->user, a->is_private,
                                 v->nodename, v->nodeid, v->current);
         }
     }
 
     crm_debug("Syncing values to %s", peer?peer->uname:"everyone");
     send_attrd_message(peer, sync);
     free_xml(sync);
 }
 
 /*!
  * \internal
  * \brief Remove all attributes and optionally peer cache entries for a node
  *
  * \param[in] host     Name of node to purge
  * \param[in] uncache  If TRUE, remove node from peer caches
  * \param[in] source   Who requested removal (only used for logging)
  */
 void
 attrd_peer_remove(const char *host, gboolean uncache, const char *source)
 {
     attribute_t *a = NULL;
     GHashTableIter aIter;
 
     CRM_CHECK(host != NULL, return);
     crm_notice("Removing all %s attributes for peer %s", host, source);
 
     g_hash_table_iter_init(&aIter, attributes);
     while (g_hash_table_iter_next(&aIter, NULL, (gpointer *) & a)) {
         if(g_hash_table_remove(a->values, host)) {
             crm_debug("Removed %s[%s] for peer %s", a->id, host, source);
         }
     }
 
     if (uncache) {
         crm_remote_peer_cache_remove(host);
         reap_crm_member(0, host);
     }
 }
 
 /*!
  * \internal
  * \brief Return host's hash table entry (creating one if needed)
  *
  * \param[in] values Hash table of values
  * \param[in] host Name of peer to look up
  * \param[in] xml XML describing the attribute
  *
  * \return Pointer to new or existing hash table entry
  */
 static attribute_value_t *
 attrd_lookup_or_create_value(GHashTable *values, const char *host, xmlNode *xml)
 {
     attribute_value_t *v = g_hash_table_lookup(values, host);
     int is_remote = 0;
 
     crm_element_value_int(xml, F_ATTRD_IS_REMOTE, &is_remote);
     if (is_remote) {
         /* If we previously assumed this node was an unseen cluster node,
          * remove its entry from the cluster peer cache.
          */
         crm_node_t *dup = crm_find_peer(0, host);
 
         if (dup && (dup->uuid == NULL)) {
             reap_crm_member(0, host);
         }
 
         /* Ensure this host is in the remote peer cache */
         crm_remote_peer_cache_add(host);
     }
 
     if (v == NULL) {
         v = calloc(1, sizeof(attribute_value_t));
         CRM_ASSERT(v != NULL);
 
         v->nodename = strdup(host);
         CRM_ASSERT(v->nodename != NULL);
 
         v->is_remote = is_remote;
         g_hash_table_replace(values, v->nodename, v);
     }
     return(v);
 }
 
 void
 attrd_peer_update(crm_node_t *peer, xmlNode *xml, const char *host, bool filter)
 {
     bool update_both = FALSE;
     attribute_t *a;
     attribute_value_t *v = NULL;
 
     const char *op = crm_element_value(xml, F_ATTRD_TASK);
     const char *attr = crm_element_value(xml, F_ATTRD_ATTRIBUTE);
     const char *value = crm_element_value(xml, F_ATTRD_VALUE);
 
     if (attr == NULL) {
         crm_warn("Could not update attribute: peer did not specify name");
         return;
     }
 
     update_both = ((op == NULL) // ATTRD_OP_SYNC_RESPONSE has no F_ATTRD_TASK
                    || safe_str_eq(op, ATTRD_OP_UPDATE_BOTH));
 
     // Look up or create attribute entry
     a = g_hash_table_lookup(attributes, attr);
     if (a == NULL) {
         if (update_both || safe_str_eq(op, ATTRD_OP_UPDATE)) {
             a = create_attribute(xml);
         } else {
             crm_warn("Could not update %s: attribute not found", attr);
             return;
         }
     }
 
     // Update attribute dampening
     if (update_both || safe_str_eq(op, ATTRD_OP_UPDATE_DELAY)) {
         const char *dvalue = crm_element_value(xml, F_ATTRD_DAMPEN);
         int dampen = 0;
 
         if (dvalue == NULL) {
             crm_warn("Could not update %s: peer did not specify value for delay",
                      attr);
             return;
         }
 
         dampen = crm_get_msec(dvalue);
         if (dampen < 0) {
             crm_warn("Could not update %s: invalid delay value %dms (%s)",
                      attr, dampen, dvalue);
             return;
         }
 
         if (a->timeout_ms != dampen) {
             mainloop_timer_stop(a->timer);
             mainloop_timer_del(a->timer);
             a->timeout_ms = dampen;
             if (dampen > 0) {
                 a->timer = mainloop_timer_add(attr, a->timeout_ms, FALSE,
                                               attribute_timer_cb, a);
                 crm_info("Update attribute %s delay to %dms (%s)",
                          attr, dampen, dvalue);
             } else {
                 a->timer = NULL;
                 crm_info("Update attribute %s to remove delay", attr);
             }
 
             /* If dampening changed, do an immediate write-out,
              * otherwise repeated dampening changes would prevent write-outs
              */
             write_or_elect_attribute(a);
         }
 
         if (!update_both) {
             return;
         }
     }
 
     // If no host was specified, update all hosts recursively
     if (host == NULL) {
         GHashTableIter vIter;
 
         crm_debug("Setting %s for all hosts to %s", attr, value);
         xml_remove_prop(xml, F_ATTRD_HOST_ID);
         g_hash_table_iter_init(&vIter, a->values);
         while (g_hash_table_iter_next(&vIter, (gpointer *) & host, NULL)) {
             attrd_peer_update(peer, xml, host, filter);
         }
         return;
     }
 
     // Update attribute value for one host
 
     v = attrd_lookup_or_create_value(a->values, host, xml);
 
     if (filter && safe_str_neq(v->current, value)
         && safe_str_eq(host, attrd_cluster->uname)) {
 
         xmlNode *sync = create_xml_node(NULL, __FUNCTION__);
 
         crm_notice("%s[%s]: local value '%s' takes priority over '%s' from %s",
                    attr, host, v->current, value, peer->uname);
 
         crm_xml_add(sync, F_ATTRD_TASK, ATTRD_OP_SYNC_RESPONSE);
         v = g_hash_table_lookup(a->values, host);
         build_attribute_xml(sync, attr, a->set, a->uuid, a->timeout_ms, a->user,
                             a->is_private, v->nodename, v->nodeid, v->current);
 
         crm_xml_add_int(sync, F_ATTRD_WRITER, election_state(writer));
 
         /* Broadcast in case any other nodes had the inconsistent value */
         send_attrd_message(NULL, sync);
         free_xml(sync);
 
     } else if (safe_str_neq(v->current, value)) {
         crm_info("Setting %s[%s]: %s -> %s from %s",
                  attr, host, v->current, value, peer->uname);
         free(v->current);
         v->current = (value? strdup(value) : NULL);
         a->changed = TRUE;
 
         // Write out new value or start dampening timer
         if (a->timer) {
             crm_trace("Delayed write out (%dms) for %s", a->timeout_ms, attr);
             mainloop_timer_start(a->timer);
         } else {
             write_or_elect_attribute(a);
         }
 
     } else {
         crm_trace("Unchanged %s[%s] from %s is %s", attr, host, peer->uname, value);
     }
 
     /* If this is a cluster node whose node ID we are learning, remember it */
     if ((v->nodeid == 0) && (v->is_remote == FALSE)
         && (crm_element_value_int(xml, F_ATTRD_HOST_ID, (int*)&v->nodeid) == 0)) {
 
         crm_node_t *known_peer = crm_get_peer(v->nodeid, host);
 
         crm_trace("Learned %s has node id %s",
                   known_peer->uname, known_peer->uuid);
         if (election_state(writer) == election_won) {
             write_attributes(FALSE);
         }
     }
 }
 
 void
 write_or_elect_attribute(attribute_t *a)
 {
     enum election_result rc = election_state(writer);
     if(rc == election_won) {
         write_attribute(a);
 
     } else if(rc == election_in_progress) {
         crm_trace("Election in progress to determine who will write out %s", a->id);
 
     } else if(peer_writer == NULL) {
         crm_info("Starting an election to determine who will write out %s", a->id);
         election_vote(writer);
 
     } else {
         crm_trace("%s will write out %s, we are in state %d", peer_writer, a->id, rc);
     }
 }
 
 gboolean
 attrd_election_cb(gpointer user_data)
 {
     free(peer_writer);
     peer_writer = strdup(attrd_cluster->uname);
 
     /* Update the peers after an election */
     attrd_peer_sync(NULL, NULL);
 
     /* Update the CIB after an election */
     write_attributes(TRUE);
     return FALSE;
 }
 
 
 void
 attrd_peer_change_cb(enum crm_status_type kind, crm_node_t *peer, const void *data)
 {
     if ((kind == crm_status_nstate) || (kind == crm_status_rstate)) {
         if (safe_str_eq(peer->state, CRM_NODE_MEMBER)) {
             /* If we're the writer, send new peers a list of all attributes
              * (unless it's a remote node, which doesn't run its own attrd)
              */
             if ((election_state(writer) == election_won)
                 && !is_set(peer->flags, crm_remote_node)) {
                 attrd_peer_sync(peer, NULL);
             }
         } else {
             /* Remove all attribute values associated with lost nodes */
             attrd_peer_remove(peer->uname, FALSE, "loss");
             if (peer_writer && safe_str_eq(peer->uname, peer_writer)) {
                 free(peer_writer);
                 peer_writer = NULL;
                 crm_notice("Lost attribute writer %s", peer->uname);
             }
         }
     }
 }
 
 static void
 attrd_cib_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
 {
     int level = LOG_ERR;
     GHashTableIter iter;
     const char *peer = NULL;
     attribute_value_t *v = NULL;
 
     char *name = user_data;
     attribute_t *a = g_hash_table_lookup(attributes, name);
 
     if(a == NULL) {
         crm_info("Attribute %s no longer exists", name);
         goto done;
     }
 
     a->update = 0;
     if (rc == pcmk_ok && call_id < 0) {
         rc = call_id;
     }
 
     switch (rc) {
         case pcmk_ok:
             level = LOG_INFO;
             last_cib_op_done = call_id;
             break;
         case -pcmk_err_diff_failed:    /* When an attr changes while the CIB is syncing */
         case -ETIME:           /* When an attr changes while there is a DC election */
         case -ENXIO:           /* When an attr changes while the CIB is syncing a
                                 *   newer config from a node that just came up
                                 */
             level = LOG_WARNING;
             break;
     }
 
     do_crm_log(level, "Update %d for %s: %s (%d)", call_id, name, pcmk_strerror(rc), rc);
 
     g_hash_table_iter_init(&iter, a->values);
     while (g_hash_table_iter_next(&iter, (gpointer *) & peer, (gpointer *) & v)) {
         do_crm_log(level, "Update %d for %s[%s]=%s: %s (%d)", call_id, a->id, peer, v->requested, pcmk_strerror(rc), rc);
         free(v->requested);
         v->requested = NULL;
         if (rc != pcmk_ok) {
             a->changed = TRUE; /* Attempt write out again */
         }
     }
   done:
     if(a && a->changed && election_state(writer) == election_won) {
         write_attribute(a);
     }
 }
 
 void
 write_attributes(bool all)
 {
     GHashTableIter iter;
     attribute_t *a = NULL;
 
     crm_debug("Writing out %s attributes", all? "all" : "changed");
     g_hash_table_iter_init(&iter, attributes);
     while (g_hash_table_iter_next(&iter, NULL, (gpointer *) & a)) {
         if (!all && a->unknown_peer_uuids) {
             // Try writing this attribute again, in case peer ID was learned
             a->changed = TRUE;
         }
 
         if(all || a->changed) {
             write_attribute(a);
         } else {
             crm_debug("Skipping unchanged attribute %s", a->id);
         }
     }
 }
 
 static void
 build_update_element(xmlNode *parent, attribute_t *a, const char *nodeid, const char *value)
 {
     const char *set = NULL;
     xmlNode *xml_obj = NULL;
 
     xml_obj = create_xml_node(parent, XML_CIB_TAG_STATE);
     crm_xml_add(xml_obj, XML_ATTR_ID, nodeid);
 
     xml_obj = create_xml_node(xml_obj, XML_TAG_TRANSIENT_NODEATTRS);
     crm_xml_add(xml_obj, XML_ATTR_ID, nodeid);
 
     xml_obj = create_xml_node(xml_obj, XML_TAG_ATTR_SETS);
     if (a->set) {
         crm_xml_set_id(xml_obj, "%s", a->set);
     } else {
         crm_xml_set_id(xml_obj, "%s-%s", XML_CIB_TAG_STATUS, nodeid);
     }
     set = ID(xml_obj);
 
     xml_obj = create_xml_node(xml_obj, XML_CIB_TAG_NVPAIR);
     if (a->uuid) {
         crm_xml_set_id(xml_obj, "%s", a->uuid);
     } else {
         crm_xml_set_id(xml_obj, "%s-%s", set, a->id);
     }
     crm_xml_add(xml_obj, XML_NVPAIR_ATTR_NAME, a->id);
 
     if(value) {
         crm_xml_add(xml_obj, XML_NVPAIR_ATTR_VALUE, value);
 
     } else {
         crm_xml_add(xml_obj, XML_NVPAIR_ATTR_VALUE, "");
         crm_xml_add(xml_obj, "__delete__", XML_NVPAIR_ATTR_VALUE);
     }
 }
 
 static void
 set_alert_attribute_value(GHashTable *t, attribute_value_t *v)
 {
     attribute_value_t *a_v = NULL;
     a_v = calloc(1, sizeof(attribute_value_t));
     CRM_ASSERT(a_v != NULL);
 
     a_v->nodeid = v->nodeid;
     a_v->nodename = strdup(v->nodename);
 
     if (v->current != NULL) {
         a_v->current = strdup(v->current);
     }
 
     g_hash_table_replace(t, a_v->nodename, a_v);
 }
 
 static void
 send_alert_attributes_value(attribute_t *a, GHashTable *t)
 {
     int rc = 0;
     attribute_value_t *at = NULL;
     GHashTableIter vIter;
 
     g_hash_table_iter_init(&vIter, t);
 
     while (g_hash_table_iter_next(&vIter, NULL, (gpointer *) & at)) {
         rc = attrd_send_attribute_alert(at->nodename, at->nodeid,
                                         a->id, at->current);
         crm_trace("Sent alerts for %s[%s]=%s: nodeid=%d rc=%d",
                   a->id, at->nodename, at->current, at->nodeid, rc);
     }
 }
 
 void
 write_attribute(attribute_t *a)
 {
     int private_updates = 0, cib_updates = 0;
     xmlNode *xml_top = NULL;
     attribute_value_t *v = NULL;
     GHashTableIter iter;
     enum cib_call_options flags = cib_quorum_override;
     GHashTable *alert_attribute_value = NULL;
 
     if (a == NULL) {
         return;
     }
 
     /* If this attribute will be written to the CIB ... */
     if (!a->is_private) {
 
         /* Defer the write if now's not a good time */
         if (the_cib == NULL) {
             crm_info("Write out of '%s' delayed: cib not connected", a->id);
             return;
 
         } else if (a->update && (a->update < last_cib_op_done)) {
             crm_info("Write out of '%s' continuing: update %d considered lost", a->id, a->update);
 
         } else if (a->update) {
             crm_info("Write out of '%s' delayed: update %d in progress", a->id, a->update);
             return;
 
         } else if (mainloop_timer_running(a->timer)) {
             crm_info("Write out of '%s' delayed: timer is running", a->id);
             return;
         }
 
         /* Initialize the status update XML */
         xml_top = create_xml_node(NULL, XML_CIB_TAG_STATUS);
     }
 
     /* Attribute will be written shortly, so clear changed flag */
     a->changed = FALSE;
 
     /* We will check all peers' uuids shortly, so initialize this to false */
     a->unknown_peer_uuids = FALSE;
 
     /* Make the table for the attribute trap */
     alert_attribute_value = g_hash_table_new_full(crm_strcase_hash,
                                                   crm_strcase_equal, NULL,
                                                   free_attribute_value);
 
     /* Iterate over each peer value of this attribute */
     g_hash_table_iter_init(&iter, a->values);
     while (g_hash_table_iter_next(&iter, NULL, (gpointer *) & v)) {
         crm_node_t *peer = crm_get_peer_full(v->nodeid, v->nodename, CRM_GET_PEER_ANY);
 
         /* If the value's peer info does not correspond to a peer, ignore it */
         if (peer == NULL) {
             crm_notice("Update error (peer not found): %s[%s]=%s failed (host=%p)",
                        a->id, v->nodename, v->current, peer);
             continue;
         }
 
         /* If we're just learning the peer's node id, remember it */
         if (peer->id && (v->nodeid == 0)) {
             crm_trace("Updating value's nodeid");
             v->nodeid = peer->id;
         }
 
         /* If this is a private attribute, no update needs to be sent */
         if (a->is_private) {
             private_updates++;
             continue;
         }
 
         /* If the peer is found, but its uuid is unknown, defer write */
         if (peer->uuid == NULL) {
             a->unknown_peer_uuids = TRUE;
             crm_notice("Update %s[%s]=%s postponed: unknown peer UUID, will retry if UUID is learned",
                        a->id, v->nodename, v->current, peer);
             continue;
         }
 
         /* Add this value to status update XML */
         crm_debug("Update: %s[%s]=%s (%s %u %u %s)", a->id, v->nodename,
                   v->current, peer->uuid, peer->id, v->nodeid, peer->uname);
         build_update_element(xml_top, a, peer->uuid, v->current);
         cib_updates++;
 
         /* Preservation of the attribute to transmit alert */
         set_alert_attribute_value(alert_attribute_value, v);
 
         free(v->requested);
         v->requested = NULL;
         if (v->current) {
             v->requested = strdup(v->current);
         } else {
             /* Older attrd versions don't know about the cib_mixed_update
              * flag so make sure it goes to the local cib which does
              */
             flags |= cib_mixed_update|cib_scope_local;
         }
     }
 
     if (private_updates) {
         crm_info("Processed %d private change%s for %s, id=%s, set=%s",
                  private_updates, ((private_updates == 1)? "" : "s"),
                  a->id, (a->uuid? a->uuid : "<n/a>"), a->set);
     }
     if (cib_updates) {
         crm_log_xml_trace(xml_top, __FUNCTION__);
 
         a->update = cib_internal_op(the_cib, CIB_OP_MODIFY, NULL, XML_CIB_TAG_STATUS, xml_top, NULL,
                                     flags, a->user);
 
         crm_info("Sent update %d with %d changes for %s, id=%s, set=%s",
                  a->update, cib_updates, a->id, (a->uuid? a->uuid : "<n/a>"), a->set);
 
         the_cib->cmds->register_callback_full(the_cib, a->update, 120, FALSE,
                                               strdup(a->id),
                                               "attrd_cib_callback",
                                               attrd_cib_callback, free);
         /* Transmit alert of the attribute */
         send_alert_attributes_value(a, alert_attribute_value);
 
     }
 
     g_hash_table_destroy(alert_attribute_value);
     free_xml(xml_top);
 }
diff --git a/cts/CTSaudits.py b/cts/CTSaudits.py
index c50d578fd6..8ab2b188f1 100755
--- a/cts/CTSaudits.py
+++ b/cts/CTSaudits.py
@@ -1,878 +1,877 @@
 '''CTS: Cluster Testing System: Audit module
  '''
 
 __copyright__ = '''
 Copyright (C) 2000, 2001,2005 Alan Robertson <alanr@unix.sh>
 Licensed under the GNU GPL.
 '''
 
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
 # as published by the Free Software Foundation; either version 2
 # of the License, or (at your option) any later version.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA.
 
 import time, re, uuid
 from watcher import LogWatcher
 
 
 class ClusterAudit:
 
     def __init__(self, cm):
         self.CM = cm
 
     def __call__(self):
          raise ValueError("Abstract Class member (__call__)")
     
     def is_applicable(self):
         '''Return TRUE if we are applicable in the current test configuration'''
         raise ValueError("Abstract Class member (is_applicable)")
         return 1
 
     def log(self, args):
         self.CM.log("audit: %s" % args)
 
     def debug(self, args):
         self.CM.debug("audit: %s" % args)
 
     def name(self):
          raise ValueError("Abstract Class member (name)")
 
 AllAuditClasses = [ ]
 
 
 class LogAudit(ClusterAudit):
 
     def name(self):
         return "LogAudit"
 
     def __init__(self, cm):
         self.CM = cm
         self.kinds = [ "combined syslog", "journal", "remote" ]
 
     def RestartClusterLogging(self, nodes=None):
         if not nodes:
             nodes = self.CM.Env["nodes"]
 
         self.CM.debug("Restarting logging on: %s" % repr(nodes))
 
         for node in nodes:
             if self.CM.Env["have_systemd"]:
                 if self.CM.rsh(node, "systemctl stop systemd-journald.socket") != 0:
                     self.CM.log ("ERROR: Cannot stop 'systemd-journald' on %s" % node)
                 if self.CM.rsh(node, "systemctl start systemd-journald.service") != 0:
                     self.CM.log ("ERROR: Cannot start 'systemd-journald' on %s" % node)
 
             if self.CM.rsh(node, "service %s restart" % self.CM.Env["syslogd"]) != 0:
                 self.CM.log ("ERROR: Cannot restart '%s' on %s" % (self.CM.Env["syslogd"], node))
 
     def TestLogging(self):
         patterns = []
         prefix   = "Test message from"
         suffix   = str(uuid.uuid4())
         watch    = {}
 
         for node in self.CM.Env["nodes"]:
             # Look for the node name in two places to make sure 
             # that syslog is logging with the correct hostname
             m = re.search("^([^.]+).*", node)
             if m:
                 simple = m.group(1)
             else:
                 simple = node
             patterns.append("%s.*%s %s %s" % (simple, prefix, node, suffix))
 
         watch_pref = self.CM.Env["LogWatcher"]
         if watch_pref == "any": 
             for k in self.kinds:
                 watch[k] = LogWatcher(self.CM.Env["LogFileName"], patterns, "LogAudit", 5, silent=True, hosts=self.CM.Env["nodes"], kind=k)
                 watch[k].setwatch()
         else:
             k = watch_pref
             watch[k] = LogWatcher(self.CM.Env["LogFileName"], patterns, "LogAudit", 5, silent=True, hosts=self.CM.Env["nodes"], kind=k)
             watch[k].setwatch()
 
         if watch_pref == "any": self.CM.log("Writing log with key: %s" % (suffix))
         for node in self.CM.Env["nodes"]:
             cmd = "logger -p %s.info %s %s %s" % (self.CM.Env["SyslogFacility"], prefix, node, suffix)
             if self.CM.rsh(node, cmd, synchronous=0, silent=True) != 0:
                 self.CM.log ("ERROR: Cannot execute remote command [%s] on %s" % (cmd, node))
 
         for k in self.kinds:
             if k in watch:
                 w = watch[k]
                 if watch_pref == "any": self.CM.log("Testing for %s logs" % (k))
                 w.lookforall(silent=True)
                 if not w.unmatched:
                     if watch_pref == "any": 
                         self.CM.log ("Continuing with %s-based log reader" % (w.kind))
                         self.CM.Env["LogWatcher"] = w.kind
                     return 1
 
         for k in list(watch.keys()):
             w = watch[k]
             if w.unmatched:
                 for regex in w.unmatched:
                     self.CM.log ("Test message [%s] not found in %s logs." % (regex, w.kind))
 
         return 0
 
     def __call__(self):
         max = 3
         attempt = 0
 
         self.CM.ns.WaitForAllNodesToComeUp(self.CM.Env["nodes"])
         while attempt <= max and self.TestLogging() == 0:
             attempt = attempt + 1
             self.RestartClusterLogging()
             time.sleep(60*attempt)
 
         if attempt > max:
             self.CM.log("ERROR: Cluster logging unrecoverable.")
             return 0
 
         return 1
     
     def is_applicable(self):
         if self.CM.Env["DoBSC"]:
             return 0
         if self.CM.Env["LogAuditDisabled"]:
             return 0
         return 1
 
 
 class DiskAudit(ClusterAudit):
 
     def name(self):
         return "DiskspaceAudit"
 
     def __init__(self, cm):
         self.CM = cm
 
     def __call__(self):
         result = 1
         dfcmd = "df -BM /var/log | tail -1 | awk '{print $(NF-1)\" \"$(NF-2)}' | tr -d 'M%'"
 
         self.CM.ns.WaitForAllNodesToComeUp(self.CM.Env["nodes"])
         for node in self.CM.Env["nodes"]:
             dfout = self.CM.rsh(node, dfcmd, 1)
             if not dfout:
                 self.CM.log ("ERROR: Cannot execute remote df command [%s] on %s" % (dfcmd, node))
             else:
                 try:
                     (used, remain) = dfout.split()
                     used_percent = int(used)
                     remaining_mb = int(remain)
                 except (ValueError, TypeError):
                     self.CM.log("Warning: df output '%s' from %s was invalid [%s, %s]"
                                 % (dfout, node, used, remain))
                 else:
                     if remaining_mb < 10 or used_percent > 95:
                         self.CM.log("CRIT: Out of log disk space on %s (%d%% / %dMB)"
                                     % (node, used_percent, remaining_mb))
                         result = None
                         if self.CM.Env["continue"] == 1:
                             answer = "Y"
                         else:
                             try:
                                 answer = raw_input('Continue? [nY]')
                             except EOFError as e:
                                 answer = "n"
 
                         if answer and answer == "n":
                             raise ValueError("Disk full on %s" % (node))
-                            ret = 0
 
                     elif remaining_mb < 100 or used_percent > 90:
                         self.CM.log("WARN: Low on log disk space (%dMB) on %s" % (remaining_mb, node))
         return result
 
     def is_applicable(self):
         if self.CM.Env["DoBSC"]:
             return 0
         return 1
 
 
 class FileAudit(ClusterAudit):
 
     def name(self):
         return "FileAudit"
 
     def __init__(self, cm):
         self.CM = cm
         self.known = []
 
     def __call__(self):
         result = 1
 
         self.CM.ns.WaitForAllNodesToComeUp(self.CM.Env["nodes"])
         for node in self.CM.Env["nodes"]:
 
             (rc, lsout) = self.CM.rsh(node, "ls -al /var/lib/pacemaker/cores/* | grep core.[0-9]", None)
             for line in lsout:
                 line = line.strip()
                 if line not in self.known:
                     result = 0
                     self.known.append(line)
                     self.CM.log("Warning: Pacemaker core file on %s: %s" % (node, line))
 
             (rc, lsout) = self.CM.rsh(node, "ls -al /var/lib/corosync | grep core.[0-9]", None)
             for line in lsout:
                 line = line.strip()
                 if line not in self.known:
                     result = 0
                     self.known.append(line)
                     self.CM.log("Warning: Corosync core file on %s: %s" % (node, line))
 
             if node in self.CM.ShouldBeStatus and self.CM.ShouldBeStatus[node] == "down":
                 clean = 0
                 (rc, lsout) = self.CM.rsh(node, "ls -al /dev/shm | grep qb-", None)
                 for line in lsout:
                     result = 0
                     clean = 1
                     self.CM.log("Warning: Stale IPC file on %s: %s" % (node, line))
 
                 if clean:
                     (rc, lsout) = self.CM.rsh(node, "ps axf | grep -e pacemaker -e corosync", None)
                     for line in lsout:
                         self.CM.debug("ps[%s]: %s" % (node, line))
 
                     self.CM.rsh(node, "rm -f /dev/shm/qb-*")
 
             else:
                 self.CM.debug("Skipping %s" % node)
 
         return result
     
     def is_applicable(self):
         return 1
 
 
 class AuditResource:
     def __init__(self, cm, line):
         fields = line.split()
         self.CM = cm
         self.line = line
         self.type = fields[1]
         self.id = fields[2]
         self.clone_id = fields[3]
         self.parent = fields[4]
         self.rprovider = fields[5]
         self.rclass = fields[6]
         self.rtype = fields[7]
         self.host = fields[8]
         self.needs_quorum = fields[9]
         self.flags = int(fields[10])
         self.flags_s = fields[11]
 
         if self.parent == "NA":
             self.parent = None
 
     def unique(self):
         if self.flags & int("0x00000020", 16):
             return 1
         return 0
 
     def orphan(self):
         if self.flags & int("0x00000001", 16):
             return 1
         return 0
 
     def managed(self):
         if self.flags & int("0x00000002", 16):
             return 1
         return 0
             
 
 class AuditConstraint:
     def __init__(self, cm, line):
         fields = line.split()
         self.CM = cm
         self.line = line
         self.type = fields[1]
         self.id = fields[2]
         self.rsc = fields[3]
         self.target = fields[4]
         self.score = fields[5]
         self.rsc_role = fields[6]
         self.target_role = fields[7]
 
         if self.rsc_role == "NA":
             self.rsc_role = None
         if self.target_role == "NA":
             self.target_role = None
 
 
 class PrimitiveAudit(ClusterAudit):
     def name(self):
         return "PrimitiveAudit"
 
     def __init__(self, cm):
         self.CM = cm
 
     def doResourceAudit(self, resource, quorum):
         rc = 1
         active = self.CM.ResourceLocation(resource.id)
 
         if len(active) == 1:
             if quorum:
                 self.debug("Resource %s active on %s" % (resource.id, repr(active)))
                 
             elif resource.needs_quorum == 1:
                 self.CM.log("Resource %s active without quorum: %s" 
                             % (resource.id, repr(active)))
                 rc = 0
 
         elif not resource.managed():
             self.CM.log("Resource %s not managed. Active on %s"
                         % (resource.id, repr(active)))
 
         elif not resource.unique():
             # TODO: Figure out a clever way to actually audit these resource types
             if len(active) > 1:
                 self.debug("Non-unique resource %s is active on: %s" 
                               % (resource.id, repr(active)))
             else:
                 self.debug("Non-unique resource %s is not active" % resource.id)
 
         elif len(active) > 1:
             self.CM.log("Resource %s is active multiple times: %s" 
                         % (resource.id, repr(active)))
             rc = 0
             
         elif resource.orphan():
             self.debug("Resource %s is an inactive orphan" % resource.id)
 
         elif len(self.inactive_nodes) == 0:
             self.CM.log("WARN: Resource %s not served anywhere" % resource.id)
             rc = 0
 
         elif self.CM.Env["warn-inactive"] == 1:
             if quorum or not resource.needs_quorum:
                 self.CM.log("WARN: Resource %s not served anywhere (Inactive nodes: %s)" 
                             % (resource.id, repr(self.inactive_nodes)))
             else:
                 self.debug("Resource %s not served anywhere (Inactive nodes: %s)" 
                               % (resource.id, repr(self.inactive_nodes)))
 
         elif quorum or not resource.needs_quorum:
             self.debug("Resource %s not served anywhere (Inactive nodes: %s)" 
                           % (resource.id, repr(self.inactive_nodes)))
 
         return rc
 
     def setup(self):
         self.target = None
         self.resources = []
         self.constraints = []
         self.active_nodes = []
         self.inactive_nodes = []
 
         for node in self.CM.Env["nodes"]:
             if self.CM.ShouldBeStatus[node] == "up":
                 self.active_nodes.append(node)
             else:
                 self.inactive_nodes.append(node)
 
         for node in self.CM.Env["nodes"]:
             if self.target == None and self.CM.ShouldBeStatus[node] == "up":
                 self.target = node
 
         if not self.target:
             # TODO: In Pacemaker 1.0 clusters we'll be able to run crm_resource 
             # with CIB_file=/path/to/cib.xml even when the cluster isn't running
             self.debug("No nodes active - skipping %s" % self.name())
             return 0
 
         (rc, lines) = self.CM.rsh(self.target, "crm_resource -c", None)
 
         for line in lines:
             if re.search("^Resource", line):
                 self.resources.append(AuditResource(self.CM, line))
             elif re.search("^Constraint", line):
                 self.constraints.append(AuditConstraint(self.CM, line))
             else:
                 self.CM.log("Unknown entry: %s" % line);
 
         return 1
 
     def __call__(self):
         rc = 1
                 
         if not self.setup():
             return 1
 
         quorum = self.CM.HasQuorum(None)
         for resource in self.resources:
             if resource.type == "primitive":
                 if self.doResourceAudit(resource, quorum) == 0:
                     rc = 0
         return rc
 
     def is_applicable(self):
         # @TODO Due to long-ago refactoring, this name test would never match,
         # so this audit (and those derived from it) would never run.
         # Uncommenting the next lines fixes the name test, but that then
         # exposes pre-existing bugs that need to be fixed.
         #if self.CM["Name"] == "crm-corosync":
         #    return 1
         return 0
 
 
 class GroupAudit(PrimitiveAudit):
     def name(self):
         return "GroupAudit"
 
     def __call__(self):
         rc = 1
         if not self.setup():
             return 1
 
         for group in self.resources:
             if group.type == "group":
                 first_match = 1
                 group_location = None
                 for child in self.resources:
                     if child.parent == group.id:
                         nodes = self.CM.ResourceLocation(child.id)
 
                         if first_match and len(nodes) > 0:
                             group_location = nodes[0]
 
                         first_match = 0
 
                         if len(nodes) > 1:
                             rc = 0
                             self.CM.log("Child %s of %s is active more than once: %s" 
                                         % (child.id, group.id, repr(nodes)))
 
                         elif len(nodes) == 0:
                             # Groups are allowed to be partially active
                             # However we do need to make sure later children aren't running
                             group_location = None
                             self.debug("Child %s of %s is stopped" % (child.id, group.id))
 
                         elif nodes[0] != group_location:  
                             rc = 0
                             self.CM.log("Child %s of %s is active on the wrong node (%s) expected %s" 
                                         % (child.id, group.id, nodes[0], group_location))
                         else:
                             self.debug("Child %s of %s is active on %s" % (child.id, group.id, nodes[0]))
 
         return rc
     
 
 class CloneAudit(PrimitiveAudit):
     def name(self):
         return "CloneAudit"
 
     def __call__(self):
         rc = 1
         if not self.setup():
             return 1
 
         for clone in self.resources:
             if clone.type == "clone":
                 for child in self.resources:
                     if child.parent == clone.id and child.type == "primitive":
                         self.debug("Checking child %s of %s..." % (child.id, clone.id))
                         # Check max and node_max
                         # Obtain with:
                         #    crm_resource -g clone_max --meta -r child.id
                         #    crm_resource -g clone_node_max --meta -r child.id
 
         return rc
     
 
 class ColocationAudit(PrimitiveAudit):
     def name(self):
         return "ColocationAudit"
 
     def crm_location(self, resource):
         (rc, lines) = self.CM.rsh(self.target, "crm_resource -W -r %s -Q"%resource, None)
         hosts = []
         if rc == 0:
             for line in lines:
                 fields = line.split()
                 hosts.append(fields[0])
 
         return hosts
 
     def __call__(self):
         rc = 1
         if not self.setup():
             return 1
 
         for coloc in self.constraints:
             if coloc.type == "rsc_colocation":
                 source = self.crm_location(coloc.rsc)
                 target = self.crm_location(coloc.target)
                 if len(source) == 0:
                     self.debug("Colocation audit (%s): %s not running" % (coloc.id, coloc.rsc))
                 else:
                     for node in source:
                         if not node in target:
                             rc = 0
                             self.CM.log("Colocation audit (%s): %s running on %s (not in %s)" 
                                         % (coloc.id, coloc.rsc, node, repr(target)))
                         else:
                             self.debug("Colocation audit (%s): %s running on %s (in %s)" 
                                           % (coloc.id, coloc.rsc, node, repr(target)))
 
         return rc
 
 
 class CrmdStateAudit(ClusterAudit):
     def __init__(self, cm):
         self.CM = cm
         self.Stats = {"calls":0
         ,        "success":0
         ,        "failure":0
         ,        "skipped":0
         ,        "auditfail":0}
 
     def has_key(self, key):
         return key in self.Stats
 
     def __setitem__(self, key, value):
         self.Stats[key] = value
         
     def __getitem__(self, key):
         return self.Stats[key]
 
     def incr(self, name):
         '''Increment (or initialize) the value associated with the given name'''
         if not name in self.Stats:
             self.Stats[name] = 0
         self.Stats[name] = self.Stats[name]+1
 
     def __call__(self):
         passed = 1
         up_are_down = 0
         down_are_up = 0
         unstable_list = []
 
         for node in self.CM.Env["nodes"]:
             should_be = self.CM.ShouldBeStatus[node]
             rc = self.CM.test_node_CM(node)
             if rc > 0:
                 if should_be == "down":
                     down_are_up = down_are_up + 1
                 if rc == 1:
                     unstable_list.append(node)
             elif should_be == "up":
                 up_are_down = up_are_down + 1
 
         if len(unstable_list) > 0:
             passed = 0
             self.CM.log("Cluster is not stable: %d (of %d): %s" 
                      % (len(unstable_list), self.CM.upcount(), repr(unstable_list)))
 
         if up_are_down > 0:
             passed = 0
             self.CM.log("%d (of %d) nodes expected to be up were down."
                      % (up_are_down, len(self.CM.Env["nodes"])))
 
         if down_are_up > 0:
             passed = 0
             self.CM.log("%d (of %d) nodes expected to be down were up." 
                      % (down_are_up, len(self.CM.Env["nodes"])))
             
         return passed
 
     def name(self):
         return "CrmdStateAudit"
     
     def is_applicable(self):
         # @TODO Due to long-ago refactoring, this name test would never match,
         # so this audit (and those derived from it) would never run.
         # Uncommenting the next lines fixes the name test, but that then
         # exposes pre-existing bugs that need to be fixed.
         #if self.CM["Name"] == "crm-corosync":
         #    return 1
         return 0
 
 
 class CIBAudit(ClusterAudit):
     def __init__(self, cm):
         self.CM = cm
         self.Stats = {"calls":0
         ,        "success":0
         ,        "failure":0
         ,        "skipped":0
         ,        "auditfail":0}
 
     def has_key(self, key):
         return key in self.Stats
 
     def __setitem__(self, key, value):
         self.Stats[key] = value
         
     def __getitem__(self, key):
         return self.Stats[key]
     
     def incr(self, name):
         '''Increment (or initialize) the value associated with the given name'''
         if not name in self.Stats:
             self.Stats[name] = 0
         self.Stats[name] = self.Stats[name]+1
 
     def __call__(self):
         passed = 1
         ccm_partitions = self.CM.find_partitions()
 
         if len(ccm_partitions) == 0:
             self.debug("\tNo partitions to audit")
             return 1
         
         for partition in ccm_partitions:
             self.debug("\tAuditing CIB consistency for: %s" % partition)
             partition_passed = 0
             if self.audit_cib_contents(partition) == 0:
                 passed = 0
         
         return passed
 
     def audit_cib_contents(self, hostlist):
         passed = 1
         node0 = None
         node0_xml = None
 
         partition_hosts = hostlist.split()
         for node in partition_hosts:
             node_xml = self.store_remote_cib(node, node0)
 
             if node_xml == None:
                 self.CM.log("Could not perform audit: No configuration from %s" % node)
                 passed = 0
                 
             elif node0 == None:
                 node0 = node
                 node0_xml = node_xml
                 
             elif node0_xml == None: 
                 self.CM.log("Could not perform audit: No configuration from %s" % node0)
                 passed = 0
                 
             else:
                 (rc, result) = self.CM.rsh(
                     node0, "crm_diff -VV -cf --new %s --original %s" % (node_xml, node0_xml), None)
                 
                 if rc != 0:
                     self.CM.log("Diff between %s and %s failed: %d" % (node0_xml, node_xml, rc))
                     passed = 0
                     
                 for line in result:
                     if not re.search("<diff/>", line):
                         passed = 0
                         self.debug("CibDiff[%s-%s]: %s" % (node0, node, line)) 
                     else:
                         self.debug("CibDiff[%s-%s] Ignoring: %s" % (node0, node, line)) 
                         
 #            self.CM.rsh(node0, "rm -f %s" % node_xml)                        
 #        self.CM.rsh(node0, "rm -f %s" % node0_xml) 
         return passed
                 
     def store_remote_cib(self, node, target):
         combined = ""
         filename = "/tmp/ctsaudit.%s.xml" % node
 
         if not target:
             target = node
 
         (rc, lines) = self.CM.rsh(node, self.CM["CibQuery"], None)
         if rc != 0:
             self.CM.log("Could not retrieve configuration")
             return None
 
         self.CM.rsh("localhost", "rm -f %s" % filename)
         for line in lines:
             self.CM.rsh("localhost", "echo \'%s\' >> %s" % (line[:-1], filename), silent=True)
 
         if self.CM.rsh.cp(filename, "root@%s:%s" % (target, filename), silent=True) != 0:
             self.CM.log("Could not store configuration")
             return None
         return filename
 
     def name(self):
         return "CibAudit"
     
     def is_applicable(self):
         # @TODO Due to long-ago refactoring, this name test would never match,
         # so this audit (and those derived from it) would never run.
         # Uncommenting the next lines fixes the name test, but that then
         # exposes pre-existing bugs that need to be fixed.
         #if self.CM["Name"] == "crm-corosync":
         #    return 1
         return 0
 
 
 class PartitionAudit(ClusterAudit):
     def __init__(self, cm):
         self.CM = cm
         self.Stats = {"calls":0
         ,        "success":0
         ,        "failure":0
         ,        "skipped":0
         ,        "auditfail":0}
         self.NodeEpoch = {}
         self.NodeState = {}
         self.NodeQuorum = {}
 
     def has_key(self, key):
         return self.Stats.has_key(key)
 
     def __setitem__(self, key, value):
         self.Stats[key] = value
         
     def __getitem__(self, key):
         return self.Stats[key]
     
     def incr(self, name):
         '''Increment (or initialize) the value associated with the given name'''
         if not name in self.Stats:
             self.Stats[name] = 0
         self.Stats[name] = self.Stats[name]+1
 
     def __call__(self):
         passed = 1
         ccm_partitions = self.CM.find_partitions()
 
         if ccm_partitions == None or len(ccm_partitions) == 0:
             return 1
 
         self.CM.cluster_stable(double_check=True)
 
         if len(ccm_partitions) != self.CM.partitions_expected:
             self.CM.log("ERROR: %d cluster partitions detected:" % len(ccm_partitions))
             passed = 0
             for partition in ccm_partitions:
                 self.CM.log("\t %s" % partition)
 
         for partition in ccm_partitions:
             partition_passed = 0
             if self.audit_partition(partition) == 0:
                 passed = 0
 
         return passed
 
     def trim_string(self, avalue):
         if not avalue:
             return None
         if len(avalue) > 1:
             return avalue[:-1]
 
     def trim2int(self, avalue):
         if not avalue:
             return None
         if len(avalue) > 1:
             return int(avalue[:-1])
 
     def audit_partition(self, partition):
         passed = 1
         dc_found = []
         dc_allowed_list = []
         lowest_epoch = None
         node_list = partition.split()
 
         self.debug("Auditing partition: %s" % (partition))
         for node in node_list:
             if self.CM.ShouldBeStatus[node] != "up":
                 self.CM.log("Warn: Node %s appeared out of nowhere" % (node))
                 self.CM.ShouldBeStatus[node] = "up"
                 # not in itself a reason to fail the audit (not what we're
                 #  checking for in this audit)
 
             self.NodeState[node]  = self.CM.rsh(node, self.CM["StatusCmd"] % node, 1)
             self.NodeEpoch[node] = self.CM.rsh(node, self.CM["EpochCmd"], 1)
             self.NodeQuorum[node] = self.CM.rsh(node, self.CM["QuorumCmd"], 1)
             
             self.debug("Node %s: %s - %s - %s." % (node, self.NodeState[node], self.NodeEpoch[node], self.NodeQuorum[node]))
             self.NodeState[node]  = self.trim_string(self.NodeState[node])
             self.NodeEpoch[node] = self.trim2int(self.NodeEpoch[node])
             self.NodeQuorum[node] = self.trim_string(self.NodeQuorum[node])
 
             if not self.NodeEpoch[node]:
                 self.CM.log("Warn: Node %s dissappeared: cant determin epoch" % (node))
                 self.CM.ShouldBeStatus[node] = "down"
                 # not in itself a reason to fail the audit (not what we're
                 #  checking for in this audit)
             elif lowest_epoch == None or self.NodeEpoch[node] < lowest_epoch:
                 lowest_epoch = self.NodeEpoch[node]
                 
         if not lowest_epoch:
             self.CM.log("Lowest epoch not determined in %s" % (partition))
             passed = 0
 
         for node in node_list:
             if self.CM.ShouldBeStatus[node] == "up":
                 if self.CM.is_node_dc(node, self.NodeState[node]):
                     dc_found.append(node)
                     if self.NodeEpoch[node] == lowest_epoch:
                         self.debug("%s: OK" % node)
                     elif not self.NodeEpoch[node]:
                         self.debug("Check on %s ignored: no node epoch" % node)
                     elif not lowest_epoch:
                         self.debug("Check on %s ignored: no lowest epoch" % node)
                     else:
                         self.CM.log("DC %s is not the oldest node (%d vs. %d)"
                             % (node, self.NodeEpoch[node], lowest_epoch))
                         passed = 0
 
         if len(dc_found) == 0:
             self.CM.log("DC not found on any of the %d allowed nodes: %s (of %s)"
                         % (len(dc_allowed_list), str(dc_allowed_list), str(node_list)))
 
         elif len(dc_found) > 1:
             self.CM.log("%d DCs (%s) found in cluster partition: %s"
                         % (len(dc_found), str(dc_found), str(node_list)))
             passed = 0
 
         if passed == 0:
             for node in node_list:
                 if self.CM.ShouldBeStatus[node] == "up":
                     self.CM.log("epoch %s : %s"  
                                 % (self.NodeEpoch[node], self.NodeState[node]))
 
         return passed
 
     def name(self):
         return "PartitionAudit"
     
     def is_applicable(self):
         # @TODO Due to long-ago refactoring, this name test would never match,
         # so this audit (and those derived from it) would never run.
         # Uncommenting the next lines fixes the name test, but that then
         # exposes pre-existing bugs that need to be fixed.
         #if self.CM["Name"] == "crm-corosync":
         #    return 1
         return 0
 
 AllAuditClasses.append(DiskAudit)
 AllAuditClasses.append(FileAudit)
 AllAuditClasses.append(LogAudit)
 AllAuditClasses.append(CrmdStateAudit)
 AllAuditClasses.append(PartitionAudit)
 AllAuditClasses.append(PrimitiveAudit)
 AllAuditClasses.append(GroupAudit)
 AllAuditClasses.append(CloneAudit)
 AllAuditClasses.append(ColocationAudit)
 AllAuditClasses.append(CIBAudit)
 
 
 def AuditList(cm):
     result = []
     for auditclass in AllAuditClasses:
         a = auditclass(cm)
         if a.is_applicable():
             result.append(a)
     return result
diff --git a/cts/environment.py b/cts/environment.py
index 4b95b13d41..a2bd43d25f 100644
--- a/cts/environment.py
+++ b/cts/environment.py
@@ -1,649 +1,648 @@
 '''
 Classes related to producing and searching logs
 '''
 
 __copyright__='''
 Copyright (C) 2014 Andrew Beekhof <andrew@beekhof.net>
 Licensed under the GNU GPL.
 '''
 
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
 # as published by the Free Software Foundation; either version 2
 # of the License, or (at your option) any later version.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA.
 
 import sys, time, os, socket, random
 
 from cts.remote import *
 
 class Environment:
 
     def __init__(self, args):
         self.data = {}
         self.Nodes = []
 
         self["DeadTime"] = 300
         self["StartTime"] = 300
         self["StableTime"] = 30
         self["tests"] = []
         self["IPagent"] = "IPaddr2"
         self["DoStandby"] = 1
         self["DoFencing"] = 1
         self["XmitLoss"] = "0.0"
         self["RecvLoss"] = "0.0"
         self["ClobberCIB"] = 0
         self["CIBfilename"] = None
         self["CIBResource"] = 0
         self["DoBSC"]    = 0
         self["oprofile"] = []
         self["warn-inactive"] = 0
         self["ListTests"] = 0
         self["benchmark"] = 0
         self["LogWatcher"] = "any"
         self["SyslogFacility"] = "daemon"
         self["LogFileName"] = "/var/log/messages"
         self["Schema"] = "pacemaker-2.0"
         self["Stack"] = "corosync"
         self["stonith-type"] = "external/ssh"
         self["stonith-params"] = "hostlist=all,livedangerously=yes"
         self["notification-agent"] = "/var/lib/pacemaker/notify.sh"
         self["notification-recipient"] = "/var/lib/pacemaker/notify.log"
         self["loop-minutes"] = 60
         self["valgrind-prefix"] = None
         self["valgrind-procs"] = "attrd cib crmd lrmd pengine stonith-ng"
         self["valgrind-opts"] = """--leak-check=full --show-reachable=yes --trace-children=no --num-callers=25 --gen-suppressions=all --suppressions="""+CTSvars.CTS_home+"""/cts.supp"""
 
         self["experimental-tests"] = 0
         self["container-tests"] = 0
         self["valgrind-tests"] = 0
         self["unsafe-tests"] = 1
         self["loop-tests"] = 1
         self["scenario"] = "random"
         self["stats"] = 0
         self["docker"] = 0
         self["continue"] = 0
 
         self.RandomGen = random.Random()
         self.logger = LogFactory()
 
         self.SeedRandom()
         self.rsh = RemoteFactory().getInstance()
 
         self.target = "localhost"
 
         self.parse_args(args)
         self.discover()
         self.validate()
 
     def SeedRandom(self, seed=None):
         if not seed:
             seed = int(time.time())
 
         self["RandSeed"] = seed
         self.RandomGen.seed(str(seed))
 
     def dump(self):
         keys = []
         for key in list(self.data.keys()):
             keys.append(key)
 
         keys.sort()
         for key in keys:
             self.logger.debug("Environment["+key+"]:\t"+str(self[key]))
 
     def keys(self):
         return self.data.keys()
 
     def has_key(self, key):
         if key == "nodes":
             return True
 
         return key in self.data
 
     def __getitem__(self, key):
         if str(key) == "0":
             raise ValueError("Bad call to 'foo in X', should reference 'foo in X.keys()' instead")
 
         if key == "nodes":
             return self.Nodes
 
         elif key == "Name":
             return self.get_stack_short()
 
         elif key in self.data:
             return self.data[key]
 
         else:
             return None
 
     def __setitem__(self, key, value):
         if key == "Stack":
             self.set_stack(value)
 
         elif key == "node-limit":
             self.data[key] = value
             self.filter_nodes()
 
         elif key == "nodes":
             self.Nodes = []
             for node in value:
                 # I don't think I need the IP address, etc. but this validates
                 # the node name against /etc/hosts and/or DNS, so it's a
                 # GoodThing(tm).
                 try:
                     n = node.strip()
                     if self.data["docker"] == 0:
                         socket.gethostbyname_ex(n)
 
                     self.Nodes.append(n) 
                 except:
                     self.logger.log(node+" not found in DNS... aborting")
                     raise
 
             self.filter_nodes()
 
         else:
             self.data[key] = value
 
     def RandomNode(self):
         '''Choose a random node from the cluster'''
         return self.RandomGen.choice(self["nodes"])
 
     def set_stack(self, name):
         # Normalize stack names
         if name == "corosync" or name == "cs" or name == "mcp":
             self.data["Stack"] = "corosync 2.x"
 
         else:
             raise ValueError("Unknown stack: "+name)
-            sys.exit(1)
 
     def get_stack_short(self):
         # Create the Cluster Manager object
         if not "Stack" in self.data:
             return "unknown"
 
         elif self.data["Stack"] == "corosync 2.x":
             if self["docker"]:
                 return "crm-corosync-docker"
             else:
                 return "crm-corosync"
 
         else:
             LogFactory().log("Unknown stack: "+self["stack"])
             raise ValueError("Unknown stack: "+self["stack"])
 
     def detect_syslog(self):
         # Detect syslog variant
         if not "syslogd" in self.data:
             if self["have_systemd"]:
                 # Systemd
                 self["syslogd"] = self.rsh(self.target, "systemctl list-units | grep syslog.*\.service.*active.*running | sed 's:.service.*::'", stdout=1).strip()
             else:
                 # SYS-V
                 self["syslogd"] = self.rsh(self.target, "chkconfig --list | grep syslog.*on | awk '{print $1}' | head -n 1", stdout=1).strip()
 
             if not "syslogd" in self.data or not self["syslogd"]:
                 # default
                 self["syslogd"] = "rsyslog"
 
     def detect_at_boot(self):
         # Detect if the cluster starts at boot
         if not "at-boot" in self.data:
             atboot = 0
 
             if self["have_systemd"]:
             # Systemd
                 atboot = atboot or not self.rsh(self.target, "systemctl is-enabled corosync.service")
                 atboot = atboot or not self.rsh(self.target, "systemctl is-enabled pacemaker.service")
             else:
                 # SYS-V
                 atboot = atboot or not self.rsh(self.target, "chkconfig --list | grep -e corosync.*on -e pacemaker.*on")
 
             self["at-boot"] = atboot
 
     def detect_ip_offset(self):
         # Try to determin an offset for IPaddr resources
         if self["CIBResource"] and not "IPBase" in self.data:
             network=self.rsh(self.target, "ip addr | grep inet | grep -v -e link -e inet6 -e '/32' -e ' lo' | awk '{print $2}'", stdout=1).strip()
             self["IPBase"] = self.rsh(self.target, "nmap -sn -n %s | grep 'scan report' | awk '{print $NF}' | sed 's:(::' | sed 's:)::' | sort -V | tail -n 1" % network, stdout=1).strip()
             if not self["IPBase"]:
                 self["IPBase"] = " fe80::1234:56:7890:1000"
                 self.logger.log("Could not determine an offset for IPaddr resources.  Perhaps nmap is not installed on the nodes.")
                 self.logger.log("Defaulting to '%s', use --test-ip-base to override" % self["IPBase"])
 
             elif int(self["IPBase"].split('.')[3]) >= 240:
                 self.logger.log("Could not determine an offset for IPaddr resources. Upper bound is too high: %s %s"
                                 % (self["IPBase"], self["IPBase"].split('.')[3]))
                 self["IPBase"] = " fe80::1234:56:7890:1000"
                 self.logger.log("Defaulting to '%s', use --test-ip-base to override" % self["IPBase"])
 
     def filter_nodes(self):
         if self["node-limit"] > 0:
             if len(self["nodes"]) > self["node-limit"]:
                 self.logger.log("Limiting the number of nodes configured=%d (max=%d)"
                                 %(len(self["nodes"]), self["node-limit"]))
                 while len(self["nodes"]) > self["node-limit"]:
                     self["nodes"].pop(len(self["nodes"])-1)
 
     def validate(self):
         if len(self["nodes"]) < 1:
             print("No nodes specified!")
             sys.exit(1)
 
     def discover(self):
         self.target = random.Random().choice(self["nodes"])
 
         master = socket.gethostname()
 
         # Use the IP where possible to avoid name lookup failures
         for ip in socket.gethostbyname_ex(master)[2]:
             if ip != "127.0.0.1":
                 master = ip
                 break;
         self["cts-master"] = master
 
         if not "have_systemd" in self.data:
             self["have_systemd"] = not self.rsh(self.target, "systemctl list-units")
         
         self.detect_syslog()
         self.detect_at_boot()
         self.detect_ip_offset()
 
         self.validate()
 
     def parse_args(self, args):
         skipthis=None
 
         if not args:
             args=sys.argv[1:]
 
         for i in range(0, len(args)):
             if skipthis:
                 skipthis=None
                 continue
 
             elif args[i] == "-l" or args[i] == "--limit-nodes":
                 skipthis=1
                 self["node-limit"] = int(args[i+1])
 
             elif args[i] == "-r" or args[i] == "--populate-resources":
                 self["CIBResource"] = 1
                 self["ClobberCIB"] = 1
 
             elif args[i] == "--outputfile":
                 skipthis=1
                 self["OutputFile"] = args[i+1]
                 LogFactory().add_file(self["OutputFile"])
 
             elif args[i] == "-L" or args[i] == "--logfile":
                 skipthis=1
                 self["LogWatcher"] = "remote"
                 self["LogAuditDisabled"] = 1
                 self["LogFileName"] = args[i+1]
 
             elif args[i] == "--ip" or args[i] == "--test-ip-base":
                 skipthis=1
                 self["IPBase"] = args[i+1]
                 self["CIBResource"] = 1
                 self["ClobberCIB"] = 1
 
             elif args[i] == "--oprofile":
                 skipthis=1
                 self["oprofile"] = args[i+1].split(' ')
 
             elif args[i] == "--trunc":
                 self["TruncateLog"]=1
 
             elif args[i] == "--list-tests" or args[i] == "--list" :
                 self["ListTests"]=1
 
             elif args[i] == "--benchmark":
                 self["benchmark"]=1
 
             elif args[i] == "--bsc":
                 self["DoBSC"] = 1
                 self["scenario"] = "basic-sanity"
 
             elif args[i] == "--qarsh":
                 RemoteFactory().enable_qarsh()
 
             elif args[i] == "--docker":
                 self["docker"] = 1
                 RemoteFactory().enable_docker()
             elif args[i] == "--yes" or args[i] == "-y":
                 self["continue"] = 1
             elif args[i] == "--stonith" or args[i] == "--fencing":
                 skipthis=1
                 if args[i+1] == "1" or args[i+1] == "yes":
                     self["DoFencing"]=1
                 elif args[i+1] == "0" or args[i+1] == "no":
                     self["DoFencing"]=0
                 elif args[i+1] == "phd":
                     self["DoStonith"]=1
                     self["stonith-type"] = "fence_phd_kvm"
                 elif args[i+1] == "rhcs" or args[i+1] == "xvm" or args[i+1] == "virt":
                     self["DoStonith"]=1
                     self["stonith-type"] = "fence_xvm"
                 elif args[i+1] == "docker":
                     self["DoStonith"]=1
                     self["stonith-type"] = "fence_docker_cts"
                 elif args[i+1] == "scsi":
                     self["DoStonith"]=1
                     self["stonith-type"] = "fence_scsi"
                 elif args[i+1] == "ssh" or args[i+1] == "lha":
                     self["DoStonith"]=1
                     self["stonith-type"] = "external/ssh"
                     self["stonith-params"] = "hostlist=all,livedangerously=yes"
                 elif args[i+1] == "north":
                     self["DoStonith"]=1
                     self["stonith-type"] = "fence_apc"
                     self["stonith-params"] = "ipaddr=north-apc,login=apc,passwd=apc,pcmk_host_map=north-01:2;north-02:3;north-03:4;north-04:5;north-05:6;north-06:7;north-07:9;north-08:10;north-09:11;north-10:12;north-11:13;north-12:14;north-13:15;north-14:18;north-15:17;north-16:19;"
                 elif args[i+1] == "south":
                     self["DoStonith"]=1
                     self["stonith-type"] = "fence_apc"
                     self["stonith-params"] = "ipaddr=south-apc,login=apc,passwd=apc,pcmk_host_map=south-01:2;south-02:3;south-03:4;south-04:5;south-05:6;south-06:7;south-07:9;south-08:10;south-09:11;south-10:12;south-11:13;south-12:14;south-13:15;south-14:18;south-15:17;south-16:19;"
                 elif args[i+1] == "east":
                     self["DoStonith"]=1
                     self["stonith-type"] = "fence_apc"
                     self["stonith-params"] = "ipaddr=east-apc,login=apc,passwd=apc,pcmk_host_map=east-01:2;east-02:3;east-03:4;east-04:5;east-05:6;east-06:7;east-07:9;east-08:10;east-09:11;east-10:12;east-11:13;east-12:14;east-13:15;east-14:18;east-15:17;east-16:19;"
                 elif args[i+1] == "west":
                     self["DoStonith"]=1
                     self["stonith-type"] = "fence_apc"
                     self["stonith-params"] = "ipaddr=west-apc,login=apc,passwd=apc,pcmk_host_map=west-01:2;west-02:3;west-03:4;west-04:5;west-05:6;west-06:7;west-07:9;west-08:10;west-09:11;west-10:12;west-11:13;west-12:14;west-13:15;west-14:18;west-15:17;west-16:19;"
                 elif args[i+1] == "openstack":
                     self["DoStonith"]=1
                     self["stonith-type"] = "fence_openstack"
                     
                     print("Obtaining OpenStack credentials from the current environment")
                     self["stonith-params"] = "region=%s,tenant=%s,auth=%s,user=%s,password=%s" % (
                         os.environ['OS_REGION_NAME'],
                         os.environ['OS_TENANT_NAME'],
                         os.environ['OS_AUTH_URL'],
                         os.environ['OS_USERNAME'],
                         os.environ['OS_PASSWORD']
                     )
                     
                 elif args[i+1] == "rhevm":
                     self["DoStonith"]=1
                     self["stonith-type"] = "fence_rhevm"
                     
                     print("Obtaining RHEV-M credentials from the current environment")
                     self["stonith-params"] = "login=%s,passwd=%s,ipaddr=%s,ipport=%s,ssl=1,shell_timeout=10" % (
                         os.environ['RHEVM_USERNAME'],
                         os.environ['RHEVM_PASSWORD'],
                         os.environ['RHEVM_SERVER'],
                         os.environ['RHEVM_PORT'],
                     )
                     
                 else:
                     self.usage(args[i+1])
 
             elif args[i] == "--stonith-type":
                 self["stonith-type"] = args[i+1]
                 skipthis=1
 
             elif args[i] == "--stonith-args":
                 self["stonith-params"] = args[i+1]
                 skipthis=1
 
             elif args[i] == "--standby":
                 skipthis=1
                 if args[i+1] == "1" or args[i+1] == "yes":
                     self["DoStandby"] = 1
                 elif args[i+1] == "0" or args[i+1] == "no":
                     self["DoStandby"] = 0
                 else:
                     self.usage(args[i+1])
 
             elif args[i] == "--clobber-cib" or args[i] == "-c":
                 self["ClobberCIB"] = 1
                 
             elif args[i] == "--cib-filename":
                 skipthis=1
                 self["CIBfilename"] = args[i+1]
 
             elif args[i] == "--xmit-loss":
                 try:
                     float(args[i+1])
                 except ValueError:
                     print("--xmit-loss parameter should be float")
                     self.usage(args[i+1])
                 skipthis=1
                 self["XmitLoss"] = args[i+1]
 
             elif args[i] == "--recv-loss":
                 try:
                     float(args[i+1])
                 except ValueError:
                     print("--recv-loss parameter should be float")
                     self.usage(args[i+1])
                 skipthis=1
                 self["RecvLoss"] = args[i+1]
 
             elif args[i] == "--choose":
                 skipthis=1
                 self["tests"].append(args[i+1])
                 self["scenario"] = "sequence"
 
             elif args[i] == "--nodes":
                 skipthis=1
                 self["nodes"] = args[i+1].split(' ')
 
             elif args[i] == "-g" or args[i] == "--group" or args[i] == "--dsh-group":
                 skipthis=1
                 self["OutputFile"] = "%s/cluster-%s.log" % (os.environ['HOME'], args[i+1])
                 LogFactory().add_file(self["OutputFile"], "CTS")
 
                 dsh_file = "%s/.dsh/group/%s" % (os.environ['HOME'], args[i+1])
 
                 # Hacks to make my life easier
                 if args[i+1] == "virt1":
                     self["Stack"] = "corosync"
                     self["DoStonith"]=1
                     self["stonith-type"] = "fence_xvm"
                     self["stonith-params"] = "delay=0"
                     self["IPBase"] = " fe80::1234:56:7890:1000"
 
                 elif args[i+1] == "east16" or args[i+1] == "nsew":
                     self["Stack"] = "corosync"
                     self["DoStonith"]=1
                     self["stonith-type"] = "fence_apc"
                     self["stonith-params"] = "ipaddr=east-apc,login=apc,passwd=apc,pcmk_host_map=east-01:2;east-02:3;east-03:4;east-04:5;east-05:6;east-06:7;east-07:9;east-08:10;east-09:11;east-10:12;east-11:13;east-12:14;east-13:15;east-14:18;east-15:17;east-16:19;"
                     self["IPBase"] = " fe80::1234:56:7890:2000"
 
                     if args[i+1] == "east16":
                         # Requires newer python than available via nsew
                         self["IPagent"] = "Dummy"
 
                 elif args[i+1] == "corosync8":
                     self["Stack"] = "corosync"
                     self["DoStonith"]=1
                     self["stonith-type"] = "fence_rhevm"
 
                     print("Obtaining RHEV-M credentials from the current environment")
                     self["stonith-params"] = "login=%s,passwd=%s,ipaddr=%s,ipport=%s,ssl=1,shell_timeout=10" % (
                         os.environ['RHEVM_USERNAME'],
                         os.environ['RHEVM_PASSWORD'],
                         os.environ['RHEVM_SERVER'],
                         os.environ['RHEVM_PORT'],
                    )
                     self["IPBase"] = " fe80::1234:56:7890:3000"
 
                 if os.path.isfile(dsh_file):
                     self["nodes"] = []
                     f = open(dsh_file, 'r')
                     for line in f:
                         l = line.strip().rstrip()
                         if not l.startswith('#'):
                             self["nodes"].append(l)
                     f.close()
 
                 else:
                     print("Unknown DSH group: %s" % args[i+1])
 
             elif args[i] == "--syslog-facility" or args[i] == "--facility":
                 skipthis=1
                 self["SyslogFacility"] = args[i+1]
                 
             elif args[i] == "--seed":
                 skipthis=1
                 self.SeedRandom(args[i+1])
 
             elif args[i] == "--warn-inactive":
                 self["warn-inactive"] = 1
 
             elif args[i] == "--schema":
                 skipthis=1
                 self["Schema"] = args[i+1]
 
             elif args[i] == "--at-boot" or args[i] == "--cluster-starts-at-boot":
                 skipthis=1
                 if args[i+1] == "1" or args[i+1] == "yes":
                     self["at-boot"] = 1
                 elif args[i+1] == "0" or args[i+1] == "no":
                     self["at-boot"] = 0
                 else:
                     self.usage(args[i+1])
 
             elif args[i] == "--stack":
                 if args[i+1] == "fedora" or args[i+1] == "fedora-17" or args[i+1] == "fedora-18":
                     self["Stack"] = "corosync"
                 elif args[i+1] == "rhel-7":
                     self["Stack"] = "corosync"
                 else:
                     self["Stack"] = args[i+1]
                 skipthis=1
 
             elif args[i] == "--once":
                 self["scenario"] = "all-once"
 
             elif args[i] == "--boot":
                 self["scenario"] = "boot"
 
             elif args[i] == "--notification-agent":
                 self["notification-agent"] = args[i+1]
                 skipthis = 1
 
             elif args[i] == "--notification-recipient":
                 self["notification-recipient"] = args[i+1]
                 skipthis = 1
 
             elif args[i] == "--valgrind-tests":
                 self["valgrind-tests"] = 1
 
             elif args[i] == "--valgrind-procs":
                 self["valgrind-procs"] = args[i+1]
                 skipthis = 1
 
             elif args[i] == "--no-loop-tests":
                 self["loop-tests"] = 0
 
             elif args[i] == "--loop-minutes":
                 skipthis=1
                 try:
                     self["loop-minutes"]=int(args[i+1])
                 except ValueError:
                     self.usage(args[i])
 
             elif args[i] == "--no-unsafe-tests":
                 self["unsafe-tests"] = 0
 
             elif args[i] == "--experimental-tests":
                 self["experimental-tests"] = 1
 
             elif args[i] == "--container-tests":
                 self["container-tests"] = 1
 
             elif args[i] == "--set":
                 skipthis=1
                 (name, value) = args[i+1].split('=')
                 self[name] = value
                 print("Setting %s = %s" % (name, value))
                 
             elif args[i] == "--help":
                 self.usage(args[i], 0)
 
             elif args[i] == "--":
                 break
 
             else:
                 try:
                     NumIter=int(args[i])
                     self["iterations"] = NumIter
                 except ValueError:
                     self.usage(args[i])
 
     def usage(self, arg, status=1):
         if status:
             print("Illegal argument %s" % arg)
         print("usage: " + sys.argv[0] +" [options] number-of-iterations")
         print("\nCommon options: ")
         print("\t [--nodes 'node list']        list of cluster nodes separated by whitespace")
         print("\t [--group | -g 'name']        use the nodes listed in the named DSH group (~/.dsh/groups/$name)")
         print("\t [--limit-nodes max]          only use the first 'max' cluster nodes supplied with --nodes")
         print("\t [--stack corosync]           which cluster stack is installed")
         print("\t [--list-tests]               list the valid tests")
         print("\t [--benchmark]                add the timing information")
         print("\t ")
         print("Options that CTS will usually auto-detect correctly: ")
         print("\t [--logfile path]             where should the test software look for logs from cluster nodes")
         print("\t [--syslog-facility name]     which syslog facility should the test software log to")
         print("\t [--at-boot (1|0)]            does the cluster software start at boot time")
         print("\t [--test-ip-base ip]          offset for generated IP address resources")
         print("\t ")
         print("Options for release testing: ")
         print("\t [--populate-resources | -r]  generate a sample configuration")
         print("\t [--choose name]              run only the named test")
         print("\t [--stonith (1 | 0 | yes | no | rhcs | ssh)]")
         print("\t [--once]                     run all valid tests once")
         print("\t ")
         print("Additional (less common) options: ")
         print("\t [--clobber-cib | -c ]        erase any existing configuration")
         print("\t [--outputfile path]          optional location for the test software to write logs to")
         print("\t [--trunc]                    truncate logfile before starting")
         print("\t [--xmit-loss lost-rate(0.0-1.0)]")
         print("\t [--recv-loss lost-rate(0.0-1.0)]")
         print("\t [--standby (1 | 0 | yes | no)]")
         print("\t [--fencing (1 | 0 | yes | no | rhcs | lha | openstack )]")
         print("\t [--stonith-type type]")
         print("\t [--stonith-args name=value]")
         print("\t [--bsc]")
         print("\t [--notification-agent path]  script to configure for Pacemaker alerts")
         print("\t [--notification-recipient r] recipient to pass to alert script")
         print("\t [--no-loop-tests]            don't run looping/time-based tests")
         print("\t [--no-unsafe-tests]          don't run tests that are unsafe for use with ocfs2/drbd")
         print("\t [--valgrind-tests]           include tests using valgrind")
         print("\t [--experimental-tests]       include experimental tests")
         print("\t [--container-tests]          include pacemaker_remote tests that run in lxc container resources")
         print("\t [--oprofile 'node list']     list of cluster nodes to run oprofile on]")
         print("\t [--qarsh]                    use the QARSH backdoor to access nodes instead of SSH")
         print("\t [--docker]                   Indicates nodes are docker nodes.")
         print("\t [--seed random_seed]")
         print("\t [--set option=value]")
         print("\t [--yes | -y]                 continue to run cts when there is an interaction whether to continue running pacemaker-cts")
         print("\t ")
         print("\t Example: ")
         print("\t    python sys.argv[0] -g virt1 --stack cs -r --stonith ssh --schema pacemaker-1.0 500")
 
         sys.exit(status)
 
 class EnvFactory:
     instance = None
     def __init__(self):
         pass
 
     def getInstance(self, args=None):
         if not EnvFactory.instance:
             EnvFactory.instance = Environment(args)
         return EnvFactory.instance
diff --git a/cts/remote.py b/cts/remote.py
index 8c3691826e..7cef40e3bd 100644
--- a/cts/remote.py
+++ b/cts/remote.py
@@ -1,281 +1,278 @@
 '''
 Classes related to running command remotely
 '''
 
 __copyright__='''
 Copyright (C) 2014 Andrew Beekhof <andrew@beekhof.net>
 Licensed under the GNU GPL.
 '''
 
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
 # as published by the Free Software Foundation; either version 2
 # of the License, or (at your option) any later version.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA.
 
 import string, sys, re, os
 
 from subprocess import Popen,PIPE
 from threading import Thread
 
 pdir=os.path.dirname(sys.path[0])
 sys.path.insert(0, pdir) # So that things work from the source directory
 
 from cts.CTSvars import *
 from cts.logging import *
 
 trace_rsh=None
 trace_lw=None
 
 class AsyncWaitProc(Thread):
     def __init__(self, proc, node, command, completionDelegate=None):
         self.proc = proc
         self.node = node
         self.command = command
         self.logger = LogFactory()
         self.delegate = completionDelegate;
         Thread.__init__(self)
 
     def run(self):
         outLines = None
         errLines = None
         self.logger.debug("cmd: async: target=%s, pid=%d: %s" % (self.node, self.proc.pid, self.command))
 
         self.proc.wait()
         self.logger.debug("cmd: pid %d returned %d" % (self.proc.pid, self.proc.returncode))
 
         if self.proc.stderr:
             errLines = self.proc.stderr.readlines()
             self.proc.stderr.close()
             for line in errLines:
                 self.logger.debug("cmd: stderr[%d]: %s" % (self.proc.pid, line))
 
         if self.proc.stdout:
             outLines = self.proc.stdout.readlines()
             self.proc.stdout.close()
 #            for line in outLines:
 #                self.logger.debug("cmd: stdout[%d]: %s" % (self.proc.pid, line))
 
         if self.delegate:
             self.delegate.async_complete(self.proc.pid, self.proc.returncode, outLines, errLines)
 
 class AsyncRemoteCmd(Thread):
     def __init__(self, node, command, completionDelegate=None):
         self.proc = None
         self.node = node
         self.command = command
         self.logger = LogFactory()
         self.delegate = completionDelegate;
         Thread.__init__(self)
 
     def run(self):
         outLines = None
         errLines = None
 
         self.proc = Popen(self.command, stdout = PIPE, stderr = PIPE, close_fds = True, shell = True)
 
         self.logger.debug("cmd: async: target=%s, pid=%d: %s" % (self.node, self.proc.pid, self.command))
         self.proc.wait()
         self.logger.debug("cmd: pid %d returned %d to %s" % (self.proc.pid, self.proc.returncode, repr(self.delegate)))
 
         if self.proc.stderr:
             errLines = self.proc.stderr.readlines()
             self.proc.stderr.close()
             for line in errLines:
                 self.logger.debug("cmd: stderr[%d]: %s" % (self.proc.pid, line))
 
         if self.proc.stdout:
             outLines = self.proc.stdout.readlines()
             self.proc.stdout.close()
 #            for line in outLines:
 #                self.logger.log("cmd: stdout[%d]: %s" % (self.proc.pid, line))
 
         if self.delegate:
             self.delegate.async_complete(self.proc.pid, self.proc.returncode, outLines, errLines)
 
 class RemotePrimitives:
     def __init__(self, Command=None, CpCommand=None):
         if CpCommand:
             self.CpCommand = CpCommand
         else:
             #        -B: batch mode, -q: no stats (quiet)
             self.CpCommand = "scp -B -q"
 
         if Command:
             self.Command = Command
         else:
             #   -n: no stdin, -x: no X11,
             #   -o ServerAliveInterval=5 disconnect after 3*5s if the server stops responding
             self.Command = "ssh -l root -n -x -o ServerAliveInterval=5 -o ConnectTimeout=10 -o TCPKeepAlive=yes -o ServerAliveCountMax=3 "
 
 class RemoteExec:
     '''This is an abstract remote execution class.  It runs a command on another
        machine - somehow.  The somehow is up to us.  This particular
        class uses ssh.
        Most of the work is done by fork/exec of ssh or scp.
     '''
 
     def __init__(self, rsh, silent=False):
         self.async = []
         self.rsh = rsh
         self.silent = silent
         self.logger = LogFactory()
 
         if trace_rsh:
             self.silent = False
 
         self.OurNode=string.lower(os.uname()[1])
 
     def _fixcmd(self, cmd):
         return re.sub("\'", "'\\''", cmd)
 
     def _cmd(self, *args):
 
         '''Compute the string that will run the given command on the
         given remote system'''
 
         args= args[0]
         sysname = args[0]
         command = args[1]
 
         #print("sysname: %s, us: %s" % (sysname, self.OurNode))
         if sysname == None or string.lower(sysname) == self.OurNode or sysname == "localhost":
             ret = command
         else:
             ret = self.rsh.Command + " " + sysname + " '" + self._fixcmd(command) + "'"
         #print ("About to run %s\n" % ret)
         return ret
 
     def log(self, args):
         if not self.silent:
             self.logger.log(args)
 
     def debug(self, args):
         if not self.silent:
             self.logger.debug(args)
 
     def call_async(self, node, command, completionDelegate=None):
         #if completionDelegate: print("Waiting for %d on %s: %s" % (proc.pid, node, command))
         aproc = AsyncRemoteCmd(node, self._cmd([node, command]), completionDelegate=completionDelegate)
         aproc.start()
         return aproc
 
 
     def __call__(self, node, command, stdout=0, synchronous=1, silent=False, blocking=True, completionDelegate=None):
         '''Run the given command on the given remote system
         If you call this class like a function, this is the function that gets
         called.  It just runs it roughly as though it were a system() call
         on the remote machine.  The first argument is name of the machine to
         run it on.
         '''
 
         if trace_rsh:
             silent = False
 
         rc = 0
         result = None
         proc = Popen(self._cmd([node, command]),
                      stdout = PIPE, stderr = PIPE, close_fds = True, shell = True)
 
         #if completionDelegate: print("Waiting for %d on %s: %s" % (proc.pid, node, command))
         if not synchronous and proc.pid > 0 and not self.silent:
             aproc = AsyncWaitProc(proc, node, command, completionDelegate=completionDelegate)
             aproc.start()
             return 0
 
         #if not blocking:
         #    import fcntl
         #    fcntl.fcntl(proc.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)
 
         if proc.stdout:
             if stdout == 1:
                 result = proc.stdout.readline()
             else:
                 result = proc.stdout.readlines()
             proc.stdout.close()
         else:
             self.log("No stdout stream")
 
         rc = proc.wait()
 
         if not silent: self.debug("cmd: target=%s, rc=%d: %s" % (node, rc, command))
         if stdout == 1:
             return result
 
         if proc.stderr:
             errors = proc.stderr.readlines()
             proc.stderr.close()
 
         if completionDelegate:
             completionDelegate.async_complete(proc.pid, proc.returncode, result, errors)
 
         if not silent:
             for err in errors:
-                if stdout == 3:
-                    result.append("error: "+err)
-                else:
-                    self.debug("cmd: stderr: %s" % err)
+                self.debug("cmd: stderr: %s" % err)
 
         if stdout == 0:
             if not silent and result:
                 for line in result:
                     self.debug("cmd: stdout: %s" % line)
             return rc
 
         return (rc, result)
 
     def cp(self, source, target, silent=False):
         '''Perform a remote copy'''
         cpstring = self.rsh.CpCommand  + " \'" + source + "\'"  + " \'" + target + "\'"
         rc = os.system(cpstring)
         if trace_rsh:
             silent = False
         if not silent: self.debug("cmd: rc=%d: %s" % (rc, cpstring))
 
         return rc
 
     def exists_on_all(self, filename, hosts, test="r"):
         """ Return True if specified file exists on all specified hosts. """
 
         for host in hosts:
             rc = self(host, "test -%s %s" % (test, filename)) 
             if rc != 0:
                 return False
         return True
 
 
 class RemoteFactory:
     # Class variables
     rsh = RemotePrimitives()
     instance = None
 
     def getInstance(self):
         if not RemoteFactory.instance:
             RemoteFactory.instance = RemoteExec(RemoteFactory.rsh, False)
         return RemoteFactory.instance
 
     def new(self, silent=False):
         return RemoteExec(RemoteFactory.rsh, silent)
 
     def enable_docker(self):
         print("Using DOCKER backend for connections to cluster nodes")
 
         RemoteFactory.rsh.Command = "/usr/libexec/phd/docker/phd_docker_remote_cmd "
         RemoteFactory.rsh.CpCommand = "/usr/libexec/phd/docker/phd_docker_cp"
 
     def enable_qarsh(self):
         # http://nstraz.wordpress.com/2008/12/03/introducing-qarsh/
         print("Using QARSH for connections to cluster nodes")
 
         RemoteFactory.rsh.Command = "qarsh -t 300 -l root"
         RemoteFactory.rsh.CpCommand = "qacp -q"
 
diff --git a/cts/watcher.py b/cts/watcher.py
index de032f7b43..42685adfad 100644
--- a/cts/watcher.py
+++ b/cts/watcher.py
@@ -1,550 +1,550 @@
 '''
 Classes related to searching logs
 '''
 
 __copyright__='''
 Copyright (C) 2014 Andrew Beekhof <andrew@beekhof.net>
 Licensed under the GNU GPL.
 '''
 
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
 # as published by the Free Software Foundation; either version 2
 # of the License, or (at your option) any later version.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA.
 
 import time, re, os, threading
 
 from cts.remote import *
 from cts.logging import *
 
 has_log_watcher = {}
 log_watcher_file = "cts_log_watcher.py"
 log_watcher_bin = CTSvars.CRM_DAEMON_DIR + "/" + log_watcher_file
 log_watcher = """
 import sys, os, fcntl
 
 '''
 Remote logfile reader for CTS
 Reads a specified number of lines from the supplied offset
 Returns the current offset
 
 Contains logic for handling truncation
 '''
 
 limit    = 0
 offset   = 0
 prefix   = ''
 filename = '/var/log/messages'
 
 skipthis=None
 args=sys.argv[1:]
 for i in range(0, len(args)):
     if skipthis:
         skipthis=None
         continue
 
     elif args[i] == '-l' or args[i] == '--limit':
         skipthis=1
         limit = int(args[i+1])
 
     elif args[i] == '-f' or args[i] == '--filename':
         skipthis=1
         filename = args[i+1]
 
     elif args[i] == '-o' or args[i] == '--offset':
         skipthis=1
         offset = args[i+1]
 
     elif args[i] == '-p' or args[i] == '--prefix':
         skipthis=1
         prefix = args[i+1]
 
     elif args[i] == '-t' or args[i] == '--tag':
         skipthis=1
 
 if not os.access(filename, os.R_OK):
     print(prefix + 'Last read: %d, limit=%d, count=%d - unreadable' % (0, limit, 0))
     sys.exit(1)
 
 logfile=open(filename, 'r')
 logfile.seek(0, os.SEEK_END)
 newsize=logfile.tell()
 
 if offset != 'EOF':
     offset = int(offset)
     if newsize >= offset:
         logfile.seek(offset)
     else:
         print(prefix + ('File truncated from %d to %d' % (offset, newsize)))
         if (newsize*1.05) < offset:
             logfile.seek(0)
         # else: we probably just lost a few logs after a fencing op
         #       continue from the new end
         # TODO: accept a timestamp and discard all messages older than it
 
 # Don't block when we reach EOF
 fcntl.fcntl(logfile.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)
 
 count = 0
 while True:
     if logfile.tell() >= newsize:   break
     elif limit and count >= limit: break
 
     line = logfile.readline()
     if not line: break
 
     print(line.strip())
     count += 1
 
 print(prefix + 'Last read: %d, limit=%d, count=%d' % (logfile.tell(), limit, count))
 logfile.close()
 """
 
 class SearchObj:
     def __init__(self, filename, host=None, name=None):
 
         self.limit = None
         self.cache = []
         self.logger = LogFactory()
         self.host = host
         self.name = name
         self.filename = filename
         self.rsh = RemoteFactory().getInstance()
 
         self.offset = "EOF"
 
         if host == None:
             host = "localhost"
 
     def __str__(self):
         if self.host:
             return "%s:%s" % (self.host, self.filename)
         return self.filename
 
     def log(self, args):
         message = "lw: %s: %s" % (self, args)
         self.logger.log(message)
 
     def debug(self, args):
         message = "lw: %s: %s" % (self, args)
         self.logger.debug(message)
 
     def harvest(self, delegate=None):
         async = self.harvest_async(delegate)
         async.join()
 
     def harvest_async(self, delegate=None):
         self.log("Not implemented")
         raise
 
     def end(self):
         self.debug("Unsetting the limit")
         # Unset the limit
         self.limit = None
 
 class FileObj(SearchObj):
     def __init__(self, filename, host=None, name=None):
         global has_log_watcher
         SearchObj.__init__(self, filename, host, name)
 
         if host is not None:
             if not host in has_log_watcher:
 
                 global log_watcher
                 global log_watcher_bin
 
                 self.debug("Installing %s on %s" % (log_watcher_file, host))
 
                 os.system("cat << END >> %s\n%s\nEND" %(log_watcher_file, log_watcher))
                 os.system("chmod 755 %s" %(log_watcher_file))
 
                 self.rsh.cp(log_watcher_file, "root@%s:%s" % (host, log_watcher_bin))
                 has_log_watcher[host] = 1
 
                 os.system("rm -f %s" %(log_watcher_file))
 
             self.harvest()
 
     def async_complete(self, pid, returncode, outLines, errLines):
         for line in outLines:
             match = re.search("^CTSwatcher:Last read: (\d+)", line)
             if match:
                 last_offset = self.offset
                 self.offset = match.group(1)
                 #if last_offset == "EOF": self.debug("Got %d lines, new offset: %s" % (len(outLines), self.offset))
                 self.debug("Got %d lines, new offset: %s  %s" % (len(outLines), self.offset, repr(self.delegate)))
 
             elif re.search("^CTSwatcher:.*truncated", line):
                 self.log(line)
             elif re.search("^CTSwatcher:", line):
                 self.debug("Got control line: "+ line)
             else:
                 self.cache.append(line)
 
         if self.delegate:
             self.delegate.async_complete(pid, returncode, self.cache, errLines)
 
     def harvest_async(self, delegate=None):
         self.delegate = delegate
         self.cache = []
 
         if self.limit != None and self.offset > self.limit:
             if self.delegate:
                 self.delegate.async_complete(-1, -1, [], [])
             return None
 
         global log_watcher_bin
         return self.rsh.call_async(self.host,
                                    "python %s -t %s -p CTSwatcher: -l 200 -f %s -o %s" % (log_watcher_bin, self.name, self.filename, self.offset),
                 completionDelegate=self)
 
     def setend(self):
         if self.limit: 
             return
 
         global log_watcher_bin
         (rc, lines) = self.rsh(self.host,
                                "python %s -t %s -p CTSwatcher: -l 2 -f %s -o %s" % (log_watcher_bin, self.name, self.filename, "EOF"),
                  None, silent=True)
 
         for line in lines:
             match = re.search("^CTSwatcher:Last read: (\d+)", line)
             if match:
                 last_offset = self.offset
                 self.limit = int(match.group(1))
                 #if last_offset == "EOF": self.debug("Got %d lines, new offset: %s" % (len(lines), self.offset))
                 self.debug("Set limit to: %d" % self.limit)
 
         return
 
 class JournalObj(SearchObj):
 
     def __init__(self, host=None, name=None):
         SearchObj.__init__(self, name, host, name)
         self.harvest()
 
     def async_complete(self, pid, returncode, outLines, errLines):
         #self.log( "%d returned on %s" % (pid, self.host))
         foundCursor = False
         for line in outLines:
             match = re.search("^-- cursor: ([^.]+)", line)
             if match:
                 foundCursor = True
                 last_offset = self.offset
                 self.offset = match.group(1).strip()
                 self.debug("Got %d lines, new cursor: %s" % (len(outLines), self.offset))
             else:
                 self.cache.append(line)
 
         if self.limit and not foundCursor:
             self.hitLimit = True
             self.debug("Got %d lines but no cursor: %s" % (len(outLines), self.offset))
             
             # Get the current cursor
             (rc, outLines) = self.rsh(self.host, "journalctl -q -n 0 --show-cursor", stdout=None, silent=True, synchronous=True)
             for line in outLines:
                 match = re.search("^-- cursor: ([^.]+)", line)
                 if match:
                     last_offset = self.offset
                     self.offset = match.group(1).strip()
                     self.debug("Got %d lines, new cursor: %s" % (len(outLines), self.offset))
                 else:
                     self.log("Not a new cursor: %s" % line)
                     self.cache.append(line)
 
         if self.delegate:
             self.delegate.async_complete(pid, returncode, self.cache, errLines)
 
     def harvest_async(self, delegate=None):
         self.delegate = delegate
         self.cache = []
 
         # Use --lines to prevent journalctl from overflowing the Popen input buffer
         if self.limit and self.hitLimit:
             return None
 
         elif self.limit:
             command = "journalctl -q --after-cursor='%s' --until '%s' --lines=200 --show-cursor" % (self.offset, self.limit)
         else:
             command = "journalctl -q --after-cursor='%s' --lines=200 --show-cursor" % (self.offset)
 
         if self.offset == "EOF":
             command = "journalctl -q -n 0 --show-cursor"
 
         return self.rsh.call_async(self.host, command, completionDelegate=self)
 
     def setend(self):
         if self.limit: 
             return
 
         self.hitLimit = False
         (rc, lines) = self.rsh(self.host, "date +'%Y-%m-%d %H:%M:%S'", stdout=None, silent=True)
 
         if (rc == 0) and (len(lines) == 1):
             self.limit = lines[0].strip()
             self.debug("Set limit to: %s" % self.limit)
         else:
             self.debug("Unable to set limit for %s because date returned %d lines with status %d" % (self.host,
                 len(lines), rc))
 
         return
 
 class LogWatcher(RemoteExec):
 
     '''This class watches logs for messages that fit certain regular
        expressions.  Watching logs for events isn't the ideal way
        to do business, but it's better than nothing :-)
 
        On the other hand, this class is really pretty cool ;-)
 
        The way you use this class is as follows:
           Construct a LogWatcher object
           Call setwatch() when you want to start watching the log
           Call look() to scan the log looking for the patterns
     '''
 
     def __init__(self, log, regexes, name="Anon", timeout=10, debug_level=None, silent=False, hosts=None, kind=None):
         '''This is the constructor for the LogWatcher class.  It takes a
         log name to watch, and a list of regular expressions to watch for."
         '''
         self.logger = LogFactory()
 
         self.name        = name
         self.regexes     = regexes
         self.debug_level = debug_level
         self.whichmatch  = -1
         self.unmatched   = None
         self.cache_lock = threading.Lock()
 
         self.file_list = []
         self.line_cache = []
 
         #  Validate our arguments.  Better sooner than later ;-)
         for regex in regexes:
             assert re.compile(regex)
 
         if kind:
             self.kind    = kind
         else:
             raise
-            self.kind    = self.Env["LogWatcher"]
+            #self.kind    = self.Env["LogWatcher"]
 
         if log:
             self.filename    = log
         else:
             raise
-            self.filename    = self.Env["LogFileName"]
+            #self.filename    = self.Env["LogFileName"]
 
         if hosts:
             self.hosts = hosts
         else:
             raise
-            self.hosts = self.Env["nodes"]
+            #self.hosts = self.Env["nodes"]
 
         if trace_lw:
             self.debug_level = 3
             silent = False
 
         if not silent:
             for regex in self.regexes:
                 self.debug("Looking for regex: "+regex)
 
         self.Timeout = int(timeout)
         self.returnonlymatch = None
 
     def debug(self, args):
         message = "lw: %s: %s" % (self.name, args)
         self.logger.debug(message)
 
     def setwatch(self):
         '''Mark the place to start watching the log from.
         '''
 
         if self.kind == "remote":
             for node in self.hosts:
                 self.file_list.append(FileObj(self.filename, node, self.name))
 
         elif self.kind == "journal":
             for node in self.hosts:
                 self.file_list.append(JournalObj(node, self.name))
 
         else:
             self.file_list.append(FileObj(self.filename))
 
         # print("%s now has %d files" % (self.name, len(self.file_list)))
 
     def __del__(self):
         if self.debug_level > 1: self.debug("Destroy")
 
     def ReturnOnlyMatch(self, onlymatch=1):
         '''Specify one or more subgroups of the match to return rather than the whole string
            http://www.python.org/doc/2.5.2/lib/match-objects.html
         '''
         self.returnonlymatch = onlymatch
 
     def async_complete(self, pid, returncode, outLines, errLines):
         # TODO: Probably need a lock for updating self.line_cache
         self.logger.debug("%s: Got %d lines from %d (total %d)" % (self.name, len(outLines), pid, len(self.line_cache)))
         if len(outLines):
             self.cache_lock.acquire()
             self.line_cache.extend(outLines)
             self.cache_lock.release()
 
     def __get_lines(self, timeout):
         count=0
         if not len(self.file_list):
             raise ValueError("No sources to read from")
 
         pending = []
         #print("%s waiting for %d operations" % (self.name, self.pending))
         for f in self.file_list:
             t = f.harvest_async(self)
             if t:
                 pending.append(t)
 
         for t in pending:
             t.join(60.0)
             if t.isAlive():
                 self.logger.log("%s: Aborting after 20s waiting for %s logging commands" % (self.name, repr(t)))
                 return
 
         #print("Got %d lines" % len(self.line_cache))
 
     def end(self):
         for f in self.file_list:
             f.end()
 
     def look(self, timeout=None, silent=False):
         '''Examine the log looking for the given patterns.
         It starts looking from the place marked by setwatch().
         This function looks in the file in the fashion of tail -f.
         It properly recovers from log file truncation, but not from
         removing and recreating the log.  It would be nice if it
         recovered from this as well :-)
 
         We return the first line which matches any of our patterns.
         '''
         if timeout == None: timeout = self.Timeout
 
         if trace_lw:
             silent = False
 
         lines=0
         needlines=True
         begin=time.time()
         end=begin+timeout+1
         if self.debug_level > 2: self.debug("starting single search: timeout=%d, begin=%d, end=%d" % (timeout, begin, end))
 
         if not self.regexes:
             self.debug("Nothing to look for")
             return None
 
         if timeout == 0:
             for f in self.file_list:
                 f.setend()
 
         while True:
             if len(self.line_cache):
                 lines += 1
 
                 self.cache_lock.acquire()
                 line = self.line_cache[0]
                 self.line_cache.remove(line)
                 self.cache_lock.release()
 
                 which=-1
                 if re.search("CTS:", line):
                     continue
                 if self.debug_level > 2: self.debug("Processing: "+ line)
                 for regex in self.regexes:
                     which=which+1
                     if self.debug_level > 3: self.debug("Comparing line to: "+ regex)
                     #import string
                     #matchobj = re.search(string.lower(regex), string.lower(line))
                     matchobj = re.search(regex, line)
                     if matchobj:
                         self.whichmatch=which
                         if self.returnonlymatch:
                             return matchobj.group(self.returnonlymatch)
                         else:
                             self.debug("Matched: "+line)
                             if self.debug_level > 1: self.debug("With: "+ regex)
                             return line
 
             elif timeout > 0 and end < time.time():
                 if self.debug_level > 1: self.debug("hit timeout: %d" % timeout)
 
                 timeout = 0
                 for f in self.file_list:
                     f.setend()
 
             else:
                 self.__get_lines(timeout)
                 if len(self.line_cache) == 0 and end < time.time():
                     self.debug("Single search terminated: start=%d, end=%d, now=%d, lines=%d" % (begin, end, time.time(), lines))
                     return None
                 else:
                     self.debug("Waiting: start=%d, end=%d, now=%d, lines=%d" % (begin, end, time.time(), len(self.line_cache)))
                     time.sleep(1)
 
         self.debug("How did we get here")
         return None
 
     def lookforall(self, timeout=None, allow_multiple_matches=None, silent=False):
         '''Examine the log looking for ALL of the given patterns.
         It starts looking from the place marked by setwatch().
 
         We return when the timeout is reached, or when we have found
         ALL of the regexes that were part of the watch
         '''
 
         if timeout == None: timeout = self.Timeout
         save_regexes = self.regexes
         returnresult = []
 
         if trace_lw:
             silent = False
 
         if not silent:
             self.debug("starting search: timeout=%d" % timeout)
             for regex in self.regexes:
                 if self.debug_level > 2: self.debug("Looking for regex: "+regex)
 
         while (len(self.regexes) > 0):
             oneresult = self.look(timeout)
             if not oneresult:
                 self.unmatched = self.regexes
                 self.matched = returnresult
                 self.regexes = save_regexes
                 self.end()
                 return None
 
             returnresult.append(oneresult)
             if not allow_multiple_matches:
                 del self.regexes[self.whichmatch]
 
             else:
                 # Allow multiple regexes to match a single line
                 tmp_regexes = self.regexes
                 self.regexes = []
                 which = 0
                 for regex in tmp_regexes:
                     matchobj = re.search(regex, oneresult)
                     if not matchobj:
                         self.regexes.append(regex)
 
         self.unmatched = None
         self.matched = returnresult
         self.regexes = save_regexes
         return returnresult
 
diff --git a/lib/cluster/corosync.c b/lib/cluster/corosync.c
index c331774d21..2f8faea845 100644
--- a/lib/cluster/corosync.c
+++ b/lib/cluster/corosync.c
@@ -1,652 +1,643 @@
 /*
  * 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 <bzlib.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <netdb.h>
 
 #include <crm/common/ipc.h>
 #include <crm/cluster/internal.h>
 #include <crm/common/mainloop.h>
 #include <sys/utsname.h>
 
 #include <qb/qbipcc.h>
 #include <qb/qbutil.h>
 
 #include <corosync/corodefs.h>
 #include <corosync/corotypes.h>
 #include <corosync/hdb.h>
 #include <corosync/cfg.h>
 #include <corosync/cmap.h>
 #include <corosync/quorum.h>
 
 #include <crm/msg_xml.h>
 
 quorum_handle_t pcmk_quorum_handle = 0;
 
 gboolean(*quorum_app_callback) (unsigned long long seq, gboolean quorate) = NULL;
 
 char *
 get_corosync_uuid(crm_node_t *node)
 {
     if (node && is_corosync_cluster()) {
         if (node->id > 0) {
-            int len = 32;
-            char *buffer = NULL;
-
-            buffer = calloc(1, (len + 1));
-            if (buffer != NULL) {
-                snprintf(buffer, len, "%u", node->id);
-            }
-
-            return buffer;
-
+            return crm_strdup_printf("%u", node->id);
         } else {
             crm_info("Node %s is not yet known by corosync", node->uname);
         }
     }
     return NULL;
 }
 
 /*
  * CFG functionality stolen from node_name() in corosync-quorumtool.c
  * This resolves the first address assigned to a node and returns the name or IP address.
  */
 char *
 corosync_node_name(uint64_t /*cmap_handle_t */ cmap_handle, uint32_t nodeid)
 {
     int lpc = 0;
     int rc = CS_OK;
     int retries = 0;
     char *name = NULL;
     cmap_handle_t local_handle = 0;
 
     if (nodeid == 0) {
         nodeid = get_local_nodeid(0);
     }
 
     if (cmap_handle == 0 && local_handle == 0) {
         retries = 0;
         crm_trace("Initializing CMAP connection");
         do {
             rc = cmap_initialize(&local_handle);
             if (rc != CS_OK) {
                 retries++;
                 crm_debug("API connection setup failed: %s.  Retrying in %ds", cs_strerror(rc),
                           retries);
                 sleep(retries);
             }
 
         } while (retries < 5 && rc != CS_OK);
 
         if (rc != CS_OK) {
             crm_warn("Could not connect to Cluster Configuration Database API, error %s",
                      cs_strerror(rc));
             local_handle = 0;
         }
     }
 
     if (cmap_handle == 0) {
         cmap_handle = local_handle;
     }
 
     while (name == NULL && cmap_handle != 0) {
         uint32_t id = 0;
         char *key = NULL;
 
         key = crm_strdup_printf("nodelist.node.%d.nodeid", lpc);
         rc = cmap_get_uint32(cmap_handle, key, &id);
         crm_trace("Checking %u vs %u from %s", nodeid, id, key);
         free(key);
 
         if (rc != CS_OK) {
             break;
         }
 
         if (nodeid == id) {
             crm_trace("Searching for node name for %u in nodelist.node.%d %s", nodeid, lpc, name);
             if (name == NULL) {
                 key = crm_strdup_printf("nodelist.node.%d.ring0_addr", lpc);
                 cmap_get_string(cmap_handle, key, &name);
                 crm_trace("%s = %s", key, name);
 
                 if (node_name_is_valid(key, name) == FALSE) {
                     free(name);
                     name = NULL;
                 }
                 free(key);
             }
 
             if (name == NULL) {
                 key = crm_strdup_printf("nodelist.node.%d.name", lpc);
                 cmap_get_string(cmap_handle, key, &name);
                 crm_trace("%s = %s %d", key, name, rc);
                 free(key);
             }
             break;
         }
 
         lpc++;
     }
 
     if(local_handle) {
         cmap_finalize(local_handle);
     }
 
     if (name == NULL) {
         crm_info("Unable to get node name for nodeid %u", nodeid);
     }
     return name;
 }
 
 void
 terminate_cs_connection(crm_cluster_t *cluster)
 {
     cluster_disconnect_cpg(cluster);
     if (pcmk_quorum_handle) {
         quorum_finalize(pcmk_quorum_handle);
         pcmk_quorum_handle = 0;
     }
     crm_notice("Disconnected from Corosync");
 }
 
 static int
 pcmk_quorum_dispatch(gpointer user_data)
 {
     int rc = 0;
 
     rc = quorum_dispatch(pcmk_quorum_handle, CS_DISPATCH_ALL);
     if (rc < 0) {
         crm_err("Connection to the Quorum API failed: %d", rc);
         pcmk_quorum_handle = 0;
         return -1;
     }
     return 0;
 }
 
 static void
 pcmk_quorum_notification(quorum_handle_t handle,
                          uint32_t quorate,
                          uint64_t ring_id, uint32_t view_list_entries, uint32_t * view_list)
 {
     int i;
     GHashTableIter iter;
     crm_node_t *node = NULL;
     static gboolean init_phase = TRUE;
 
     if (quorate != crm_have_quorum) {
         if (quorate) {
             crm_notice("Quorum acquired " CRM_XS " membership=" U64T " members=%lu",
                        ring_id, (long unsigned int)view_list_entries);
         } else {
             crm_warn("Quorum lost " CRM_XS " membership=" U64T " members=%lu",
                      ring_id, (long unsigned int)view_list_entries);
         }
         crm_have_quorum = quorate;
 
     } else {
         crm_info("Quorum %s " CRM_XS " membership=" U64T " members=%lu",
                  (quorate? "retained" : "still lost"), ring_id,
                  (long unsigned int)view_list_entries);
     }
 
     if (view_list_entries == 0 && init_phase) {
         crm_info("Corosync membership is still forming, ignoring");
         return;
     }
 
     init_phase = FALSE;
 
     /* Reset last_seen for all cached nodes so we can tell which ones aren't
      * in the view list */
     g_hash_table_iter_init(&iter, crm_peer_cache);
     while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &node)) {
         node->last_seen = 0;
     }
 
     /* Update the peer cache for each node in view list */
     for (i = 0; i < view_list_entries; i++) {
         uint32_t id = view_list[i];
 
         crm_debug("Member[%d] %u ", i, id);
 
         /* Get this node's peer cache entry (adding one if not already there) */
         node = crm_get_peer(id, NULL);
         if (node->uname == NULL) {
             char *name = corosync_node_name(0, id);
 
             crm_info("Obtaining name for new node %u", id);
             node = crm_get_peer(id, name);
             free(name);
         }
 
         /* Update the node state (including updating last_seen to ring_id) */
         crm_update_peer_state(__FUNCTION__, node, CRM_NODE_MEMBER, ring_id);
     }
 
     /* Remove any peer cache entries we didn't update */
     crm_reap_unseen_nodes(ring_id);
 
     if (quorum_app_callback) {
         quorum_app_callback(ring_id, quorate);
     }
 }
 
 quorum_callbacks_t quorum_callbacks = {
     .quorum_notify_fn = pcmk_quorum_notification,
 };
 
 gboolean
 cluster_connect_quorum(gboolean(*dispatch) (unsigned long long, gboolean),
                        void (*destroy) (gpointer))
 {
     int rc = -1;
     int fd = 0;
     int quorate = 0;
     uint32_t quorum_type = 0;
     struct mainloop_fd_callbacks quorum_fd_callbacks;
 
     quorum_fd_callbacks.dispatch = pcmk_quorum_dispatch;
     quorum_fd_callbacks.destroy = destroy;
 
     crm_debug("Configuring Pacemaker to obtain quorum from Corosync");
 
     rc = quorum_initialize(&pcmk_quorum_handle, &quorum_callbacks, &quorum_type);
     if (rc != CS_OK) {
         crm_err("Could not connect to the Quorum API: %d", rc);
         goto bail;
 
     } else if (quorum_type != QUORUM_SET) {
         crm_err("Corosync quorum is not configured");
         goto bail;
     }
 
     rc = quorum_getquorate(pcmk_quorum_handle, &quorate);
     if (rc != CS_OK) {
         crm_err("Could not obtain the current Quorum API state: %d", rc);
         goto bail;
     }
 
     if (quorate) {
         crm_notice("Quorum acquired");
     } else {
         crm_warn("Quorum lost");
     }
     quorum_app_callback = dispatch;
     crm_have_quorum = quorate;
 
     rc = quorum_trackstart(pcmk_quorum_handle, CS_TRACK_CHANGES | CS_TRACK_CURRENT);
     if (rc != CS_OK) {
         crm_err("Could not setup Quorum API notifications: %d", rc);
         goto bail;
     }
 
     rc = quorum_fd_get(pcmk_quorum_handle, &fd);
     if (rc != CS_OK) {
         crm_err("Could not obtain the Quorum API connection: %d", rc);
         goto bail;
     }
 
     mainloop_add_fd("quorum", G_PRIORITY_HIGH, fd, dispatch, &quorum_fd_callbacks);
 
     corosync_initialize_nodelist(NULL, FALSE, NULL);
 
   bail:
     if (rc != CS_OK) {
         quorum_finalize(pcmk_quorum_handle);
         return FALSE;
     }
     return TRUE;
 }
 
 gboolean
 init_cs_connection(crm_cluster_t * cluster)
 {
     int retries = 0;
 
     while (retries < 5) {
         int rc = init_cs_connection_once(cluster);
 
         retries++;
 
         switch (rc) {
             case CS_OK:
                 return TRUE;
                 break;
             case CS_ERR_TRY_AGAIN:
             case CS_ERR_QUEUE_FULL:
                 sleep(retries);
                 break;
             default:
                 return FALSE;
         }
     }
 
     crm_err("Could not connect to corosync after %d retries", retries);
     return FALSE;
 }
 
 gboolean
 init_cs_connection_once(crm_cluster_t * cluster)
 {
     crm_node_t *peer = NULL;
     enum cluster_type_e stack = get_cluster_type();
 
     crm_peer_init();
 
     /* Here we just initialize comms */
     if (stack != pcmk_cluster_corosync) {
         crm_err("Invalid cluster type: %s (%d)", name_for_cluster_type(stack), stack);
         return FALSE;
     }
 
     if (cluster_connect_cpg(cluster) == FALSE) {
         return FALSE;
     }
     crm_info("Connection to '%s': established", name_for_cluster_type(stack));
 
     cluster->nodeid = get_local_nodeid(0);
     if(cluster->nodeid == 0) {
         crm_err("Could not establish local nodeid");
         return FALSE;
     }
 
     cluster->uname = get_node_name(0);
     if(cluster->uname == NULL) {
         crm_err("Could not establish local node name");
         return FALSE;
     }
 
     /* Ensure the local node always exists */
     peer = crm_get_peer(cluster->nodeid, cluster->uname);
     cluster->uuid = get_corosync_uuid(peer);
 
     return TRUE;
 }
 
 gboolean
 check_message_sanity(const AIS_Message * msg, const char *data)
 {
     gboolean sane = TRUE;
     int dest = msg->host.type;
     int tmp_size = msg->header.size - sizeof(AIS_Message);
 
     if (sane && msg->header.size == 0) {
         crm_warn("Message with no size");
         sane = FALSE;
     }
 
     if (sane && msg->header.error != CS_OK) {
         crm_warn("Message header contains an error: %d", msg->header.error);
         sane = FALSE;
     }
 
     if (sane && ais_data_len(msg) != tmp_size) {
         crm_warn("Message payload size is incorrect: expected %d, got %d", ais_data_len(msg),
                  tmp_size);
         sane = TRUE;
     }
 
     if (sane && ais_data_len(msg) == 0) {
         crm_warn("Message with no payload");
         sane = FALSE;
     }
 
     if (sane && data && msg->is_compressed == FALSE) {
         int str_size = strlen(data) + 1;
 
         if (ais_data_len(msg) != str_size) {
             int lpc = 0;
 
             crm_warn("Message payload is corrupted: expected %d bytes, got %d",
                      ais_data_len(msg), str_size);
             sane = FALSE;
             for (lpc = (str_size - 10); lpc < msg->size; lpc++) {
                 if (lpc < 0) {
                     lpc = 0;
                 }
                 crm_debug("bad_data[%d]: %d / '%c'", lpc, data[lpc], data[lpc]);
             }
         }
     }
 
     if (sane == FALSE) {
         crm_err("Invalid message %d: (dest=%s:%s, from=%s:%s.%u, compressed=%d, size=%d, total=%d)",
                 msg->id, ais_dest(&(msg->host)), msg_type2text(dest),
                 ais_dest(&(msg->sender)), msg_type2text(msg->sender.type),
                 msg->sender.pid, msg->is_compressed, ais_data_len(msg), msg->header.size);
 
     } else {
         crm_trace
             ("Verified message %d: (dest=%s:%s, from=%s:%s.%u, compressed=%d, size=%d, total=%d)",
              msg->id, ais_dest(&(msg->host)), msg_type2text(dest), ais_dest(&(msg->sender)),
              msg_type2text(msg->sender.type), msg->sender.pid, msg->is_compressed,
              ais_data_len(msg), msg->header.size);
     }
 
     return sane;
 }
 
 enum cluster_type_e
 find_corosync_variant(void)
 {
     int rc = CS_OK;
     cmap_handle_t handle;
 
     rc = cmap_initialize(&handle);
 
     switch(rc) {
         case CS_OK:
             break;
         case CS_ERR_SECURITY:
             crm_debug("Failed to initialize the cmap API: Permission denied (%d)", rc);
             /* It's there, we just can't talk to it.
              * Good enough for us to identify as 'corosync'
              */
             return pcmk_cluster_corosync;
 
         default:
             crm_info("Failed to initialize the cmap API: %s (%d)",
                      ais_error2text(rc), rc);
             return pcmk_cluster_unknown;
     }
 
     cmap_finalize(handle);
     return pcmk_cluster_corosync;
 }
 
 gboolean
 crm_is_corosync_peer_active(const crm_node_t * node)
 {
     if (node == NULL) {
         crm_trace("NULL");
         return FALSE;
 
     } else if (safe_str_neq(node->state, CRM_NODE_MEMBER)) {
         crm_trace("%s: state=%s", node->uname, node->state);
         return FALSE;
 
     } else if ((node->processes & crm_proc_cpg) == 0) {
         crm_trace("%s: processes=%.16x", node->uname, node->processes);
         return FALSE;
     }
     return TRUE;
 }
 
 gboolean
 corosync_initialize_nodelist(void *cluster, gboolean force_member, xmlNode * xml_parent)
 {
     int lpc = 0;
     int rc = CS_OK;
     int retries = 0;
     gboolean any = FALSE;
     cmap_handle_t cmap_handle;
 
     do {
         rc = cmap_initialize(&cmap_handle);
         if (rc != CS_OK) {
             retries++;
             crm_debug("API connection setup failed: %s.  Retrying in %ds", cs_strerror(rc),
                       retries);
             sleep(retries);
         }
 
     } while (retries < 5 && rc != CS_OK);
 
     if (rc != CS_OK) {
         crm_warn("Could not connect to Cluster Configuration Database API, error %d", rc);
         return FALSE;
     }
 
     crm_peer_init();
     crm_trace("Initializing corosync nodelist");
     for (lpc = 0; TRUE; lpc++) {
         uint32_t nodeid = 0;
         char *name = NULL;
         char *key = NULL;
 
         key = crm_strdup_printf("nodelist.node.%d.nodeid", lpc);
         rc = cmap_get_uint32(cmap_handle, key, &nodeid);
         free(key);
 
         if (rc != CS_OK) {
             break;
         }
 
         name = corosync_node_name(cmap_handle, nodeid);
         if (name != NULL) {
             GHashTableIter iter;
             crm_node_t *node = NULL;
 
             g_hash_table_iter_init(&iter, crm_peer_cache);
             while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &node)) {
                 if(node && node->uname && strcasecmp(node->uname, name) == 0) {
                     if (node->id && node->id != nodeid) {
                         crm_crit("Nodes %u and %u share the same name '%s': shutting down", node->id,
                                  nodeid, name);
                         crm_exit(DAEMON_RESPAWN_STOP);
                     }
                 }
             }
         }
 
         if (nodeid > 0 || name != NULL) {
             crm_trace("Initializing node[%d] %u = %s", lpc, nodeid, name);
             crm_get_peer(nodeid, name);
         }
 
         if (nodeid > 0 && name != NULL) {
             any = TRUE;
 
             if (xml_parent) {
                 xmlNode *node = create_xml_node(xml_parent, XML_CIB_TAG_NODE);
 
                 crm_xml_set_id(node, "%u", nodeid);
                 crm_xml_add(node, XML_ATTR_UNAME, name);
                 if (force_member) {
                     crm_xml_add(node, XML_ATTR_TYPE, CRM_NODE_MEMBER);
                 }
             }
         }
 
         free(name);
     }
     cmap_finalize(cmap_handle);
     return any;
 }
 
 char *
 corosync_cluster_name(void)
 {
     cmap_handle_t handle;
     char *cluster_name = NULL;
     int rc = CS_OK;
 
     rc = cmap_initialize(&handle);
     if (rc != CS_OK) {
         crm_info("Failed to initialize the cmap API: %s (%d)", ais_error2text(rc), rc);
         return NULL;
     }
 
     rc = cmap_get_string(handle, "totem.cluster_name", &cluster_name);
     if (rc != CS_OK) {
         crm_info("Cannot get totem.cluster_name: %s (%d)", ais_error2text(rc), rc);
 
     } else {
         crm_debug("cmap totem.cluster_name = '%s'", cluster_name);
     }
 
     cmap_finalize(handle);
 
     return cluster_name;
 }
 
 int
 corosync_cmap_has_config(const char *prefix)
 {
     int rc = CS_OK;
     int retries = 0;
     static int found = -1;
     cmap_handle_t cmap_handle;
     cmap_iter_handle_t iter_handle;
     char key_name[CMAP_KEYNAME_MAXLEN + 1];
 
     if(found != -1) {
         return found;
     }
 
     do {
         rc = cmap_initialize(&cmap_handle);
         if (rc != CS_OK) {
             retries++;
             crm_debug("API connection setup failed: %s.  Retrying in %ds", cs_strerror(rc),
                       retries);
             sleep(retries);
         }
 
     } while (retries < 5 && rc != CS_OK);
 
     if (rc != CS_OK) {
         crm_warn("Could not connect to Cluster Configuration Database API: %s (rc=%d)",
                  cs_strerror(rc), rc);
         return -1;
     }
 
     rc = cmap_iter_init(cmap_handle, prefix, &iter_handle);
     if (rc != CS_OK) {
         crm_warn("Failed to initialize iteration for corosync cmap '%s': %s (rc=%d)",
                  prefix, cs_strerror(rc), rc);
         goto bail;
     }
 
     found = 0;
     while ((rc = cmap_iter_next(cmap_handle, iter_handle, key_name, NULL, NULL)) == CS_OK) {
         crm_trace("'%s' is configured in corosync cmap: %s", prefix, key_name);
         found++;
         break;
     }
     cmap_iter_finalize(cmap_handle, iter_handle);
 
 bail:
     cmap_finalize(cmap_handle);
 
     return found;
 }
diff --git a/lib/services/dbus.c b/lib/services/dbus.c
index fb3e867f9f..58df9270df 100644
--- a/lib/services/dbus.c
+++ b/lib/services/dbus.c
@@ -1,638 +1,665 @@
 /*
  * Copyright (C) 2014-2016 Andrew Beekhof <andrew@beekhof.net>
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 #include <crm/crm.h>
 #include <crm/services.h>
 #include <dbus/dbus.h>
 #include <pcmk-dbus.h>
 
 #define BUS_PROPERTY_IFACE "org.freedesktop.DBus.Properties"
 
 static GList *conn_dispatches = NULL;
 
 struct db_getall_data {
     char *name;
     char *target;
     char *object;
     void *userdata;
     void (*callback)(const char *name, const char *value, void *userdata);
 };
 
+static void
+free_db_getall_data(struct db_getall_data *data)
+{
+    free(data->target);
+    free(data->object);
+    free(data->name);
+    free(data);
+}
+
 DBusConnection *
 pcmk_dbus_connect(void)
 {
     DBusError err;
     DBusConnection *connection;
 
     dbus_error_init(&err);
     connection = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
     if (dbus_error_is_set(&err)) {
         crm_err("Could not connect to System DBus: %s", err.message);
         dbus_error_free(&err);
         return NULL;
     }
     if(connection) {
         pcmk_dbus_connection_setup_with_select(connection);
     }
     return connection;
 }
 
 void
 pcmk_dbus_disconnect(DBusConnection *connection)
 {
     return;
 }
 
 /*!
  * \internal
  * \brief Check whether a DBus reply indicates an error occurred
  *
  * \param[in]  pending If non-NULL, indicates that a DBus request was sent
  * \param[in]  reply   Reply received from DBus
  * \param[out] ret     If non-NULL, will be set to DBus error, if any
  *
  * \return TRUE if an error was found, FALSE otherwise
  *
  * \note Following the DBus API convention, a TRUE return is exactly equivalent
  *       to ret being set. If ret is provided and this function returns TRUE,
  *       the caller is responsible for calling dbus_error_free() on ret when
  *       done using it.
  */
 bool
 pcmk_dbus_find_error(DBusPendingCall *pending, DBusMessage *reply,
                      DBusError *ret)
 {
     DBusError error;
 
     dbus_error_init(&error);
 
     if(pending == NULL) {
         dbus_set_error_const(&error, "org.clusterlabs.pacemaker.NoRequest",
                              "No request sent");
 
     } else if(reply == NULL) {
         dbus_set_error_const(&error, "org.clusterlabs.pacemaker.NoReply",
                              "No reply");
 
     } else {
         DBusMessageIter args;
         int dtype = dbus_message_get_type(reply);
         char *sig;
 
         switch(dtype) {
             case DBUS_MESSAGE_TYPE_METHOD_RETURN:
                 dbus_message_iter_init(reply, &args);
                 sig = dbus_message_iter_get_signature(&args);
                 crm_trace("DBus call returned output args '%s'", sig);
                 dbus_free(sig);
                 break;
             case DBUS_MESSAGE_TYPE_INVALID:
                 dbus_set_error_const(&error,
                                      "org.clusterlabs.pacemaker.InvalidReply",
                                      "Invalid reply");
                 break;
             case DBUS_MESSAGE_TYPE_METHOD_CALL:
                 dbus_set_error_const(&error,
                                      "org.clusterlabs.pacemaker.InvalidReply.Method",
                                      "Invalid reply (method call)");
                 break;
             case DBUS_MESSAGE_TYPE_SIGNAL:
                 dbus_set_error_const(&error,
                                      "org.clusterlabs.pacemaker.InvalidReply.Signal",
                                      "Invalid reply (signal)");
                 break;
             case DBUS_MESSAGE_TYPE_ERROR:
                 dbus_set_error_from_message(&error, reply);
                 break;
             default:
                 dbus_set_error(&error,
                                "org.clusterlabs.pacemaker.InvalidReply.Type",
                                "Unknown reply type %d", dtype);
         }
     }
 
     if (dbus_error_is_set(&error)) {
         crm_trace("DBus reply indicated error '%s' (%s)",
                   error.name, error.message);
         if (ret) {
             dbus_error_init(ret);
             dbus_move_error(&error, ret);
         } else {
             dbus_error_free(&error);
         }
         return TRUE;
     }
 
     return FALSE;
 }
 
 /*!
  * \internal
  * \brief Send a DBus request and wait for the reply
  *
  * \param[in]  msg         DBus request to send
  * \param[in]  connection  DBus connection to use
  * \param[out] error       If non-NULL, will be set to error, if any
  * \param[in]  timeout     Timeout to use for request
  *
  * \return DBus reply
  *
  * \note If error is non-NULL, it is initialized, so the caller may always use
  *       dbus_error_is_set() to determine whether an error occurred; the caller
  *       is responsible for calling dbus_error_free() in this case.
  */
 DBusMessage *
 pcmk_dbus_send_recv(DBusMessage *msg, DBusConnection *connection,
                     DBusError *error, int timeout)
 {
     const char *method = NULL;
     DBusMessage *reply = NULL;
     DBusPendingCall* pending = NULL;
 
     CRM_ASSERT(dbus_message_get_type (msg) == DBUS_MESSAGE_TYPE_METHOD_CALL);
     method = dbus_message_get_member (msg);
 
     /* Ensure caller can reliably check whether error is set */
     if (error) {
         dbus_error_init(error);
     }
 
     if (timeout <= 0) {
         /* DBUS_TIMEOUT_USE_DEFAULT (-1) tells DBus to use a sane default */
         timeout = DBUS_TIMEOUT_USE_DEFAULT;
     }
 
     // send message and get a handle for a reply
     if (!dbus_connection_send_with_reply(connection, msg, &pending, timeout)) {
         if(error) {
             dbus_set_error(error, "org.clusterlabs.pacemaker.SendFailed",
                            "Could not queue DBus '%s' request", method);
         }
         return NULL;
     }
 
     dbus_connection_flush(connection);
 
     if(pending) {
         /* block until we receive a reply */
         dbus_pending_call_block(pending);
 
         /* get the reply message */
         reply = dbus_pending_call_steal_reply(pending);
     }
 
     (void)pcmk_dbus_find_error(pending, reply, error);
 
     if(pending) {
         /* free the pending message handle */
         dbus_pending_call_unref(pending);
     }
 
     return reply;
 }
 
+/*!
+ * \internal
+ * \brief Send a DBus message with a callback for the reply
+ *
+ * \param[in]     msg         DBus message to send
+ * \param[in,out] connection  DBus connection to send on
+ * \param[in]     done        Function to call when pending call completes
+ * \param[in]     user_data   Data to pass to done callback
+ *
+ * \return Handle for reply on success, NULL on error
+ * \note The caller can assume that the done callback is called always and
+ *       only when the return value is non-NULL. (This allows the caller to
+ *       know where it should free dynamically allocated user_data.)
+ */
 DBusPendingCall *
 pcmk_dbus_send(DBusMessage *msg, DBusConnection *connection,
                void(*done)(DBusPendingCall *pending, void *user_data),
                void *user_data, int timeout)
 {
     const char *method = NULL;
     DBusPendingCall* pending = NULL;
 
     CRM_ASSERT(done);
     CRM_ASSERT(dbus_message_get_type (msg) == DBUS_MESSAGE_TYPE_METHOD_CALL);
     method = dbus_message_get_member (msg);
 
 
     if (timeout <= 0) {
         /* DBUS_TIMEOUT_USE_DEFAULT (-1) tells DBus to use a sane default */
         timeout = DBUS_TIMEOUT_USE_DEFAULT;
     }
 
     // send message and get a handle for a reply
     if (!dbus_connection_send_with_reply(connection, msg, &pending, timeout)) {
         crm_err("Send with reply failed for %s", method);
         return NULL;
 
     } else if (pending == NULL) {
         crm_err("No pending call found for %s: Connection to System DBus may be closed", method);
         return NULL;
     }
 
     crm_trace("DBus %s call sent", method);
     if (dbus_pending_call_get_completed(pending)) {
         crm_info("DBus %s call completed too soon", method);
         if(done) {
 #if 0
             /* This sounds like a good idea, but allegedly it breaks things */
             done(pending, user_data);
             pending = NULL;
 #else
             CRM_ASSERT(dbus_pending_call_set_notify(pending, done, user_data, NULL));
 #endif
         }
 
     } else if(done) {
         CRM_ASSERT(dbus_pending_call_set_notify(pending, done, user_data, NULL));
     }
     return pending;
 }
 
 bool
 pcmk_dbus_type_check(DBusMessage *msg, DBusMessageIter *field, int expected,
                      const char *function, int line)
 {
     int dtype = 0;
     DBusMessageIter lfield;
 
     if(field == NULL) {
         if(dbus_message_iter_init(msg, &lfield)) {
             field = &lfield;
         }
     }
 
     if(field == NULL) {
         do_crm_log_alias(LOG_ERR, __FILE__, function, line,
                          "Empty parameter list in reply expecting '%c'", expected);
         return FALSE;
     }
 
     dtype = dbus_message_iter_get_arg_type(field);
 
     if(dtype != expected) {
         DBusMessageIter args;
         char *sig;
 
         dbus_message_iter_init(msg, &args);
         sig = dbus_message_iter_get_signature(&args);
         do_crm_log_alias(LOG_ERR, __FILE__, function, line,
                          "Unexpected DBus type, expected %c in '%s' instead of %c",
                          expected, sig, dtype);
         dbus_free(sig);
         return FALSE;
     }
 
     return TRUE;
 }
 
 static char *
 pcmk_dbus_lookup_result(DBusMessage *reply, struct db_getall_data *data)
 {
     DBusError error;
     char *output = NULL;
     DBusMessageIter dict;
     DBusMessageIter args;
 
     if (pcmk_dbus_find_error((void*)&error, reply, &error)) {
         crm_err("Cannot get properties from %s for %s: %s",
                 data->target, data->object, error.message);
         dbus_error_free(&error);
         goto cleanup;
     }
 
     dbus_message_iter_init(reply, &args);
     if(!pcmk_dbus_type_check(reply, &args, DBUS_TYPE_ARRAY, __FUNCTION__, __LINE__)) {
         crm_err("Invalid reply from %s for %s", data->target, data->object);
         goto cleanup;
     }
 
     dbus_message_iter_recurse(&args, &dict);
     while (dbus_message_iter_get_arg_type (&dict) != DBUS_TYPE_INVALID) {
         DBusMessageIter sv;
         DBusMessageIter v;
         DBusBasicValue name;
         DBusBasicValue value;
 
         if(!pcmk_dbus_type_check(reply, &dict, DBUS_TYPE_DICT_ENTRY, __FUNCTION__, __LINE__)) {
             dbus_message_iter_next (&dict);
             continue;
         }
 
         dbus_message_iter_recurse(&dict, &sv);
         while (dbus_message_iter_get_arg_type (&sv) != DBUS_TYPE_INVALID) {
             int dtype = dbus_message_iter_get_arg_type(&sv);
 
             switch(dtype) {
                 case DBUS_TYPE_STRING:
                     dbus_message_iter_get_basic(&sv, &name);
 
                     if(data->name && strcmp(name.str, data->name) != 0) {
                         dbus_message_iter_next (&sv); /* Skip the value */
                     }
                     break;
                 case DBUS_TYPE_VARIANT:
                     dbus_message_iter_recurse(&sv, &v);
                     if(pcmk_dbus_type_check(reply, &v, DBUS_TYPE_STRING, __FUNCTION__, __LINE__)) {
                         dbus_message_iter_get_basic(&v, &value);
 
                         crm_trace("Property %s[%s] is '%s'", data->object, name.str, value.str);
                         if(data->callback) {
                             data->callback(name.str, value.str, data->userdata);
 
                         } else {
                             free(output);
                             output = strdup(value.str);
                         }
 
                         if(data->name) {
                             goto cleanup;
                         }
                     }
                     break;
                 default:
                     pcmk_dbus_type_check(reply, &sv, DBUS_TYPE_STRING, __FUNCTION__, __LINE__);
             }
             dbus_message_iter_next (&sv);
         }
 
         dbus_message_iter_next (&dict);
     }
 
     if(data->name && data->callback) {
         crm_trace("No value for property %s[%s]", data->object, data->name);
         data->callback(data->name, NULL, data->userdata);
     }
 
   cleanup:
-    free(data->target);
-    free(data->object);
-    free(data->name);
-    free(data);
-
+    free_db_getall_data(data);
     return output;
 }
 
 static void
 pcmk_dbus_lookup_cb(DBusPendingCall *pending, void *user_data)
 {
     DBusMessage *reply = NULL;
     char *value = NULL;
 
     if(pending) {
         reply = dbus_pending_call_steal_reply(pending);
     }
 
     value = pcmk_dbus_lookup_result(reply, user_data);
     free(value);
 
     if(reply) {
         dbus_message_unref(reply);
     }
 }
 
 char *
 pcmk_dbus_get_property(DBusConnection *connection, const char *target,
                        const char *obj, const gchar * iface, const char *name,
                        void (*callback)(const char *name, const char *value, void *userdata),
                        void *userdata, DBusPendingCall **pending, int timeout)
 {
     DBusMessage *msg;
     const char *method = "GetAll";
     char *output = NULL;
     struct db_getall_data *query_data = NULL;
 
     crm_debug("Calling: %s on %s", method, target);
     msg = dbus_message_new_method_call(target, // target for the method call
                                        obj, // object to call on
                                        BUS_PROPERTY_IFACE, // interface to call on
                                        method); // method name
     if (NULL == msg) {
         crm_err("Call to %s failed: No message", method);
         return NULL;
     }
 
     CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_STRING, &iface, DBUS_TYPE_INVALID));
 
     query_data = malloc(sizeof(struct db_getall_data));
     if(query_data == NULL) {
         crm_err("Call to %s failed: malloc failed", method);
         return NULL;
     }
 
     query_data->target = strdup(target);
     query_data->object = strdup(obj);
     query_data->callback = callback;
     query_data->userdata = userdata;
     query_data->name = NULL;
 
     if(name) {
         query_data->name = strdup(name);
     }
 
-    if(query_data->callback) {
-        DBusPendingCall* _pending;
-        _pending = pcmk_dbus_send(msg, connection, pcmk_dbus_lookup_cb, query_data, timeout);
-        if (pending != NULL) {
-            *pending = _pending;
+    if (query_data->callback) {
+        DBusPendingCall *local_pending;
+
+        local_pending = pcmk_dbus_send(msg, connection, pcmk_dbus_lookup_cb,
+                                       query_data, timeout);
+        if (local_pending == NULL) {
+            // pcmk_dbus_lookup_cb() was not called in this case
+            free_db_getall_data(query_data);
+            query_data = NULL;
+        }
+
+        if (pending) {
+            *pending = local_pending;
         }
 
     } else {
         DBusMessage *reply = pcmk_dbus_send_recv(msg, connection, NULL, timeout);
 
         output = pcmk_dbus_lookup_result(reply, query_data);
 
         if(reply) {
             dbus_message_unref(reply);
         }
     }
 
     dbus_message_unref(msg);
 
     return output;
 }
 
 static void
 pcmk_dbus_connection_dispatch_status(DBusConnection *connection,
                               DBusDispatchStatus new_status, void *data)
 {
     crm_trace("New status %d for connection %p", new_status, connection);
     if (new_status == DBUS_DISPATCH_DATA_REMAINS){
         conn_dispatches = g_list_prepend(conn_dispatches, connection);
     }
 }
 
 static void
 pcmk_dbus_connections_dispatch()
 {
     GList *gIter = NULL;
 
     for (gIter = conn_dispatches; gIter != NULL; gIter = gIter->next) {
         DBusConnection *connection = gIter->data;
 
         while (dbus_connection_get_dispatch_status(connection) == DBUS_DISPATCH_DATA_REMAINS) {
             crm_trace("Dispatching for connection %p", connection);
             dbus_connection_dispatch(connection);
         }
     }
 
     g_list_free(conn_dispatches);
     conn_dispatches = NULL;
 }
 
 /* Copied from dbus-watch.c */
 
 static const char*
 dbus_watch_flags_to_string(int flags)
 {
     const char *watch_type;
 
     if ((flags & DBUS_WATCH_READABLE) && (flags & DBUS_WATCH_WRITABLE)) {
         watch_type = "readwrite";
     } else if (flags & DBUS_WATCH_READABLE) {
         watch_type = "read";
     } else if (flags & DBUS_WATCH_WRITABLE) {
         watch_type = "write";
     } else {
         watch_type = "not read or write";
     }
     return watch_type;
 }
 
 static int
 pcmk_dbus_watch_dispatch(gpointer userdata)
 {
     bool oom = FALSE;
     DBusWatch *watch = userdata;
     int flags = dbus_watch_get_flags(watch);
     bool enabled = dbus_watch_get_enabled (watch);
     mainloop_io_t *client = dbus_watch_get_data(watch);
 
     crm_trace("Dispatching client %p: %s", client, dbus_watch_flags_to_string(flags));
     if (enabled && (flags & (DBUS_WATCH_READABLE|DBUS_WATCH_WRITABLE))) {
         oom = !dbus_watch_handle(watch, flags);
 
     } else if(enabled) {
         oom = !dbus_watch_handle(watch, DBUS_WATCH_ERROR);
     }
 
     if(flags != dbus_watch_get_flags(watch)) {
         flags = dbus_watch_get_flags(watch);
         crm_trace("Dispatched client %p: %s (%d)", client,
                   dbus_watch_flags_to_string(flags), flags);
     }
 
     if(oom) {
         crm_err("DBus encountered OOM while attempting to dispatch %p (%s)",
                 client, dbus_watch_flags_to_string(flags));
 
     } else {
         pcmk_dbus_connections_dispatch();
     }
 
     return 0;
 }
 
 static void
 pcmk_dbus_watch_destroy(gpointer userdata)
 {
     mainloop_io_t *client = dbus_watch_get_data(userdata);
     crm_trace("Destroyed %p", client);
 }
 
 
 struct mainloop_fd_callbacks pcmk_dbus_cb = {
     .dispatch = pcmk_dbus_watch_dispatch,
     .destroy = pcmk_dbus_watch_destroy,
 };
 
 static dbus_bool_t
 pcmk_dbus_watch_add(DBusWatch *watch, void *data)
 {
     int fd = dbus_watch_get_unix_fd(watch);
 
     mainloop_io_t *client = mainloop_add_fd(
         "dbus", G_PRIORITY_DEFAULT, fd, watch, &pcmk_dbus_cb);
 
     crm_trace("Added watch %p with fd=%d to client %p", watch, fd, client);
     dbus_watch_set_data(watch, client, NULL);
     return TRUE;
 }
 
 static void
 pcmk_dbus_watch_toggle(DBusWatch *watch, void *data)
 {
     mainloop_io_t *client = dbus_watch_get_data(watch);
     crm_notice("DBus client %p is now %s",
                client, (dbus_watch_get_enabled(watch)? "enabled" : "disabled"));
 }
 
 
 static void
 pcmk_dbus_watch_remove(DBusWatch *watch, void *data)
 {
     mainloop_io_t *client = dbus_watch_get_data(watch);
 
     crm_trace("Removed client %p (%p)", client, data);
     mainloop_del_fd(client);
 }
 
 static gboolean
 pcmk_dbus_timeout_dispatch(gpointer data)
 {
     crm_info("Timeout %p expired", data);
     dbus_timeout_handle(data);
     return FALSE;
 }
 
 static dbus_bool_t
 pcmk_dbus_timeout_add(DBusTimeout *timeout, void *data)
 {
     guint id = g_timeout_add(dbus_timeout_get_interval(timeout),
                              pcmk_dbus_timeout_dispatch, timeout);
 
     crm_trace("Adding timeout %p (%d)", timeout, dbus_timeout_get_interval(timeout));
 
     if(id) {
         dbus_timeout_set_data(timeout, GUINT_TO_POINTER(id), NULL);
     }
     return TRUE;
 }
 
 static void
 pcmk_dbus_timeout_remove(DBusTimeout *timeout, void *data)
 {
     void *vid = dbus_timeout_get_data(timeout);
     guint id = GPOINTER_TO_UINT(vid);
 
     crm_trace("Removing timeout %p (%p)", timeout, data);
 
     if(id) {
         g_source_remove(id);
         dbus_timeout_set_data(timeout, 0, NULL);
     }
 }
 
 static void
 pcmk_dbus_timeout_toggle(DBusTimeout *timeout, void *data)
 {
     bool enabled = dbus_timeout_get_enabled(timeout);
 
     crm_trace("Toggling timeout for %p to %s", timeout, enabled?"off":"on");
 
     if(enabled) {
         pcmk_dbus_timeout_add(timeout, data);
     } else {
         pcmk_dbus_timeout_remove(timeout, data);
     }
 }
 
 /* Inspired by http://www.kolej.mff.cuni.cz/~vesej3am/devel/dbus-select.c */
 
 void
 pcmk_dbus_connection_setup_with_select(DBusConnection *c)
 {
     dbus_connection_set_exit_on_disconnect(c, FALSE);
     dbus_connection_set_timeout_functions(c, pcmk_dbus_timeout_add,
                                           pcmk_dbus_timeout_remove,
                                           pcmk_dbus_timeout_toggle, NULL, NULL);
     dbus_connection_set_watch_functions(c, pcmk_dbus_watch_add,
                                         pcmk_dbus_watch_remove,
                                         pcmk_dbus_watch_toggle, NULL, NULL);
     dbus_connection_set_dispatch_status_function(c, pcmk_dbus_connection_dispatch_status, NULL, NULL);
     pcmk_dbus_connection_dispatch_status(c, dbus_connection_get_dispatch_status(c), NULL);
 }
diff --git a/lrmd/pacemaker_remote.service.in b/lrmd/pacemaker_remote.service.in
index 833e946139..8269d796e8 100644
--- a/lrmd/pacemaker_remote.service.in
+++ b/lrmd/pacemaker_remote.service.in
@@ -1,41 +1,44 @@
 [Unit]
 Description=Pacemaker Remote Service
 Documentation=man:pacemaker_remoted http://clusterlabs.org/doc/en-US/Pacemaker/1.1-pcs/html/Pacemaker_Remote/index.html
 
+# See main pacemaker unit file for descriptions of why these are needed
 After=network.target
 After=time-sync.target
+After=dbus.service
+Wants=dbus.service
 After=resource-agents-deps.target
 Wants=resource-agents-deps.target
 After=syslog.service
 After=rsyslog.service
 
 [Install]
 WantedBy=multi-user.target
 
 [Service]
 Type=simple
 KillMode=process
 NotifyAccess=none
 EnvironmentFile=-@CONFIGDIR@/pacemaker
 EnvironmentFile=-@CONFIGDIR@/sbd
 
 ExecStart=@sbindir@/pacemaker_remoted
 
 # Systemd v227 and above can limit the number of processes spawned by a
 # service. That is a bad idea for an HA cluster resource manager, so disable it
 # by default. The administrator can create a local override if they really want
 # a limit. If your systemd version does not support TasksMax, and you want to
 # get rid of the resulting log warnings, comment out this option.
 TasksMax=infinity
 
 # Pacemaker Remote can exit only after all managed services have shut down;
 # an HA database could conceivably take even longer than this 
 TimeoutStopSec=30min
 TimeoutStartSec=30s
 
 # Restart options include: no, on-success, on-failure, on-abort or always
 Restart=on-failure
 
 # crm_perror() writes directly to stderr, so ignore it here
 # to avoid double-logging with the wrong format
 StandardError=null
diff --git a/mcp/pacemaker.service.in b/mcp/pacemaker.service.in
index 66182d135f..943a644b9a 100644
--- a/mcp/pacemaker.service.in
+++ b/mcp/pacemaker.service.in
@@ -1,83 +1,87 @@
 [Unit]
 Description=Pacemaker High Availability Cluster Manager
 Documentation=man:pacemakerd http://clusterlabs.org/doc/en-US/Pacemaker/1.1-pcs/html/Pacemaker_Explained/index.html
 
 # DefaultDependencies takes care of sysinit.target,
 # basic.target, and shutdown.target
 
 # We need networking to bind to a network address. It is recommended not to
 # use Wants or Requires with network.target, and not to use
 # network-online.target for server daemons.
 After=network.target
 
 # Time syncs can make the clock jump backward, which messes with logging
 # and failure timestamps, so wait until it's done.
 After=time-sync.target
 
+# Managing systemd resources requires DBus.
+After=dbus.service
+Wants=dbus.service
+
 # Some OCF resources may have dependencies that aren't managed by the cluster;
 # these must be started before Pacemaker and stopped after it. The
 # resource-agents package provides this target, which lets system adminstrators
 # add drop-ins for those dependencies.
 After=resource-agents-deps.target
 Wants=resource-agents-deps.target
 
 After=syslog.service
 After=rsyslog.service
 After=corosync.service
 Requires=corosync.service
 
 
 [Install]
 WantedBy=multi-user.target
 
 
 [Service]
 Type=simple
 KillMode=process
 NotifyAccess=main
 EnvironmentFile=-@CONFIGDIR@/pacemaker
 EnvironmentFile=-@CONFIGDIR@/sbd
 SuccessExitStatus=100
 
 ExecStart=@sbindir@/pacemakerd -f
 
 # Systemd v227 and above can limit the number of processes spawned by a
 # service. That is a bad idea for an HA cluster resource manager, so disable it
 # by default. The administrator can create a local override if they really want
 # a limit. If your systemd version does not support TasksMax, and you want to
 # get rid of the resulting log warnings, comment out this option.
 TasksMax=infinity
 
 # If pacemakerd doesn't stop, it's probably waiting on a cluster
 # resource.  Sending -KILL will just get the node fenced
 SendSIGKILL=no
 
 # If we ever hit the StartLimitInterval/StartLimitBurst limit and the
 # admin wants to stop the cluster while pacemakerd is not running, it
 # might be a good idea to enable the ExecStopPost directive below.
 #
 # Although the node will likely end up being fenced as a result so it's
 # not on by default
 #
 # ExecStopPost=/usr/bin/killall -TERM crmd attrd stonithd cib pengine lrmd
 
 # If you want Corosync to stop whenever Pacemaker is stopped,
 # uncomment the next line too:
 #
 # ExecStopPost=/bin/sh -c 'pidof crmd || killall -TERM corosync'
 
 # Uncomment this for older versions of systemd that didn't support
 # TimeoutStopSec
 # TimeoutSec=30min
 
 # Pacemaker can only exit after all managed services have shut down
 # A HA database could conceivably take even longer than this 
 TimeoutStopSec=30min
 TimeoutStartSec=60s
 
 # Restart options include: no, on-success, on-failure, on-abort or always
 Restart=on-failure
 
 # crm_perror() writes directly to stderr, so ignore it here
 # to avoid double-logging with the wrong format
 StandardError=null
diff --git a/pengine/clone.c b/pengine/clone.c
index 99bac7e139..e81dbc85d3 100644
--- a/pengine/clone.c
+++ b/pengine/clone.c
@@ -1,1453 +1,1454 @@
 /* 
  * 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 <crm/msg_xml.h>
 #include <allocate.h>
 #include <notif.h>
 #include <utils.h>
 #include <allocate.h>
 
 #define VARIANT_CLONE 1
 #include <lib/pengine/variant.h>
 
 gint sort_clone_instance(gconstpointer a, gconstpointer b, gpointer data_set);
 static void append_parent_colocation(resource_t * rsc, resource_t * child, gboolean all);
 
 static gint
 sort_rsc_id(gconstpointer a, gconstpointer b)
 {
     const resource_t *resource1 = (const resource_t *)a;
     const resource_t *resource2 = (const resource_t *)b;
 
     CRM_ASSERT(resource1 != NULL);
     CRM_ASSERT(resource2 != NULL);
 
     return strcmp(resource1->id, resource2->id);
 }
 
 static node_t *
 parent_node_instance(const resource_t * rsc, node_t * node)
 {
     node_t *ret = NULL;
 
     if (node != NULL && rsc->parent) {
         ret = pe_hash_table_lookup(rsc->parent->allowed_nodes, node->details->id);
     } else if(node != NULL) {
         ret = pe_hash_table_lookup(rsc->allowed_nodes, node->details->id);
     }
     return ret;
 }
 
 static gboolean
 did_fail(const resource_t * rsc)
 {
     GListPtr gIter = rsc->children;
 
     if (is_set(rsc->flags, pe_rsc_failed)) {
         return TRUE;
     }
 
     for (; gIter != NULL; gIter = gIter->next) {
         resource_t *child_rsc = (resource_t *) gIter->data;
 
         if (did_fail(child_rsc)) {
             return TRUE;
         }
     }
     return FALSE;
 }
 
 gint
 sort_clone_instance(gconstpointer a, gconstpointer b, gpointer data_set)
 {
     int rc = 0;
     node_t *node1 = NULL;
     node_t *node2 = NULL;
 
     gboolean can1 = TRUE;
     gboolean can2 = TRUE;
 
     const resource_t *resource1 = (const resource_t *)a;
     const resource_t *resource2 = (const resource_t *)b;
 
     CRM_ASSERT(resource1 != NULL);
     CRM_ASSERT(resource2 != NULL);
 
     /* allocation order:
      *  - active instances
      *  - instances running on nodes with the least copies
      *  - active instances on nodes that can't support them or are to be fenced
      *  - failed instances
      *  - inactive instances
      */
 
     if (resource1->running_on && resource2->running_on) {
         if (g_list_length(resource1->running_on) < g_list_length(resource2->running_on)) {
             crm_trace("%s < %s: running_on", resource1->id, resource2->id);
             return -1;
 
         } else if (g_list_length(resource1->running_on) > g_list_length(resource2->running_on)) {
             crm_trace("%s > %s: running_on", resource1->id, resource2->id);
             return 1;
         }
     }
 
     if (resource1->running_on) {
         node1 = resource1->running_on->data;
     }
     if (resource2->running_on) {
         node2 = resource2->running_on->data;
     }
 
     if (node1) {
         node_t *match = pe_hash_table_lookup(resource1->allowed_nodes, node1->details->id);
 
         if (match == NULL || match->weight < 0) {
             crm_trace("%s: current location is unavailable", resource1->id);
             node1 = NULL;
             can1 = FALSE;
         }
     }
 
     if (node2) {
         node_t *match = pe_hash_table_lookup(resource2->allowed_nodes, node2->details->id);
 
         if (match == NULL || match->weight < 0) {
             crm_trace("%s: current location is unavailable", resource2->id);
             node2 = NULL;
             can2 = FALSE;
         }
     }
 
     if (can1 != can2) {
         if (can1) {
             crm_trace("%s < %s: availability of current location", resource1->id, resource2->id);
             return -1;
         }
         crm_trace("%s > %s: availability of current location", resource1->id, resource2->id);
         return 1;
     }
 
     if (resource1->priority < resource2->priority) {
         crm_trace("%s < %s: priority", resource1->id, resource2->id);
         return 1;
 
     } else if (resource1->priority > resource2->priority) {
         crm_trace("%s > %s: priority", resource1->id, resource2->id);
         return -1;
     }
 
     if (node1 == NULL && node2 == NULL) {
         crm_trace("%s == %s: not active", resource1->id, resource2->id);
         return 0;
     }
 
     if (node1 != node2) {
         if (node1 == NULL) {
             crm_trace("%s > %s: active", resource1->id, resource2->id);
             return 1;
         } else if (node2 == NULL) {
             crm_trace("%s < %s: active", resource1->id, resource2->id);
             return -1;
         }
     }
 
     can1 = can_run_resources(node1);
     can2 = can_run_resources(node2);
     if (can1 != can2) {
         if (can1) {
             crm_trace("%s < %s: can", resource1->id, resource2->id);
             return -1;
         }
         crm_trace("%s > %s: can", resource1->id, resource2->id);
         return 1;
     }
 
     node1 = parent_node_instance(resource1, node1);
     node2 = parent_node_instance(resource2, node2);
     if (node1 != NULL && node2 == NULL) {
         crm_trace("%s < %s: not allowed", resource1->id, resource2->id);
         return -1;
     } else if (node1 == NULL && node2 != NULL) {
         crm_trace("%s > %s: not allowed", resource1->id, resource2->id);
         return 1;
     }
 
     if (node1 == NULL || node2 == NULL) {
         crm_trace("%s == %s: not allowed", resource1->id, resource2->id);
         return 0;
     }
 
     if (node1->count < node2->count) {
         crm_trace("%s < %s: count", resource1->id, resource2->id);
         return -1;
 
     } else if (node1->count > node2->count) {
         crm_trace("%s > %s: count", resource1->id, resource2->id);
         return 1;
     }
 
     can1 = did_fail(resource1);
     can2 = did_fail(resource2);
     if (can1 != can2) {
         if (can1) {
             crm_trace("%s > %s: failed", resource1->id, resource2->id);
             return 1;
         }
         crm_trace("%s < %s: failed", resource1->id, resource2->id);
         return -1;
     }
 
     if (node1 && node2) {
         int lpc = 0;
         int max = 0;
         node_t *n = NULL;
         GListPtr gIter = NULL;
         GListPtr list1 = NULL;
         GListPtr list2 = NULL;
         GHashTable *hash1 =
             g_hash_table_new_full(crm_str_hash, g_str_equal, NULL, g_hash_destroy_str);
         GHashTable *hash2 =
             g_hash_table_new_full(crm_str_hash, g_str_equal, NULL, g_hash_destroy_str);
 
         n = node_copy(resource1->running_on->data);
         g_hash_table_insert(hash1, (gpointer) n->details->id, n);
 
         n = node_copy(resource2->running_on->data);
         g_hash_table_insert(hash2, (gpointer) n->details->id, n);
 
         if(resource1->parent) {
             for (gIter = resource1->parent->rsc_cons; gIter; gIter = gIter->next) {
                 rsc_colocation_t *constraint = (rsc_colocation_t *) gIter->data;
 
                 crm_trace("Applying %s to %s", constraint->id, resource1->id);
 
                 hash1 = native_merge_weights(constraint->rsc_rh, resource1->id, hash1,
                                              constraint->node_attribute,
                                              (float)constraint->score / INFINITY, 0);
             }
 
             for (gIter = resource1->parent->rsc_cons_lhs; gIter; gIter = gIter->next) {
                 rsc_colocation_t *constraint = (rsc_colocation_t *) gIter->data;
 
                 crm_trace("Applying %s to %s", constraint->id, resource1->id);
 
                 hash1 = native_merge_weights(constraint->rsc_lh, resource1->id, hash1,
                                              constraint->node_attribute,
                                              (float)constraint->score / INFINITY, pe_weights_positive);
             }
         }
 
         if(resource2->parent) {
             for (gIter = resource2->parent->rsc_cons; gIter; gIter = gIter->next) {
                 rsc_colocation_t *constraint = (rsc_colocation_t *) gIter->data;
 
                 crm_trace("Applying %s to %s", constraint->id, resource2->id);
 
                 hash2 = native_merge_weights(constraint->rsc_rh, resource2->id, hash2,
                                              constraint->node_attribute,
                                              (float)constraint->score / INFINITY, 0);
             }
 
             for (gIter = resource2->parent->rsc_cons_lhs; gIter; gIter = gIter->next) {
                 rsc_colocation_t *constraint = (rsc_colocation_t *) gIter->data;
 
                 crm_trace("Applying %s to %s", constraint->id, resource2->id);
 
                 hash2 = native_merge_weights(constraint->rsc_lh, resource2->id, hash2,
                                              constraint->node_attribute,
                                              (float)constraint->score / INFINITY, pe_weights_positive);
             }
         }
 
         /* Current location score */
         node1 = g_list_nth_data(resource1->running_on, 0);
         node1 = g_hash_table_lookup(hash1, node1->details->id);
 
         node2 = g_list_nth_data(resource2->running_on, 0);
         node2 = g_hash_table_lookup(hash2, node2->details->id);
 
         if (node1->weight < node2->weight) {
             if (node1->weight < 0) {
                 crm_trace("%s > %s: current score: %d %d", resource1->id, resource2->id, node1->weight, node2->weight);
                 rc = -1;
                 goto out;
 
             } else {
                 crm_trace("%s < %s: current score: %d %d", resource1->id, resource2->id, node1->weight, node2->weight);
                 rc = 1;
                 goto out;
             }
 
         } else if (node1->weight > node2->weight) {
             crm_trace("%s > %s: current score: %d %d", resource1->id, resource2->id, node1->weight, node2->weight);
             rc = -1;
             goto out;
         }
 
         /* All location scores */
         list1 = g_hash_table_get_values(hash1);
         list2 = g_hash_table_get_values(hash2);
 
         list1 =
             g_list_sort_with_data(list1, sort_node_weight,
                                   g_list_nth_data(resource1->running_on, 0));
         list2 =
             g_list_sort_with_data(list2, sort_node_weight,
                                   g_list_nth_data(resource2->running_on, 0));
         max = g_list_length(list1);
         if (max < g_list_length(list2)) {
             max = g_list_length(list2);
         }
 
         for (; lpc < max; lpc++) {
             node1 = g_list_nth_data(list1, lpc);
             node2 = g_list_nth_data(list2, lpc);
             if (node1 == NULL) {
                 crm_trace("%s < %s: colocated score NULL", resource1->id, resource2->id);
                 rc = 1;
                 break;
 
             } else if (node2 == NULL) {
                 crm_trace("%s > %s: colocated score NULL", resource1->id, resource2->id);
                 rc = -1;
                 break;
             }
 
             if (node1->weight < node2->weight) {
                 crm_trace("%s < %s: colocated score", resource1->id, resource2->id);
                 rc = 1;
                 break;
 
             } else if (node1->weight > node2->weight) {
                 crm_trace("%s > %s: colocated score", resource1->id, resource2->id);
                 rc = -1;
                 break;
             }
         }
 
         /* Order by reverse uname - same as sort_node_weight() does? */
   out:
         g_hash_table_destroy(hash1);    /* Free mem */
         g_hash_table_destroy(hash2);    /* Free mem */
         g_list_free(list1);
         g_list_free(list2);
 
         if (rc != 0) {
             return rc;
         }
     }
 
     rc = strcmp(resource1->id, resource2->id);
     crm_trace("%s %c %s: default", resource1->id, rc < 0 ? '<' : '>', resource2->id);
     return rc;
 }
 
 static node_t *
 can_run_instance(resource_t * rsc, node_t * node, int limit)
 {
     node_t *local_node = NULL;
 
     if (node == NULL && rsc->allowed_nodes) {
         GHashTableIter iter;
         g_hash_table_iter_init(&iter, rsc->allowed_nodes);
         while (g_hash_table_iter_next(&iter, NULL, (void **)&local_node)) {
             can_run_instance(rsc, local_node, limit);
         }
         return NULL;
     }
 
     if (can_run_resources(node) == FALSE) {
         goto bail;
 
     } else if (is_set(rsc->flags, pe_rsc_orphan)) {
         goto bail;
     }
 
     local_node = parent_node_instance(rsc, node);
 
     if (local_node == NULL) {
         crm_warn("%s cannot run on %s: node not allowed", rsc->id, node->details->uname);
         goto bail;
 
     } else if (local_node->weight < 0) {
         common_update_score(rsc, node->details->id, local_node->weight);
         pe_rsc_trace(rsc, "%s cannot run on %s: Parent node weight doesn't allow it.",
                      rsc->id, node->details->uname);
 
     } else if (local_node->count < limit) {
         pe_rsc_trace(rsc, "%s can run on %s (already running %d)",
                      rsc->id, node->details->uname, local_node->count);
         return local_node;
 
     } else {
         pe_rsc_trace(rsc, "%s cannot run on %s: node full (%d >= %d)",
                      rsc->id, node->details->uname, local_node->count, limit);
     }
 
   bail:
     if (node) {
         common_update_score(rsc, node->details->id, -INFINITY);
     }
     return NULL;
 }
 
 static node_t *
 color_instance(resource_t * rsc, node_t * prefer, gboolean all_coloc, int limit, pe_working_set_t * data_set)
 {
     node_t *chosen = NULL;
     GHashTable *backup = NULL;
 
     CRM_ASSERT(rsc);
     pe_rsc_trace(rsc, "Checking allocation of %s (preferring %s, using %s parent colocations)",
                  rsc->id, (prefer? prefer->details->uname: "none"),
                  (all_coloc? "all" : "some"));
 
     if (is_not_set(rsc->flags, pe_rsc_provisional)) {
         return rsc->fns->location(rsc, NULL, FALSE);
 
     } else if (is_set(rsc->flags, pe_rsc_allocating)) {
         pe_rsc_debug(rsc, "Dependency loop detected involving %s", rsc->id);
         return NULL;
     }
 
     /* Only include positive colocation preferences of dependent resources
      * if not every node will get a copy of the clone
      */
     append_parent_colocation(rsc->parent, rsc, all_coloc);
 
     if (prefer) {
         node_t *local_prefer = g_hash_table_lookup(rsc->allowed_nodes, prefer->details->id);
 
         if (local_prefer == NULL || local_prefer->weight < 0) {
             pe_rsc_trace(rsc, "Not pre-allocating %s to %s - unavailable", rsc->id,
                          prefer->details->uname);
             return NULL;
         }
     }
 
     can_run_instance(rsc, NULL, limit);
 
     backup = node_hash_dup(rsc->allowed_nodes);
     chosen = rsc->cmds->allocate(rsc, prefer, data_set);
     if (chosen) {
         node_t *local_node = parent_node_instance(rsc, chosen);
         if (prefer && chosen && chosen->details != prefer->details) {
             crm_notice("Pre-allocation failed: got %s instead of %s",
                        chosen->details->uname, prefer->details->uname);
             g_hash_table_destroy(rsc->allowed_nodes);
             rsc->allowed_nodes = backup;
             native_deallocate(rsc);
             chosen = NULL;
             backup = NULL;
 
         } else if (local_node) {
             local_node->count++;
 
         } else if (is_set(rsc->flags, pe_rsc_managed)) {
             /* what to do? we can't enforce per-node limits in this case */
             crm_config_err("%s not found in %s (list=%d)",
                            chosen->details->id, rsc->parent->id,
                            g_hash_table_size(rsc->parent->allowed_nodes));
         }
     }
 
     if(backup) {
         g_hash_table_destroy(backup);
     }
     return chosen;
 }
 
 static void
 append_parent_colocation(resource_t * rsc, resource_t * child, gboolean all)
 {
 
     GListPtr gIter = NULL;
 
     gIter = rsc->rsc_cons;
     for (; gIter != NULL; gIter = gIter->next) {
         rsc_colocation_t *cons = (rsc_colocation_t *) gIter->data;
 
         if (all || cons->score < 0 || cons->score == INFINITY) {
             child->rsc_cons = g_list_prepend(child->rsc_cons, cons);
         }
     }
 
     gIter = rsc->rsc_cons_lhs;
     for (; gIter != NULL; gIter = gIter->next) {
         rsc_colocation_t *cons = (rsc_colocation_t *) gIter->data;
 
         if (all || cons->score < 0) {
             child->rsc_cons_lhs = g_list_prepend(child->rsc_cons_lhs, cons);
         }
     }
 }
 
 
 void
 distribute_children(resource_t *rsc, GListPtr children, GListPtr nodes,
                     int max, int per_host_max, pe_working_set_t * data_set);
 
 void
 distribute_children(resource_t *rsc, GListPtr children, GListPtr nodes,
                     int max, int per_host_max, pe_working_set_t * data_set) 
 {
     int loop_max = 0;
     int allocated = 0;
     int available_nodes = 0;
 
     /* count now tracks the number of clones currently allocated */
     for(GListPtr nIter = nodes; nIter != NULL; nIter = nIter->next) {
         pe_node_t *node = nIter->data;
 
         node->count = 0;
         if (can_run_resources(node)) {
             available_nodes++;
         }
     }
 
     if(available_nodes) {
         loop_max = max / available_nodes;
     }
     if (loop_max < 1) {
         loop_max = 1;
     }
 
     pe_rsc_debug(rsc, "Allocating up to %d %s instances to a possible %d nodes (at most %d per host, %d optimal)",
                  max, rsc->id, available_nodes, per_host_max, loop_max);
 
     /* Pre-allocate as many instances as we can to their current location */
     for (GListPtr gIter = children; gIter != NULL && allocated < max; gIter = gIter->next) {
         resource_t *child = (resource_t *) gIter->data;
 
         if (child->running_on && is_set(child->flags, pe_rsc_provisional)
             && is_not_set(child->flags, pe_rsc_failed)) {
             node_t *child_node = child->running_on->data;
             node_t *local_node = parent_node_instance(child, child->running_on->data);
 
             pe_rsc_trace(rsc, "Checking pre-allocation of %s to %s (%d remaining of %d)",
                          child->id, child_node->details->uname, max - allocated, max);
 
             if (can_run_resources(child_node) == FALSE || child_node->weight < 0) {
                 pe_rsc_trace(rsc, "Not pre-allocating because %s can not run %s",
                              child_node->details->uname, child->id);
 
             } else if(local_node && local_node->count >= loop_max) {
                 pe_rsc_trace(rsc,
                              "Not pre-allocating because %s already allocated optimal instances",
                              child_node->details->uname);
 
             } else if (color_instance(child, child_node, max < available_nodes, per_host_max, data_set)) {
                 pe_rsc_trace(rsc, "Pre-allocated %s to %s", child->id,
                              child_node->details->uname);
                 allocated++;
             }
         }
     }
 
     pe_rsc_trace(rsc, "Done pre-allocating (%d of %d)", allocated, max);
 
     for (GListPtr gIter = children; gIter != NULL; gIter = gIter->next) {
         resource_t *child = (resource_t *) gIter->data;
 
         if (g_list_length(child->running_on) > 0) {
             node_t *child_node = child->running_on->data;
             node_t *local_node = parent_node_instance(child, child->running_on->data);
 
             if (local_node == NULL) {
                 crm_err("%s is running on %s which isn't allowed",
                         child->id, child_node->details->uname);
             }
         }
 
         if (is_not_set(child->flags, pe_rsc_provisional)) {
         } else if (allocated >= max) {
             pe_rsc_debug(rsc, "Child %s not allocated - limit reached %d %d", child->id, allocated, max);
             resource_location(child, NULL, -INFINITY, "clone_color:limit_reached", data_set);
         } else {
             if (color_instance(child, NULL, max < available_nodes, per_host_max, data_set)) {
                 allocated++;
             }
         }
     }
 
     pe_rsc_debug(rsc, "Allocated %d %s instances of a possible %d",
                  allocated, rsc->id, max);
 }
 
 
 node_t *
 clone_color(resource_t * rsc, node_t * prefer, pe_working_set_t * data_set)
 {
     GListPtr nodes = NULL;
 
     clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, rsc);
 
     if (is_not_set(rsc->flags, pe_rsc_provisional)) {
         return NULL;
 
     } else if (is_set(rsc->flags, pe_rsc_allocating)) {
         pe_rsc_debug(rsc, "Dependency loop detected involving %s", rsc->id);
         return NULL;
     }
 
     set_bit(rsc->flags, pe_rsc_allocating);
     pe_rsc_trace(rsc, "Processing %s", rsc->id);
 
     /* this information is used by sort_clone_instance() when deciding in which 
      * order to allocate clone instances
      */
     for (GListPtr gIter = rsc->rsc_cons; gIter != NULL; gIter = gIter->next) {
         rsc_colocation_t *constraint = (rsc_colocation_t *) gIter->data;
 
         pe_rsc_trace(rsc, "%s: Coloring %s first", rsc->id, constraint->rsc_rh->id);
         constraint->rsc_rh->cmds->allocate(constraint->rsc_rh, prefer, data_set);
     }
 
     for (GListPtr gIter = rsc->rsc_cons_lhs; gIter != NULL; gIter = gIter->next) {
         rsc_colocation_t *constraint = (rsc_colocation_t *) gIter->data;
 
         rsc->allowed_nodes =
             constraint->rsc_lh->cmds->merge_weights(constraint->rsc_lh, rsc->id, rsc->allowed_nodes,
                                                     constraint->node_attribute,
                                                     (float)constraint->score / INFINITY,
                                                     (pe_weights_rollback | pe_weights_positive));
     }
 
     dump_node_scores(show_scores ? 0 : scores_log_level, rsc, __FUNCTION__, rsc->allowed_nodes);
 
     nodes = g_hash_table_get_values(rsc->allowed_nodes);
     nodes = g_list_sort_with_data(nodes, sort_node_weight, NULL);
     rsc->children = g_list_sort_with_data(rsc->children, sort_clone_instance, data_set);
     distribute_children(rsc, rsc->children, nodes, clone_data->clone_max, clone_data->clone_node_max, data_set);
     g_list_free(nodes);
 
     clear_bit(rsc->flags, pe_rsc_provisional);
     clear_bit(rsc->flags, pe_rsc_allocating);
 
     pe_rsc_trace(rsc, "Done allocating %s", rsc->id);
     return NULL;
 }
 
 static void
 clone_update_pseudo_status(resource_t * rsc, gboolean * stopping, gboolean * starting,
                            gboolean * active)
 {
     GListPtr gIter = NULL;
 
     if (rsc->children) {
 
         gIter = rsc->children;
         for (; gIter != NULL; gIter = gIter->next) {
             resource_t *child = (resource_t *) gIter->data;
 
             clone_update_pseudo_status(child, stopping, starting, active);
         }
 
         return;
     }
 
     CRM_ASSERT(active != NULL);
     CRM_ASSERT(starting != NULL);
     CRM_ASSERT(stopping != NULL);
 
     if (rsc->running_on) {
         *active = TRUE;
     }
 
     gIter = rsc->actions;
     for (; gIter != NULL; gIter = gIter->next) {
         action_t *action = (action_t *) gIter->data;
 
         if (*starting && *stopping) {
             return;
 
         } else if (is_set(action->flags, pe_action_optional)) {
             pe_rsc_trace(rsc, "Skipping optional: %s", action->uuid);
             continue;
 
         } else if (is_set(action->flags, pe_action_pseudo) == FALSE
                    && is_set(action->flags, pe_action_runnable) == FALSE) {
             pe_rsc_trace(rsc, "Skipping unrunnable: %s", action->uuid);
             continue;
 
         } else if (safe_str_eq(RSC_STOP, action->task)) {
             pe_rsc_trace(rsc, "Stopping due to: %s", action->uuid);
             *stopping = TRUE;
 
         } else if (safe_str_eq(RSC_START, action->task)) {
             if (is_set(action->flags, pe_action_runnable) == FALSE) {
                 pe_rsc_trace(rsc, "Skipping pseudo-op: %s run=%d, pseudo=%d",
                              action->uuid, is_set(action->flags, pe_action_runnable),
                              is_set(action->flags, pe_action_pseudo));
             } else {
                 pe_rsc_trace(rsc, "Starting due to: %s", action->uuid);
                 pe_rsc_trace(rsc, "%s run=%d, pseudo=%d",
                              action->uuid, is_set(action->flags, pe_action_runnable),
                              is_set(action->flags, pe_action_pseudo));
                 *starting = TRUE;
             }
         }
     }
 }
 
 static action_t *
 find_rsc_action(resource_t * rsc, const char *key, gboolean active_only, GListPtr * list)
 {
     action_t *match = NULL;
     GListPtr possible = NULL;
     GListPtr active = NULL;
 
     possible = find_actions(rsc->actions, key, NULL);
 
     if (active_only) {
         GListPtr gIter = possible;
 
         for (; gIter != NULL; gIter = gIter->next) {
             action_t *op = (action_t *) gIter->data;
 
             if (is_set(op->flags, pe_action_optional) == FALSE) {
                 active = g_list_prepend(active, op);
             }
         }
 
         if (active && g_list_length(active) == 1) {
             match = g_list_nth_data(active, 0);
         }
 
         if (list) {
             *list = active;
             active = NULL;
         }
 
     } else if (possible && g_list_length(possible) == 1) {
         match = g_list_nth_data(possible, 0);
 
     }
     if (list) {
         *list = possible;
         possible = NULL;
     }
 
     if (possible) {
         g_list_free(possible);
     }
     if (active) {
         g_list_free(active);
     }
 
     return match;
 }
 
 static void
 child_ordering_constraints(resource_t * rsc, pe_working_set_t * data_set)
 {
     char *key = NULL;
     action_t *stop = NULL;
     action_t *start = NULL;
     action_t *last_stop = NULL;
     action_t *last_start = NULL;
     GListPtr gIter = NULL;
     gboolean active_only = TRUE;        /* change to false to get the old behavior */
     clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, rsc);
 
     if (clone_data->ordered == FALSE) {
         return;
     }
     /* we have to maintain a consistent sorted child list when building order constraints */
     rsc->children = g_list_sort(rsc->children, sort_rsc_id);
 
     for (gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
         resource_t *child = (resource_t *) gIter->data;
 
         key = stop_key(child);
         stop = find_rsc_action(child, key, active_only, NULL);
         free(key);
 
         key = start_key(child);
         start = find_rsc_action(child, key, active_only, NULL);
         free(key);
 
         if (stop) {
             if (last_stop) {
                 /* child/child relative stop */
                 order_actions(stop, last_stop, pe_order_optional);
             }
             last_stop = stop;
         }
 
         if (start) {
             if (last_start) {
                 /* child/child relative start */
                 order_actions(last_start, start, pe_order_optional);
             }
             last_start = start;
         }
     }
 }
 
 void
 clone_create_actions(resource_t * rsc, pe_working_set_t * data_set)
 {
     clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, rsc);
     clone_create_pseudo_actions(rsc, rsc->children, &clone_data->start_notify, &clone_data->stop_notify,data_set);
     child_ordering_constraints(rsc, data_set);
 }
 
 void
 clone_create_pseudo_actions(
     resource_t * rsc, GListPtr children, notify_data_t **start_notify, notify_data_t **stop_notify,  pe_working_set_t * data_set)
 {
     gboolean child_active = FALSE;
     gboolean child_starting = FALSE;
     gboolean child_stopping = FALSE;
     gboolean allow_dependent_migrations = TRUE;
 
     action_t *stop = NULL;
     action_t *stopped = NULL;
 
     action_t *start = NULL;
     action_t *started = NULL;
 
     pe_rsc_trace(rsc, "Creating actions for %s", rsc->id);
 
     for (GListPtr gIter = children; gIter != NULL; gIter = gIter->next) {
         resource_t *child_rsc = (resource_t *) gIter->data;
         gboolean starting = FALSE;
         gboolean stopping = FALSE;
 
         child_rsc->cmds->create_actions(child_rsc, data_set);
         clone_update_pseudo_status(child_rsc, &stopping, &starting, &child_active);
         if (stopping && starting) {
             allow_dependent_migrations = FALSE;
         }
 
         child_stopping |= stopping;
         child_starting |= starting;
     }
 
     /* start */
     start = create_pseudo_resource_op(rsc, RSC_START, !child_starting, TRUE, data_set);
     started = create_pseudo_resource_op(rsc, RSC_STARTED, !child_starting, FALSE, data_set);
     started->priority = INFINITY;
 
     if (child_active || child_starting) {
         update_action_flags(started, pe_action_runnable, __FUNCTION__, __LINE__);
     }
 
     if (start_notify != NULL && *start_notify == NULL) {
         *start_notify = create_notification_boundaries(rsc, RSC_START, start, started, data_set);
     }
 
     /* stop */
     stop = create_pseudo_resource_op(rsc, RSC_STOP, !child_stopping, TRUE, data_set);
     stopped = create_pseudo_resource_op(rsc, RSC_STOPPED, !child_stopping, TRUE, data_set);
     stopped->priority = INFINITY;
     if (allow_dependent_migrations) {
         update_action_flags(stop, pe_action_migrate_runnable, __FUNCTION__, __LINE__);
     }
 
     if (stop_notify != NULL && *stop_notify == NULL) {
         *stop_notify = create_notification_boundaries(rsc, RSC_STOP, stop, stopped, data_set);
 
         if (start_notify && *start_notify && *stop_notify) {
             order_actions((*stop_notify)->post_done, (*start_notify)->pre, pe_order_optional);
         }
     }
 }
 
 void
 clone_internal_constraints(resource_t * rsc, pe_working_set_t * data_set)
 {
     resource_t *last_rsc = NULL;
     GListPtr gIter;
     clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, rsc);
 
     pe_rsc_trace(rsc, "Internal constraints for %s", rsc->id);
     new_rsc_order(rsc, RSC_STOPPED, rsc, RSC_START, pe_order_optional, data_set);
     new_rsc_order(rsc, RSC_START, rsc, RSC_STARTED, pe_order_runnable_left, data_set);
     new_rsc_order(rsc, RSC_STOP, rsc, RSC_STOPPED, pe_order_runnable_left, data_set);
 
     if (rsc->variant == pe_master) {
         new_rsc_order(rsc, RSC_DEMOTED, rsc, RSC_STOP, pe_order_optional, data_set);
         new_rsc_order(rsc, RSC_STARTED, rsc, RSC_PROMOTE, pe_order_runnable_left, data_set);
     }
 
     if (clone_data->ordered) {
         /* we have to maintain a consistent sorted child list when building order constraints */
         rsc->children = g_list_sort(rsc->children, sort_rsc_id);
     }
     for (gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
         resource_t *child_rsc = (resource_t *) gIter->data;
 
         child_rsc->cmds->internal_constraints(child_rsc, data_set);
 
         order_start_start(rsc, child_rsc, pe_order_runnable_left | pe_order_implies_first_printed);
         new_rsc_order(child_rsc, RSC_START, rsc, RSC_STARTED, pe_order_implies_then_printed,
                       data_set);
         if (clone_data->ordered && last_rsc) {
             order_start_start(last_rsc, child_rsc, pe_order_optional);
         }
 
         order_stop_stop(rsc, child_rsc, pe_order_implies_first_printed);
         new_rsc_order(child_rsc, RSC_STOP, rsc, RSC_STOPPED, pe_order_implies_then_printed,
                       data_set);
         if (clone_data->ordered && last_rsc) {
             order_stop_stop(child_rsc, last_rsc, pe_order_optional);
         }
 
         last_rsc = child_rsc;
     }
 }
 
 bool
 assign_node(resource_t * rsc, node_t * node, gboolean force)
 {
     bool changed = FALSE;
 
     if (rsc->children) {
 
         for (GListPtr gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
             resource_t *child_rsc = (resource_t *) gIter->data;
 
             changed |= assign_node(child_rsc, node, force);
         }
 
         return changed;
     }
 
     if (rsc->allocated_to != NULL) {
         changed = true;
     }
 
     native_assign_node(rsc, NULL, node, force);
     return changed;
 }
 
 gboolean
 is_child_compatible(resource_t *child_rsc, node_t * local_node, enum rsc_role_e filter, gboolean current) 
 {
     node_t *node = NULL;
     enum rsc_role_e next_role = child_rsc->fns->state(child_rsc, current);
 
+    CRM_CHECK(child_rsc && local_node, return FALSE);
     if (is_set_recursive(child_rsc, pe_rsc_block, TRUE) == FALSE) {
         /* We only want instances that haven't failed */
         node = child_rsc->fns->location(child_rsc, NULL, current);
     }
 
     if (filter != RSC_ROLE_UNKNOWN && next_role != filter) {
         crm_trace("Filtered %s", child_rsc->id);
         return FALSE;
     }
 
-    if (node && local_node && node->details == local_node->details) {
+    if (node && (node->details == local_node->details)) {
         return TRUE;
 
     } else if (node) {
         crm_trace("%s - %s vs %s", child_rsc->id, node->details->uname,
                   local_node->details->uname);
 
     } else {
         crm_trace("%s - not allocated %d", child_rsc->id, current);
     }
     return FALSE;
 }
 
 resource_t *
 find_compatible_child(resource_t * local_child, resource_t * rsc, enum rsc_role_e filter, gboolean current)
 {
     resource_t *pair = NULL;
     GListPtr gIter = NULL;
     GListPtr scratch = NULL;
     node_t *local_node = NULL;
 
     local_node = local_child->fns->location(local_child, NULL, current);
     if (local_node) {
         return find_compatible_child_by_node(local_child, local_node, rsc, filter, current);
     }
 
     scratch = g_hash_table_get_values(local_child->allowed_nodes);
     scratch = g_list_sort_with_data(scratch, sort_node_weight, NULL);
 
     gIter = scratch;
     for (; gIter != NULL; gIter = gIter->next) {
         node_t *node = (node_t *) gIter->data;
 
         pair = find_compatible_child_by_node(local_child, node, rsc, filter, current);
         if (pair) {
             goto done;
         }
     }
 
     pe_rsc_debug(rsc, "Can't pair %s with %s", local_child->id, rsc->id);
   done:
     g_list_free(scratch);
     return pair;
 }
 
 void
 clone_rsc_colocation_lh(resource_t * rsc_lh, resource_t * rsc_rh, rsc_colocation_t * constraint)
 {
     /* -- Never called --
      *
      * Instead we add the colocation constraints to the child and call from there
      */
     CRM_ASSERT(FALSE);
 }
 
 void
 clone_rsc_colocation_rh(resource_t * rsc_lh, resource_t * rsc_rh, rsc_colocation_t * constraint)
 {
     GListPtr gIter = NULL;
     gboolean do_interleave = FALSE;
     const char *interleave_s = NULL;
 
     CRM_CHECK(constraint != NULL, return);
     CRM_CHECK(rsc_lh != NULL, pe_err("rsc_lh was NULL for %s", constraint->id); return);
     CRM_CHECK(rsc_rh != NULL, pe_err("rsc_rh was NULL for %s", constraint->id); return);
     CRM_CHECK(rsc_lh->variant == pe_native, return);
 
     pe_rsc_trace(rsc_rh, "Processing constraint %s: %s -> %s %d",
                  constraint->id, rsc_lh->id, rsc_rh->id, constraint->score);
 
     /* only the LHS side needs to be labeled as interleave */
     interleave_s = g_hash_table_lookup(constraint->rsc_lh->meta, XML_RSC_ATTR_INTERLEAVE);
     if(crm_is_true(interleave_s) && constraint->rsc_lh->variant > pe_group) {
         // TODO: Do we actually care about multiple RH copies sharing a LH copy anymore?
         if (copies_per_node(constraint->rsc_lh) != copies_per_node(constraint->rsc_rh)) {
             crm_config_err("Cannot interleave %s and %s because"
                            " they do not support the same number of copies per node",
                            constraint->rsc_lh->id, constraint->rsc_rh->id);
 
         } else {
             do_interleave = TRUE;
         }
     }
 
     if (is_set(rsc_rh->flags, pe_rsc_provisional)) {
         pe_rsc_trace(rsc_rh, "%s is still provisional", rsc_rh->id);
         return;
 
     } else if (do_interleave) {
         resource_t *rh_child = NULL;
 
         rh_child = find_compatible_child(rsc_lh, rsc_rh, RSC_ROLE_UNKNOWN, FALSE);
 
         if (rh_child) {
             pe_rsc_debug(rsc_rh, "Pairing %s with %s", rsc_lh->id, rh_child->id);
             rsc_lh->cmds->rsc_colocation_lh(rsc_lh, rh_child, constraint);
 
         } else if (constraint->score >= INFINITY) {
             crm_notice("Cannot pair %s with instance of %s", rsc_lh->id, rsc_rh->id);
             assign_node(rsc_lh, NULL, TRUE);
 
         } else {
             pe_rsc_debug(rsc_rh, "Cannot pair %s with instance of %s", rsc_lh->id, rsc_rh->id);
         }
 
         return;
 
     } else if (constraint->score >= INFINITY) {
         GListPtr rhs = NULL;
 
         gIter = rsc_rh->children;
         for (; gIter != NULL; gIter = gIter->next) {
             resource_t *child_rsc = (resource_t *) gIter->data;
             node_t *chosen = child_rsc->fns->location(child_rsc, NULL, FALSE);
 
             if (chosen != NULL && is_set_recursive(child_rsc, pe_rsc_block, TRUE) == FALSE) {
                 pe_rsc_trace(rsc_rh, "Allowing %s: %s %d", constraint->id, chosen->details->uname, chosen->weight);
                 rhs = g_list_prepend(rhs, chosen);
             }
         }
 
         node_list_exclude(rsc_lh->allowed_nodes, rhs, FALSE);
         g_list_free(rhs);
         return;
     }
 
     gIter = rsc_rh->children;
     for (; gIter != NULL; gIter = gIter->next) {
         resource_t *child_rsc = (resource_t *) gIter->data;
 
         child_rsc->cmds->rsc_colocation_rh(rsc_lh, child_rsc, constraint);
     }
 }
 
 enum action_tasks
 clone_child_action(action_t * action)
 {
     enum action_tasks result = no_action;
     resource_t *child = (resource_t *) action->rsc->children->data;
 
     if (safe_str_eq(action->task, "notify")
         || safe_str_eq(action->task, "notified")) {
 
         /* Find the action we're notifying about instead */
 
         int stop = 0;
         char *key = action->uuid;
         int lpc = strlen(key);
 
         for (; lpc > 0; lpc--) {
             if (key[lpc] == '_' && stop == 0) {
                 stop = lpc;
 
             } else if (key[lpc] == '_') {
                 char *task_mutable = NULL;
 
                 lpc++;
                 task_mutable = strdup(key + lpc);
                 task_mutable[stop - lpc] = 0;
 
                 crm_trace("Extracted action '%s' from '%s'", task_mutable, key);
                 result = get_complex_task(child, task_mutable, TRUE);
                 free(task_mutable);
                 break;
             }
         }
 
     } else {
         result = get_complex_task(child, action->task, TRUE);
     }
     return result;
 }
 
 enum pe_action_flags
 summary_action_flags(action_t * action, GListPtr children, node_t * node)
 {
     GListPtr gIter = NULL;
     gboolean any_runnable = FALSE;
     gboolean check_runnable = TRUE;
     enum action_tasks task = clone_child_action(action);
     enum pe_action_flags flags = (pe_action_optional | pe_action_runnable | pe_action_pseudo);
     const char *task_s = task2text(task);
 
     for (gIter = children; gIter != NULL; gIter = gIter->next) {
         action_t *child_action = NULL;
         resource_t *child = (resource_t *) gIter->data;
 
         child_action = find_first_action(child->actions, NULL, task_s, child->children ? NULL : node);
         pe_rsc_trace(action->rsc, "Checking for %s in %s on %s (%s)", task_s, child->id,
                      node ? node->details->uname : "none", child_action?child_action->uuid:"NA");
         if (child_action) {
             enum pe_action_flags child_flags = child->cmds->action_flags(child_action, node);
 
             if (is_set(flags, pe_action_optional)
                 && is_set(child_flags, pe_action_optional) == FALSE) {
                 pe_rsc_trace(child, "%s is mandatory because of %s", action->uuid,
                              child_action->uuid);
                 flags = crm_clear_bit(__FUNCTION__, __LINE__, action->rsc->id, flags, pe_action_optional);
                 pe_clear_action_bit(action, pe_action_optional);
             }
             if (is_set(child_flags, pe_action_runnable)) {
                 any_runnable = TRUE;
             }
         }
     }
 
     if (check_runnable && any_runnable == FALSE) {
         pe_rsc_trace(action->rsc, "%s is not runnable because no children are", action->uuid);
         flags = crm_clear_bit(__FUNCTION__, __LINE__, action->rsc->id, flags, pe_action_runnable);
         if (node == NULL) {
             pe_clear_action_bit(action, pe_action_runnable);
         }
     }
 
     return flags;
 }
 
 enum pe_action_flags
 clone_action_flags(action_t * action, node_t * node)
 {
     return summary_action_flags(action, action->rsc->children, node);
 }
 
 void
 clone_rsc_location(resource_t * rsc, rsc_to_node_t * constraint)
 {
     GListPtr gIter = rsc->children;
 
     pe_rsc_trace(rsc, "Processing location constraint %s for %s", constraint->id, rsc->id);
 
     native_rsc_location(rsc, constraint);
 
     for (; gIter != NULL; gIter = gIter->next) {
         resource_t *child_rsc = (resource_t *) gIter->data;
 
         child_rsc->cmds->rsc_location(child_rsc, constraint);
     }
 }
 
 void
 clone_expand(resource_t * rsc, pe_working_set_t * data_set)
 {
     GListPtr gIter = NULL;
     clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, rsc);
 
     gIter = rsc->actions;
     for (; gIter != NULL; gIter = gIter->next) {
         action_t *op = (action_t *) gIter->data;
 
         rsc->cmds->action_flags(op, NULL);
     }
 
     if (clone_data->start_notify) {
         collect_notification_data(rsc, TRUE, TRUE, clone_data->start_notify);
         expand_notification_data(rsc, clone_data->start_notify, data_set);
         create_notifications(rsc, clone_data->start_notify, data_set);
     }
 
     if (clone_data->stop_notify) {
         collect_notification_data(rsc, TRUE, TRUE, clone_data->stop_notify);
         expand_notification_data(rsc, clone_data->stop_notify, data_set);
         create_notifications(rsc, clone_data->stop_notify, data_set);
     }
 
     if (clone_data->promote_notify) {
         collect_notification_data(rsc, TRUE, TRUE, clone_data->promote_notify);
         expand_notification_data(rsc, clone_data->promote_notify, data_set);
         create_notifications(rsc, clone_data->promote_notify, data_set);
     }
 
     if (clone_data->demote_notify) {
         collect_notification_data(rsc, TRUE, TRUE, clone_data->demote_notify);
         expand_notification_data(rsc, clone_data->demote_notify, data_set);
         create_notifications(rsc, clone_data->demote_notify, data_set);
     }
 
     /* Now that the notifcations have been created we can expand the children */
 
     gIter = rsc->children;
     for (; gIter != NULL; gIter = gIter->next) {
         resource_t *child_rsc = (resource_t *) gIter->data;
 
         child_rsc->cmds->expand(child_rsc, data_set);
     }
 
     native_expand(rsc, data_set);
 
     /* The notifications are in the graph now, we can destroy the notify_data */
     free_notification_data(clone_data->demote_notify);
     clone_data->demote_notify = NULL;
     free_notification_data(clone_data->stop_notify);
     clone_data->stop_notify = NULL;
     free_notification_data(clone_data->start_notify);
     clone_data->start_notify = NULL;
     free_notification_data(clone_data->promote_notify);
     clone_data->promote_notify = NULL;
 }
 
 node_t *
 rsc_known_on(resource_t * rsc, GListPtr * list)
 {
     GListPtr gIter = NULL;
     node_t *one = NULL;
     GListPtr result = NULL;
 
     if (rsc->children) {
 
         gIter = rsc->children;
         for (; gIter != NULL; gIter = gIter->next) {
             resource_t *child = (resource_t *) gIter->data;
 
             rsc_known_on(child, &result);
         }
 
     } else if (rsc->known_on) {
         result = g_hash_table_get_values(rsc->known_on);
     }
 
     if (result && g_list_length(result) == 1) {
         one = g_list_nth_data(result, 0);
     }
 
     if (list) {
         GListPtr gIter = NULL;
 
         gIter = result;
         for (; gIter != NULL; gIter = gIter->next) {
             node_t *node = (node_t *) gIter->data;
 
             if (*list == NULL || pe_find_node_id(*list, node->details->id) == NULL) {
                 *list = g_list_prepend(*list, node);
             }
         }
     }
 
     g_list_free(result);
     return one;
 }
 
 static resource_t *
 find_instance_on(resource_t * rsc, node_t * node)
 {
     GListPtr gIter = NULL;
 
     gIter = rsc->children;
     for (; gIter != NULL; gIter = gIter->next) {
         GListPtr gIter2 = NULL;
         GListPtr known_list = NULL;
         resource_t *child = (resource_t *) gIter->data;
 
         rsc_known_on(child, &known_list);
 
         gIter2 = known_list;
         for (; gIter2 != NULL; gIter2 = gIter2->next) {
             node_t *known = (node_t *) gIter2->data;
 
             if (node->details == known->details) {
                 g_list_free(known_list);
                 return child;
             }
         }
         g_list_free(known_list);
     }
 
     return NULL;
 }
 
 gboolean
 clone_create_probe(resource_t * rsc, node_t * node, action_t * complete,
                    gboolean force, pe_working_set_t * data_set)
 {
     GListPtr gIter = NULL;
     gboolean any_created = FALSE;
     clone_variant_data_t *clone_data = NULL;
 
     CRM_ASSERT(rsc);
     get_clone_variant_data(clone_data, rsc);
 
     rsc->children = g_list_sort(rsc->children, sort_rsc_id);
     if (rsc->children == NULL) {
         pe_warn("Clone %s has no children", rsc->id);
         return FALSE;
     }
 
     if (rsc->exclusive_discover) {
         node_t *allowed = g_hash_table_lookup(rsc->allowed_nodes, node->details->id);
         if (allowed && allowed->rsc_discover_mode != pe_discover_exclusive) {
             /* exclusive discover is enabled and this node is not marked
              * as a node this resource should be discovered on
              *
              * remove the node from allowed_nodes so that the
              * notification contains only nodes that we might ever run
              * on
              */
             g_hash_table_remove(rsc->allowed_nodes, node->details->id);
 
             /* Bit of a shortcut - might as well take it */
             return FALSE;
         }
     }
 
     if (is_not_set(rsc->flags, pe_rsc_unique)
         && clone_data->clone_node_max == 1) {
         /* only look for one copy */
         resource_t *child = NULL;
 
         /* Try whoever we probed last time */
         child = find_instance_on(rsc, node);
         if (child) {
             return child->cmds->create_probe(child, node, complete, force, data_set);
         }
 
         /* Try whoever we plan on starting there */
         gIter = rsc->children;
         for (; gIter != NULL; gIter = gIter->next) {
             node_t *local_node = NULL;
             resource_t *child_rsc = (resource_t *) gIter->data;
 
             CRM_ASSERT(child_rsc);
             local_node = child_rsc->fns->location(child_rsc, NULL, FALSE);
             if (local_node == NULL) {
                 continue;
             }
 
             if (local_node->details == node->details) {
                 return child_rsc->cmds->create_probe(child_rsc, node, complete, force, data_set);
             }
         }
 
         /* Fall back to the first clone instance */
         CRM_ASSERT(rsc->children);
         child = rsc->children->data;
         return child->cmds->create_probe(child, node, complete, force, data_set);
     }
 
     gIter = rsc->children;
     for (; gIter != NULL; gIter = gIter->next) {
         resource_t *child_rsc = (resource_t *) gIter->data;
 
         if (child_rsc->cmds->create_probe(child_rsc, node, complete, force, data_set)) {
             any_created = TRUE;
         }
 
         if (any_created && is_not_set(rsc->flags, pe_rsc_unique)
             && clone_data->clone_node_max == 1) {
             /* only look for one copy (clone :0) */
             break;
         }
     }
 
     return any_created;
 }
 
 void
 clone_append_meta(resource_t * rsc, xmlNode * xml)
 {
     char *name = NULL;
     clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, rsc);
 
     name = crm_meta_name(XML_RSC_ATTR_UNIQUE);
     crm_xml_add(xml, name, is_set(rsc->flags, pe_rsc_unique) ? "true" : "false");
     free(name);
 
     name = crm_meta_name(XML_RSC_ATTR_NOTIFY);
     crm_xml_add(xml, name, is_set(rsc->flags, pe_rsc_notify) ? "true" : "false");
     free(name);
 
     name = crm_meta_name(XML_RSC_ATTR_INCARNATION_MAX);
     crm_xml_add_int(xml, name, clone_data->clone_max);
     free(name);
 
     name = crm_meta_name(XML_RSC_ATTR_INCARNATION_NODEMAX);
     crm_xml_add_int(xml, name, clone_data->clone_node_max);
     free(name);
 }
 
 GHashTable *
 clone_merge_weights(resource_t * rsc, const char *rhs, GHashTable * nodes, const char *attr,
                     float factor, enum pe_weights flags)
 {
     return rsc_merge_weights(rsc, rhs, nodes, attr, factor, flags);
 }
diff --git a/pengine/utilization.c b/pengine/utilization.c
index f42c85d992..05f8d78fe2 100644
--- a/pengine/utilization.c
+++ b/pengine/utilization.c
@@ -1,480 +1,481 @@
 /*
  * Copyright (C) 2014 Gao,Yan <ygao@suse.com>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public
  * License as published by the Free Software Foundation; either
  * version 2 of the License, or (at your option) any later version.
  *
  * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * General Public License for more details.
  *
  * You should have received a copy of the GNU General Public
  * License along with this library; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
 #include <crm_internal.h>
 #include <crm/msg_xml.h>
 #include <allocate.h>
 #include <utils.h>
 
 static GListPtr find_colocated_rscs(GListPtr colocated_rscs, resource_t * rsc,
                                     resource_t * orig_rsc);
 
 static GListPtr group_find_colocated_rscs(GListPtr colocated_rscs, resource_t * rsc,
                                           resource_t * orig_rsc);
 
 static void group_add_unallocated_utilization(GHashTable * all_utilization, resource_t * rsc,
                                               GListPtr all_rscs);
 
 struct compare_data {
     const node_t *node1;
     const node_t *node2;
     int result;
 };
 
 static void
 do_compare_capacity1(gpointer key, gpointer value, gpointer user_data)
 {
     int node1_capacity = 0;
     int node2_capacity = 0;
     struct compare_data *data = user_data;
 
     node1_capacity = crm_parse_int(value, "0");
     node2_capacity =
         crm_parse_int(g_hash_table_lookup(data->node2->details->utilization, key), "0");
 
     if (node1_capacity > node2_capacity) {
         data->result--;
     } else if (node1_capacity < node2_capacity) {
         data->result++;
     }
 }
 
 static void
 do_compare_capacity2(gpointer key, gpointer value, gpointer user_data)
 {
     int node1_capacity = 0;
     int node2_capacity = 0;
     struct compare_data *data = user_data;
 
     if (g_hash_table_lookup_extended(data->node1->details->utilization, key, NULL, NULL)) {
         return;
     }
 
     node1_capacity = 0;
     node2_capacity = crm_parse_int(value, "0");
 
     if (node1_capacity > node2_capacity) {
         data->result--;
     } else if (node1_capacity < node2_capacity) {
         data->result++;
     }
 }
 
 /* rc < 0 if 'node1' has more capacity remaining
  * rc > 0 if 'node1' has less capacity remaining
  */
 int
 compare_capacity(const node_t * node1, const node_t * node2)
 {
     struct compare_data data;
 
     data.node1 = node1;
     data.node2 = node2;
     data.result = 0;
 
     g_hash_table_foreach(node1->details->utilization, do_compare_capacity1, &data);
     g_hash_table_foreach(node2->details->utilization, do_compare_capacity2, &data);
 
     return data.result;
 }
 
 struct calculate_data {
     GHashTable *current_utilization;
     gboolean plus;
 };
 
 static void
 do_calculate_utilization(gpointer key, gpointer value, gpointer user_data)
 {
     const char *current = NULL;
     char *result = NULL;
     struct calculate_data *data = user_data;
 
     current = g_hash_table_lookup(data->current_utilization, key);
     if (data->plus) {
         result = crm_itoa(crm_parse_int(current, "0") + crm_parse_int(value, "0"));
         g_hash_table_replace(data->current_utilization, strdup(key), result);
 
     } else if (current) {
         result = crm_itoa(crm_parse_int(current, "0") - crm_parse_int(value, "0"));
         g_hash_table_replace(data->current_utilization, strdup(key), result);
     }
 }
 
 /* Specify 'plus' to FALSE when allocating
  * Otherwise to TRUE when deallocating
  */
 void
 calculate_utilization(GHashTable * current_utilization,
                       GHashTable * utilization, gboolean plus)
 {
     struct calculate_data data;
 
     data.current_utilization = current_utilization;
     data.plus = plus;
 
     g_hash_table_foreach(utilization, do_calculate_utilization, &data);
 }
 
 
 struct capacity_data {
     node_t *node;
     const char *rsc_id;
     gboolean is_enough;
 };
 
 static void
 check_capacity(gpointer key, gpointer value, gpointer user_data)
 {
     int required = 0;
     int remaining = 0;
     struct capacity_data *data = user_data;
 
     required = crm_parse_int(value, "0");
     remaining = crm_parse_int(g_hash_table_lookup(data->node->details->utilization, key), "0");
 
     if (required > remaining) {
         CRM_ASSERT(data->rsc_id);
         CRM_ASSERT(data->node);
 
         crm_debug("Node %s does not have enough %s for %s: required=%d remaining=%d",
                   data->node->details->uname, (char *)key, data->rsc_id, required, remaining);
         data->is_enough = FALSE;
     }
 }
 
 static gboolean
 have_enough_capacity(node_t * node, const char * rsc_id, GHashTable * utilization)
 {
     struct capacity_data data;
 
     data.node = node;
     data.rsc_id = rsc_id;
     data.is_enough = TRUE;
 
     g_hash_table_foreach(utilization, check_capacity, &data);
 
     return data.is_enough;
 }
 
 
 static void
 native_add_unallocated_utilization(GHashTable * all_utilization, resource_t * rsc)
 {
     if(is_set(rsc->flags, pe_rsc_provisional) == FALSE) {
         return;
     }
 
     calculate_utilization(all_utilization, rsc->utilization, TRUE);
 }
 
 static void
 add_unallocated_utilization(GHashTable * all_utilization, resource_t * rsc,
                     GListPtr all_rscs, resource_t * orig_rsc)
 {
     if(is_set(rsc->flags, pe_rsc_provisional) == FALSE) {
         return;
     }
 
     if (rsc->variant == pe_native) {
         pe_rsc_trace(orig_rsc, "%s: Adding %s as colocated utilization",
                      orig_rsc->id, rsc->id);
         native_add_unallocated_utilization(all_utilization, rsc);
 
     } else if (rsc->variant == pe_group) {
         pe_rsc_trace(orig_rsc, "%s: Adding %s as colocated utilization",
                      orig_rsc->id, rsc->id);
         group_add_unallocated_utilization(all_utilization, rsc, all_rscs);
 
     } else if (pe_rsc_is_clone(rsc)) {
         GListPtr gIter1 = NULL;
         gboolean existing = FALSE;
 
         /* Check if there's any child already existing in the list */
         gIter1 = rsc->children;
         for (; gIter1 != NULL; gIter1 = gIter1->next) {
             resource_t *child = (resource_t *) gIter1->data;
             GListPtr gIter2 = NULL;
 
             if (g_list_find(all_rscs, child)) {
                 existing = TRUE;
 
             } else {
                 /* Check if there's any child of another cloned group already existing in the list */
                 gIter2 = child->children;
                 for (; gIter2 != NULL; gIter2 = gIter2->next) {
                     resource_t *grandchild = (resource_t *) gIter2->data;
 
                     if (g_list_find(all_rscs, grandchild)) {
                         pe_rsc_trace(orig_rsc, "%s: Adding %s as colocated utilization",
                                      orig_rsc->id, child->id);
                         add_unallocated_utilization(all_utilization, child, all_rscs, orig_rsc);
                         existing = TRUE;
                         break;
                     }
                 }
             }
         }
 
         if (existing == FALSE) {
             resource_t *first_child = (resource_t *) rsc->children->data;
 
             pe_rsc_trace(orig_rsc, "%s: Adding %s as colocated utilization",
                          orig_rsc->id, ID(first_child->xml));
             add_unallocated_utilization(all_utilization, first_child, all_rscs, orig_rsc);
         }
     }
 }
 
 static GHashTable *
 sum_unallocated_utilization(resource_t * rsc, GListPtr colocated_rscs)
 {
     GListPtr gIter = NULL;
     GListPtr all_rscs = NULL;
     GHashTable *all_utilization = crm_str_table_new();
 
     all_rscs = g_list_copy(colocated_rscs);
     if (g_list_find(all_rscs, rsc) == FALSE) {
         all_rscs = g_list_append(all_rscs, rsc);
     }
 
     for (gIter = all_rscs; gIter != NULL; gIter = gIter->next) {
         resource_t *listed_rsc = (resource_t *) gIter->data;
 
         if(is_set(listed_rsc->flags, pe_rsc_provisional) == FALSE) {
             continue;
         }
 
         pe_rsc_trace(rsc, "%s: Processing unallocated colocated %s", rsc->id, listed_rsc->id);
         add_unallocated_utilization(all_utilization, listed_rsc, all_rscs, rsc);
     }
 
     g_list_free(all_rscs);
 
     return all_utilization;
 }
 
 static GListPtr
 find_colocated_rscs(GListPtr colocated_rscs, resource_t * rsc, resource_t * orig_rsc)
 {
     GListPtr gIter = NULL;
 
     if (rsc == NULL) {
         return colocated_rscs;
 
     } else if (g_list_find(colocated_rscs, rsc)) {
         return colocated_rscs;
     }
 
     crm_trace("%s: %s is supposed to be colocated with %s", orig_rsc->id, rsc->id, orig_rsc->id);
     colocated_rscs = g_list_append(colocated_rscs, rsc);
 
     for (gIter = rsc->rsc_cons; gIter != NULL; gIter = gIter->next) {
         rsc_colocation_t *constraint = (rsc_colocation_t *) gIter->data;
         resource_t *rsc_rh = constraint->rsc_rh;
 
         /* Break colocation loop */
         if (rsc_rh == orig_rsc) {
             continue;
         }
 
         if (constraint->score == INFINITY
             && filter_colocation_constraint(rsc, rsc_rh, constraint, TRUE) == influence_rsc_location) {
 
             if (rsc_rh->variant == pe_group) {
                 /* Need to use group_variant_data */
                 colocated_rscs = group_find_colocated_rscs(colocated_rscs, rsc_rh, orig_rsc);
 
             } else {
                 colocated_rscs = find_colocated_rscs(colocated_rscs, rsc_rh, orig_rsc);
             }
         }
     }
 
     for (gIter = rsc->rsc_cons_lhs; gIter != NULL; gIter = gIter->next) {
         rsc_colocation_t *constraint = (rsc_colocation_t *) gIter->data;
         resource_t *rsc_lh = constraint->rsc_lh;
 
         /* Break colocation loop */
         if (rsc_lh == orig_rsc) {
             continue;
         }
 
         if (pe_rsc_is_clone(rsc_lh) == FALSE && pe_rsc_is_clone(rsc)) {
             /* We do not know if rsc_lh will be colocated with orig_rsc in this case */
             continue;
         }
 
         if (constraint->score == INFINITY
             && filter_colocation_constraint(rsc_lh, rsc, constraint, TRUE) == influence_rsc_location) {
 
             if (rsc_lh->variant == pe_group) {
                 /* Need to use group_variant_data */
                 colocated_rscs = group_find_colocated_rscs(colocated_rscs, rsc_lh, orig_rsc);
 
             } else {
                 colocated_rscs = find_colocated_rscs(colocated_rscs, rsc_lh, orig_rsc);
             }
         }
     }
 
     return colocated_rscs;
 }
 
 void
 process_utilization(resource_t * rsc, node_t ** prefer, pe_working_set_t * data_set)
 {
     int alloc_details = scores_log_level + 1;
 
+    CRM_CHECK(rsc && prefer && data_set, return);
     if (safe_str_neq(data_set->placement_strategy, "default")) {
         GHashTableIter iter;
         GListPtr colocated_rscs = NULL;
         gboolean any_capable = FALSE;
         node_t *node = NULL;
 
         colocated_rscs = find_colocated_rscs(colocated_rscs, rsc, rsc);
         if (colocated_rscs) {
             GHashTable *unallocated_utilization = NULL;
             char *rscs_id = crm_concat(rsc->id, "and its colocated resources", ' ');
             node_t *most_capable_node = NULL;
 
             unallocated_utilization = sum_unallocated_utilization(rsc, colocated_rscs);
 
             g_hash_table_iter_init(&iter, rsc->allowed_nodes);
             while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
                 if (can_run_resources(node) == FALSE || node->weight < 0) {
                     continue;
                 }
 
                 if (have_enough_capacity(node, rscs_id, unallocated_utilization)) {
                     any_capable = TRUE;
                 }
 
                 if (most_capable_node == NULL ||
                     compare_capacity(node, most_capable_node) < 0) {
                     /* < 0 means 'node' is more capable */
                     most_capable_node = node;
                 }
             }
 
             if (any_capable) {
                 g_hash_table_iter_init(&iter, rsc->allowed_nodes);
                 while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
                     if (can_run_resources(node) == FALSE || node->weight < 0) {
                         continue;
                     }
 
                     if (have_enough_capacity(node, rscs_id, unallocated_utilization) == FALSE) {
                         pe_rsc_debug(rsc,
                                      "Resource %s and its colocated resources"
                                      " cannot be allocated to node %s: not enough capacity",
                                      rsc->id, node->details->uname);
                         resource_location(rsc, node, -INFINITY, "__limit_utilization__", data_set);
                     }
                 }
 
             } else if (*prefer == NULL) {
                 *prefer = most_capable_node;
             }
 
             if (unallocated_utilization) {
                 g_hash_table_destroy(unallocated_utilization);
             }
 
             g_list_free(colocated_rscs);
             free(rscs_id);
         }
 
         if (any_capable == FALSE) {
             g_hash_table_iter_init(&iter, rsc->allowed_nodes);
             while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
                 if (can_run_resources(node) == FALSE || node->weight < 0) {
                     continue;
                 }
 
                 if (have_enough_capacity(node, rsc->id, rsc->utilization) == FALSE) {
                     pe_rsc_debug(rsc,
                                  "Resource %s cannot be allocated to node %s:"
                                  " not enough capacity",
                                  rsc->id, node->details->uname);
                     resource_location(rsc, node, -INFINITY, "__limit_utilization__", data_set);
                 }
             }
         }
         dump_node_scores(alloc_details, rsc, "Post-utilization", rsc->allowed_nodes);
     }
 }
 
 #define VARIANT_GROUP 1
 #include <lib/pengine/variant.h>
 
 GListPtr
 group_find_colocated_rscs(GListPtr colocated_rscs, resource_t * rsc, resource_t * orig_rsc)
 {
     group_variant_data_t *group_data = NULL;
 
     get_group_variant_data(group_data, rsc);
     if (group_data->colocated || pe_rsc_is_clone(rsc->parent)) {
         GListPtr gIter = rsc->children;
 
         for (; gIter != NULL; gIter = gIter->next) {
             resource_t *child_rsc = (resource_t *) gIter->data;
 
             colocated_rscs = find_colocated_rscs(colocated_rscs, child_rsc, orig_rsc);
         }
 
     } else {
         if (group_data->first_child) {
             colocated_rscs = find_colocated_rscs(colocated_rscs, group_data->first_child, orig_rsc);
         }
     }
 
     colocated_rscs = find_colocated_rscs(colocated_rscs, rsc, orig_rsc);
 
     return colocated_rscs;
 }
 
 static void
 group_add_unallocated_utilization(GHashTable * all_utilization, resource_t * rsc,
                                   GListPtr all_rscs)
 {
     group_variant_data_t *group_data = NULL;
 
     get_group_variant_data(group_data, rsc);
     if (group_data->colocated || pe_rsc_is_clone(rsc->parent)) {
         GListPtr gIter = rsc->children;
 
         for (; gIter != NULL; gIter = gIter->next) {
             resource_t *child_rsc = (resource_t *) gIter->data;
 
             if (is_set(child_rsc->flags, pe_rsc_provisional) &&
                 g_list_find(all_rscs, child_rsc) == FALSE) {
                 native_add_unallocated_utilization(all_utilization, child_rsc);
             }
         }
 
     } else {
         if (group_data->first_child &&
             is_set(group_data->first_child->flags, pe_rsc_provisional) &&
             g_list_find(all_rscs, group_data->first_child) == FALSE) {
             native_add_unallocated_utilization(all_utilization, group_data->first_child);
         }
     }
 }