Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F3687397
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
230 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/daemons/fenced/fenced_scheduler.c b/daemons/fenced/fenced_scheduler.c
index 27d990f2a9..a7f404801b 100644
--- a/daemons/fenced/fenced_scheduler.c
+++ b/daemons/fenced/fenced_scheduler.c
@@ -1,225 +1,225 @@
/*
* Copyright 2009-2023 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdio.h>
#include <errno.h>
#include <glib.h>
#include <crm/pengine/status.h>
#include <crm/pengine/internal.h>
#include <pacemaker-internal.h>
#include <pacemaker-fenced.h>
static pcmk_scheduler_t *scheduler = NULL;
/*!
* \internal
* \brief Initialize scheduler data for fencer purposes
*
* \return Standard Pacemaker return code
*/
int
fenced_scheduler_init(void)
{
pcmk__output_t *logger = NULL;
int rc = pcmk__log_output_new(&logger);
if (rc != pcmk_rc_ok) {
return rc;
}
scheduler = pe_new_working_set();
if (scheduler == NULL) {
pcmk__output_free(logger);
return ENOMEM;
}
pe__register_messages(logger);
pcmk__register_lib_messages(logger);
pcmk__output_set_log_level(logger, LOG_TRACE);
scheduler->priv = logger;
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Free all scheduler-related resources
*/
void
fenced_scheduler_cleanup(void)
{
if (scheduler != NULL) {
pcmk__output_t *logger = scheduler->priv;
if (logger != NULL) {
logger->finish(logger, CRM_EX_OK, true, NULL);
pcmk__output_free(logger);
scheduler->priv = NULL;
}
pe_free_working_set(scheduler);
scheduler = NULL;
}
}
/*!
* \internal
* \brief Check whether the local node is in a resource's allowed node list
*
* \param[in] rsc Resource to check
*
* \return Pointer to node if found, otherwise NULL
*/
static pcmk_node_t *
local_node_allowed_for(const pcmk_resource_t *rsc)
{
if ((rsc != NULL) && (stonith_our_uname != NULL)) {
GHashTableIter iter;
pcmk_node_t *node = NULL;
g_hash_table_iter_init(&iter, rsc->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
if (pcmk__str_eq(node->details->uname, stonith_our_uname,
pcmk__str_casei)) {
return node;
}
}
}
return NULL;
}
/*!
* \internal
* \brief If a given resource or any of its children are fencing devices,
* register the devices
*
* \param[in,out] data Resource to check
* \param[in,out] user_data Ignored
*/
static void
register_if_fencing_device(gpointer data, gpointer user_data)
{
pcmk_resource_t *rsc = data;
xmlNode *xml = NULL;
GHashTableIter hash_iter;
pcmk_node_t *node = NULL;
const char *name = NULL;
const char *value = NULL;
const char *rclass = NULL;
const char *agent = NULL;
const char *rsc_provides = NULL;
stonith_key_value_t *params = NULL;
// If this is a collective resource, check children instead
if (rsc->children != NULL) {
for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
register_if_fencing_device(iter->data, NULL);
if (pe_rsc_is_clone(rsc)) {
return; // Only one instance needs to be checked for clones
}
}
return;
}
rclass = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS);
if (!pcmk__str_eq(rclass, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
return; // Not a fencing device
}
if (pe__resource_is_disabled(rsc)) {
crm_info("Ignoring fencing device %s because it is disabled", rsc->id);
return;
}
if ((stonith_watchdog_timeout_ms <= 0) &&
pcmk__str_eq(rsc->id, STONITH_WATCHDOG_ID, pcmk__str_none)) {
crm_info("Ignoring fencing device %s "
"because watchdog fencing is disabled", rsc->id);
return;
}
// Check whether local node is allowed to run resource
node = local_node_allowed_for(rsc);
if (node == NULL) {
crm_info("Ignoring fencing device %s "
"because local node is not allowed to run it", rsc->id);
return;
}
if (node->weight < 0) {
crm_info("Ignoring fencing device %s "
"because local node has preference %s for it",
rsc->id, pcmk_readable_score(node->weight));
return;
}
// If device is in a group, check whether local node is allowed for group
if ((rsc->parent != NULL)
&& (rsc->parent->variant == pcmk_rsc_variant_group)) {
pcmk_node_t *group_node = local_node_allowed_for(rsc->parent);
if ((group_node != NULL) && (group_node->weight < 0)) {
crm_info("Ignoring fencing device %s "
"because local node has preference %s for its group",
rsc->id, pcmk_readable_score(group_node->weight));
return;
}
}
crm_debug("Reloading configuration of fencing device %s", rsc->id);
- agent = crm_element_value(rsc->xml, XML_EXPR_ATTR_TYPE);
+ agent = crm_element_value(rsc->xml, XML_ATTR_TYPE);
get_meta_attributes(rsc->meta, rsc, node, scheduler);
rsc_provides = g_hash_table_lookup(rsc->meta, PCMK_STONITH_PROVIDES);
g_hash_table_iter_init(&hash_iter, pe_rsc_params(rsc, node, scheduler));
while (g_hash_table_iter_next(&hash_iter, (gpointer *) &name,
(gpointer *) &value)) {
if ((name == NULL) || (value == NULL)) {
continue;
}
params = stonith_key_value_add(params, name, value);
}
xml = create_device_registration_xml(pcmk__s(rsc->clone_name, rsc->id),
st_namespace_any, agent, params,
rsc_provides);
stonith_key_value_freeall(params, 1, 1);
CRM_ASSERT(stonith_device_register(xml, TRUE) == pcmk_ok);
free_xml(xml);
}
/*!
* \internal
* \brief Run the scheduler for fencer purposes
*
* \param[in] cib Cluster's current CIB
*/
void
fenced_scheduler_run(xmlNode *cib)
{
CRM_CHECK((cib != NULL) && (scheduler != NULL), return);
if (scheduler->now != NULL) {
crm_time_free(scheduler->now);
scheduler->now = NULL;
}
scheduler->localhost = stonith_our_uname;
pcmk__schedule_actions(cib, pcmk_sched_location_only
|pcmk_sched_no_compat
|pcmk_sched_no_counts, scheduler);
g_list_foreach(scheduler->resources, register_if_fencing_device, NULL);
scheduler->input = NULL; // Wasn't a copy, so don't let API free it
pe_reset_working_set(scheduler);
}
diff --git a/lib/pengine/complex.c b/lib/pengine/complex.c
index 8bdd229872..50198448f9 100644
--- a/lib/pengine/complex.c
+++ b/lib/pengine/complex.c
@@ -1,1201 +1,1201 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <crm/pengine/rules.h>
#include <crm/pengine/internal.h>
#include <crm/msg_xml.h>
#include <crm/common/xml_internal.h>
#include <crm/common/scheduler_internal.h>
#include "pe_status_private.h"
void populate_hash(xmlNode * nvpair_list, GHashTable * hash, const char **attrs, int attrs_length);
static pcmk_node_t *active_node(const pcmk_resource_t *rsc,
unsigned int *count_all,
unsigned int *count_clean);
pcmk_rsc_methods_t resource_class_functions[] = {
{
native_unpack,
native_find_rsc,
native_parameter,
native_print,
native_active,
native_resource_state,
native_location,
native_free,
pe__count_common,
pe__native_is_filtered,
active_node,
pe__primitive_max_per_node,
},
{
group_unpack,
native_find_rsc,
native_parameter,
group_print,
group_active,
group_resource_state,
native_location,
group_free,
pe__count_common,
pe__group_is_filtered,
active_node,
pe__group_max_per_node,
},
{
clone_unpack,
native_find_rsc,
native_parameter,
clone_print,
clone_active,
clone_resource_state,
native_location,
clone_free,
pe__count_common,
pe__clone_is_filtered,
active_node,
pe__clone_max_per_node,
},
{
pe__unpack_bundle,
native_find_rsc,
native_parameter,
pe__print_bundle,
pe__bundle_active,
pe__bundle_resource_state,
native_location,
pe__free_bundle,
pe__count_bundle,
pe__bundle_is_filtered,
pe__bundle_active_node,
pe__bundle_max_per_node,
}
};
static enum pe_obj_types
get_resource_type(const char *name)
{
if (pcmk__str_eq(name, XML_CIB_TAG_RESOURCE, pcmk__str_casei)) {
return pcmk_rsc_variant_primitive;
} else if (pcmk__str_eq(name, XML_CIB_TAG_GROUP, pcmk__str_casei)) {
return pcmk_rsc_variant_group;
} else if (pcmk__str_eq(name, XML_CIB_TAG_INCARNATION, pcmk__str_casei)) {
return pcmk_rsc_variant_clone;
} else if (pcmk__str_eq(name, PCMK_XE_PROMOTABLE_LEGACY, pcmk__str_casei)) {
// @COMPAT deprecated since 2.0.0
return pcmk_rsc_variant_clone;
} else if (pcmk__str_eq(name, XML_CIB_TAG_CONTAINER, pcmk__str_casei)) {
return pcmk_rsc_variant_bundle;
}
return pcmk_rsc_variant_unknown;
}
static void
dup_attr(gpointer key, gpointer value, gpointer user_data)
{
add_hash_param(user_data, key, value);
}
static void
expand_parents_fixed_nvpairs(pcmk_resource_t *rsc,
pe_rule_eval_data_t *rule_data,
GHashTable *meta_hash, pcmk_scheduler_t *scheduler)
{
GHashTable *parent_orig_meta = pcmk__strkey_table(free, free);
pcmk_resource_t *p = rsc->parent;
if (p == NULL) {
return ;
}
/* Search all parent resources, get the fixed value of "meta_attributes" set only in the original xml, and stack it in the hash table. */
/* The fixed value of the lower parent resource takes precedence and is not overwritten. */
while(p != NULL) {
/* A hash table for comparison is generated, including the id-ref. */
pe__unpack_dataset_nvpairs(p->xml, XML_TAG_META_SETS, rule_data,
parent_orig_meta, NULL, FALSE, scheduler);
p = p->parent;
}
/* If there is a fixed value of "meta_attributes" of the parent resource, it will be processed. */
if (parent_orig_meta != NULL) {
GHashTableIter iter;
char *key = NULL;
char *value = NULL;
g_hash_table_iter_init(&iter, parent_orig_meta);
while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value)) {
/* Parameters set in the original xml of the parent resource will also try to overwrite the child resource. */
/* Attributes that already exist in the child lease are not updated. */
dup_attr(key, value, meta_hash);
}
}
if (parent_orig_meta != NULL) {
g_hash_table_destroy(parent_orig_meta);
}
return ;
}
void
get_meta_attributes(GHashTable * meta_hash, pcmk_resource_t * rsc,
pcmk_node_t *node, pcmk_scheduler_t *scheduler)
{
pe_rsc_eval_data_t rsc_rule_data = {
.standard = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS),
.provider = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER),
- .agent = crm_element_value(rsc->xml, XML_EXPR_ATTR_TYPE)
+ .agent = crm_element_value(rsc->xml, XML_ATTR_TYPE)
};
pe_rule_eval_data_t rule_data = {
.node_hash = NULL,
.role = pcmk_role_unknown,
.now = scheduler->now,
.match_data = NULL,
.rsc_data = &rsc_rule_data,
.op_data = NULL
};
if (node) {
rule_data.node_hash = node->details->attrs;
}
for (xmlAttrPtr a = pcmk__xe_first_attr(rsc->xml); a != NULL; a = a->next) {
const char *prop_name = (const char *) a->name;
const char *prop_value = pcmk__xml_attr_value(a);
add_hash_param(meta_hash, prop_name, prop_value);
}
pe__unpack_dataset_nvpairs(rsc->xml, XML_TAG_META_SETS, &rule_data,
meta_hash, NULL, FALSE, scheduler);
/* Set the "meta_attributes" explicitly set in the parent resource to the hash table of the child resource. */
/* If it is already explicitly set as a child, it will not be overwritten. */
if (rsc->parent != NULL) {
expand_parents_fixed_nvpairs(rsc, &rule_data, meta_hash, scheduler);
}
/* check the defaults */
pe__unpack_dataset_nvpairs(scheduler->rsc_defaults, XML_TAG_META_SETS,
&rule_data, meta_hash, NULL, FALSE, scheduler);
/* If there is "meta_attributes" that the parent resource has not explicitly set, set a value that is not set from rsc_default either. */
/* The values already set up to this point will not be overwritten. */
if (rsc->parent) {
g_hash_table_foreach(rsc->parent->meta, dup_attr, meta_hash);
}
}
void
get_rsc_attributes(GHashTable *meta_hash, const pcmk_resource_t *rsc,
const pcmk_node_t *node, pcmk_scheduler_t *scheduler)
{
pe_rule_eval_data_t rule_data = {
.node_hash = NULL,
.role = pcmk_role_unknown,
.now = scheduler->now,
.match_data = NULL,
.rsc_data = NULL,
.op_data = NULL
};
if (node) {
rule_data.node_hash = node->details->attrs;
}
pe__unpack_dataset_nvpairs(rsc->xml, XML_TAG_ATTR_SETS, &rule_data,
meta_hash, NULL, FALSE, scheduler);
/* set anything else based on the parent */
if (rsc->parent != NULL) {
get_rsc_attributes(meta_hash, rsc->parent, node, scheduler);
} else {
/* and finally check the defaults */
pe__unpack_dataset_nvpairs(scheduler->rsc_defaults, XML_TAG_ATTR_SETS,
&rule_data, meta_hash, NULL, FALSE,
scheduler);
}
}
static char *
template_op_key(xmlNode * op)
{
const char *name = crm_element_value(op, "name");
const char *role = crm_element_value(op, "role");
char *key = NULL;
if ((role == NULL)
|| pcmk__strcase_any_of(role, PCMK__ROLE_STARTED, PCMK__ROLE_UNPROMOTED,
PCMK__ROLE_UNPROMOTED_LEGACY, NULL)) {
role = PCMK__ROLE_UNKNOWN;
}
key = crm_strdup_printf("%s-%s", name, role);
return key;
}
static gboolean
unpack_template(xmlNode *xml_obj, xmlNode **expanded_xml,
pcmk_scheduler_t *scheduler)
{
xmlNode *cib_resources = NULL;
xmlNode *template = NULL;
xmlNode *new_xml = NULL;
xmlNode *child_xml = NULL;
xmlNode *rsc_ops = NULL;
xmlNode *template_ops = NULL;
const char *template_ref = NULL;
const char *id = NULL;
if (xml_obj == NULL) {
pcmk__config_err("No resource object for template unpacking");
return FALSE;
}
template_ref = crm_element_value(xml_obj, XML_CIB_TAG_RSC_TEMPLATE);
if (template_ref == NULL) {
return TRUE;
}
id = ID(xml_obj);
if (id == NULL) {
pcmk__config_err("'%s' object must have a id", xml_obj->name);
return FALSE;
}
if (pcmk__str_eq(template_ref, id, pcmk__str_none)) {
pcmk__config_err("The resource object '%s' should not reference itself",
id);
return FALSE;
}
cib_resources = get_xpath_object("//" XML_CIB_TAG_RESOURCES,
scheduler->input, LOG_TRACE);
if (cib_resources == NULL) {
pcmk__config_err("No resources configured");
return FALSE;
}
template = pcmk__xe_match(cib_resources, XML_CIB_TAG_RSC_TEMPLATE,
PCMK_XA_ID, template_ref);
if (template == NULL) {
pcmk__config_err("No template named '%s'", template_ref);
return FALSE;
}
new_xml = copy_xml(template);
xmlNodeSetName(new_xml, xml_obj->name);
crm_xml_add(new_xml, PCMK_XA_ID, id);
crm_xml_add(new_xml, PCMK__META_CLONE_INSTANCE_NUM,
crm_element_value(xml_obj, PCMK__META_CLONE_INSTANCE_NUM));
template_ops = find_xml_node(new_xml, "operations", FALSE);
for (child_xml = pcmk__xe_first_child(xml_obj); child_xml != NULL;
child_xml = pcmk__xe_next(child_xml)) {
xmlNode *new_child = NULL;
new_child = add_node_copy(new_xml, child_xml);
if (pcmk__str_eq((const char *)new_child->name, "operations", pcmk__str_none)) {
rsc_ops = new_child;
}
}
if (template_ops && rsc_ops) {
xmlNode *op = NULL;
GHashTable *rsc_ops_hash = pcmk__strkey_table(free, NULL);
for (op = pcmk__xe_first_child(rsc_ops); op != NULL;
op = pcmk__xe_next(op)) {
char *key = template_op_key(op);
g_hash_table_insert(rsc_ops_hash, key, op);
}
for (op = pcmk__xe_first_child(template_ops); op != NULL;
op = pcmk__xe_next(op)) {
char *key = template_op_key(op);
if (g_hash_table_lookup(rsc_ops_hash, key) == NULL) {
add_node_copy(rsc_ops, op);
}
free(key);
}
if (rsc_ops_hash) {
g_hash_table_destroy(rsc_ops_hash);
}
free_xml(template_ops);
}
/*free_xml(*expanded_xml); */
*expanded_xml = new_xml;
#if 0 /* Disable multi-level templates for now */
if (!unpack_template(new_xml, expanded_xml, scheduler)) {
free_xml(*expanded_xml);
*expanded_xml = NULL;
return FALSE;
}
#endif
return TRUE;
}
static gboolean
add_template_rsc(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
{
const char *template_ref = NULL;
const char *id = NULL;
if (xml_obj == NULL) {
pcmk__config_err("No resource object for processing resource list "
"of template");
return FALSE;
}
template_ref = crm_element_value(xml_obj, XML_CIB_TAG_RSC_TEMPLATE);
if (template_ref == NULL) {
return TRUE;
}
id = ID(xml_obj);
if (id == NULL) {
pcmk__config_err("'%s' object must have a id", xml_obj->name);
return FALSE;
}
if (pcmk__str_eq(template_ref, id, pcmk__str_none)) {
pcmk__config_err("The resource object '%s' should not reference itself",
id);
return FALSE;
}
if (add_tag_ref(scheduler->template_rsc_sets, template_ref, id) == FALSE) {
return FALSE;
}
return TRUE;
}
static bool
detect_promotable(pcmk_resource_t *rsc)
{
const char *promotable = g_hash_table_lookup(rsc->meta,
PCMK_META_PROMOTABLE);
if (crm_is_true(promotable)) {
return TRUE;
}
// @COMPAT deprecated since 2.0.0
if (pcmk__xe_is(rsc->xml, PCMK_XE_PROMOTABLE_LEGACY)) {
/* @TODO in some future version, pcmk__warn_once() here,
* then drop support in even later version
*/
g_hash_table_insert(rsc->meta, strdup(PCMK_META_PROMOTABLE),
strdup(XML_BOOLEAN_TRUE));
return TRUE;
}
return FALSE;
}
static void
free_params_table(gpointer data)
{
g_hash_table_destroy((GHashTable *) data);
}
/*!
* \brief Get a table of resource parameters
*
* \param[in,out] rsc Resource to query
* \param[in] node Node for evaluating rules (NULL for defaults)
* \param[in,out] scheduler Scheduler data
*
* \return Hash table containing resource parameter names and values
* (or NULL if \p rsc or \p scheduler is NULL)
* \note The returned table will be destroyed when the resource is freed, so
* callers should not destroy it.
*/
GHashTable *
pe_rsc_params(pcmk_resource_t *rsc, const pcmk_node_t *node,
pcmk_scheduler_t *scheduler)
{
GHashTable *params_on_node = NULL;
/* A NULL node is used to request the resource's default parameters
* (not evaluated for node), but we always want something non-NULL
* as a hash table key.
*/
const char *node_name = "";
// Sanity check
if ((rsc == NULL) || (scheduler == NULL)) {
return NULL;
}
if ((node != NULL) && (node->details->uname != NULL)) {
node_name = node->details->uname;
}
// Find the parameter table for given node
if (rsc->parameter_cache == NULL) {
rsc->parameter_cache = pcmk__strikey_table(free, free_params_table);
} else {
params_on_node = g_hash_table_lookup(rsc->parameter_cache, node_name);
}
// If none exists yet, create one with parameters evaluated for node
if (params_on_node == NULL) {
params_on_node = pcmk__strkey_table(free, free);
get_rsc_attributes(params_on_node, rsc, node, scheduler);
g_hash_table_insert(rsc->parameter_cache, strdup(node_name),
params_on_node);
}
return params_on_node;
}
/*!
* \internal
* \brief Unpack a resource's "requires" meta-attribute
*
* \param[in,out] rsc Resource being unpacked
* \param[in] value Value of "requires" meta-attribute
* \param[in] is_default Whether \p value was selected by default
*/
static void
unpack_requires(pcmk_resource_t *rsc, const char *value, bool is_default)
{
if (pcmk__str_eq(value, PCMK__VALUE_NOTHING, pcmk__str_casei)) {
} else if (pcmk__str_eq(value, PCMK__VALUE_QUORUM, pcmk__str_casei)) {
pe__set_resource_flags(rsc, pcmk_rsc_needs_quorum);
} else if (pcmk__str_eq(value, PCMK__VALUE_FENCING, pcmk__str_casei)) {
pe__set_resource_flags(rsc, pcmk_rsc_needs_fencing);
if (!pcmk_is_set(rsc->cluster->flags, pcmk_sched_fencing_enabled)) {
pcmk__config_warn("%s requires fencing but fencing is disabled",
rsc->id);
}
} else if (pcmk__str_eq(value, PCMK__VALUE_UNFENCING, pcmk__str_casei)) {
if (pcmk_is_set(rsc->flags, pcmk_rsc_fence_device)) {
pcmk__config_warn("Resetting \"" PCMK_META_REQUIRES "\" for %s "
"to \"" PCMK__VALUE_QUORUM "\" because fencing "
"devices cannot require unfencing", rsc->id);
unpack_requires(rsc, PCMK__VALUE_QUORUM, true);
return;
} else if (!pcmk_is_set(rsc->cluster->flags,
pcmk_sched_fencing_enabled)) {
pcmk__config_warn("Resetting \"" PCMK_META_REQUIRES "\" for %s "
"to \"" PCMK__VALUE_QUORUM "\" because fencing "
"is disabled", rsc->id);
unpack_requires(rsc, PCMK__VALUE_QUORUM, true);
return;
} else {
pe__set_resource_flags(rsc, pcmk_rsc_needs_fencing
|pcmk_rsc_needs_unfencing);
}
} else {
const char *orig_value = value;
if (pcmk_is_set(rsc->flags, pcmk_rsc_fence_device)) {
value = PCMK__VALUE_QUORUM;
} else if ((rsc->variant == pcmk_rsc_variant_primitive)
&& xml_contains_remote_node(rsc->xml)) {
value = PCMK__VALUE_QUORUM;
} else if (pcmk_is_set(rsc->cluster->flags,
pcmk_sched_enable_unfencing)) {
value = PCMK__VALUE_UNFENCING;
} else if (pcmk_is_set(rsc->cluster->flags,
pcmk_sched_fencing_enabled)) {
value = PCMK__VALUE_FENCING;
} else if (rsc->cluster->no_quorum_policy == pcmk_no_quorum_ignore) {
value = PCMK__VALUE_NOTHING;
} else {
value = PCMK__VALUE_QUORUM;
}
if (orig_value != NULL) {
pcmk__config_err("Resetting '" PCMK_META_REQUIRES "' for %s "
"to '%s' because '%s' is not valid",
rsc->id, value, orig_value);
}
unpack_requires(rsc, value, true);
return;
}
pcmk__rsc_trace(rsc, "\tRequired to start: %s%s", value,
(is_default? " (default)" : ""));
}
#ifndef PCMK__COMPAT_2_0
static void
warn_about_deprecated_classes(pcmk_resource_t *rsc)
{
const char *std = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS);
if (pcmk__str_eq(std, PCMK_RESOURCE_CLASS_UPSTART, pcmk__str_none)) {
pcmk__warn_once(pcmk__wo_upstart,
"Support for Upstart resources (such as %s) is "
"deprecated and will be removed in a future release",
rsc->id);
} else if (pcmk__str_eq(std, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_none)) {
pcmk__warn_once(pcmk__wo_nagios,
"Support for Nagios resources (such as %s) is "
"deprecated and will be removed in a future release",
rsc->id);
}
}
#endif
/*!
* \internal
* \brief Unpack configuration XML for a given resource
*
* Unpack the XML object containing a resource's configuration into a new
* \c pcmk_resource_t object.
*
* \param[in] xml_obj XML node containing the resource's configuration
* \param[out] rsc Where to store the unpacked resource information
* \param[in] parent Resource's parent, if any
* \param[in,out] scheduler Scheduler data
*
* \return Standard Pacemaker return code
* \note If pcmk_rc_ok is returned, \p *rsc is guaranteed to be non-NULL, and
* the caller is responsible for freeing it using its variant-specific
* free() method. Otherwise, \p *rsc is guaranteed to be NULL.
*/
int
pe__unpack_resource(xmlNode *xml_obj, pcmk_resource_t **rsc,
pcmk_resource_t *parent, pcmk_scheduler_t *scheduler)
{
xmlNode *expanded_xml = NULL;
xmlNode *ops = NULL;
const char *value = NULL;
const char *id = NULL;
bool guest_node = false;
bool remote_node = false;
pe_rule_eval_data_t rule_data = {
.node_hash = NULL,
.role = pcmk_role_unknown,
.now = NULL,
.match_data = NULL,
.rsc_data = NULL,
.op_data = NULL
};
CRM_CHECK(rsc != NULL, return EINVAL);
CRM_CHECK((xml_obj != NULL) && (scheduler != NULL),
*rsc = NULL;
return EINVAL);
rule_data.now = scheduler->now;
crm_log_xml_trace(xml_obj, "[raw XML]");
id = crm_element_value(xml_obj, PCMK_XA_ID);
if (id == NULL) {
pcmk__config_err("Ignoring <%s> configuration without " PCMK_XA_ID,
xml_obj->name);
return pcmk_rc_unpack_error;
}
if (unpack_template(xml_obj, &expanded_xml, scheduler) == FALSE) {
return pcmk_rc_unpack_error;
}
*rsc = calloc(1, sizeof(pcmk_resource_t));
if (*rsc == NULL) {
pcmk__sched_err("Unable to allocate memory for resource '%s'", id);
return ENOMEM;
}
(*rsc)->cluster = scheduler;
if (expanded_xml) {
crm_log_xml_trace(expanded_xml, "[expanded XML]");
(*rsc)->xml = expanded_xml;
(*rsc)->orig_xml = xml_obj;
} else {
(*rsc)->xml = xml_obj;
(*rsc)->orig_xml = NULL;
}
/* Do not use xml_obj from here on, use (*rsc)->xml in case templates are involved */
(*rsc)->parent = parent;
ops = find_xml_node((*rsc)->xml, "operations", FALSE);
(*rsc)->ops_xml = expand_idref(ops, scheduler->input);
(*rsc)->variant = get_resource_type((const char *) (*rsc)->xml->name);
if ((*rsc)->variant == pcmk_rsc_variant_unknown) {
pcmk__config_err("Ignoring resource '%s' of unknown type '%s'",
id, (*rsc)->xml->name);
common_free(*rsc);
*rsc = NULL;
return pcmk_rc_unpack_error;
}
#ifndef PCMK__COMPAT_2_0
warn_about_deprecated_classes(*rsc);
#endif
(*rsc)->meta = pcmk__strkey_table(free, free);
(*rsc)->allowed_nodes = pcmk__strkey_table(NULL, free);
(*rsc)->known_on = pcmk__strkey_table(NULL, free);
value = crm_element_value((*rsc)->xml, PCMK__META_CLONE_INSTANCE_NUM);
if (value) {
(*rsc)->id = crm_strdup_printf("%s:%s", id, value);
add_hash_param((*rsc)->meta, PCMK__META_CLONE_INSTANCE_NUM, value);
} else {
(*rsc)->id = strdup(id);
}
(*rsc)->fns = &resource_class_functions[(*rsc)->variant];
get_meta_attributes((*rsc)->meta, *rsc, NULL, scheduler);
(*rsc)->parameters = pe_rsc_params(*rsc, NULL, scheduler); // \deprecated
(*rsc)->flags = 0;
pe__set_resource_flags(*rsc, pcmk_rsc_runnable|pcmk_rsc_unassigned);
if (!pcmk_is_set(scheduler->flags, pcmk_sched_in_maintenance)) {
pe__set_resource_flags(*rsc, pcmk_rsc_managed);
}
(*rsc)->rsc_cons = NULL;
(*rsc)->rsc_tickets = NULL;
(*rsc)->actions = NULL;
(*rsc)->role = pcmk_role_stopped;
(*rsc)->next_role = pcmk_role_unknown;
(*rsc)->recovery_type = pcmk_multiply_active_restart;
(*rsc)->stickiness = 0;
(*rsc)->migration_threshold = INFINITY;
(*rsc)->failure_timeout = 0;
value = g_hash_table_lookup((*rsc)->meta, PCMK_META_PRIORITY);
(*rsc)->priority = char2score(value);
value = g_hash_table_lookup((*rsc)->meta, PCMK_META_CRITICAL);
if ((value == NULL) || crm_is_true(value)) {
pe__set_resource_flags(*rsc, pcmk_rsc_critical);
}
value = g_hash_table_lookup((*rsc)->meta, PCMK_META_NOTIFY);
if (crm_is_true(value)) {
pe__set_resource_flags(*rsc, pcmk_rsc_notify);
}
if (xml_contains_remote_node((*rsc)->xml)) {
(*rsc)->is_remote_node = TRUE;
if (g_hash_table_lookup((*rsc)->meta, PCMK__META_CONTAINER)) {
guest_node = true;
} else {
remote_node = true;
}
}
value = g_hash_table_lookup((*rsc)->meta, PCMK_META_ALLOW_MIGRATE);
if (crm_is_true(value)) {
pe__set_resource_flags(*rsc, pcmk_rsc_migratable);
} else if ((value == NULL) && remote_node) {
/* By default, we want remote nodes to be able
* to float around the cluster without having to stop all the
* resources within the remote-node before moving. Allowing
* migration support enables this feature. If this ever causes
* problems, migration support can be explicitly turned off with
* allow-migrate=false.
*/
pe__set_resource_flags(*rsc, pcmk_rsc_migratable);
}
value = g_hash_table_lookup((*rsc)->meta, PCMK_META_IS_MANAGED);
if (value != NULL && !pcmk__str_eq("default", value, pcmk__str_casei)) {
if (crm_is_true(value)) {
pe__set_resource_flags(*rsc, pcmk_rsc_managed);
} else {
pe__clear_resource_flags(*rsc, pcmk_rsc_managed);
}
}
value = g_hash_table_lookup((*rsc)->meta, PCMK_META_MAINTENANCE);
if (crm_is_true(value)) {
pe__clear_resource_flags(*rsc, pcmk_rsc_managed);
pe__set_resource_flags(*rsc, pcmk_rsc_maintenance);
}
if (pcmk_is_set(scheduler->flags, pcmk_sched_in_maintenance)) {
pe__clear_resource_flags(*rsc, pcmk_rsc_managed);
pe__set_resource_flags(*rsc, pcmk_rsc_maintenance);
}
if (pe_rsc_is_clone(pe__const_top_resource(*rsc, false))) {
value = g_hash_table_lookup((*rsc)->meta, PCMK_META_GLOBALLY_UNIQUE);
if (crm_is_true(value)) {
pe__set_resource_flags(*rsc, pcmk_rsc_unique);
}
if (detect_promotable(*rsc)) {
pe__set_resource_flags(*rsc, pcmk_rsc_promotable);
}
} else {
pe__set_resource_flags(*rsc, pcmk_rsc_unique);
}
// @COMPAT Deprecated meta-attribute
value = g_hash_table_lookup((*rsc)->meta, PCMK__META_RESTART_TYPE);
if (pcmk__str_eq(value, "restart", pcmk__str_casei)) {
(*rsc)->restart_type = pe_restart_restart;
pcmk__rsc_trace(*rsc, "%s dependency restart handling: restart",
(*rsc)->id);
pcmk__warn_once(pcmk__wo_restart_type,
"Support for " PCMK__META_RESTART_TYPE " is deprecated "
"and will be removed in a future release");
} else {
(*rsc)->restart_type = pe_restart_ignore;
pcmk__rsc_trace(*rsc, "%s dependency restart handling: ignore",
(*rsc)->id);
}
value = g_hash_table_lookup((*rsc)->meta, PCMK_META_MULTIPLE_ACTIVE);
if (pcmk__str_eq(value, "stop_only", pcmk__str_casei)) {
(*rsc)->recovery_type = pcmk_multiply_active_stop;
pcmk__rsc_trace(*rsc, "%s multiple running resource recovery: stop only",
(*rsc)->id);
} else if (pcmk__str_eq(value, "block", pcmk__str_casei)) {
(*rsc)->recovery_type = pcmk_multiply_active_block;
pcmk__rsc_trace(*rsc, "%s multiple running resource recovery: block",
(*rsc)->id);
} else if (pcmk__str_eq(value, "stop_unexpected", pcmk__str_casei)) {
(*rsc)->recovery_type = pcmk_multiply_active_unexpected;
pcmk__rsc_trace(*rsc,
"%s multiple running resource recovery: "
"stop unexpected instances",
(*rsc)->id);
} else { // "stop_start"
if (!pcmk__str_eq(value, "stop_start",
pcmk__str_casei|pcmk__str_null_matches)) {
pcmk__config_warn("%s is not a valid value for "
PCMK_META_MULTIPLE_ACTIVE
", using default of \"stop_start\"", value);
}
(*rsc)->recovery_type = pcmk_multiply_active_restart;
pcmk__rsc_trace(*rsc,
"%s multiple running resource recovery: stop/start",
(*rsc)->id);
}
value = g_hash_table_lookup((*rsc)->meta, PCMK_META_RESOURCE_STICKINESS);
if (value != NULL && !pcmk__str_eq("default", value, pcmk__str_casei)) {
(*rsc)->stickiness = char2score(value);
}
value = g_hash_table_lookup((*rsc)->meta, PCMK_META_MIGRATION_THRESHOLD);
if (value != NULL && !pcmk__str_eq("default", value, pcmk__str_casei)) {
(*rsc)->migration_threshold = char2score(value);
if ((*rsc)->migration_threshold < 0) {
/* @TODO We use 1 here to preserve previous behavior, but this
* should probably use the default (INFINITY) or 0 (to disable)
* instead.
*/
pcmk__warn_once(pcmk__wo_neg_threshold,
PCMK_META_MIGRATION_THRESHOLD
" must be non-negative, using 1 instead");
(*rsc)->migration_threshold = 1;
}
}
if (pcmk__str_eq(crm_element_value((*rsc)->xml, XML_AGENT_ATTR_CLASS),
PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
pe__set_working_set_flags(scheduler, pcmk_sched_have_fencing);
pe__set_resource_flags(*rsc, pcmk_rsc_fence_device);
}
value = g_hash_table_lookup((*rsc)->meta, PCMK_META_REQUIRES);
unpack_requires(*rsc, value, false);
value = g_hash_table_lookup((*rsc)->meta, PCMK_META_FAILURE_TIMEOUT);
if (value != NULL) {
guint interval_ms = 0U;
// Stored as seconds
pcmk_parse_interval_spec(value, &interval_ms);
(*rsc)->failure_timeout = (int) (interval_ms / 1000);
}
if (remote_node) {
GHashTable *params = pe_rsc_params(*rsc, NULL, scheduler);
/* Grabbing the value now means that any rules based on node attributes
* will evaluate to false, so such rules should not be used with
* reconnect_interval.
*
* @TODO Evaluate per node before using
*/
value = g_hash_table_lookup(params, PCMK_REMOTE_RA_RECONNECT_INTERVAL);
if (value) {
/* reconnect delay works by setting failure_timeout and preventing the
* connection from starting until the failure is cleared. */
pcmk_parse_interval_spec(value, &((*rsc)->remote_reconnect_ms));
/* we want to override any default failure_timeout in use when remote
* reconnect_interval is in use. */
(*rsc)->failure_timeout = (*rsc)->remote_reconnect_ms / 1000;
}
}
get_target_role(*rsc, &((*rsc)->next_role));
pcmk__rsc_trace(*rsc, "%s desired next state: %s", (*rsc)->id,
(*rsc)->next_role != pcmk_role_unknown? role2text((*rsc)->next_role) : "default");
if ((*rsc)->fns->unpack(*rsc, scheduler) == FALSE) {
(*rsc)->fns->free(*rsc);
*rsc = NULL;
return pcmk_rc_unpack_error;
}
if (pcmk_is_set(scheduler->flags, pcmk_sched_symmetric_cluster)) {
// This tag must stay exactly the same because it is tested elsewhere
resource_location(*rsc, NULL, 0, "symmetric_default", scheduler);
} else if (guest_node) {
/* remote resources tied to a container resource must always be allowed
* to opt-in to the cluster. Whether the connection resource is actually
* allowed to be placed on a node is dependent on the container resource */
resource_location(*rsc, NULL, 0, "remote_connection_default",
scheduler);
}
pcmk__rsc_trace(*rsc, "%s action notification: %s", (*rsc)->id,
pcmk_is_set((*rsc)->flags, pcmk_rsc_notify)? "required" : "not required");
(*rsc)->utilization = pcmk__strkey_table(free, free);
pe__unpack_dataset_nvpairs((*rsc)->xml, XML_TAG_UTILIZATION, &rule_data,
(*rsc)->utilization, NULL, FALSE, scheduler);
if (expanded_xml) {
if (add_template_rsc(xml_obj, scheduler) == FALSE) {
(*rsc)->fns->free(*rsc);
*rsc = NULL;
return pcmk_rc_unpack_error;
}
}
return pcmk_rc_ok;
}
gboolean
is_parent(pcmk_resource_t *child, pcmk_resource_t *rsc)
{
pcmk_resource_t *parent = child;
if (parent == NULL || rsc == NULL) {
return FALSE;
}
while (parent->parent != NULL) {
if (parent->parent == rsc) {
return TRUE;
}
parent = parent->parent;
}
return FALSE;
}
pcmk_resource_t *
uber_parent(pcmk_resource_t *rsc)
{
pcmk_resource_t *parent = rsc;
if (parent == NULL) {
return NULL;
}
while ((parent->parent != NULL)
&& (parent->parent->variant != pcmk_rsc_variant_bundle)) {
parent = parent->parent;
}
return parent;
}
/*!
* \internal
* \brief Get the topmost parent of a resource as a const pointer
*
* \param[in] rsc Resource to check
* \param[in] include_bundle If true, go all the way to bundle
*
* \return \p NULL if \p rsc is NULL, \p rsc if \p rsc has no parent,
* the bundle if \p rsc is bundled and \p include_bundle is true,
* otherwise the topmost parent of \p rsc up to a clone
*/
const pcmk_resource_t *
pe__const_top_resource(const pcmk_resource_t *rsc, bool include_bundle)
{
const pcmk_resource_t *parent = rsc;
if (parent == NULL) {
return NULL;
}
while (parent->parent != NULL) {
if (!include_bundle
&& (parent->parent->variant == pcmk_rsc_variant_bundle)) {
break;
}
parent = parent->parent;
}
return parent;
}
void
common_free(pcmk_resource_t * rsc)
{
if (rsc == NULL) {
return;
}
pcmk__rsc_trace(rsc, "Freeing %s %d", rsc->id, rsc->variant);
g_list_free(rsc->rsc_cons);
g_list_free(rsc->rsc_cons_lhs);
g_list_free(rsc->rsc_tickets);
g_list_free(rsc->dangling_migrations);
if (rsc->parameter_cache != NULL) {
g_hash_table_destroy(rsc->parameter_cache);
}
if (rsc->meta != NULL) {
g_hash_table_destroy(rsc->meta);
}
if (rsc->utilization != NULL) {
g_hash_table_destroy(rsc->utilization);
}
if ((rsc->parent == NULL)
&& pcmk_is_set(rsc->flags, pcmk_rsc_removed)) {
free_xml(rsc->xml);
rsc->xml = NULL;
free_xml(rsc->orig_xml);
rsc->orig_xml = NULL;
/* if rsc->orig_xml, then rsc->xml is an expanded xml from a template */
} else if (rsc->orig_xml) {
free_xml(rsc->xml);
rsc->xml = NULL;
}
if (rsc->running_on) {
g_list_free(rsc->running_on);
rsc->running_on = NULL;
}
if (rsc->known_on) {
g_hash_table_destroy(rsc->known_on);
rsc->known_on = NULL;
}
if (rsc->actions) {
g_list_free(rsc->actions);
rsc->actions = NULL;
}
if (rsc->allowed_nodes) {
g_hash_table_destroy(rsc->allowed_nodes);
rsc->allowed_nodes = NULL;
}
g_list_free(rsc->fillers);
g_list_free(rsc->rsc_location);
pcmk__rsc_trace(rsc, "Resource freed");
free(rsc->id);
free(rsc->clone_name);
free(rsc->allocated_to);
free(rsc->variant_opaque);
free(rsc->pending_task);
free(rsc);
}
/*!
* \internal
* \brief Count a node and update most preferred to it as appropriate
*
* \param[in] rsc An active resource
* \param[in] node A node that \p rsc is active on
* \param[in,out] active This will be set to \p node if \p node is more
* preferred than the current value
* \param[in,out] count_all If not NULL, this will be incremented
* \param[in,out] count_clean If not NULL, this will be incremented if \p node
* is online and clean
*
* \return true if the count should continue, or false if sufficiently known
*/
bool
pe__count_active_node(const pcmk_resource_t *rsc, pcmk_node_t *node,
pcmk_node_t **active, unsigned int *count_all,
unsigned int *count_clean)
{
bool keep_looking = false;
bool is_happy = false;
CRM_CHECK((rsc != NULL) && (node != NULL) && (active != NULL),
return false);
is_happy = node->details->online && !node->details->unclean;
if (count_all != NULL) {
++*count_all;
}
if ((count_clean != NULL) && is_happy) {
++*count_clean;
}
if ((count_all != NULL) || (count_clean != NULL)) {
keep_looking = true; // We're counting, so go through entire list
}
if (rsc->partial_migration_source != NULL) {
if (node->details == rsc->partial_migration_source->details) {
*active = node; // This is the migration source
} else {
keep_looking = true;
}
} else if (!pcmk_is_set(rsc->flags, pcmk_rsc_needs_fencing)) {
if (is_happy && ((*active == NULL) || !(*active)->details->online
|| (*active)->details->unclean)) {
*active = node; // This is the first clean node
} else {
keep_looking = true;
}
}
if (*active == NULL) {
*active = node; // This is the first node checked
}
return keep_looking;
}
// Shared implementation of pcmk_rsc_methods_t:active_node()
static pcmk_node_t *
active_node(const pcmk_resource_t *rsc, unsigned int *count_all,
unsigned int *count_clean)
{
pcmk_node_t *active = NULL;
if (count_all != NULL) {
*count_all = 0;
}
if (count_clean != NULL) {
*count_clean = 0;
}
if (rsc == NULL) {
return NULL;
}
for (GList *iter = rsc->running_on; iter != NULL; iter = iter->next) {
if (!pe__count_active_node(rsc, (pcmk_node_t *) iter->data, &active,
count_all, count_clean)) {
break; // Don't waste time iterating if we don't have to
}
}
return active;
}
/*!
* \brief
* \internal Find and count active nodes according to "requires"
*
* \param[in] rsc Resource to check
* \param[out] count If not NULL, will be set to count of active nodes
*
* \return An active node (or NULL if resource is not active anywhere)
*
* \note This is a convenience wrapper for active_node() where the count of all
* active nodes or only clean active nodes is desired according to the
* "requires" meta-attribute.
*/
pcmk_node_t *
pe__find_active_requires(const pcmk_resource_t *rsc, unsigned int *count)
{
if (rsc == NULL) {
if (count != NULL) {
*count = 0;
}
return NULL;
} else if (pcmk_is_set(rsc->flags, pcmk_rsc_needs_fencing)) {
return rsc->fns->active_node(rsc, count, NULL);
} else {
return rsc->fns->active_node(rsc, NULL, count);
}
}
void
pe__count_common(pcmk_resource_t *rsc)
{
if (rsc->children != NULL) {
for (GList *item = rsc->children; item != NULL; item = item->next) {
((pcmk_resource_t *) item->data)->fns->count(item->data);
}
} else if (!pcmk_is_set(rsc->flags, pcmk_rsc_removed)
|| (rsc->role > pcmk_role_stopped)) {
rsc->cluster->ninstances++;
if (pe__resource_is_disabled(rsc)) {
rsc->cluster->disabled_resources++;
}
if (pcmk_is_set(rsc->flags, pcmk_rsc_blocked)) {
rsc->cluster->blocked_resources++;
}
}
}
/*!
* \internal
* \brief Update a resource's next role
*
* \param[in,out] rsc Resource to be updated
* \param[in] role Resource's new next role
* \param[in] why Human-friendly reason why role is changing (for logs)
*/
void
pe__set_next_role(pcmk_resource_t *rsc, enum rsc_role_e role, const char *why)
{
CRM_ASSERT((rsc != NULL) && (why != NULL));
if (rsc->next_role != role) {
pcmk__rsc_trace(rsc, "Resetting next role for %s from %s to %s (%s)",
rsc->id, role2text(rsc->next_role), role2text(role),
why);
rsc->next_role = role;
}
}
diff --git a/lib/pengine/pe_actions.c b/lib/pengine/pe_actions.c
index 0d42aa7544..53b2f8c331 100644
--- a/lib/pengine/pe_actions.c
+++ b/lib/pengine/pe_actions.c
@@ -1,1889 +1,1889 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <glib.h>
#include <stdbool.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/scheduler_internal.h>
#include <crm/pengine/internal.h>
#include <crm/common/xml_internal.h>
#include "pe_status_private.h"
static void unpack_operation(pcmk_action_t *action, const xmlNode *xml_obj,
guint interval_ms);
static void
add_singleton(pcmk_scheduler_t *scheduler, pcmk_action_t *action)
{
if (scheduler->singletons == NULL) {
scheduler->singletons = pcmk__strkey_table(NULL, NULL);
}
g_hash_table_insert(scheduler->singletons, action->uuid, action);
}
static pcmk_action_t *
lookup_singleton(pcmk_scheduler_t *scheduler, const char *action_uuid)
{
if (scheduler->singletons == NULL) {
return NULL;
}
return g_hash_table_lookup(scheduler->singletons, action_uuid);
}
/*!
* \internal
* \brief Find an existing action that matches arguments
*
* \param[in] key Action key to match
* \param[in] rsc Resource to match (if any)
* \param[in] node Node to match (if any)
* \param[in] scheduler Scheduler data
*
* \return Existing action that matches arguments (or NULL if none)
*/
static pcmk_action_t *
find_existing_action(const char *key, const pcmk_resource_t *rsc,
const pcmk_node_t *node, const pcmk_scheduler_t *scheduler)
{
GList *matches = NULL;
pcmk_action_t *action = NULL;
/* When rsc is NULL, it would be quicker to check scheduler->singletons,
* but checking all scheduler->actions takes the node into account.
*/
matches = find_actions(((rsc == NULL)? scheduler->actions : rsc->actions),
key, node);
if (matches == NULL) {
return NULL;
}
CRM_LOG_ASSERT(!pcmk__list_of_multiple(matches));
action = matches->data;
g_list_free(matches);
return action;
}
/*!
* \internal
* \brief Find the XML configuration corresponding to a specific action key
*
* \param[in] rsc Resource to find action configuration for
* \param[in] key "RSC_ACTION_INTERVAL" of action to find
* \param[in] include_disabled If false, do not return disabled actions
*
* \return XML configuration of desired action if any, otherwise NULL
*/
static xmlNode *
find_exact_action_config(const pcmk_resource_t *rsc, const char *action_name,
guint interval_ms, bool include_disabled)
{
for (xmlNode *operation = first_named_child(rsc->ops_xml, XML_ATTR_OP);
operation != NULL; operation = crm_next_same_xml(operation)) {
bool enabled = false;
const char *config_name = NULL;
const char *interval_spec = NULL;
guint tmp_ms = 0U;
// @TODO This does not consider rules, defaults, etc.
if (!include_disabled
&& (pcmk__xe_get_bool_attr(operation, "enabled",
&enabled) == pcmk_rc_ok) && !enabled) {
continue;
}
interval_spec = crm_element_value(operation, XML_LRM_ATTR_INTERVAL);
pcmk_parse_interval_spec(interval_spec, &tmp_ms);
if (tmp_ms != interval_ms) {
continue;
}
config_name = crm_element_value(operation, "name");
if (pcmk__str_eq(action_name, config_name, pcmk__str_none)) {
return operation;
}
}
return NULL;
}
/*!
* \internal
* \brief Find the XML configuration of a resource action
*
* \param[in] rsc Resource to find action configuration for
* \param[in] action_name Action name to search for
* \param[in] interval_ms Action interval (in milliseconds) to search for
* \param[in] include_disabled If false, do not return disabled actions
*
* \return XML configuration of desired action if any, otherwise NULL
*/
xmlNode *
pcmk__find_action_config(const pcmk_resource_t *rsc, const char *action_name,
guint interval_ms, bool include_disabled)
{
xmlNode *action_config = NULL;
// Try requested action first
action_config = find_exact_action_config(rsc, action_name, interval_ms,
include_disabled);
// For migrate_to and migrate_from actions, retry with "migrate"
// @TODO This should be either documented or deprecated
if ((action_config == NULL)
&& pcmk__str_any_of(action_name, PCMK_ACTION_MIGRATE_TO,
PCMK_ACTION_MIGRATE_FROM, NULL)) {
action_config = find_exact_action_config(rsc, "migrate", 0,
include_disabled);
}
return action_config;
}
/*!
* \internal
* \brief Create a new action object
*
* \param[in] key Action key
* \param[in] task Action name
* \param[in,out] rsc Resource that action is for (if any)
* \param[in] node Node that action is on (if any)
* \param[in] optional Whether action should be considered optional
* \param[in,out] scheduler Scheduler data
*
* \return Newly allocated action
* \note This function takes ownership of \p key. It is the caller's
* responsibility to free the return value with pe_free_action().
*/
static pcmk_action_t *
new_action(char *key, const char *task, pcmk_resource_t *rsc,
const pcmk_node_t *node, bool optional, pcmk_scheduler_t *scheduler)
{
pcmk_action_t *action = calloc(1, sizeof(pcmk_action_t));
CRM_ASSERT(action != NULL);
action->rsc = rsc;
action->task = strdup(task); CRM_ASSERT(action->task != NULL);
action->uuid = key;
if (node) {
action->node = pe__copy_node(node);
}
if (pcmk__str_eq(task, PCMK_ACTION_LRM_DELETE, pcmk__str_casei)) {
// Resource history deletion for a node can be done on the DC
pe__set_action_flags(action, pcmk_action_on_dc);
}
pe__set_action_flags(action, pcmk_action_runnable);
if (optional) {
pe__set_action_flags(action, pcmk_action_optional);
} else {
pe__clear_action_flags(action, pcmk_action_optional);
}
if (rsc == NULL) {
action->meta = pcmk__strkey_table(free, free);
} else {
guint interval_ms = 0;
parse_op_key(key, NULL, NULL, &interval_ms);
action->op_entry = pcmk__find_action_config(rsc, task, interval_ms,
true);
/* If the given key is for one of the many notification pseudo-actions
* (pre_notify_promote, etc.), the actual action name is "notify"
*/
if ((action->op_entry == NULL) && (strstr(key, "_notify_") != NULL)) {
action->op_entry = find_exact_action_config(rsc, PCMK_ACTION_NOTIFY,
0, true);
}
unpack_operation(action, action->op_entry, interval_ms);
}
pcmk__rsc_trace(rsc, "Created %s action %d (%s): %s for %s on %s",
(optional? "optional" : "required"),
scheduler->action_id, key, task,
((rsc == NULL)? "no resource" : rsc->id),
pe__node_name(node));
action->id = scheduler->action_id++;
scheduler->actions = g_list_prepend(scheduler->actions, action);
if (rsc == NULL) {
add_singleton(scheduler, action);
} else {
rsc->actions = g_list_prepend(rsc->actions, action);
}
return action;
}
/*!
* \internal
* \brief Unpack a resource's action-specific instance parameters
*
* \param[in] action_xml XML of action's configuration in CIB (if any)
* \param[in,out] node_attrs Table of node attributes (for rule evaluation)
* \param[in,out] scheduler Cluster working set (for rule evaluation)
*
* \return Newly allocated hash table of action-specific instance parameters
*/
GHashTable *
pcmk__unpack_action_rsc_params(const xmlNode *action_xml,
GHashTable *node_attrs,
pcmk_scheduler_t *scheduler)
{
GHashTable *params = pcmk__strkey_table(free, free);
pe_rule_eval_data_t rule_data = {
.node_hash = node_attrs,
.role = pcmk_role_unknown,
.now = scheduler->now,
.match_data = NULL,
.rsc_data = NULL,
.op_data = NULL
};
pe__unpack_dataset_nvpairs(action_xml, XML_TAG_ATTR_SETS,
&rule_data, params, NULL,
FALSE, scheduler);
return params;
}
/*!
* \internal
* \brief Update an action's optional flag
*
* \param[in,out] action Action to update
* \param[in] optional Requested optional status
*/
static void
update_action_optional(pcmk_action_t *action, gboolean optional)
{
// Force a non-recurring action to be optional if its resource is unmanaged
if ((action->rsc != NULL) && (action->node != NULL)
&& !pcmk_is_set(action->flags, pcmk_action_pseudo)
&& !pcmk_is_set(action->rsc->flags, pcmk_rsc_managed)
&& (g_hash_table_lookup(action->meta,
XML_LRM_ATTR_INTERVAL_MS) == NULL)) {
pcmk__rsc_debug(action->rsc,
"%s on %s is optional (%s is unmanaged)",
action->uuid, pe__node_name(action->node),
action->rsc->id);
pe__set_action_flags(action, pcmk_action_optional);
// We shouldn't clear runnable here because ... something
// Otherwise require the action if requested
} else if (!optional) {
pe__clear_action_flags(action, pcmk_action_optional);
}
}
static enum pe_quorum_policy
effective_quorum_policy(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler)
{
enum pe_quorum_policy policy = scheduler->no_quorum_policy;
if (pcmk_is_set(scheduler->flags, pcmk_sched_quorate)) {
policy = pcmk_no_quorum_ignore;
} else if (scheduler->no_quorum_policy == pcmk_no_quorum_demote) {
switch (rsc->role) {
case pcmk_role_promoted:
case pcmk_role_unpromoted:
if (rsc->next_role > pcmk_role_unpromoted) {
pe__set_next_role(rsc, pcmk_role_unpromoted,
PCMK_OPT_NO_QUORUM_POLICY "=demote");
}
policy = pcmk_no_quorum_ignore;
break;
default:
policy = pcmk_no_quorum_stop;
break;
}
}
return policy;
}
/*!
* \internal
* \brief Update a resource action's runnable flag
*
* \param[in,out] action Action to update
* \param[in,out] scheduler Scheduler data
*
* \note This may also schedule fencing if a stop is unrunnable.
*/
static void
update_resource_action_runnable(pcmk_action_t *action,
pcmk_scheduler_t *scheduler)
{
if (pcmk_is_set(action->flags, pcmk_action_pseudo)) {
return;
}
if (action->node == NULL) {
pcmk__rsc_trace(action->rsc, "%s is unrunnable (unallocated)",
action->uuid);
pe__clear_action_flags(action, pcmk_action_runnable);
} else if (!pcmk_is_set(action->flags, pcmk_action_on_dc)
&& !(action->node->details->online)
&& (!pe__is_guest_node(action->node)
|| action->node->details->remote_requires_reset)) {
pe__clear_action_flags(action, pcmk_action_runnable);
do_crm_log(LOG_WARNING, "%s on %s is unrunnable (node is offline)",
action->uuid, pe__node_name(action->node));
if (pcmk_is_set(action->rsc->flags, pcmk_rsc_managed)
&& pcmk__str_eq(action->task, PCMK_ACTION_STOP, pcmk__str_casei)
&& !(action->node->details->unclean)) {
pe_fence_node(scheduler, action->node, "stop is unrunnable", false);
}
} else if (!pcmk_is_set(action->flags, pcmk_action_on_dc)
&& action->node->details->pending) {
pe__clear_action_flags(action, pcmk_action_runnable);
do_crm_log(LOG_WARNING,
"Action %s on %s is unrunnable (node is pending)",
action->uuid, pe__node_name(action->node));
} else if (action->needs == pcmk_requires_nothing) {
pe_action_set_reason(action, NULL, TRUE);
if (pe__is_guest_node(action->node)
&& !pe_can_fence(scheduler, action->node)) {
/* An action that requires nothing usually does not require any
* fencing in order to be runnable. However, there is an exception:
* such an action cannot be completed if it is on a guest node whose
* host is unclean and cannot be fenced.
*/
pcmk__rsc_debug(action->rsc,
"%s on %s is unrunnable "
"(node's host cannot be fenced)",
action->uuid, pe__node_name(action->node));
pe__clear_action_flags(action, pcmk_action_runnable);
} else {
pcmk__rsc_trace(action->rsc,
"%s on %s does not require fencing or quorum",
action->uuid, pe__node_name(action->node));
pe__set_action_flags(action, pcmk_action_runnable);
}
} else {
switch (effective_quorum_policy(action->rsc, scheduler)) {
case pcmk_no_quorum_stop:
pcmk__rsc_debug(action->rsc,
"%s on %s is unrunnable (no quorum)",
action->uuid, pe__node_name(action->node));
pe__clear_action_flags(action, pcmk_action_runnable);
pe_action_set_reason(action, "no quorum", true);
break;
case pcmk_no_quorum_freeze:
if (!action->rsc->fns->active(action->rsc, TRUE)
|| (action->rsc->next_role > action->rsc->role)) {
pcmk__rsc_debug(action->rsc,
"%s on %s is unrunnable (no quorum)",
action->uuid, pe__node_name(action->node));
pe__clear_action_flags(action, pcmk_action_runnable);
pe_action_set_reason(action, "quorum freeze", true);
}
break;
default:
//pe_action_set_reason(action, NULL, TRUE);
pe__set_action_flags(action, pcmk_action_runnable);
break;
}
}
}
/*!
* \internal
* \brief Update a resource object's flags for a new action on it
*
* \param[in,out] rsc Resource that action is for (if any)
* \param[in] action New action
*/
static void
update_resource_flags_for_action(pcmk_resource_t *rsc,
const pcmk_action_t *action)
{
/* @COMPAT pcmk_rsc_starting and pcmk_rsc_stopping are deprecated and unused
* within Pacemaker, and will eventually be removed
*/
if (pcmk__str_eq(action->task, PCMK_ACTION_STOP, pcmk__str_casei)) {
pe__set_resource_flags(rsc, pcmk_rsc_stopping);
} else if (pcmk__str_eq(action->task, PCMK_ACTION_START, pcmk__str_casei)) {
if (pcmk_is_set(action->flags, pcmk_action_runnable)) {
pe__set_resource_flags(rsc, pcmk_rsc_starting);
} else {
pe__clear_resource_flags(rsc, pcmk_rsc_starting);
}
}
}
static bool
valid_stop_on_fail(const char *value)
{
return !pcmk__strcase_any_of(value, "standby", "demote", "stop", NULL);
}
/*!
* \internal
* \brief Validate (and possibly reset) resource action's on_fail meta-attribute
*
* \param[in] rsc Resource that action is for
* \param[in] action_name Action name
* \param[in] action_config Action configuration XML from CIB (if any)
* \param[in,out] meta Table of action meta-attributes
*/
static void
validate_on_fail(const pcmk_resource_t *rsc, const char *action_name,
const xmlNode *action_config, GHashTable *meta)
{
const char *name = NULL;
const char *role = NULL;
const char *interval_spec = NULL;
const char *value = g_hash_table_lookup(meta, XML_OP_ATTR_ON_FAIL);
char *key = NULL;
char *new_value = NULL;
guint interval_ms = 0U;
// Stop actions can only use certain on-fail values
if (pcmk__str_eq(action_name, PCMK_ACTION_STOP, pcmk__str_none)
&& !valid_stop_on_fail(value)) {
pcmk__config_err("Resetting '" XML_OP_ATTR_ON_FAIL "' for %s stop "
"action to default value because '%s' is not "
"allowed for stop", rsc->id, value);
g_hash_table_remove(meta, XML_OP_ATTR_ON_FAIL);
return;
}
/* Demote actions default on-fail to the on-fail value for the first
* recurring monitor for the promoted role (if any).
*/
if (pcmk__str_eq(action_name, PCMK_ACTION_DEMOTE, pcmk__str_none)
&& (value == NULL)) {
/* @TODO This does not consider promote options set in a meta-attribute
* block (which may have rules that need to be evaluated) rather than
* XML properties.
*/
for (xmlNode *operation = first_named_child(rsc->ops_xml, XML_ATTR_OP);
operation != NULL; operation = crm_next_same_xml(operation)) {
bool enabled = false;
const char *promote_on_fail = NULL;
/* We only care about explicit on-fail (if promote uses default, so
* can demote)
*/
promote_on_fail = crm_element_value(operation, XML_OP_ATTR_ON_FAIL);
if (promote_on_fail == NULL) {
continue;
}
// We only care about recurring monitors for the promoted role
name = crm_element_value(operation, "name");
role = crm_element_value(operation, "role");
if (!pcmk__str_eq(name, PCMK_ACTION_MONITOR, pcmk__str_none)
|| !pcmk__strcase_any_of(role, PCMK__ROLE_PROMOTED,
PCMK__ROLE_PROMOTED_LEGACY, NULL)) {
continue;
}
interval_spec = crm_element_value(operation, XML_LRM_ATTR_INTERVAL);
pcmk_parse_interval_spec(interval_spec, &interval_ms);
if (interval_ms == 0U) {
continue;
}
// We only care about enabled monitors
if ((pcmk__xe_get_bool_attr(operation, "enabled",
&enabled) == pcmk_rc_ok) && !enabled) {
continue;
}
// Demote actions can't default to on-fail="demote"
if (pcmk__str_eq(promote_on_fail, "demote", pcmk__str_casei)) {
continue;
}
// Use value from first applicable promote action found
key = strdup(XML_OP_ATTR_ON_FAIL);
new_value = strdup(promote_on_fail);
CRM_ASSERT((key != NULL) && (new_value != NULL));
g_hash_table_insert(meta, key, new_value);
}
return;
}
if (pcmk__str_eq(action_name, PCMK_ACTION_LRM_DELETE, pcmk__str_none)
&& !pcmk__str_eq(value, "ignore", pcmk__str_casei)) {
key = strdup(XML_OP_ATTR_ON_FAIL);
new_value = strdup("ignore");
CRM_ASSERT((key != NULL) && (new_value != NULL));
g_hash_table_insert(meta, key, new_value);
return;
}
// on-fail="demote" is allowed only for certain actions
if (pcmk__str_eq(value, "demote", pcmk__str_casei)) {
name = crm_element_value(action_config, "name");
role = crm_element_value(action_config, "role");
interval_spec = crm_element_value(action_config,
XML_LRM_ATTR_INTERVAL);
pcmk_parse_interval_spec(interval_spec, &interval_ms);
if (!pcmk__str_eq(name, PCMK_ACTION_PROMOTE, pcmk__str_none)
&& ((interval_ms == 0U)
|| !pcmk__str_eq(name, PCMK_ACTION_MONITOR, pcmk__str_none)
|| !pcmk__strcase_any_of(role, PCMK__ROLE_PROMOTED,
PCMK__ROLE_PROMOTED_LEGACY, NULL))) {
pcmk__config_err("Resetting '" XML_OP_ATTR_ON_FAIL "' for %s %s "
"action to default value because 'demote' is not "
"allowed for it", rsc->id, name);
g_hash_table_remove(meta, XML_OP_ATTR_ON_FAIL);
return;
}
}
}
static int
unpack_timeout(const char *value)
{
int timeout_ms = crm_get_msec(value);
if (timeout_ms < 0) {
timeout_ms = PCMK_DEFAULT_ACTION_TIMEOUT_MS;
}
return timeout_ms;
}
// true if value contains valid, non-NULL interval origin for recurring op
static bool
unpack_interval_origin(const char *value, const xmlNode *xml_obj,
guint interval_ms, const crm_time_t *now,
long long *start_delay)
{
long long result = 0;
guint interval_sec = interval_ms / 1000;
crm_time_t *origin = NULL;
// Ignore unspecified values and non-recurring operations
if ((value == NULL) || (interval_ms == 0) || (now == NULL)) {
return false;
}
// Parse interval origin from text
origin = crm_time_new(value);
if (origin == NULL) {
pcmk__config_err("Ignoring '" XML_OP_ATTR_ORIGIN "' for operation "
"'%s' because '%s' is not valid",
(ID(xml_obj)? ID(xml_obj) : "(missing ID)"), value);
return false;
}
// Get seconds since origin (negative if origin is in the future)
result = crm_time_get_seconds(now) - crm_time_get_seconds(origin);
crm_time_free(origin);
// Calculate seconds from closest interval to now
result = result % interval_sec;
// Calculate seconds remaining until next interval
result = ((result <= 0)? 0 : interval_sec) - result;
crm_info("Calculated a start delay of %llds for operation '%s'",
result,
(ID(xml_obj)? ID(xml_obj) : "(unspecified)"));
if (start_delay != NULL) {
*start_delay = result * 1000; // milliseconds
}
return true;
}
static int
unpack_start_delay(const char *value, GHashTable *meta)
{
int start_delay = 0;
if (value != NULL) {
start_delay = crm_get_msec(value);
if (start_delay < 0) {
start_delay = 0;
}
if (meta) {
g_hash_table_replace(meta, strdup(XML_OP_ATTR_START_DELAY),
pcmk__itoa(start_delay));
}
}
return start_delay;
}
/*!
* \internal
* \brief Find a resource's most frequent recurring monitor
*
* \param[in] rsc Resource to check
*
* \return Operation XML configured for most frequent recurring monitor for
* \p rsc (if any)
*/
static xmlNode *
most_frequent_monitor(const pcmk_resource_t *rsc)
{
guint min_interval_ms = G_MAXUINT;
xmlNode *op = NULL;
for (xmlNode *operation = first_named_child(rsc->ops_xml, XML_ATTR_OP);
operation != NULL; operation = crm_next_same_xml(operation)) {
bool enabled = false;
guint interval_ms = 0U;
const char *interval_spec = crm_element_value(operation,
XML_LRM_ATTR_INTERVAL);
// We only care about enabled recurring monitors
if (!pcmk__str_eq(crm_element_value(operation, "name"),
PCMK_ACTION_MONITOR, pcmk__str_none)) {
continue;
}
pcmk_parse_interval_spec(interval_spec, &interval_ms);
if (interval_ms == 0U) {
continue;
}
// @TODO This does not account for rules, defaults, etc.
if ((pcmk__xe_get_bool_attr(operation, "enabled",
&enabled) == pcmk_rc_ok) && !enabled) {
continue;
}
if (interval_ms < min_interval_ms) {
min_interval_ms = interval_ms;
op = operation;
}
}
return op;
}
/*!
* \internal
* \brief Unpack action meta-attributes
*
* \param[in,out] rsc Resource that action is for
* \param[in] node Node that action is on
* \param[in] action_name Action name
* \param[in] interval_ms Action interval (in milliseconds)
* \param[in] action_config Action XML configuration from CIB (if any)
*
* Unpack a resource action's meta-attributes (normalizing the interval,
* timeout, and start delay values as integer milliseconds) from its CIB XML
* configuration (including defaults).
*
* \return Newly allocated hash table with normalized action meta-attributes
*/
GHashTable *
pcmk__unpack_action_meta(pcmk_resource_t *rsc, const pcmk_node_t *node,
const char *action_name, guint interval_ms,
const xmlNode *action_config)
{
GHashTable *meta = NULL;
char *name = NULL;
char *value = NULL;
const char *timeout_spec = NULL;
const char *str = NULL;
pe_rsc_eval_data_t rsc_rule_data = {
.standard = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS),
.provider = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER),
- .agent = crm_element_value(rsc->xml, XML_EXPR_ATTR_TYPE),
+ .agent = crm_element_value(rsc->xml, XML_ATTR_TYPE),
};
pe_op_eval_data_t op_rule_data = {
.op_name = action_name,
.interval = interval_ms,
};
pe_rule_eval_data_t rule_data = {
.node_hash = (node == NULL)? NULL : node->details->attrs,
.role = pcmk_role_unknown,
.now = rsc->cluster->now,
.match_data = NULL,
.rsc_data = &rsc_rule_data,
.op_data = &op_rule_data,
};
meta = pcmk__strkey_table(free, free);
// Cluster-wide <op_defaults> <meta_attributes>
pe__unpack_dataset_nvpairs(rsc->cluster->op_defaults, XML_TAG_META_SETS,
&rule_data, meta, NULL, FALSE, rsc->cluster);
// Derive default timeout for probes from recurring monitor timeouts
if (pcmk_is_probe(action_name, interval_ms)) {
xmlNode *min_interval_mon = most_frequent_monitor(rsc);
if (min_interval_mon != NULL) {
/* @TODO This does not consider timeouts set in meta_attributes
* blocks (which may also have rules that need to be evaluated).
*/
timeout_spec = crm_element_value(min_interval_mon,
XML_ATTR_TIMEOUT);
if (timeout_spec != NULL) {
pcmk__rsc_trace(rsc,
"Setting default timeout for %s probe to "
"most frequent monitor's timeout '%s'",
rsc->id, timeout_spec);
name = strdup(XML_ATTR_TIMEOUT);
value = strdup(timeout_spec);
CRM_ASSERT((name != NULL) && (value != NULL));
g_hash_table_insert(meta, name, value);
}
}
}
if (action_config != NULL) {
// <op> <meta_attributes> take precedence over defaults
pe__unpack_dataset_nvpairs(action_config, XML_TAG_META_SETS, &rule_data,
meta, NULL, TRUE, rsc->cluster);
/* Anything set as an <op> XML property has highest precedence.
* This ensures we use the name and interval from the <op> tag.
* (See below for the only exception, fence device start/probe timeout.)
*/
for (xmlAttrPtr attr = action_config->properties;
attr != NULL; attr = attr->next) {
name = strdup((const char *) attr->name);
value = strdup(pcmk__xml_attr_value(attr));
CRM_ASSERT((name != NULL) && (value != NULL));
g_hash_table_insert(meta, name, value);
}
}
g_hash_table_remove(meta, PCMK_XA_ID);
// Normalize interval to milliseconds
if (interval_ms > 0) {
name = strdup(XML_LRM_ATTR_INTERVAL);
CRM_ASSERT(name != NULL);
value = crm_strdup_printf("%u", interval_ms);
g_hash_table_insert(meta, name, value);
} else {
g_hash_table_remove(meta, XML_LRM_ATTR_INTERVAL);
}
/* Timeout order of precedence (highest to lowest):
* 1. pcmk_monitor_timeout resource parameter (only for starts and probes
* when rsc has pcmk_ra_cap_fence_params; this gets used for recurring
* monitors via the executor instead)
* 2. timeout configured in <op> (with <op timeout> taking precedence over
* <op> <meta_attributes>)
* 3. timeout configured in <op_defaults> <meta_attributes>
* 4. PCMK_DEFAULT_ACTION_TIMEOUT_MS
*/
// Check for pcmk_monitor_timeout
if (pcmk_is_set(pcmk_get_ra_caps(rsc_rule_data.standard),
pcmk_ra_cap_fence_params)
&& (pcmk__str_eq(action_name, PCMK_ACTION_START, pcmk__str_none)
|| pcmk_is_probe(action_name, interval_ms))) {
GHashTable *params = pe_rsc_params(rsc, node, rsc->cluster);
timeout_spec = g_hash_table_lookup(params, "pcmk_monitor_timeout");
if (timeout_spec != NULL) {
pcmk__rsc_trace(rsc,
"Setting timeout for %s %s to "
"pcmk_monitor_timeout (%s)",
rsc->id, action_name, timeout_spec);
name = strdup(XML_ATTR_TIMEOUT);
value = strdup(timeout_spec);
CRM_ASSERT((name != NULL) && (value != NULL));
g_hash_table_insert(meta, name, value);
}
}
// Normalize timeout to positive milliseconds
name = strdup(XML_ATTR_TIMEOUT);
CRM_ASSERT(name != NULL);
timeout_spec = g_hash_table_lookup(meta, XML_ATTR_TIMEOUT);
g_hash_table_insert(meta, name, pcmk__itoa(unpack_timeout(timeout_spec)));
// Ensure on-fail has a valid value
validate_on_fail(rsc, action_name, action_config, meta);
// Normalize start-delay
str = g_hash_table_lookup(meta, XML_OP_ATTR_START_DELAY);
if (str != NULL) {
unpack_start_delay(str, meta);
} else {
long long start_delay = 0;
str = g_hash_table_lookup(meta, XML_OP_ATTR_ORIGIN);
if (unpack_interval_origin(str, action_config, interval_ms,
rsc->cluster->now, &start_delay)) {
name = strdup(XML_OP_ATTR_START_DELAY);
CRM_ASSERT(name != NULL);
g_hash_table_insert(meta, name,
crm_strdup_printf("%lld", start_delay));
}
}
return meta;
}
/*!
* \internal
* \brief Determine an action's quorum and fencing dependency
*
* \param[in] rsc Resource that action is for
* \param[in] action_name Name of action being unpacked
*
* \return Quorum and fencing dependency appropriate to action
*/
enum rsc_start_requirement
pcmk__action_requires(const pcmk_resource_t *rsc, const char *action_name)
{
const char *value = NULL;
enum rsc_start_requirement requires = pcmk_requires_nothing;
CRM_CHECK((rsc != NULL) && (action_name != NULL), return requires);
if (!pcmk__strcase_any_of(action_name, PCMK_ACTION_START,
PCMK_ACTION_PROMOTE, NULL)) {
value = "nothing (not start or promote)";
} else if (pcmk_is_set(rsc->flags, pcmk_rsc_needs_fencing)) {
requires = pcmk_requires_fencing;
value = "fencing";
} else if (pcmk_is_set(rsc->flags, pcmk_rsc_needs_quorum)) {
requires = pcmk_requires_quorum;
value = "quorum";
} else {
value = "nothing";
}
pcmk__rsc_trace(rsc, "%s of %s requires %s", action_name, rsc->id, value);
return requires;
}
/*!
* \internal
* \brief Parse action failure response from a user-provided string
*
* \param[in] rsc Resource that action is for
* \param[in] action_name Name of action
* \param[in] interval_ms Action interval (in milliseconds)
* \param[in] value User-provided configuration value for on-fail
*
* \return Action failure response parsed from \p text
*/
enum action_fail_response
pcmk__parse_on_fail(const pcmk_resource_t *rsc, const char *action_name,
guint interval_ms, const char *value)
{
const char *desc = NULL;
bool needs_remote_reset = false;
enum action_fail_response on_fail = pcmk_on_fail_ignore;
if (value == NULL) {
// Use default
} else if (pcmk__str_eq(value, "block", pcmk__str_casei)) {
on_fail = pcmk_on_fail_block;
desc = "block";
} else if (pcmk__str_eq(value, "fence", pcmk__str_casei)) {
if (pcmk_is_set(rsc->cluster->flags, pcmk_sched_fencing_enabled)) {
on_fail = pcmk_on_fail_fence_node;
desc = "node fencing";
} else {
pcmk__config_err("Resetting '" XML_OP_ATTR_ON_FAIL "' for "
"%s of %s to 'stop' because 'fence' is not "
"valid when fencing is disabled",
action_name, rsc->id);
on_fail = pcmk_on_fail_stop;
desc = "stop resource";
}
} else if (pcmk__str_eq(value, "standby", pcmk__str_casei)) {
on_fail = pcmk_on_fail_standby_node;
desc = "node standby";
} else if (pcmk__strcase_any_of(value, "ignore", PCMK__VALUE_NOTHING,
NULL)) {
desc = "ignore";
} else if (pcmk__str_eq(value, "migrate", pcmk__str_casei)) {
on_fail = pcmk_on_fail_ban;
desc = "force migration";
} else if (pcmk__str_eq(value, "stop", pcmk__str_casei)) {
on_fail = pcmk_on_fail_stop;
desc = "stop resource";
} else if (pcmk__str_eq(value, "restart", pcmk__str_casei)) {
on_fail = pcmk_on_fail_restart;
desc = "restart (and possibly migrate)";
} else if (pcmk__str_eq(value, "restart-container", pcmk__str_casei)) {
if (rsc->container == NULL) {
pcmk__rsc_debug(rsc,
"Using default " XML_OP_ATTR_ON_FAIL " for %s "
"of %s because it does not have a container",
action_name, rsc->id);
} else {
on_fail = pcmk_on_fail_restart_container;
desc = "restart container (and possibly migrate)";
}
} else if (pcmk__str_eq(value, "demote", pcmk__str_casei)) {
on_fail = pcmk_on_fail_demote;
desc = "demote instance";
} else {
pcmk__config_err("Using default '" XML_OP_ATTR_ON_FAIL "' for "
"%s of %s because '%s' is not valid",
action_name, rsc->id, value);
}
/* Remote node connections are handled specially. Failures that result
* in dropping an active connection must result in fencing. The only
* failures that don't are probes and starts. The user can explicitly set
* on-fail="fence" to fence after start failures.
*/
if (pe__resource_is_remote_conn(rsc)
&& !pcmk_is_probe(action_name, interval_ms)
&& !pcmk__str_eq(action_name, PCMK_ACTION_START, pcmk__str_none)) {
needs_remote_reset = true;
if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
desc = NULL; // Force default for unmanaged connections
}
}
if (desc != NULL) {
// Explicit value used, default not needed
} else if (rsc->container != NULL) {
on_fail = pcmk_on_fail_restart_container;
desc = "restart container (and possibly migrate) (default)";
} else if (needs_remote_reset) {
if (pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
if (pcmk_is_set(rsc->cluster->flags,
pcmk_sched_fencing_enabled)) {
desc = "fence remote node (default)";
} else {
desc = "recover remote node connection (default)";
}
on_fail = pcmk_on_fail_reset_remote;
} else {
on_fail = pcmk_on_fail_stop;
desc = "stop unmanaged remote node (enforcing default)";
}
} else if (pcmk__str_eq(action_name, PCMK_ACTION_STOP, pcmk__str_none)) {
if (pcmk_is_set(rsc->cluster->flags, pcmk_sched_fencing_enabled)) {
on_fail = pcmk_on_fail_fence_node;
desc = "resource fence (default)";
} else {
on_fail = pcmk_on_fail_block;
desc = "resource block (default)";
}
} else {
on_fail = pcmk_on_fail_restart;
desc = "restart (and possibly migrate) (default)";
}
pcmk__rsc_trace(rsc, "Failure handling for %s-interval %s of %s: %s",
pcmk__readable_interval(interval_ms), action_name,
rsc->id, desc);
return on_fail;
}
/*!
* \internal
* \brief Determine a resource's role after failure of an action
*
* \param[in] rsc Resource that action is for
* \param[in] action_name Action name
* \param[in] on_fail Failure handling for action
* \param[in] meta Unpacked action meta-attributes
*
* \return Resource role that results from failure of action
*/
enum rsc_role_e
pcmk__role_after_failure(const pcmk_resource_t *rsc, const char *action_name,
enum action_fail_response on_fail, GHashTable *meta)
{
const char *value = NULL;
enum rsc_role_e role = pcmk_role_unknown;
// Set default for role after failure specially in certain circumstances
switch (on_fail) {
case pcmk_on_fail_stop:
role = pcmk_role_stopped;
break;
case pcmk_on_fail_reset_remote:
if (rsc->remote_reconnect_ms != 0) {
role = pcmk_role_stopped;
}
break;
default:
break;
}
// @COMPAT Check for explicitly configured role (deprecated)
value = g_hash_table_lookup(meta, PCMK__META_ROLE_AFTER_FAILURE);
if (value != NULL) {
pcmk__warn_once(pcmk__wo_role_after,
"Support for " PCMK__META_ROLE_AFTER_FAILURE " is "
"deprecated and will be removed in a future release");
if (role == pcmk_role_unknown) {
role = text2role(value);
if (role == pcmk_role_unknown) {
pcmk__config_err("Ignoring invalid value %s for "
PCMK__META_ROLE_AFTER_FAILURE,
value);
}
}
}
if (role == pcmk_role_unknown) {
// Use default
if (pcmk__str_eq(action_name, PCMK_ACTION_PROMOTE, pcmk__str_none)) {
role = pcmk_role_unpromoted;
} else {
role = pcmk_role_started;
}
}
pcmk__rsc_trace(rsc, "Role after %s %s failure is: %s",
rsc->id, action_name, role2text(role));
return role;
}
/*!
* \internal
* \brief Unpack action configuration
*
* Unpack a resource action's meta-attributes (normalizing the interval,
* timeout, and start delay values as integer milliseconds), requirements, and
* failure policy from its CIB XML configuration (including defaults).
*
* \param[in,out] action Resource action to unpack into
* \param[in] xml_obj Action configuration XML (NULL for defaults only)
* \param[in] interval_ms How frequently to perform the operation
*/
static void
unpack_operation(pcmk_action_t *action, const xmlNode *xml_obj,
guint interval_ms)
{
const char *value = NULL;
action->meta = pcmk__unpack_action_meta(action->rsc, action->node,
action->task, interval_ms, xml_obj);
action->needs = pcmk__action_requires(action->rsc, action->task);
value = g_hash_table_lookup(action->meta, XML_OP_ATTR_ON_FAIL);
action->on_fail = pcmk__parse_on_fail(action->rsc, action->task,
interval_ms, value);
action->fail_role = pcmk__role_after_failure(action->rsc, action->task,
action->on_fail, action->meta);
}
/*!
* \brief Create or update an action object
*
* \param[in,out] rsc Resource that action is for (if any)
* \param[in,out] key Action key (must be non-NULL)
* \param[in] task Action name (must be non-NULL)
* \param[in] on_node Node that action is on (if any)
* \param[in] optional Whether action should be considered optional
* \param[in,out] scheduler Scheduler data
*
* \return Action object corresponding to arguments (guaranteed not to be
* \c NULL)
* \note This function takes ownership of (and might free) \p key, and
* \p scheduler takes ownership of the returned action (the caller should
* not free it).
*/
pcmk_action_t *
custom_action(pcmk_resource_t *rsc, char *key, const char *task,
const pcmk_node_t *on_node, gboolean optional,
pcmk_scheduler_t *scheduler)
{
pcmk_action_t *action = NULL;
CRM_ASSERT((key != NULL) && (task != NULL) && (scheduler != NULL));
action = find_existing_action(key, rsc, on_node, scheduler);
if (action == NULL) {
action = new_action(key, task, rsc, on_node, optional, scheduler);
} else {
free(key);
}
update_action_optional(action, optional);
if (rsc != NULL) {
if ((action->node != NULL) && (action->op_entry != NULL)
&& !pcmk_is_set(action->flags, pcmk_action_attrs_evaluated)) {
GHashTable *attrs = action->node->details->attrs;
if (action->extra != NULL) {
g_hash_table_destroy(action->extra);
}
action->extra = pcmk__unpack_action_rsc_params(action->op_entry,
attrs, scheduler);
pe__set_action_flags(action, pcmk_action_attrs_evaluated);
}
update_resource_action_runnable(action, scheduler);
update_resource_flags_for_action(rsc, action);
}
if (action->extra == NULL) {
action->extra = pcmk__strkey_table(free, free);
}
return action;
}
pcmk_action_t *
get_pseudo_op(const char *name, pcmk_scheduler_t *scheduler)
{
pcmk_action_t *op = lookup_singleton(scheduler, name);
if (op == NULL) {
op = custom_action(NULL, strdup(name), name, NULL, TRUE, scheduler);
pe__set_action_flags(op, pcmk_action_pseudo|pcmk_action_runnable);
}
return op;
}
static GList *
find_unfencing_devices(GList *candidates, GList *matches)
{
for (GList *gIter = candidates; gIter != NULL; gIter = gIter->next) {
pcmk_resource_t *candidate = gIter->data;
if (candidate->children != NULL) {
matches = find_unfencing_devices(candidate->children, matches);
} else if (!pcmk_is_set(candidate->flags, pcmk_rsc_fence_device)) {
continue;
} else if (pcmk_is_set(candidate->flags, pcmk_rsc_needs_unfencing)) {
matches = g_list_prepend(matches, candidate);
} else if (pcmk__str_eq(g_hash_table_lookup(candidate->meta,
PCMK_STONITH_PROVIDES),
PCMK__VALUE_UNFENCING,
pcmk__str_casei)) {
matches = g_list_prepend(matches, candidate);
}
}
return matches;
}
static int
node_priority_fencing_delay(const pcmk_node_t *node,
const pcmk_scheduler_t *scheduler)
{
int member_count = 0;
int online_count = 0;
int top_priority = 0;
int lowest_priority = 0;
GList *gIter = NULL;
// PCMK_OPT_PRIORITY_FENCING_DELAY is disabled
if (scheduler->priority_fencing_delay <= 0) {
return 0;
}
/* No need to request a delay if the fencing target is not a normal cluster
* member, for example if it's a remote node or a guest node. */
if (node->details->type != pcmk_node_variant_cluster) {
return 0;
}
// No need to request a delay if the fencing target is in our partition
if (node->details->online) {
return 0;
}
for (gIter = scheduler->nodes; gIter != NULL; gIter = gIter->next) {
pcmk_node_t *n = gIter->data;
if (n->details->type != pcmk_node_variant_cluster) {
continue;
}
member_count ++;
if (n->details->online) {
online_count++;
}
if (member_count == 1
|| n->details->priority > top_priority) {
top_priority = n->details->priority;
}
if (member_count == 1
|| n->details->priority < lowest_priority) {
lowest_priority = n->details->priority;
}
}
// No need to delay if we have more than half of the cluster members
if (online_count > member_count / 2) {
return 0;
}
/* All the nodes have equal priority.
* Any configured corresponding `pcmk_delay_base/max` will be applied. */
if (lowest_priority == top_priority) {
return 0;
}
if (node->details->priority < top_priority) {
return 0;
}
return scheduler->priority_fencing_delay;
}
pcmk_action_t *
pe_fence_op(pcmk_node_t *node, const char *op, bool optional,
const char *reason, bool priority_delay,
pcmk_scheduler_t *scheduler)
{
char *op_key = NULL;
pcmk_action_t *stonith_op = NULL;
if(op == NULL) {
op = scheduler->stonith_action;
}
op_key = crm_strdup_printf("%s-%s-%s",
PCMK_ACTION_STONITH, node->details->uname, op);
stonith_op = lookup_singleton(scheduler, op_key);
if(stonith_op == NULL) {
stonith_op = custom_action(NULL, op_key, PCMK_ACTION_STONITH, node,
TRUE, scheduler);
add_hash_param(stonith_op->meta, XML_LRM_ATTR_TARGET, node->details->uname);
add_hash_param(stonith_op->meta, XML_LRM_ATTR_TARGET_UUID, node->details->id);
add_hash_param(stonith_op->meta, "stonith_action", op);
if (pcmk_is_set(scheduler->flags, pcmk_sched_enable_unfencing)) {
/* Extra work to detect device changes
*/
GString *digests_all = g_string_sized_new(1024);
GString *digests_secure = g_string_sized_new(1024);
GList *matches = find_unfencing_devices(scheduler->resources, NULL);
char *key = NULL;
char *value = NULL;
for (GList *gIter = matches; gIter != NULL; gIter = gIter->next) {
pcmk_resource_t *match = gIter->data;
const char *agent = g_hash_table_lookup(match->meta,
XML_ATTR_TYPE);
pcmk__op_digest_t *data = NULL;
data = pe__compare_fencing_digest(match, agent, node,
scheduler);
if (data->rc == pcmk__digest_mismatch) {
optional = FALSE;
crm_notice("Unfencing node %s because the definition of "
"%s changed", pe__node_name(node), match->id);
if (!pcmk__is_daemon && scheduler->priv != NULL) {
pcmk__output_t *out = scheduler->priv;
out->info(out,
"notice: Unfencing node %s because the "
"definition of %s changed",
pe__node_name(node), match->id);
}
}
pcmk__g_strcat(digests_all,
match->id, ":", agent, ":",
data->digest_all_calc, ",", NULL);
pcmk__g_strcat(digests_secure,
match->id, ":", agent, ":",
data->digest_secure_calc, ",", NULL);
}
key = strdup(XML_OP_ATTR_DIGESTS_ALL);
value = strdup((const char *) digests_all->str);
CRM_ASSERT((key != NULL) && (value != NULL));
g_hash_table_insert(stonith_op->meta, key, value);
g_string_free(digests_all, TRUE);
key = strdup(XML_OP_ATTR_DIGESTS_SECURE);
value = strdup((const char *) digests_secure->str);
CRM_ASSERT((key != NULL) && (value != NULL));
g_hash_table_insert(stonith_op->meta, key, value);
g_string_free(digests_secure, TRUE);
}
} else {
free(op_key);
}
if (scheduler->priority_fencing_delay > 0
/* It's a suitable case where PCMK_OPT_PRIORITY_FENCING_DELAY
* applies. At least add PCMK_OPT_PRIORITY_FENCING_DELAY field as
* an indicator.
*/
&& (priority_delay
/* The priority delay needs to be recalculated if this function has
* been called by schedule_fencing_and_shutdowns() after node
* priority has already been calculated by native_add_running().
*/
|| g_hash_table_lookup(stonith_op->meta,
PCMK_OPT_PRIORITY_FENCING_DELAY) != NULL)) {
/* Add PCMK_OPT_PRIORITY_FENCING_DELAY to the fencing op even if
* it's 0 for the targeting node. So that it takes precedence over
* any possible `pcmk_delay_base/max`.
*/
char *delay_s = pcmk__itoa(node_priority_fencing_delay(node,
scheduler));
g_hash_table_insert(stonith_op->meta,
strdup(PCMK_OPT_PRIORITY_FENCING_DELAY),
delay_s);
}
if(optional == FALSE && pe_can_fence(scheduler, node)) {
pe__clear_action_flags(stonith_op, pcmk_action_optional);
pe_action_set_reason(stonith_op, reason, false);
} else if(reason && stonith_op->reason == NULL) {
stonith_op->reason = strdup(reason);
}
return stonith_op;
}
void
pe_free_action(pcmk_action_t *action)
{
if (action == NULL) {
return;
}
g_list_free_full(action->actions_before, free);
g_list_free_full(action->actions_after, free);
if (action->extra) {
g_hash_table_destroy(action->extra);
}
if (action->meta) {
g_hash_table_destroy(action->meta);
}
free(action->cancel_task);
free(action->reason);
free(action->task);
free(action->uuid);
free(action->node);
free(action);
}
int
pe_get_configured_timeout(pcmk_resource_t *rsc, const char *action,
pcmk_scheduler_t *scheduler)
{
xmlNode *child = NULL;
GHashTable *action_meta = NULL;
const char *timeout_spec = NULL;
int timeout_ms = 0;
pe_rule_eval_data_t rule_data = {
.node_hash = NULL,
.role = pcmk_role_unknown,
.now = scheduler->now,
.match_data = NULL,
.rsc_data = NULL,
.op_data = NULL
};
for (child = first_named_child(rsc->ops_xml, XML_ATTR_OP);
child != NULL; child = crm_next_same_xml(child)) {
if (pcmk__str_eq(action, crm_element_value(child, XML_NVPAIR_ATTR_NAME),
pcmk__str_casei)) {
timeout_spec = crm_element_value(child, XML_ATTR_TIMEOUT);
break;
}
}
if (timeout_spec == NULL && scheduler->op_defaults) {
action_meta = pcmk__strkey_table(free, free);
pe__unpack_dataset_nvpairs(scheduler->op_defaults, XML_TAG_META_SETS,
&rule_data, action_meta, NULL, FALSE,
scheduler);
timeout_spec = g_hash_table_lookup(action_meta, XML_ATTR_TIMEOUT);
}
// @TODO check meta-attributes
// @TODO maybe use min-interval monitor timeout as default for monitors
timeout_ms = crm_get_msec(timeout_spec);
if (timeout_ms < 0) {
timeout_ms = PCMK_DEFAULT_ACTION_TIMEOUT_MS;
}
if (action_meta != NULL) {
g_hash_table_destroy(action_meta);
}
return timeout_ms;
}
enum action_tasks
get_complex_task(const pcmk_resource_t *rsc, const char *name)
{
enum action_tasks task = text2task(name);
if ((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_primitive)) {
switch (task) {
case pcmk_action_stopped:
case pcmk_action_started:
case pcmk_action_demoted:
case pcmk_action_promoted:
crm_trace("Folding %s back into its atomic counterpart for %s",
name, rsc->id);
--task;
break;
default:
break;
}
}
return task;
}
/*!
* \internal
* \brief Find first matching action in a list
*
* \param[in] input List of actions to search
* \param[in] uuid If not NULL, action must have this UUID
* \param[in] task If not NULL, action must have this action name
* \param[in] on_node If not NULL, action must be on this node
*
* \return First action in list that matches criteria, or NULL if none
*/
pcmk_action_t *
find_first_action(const GList *input, const char *uuid, const char *task,
const pcmk_node_t *on_node)
{
CRM_CHECK(uuid || task, return NULL);
for (const GList *gIter = input; gIter != NULL; gIter = gIter->next) {
pcmk_action_t *action = (pcmk_action_t *) gIter->data;
if (uuid != NULL && !pcmk__str_eq(uuid, action->uuid, pcmk__str_casei)) {
continue;
} else if (task != NULL && !pcmk__str_eq(task, action->task, pcmk__str_casei)) {
continue;
} else if (on_node == NULL) {
return action;
} else if (action->node == NULL) {
continue;
} else if (on_node->details == action->node->details) {
return action;
}
}
return NULL;
}
GList *
find_actions(GList *input, const char *key, const pcmk_node_t *on_node)
{
GList *gIter = input;
GList *result = NULL;
CRM_CHECK(key != NULL, return NULL);
for (; gIter != NULL; gIter = gIter->next) {
pcmk_action_t *action = (pcmk_action_t *) gIter->data;
if (!pcmk__str_eq(key, action->uuid, pcmk__str_casei)) {
continue;
} else if (on_node == NULL) {
crm_trace("Action %s matches (ignoring node)", key);
result = g_list_prepend(result, action);
} else if (action->node == NULL) {
crm_trace("Action %s matches (unallocated, assigning to %s)",
key, pe__node_name(on_node));
action->node = pe__copy_node(on_node);
result = g_list_prepend(result, action);
} else if (on_node->details == action->node->details) {
crm_trace("Action %s on %s matches", key, pe__node_name(on_node));
result = g_list_prepend(result, action);
}
}
return result;
}
GList *
find_actions_exact(GList *input, const char *key, const pcmk_node_t *on_node)
{
GList *result = NULL;
CRM_CHECK(key != NULL, return NULL);
if (on_node == NULL) {
return NULL;
}
for (GList *gIter = input; gIter != NULL; gIter = gIter->next) {
pcmk_action_t *action = (pcmk_action_t *) gIter->data;
if ((action->node != NULL)
&& pcmk__str_eq(key, action->uuid, pcmk__str_casei)
&& pcmk__str_eq(on_node->details->id, action->node->details->id,
pcmk__str_casei)) {
crm_trace("Action %s on %s matches", key, pe__node_name(on_node));
result = g_list_prepend(result, action);
}
}
return result;
}
/*!
* \brief Find all actions of given type for a resource
*
* \param[in] rsc Resource to search
* \param[in] node Find only actions scheduled on this node
* \param[in] task Action name to search for
* \param[in] require_node If TRUE, NULL node or action node will not match
*
* \return List of actions found (or NULL if none)
* \note If node is not NULL and require_node is FALSE, matching actions
* without a node will be assigned to node.
*/
GList *
pe__resource_actions(const pcmk_resource_t *rsc, const pcmk_node_t *node,
const char *task, bool require_node)
{
GList *result = NULL;
char *key = pcmk__op_key(rsc->id, task, 0);
if (require_node) {
result = find_actions_exact(rsc->actions, key, node);
} else {
result = find_actions(rsc->actions, key, node);
}
free(key);
return result;
}
/*!
* \internal
* \brief Create an action reason string based on the action itself
*
* \param[in] action Action to create reason string for
* \param[in] flag Action flag that was cleared
*
* \return Newly allocated string suitable for use as action reason
* \note It is the caller's responsibility to free() the result.
*/
char *
pe__action2reason(const pcmk_action_t *action, enum pe_action_flags flag)
{
const char *change = NULL;
switch (flag) {
case pcmk_action_runnable:
change = "unrunnable";
break;
case pcmk_action_migratable:
change = "unmigrateable";
break;
case pcmk_action_optional:
change = "required";
break;
default:
// Bug: caller passed unsupported flag
CRM_CHECK(change != NULL, change = "");
break;
}
return crm_strdup_printf("%s%s%s %s", change,
(action->rsc == NULL)? "" : " ",
(action->rsc == NULL)? "" : action->rsc->id,
action->task);
}
void pe_action_set_reason(pcmk_action_t *action, const char *reason,
bool overwrite)
{
if (action->reason != NULL && overwrite) {
pcmk__rsc_trace(action->rsc, "Changing %s reason from '%s' to '%s'",
action->uuid, action->reason,
pcmk__s(reason, "(none)"));
} else if (action->reason == NULL) {
pcmk__rsc_trace(action->rsc, "Set %s reason to '%s'",
action->uuid, pcmk__s(reason, "(none)"));
} else {
// crm_assert(action->reason != NULL && !overwrite);
return;
}
pcmk__str_update(&action->reason, reason);
}
/*!
* \internal
* \brief Create an action to clear a resource's history from CIB
*
* \param[in,out] rsc Resource to clear
* \param[in] node Node to clear history on
*/
void
pe__clear_resource_history(pcmk_resource_t *rsc, const pcmk_node_t *node)
{
CRM_ASSERT((rsc != NULL) && (node != NULL));
custom_action(rsc, pcmk__op_key(rsc->id, PCMK_ACTION_LRM_DELETE, 0),
PCMK_ACTION_LRM_DELETE, node, FALSE, rsc->cluster);
}
#define sort_return(an_int, why) do { \
free(a_uuid); \
free(b_uuid); \
crm_trace("%s (%d) %c %s (%d) : %s", \
a_xml_id, a_call_id, an_int>0?'>':an_int<0?'<':'=', \
b_xml_id, b_call_id, why); \
return an_int; \
} while(0)
int
pe__is_newer_op(const xmlNode *xml_a, const xmlNode *xml_b,
bool same_node_default)
{
int a_call_id = -1;
int b_call_id = -1;
char *a_uuid = NULL;
char *b_uuid = NULL;
const char *a_xml_id = crm_element_value(xml_a, PCMK_XA_ID);
const char *b_xml_id = crm_element_value(xml_b, PCMK_XA_ID);
const char *a_node = crm_element_value(xml_a, XML_LRM_ATTR_TARGET);
const char *b_node = crm_element_value(xml_b, XML_LRM_ATTR_TARGET);
bool same_node = true;
/* @COMPAT The on_node attribute was added to last_failure as of 1.1.13 (via
* 8b3ca1c) and the other entries as of 1.1.12 (via 0b07b5c).
*
* In case that any of the lrm_rsc_op entries doesn't have on_node
* attribute, we need to explicitly tell whether the two operations are on
* the same node.
*/
if (a_node == NULL || b_node == NULL) {
same_node = same_node_default;
} else {
same_node = pcmk__str_eq(a_node, b_node, pcmk__str_casei);
}
if (same_node && pcmk__str_eq(a_xml_id, b_xml_id, pcmk__str_none)) {
/* We have duplicate lrm_rsc_op entries in the status
* section which is unlikely to be a good thing
* - we can handle it easily enough, but we need to get
* to the bottom of why it's happening.
*/
pcmk__config_err("Duplicate lrm_rsc_op entries named %s", a_xml_id);
sort_return(0, "duplicate");
}
crm_element_value_int(xml_a, XML_LRM_ATTR_CALLID, &a_call_id);
crm_element_value_int(xml_b, XML_LRM_ATTR_CALLID, &b_call_id);
if (a_call_id == -1 && b_call_id == -1) {
/* both are pending ops so it doesn't matter since
* stops are never pending
*/
sort_return(0, "pending");
} else if (same_node && a_call_id >= 0 && a_call_id < b_call_id) {
sort_return(-1, "call id");
} else if (same_node && b_call_id >= 0 && a_call_id > b_call_id) {
sort_return(1, "call id");
} else if (a_call_id >= 0 && b_call_id >= 0
&& (!same_node || a_call_id == b_call_id)) {
/*
* The op and last_failed_op are the same
* Order on last-rc-change
*/
time_t last_a = -1;
time_t last_b = -1;
crm_element_value_epoch(xml_a, XML_RSC_OP_LAST_CHANGE, &last_a);
crm_element_value_epoch(xml_b, XML_RSC_OP_LAST_CHANGE, &last_b);
crm_trace("rc-change: %lld vs %lld",
(long long) last_a, (long long) last_b);
if (last_a >= 0 && last_a < last_b) {
sort_return(-1, "rc-change");
} else if (last_b >= 0 && last_a > last_b) {
sort_return(1, "rc-change");
}
sort_return(0, "rc-change");
} else {
/* One of the inputs is a pending operation
* Attempt to use XML_ATTR_TRANSITION_MAGIC to determine its age relative to the other
*/
int a_id = -1;
int b_id = -1;
const char *a_magic = crm_element_value(xml_a, XML_ATTR_TRANSITION_MAGIC);
const char *b_magic = crm_element_value(xml_b, XML_ATTR_TRANSITION_MAGIC);
CRM_CHECK(a_magic != NULL && b_magic != NULL, sort_return(0, "No magic"));
if (!decode_transition_magic(a_magic, &a_uuid, &a_id, NULL, NULL, NULL,
NULL)) {
sort_return(0, "bad magic a");
}
if (!decode_transition_magic(b_magic, &b_uuid, &b_id, NULL, NULL, NULL,
NULL)) {
sort_return(0, "bad magic b");
}
/* try to determine the relative age of the operation...
* some pending operations (e.g. a start) may have been superseded
* by a subsequent stop
*
* [a|b]_id == -1 means it's a shutdown operation and _always_ comes last
*/
if (!pcmk__str_eq(a_uuid, b_uuid, pcmk__str_casei) || a_id == b_id) {
/*
* some of the logic in here may be redundant...
*
* if the UUID from the TE doesn't match then one better
* be a pending operation.
* pending operations don't survive between elections and joins
* because we query the LRM directly
*/
if (b_call_id == -1) {
sort_return(-1, "transition + call");
} else if (a_call_id == -1) {
sort_return(1, "transition + call");
}
} else if ((a_id >= 0 && a_id < b_id) || b_id == -1) {
sort_return(-1, "transition");
} else if ((b_id >= 0 && a_id > b_id) || a_id == -1) {
sort_return(1, "transition");
}
}
/* we should never end up here */
CRM_CHECK(FALSE, sort_return(0, "default"));
}
gint
sort_op_by_callid(gconstpointer a, gconstpointer b)
{
const xmlNode *xml_a = a;
const xmlNode *xml_b = b;
return pe__is_newer_op(xml_a, xml_b, true);
}
/*!
* \internal
* \brief Create a new pseudo-action for a resource
*
* \param[in,out] rsc Resource to create action for
* \param[in] task Action name
* \param[in] optional Whether action should be considered optional
* \param[in] runnable Whethe action should be considered runnable
*
* \return New action object corresponding to arguments
*/
pcmk_action_t *
pe__new_rsc_pseudo_action(pcmk_resource_t *rsc, const char *task, bool optional,
bool runnable)
{
pcmk_action_t *action = NULL;
CRM_ASSERT((rsc != NULL) && (task != NULL));
action = custom_action(rsc, pcmk__op_key(rsc->id, task, 0), task, NULL,
optional, rsc->cluster);
pe__set_action_flags(action, pcmk_action_pseudo);
if (runnable) {
pe__set_action_flags(action, pcmk_action_runnable);
}
return action;
}
/*!
* \internal
* \brief Add the expected result to an action
*
* \param[in,out] action Action to add expected result to
* \param[in] expected_result Expected result to add
*
* \note This is more efficient than calling add_hash_param().
*/
void
pe__add_action_expected_result(pcmk_action_t *action, int expected_result)
{
char *name = NULL;
CRM_ASSERT((action != NULL) && (action->meta != NULL));
name = strdup(XML_ATTR_TE_TARGET_RC);
CRM_ASSERT (name != NULL);
g_hash_table_insert(action->meta, name, pcmk__itoa(expected_result));
}
diff --git a/lib/pengine/pe_output.c b/lib/pengine/pe_output.c
index 024330d7a9..ce68a1c01a 100644
--- a/lib/pengine/pe_output.c
+++ b/lib/pengine/pe_output.c
@@ -1,3177 +1,3177 @@
/*
* Copyright 2019-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdint.h>
#include <crm/common/xml_internal.h>
#include <crm/common/output.h>
#include <crm/common/scheduler_internal.h>
#include <crm/cib/util.h>
#include <crm/msg_xml.h>
#include <crm/pengine/internal.h>
const char *
pe__resource_description(const pcmk_resource_t *rsc, uint32_t show_opts)
{
const char * desc = NULL;
// User-supplied description
if (pcmk_any_flags_set(show_opts, pcmk_show_rsc_only|pcmk_show_description)) {
desc = crm_element_value(rsc->xml, PCMK_XA_DESCRIPTION);
}
return desc;
}
/* Never display node attributes whose name starts with one of these prefixes */
#define FILTER_STR { PCMK__FAIL_COUNT_PREFIX, PCMK__LAST_FAILURE_PREFIX, \
"shutdown", PCMK_NODE_ATTR_TERMINATE, "standby", "#", \
NULL }
static int
compare_attribute(gconstpointer a, gconstpointer b)
{
int rc;
rc = strcmp((const char *)a, (const char *)b);
return rc;
}
/*!
* \internal
* \brief Determine whether extended information about an attribute should be added.
*
* \param[in] node Node that ran this resource
* \param[in,out] rsc_list List of resources for this node
* \param[in,out] scheduler Scheduler data
* \param[in] attrname Attribute to find
* \param[out] expected_score Expected value for this attribute
*
* \return true if extended information should be printed, false otherwise
* \note Currently, extended information is only supported for ping/pingd
* resources, for which a message will be printed if connectivity is lost
* or degraded.
*/
static bool
add_extra_info(const pcmk_node_t *node, GList *rsc_list,
pcmk_scheduler_t *scheduler, const char *attrname,
int *expected_score)
{
GList *gIter = NULL;
for (gIter = rsc_list; gIter != NULL; gIter = gIter->next) {
pcmk_resource_t *rsc = (pcmk_resource_t *) gIter->data;
- const char *type = g_hash_table_lookup(rsc->meta, "type");
+ const char *type = g_hash_table_lookup(rsc->meta, XML_ATTR_TYPE);
const char *name = NULL;
GHashTable *params = NULL;
if (rsc->children != NULL) {
if (add_extra_info(node, rsc->children, scheduler, attrname,
expected_score)) {
return true;
}
}
if (!pcmk__strcase_any_of(type, "ping", "pingd", NULL)) {
continue;
}
params = pe_rsc_params(rsc, node, scheduler);
name = g_hash_table_lookup(params, "name");
if (name == NULL) {
name = "pingd";
}
/* To identify the resource with the attribute name. */
if (pcmk__str_eq(name, attrname, pcmk__str_casei)) {
int host_list_num = 0;
const char *hosts = g_hash_table_lookup(params, "host_list");
const char *multiplier = g_hash_table_lookup(params, "multiplier");
int multiplier_i;
if (hosts) {
char **host_list = g_strsplit(hosts, " ", 0);
host_list_num = g_strv_length(host_list);
g_strfreev(host_list);
}
if ((multiplier == NULL)
|| (pcmk__scan_min_int(multiplier, &multiplier_i,
INT_MIN) != pcmk_rc_ok)) {
/* The ocf:pacemaker:ping resource agent defaults multiplier to
* 1. The agent currently does not handle invalid text, but it
* should, and this would be a reasonable choice ...
*/
multiplier_i = 1;
}
*expected_score = host_list_num * multiplier_i;
return true;
}
}
return false;
}
static GList *
filter_attr_list(GList *attr_list, char *name)
{
int i;
const char *filt_str[] = FILTER_STR;
CRM_CHECK(name != NULL, return attr_list);
/* filtering automatic attributes */
for (i = 0; filt_str[i] != NULL; i++) {
if (g_str_has_prefix(name, filt_str[i])) {
return attr_list;
}
}
return g_list_insert_sorted(attr_list, name, compare_attribute);
}
static GList *
get_operation_list(xmlNode *rsc_entry) {
GList *op_list = NULL;
xmlNode *rsc_op = NULL;
for (rsc_op = pcmk__xe_first_child(rsc_entry); rsc_op != NULL;
rsc_op = pcmk__xe_next(rsc_op)) {
const char *task = crm_element_value(rsc_op, XML_LRM_ATTR_TASK);
const char *interval_ms_s = crm_element_value(rsc_op,
XML_LRM_ATTR_INTERVAL_MS);
const char *op_rc = crm_element_value(rsc_op, XML_LRM_ATTR_RC);
int op_rc_i;
pcmk__scan_min_int(op_rc, &op_rc_i, 0);
/* Display 0-interval monitors as "probe" */
if (pcmk__str_eq(task, PCMK_ACTION_MONITOR, pcmk__str_casei)
&& pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches | pcmk__str_casei)) {
task = "probe";
}
/* Ignore notifies and some probes */
if (pcmk__str_eq(task, PCMK_ACTION_NOTIFY, pcmk__str_none)
|| (pcmk__str_eq(task, "probe", pcmk__str_none)
&& (op_rc_i == CRM_EX_NOT_RUNNING))) {
continue;
}
if (pcmk__str_eq((const char *)rsc_op->name, XML_LRM_TAG_RSC_OP, pcmk__str_none)) {
op_list = g_list_append(op_list, rsc_op);
}
}
op_list = g_list_sort(op_list, sort_op_by_callid);
return op_list;
}
static void
add_dump_node(gpointer key, gpointer value, gpointer user_data)
{
xmlNodePtr node = user_data;
pcmk_create_xml_text_node(node, (const char *) key, (const char *) value);
}
static void
append_dump_text(gpointer key, gpointer value, gpointer user_data)
{
char **dump_text = user_data;
char *new_text = crm_strdup_printf("%s %s=%s",
*dump_text, (char *)key, (char *)value);
free(*dump_text);
*dump_text = new_text;
}
#define XPATH_STACK "//" XML_CIB_TAG_NVPAIR \
"[@" XML_NVPAIR_ATTR_NAME "='" \
PCMK_OPT_CLUSTER_INFRASTRUCTURE "']"
static const char *
get_cluster_stack(pcmk_scheduler_t *scheduler)
{
xmlNode *stack = get_xpath_object(XPATH_STACK, scheduler->input, LOG_DEBUG);
return stack? crm_element_value(stack, XML_NVPAIR_ATTR_VALUE) : "unknown";
}
static char *
last_changed_string(const char *last_written, const char *user,
const char *client, const char *origin) {
if (last_written != NULL || user != NULL || client != NULL || origin != NULL) {
return crm_strdup_printf("%s%s%s%s%s%s%s",
last_written ? last_written : "",
user ? " by " : "",
user ? user : "",
client ? " via " : "",
client ? client : "",
origin ? " on " : "",
origin ? origin : "");
} else {
return strdup("");
}
}
static char *
op_history_string(xmlNode *xml_op, const char *task, const char *interval_ms_s,
int rc, bool print_timing) {
const char *call = crm_element_value(xml_op, XML_LRM_ATTR_CALLID);
char *interval_str = NULL;
char *buf = NULL;
if (interval_ms_s && !pcmk__str_eq(interval_ms_s, "0", pcmk__str_casei)) {
char *pair = pcmk__format_nvpair("interval", interval_ms_s, "ms");
interval_str = crm_strdup_printf(" %s", pair);
free(pair);
}
if (print_timing) {
char *last_change_str = NULL;
char *exec_str = NULL;
char *queue_str = NULL;
const char *value = NULL;
time_t epoch = 0;
if ((crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE, &epoch) == pcmk_ok)
&& (epoch > 0)) {
char *epoch_str = pcmk__epoch2str(&epoch, 0);
last_change_str = crm_strdup_printf(" %s=\"%s\"",
XML_RSC_OP_LAST_CHANGE,
pcmk__s(epoch_str, ""));
free(epoch_str);
}
value = crm_element_value(xml_op, XML_RSC_OP_T_EXEC);
if (value) {
char *pair = pcmk__format_nvpair(XML_RSC_OP_T_EXEC, value, "ms");
exec_str = crm_strdup_printf(" %s", pair);
free(pair);
}
value = crm_element_value(xml_op, XML_RSC_OP_T_QUEUE);
if (value) {
char *pair = pcmk__format_nvpair(XML_RSC_OP_T_QUEUE, value, "ms");
queue_str = crm_strdup_printf(" %s", pair);
free(pair);
}
buf = crm_strdup_printf("(%s) %s:%s%s%s%s rc=%d (%s)", call, task,
interval_str ? interval_str : "",
last_change_str ? last_change_str : "",
exec_str ? exec_str : "",
queue_str ? queue_str : "",
rc, services_ocf_exitcode_str(rc));
if (last_change_str) {
free(last_change_str);
}
if (exec_str) {
free(exec_str);
}
if (queue_str) {
free(queue_str);
}
} else {
buf = crm_strdup_printf("(%s) %s%s%s", call, task,
interval_str ? ":" : "",
interval_str ? interval_str : "");
}
if (interval_str) {
free(interval_str);
}
return buf;
}
static char *
resource_history_string(pcmk_resource_t *rsc, const char *rsc_id, bool all,
int failcount, time_t last_failure) {
char *buf = NULL;
if (rsc == NULL) {
buf = crm_strdup_printf("%s: orphan", rsc_id);
} else if (all || failcount || last_failure > 0) {
char *failcount_s = NULL;
char *lastfail_s = NULL;
if (failcount > 0) {
failcount_s = crm_strdup_printf(" %s=%d", PCMK__FAIL_COUNT_PREFIX,
failcount);
} else {
failcount_s = strdup("");
}
if (last_failure > 0) {
buf = pcmk__epoch2str(&last_failure, 0);
lastfail_s = crm_strdup_printf(" %s='%s'",
PCMK__LAST_FAILURE_PREFIX, buf);
free(buf);
}
buf = crm_strdup_printf("%s: migration-threshold=%d%s%s",
rsc_id, rsc->migration_threshold, failcount_s,
lastfail_s? lastfail_s : "");
free(failcount_s);
free(lastfail_s);
} else {
buf = crm_strdup_printf("%s:", rsc_id);
}
return buf;
}
/*!
* \internal
* \brief Get a node's feature set for status display purposes
*
* \param[in] node Node to check
*
* \return String representation of feature set if the node is fully up (using
* "<3.15.1" for older nodes that don't set the #feature-set attribute),
* otherwise NULL
*/
static const char *
get_node_feature_set(const pcmk_node_t *node)
{
if (node->details->online && node->details->expected_up
&& !pe__is_guest_or_remote_node(node)) {
const char *feature_set = g_hash_table_lookup(node->details->attrs,
CRM_ATTR_FEATURE_SET);
/* The feature set attribute is present since 3.15.1. If it is missing,
* then the node must be running an earlier version.
*/
return pcmk__s(feature_set, "<3.15.1");
}
return NULL;
}
static bool
is_mixed_version(pcmk_scheduler_t *scheduler)
{
const char *feature_set = NULL;
for (GList *gIter = scheduler->nodes; gIter != NULL; gIter = gIter->next) {
pcmk_node_t *node = gIter->data;
const char *node_feature_set = get_node_feature_set(node);
if (node_feature_set != NULL) {
if (feature_set == NULL) {
feature_set = node_feature_set;
} else if (strcmp(feature_set, node_feature_set) != 0) {
return true;
}
}
}
return false;
}
static char *
formatted_xml_buf(const pcmk_resource_t *rsc, bool raw)
{
if (raw) {
return dump_xml_formatted(rsc->orig_xml ? rsc->orig_xml : rsc->xml);
} else {
return dump_xml_formatted(rsc->xml);
}
}
#define XPATH_DC_VERSION "//" XML_CIB_TAG_NVPAIR \
"[@" XML_NVPAIR_ATTR_NAME "='" \
PCMK_OPT_DC_VERSION "']"
PCMK__OUTPUT_ARGS("cluster-summary", "pcmk_scheduler_t *",
"enum pcmk_pacemakerd_state", "uint32_t", "uint32_t")
static int
cluster_summary(pcmk__output_t *out, va_list args) {
pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
enum pcmk_pacemakerd_state pcmkd_state =
(enum pcmk_pacemakerd_state) va_arg(args, int);
uint32_t section_opts = va_arg(args, uint32_t);
uint32_t show_opts = va_arg(args, uint32_t);
int rc = pcmk_rc_no_output;
const char *stack_s = get_cluster_stack(scheduler);
if (pcmk_is_set(section_opts, pcmk_section_stack)) {
PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
out->message(out, "cluster-stack", stack_s, pcmkd_state);
}
if (pcmk_is_set(section_opts, pcmk_section_dc)) {
xmlNode *dc_version = get_xpath_object(XPATH_DC_VERSION,
scheduler->input, LOG_DEBUG);
const char *dc_version_s = dc_version?
crm_element_value(dc_version, XML_NVPAIR_ATTR_VALUE)
: NULL;
const char *quorum = crm_element_value(scheduler->input,
PCMK_XA_HAVE_QUORUM);
char *dc_name = scheduler->dc_node? pe__node_display_name(scheduler->dc_node, pcmk_is_set(show_opts, pcmk_show_node_id)) : NULL;
bool mixed_version = is_mixed_version(scheduler);
PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
out->message(out, "cluster-dc", scheduler->dc_node, quorum,
dc_version_s, dc_name, mixed_version);
free(dc_name);
}
if (pcmk_is_set(section_opts, pcmk_section_times)) {
const char *last_written = crm_element_value(scheduler->input,
PCMK_XA_CIB_LAST_WRITTEN);
const char *user = crm_element_value(scheduler->input,
XML_ATTR_UPDATE_USER);
const char *client = crm_element_value(scheduler->input,
XML_ATTR_UPDATE_CLIENT);
const char *origin = crm_element_value(scheduler->input,
XML_ATTR_UPDATE_ORIG);
PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
out->message(out, "cluster-times",
scheduler->localhost, last_written, user, client, origin);
}
if (pcmk_is_set(section_opts, pcmk_section_counts)) {
PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
out->message(out, "cluster-counts", g_list_length(scheduler->nodes),
scheduler->ninstances, scheduler->disabled_resources,
scheduler->blocked_resources);
}
if (pcmk_is_set(section_opts, pcmk_section_options)) {
PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
out->message(out, "cluster-options", scheduler);
}
PCMK__OUTPUT_LIST_FOOTER(out, rc);
if (pcmk_is_set(section_opts, pcmk_section_maint_mode)) {
if (out->message(out, "maint-mode", scheduler->flags) == pcmk_rc_ok) {
rc = pcmk_rc_ok;
}
}
return rc;
}
PCMK__OUTPUT_ARGS("cluster-summary", "pcmk_scheduler_t *",
"enum pcmk_pacemakerd_state", "uint32_t", "uint32_t")
static int
cluster_summary_html(pcmk__output_t *out, va_list args) {
pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
enum pcmk_pacemakerd_state pcmkd_state =
(enum pcmk_pacemakerd_state) va_arg(args, int);
uint32_t section_opts = va_arg(args, uint32_t);
uint32_t show_opts = va_arg(args, uint32_t);
int rc = pcmk_rc_no_output;
const char *stack_s = get_cluster_stack(scheduler);
if (pcmk_is_set(section_opts, pcmk_section_stack)) {
PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
out->message(out, "cluster-stack", stack_s, pcmkd_state);
}
/* Always print DC if none, even if not requested */
if ((scheduler->dc_node == NULL)
|| pcmk_is_set(section_opts, pcmk_section_dc)) {
xmlNode *dc_version = get_xpath_object(XPATH_DC_VERSION,
scheduler->input, LOG_DEBUG);
const char *dc_version_s = dc_version?
crm_element_value(dc_version, XML_NVPAIR_ATTR_VALUE)
: NULL;
const char *quorum = crm_element_value(scheduler->input,
PCMK_XA_HAVE_QUORUM);
char *dc_name = scheduler->dc_node? pe__node_display_name(scheduler->dc_node, pcmk_is_set(show_opts, pcmk_show_node_id)) : NULL;
bool mixed_version = is_mixed_version(scheduler);
PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
out->message(out, "cluster-dc", scheduler->dc_node, quorum,
dc_version_s, dc_name, mixed_version);
free(dc_name);
}
if (pcmk_is_set(section_opts, pcmk_section_times)) {
const char *last_written = crm_element_value(scheduler->input,
PCMK_XA_CIB_LAST_WRITTEN);
const char *user = crm_element_value(scheduler->input,
XML_ATTR_UPDATE_USER);
const char *client = crm_element_value(scheduler->input,
XML_ATTR_UPDATE_CLIENT);
const char *origin = crm_element_value(scheduler->input,
XML_ATTR_UPDATE_ORIG);
PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
out->message(out, "cluster-times",
scheduler->localhost, last_written, user, client, origin);
}
if (pcmk_is_set(section_opts, pcmk_section_counts)) {
PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
out->message(out, "cluster-counts", g_list_length(scheduler->nodes),
scheduler->ninstances, scheduler->disabled_resources,
scheduler->blocked_resources);
}
if (pcmk_is_set(section_opts, pcmk_section_options)) {
/* Kind of a hack - close the list we may have opened earlier in this
* function so we can put all the options into their own list. We
* only want to do this on HTML output, though.
*/
PCMK__OUTPUT_LIST_FOOTER(out, rc);
out->begin_list(out, NULL, NULL, "Config Options");
out->message(out, "cluster-options", scheduler);
}
PCMK__OUTPUT_LIST_FOOTER(out, rc);
if (pcmk_is_set(section_opts, pcmk_section_maint_mode)) {
if (out->message(out, "maint-mode", scheduler->flags) == pcmk_rc_ok) {
rc = pcmk_rc_ok;
}
}
return rc;
}
char *
pe__node_display_name(pcmk_node_t *node, bool print_detail)
{
char *node_name;
const char *node_host = NULL;
const char *node_id = NULL;
int name_len;
CRM_ASSERT((node != NULL) && (node->details != NULL) && (node->details->uname != NULL));
/* Host is displayed only if this is a guest node and detail is requested */
if (print_detail && pe__is_guest_node(node)) {
const pcmk_resource_t *container = node->details->remote_rsc->container;
const pcmk_node_t *host_node = pe__current_node(container);
if (host_node && host_node->details) {
node_host = host_node->details->uname;
}
if (node_host == NULL) {
node_host = ""; /* so we at least get "uname@" to indicate guest */
}
}
/* Node ID is displayed if different from uname and detail is requested */
if (print_detail && !pcmk__str_eq(node->details->uname, node->details->id, pcmk__str_casei)) {
node_id = node->details->id;
}
/* Determine name length */
name_len = strlen(node->details->uname) + 1;
if (node_host) {
name_len += strlen(node_host) + 1; /* "@node_host" */
}
if (node_id) {
name_len += strlen(node_id) + 3; /* + " (node_id)" */
}
/* Allocate and populate display name */
node_name = malloc(name_len);
CRM_ASSERT(node_name != NULL);
strcpy(node_name, node->details->uname);
if (node_host) {
strcat(node_name, "@");
strcat(node_name, node_host);
}
if (node_id) {
strcat(node_name, " (");
strcat(node_name, node_id);
strcat(node_name, ")");
}
return node_name;
}
int
pe__name_and_nvpairs_xml(pcmk__output_t *out, bool is_list, const char *tag_name
, size_t pairs_count, ...)
{
xmlNodePtr xml_node = NULL;
va_list args;
CRM_ASSERT(tag_name != NULL);
xml_node = pcmk__output_xml_peek_parent(out);
CRM_ASSERT(xml_node != NULL);
xml_node = create_xml_node(xml_node, tag_name);
va_start(args, pairs_count);
while(pairs_count--) {
const char *param_name = va_arg(args, const char *);
const char *param_value = va_arg(args, const char *);
if (param_name && param_value) {
crm_xml_add(xml_node, param_name, param_value);
}
};
va_end(args);
if (is_list) {
pcmk__output_xml_push_parent(out, xml_node);
}
return pcmk_rc_ok;
}
static const char *
role_desc(enum rsc_role_e role)
{
if (role == pcmk_role_promoted) {
#ifdef PCMK__COMPAT_2_0
return "as " PCMK__ROLE_PROMOTED_LEGACY " ";
#else
return "in " PCMK__ROLE_PROMOTED " role ";
#endif
}
return "";
}
PCMK__OUTPUT_ARGS("ban", "pcmk_node_t *", "pcmk__location_t *", "uint32_t")
static int
ban_html(pcmk__output_t *out, va_list args) {
pcmk_node_t *pe_node = va_arg(args, pcmk_node_t *);
pcmk__location_t *location = va_arg(args, pcmk__location_t *);
uint32_t show_opts = va_arg(args, uint32_t);
char *node_name = pe__node_display_name(pe_node,
pcmk_is_set(show_opts, pcmk_show_node_id));
char *buf = crm_strdup_printf("%s\tprevents %s from running %son %s",
location->id, location->rsc->id,
role_desc(location->role_filter), node_name);
pcmk__output_create_html_node(out, "li", NULL, NULL, buf);
free(node_name);
free(buf);
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("ban", "pcmk_node_t *", "pcmk__location_t *", "uint32_t")
static int
ban_text(pcmk__output_t *out, va_list args) {
pcmk_node_t *pe_node = va_arg(args, pcmk_node_t *);
pcmk__location_t *location = va_arg(args, pcmk__location_t *);
uint32_t show_opts = va_arg(args, uint32_t);
char *node_name = pe__node_display_name(pe_node,
pcmk_is_set(show_opts, pcmk_show_node_id));
out->list_item(out, NULL, "%s\tprevents %s from running %son %s",
location->id, location->rsc->id,
role_desc(location->role_filter), node_name);
free(node_name);
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("ban", "pcmk_node_t *", "pcmk__location_t *", "uint32_t")
static int
ban_xml(pcmk__output_t *out, va_list args) {
pcmk_node_t *pe_node = va_arg(args, pcmk_node_t *);
pcmk__location_t *location = va_arg(args, pcmk__location_t *);
uint32_t show_opts G_GNUC_UNUSED = va_arg(args, uint32_t);
const char *promoted_only = pcmk__btoa(location->role_filter == pcmk_role_promoted);
char *weight_s = pcmk__itoa(pe_node->weight);
pcmk__output_create_xml_node(out, "ban",
PCMK_XA_ID, location->id,
"resource", location->rsc->id,
"node", pe_node->details->uname,
"weight", weight_s,
"promoted-only", promoted_only,
/* This is a deprecated alias for
* promoted_only. Removing it will break
* backward compatibility of the API schema,
* which will require an API schema major
* version bump.
*/
"master_only", promoted_only,
NULL);
free(weight_s);
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("ban-list", "pcmk_scheduler_t *", "const char *", "GList *",
"uint32_t", "bool")
static int
ban_list(pcmk__output_t *out, va_list args) {
pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
const char *prefix = va_arg(args, const char *);
GList *only_rsc = va_arg(args, GList *);
uint32_t show_opts = va_arg(args, uint32_t);
bool print_spacer = va_arg(args, int);
GList *gIter, *gIter2;
int rc = pcmk_rc_no_output;
/* Print each ban */
for (gIter = scheduler->placement_constraints;
gIter != NULL; gIter = gIter->next) {
pcmk__location_t *location = gIter->data;
const pcmk_resource_t *rsc = location->rsc;
if (prefix != NULL && !g_str_has_prefix(location->id, prefix)) {
continue;
}
if (!pcmk__str_in_list(rsc_printable_id(rsc), only_rsc,
pcmk__str_star_matches)
&& !pcmk__str_in_list(rsc_printable_id(pe__const_top_resource(rsc, false)),
only_rsc, pcmk__str_star_matches)) {
continue;
}
for (gIter2 = location->nodes; gIter2 != NULL; gIter2 = gIter2->next) {
pcmk_node_t *node = (pcmk_node_t *) gIter2->data;
if (node->weight < 0) {
PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Negative Location Constraints");
out->message(out, "ban", node, location, show_opts);
}
}
}
PCMK__OUTPUT_LIST_FOOTER(out, rc);
return rc;
}
PCMK__OUTPUT_ARGS("cluster-counts", "unsigned int", "int", "int", "int")
static int
cluster_counts_html(pcmk__output_t *out, va_list args) {
unsigned int nnodes = va_arg(args, unsigned int);
int nresources = va_arg(args, int);
int ndisabled = va_arg(args, int);
int nblocked = va_arg(args, int);
xmlNodePtr nodes_node = pcmk__output_create_xml_node(out, "li", NULL);
xmlNodePtr resources_node = pcmk__output_create_xml_node(out, "li", NULL);
char *nnodes_str = crm_strdup_printf("%d node%s configured",
nnodes, pcmk__plural_s(nnodes));
pcmk_create_html_node(nodes_node, "span", NULL, NULL, nnodes_str);
free(nnodes_str);
if (ndisabled && nblocked) {
char *s = crm_strdup_printf("%d resource instance%s configured (%d ",
nresources, pcmk__plural_s(nresources),
ndisabled);
pcmk_create_html_node(resources_node, "span", NULL, NULL, s);
free(s);
pcmk_create_html_node(resources_node, "span", NULL, "bold", "DISABLED");
s = crm_strdup_printf(", %d ", nblocked);
pcmk_create_html_node(resources_node, "span", NULL, NULL, s);
free(s);
pcmk_create_html_node(resources_node, "span", NULL, "bold", "BLOCKED");
pcmk_create_html_node(resources_node, "span", NULL, NULL,
" from further action due to failure)");
} else if (ndisabled && !nblocked) {
char *s = crm_strdup_printf("%d resource instance%s configured (%d ",
nresources, pcmk__plural_s(nresources),
ndisabled);
pcmk_create_html_node(resources_node, "span", NULL, NULL, s);
free(s);
pcmk_create_html_node(resources_node, "span", NULL, "bold", "DISABLED");
pcmk_create_html_node(resources_node, "span", NULL, NULL, ")");
} else if (!ndisabled && nblocked) {
char *s = crm_strdup_printf("%d resource instance%s configured (%d ",
nresources, pcmk__plural_s(nresources),
nblocked);
pcmk_create_html_node(resources_node, "span", NULL, NULL, s);
free(s);
pcmk_create_html_node(resources_node, "span", NULL, "bold", "BLOCKED");
pcmk_create_html_node(resources_node, "span", NULL, NULL,
" from further action due to failure)");
} else {
char *s = crm_strdup_printf("%d resource instance%s configured",
nresources, pcmk__plural_s(nresources));
pcmk_create_html_node(resources_node, "span", NULL, NULL, s);
free(s);
}
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("cluster-counts", "unsigned int", "int", "int", "int")
static int
cluster_counts_text(pcmk__output_t *out, va_list args) {
unsigned int nnodes = va_arg(args, unsigned int);
int nresources = va_arg(args, int);
int ndisabled = va_arg(args, int);
int nblocked = va_arg(args, int);
out->list_item(out, NULL, "%d node%s configured",
nnodes, pcmk__plural_s(nnodes));
if (ndisabled && nblocked) {
out->list_item(out, NULL, "%d resource instance%s configured "
"(%d DISABLED, %d BLOCKED from "
"further action due to failure)",
nresources, pcmk__plural_s(nresources), ndisabled,
nblocked);
} else if (ndisabled && !nblocked) {
out->list_item(out, NULL, "%d resource instance%s configured "
"(%d DISABLED)",
nresources, pcmk__plural_s(nresources), ndisabled);
} else if (!ndisabled && nblocked) {
out->list_item(out, NULL, "%d resource instance%s configured "
"(%d BLOCKED from further action "
"due to failure)",
nresources, pcmk__plural_s(nresources), nblocked);
} else {
out->list_item(out, NULL, "%d resource instance%s configured",
nresources, pcmk__plural_s(nresources));
}
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("cluster-counts", "unsigned int", "int", "int", "int")
static int
cluster_counts_xml(pcmk__output_t *out, va_list args) {
unsigned int nnodes = va_arg(args, unsigned int);
int nresources = va_arg(args, int);
int ndisabled = va_arg(args, int);
int nblocked = va_arg(args, int);
xmlNodePtr nodes_node = pcmk__output_create_xml_node(out, "nodes_configured", NULL);
xmlNodePtr resources_node = pcmk__output_create_xml_node(out, "resources_configured", NULL);
char *s = pcmk__itoa(nnodes);
crm_xml_add(nodes_node, "number", s);
free(s);
s = pcmk__itoa(nresources);
crm_xml_add(resources_node, "number", s);
free(s);
s = pcmk__itoa(ndisabled);
crm_xml_add(resources_node, "disabled", s);
free(s);
s = pcmk__itoa(nblocked);
crm_xml_add(resources_node, "blocked", s);
free(s);
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("cluster-dc", "pcmk_node_t *", "const char *", "const char *",
"char *", "int")
static int
cluster_dc_html(pcmk__output_t *out, va_list args) {
pcmk_node_t *dc = va_arg(args, pcmk_node_t *);
const char *quorum = va_arg(args, const char *);
const char *dc_version_s = va_arg(args, const char *);
char *dc_name = va_arg(args, char *);
bool mixed_version = va_arg(args, int);
xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL);
pcmk_create_html_node(node, "span", NULL, "bold", "Current DC: ");
if (dc) {
char *buf = crm_strdup_printf("%s (version %s) -", dc_name,
dc_version_s ? dc_version_s : "unknown");
pcmk_create_html_node(node, "span", NULL, NULL, buf);
free(buf);
if (mixed_version) {
pcmk_create_html_node(node, "span", NULL, "warning",
" MIXED-VERSION");
}
pcmk_create_html_node(node, "span", NULL, NULL, " partition");
if (crm_is_true(quorum)) {
pcmk_create_html_node(node, "span", NULL, NULL, " with");
} else {
pcmk_create_html_node(node, "span", NULL, "warning", " WITHOUT");
}
pcmk_create_html_node(node, "span", NULL, NULL, " quorum");
} else {
pcmk_create_html_node(node, "span", NULL, "warning", "NONE");
}
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("cluster-dc", "pcmk_node_t *", "const char *", "const char *",
"char *", "int")
static int
cluster_dc_text(pcmk__output_t *out, va_list args) {
pcmk_node_t *dc = va_arg(args, pcmk_node_t *);
const char *quorum = va_arg(args, const char *);
const char *dc_version_s = va_arg(args, const char *);
char *dc_name = va_arg(args, char *);
bool mixed_version = va_arg(args, int);
if (dc) {
out->list_item(out, "Current DC",
"%s (version %s) - %spartition %s quorum",
dc_name, dc_version_s ? dc_version_s : "unknown",
mixed_version ? "MIXED-VERSION " : "",
crm_is_true(quorum) ? "with" : "WITHOUT");
} else {
out->list_item(out, "Current DC", "NONE");
}
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("cluster-dc", "pcmk_node_t *", "const char *", "const char *",
"char *", "int")
static int
cluster_dc_xml(pcmk__output_t *out, va_list args) {
pcmk_node_t *dc = va_arg(args, pcmk_node_t *);
const char *quorum = va_arg(args, const char *);
const char *dc_version_s = va_arg(args, const char *);
char *dc_name G_GNUC_UNUSED = va_arg(args, char *);
bool mixed_version = va_arg(args, int);
if (dc) {
pcmk__output_create_xml_node(out, "current_dc",
"present", "true",
PCMK_XA_VERSION, pcmk__s(dc_version_s, ""),
"name", dc->details->uname,
PCMK_XA_ID, dc->details->id,
"with_quorum", pcmk__btoa(crm_is_true(quorum)),
"mixed_version", pcmk__btoa(mixed_version),
NULL);
} else {
pcmk__output_create_xml_node(out, "current_dc",
"present", "false",
NULL);
}
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("maint-mode", "unsigned long long int")
static int
cluster_maint_mode_text(pcmk__output_t *out, va_list args) {
unsigned long long flags = va_arg(args, unsigned long long);
if (pcmk_is_set(flags, pcmk_sched_in_maintenance)) {
pcmk__formatted_printf(out, "\n *** Resource management is DISABLED ***\n");
pcmk__formatted_printf(out, " The cluster will not attempt to start, stop or recover services\n");
return pcmk_rc_ok;
} else if (pcmk_is_set(flags, pcmk_sched_stop_all)) {
pcmk__formatted_printf(out, "\n *** Resource management is DISABLED ***\n");
pcmk__formatted_printf(out, " The cluster will keep all resources stopped\n");
return pcmk_rc_ok;
} else {
return pcmk_rc_no_output;
}
}
PCMK__OUTPUT_ARGS("cluster-options", "pcmk_scheduler_t *")
static int
cluster_options_html(pcmk__output_t *out, va_list args) {
pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
if (pcmk_is_set(scheduler->flags, pcmk_sched_fencing_enabled)) {
out->list_item(out, NULL, "STONITH of failed nodes enabled");
} else {
out->list_item(out, NULL, "STONITH of failed nodes disabled");
}
if (pcmk_is_set(scheduler->flags, pcmk_sched_symmetric_cluster)) {
out->list_item(out, NULL, "Cluster is symmetric");
} else {
out->list_item(out, NULL, "Cluster is asymmetric");
}
switch (scheduler->no_quorum_policy) {
case pcmk_no_quorum_freeze:
out->list_item(out, NULL, "No quorum policy: Freeze resources");
break;
case pcmk_no_quorum_stop:
out->list_item(out, NULL, "No quorum policy: Stop ALL resources");
break;
case pcmk_no_quorum_demote:
out->list_item(out, NULL, "No quorum policy: Demote promotable "
"resources and stop all other resources");
break;
case pcmk_no_quorum_ignore:
out->list_item(out, NULL, "No quorum policy: Ignore");
break;
case pcmk_no_quorum_fence:
out->list_item(out, NULL, "No quorum policy: Suicide");
break;
}
if (pcmk_is_set(scheduler->flags, pcmk_sched_in_maintenance)) {
xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL);
pcmk_create_html_node(node, "span", NULL, NULL, "Resource management: ");
pcmk_create_html_node(node, "span", NULL, "bold", "DISABLED");
pcmk_create_html_node(node, "span", NULL, NULL,
" (the cluster will not attempt to start, stop, or recover services)");
} else if (pcmk_is_set(scheduler->flags, pcmk_sched_stop_all)) {
xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL);
pcmk_create_html_node(node, "span", NULL, NULL, "Resource management: ");
pcmk_create_html_node(node, "span", NULL, "bold", "STOPPED");
pcmk_create_html_node(node, "span", NULL, NULL,
" (the cluster will keep all resources stopped)");
} else {
out->list_item(out, NULL, "Resource management: enabled");
}
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("cluster-options", "pcmk_scheduler_t *")
static int
cluster_options_log(pcmk__output_t *out, va_list args) {
pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
if (pcmk_is_set(scheduler->flags, pcmk_sched_in_maintenance)) {
return out->info(out, "Resource management is DISABLED. The cluster will not attempt to start, stop or recover services.");
} else if (pcmk_is_set(scheduler->flags, pcmk_sched_stop_all)) {
return out->info(out, "Resource management is DISABLED. The cluster has stopped all resources.");
} else {
return pcmk_rc_no_output;
}
}
PCMK__OUTPUT_ARGS("cluster-options", "pcmk_scheduler_t *")
static int
cluster_options_text(pcmk__output_t *out, va_list args) {
pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
if (pcmk_is_set(scheduler->flags, pcmk_sched_fencing_enabled)) {
out->list_item(out, NULL, "STONITH of failed nodes enabled");
} else {
out->list_item(out, NULL, "STONITH of failed nodes disabled");
}
if (pcmk_is_set(scheduler->flags, pcmk_sched_symmetric_cluster)) {
out->list_item(out, NULL, "Cluster is symmetric");
} else {
out->list_item(out, NULL, "Cluster is asymmetric");
}
switch (scheduler->no_quorum_policy) {
case pcmk_no_quorum_freeze:
out->list_item(out, NULL, "No quorum policy: Freeze resources");
break;
case pcmk_no_quorum_stop:
out->list_item(out, NULL, "No quorum policy: Stop ALL resources");
break;
case pcmk_no_quorum_demote:
out->list_item(out, NULL, "No quorum policy: Demote promotable "
"resources and stop all other resources");
break;
case pcmk_no_quorum_ignore:
out->list_item(out, NULL, "No quorum policy: Ignore");
break;
case pcmk_no_quorum_fence:
out->list_item(out, NULL, "No quorum policy: Suicide");
break;
}
return pcmk_rc_ok;
}
#define bv(flag) pcmk__btoa(pcmk_is_set(scheduler->flags, (flag)))
PCMK__OUTPUT_ARGS("cluster-options", "pcmk_scheduler_t *")
static int
cluster_options_xml(pcmk__output_t *out, va_list args) {
pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
const char *no_quorum_policy = NULL;
char *stonith_timeout_str = pcmk__itoa(scheduler->stonith_timeout);
char *priority_fencing_delay_str = pcmk__itoa(scheduler->priority_fencing_delay * 1000);
switch (scheduler->no_quorum_policy) {
case pcmk_no_quorum_freeze:
no_quorum_policy = "freeze";
break;
case pcmk_no_quorum_stop:
no_quorum_policy = "stop";
break;
case pcmk_no_quorum_demote:
no_quorum_policy = "demote";
break;
case pcmk_no_quorum_ignore:
no_quorum_policy = "ignore";
break;
case pcmk_no_quorum_fence:
no_quorum_policy = "suicide";
break;
}
pcmk__output_create_xml_node(out, "cluster_options",
PCMK_OPT_STONITH_ENABLED,
bv(pcmk_sched_fencing_enabled),
PCMK_OPT_SYMMETRIC_CLUSTER,
bv(pcmk_sched_symmetric_cluster),
PCMK_OPT_NO_QUORUM_POLICY, no_quorum_policy,
PCMK_OPT_MAINTENANCE_MODE,
bv(pcmk_sched_in_maintenance),
PCMK_OPT_STOP_ALL_RESOURCES,
bv(pcmk_sched_stop_all),
PCMK_OPT_STONITH_TIMEOUT "-ms",
stonith_timeout_str,
PCMK_OPT_PRIORITY_FENCING_DELAY "-ms",
priority_fencing_delay_str,
NULL);
free(stonith_timeout_str);
free(priority_fencing_delay_str);
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("cluster-stack", "const char *", "enum pcmk_pacemakerd_state")
static int
cluster_stack_html(pcmk__output_t *out, va_list args) {
const char *stack_s = va_arg(args, const char *);
enum pcmk_pacemakerd_state pcmkd_state =
(enum pcmk_pacemakerd_state) va_arg(args, int);
xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL);
pcmk_create_html_node(node, "span", NULL, "bold", "Stack: ");
pcmk_create_html_node(node, "span", NULL, NULL, stack_s);
if (pcmkd_state != pcmk_pacemakerd_state_invalid) {
pcmk_create_html_node(node, "span", NULL, NULL, " (");
pcmk_create_html_node(node, "span", NULL, NULL,
pcmk__pcmkd_state_enum2friendly(pcmkd_state));
pcmk_create_html_node(node, "span", NULL, NULL, ")");
}
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("cluster-stack", "const char *", "enum pcmk_pacemakerd_state")
static int
cluster_stack_text(pcmk__output_t *out, va_list args) {
const char *stack_s = va_arg(args, const char *);
enum pcmk_pacemakerd_state pcmkd_state =
(enum pcmk_pacemakerd_state) va_arg(args, int);
if (pcmkd_state != pcmk_pacemakerd_state_invalid) {
out->list_item(out, "Stack", "%s (%s)",
stack_s, pcmk__pcmkd_state_enum2friendly(pcmkd_state));
} else {
out->list_item(out, "Stack", "%s", stack_s);
}
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("cluster-stack", "const char *", "enum pcmk_pacemakerd_state")
static int
cluster_stack_xml(pcmk__output_t *out, va_list args) {
const char *stack_s = va_arg(args, const char *);
enum pcmk_pacemakerd_state pcmkd_state =
(enum pcmk_pacemakerd_state) va_arg(args, int);
const char *state_s = NULL;
if (pcmkd_state != pcmk_pacemakerd_state_invalid) {
state_s = pcmk_pacemakerd_api_daemon_state_enum2text(pcmkd_state);
}
pcmk__output_create_xml_node(out, "stack",
"type", stack_s,
"pacemakerd-state", state_s,
NULL);
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("cluster-times", "const char *", "const char *",
"const char *", "const char *", "const char *")
static int
cluster_times_html(pcmk__output_t *out, va_list args) {
const char *our_nodename = va_arg(args, const char *);
const char *last_written = va_arg(args, const char *);
const char *user = va_arg(args, const char *);
const char *client = va_arg(args, const char *);
const char *origin = va_arg(args, const char *);
xmlNodePtr updated_node = pcmk__output_create_xml_node(out, "li", NULL);
xmlNodePtr changed_node = pcmk__output_create_xml_node(out, "li", NULL);
char *time_s = pcmk__epoch2str(NULL, 0);
pcmk_create_html_node(updated_node, "span", NULL, "bold", "Last updated: ");
pcmk_create_html_node(updated_node, "span", NULL, NULL, time_s);
if (our_nodename != NULL) {
pcmk_create_html_node(updated_node, "span", NULL, NULL, " on ");
pcmk_create_html_node(updated_node, "span", NULL, NULL, our_nodename);
}
free(time_s);
time_s = last_changed_string(last_written, user, client, origin);
pcmk_create_html_node(changed_node, "span", NULL, "bold", "Last change: ");
pcmk_create_html_node(changed_node, "span", NULL, NULL, time_s);
free(time_s);
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("cluster-times", "const char *", "const char *",
"const char *", "const char *", "const char *")
static int
cluster_times_xml(pcmk__output_t *out, va_list args) {
const char *our_nodename = va_arg(args, const char *);
const char *last_written = va_arg(args, const char *);
const char *user = va_arg(args, const char *);
const char *client = va_arg(args, const char *);
const char *origin = va_arg(args, const char *);
char *time_s = pcmk__epoch2str(NULL, 0);
pcmk__output_create_xml_node(out, "last_update",
"time", time_s,
"origin", our_nodename,
NULL);
pcmk__output_create_xml_node(out, "last_change",
"time", last_written ? last_written : "",
"user", user ? user : "",
"client", client ? client : "",
"origin", origin ? origin : "",
NULL);
free(time_s);
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("cluster-times", "const char *", "const char *",
"const char *", "const char *", "const char *")
static int
cluster_times_text(pcmk__output_t *out, va_list args) {
const char *our_nodename = va_arg(args, const char *);
const char *last_written = va_arg(args, const char *);
const char *user = va_arg(args, const char *);
const char *client = va_arg(args, const char *);
const char *origin = va_arg(args, const char *);
char *time_s = pcmk__epoch2str(NULL, 0);
out->list_item(out, "Last updated", "%s%s%s",
time_s, (our_nodename != NULL)? " on " : "",
pcmk__s(our_nodename, ""));
free(time_s);
time_s = last_changed_string(last_written, user, client, origin);
out->list_item(out, "Last change", " %s", time_s);
free(time_s);
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Display a failed action in less-technical natural language
*
* \param[in,out] out Output object to use for display
* \param[in] xml_op XML containing failed action
* \param[in] op_key Operation key of failed action
* \param[in] node_name Where failed action occurred
* \param[in] rc OCF exit code of failed action
* \param[in] status Execution status of failed action
* \param[in] exit_reason Exit reason given for failed action
* \param[in] exec_time String containing execution time in milliseconds
*/
static void
failed_action_friendly(pcmk__output_t *out, const xmlNode *xml_op,
const char *op_key, const char *node_name, int rc,
int status, const char *exit_reason,
const char *exec_time)
{
char *rsc_id = NULL;
char *task = NULL;
guint interval_ms = 0;
time_t last_change_epoch = 0;
GString *str = NULL;
if (pcmk__str_empty(op_key)
|| !parse_op_key(op_key, &rsc_id, &task, &interval_ms)) {
rsc_id = strdup("unknown resource");
task = strdup("unknown action");
interval_ms = 0;
}
CRM_ASSERT((rsc_id != NULL) && (task != NULL));
str = g_string_sized_new(256); // Should be sufficient for most messages
pcmk__g_strcat(str, rsc_id, " ", NULL);
if (interval_ms != 0) {
pcmk__g_strcat(str, pcmk__readable_interval(interval_ms), "-interval ",
NULL);
}
pcmk__g_strcat(str, pcmk__readable_action(task, interval_ms), " on ",
node_name, NULL);
if (status == PCMK_EXEC_DONE) {
pcmk__g_strcat(str, " returned '", services_ocf_exitcode_str(rc), "'",
NULL);
if (!pcmk__str_empty(exit_reason)) {
pcmk__g_strcat(str, " (", exit_reason, ")", NULL);
}
} else {
pcmk__g_strcat(str, " could not be executed (",
pcmk_exec_status_str(status), NULL);
if (!pcmk__str_empty(exit_reason)) {
pcmk__g_strcat(str, ": ", exit_reason, NULL);
}
g_string_append_c(str, ')');
}
if (crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE,
&last_change_epoch) == pcmk_ok) {
char *s = pcmk__epoch2str(&last_change_epoch, 0);
pcmk__g_strcat(str, " at ", s, NULL);
free(s);
}
if (!pcmk__str_empty(exec_time)) {
int exec_time_ms = 0;
if ((pcmk__scan_min_int(exec_time, &exec_time_ms, 0) == pcmk_rc_ok)
&& (exec_time_ms > 0)) {
pcmk__g_strcat(str, " after ",
pcmk__readable_interval(exec_time_ms), NULL);
}
}
out->list_item(out, NULL, "%s", str->str);
g_string_free(str, TRUE);
free(rsc_id);
free(task);
}
/*!
* \internal
* \brief Display a failed action with technical details
*
* \param[in,out] out Output object to use for display
* \param[in] xml_op XML containing failed action
* \param[in] op_key Operation key of failed action
* \param[in] node_name Where failed action occurred
* \param[in] rc OCF exit code of failed action
* \param[in] status Execution status of failed action
* \param[in] exit_reason Exit reason given for failed action
* \param[in] exec_time String containing execution time in milliseconds
*/
static void
failed_action_technical(pcmk__output_t *out, const xmlNode *xml_op,
const char *op_key, const char *node_name, int rc,
int status, const char *exit_reason,
const char *exec_time)
{
const char *call_id = crm_element_value(xml_op, XML_LRM_ATTR_CALLID);
const char *queue_time = crm_element_value(xml_op, XML_RSC_OP_T_QUEUE);
const char *exit_status = services_ocf_exitcode_str(rc);
const char *lrm_status = pcmk_exec_status_str(status);
time_t last_change_epoch = 0;
GString *str = NULL;
if (pcmk__str_empty(op_key)) {
op_key = "unknown operation";
}
if (pcmk__str_empty(exit_status)) {
exit_status = "unknown exit status";
}
if (pcmk__str_empty(call_id)) {
call_id = "unknown";
}
str = g_string_sized_new(256);
g_string_append_printf(str, "%s on %s '%s' (%d): call=%s, status='%s'",
op_key, node_name, exit_status, rc, call_id,
lrm_status);
if (!pcmk__str_empty(exit_reason)) {
pcmk__g_strcat(str, ", exitreason='", exit_reason, "'", NULL);
}
if (crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE,
&last_change_epoch) == pcmk_ok) {
char *last_change_str = pcmk__epoch2str(&last_change_epoch, 0);
pcmk__g_strcat(str,
", " XML_RSC_OP_LAST_CHANGE "="
"'", last_change_str, "'", NULL);
free(last_change_str);
}
if (!pcmk__str_empty(queue_time)) {
pcmk__g_strcat(str, ", queued=", queue_time, "ms", NULL);
}
if (!pcmk__str_empty(exec_time)) {
pcmk__g_strcat(str, ", exec=", exec_time, "ms", NULL);
}
out->list_item(out, NULL, "%s", str->str);
g_string_free(str, TRUE);
}
PCMK__OUTPUT_ARGS("failed-action", "xmlNodePtr", "uint32_t")
static int
failed_action_default(pcmk__output_t *out, va_list args)
{
xmlNodePtr xml_op = va_arg(args, xmlNodePtr);
uint32_t show_opts = va_arg(args, uint32_t);
const char *op_key = pe__xe_history_key(xml_op);
const char *node_name = crm_element_value(xml_op, XML_ATTR_UNAME);
const char *exit_reason = crm_element_value(xml_op,
XML_LRM_ATTR_EXIT_REASON);
const char *exec_time = crm_element_value(xml_op, XML_RSC_OP_T_EXEC);
int rc;
int status;
pcmk__scan_min_int(crm_element_value(xml_op, XML_LRM_ATTR_RC), &rc, 0);
pcmk__scan_min_int(crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS),
&status, 0);
if (pcmk__str_empty(node_name)) {
node_name = "unknown node";
}
if (pcmk_is_set(show_opts, pcmk_show_failed_detail)) {
failed_action_technical(out, xml_op, op_key, node_name, rc, status,
exit_reason, exec_time);
} else {
failed_action_friendly(out, xml_op, op_key, node_name, rc, status,
exit_reason, exec_time);
}
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("failed-action", "xmlNodePtr", "uint32_t")
static int
failed_action_xml(pcmk__output_t *out, va_list args) {
xmlNodePtr xml_op = va_arg(args, xmlNodePtr);
uint32_t show_opts G_GNUC_UNUSED = va_arg(args, uint32_t);
const char *op_key = pe__xe_history_key(xml_op);
const char *op_key_name = "op_key";
int rc;
int status;
const char *exit_reason = crm_element_value(xml_op, XML_LRM_ATTR_EXIT_REASON);
time_t epoch = 0;
char *rc_s = NULL;
char *reason_s = crm_xml_escape(exit_reason ? exit_reason : "none");
xmlNodePtr node = NULL;
pcmk__scan_min_int(crm_element_value(xml_op, XML_LRM_ATTR_RC), &rc, 0);
pcmk__scan_min_int(crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS),
&status, 0);
rc_s = pcmk__itoa(rc);
if (crm_element_value(xml_op, XML_LRM_ATTR_TASK_KEY) == NULL) {
op_key_name = PCMK_XA_ID;
}
node = pcmk__output_create_xml_node(out, "failure",
op_key_name, op_key,
"node", crm_element_value(xml_op, XML_ATTR_UNAME),
"exitstatus", services_ocf_exitcode_str(rc),
"exitreason", pcmk__s(reason_s, ""),
"exitcode", rc_s,
"call", crm_element_value(xml_op, XML_LRM_ATTR_CALLID),
"status", pcmk_exec_status_str(status),
NULL);
free(rc_s);
if ((crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE,
&epoch) == pcmk_ok) && (epoch > 0)) {
guint interval_ms = 0;
char *interval_ms_s = NULL;
char *rc_change = pcmk__epoch2str(&epoch,
crm_time_log_date
|crm_time_log_timeofday
|crm_time_log_with_timezone);
crm_element_value_ms(xml_op, XML_LRM_ATTR_INTERVAL_MS, &interval_ms);
interval_ms_s = crm_strdup_printf("%u", interval_ms);
pcmk__xe_set_props(node, XML_RSC_OP_LAST_CHANGE, rc_change,
"queued", crm_element_value(xml_op, XML_RSC_OP_T_QUEUE),
"exec", crm_element_value(xml_op, XML_RSC_OP_T_EXEC),
"interval", interval_ms_s,
"task", crm_element_value(xml_op, XML_LRM_ATTR_TASK),
NULL);
free(interval_ms_s);
free(rc_change);
}
free(reason_s);
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("failed-action-list", "pcmk_scheduler_t *", "GList *",
"GList *", "uint32_t", "bool")
static int
failed_action_list(pcmk__output_t *out, va_list args) {
pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
GList *only_node = va_arg(args, GList *);
GList *only_rsc = va_arg(args, GList *);
uint32_t show_opts = va_arg(args, uint32_t);
bool print_spacer = va_arg(args, int);
xmlNode *xml_op = NULL;
int rc = pcmk_rc_no_output;
if (xmlChildElementCount(scheduler->failed) == 0) {
return rc;
}
for (xml_op = pcmk__xml_first_child(scheduler->failed); xml_op != NULL;
xml_op = pcmk__xml_next(xml_op)) {
char *rsc = NULL;
if (!pcmk__str_in_list(crm_element_value(xml_op, XML_ATTR_UNAME), only_node,
pcmk__str_star_matches|pcmk__str_casei)) {
continue;
}
if (pcmk_xe_mask_probe_failure(xml_op)) {
continue;
}
if (!parse_op_key(pe__xe_history_key(xml_op), &rsc, NULL, NULL)) {
continue;
}
if (!pcmk__str_in_list(rsc, only_rsc, pcmk__str_star_matches)) {
free(rsc);
continue;
}
free(rsc);
PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Failed Resource Actions");
out->message(out, "failed-action", xml_op, show_opts);
}
PCMK__OUTPUT_LIST_FOOTER(out, rc);
return rc;
}
static void
status_node(pcmk_node_t *node, xmlNodePtr parent, uint32_t show_opts)
{
int health = pe__node_health(node);
// Cluster membership
if (node->details->online) {
pcmk_create_html_node(parent, "span", NULL, "online", " online");
} else {
pcmk_create_html_node(parent, "span", NULL, "offline", " OFFLINE");
}
// Standby mode
if (node->details->standby_onfail && (node->details->running_rsc != NULL)) {
pcmk_create_html_node(parent, "span", NULL, "standby",
" (in standby due to on-fail,"
" with active resources)");
} else if (node->details->standby_onfail) {
pcmk_create_html_node(parent, "span", NULL, "standby",
" (in standby due to on-fail)");
} else if (node->details->standby && (node->details->running_rsc != NULL)) {
pcmk_create_html_node(parent, "span", NULL, "standby",
" (in standby, with active resources)");
} else if (node->details->standby) {
pcmk_create_html_node(parent, "span", NULL, "standby", " (in standby)");
}
// Maintenance mode
if (node->details->maintenance) {
pcmk_create_html_node(parent, "span", NULL, "maint",
" (in maintenance mode)");
}
// Node health
if (health < 0) {
pcmk_create_html_node(parent, "span", NULL, "health_red",
" (health is RED)");
} else if (health == 0) {
pcmk_create_html_node(parent, "span", NULL, "health_yellow",
" (health is YELLOW)");
}
// Feature set
if (pcmk_is_set(show_opts, pcmk_show_feature_set)) {
const char *feature_set = get_node_feature_set(node);
if (feature_set != NULL) {
char *buf = crm_strdup_printf(", feature set %s", feature_set);
pcmk_create_html_node(parent, "span", NULL, NULL, buf);
free(buf);
}
}
}
PCMK__OUTPUT_ARGS("node", "pcmk_node_t *", "uint32_t", "bool",
"GList *", "GList *")
static int
node_html(pcmk__output_t *out, va_list args) {
pcmk_node_t *node = va_arg(args, pcmk_node_t *);
uint32_t show_opts = va_arg(args, uint32_t);
bool full = va_arg(args, int);
GList *only_node = va_arg(args, GList *);
GList *only_rsc = va_arg(args, GList *);
char *node_name = pe__node_display_name(node, pcmk_is_set(show_opts, pcmk_show_node_id));
if (full) {
xmlNodePtr item_node;
if (pcmk_all_flags_set(show_opts, pcmk_show_brief | pcmk_show_rscs_by_node)) {
GList *rscs = pe__filter_rsc_list(node->details->running_rsc, only_rsc);
out->begin_list(out, NULL, NULL, "%s:", node_name);
item_node = pcmk__output_xml_create_parent(out, "li", NULL);
pcmk_create_html_node(item_node, "span", NULL, NULL, "Status:");
status_node(node, item_node, show_opts);
if (rscs != NULL) {
uint32_t new_show_opts = (show_opts | pcmk_show_rsc_only) & ~pcmk_show_inactive_rscs;
out->begin_list(out, NULL, NULL, "Resources");
pe__rscs_brief_output(out, rscs, new_show_opts);
out->end_list(out);
}
pcmk__output_xml_pop_parent(out);
out->end_list(out);
} else if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
GList *lpc2 = NULL;
int rc = pcmk_rc_no_output;
out->begin_list(out, NULL, NULL, "%s:", node_name);
item_node = pcmk__output_xml_create_parent(out, "li", NULL);
pcmk_create_html_node(item_node, "span", NULL, NULL, "Status:");
status_node(node, item_node, show_opts);
for (lpc2 = node->details->running_rsc; lpc2 != NULL; lpc2 = lpc2->next) {
pcmk_resource_t *rsc = (pcmk_resource_t *) lpc2->data;
PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Resources");
show_opts |= pcmk_show_rsc_only;
out->message(out, crm_map_element_name(rsc->xml), show_opts,
rsc, only_node, only_rsc);
}
PCMK__OUTPUT_LIST_FOOTER(out, rc);
pcmk__output_xml_pop_parent(out);
out->end_list(out);
} else {
char *buf = crm_strdup_printf("%s:", node_name);
item_node = pcmk__output_create_xml_node(out, "li", NULL);
pcmk_create_html_node(item_node, "span", NULL, "bold", buf);
status_node(node, item_node, show_opts);
free(buf);
}
} else {
out->begin_list(out, NULL, NULL, "%s:", node_name);
}
free(node_name);
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Get a human-friendly textual description of a node's status
*
* \param[in] node Node to check
*
* \return String representation of node's status
*/
static const char *
node_text_status(const pcmk_node_t *node)
{
if (node->details->unclean) {
if (node->details->online) {
return "UNCLEAN (online)";
} else if (node->details->pending) {
return "UNCLEAN (pending)";
} else {
return "UNCLEAN (offline)";
}
} else if (node->details->pending) {
return "pending";
} else if (node->details->standby_onfail && node->details->online) {
return "standby (on-fail)";
} else if (node->details->standby) {
if (node->details->online) {
if (node->details->running_rsc) {
return "standby (with active resources)";
} else {
return "standby";
}
} else {
return "OFFLINE (standby)";
}
} else if (node->details->maintenance) {
if (node->details->online) {
return "maintenance";
} else {
return "OFFLINE (maintenance)";
}
} else if (node->details->online) {
return "online";
}
return "OFFLINE";
}
PCMK__OUTPUT_ARGS("node", "pcmk_node_t *", "uint32_t", "bool", "GList *",
"GList *")
static int
node_text(pcmk__output_t *out, va_list args) {
pcmk_node_t *node = va_arg(args, pcmk_node_t *);
uint32_t show_opts = va_arg(args, uint32_t);
bool full = va_arg(args, int);
GList *only_node = va_arg(args, GList *);
GList *only_rsc = va_arg(args, GList *);
if (full) {
char *node_name = pe__node_display_name(node, pcmk_is_set(show_opts, pcmk_show_node_id));
GString *str = g_string_sized_new(64);
int health = pe__node_health(node);
// Create a summary line with node type, name, and status
if (pe__is_guest_node(node)) {
g_string_append(str, "GuestNode");
} else if (pe__is_remote_node(node)) {
g_string_append(str, "RemoteNode");
} else {
g_string_append(str, "Node");
}
pcmk__g_strcat(str, " ", node_name, ": ", node_text_status(node), NULL);
if (health < 0) {
g_string_append(str, " (health is RED)");
} else if (health == 0) {
g_string_append(str, " (health is YELLOW)");
}
if (pcmk_is_set(show_opts, pcmk_show_feature_set)) {
const char *feature_set = get_node_feature_set(node);
if (feature_set != NULL) {
pcmk__g_strcat(str, ", feature set ", feature_set, NULL);
}
}
/* If we're grouping by node, print its resources */
if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
if (pcmk_is_set(show_opts, pcmk_show_brief)) {
GList *rscs = pe__filter_rsc_list(node->details->running_rsc, only_rsc);
if (rscs != NULL) {
uint32_t new_show_opts = (show_opts | pcmk_show_rsc_only) & ~pcmk_show_inactive_rscs;
out->begin_list(out, NULL, NULL, "%s", str->str);
out->begin_list(out, NULL, NULL, "Resources");
pe__rscs_brief_output(out, rscs, new_show_opts);
out->end_list(out);
out->end_list(out);
g_list_free(rscs);
}
} else {
GList *gIter2 = NULL;
out->begin_list(out, NULL, NULL, "%s", str->str);
out->begin_list(out, NULL, NULL, "Resources");
for (gIter2 = node->details->running_rsc; gIter2 != NULL; gIter2 = gIter2->next) {
pcmk_resource_t *rsc = (pcmk_resource_t *) gIter2->data;
show_opts |= pcmk_show_rsc_only;
out->message(out, crm_map_element_name(rsc->xml), show_opts,
rsc, only_node, only_rsc);
}
out->end_list(out);
out->end_list(out);
}
} else {
out->list_item(out, NULL, "%s", str->str);
}
g_string_free(str, TRUE);
free(node_name);
} else {
char *node_name = pe__node_display_name(node, pcmk_is_set(show_opts, pcmk_show_node_id));
out->begin_list(out, NULL, NULL, "Node: %s", node_name);
free(node_name);
}
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("node", "pcmk_node_t *", "uint32_t", "bool", "GList *",
"GList *")
static int
node_xml(pcmk__output_t *out, va_list args) {
pcmk_node_t *node = va_arg(args, pcmk_node_t *);
uint32_t show_opts G_GNUC_UNUSED = va_arg(args, uint32_t);
bool full = va_arg(args, int);
GList *only_node = va_arg(args, GList *);
GList *only_rsc = va_arg(args, GList *);
if (full) {
const char *node_type = "unknown";
char *length_s = pcmk__itoa(g_list_length(node->details->running_rsc));
int health = pe__node_health(node);
const char *health_s = NULL;
const char *feature_set;
switch (node->details->type) {
case pcmk_node_variant_cluster:
node_type = "member";
break;
case pcmk_node_variant_remote:
node_type = "remote";
break;
case node_ping:
node_type = "ping";
break;
}
if (health < 0) {
health_s = "red";
} else if (health == 0) {
health_s = "yellow";
} else {
health_s = "green";
}
feature_set = get_node_feature_set(node);
pe__name_and_nvpairs_xml(out, true, "node", 15,
"name", node->details->uname,
PCMK_XA_ID, node->details->id,
"online", pcmk__btoa(node->details->online),
"standby", pcmk__btoa(node->details->standby),
"standby_onfail", pcmk__btoa(node->details->standby_onfail),
"maintenance", pcmk__btoa(node->details->maintenance),
"pending", pcmk__btoa(node->details->pending),
"unclean", pcmk__btoa(node->details->unclean),
"health", health_s,
"feature_set", feature_set,
"shutdown", pcmk__btoa(node->details->shutdown),
"expected_up", pcmk__btoa(node->details->expected_up),
"is_dc", pcmk__btoa(node->details->is_dc),
"resources_running", length_s,
"type", node_type);
if (pe__is_guest_node(node)) {
xmlNodePtr xml_node = pcmk__output_xml_peek_parent(out);
crm_xml_add(xml_node, "id_as_resource", node->details->remote_rsc->container->id);
}
if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
GList *lpc = NULL;
for (lpc = node->details->running_rsc; lpc != NULL; lpc = lpc->next) {
pcmk_resource_t *rsc = (pcmk_resource_t *) lpc->data;
show_opts |= pcmk_show_rsc_only;
out->message(out, crm_map_element_name(rsc->xml), show_opts,
rsc, only_node, only_rsc);
}
}
free(length_s);
out->end_list(out);
} else {
pcmk__output_xml_create_parent(out, "node",
"name", node->details->uname,
NULL);
}
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("node-attribute", "const char *", "const char *", "bool", "int")
static int
node_attribute_text(pcmk__output_t *out, va_list args) {
const char *name = va_arg(args, const char *);
const char *value = va_arg(args, const char *);
bool add_extra = va_arg(args, int);
int expected_score = va_arg(args, int);
if (add_extra) {
int v;
if (value == NULL) {
v = 0;
} else {
pcmk__scan_min_int(value, &v, INT_MIN);
}
if (v <= 0) {
out->list_item(out, NULL, "%-32s\t: %-10s\t: Connectivity is lost", name, value);
} else if (v < expected_score) {
out->list_item(out, NULL, "%-32s\t: %-10s\t: Connectivity is degraded (Expected=%d)", name, value, expected_score);
} else {
out->list_item(out, NULL, "%-32s\t: %-10s", name, value);
}
} else {
out->list_item(out, NULL, "%-32s\t: %-10s", name, value);
}
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("node-attribute", "const char *", "const char *", "bool", "int")
static int
node_attribute_html(pcmk__output_t *out, va_list args) {
const char *name = va_arg(args, const char *);
const char *value = va_arg(args, const char *);
bool add_extra = va_arg(args, int);
int expected_score = va_arg(args, int);
if (add_extra) {
int v;
char *s = crm_strdup_printf("%s: %s", name, value);
xmlNodePtr item_node = pcmk__output_create_xml_node(out, "li", NULL);
if (value == NULL) {
v = 0;
} else {
pcmk__scan_min_int(value, &v, INT_MIN);
}
pcmk_create_html_node(item_node, "span", NULL, NULL, s);
free(s);
if (v <= 0) {
pcmk_create_html_node(item_node, "span", NULL, "bold", "(connectivity is lost)");
} else if (v < expected_score) {
char *buf = crm_strdup_printf("(connectivity is degraded -- expected %d", expected_score);
pcmk_create_html_node(item_node, "span", NULL, "bold", buf);
free(buf);
}
} else {
out->list_item(out, NULL, "%s: %s", name, value);
}
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("node-and-op", "pcmk_scheduler_t *", "xmlNodePtr")
static int
node_and_op(pcmk__output_t *out, va_list args) {
pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
xmlNodePtr xml_op = va_arg(args, xmlNodePtr);
pcmk_resource_t *rsc = NULL;
gchar *node_str = NULL;
char *last_change_str = NULL;
const char *op_rsc = crm_element_value(xml_op, "resource");
int status;
time_t last_change = 0;
pcmk__scan_min_int(crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS),
&status, PCMK_EXEC_UNKNOWN);
rsc = pe_find_resource(scheduler->resources, op_rsc);
if (rsc) {
const pcmk_node_t *node = pe__current_node(rsc);
const char *target_role = g_hash_table_lookup(rsc->meta,
PCMK_META_TARGET_ROLE);
uint32_t show_opts = pcmk_show_rsc_only | pcmk_show_pending;
if (node == NULL) {
node = rsc->pending_node;
}
node_str = pcmk__native_output_string(rsc, rsc_printable_id(rsc), node,
show_opts, target_role, false);
} else {
node_str = crm_strdup_printf("Unknown resource %s", op_rsc);
}
if (crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE,
&last_change) == pcmk_ok) {
last_change_str = crm_strdup_printf(", %s='%s', exec=%sms",
XML_RSC_OP_LAST_CHANGE,
pcmk__trim(ctime(&last_change)),
crm_element_value(xml_op, XML_RSC_OP_T_EXEC));
}
out->list_item(out, NULL, "%s: %s (node=%s, call=%s, rc=%s%s): %s",
node_str, pe__xe_history_key(xml_op),
crm_element_value(xml_op, XML_ATTR_UNAME),
crm_element_value(xml_op, XML_LRM_ATTR_CALLID),
crm_element_value(xml_op, XML_LRM_ATTR_RC),
last_change_str ? last_change_str : "",
pcmk_exec_status_str(status));
g_free(node_str);
free(last_change_str);
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("node-and-op", "pcmk_scheduler_t *", "xmlNodePtr")
static int
node_and_op_xml(pcmk__output_t *out, va_list args) {
pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
xmlNodePtr xml_op = va_arg(args, xmlNodePtr);
pcmk_resource_t *rsc = NULL;
const char *op_rsc = crm_element_value(xml_op, "resource");
int status;
time_t last_change = 0;
xmlNode *node = NULL;
pcmk__scan_min_int(crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS),
&status, PCMK_EXEC_UNKNOWN);
node = pcmk__output_create_xml_node(out, "operation",
"op", pe__xe_history_key(xml_op),
"node", crm_element_value(xml_op, XML_ATTR_UNAME),
"call", crm_element_value(xml_op, XML_LRM_ATTR_CALLID),
"rc", crm_element_value(xml_op, XML_LRM_ATTR_RC),
"status", pcmk_exec_status_str(status),
NULL);
rsc = pe_find_resource(scheduler->resources, op_rsc);
if (rsc) {
const char *class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS);
const char *kind = crm_element_value(rsc->xml, XML_ATTR_TYPE);
char *agent_tuple = NULL;
agent_tuple = crm_strdup_printf("%s:%s:%s", class,
pcmk_is_set(pcmk_get_ra_caps(class), pcmk_ra_cap_provider) ? crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER) : "",
kind);
pcmk__xe_set_props(node, "rsc", rsc_printable_id(rsc),
"agent", agent_tuple,
NULL);
free(agent_tuple);
}
if (crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE,
&last_change) == pcmk_ok) {
pcmk__xe_set_props(node, XML_RSC_OP_LAST_CHANGE,
pcmk__trim(ctime(&last_change)),
XML_RSC_OP_T_EXEC, crm_element_value(xml_op, XML_RSC_OP_T_EXEC),
NULL);
}
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("node-attribute", "const char *", "const char *", "bool", "int")
static int
node_attribute_xml(pcmk__output_t *out, va_list args) {
const char *name = va_arg(args, const char *);
const char *value = va_arg(args, const char *);
bool add_extra = va_arg(args, int);
int expected_score = va_arg(args, int);
xmlNodePtr node = pcmk__output_create_xml_node(out, "attribute",
"name", name,
"value", value,
NULL);
if (add_extra) {
char *buf = pcmk__itoa(expected_score);
crm_xml_add(node, "expected", buf);
free(buf);
}
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("node-attribute-list", "pcmk_scheduler_t *", "uint32_t",
"bool", "GList *", "GList *")
static int
node_attribute_list(pcmk__output_t *out, va_list args) {
pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
uint32_t show_opts = va_arg(args, uint32_t);
bool print_spacer = va_arg(args, int);
GList *only_node = va_arg(args, GList *);
GList *only_rsc = va_arg(args, GList *);
int rc = pcmk_rc_no_output;
/* Display each node's attributes */
for (GList *gIter = scheduler->nodes; gIter != NULL; gIter = gIter->next) {
pcmk_node_t *node = gIter->data;
GList *attr_list = NULL;
GHashTableIter iter;
gpointer key;
if (!node || !node->details || !node->details->online) {
continue;
}
g_hash_table_iter_init(&iter, node->details->attrs);
while (g_hash_table_iter_next (&iter, &key, NULL)) {
attr_list = filter_attr_list(attr_list, key);
}
if (attr_list == NULL) {
continue;
}
if (!pcmk__str_in_list(node->details->uname, only_node, pcmk__str_star_matches|pcmk__str_casei)) {
g_list_free(attr_list);
continue;
}
PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Node Attributes");
out->message(out, "node", node, show_opts, false, only_node, only_rsc);
for (GList *aIter = attr_list; aIter != NULL; aIter = aIter->next) {
const char *name = aIter->data;
const char *value = NULL;
int expected_score = 0;
bool add_extra = false;
value = pe_node_attribute_raw(node, name);
add_extra = add_extra_info(node, node->details->running_rsc,
scheduler, name, &expected_score);
/* Print attribute name and value */
out->message(out, "node-attribute", name, value, add_extra,
expected_score);
}
g_list_free(attr_list);
out->end_list(out);
}
PCMK__OUTPUT_LIST_FOOTER(out, rc);
return rc;
}
PCMK__OUTPUT_ARGS("node-capacity", "const pcmk_node_t *", "const char *")
static int
node_capacity(pcmk__output_t *out, va_list args)
{
const pcmk_node_t *node = va_arg(args, pcmk_node_t *);
const char *comment = va_arg(args, const char *);
char *dump_text = crm_strdup_printf("%s: %s capacity:",
comment, pe__node_name(node));
g_hash_table_foreach(node->details->utilization, append_dump_text, &dump_text);
out->list_item(out, NULL, "%s", dump_text);
free(dump_text);
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("node-capacity", "const pcmk_node_t *", "const char *")
static int
node_capacity_xml(pcmk__output_t *out, va_list args)
{
const pcmk_node_t *node = va_arg(args, pcmk_node_t *);
const char *comment = va_arg(args, const char *);
xmlNodePtr xml_node = pcmk__output_create_xml_node(out, "capacity",
"node", node->details->uname,
"comment", comment,
NULL);
g_hash_table_foreach(node->details->utilization, add_dump_node, xml_node);
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("node-history-list", "pcmk_scheduler_t *", "pcmk_node_t *",
"xmlNodePtr", "GList *", "GList *", "uint32_t", "uint32_t")
static int
node_history_list(pcmk__output_t *out, va_list args) {
pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
pcmk_node_t *node = va_arg(args, pcmk_node_t *);
xmlNode *node_state = va_arg(args, xmlNode *);
GList *only_node = va_arg(args, GList *);
GList *only_rsc = va_arg(args, GList *);
uint32_t section_opts = va_arg(args, uint32_t);
uint32_t show_opts = va_arg(args, uint32_t);
xmlNode *lrm_rsc = NULL;
xmlNode *rsc_entry = NULL;
int rc = pcmk_rc_no_output;
lrm_rsc = find_xml_node(node_state, XML_CIB_TAG_LRM, FALSE);
lrm_rsc = find_xml_node(lrm_rsc, XML_LRM_TAG_RESOURCES, FALSE);
/* Print history of each of the node's resources */
for (rsc_entry = first_named_child(lrm_rsc, XML_LRM_TAG_RESOURCE);
rsc_entry != NULL; rsc_entry = crm_next_same_xml(rsc_entry)) {
const char *rsc_id = crm_element_value(rsc_entry, PCMK_XA_ID);
pcmk_resource_t *rsc = pe_find_resource(scheduler->resources, rsc_id);
const pcmk_resource_t *parent = pe__const_top_resource(rsc, false);
/* We can't use is_filtered here to filter group resources. For is_filtered,
* we have to decide whether to check the parent or not. If we check the
* parent, all elements of a group will always be printed because that's how
* is_filtered works for groups. If we do not check the parent, sometimes
* this will filter everything out.
*
* For other resource types, is_filtered is okay.
*/
if (parent->variant == pcmk_rsc_variant_group) {
if (!pcmk__str_in_list(rsc_printable_id(rsc), only_rsc,
pcmk__str_star_matches)
&& !pcmk__str_in_list(rsc_printable_id(parent), only_rsc,
pcmk__str_star_matches)) {
continue;
}
} else {
if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) {
continue;
}
}
if (!pcmk_is_set(section_opts, pcmk_section_operations)) {
time_t last_failure = 0;
int failcount = pe_get_failcount(node, rsc, &last_failure,
pcmk__fc_default, NULL);
if (failcount <= 0) {
continue;
}
if (rc == pcmk_rc_no_output) {
rc = pcmk_rc_ok;
out->message(out, "node", node, show_opts, false, only_node,
only_rsc);
}
out->message(out, "resource-history", rsc, rsc_id, false,
failcount, last_failure, false);
} else {
GList *op_list = get_operation_list(rsc_entry);
pcmk_resource_t *rsc = NULL;
if (op_list == NULL) {
continue;
}
rsc = pe_find_resource(scheduler->resources,
crm_element_value(rsc_entry, PCMK_XA_ID));
if (rc == pcmk_rc_no_output) {
rc = pcmk_rc_ok;
out->message(out, "node", node, show_opts, false, only_node,
only_rsc);
}
out->message(out, "resource-operation-list", scheduler, rsc, node,
op_list, show_opts);
}
}
PCMK__OUTPUT_LIST_FOOTER(out, rc);
return rc;
}
PCMK__OUTPUT_ARGS("node-list", "GList *", "GList *", "GList *", "uint32_t", "bool")
static int
node_list_html(pcmk__output_t *out, va_list args) {
GList *nodes = va_arg(args, GList *);
GList *only_node = va_arg(args, GList *);
GList *only_rsc = va_arg(args, GList *);
uint32_t show_opts = va_arg(args, uint32_t);
bool print_spacer G_GNUC_UNUSED = va_arg(args, int);
int rc = pcmk_rc_no_output;
for (GList *gIter = nodes; gIter != NULL; gIter = gIter->next) {
pcmk_node_t *node = (pcmk_node_t *) gIter->data;
if (!pcmk__str_in_list(node->details->uname, only_node,
pcmk__str_star_matches|pcmk__str_casei)) {
continue;
}
PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Node List");
out->message(out, "node", node, show_opts, true, only_node, only_rsc);
}
PCMK__OUTPUT_LIST_FOOTER(out, rc);
return rc;
}
PCMK__OUTPUT_ARGS("node-list", "GList *", "GList *", "GList *", "uint32_t", "bool")
static int
node_list_text(pcmk__output_t *out, va_list args) {
GList *nodes = va_arg(args, GList *);
GList *only_node = va_arg(args, GList *);
GList *only_rsc = va_arg(args, GList *);
uint32_t show_opts = va_arg(args, uint32_t);
bool print_spacer = va_arg(args, int);
/* space-separated lists of node names */
GString *online_nodes = NULL;
GString *online_remote_nodes = NULL;
GString *online_guest_nodes = NULL;
GString *offline_nodes = NULL;
GString *offline_remote_nodes = NULL;
int rc = pcmk_rc_no_output;
for (GList *gIter = nodes; gIter != NULL; gIter = gIter->next) {
pcmk_node_t *node = (pcmk_node_t *) gIter->data;
char *node_name = pe__node_display_name(node, pcmk_is_set(show_opts, pcmk_show_node_id));
if (!pcmk__str_in_list(node->details->uname, only_node,
pcmk__str_star_matches|pcmk__str_casei)) {
free(node_name);
continue;
}
PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Node List");
// Determine whether to display node individually or in a list
if (node->details->unclean || node->details->pending
|| (node->details->standby_onfail && node->details->online)
|| node->details->standby || node->details->maintenance
|| pcmk_is_set(show_opts, pcmk_show_rscs_by_node)
|| pcmk_is_set(show_opts, pcmk_show_feature_set)
|| (pe__node_health(node) <= 0)) {
// Display node individually
} else if (node->details->online) {
// Display online node in a list
if (pe__is_guest_node(node)) {
pcmk__add_word(&online_guest_nodes, 1024, node_name);
} else if (pe__is_remote_node(node)) {
pcmk__add_word(&online_remote_nodes, 1024, node_name);
} else {
pcmk__add_word(&online_nodes, 1024, node_name);
}
free(node_name);
continue;
} else {
// Display offline node in a list
if (pe__is_remote_node(node)) {
pcmk__add_word(&offline_remote_nodes, 1024, node_name);
} else if (pe__is_guest_node(node)) {
/* ignore offline guest nodes */
} else {
pcmk__add_word(&offline_nodes, 1024, node_name);
}
free(node_name);
continue;
}
/* If we get here, node is in bad state, or we're grouping by node */
out->message(out, "node", node, show_opts, true, only_node, only_rsc);
free(node_name);
}
/* If we're not grouping by node, summarize nodes by status */
if (online_nodes != NULL) {
out->list_item(out, "Online", "[ %s ]",
(const char *) online_nodes->str);
g_string_free(online_nodes, TRUE);
}
if (offline_nodes != NULL) {
out->list_item(out, "OFFLINE", "[ %s ]",
(const char *) offline_nodes->str);
g_string_free(offline_nodes, TRUE);
}
if (online_remote_nodes) {
out->list_item(out, "RemoteOnline", "[ %s ]",
(const char *) online_remote_nodes->str);
g_string_free(online_remote_nodes, TRUE);
}
if (offline_remote_nodes) {
out->list_item(out, "RemoteOFFLINE", "[ %s ]",
(const char *) offline_remote_nodes->str);
g_string_free(offline_remote_nodes, TRUE);
}
if (online_guest_nodes != NULL) {
out->list_item(out, "GuestOnline", "[ %s ]",
(const char *) online_guest_nodes->str);
g_string_free(online_guest_nodes, TRUE);
}
PCMK__OUTPUT_LIST_FOOTER(out, rc);
return rc;
}
PCMK__OUTPUT_ARGS("node-list", "GList *", "GList *", "GList *", "uint32_t", "bool")
static int
node_list_xml(pcmk__output_t *out, va_list args) {
GList *nodes = va_arg(args, GList *);
GList *only_node = va_arg(args, GList *);
GList *only_rsc = va_arg(args, GList *);
uint32_t show_opts = va_arg(args, uint32_t);
bool print_spacer G_GNUC_UNUSED = va_arg(args, int);
out->begin_list(out, NULL, NULL, "nodes");
for (GList *gIter = nodes; gIter != NULL; gIter = gIter->next) {
pcmk_node_t *node = (pcmk_node_t *) gIter->data;
if (!pcmk__str_in_list(node->details->uname, only_node,
pcmk__str_star_matches|pcmk__str_casei)) {
continue;
}
out->message(out, "node", node, show_opts, true, only_node, only_rsc);
}
out->end_list(out);
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("node-summary", "pcmk_scheduler_t *", "GList *", "GList *",
"uint32_t", "uint32_t", "bool")
static int
node_summary(pcmk__output_t *out, va_list args) {
pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
GList *only_node = va_arg(args, GList *);
GList *only_rsc = va_arg(args, GList *);
uint32_t section_opts = va_arg(args, uint32_t);
uint32_t show_opts = va_arg(args, uint32_t);
bool print_spacer = va_arg(args, int);
xmlNode *node_state = NULL;
xmlNode *cib_status = pcmk_find_cib_element(scheduler->input,
XML_CIB_TAG_STATUS);
int rc = pcmk_rc_no_output;
if (xmlChildElementCount(cib_status) == 0) {
return rc;
}
for (node_state = first_named_child(cib_status, XML_CIB_TAG_STATE);
node_state != NULL; node_state = crm_next_same_xml(node_state)) {
pcmk_node_t *node = pe_find_node_id(scheduler->nodes, ID(node_state));
if (!node || !node->details || !node->details->online) {
continue;
}
if (!pcmk__str_in_list(node->details->uname, only_node,
pcmk__str_star_matches|pcmk__str_casei)) {
continue;
}
PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc,
pcmk_is_set(section_opts, pcmk_section_operations) ? "Operations" : "Migration Summary");
out->message(out, "node-history-list", scheduler, node, node_state,
only_node, only_rsc, section_opts, show_opts);
}
PCMK__OUTPUT_LIST_FOOTER(out, rc);
return rc;
}
PCMK__OUTPUT_ARGS("node-weight", "const pcmk_resource_t *", "const char *",
"const char *", "const char *")
static int
node_weight(pcmk__output_t *out, va_list args)
{
const pcmk_resource_t *rsc = va_arg(args, const pcmk_resource_t *);
const char *prefix = va_arg(args, const char *);
const char *uname = va_arg(args, const char *);
const char *score = va_arg(args, const char *);
if (rsc) {
out->list_item(out, NULL, "%s: %s allocation score on %s: %s",
prefix, rsc->id, uname, score);
} else {
out->list_item(out, NULL, "%s: %s = %s", prefix, uname, score);
}
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("node-weight", "const pcmk_resource_t *", "const char *",
"const char *", "const char *")
static int
node_weight_xml(pcmk__output_t *out, va_list args)
{
const pcmk_resource_t *rsc = va_arg(args, const pcmk_resource_t *);
const char *prefix = va_arg(args, const char *);
const char *uname = va_arg(args, const char *);
const char *score = va_arg(args, const char *);
xmlNodePtr node = pcmk__output_create_xml_node(out, "node_weight",
"function", prefix,
"node", uname,
"score", score,
NULL);
if (rsc) {
crm_xml_add(node, PCMK_XA_ID, rsc->id);
}
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("op-history", "xmlNodePtr", "const char *", "const char *", "int", "uint32_t")
static int
op_history_text(pcmk__output_t *out, va_list args) {
xmlNodePtr xml_op = va_arg(args, xmlNodePtr);
const char *task = va_arg(args, const char *);
const char *interval_ms_s = va_arg(args, const char *);
int rc = va_arg(args, int);
uint32_t show_opts = va_arg(args, uint32_t);
char *buf = op_history_string(xml_op, task, interval_ms_s, rc,
pcmk_is_set(show_opts, pcmk_show_timing));
out->list_item(out, NULL, "%s", buf);
free(buf);
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("op-history", "xmlNodePtr", "const char *", "const char *", "int", "uint32_t")
static int
op_history_xml(pcmk__output_t *out, va_list args) {
xmlNodePtr xml_op = va_arg(args, xmlNodePtr);
const char *task = va_arg(args, const char *);
const char *interval_ms_s = va_arg(args, const char *);
int rc = va_arg(args, int);
uint32_t show_opts = va_arg(args, uint32_t);
char *rc_s = pcmk__itoa(rc);
xmlNodePtr node = pcmk__output_create_xml_node(out, "operation_history",
"call", crm_element_value(xml_op, XML_LRM_ATTR_CALLID),
"task", task,
"rc", rc_s,
"rc_text", services_ocf_exitcode_str(rc),
NULL);
free(rc_s);
if (interval_ms_s && !pcmk__str_eq(interval_ms_s, "0", pcmk__str_casei)) {
char *s = crm_strdup_printf("%sms", interval_ms_s);
crm_xml_add(node, "interval", s);
free(s);
}
if (pcmk_is_set(show_opts, pcmk_show_timing)) {
const char *value = NULL;
time_t epoch = 0;
if ((crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE,
&epoch) == pcmk_ok) && (epoch > 0)) {
char *s = pcmk__epoch2str(&epoch, 0);
crm_xml_add(node, XML_RSC_OP_LAST_CHANGE, s);
free(s);
}
value = crm_element_value(xml_op, XML_RSC_OP_T_EXEC);
if (value) {
char *s = crm_strdup_printf("%sms", value);
crm_xml_add(node, XML_RSC_OP_T_EXEC, s);
free(s);
}
value = crm_element_value(xml_op, XML_RSC_OP_T_QUEUE);
if (value) {
char *s = crm_strdup_printf("%sms", value);
crm_xml_add(node, XML_RSC_OP_T_QUEUE, s);
free(s);
}
}
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("promotion-score", "pcmk_resource_t *", "pcmk_node_t *",
"const char *")
static int
promotion_score(pcmk__output_t *out, va_list args)
{
pcmk_resource_t *child_rsc = va_arg(args, pcmk_resource_t *);
pcmk_node_t *chosen = va_arg(args, pcmk_node_t *);
const char *score = va_arg(args, const char *);
out->list_item(out, NULL, "%s promotion score on %s: %s",
child_rsc->id,
chosen? chosen->details->uname : "none",
score);
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("promotion-score", "pcmk_resource_t *", "pcmk_node_t *",
"const char *")
static int
promotion_score_xml(pcmk__output_t *out, va_list args)
{
pcmk_resource_t *child_rsc = va_arg(args, pcmk_resource_t *);
pcmk_node_t *chosen = va_arg(args, pcmk_node_t *);
const char *score = va_arg(args, const char *);
xmlNodePtr node = pcmk__output_create_xml_node(out, "promotion_score",
PCMK_XA_ID, child_rsc->id,
"score", score,
NULL);
if (chosen) {
crm_xml_add(node, "node", chosen->details->uname);
}
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("resource-config", "const pcmk_resource_t *", "bool")
static int
resource_config(pcmk__output_t *out, va_list args) {
const pcmk_resource_t *rsc = va_arg(args, const pcmk_resource_t *);
bool raw = va_arg(args, int);
char *rsc_xml = formatted_xml_buf(rsc, raw);
out->output_xml(out, "xml", rsc_xml);
free(rsc_xml);
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("resource-config", "const pcmk_resource_t *", "bool")
static int
resource_config_text(pcmk__output_t *out, va_list args) {
const pcmk_resource_t *rsc = va_arg(args, const pcmk_resource_t *);
bool raw = va_arg(args, int);
char *rsc_xml = formatted_xml_buf(rsc, raw);
pcmk__formatted_printf(out, "Resource XML:\n");
out->output_xml(out, "xml", rsc_xml);
free(rsc_xml);
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("resource-history", "pcmk_resource_t *", "const char *",
"bool", "int", "time_t", "bool")
static int
resource_history_text(pcmk__output_t *out, va_list args) {
pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
const char *rsc_id = va_arg(args, const char *);
bool all = va_arg(args, int);
int failcount = va_arg(args, int);
time_t last_failure = va_arg(args, time_t);
bool as_header = va_arg(args, int);
char *buf = resource_history_string(rsc, rsc_id, all, failcount, last_failure);
if (as_header) {
out->begin_list(out, NULL, NULL, "%s", buf);
} else {
out->list_item(out, NULL, "%s", buf);
}
free(buf);
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("resource-history", "pcmk_resource_t *", "const char *",
"bool", "int", "time_t", "bool")
static int
resource_history_xml(pcmk__output_t *out, va_list args) {
pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
const char *rsc_id = va_arg(args, const char *);
bool all = va_arg(args, int);
int failcount = va_arg(args, int);
time_t last_failure = va_arg(args, time_t);
bool as_header = va_arg(args, int);
xmlNodePtr node = pcmk__output_xml_create_parent(out, "resource_history",
PCMK_XA_ID, rsc_id,
NULL);
if (rsc == NULL) {
pcmk__xe_set_bool_attr(node, "orphan", true);
} else if (all || failcount || last_failure > 0) {
char *migration_s = pcmk__itoa(rsc->migration_threshold);
pcmk__xe_set_props(node, "orphan", "false",
"migration-threshold", migration_s,
NULL);
free(migration_s);
if (failcount > 0) {
char *s = pcmk__itoa(failcount);
crm_xml_add(node, PCMK__FAIL_COUNT_PREFIX, s);
free(s);
}
if (last_failure > 0) {
char *s = pcmk__epoch2str(&last_failure, 0);
crm_xml_add(node, PCMK__LAST_FAILURE_PREFIX, s);
free(s);
}
}
if (!as_header) {
pcmk__output_xml_pop_parent(out);
}
return pcmk_rc_ok;
}
static void
print_resource_header(pcmk__output_t *out, uint32_t show_opts)
{
if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
/* Active resources have already been printed by node */
out->begin_list(out, NULL, NULL, "Inactive Resources");
} else if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
out->begin_list(out, NULL, NULL, "Full List of Resources");
} else {
out->begin_list(out, NULL, NULL, "Active Resources");
}
}
PCMK__OUTPUT_ARGS("resource-list", "pcmk_scheduler_t *", "uint32_t", "bool",
"GList *", "GList *", "bool")
static int
resource_list(pcmk__output_t *out, va_list args)
{
pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
uint32_t show_opts = va_arg(args, uint32_t);
bool print_summary = va_arg(args, int);
GList *only_node = va_arg(args, GList *);
GList *only_rsc = va_arg(args, GList *);
bool print_spacer = va_arg(args, int);
GList *rsc_iter;
int rc = pcmk_rc_no_output;
bool printed_header = false;
/* If we already showed active resources by node, and
* we're not showing inactive resources, we have nothing to do
*/
if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node) &&
!pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
return rc;
}
/* If we haven't already printed resources grouped by node,
* and brief output was requested, print resource summary */
if (pcmk_is_set(show_opts, pcmk_show_brief)
&& !pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
GList *rscs = pe__filter_rsc_list(scheduler->resources, only_rsc);
PCMK__OUTPUT_SPACER_IF(out, print_spacer);
print_resource_header(out, show_opts);
printed_header = true;
rc = pe__rscs_brief_output(out, rscs, show_opts);
g_list_free(rscs);
}
/* For each resource, display it if appropriate */
for (rsc_iter = scheduler->resources; rsc_iter != NULL; rsc_iter = rsc_iter->next) {
pcmk_resource_t *rsc = (pcmk_resource_t *) rsc_iter->data;
int x;
/* Complex resources may have some sub-resources active and some inactive */
gboolean is_active = rsc->fns->active(rsc, TRUE);
gboolean partially_active = rsc->fns->active(rsc, FALSE);
/* Skip inactive orphans (deleted but still in CIB) */
if (pcmk_is_set(rsc->flags, pcmk_rsc_removed) && !is_active) {
continue;
/* Skip active resources if we already displayed them by node */
} else if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
if (is_active) {
continue;
}
/* Skip primitives already counted in a brief summary */
} else if (pcmk_is_set(show_opts, pcmk_show_brief)
&& (rsc->variant == pcmk_rsc_variant_primitive)) {
continue;
/* Skip resources that aren't at least partially active,
* unless we're displaying inactive resources
*/
} else if (!partially_active && !pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
continue;
} else if (partially_active && !pe__rsc_running_on_any(rsc, only_node)) {
continue;
}
if (!printed_header) {
PCMK__OUTPUT_SPACER_IF(out, print_spacer);
print_resource_header(out, show_opts);
printed_header = true;
}
/* Print this resource */
x = out->message(out, crm_map_element_name(rsc->xml), show_opts, rsc,
only_node, only_rsc);
if (x == pcmk_rc_ok) {
rc = pcmk_rc_ok;
}
}
if (print_summary && rc != pcmk_rc_ok) {
if (!printed_header) {
PCMK__OUTPUT_SPACER_IF(out, print_spacer);
print_resource_header(out, show_opts);
printed_header = true;
}
if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
out->list_item(out, NULL, "No inactive resources");
} else if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
out->list_item(out, NULL, "No resources");
} else {
out->list_item(out, NULL, "No active resources");
}
}
if (printed_header) {
out->end_list(out);
}
return rc;
}
PCMK__OUTPUT_ARGS("resource-operation-list", "pcmk_scheduler_t *",
"pcmk_resource_t *", "pcmk_node_t *", "GList *", "uint32_t")
static int
resource_operation_list(pcmk__output_t *out, va_list args)
{
pcmk_scheduler_t *scheduler G_GNUC_UNUSED = va_arg(args,
pcmk_scheduler_t *);
pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
pcmk_node_t *node = va_arg(args, pcmk_node_t *);
GList *op_list = va_arg(args, GList *);
uint32_t show_opts = va_arg(args, uint32_t);
GList *gIter = NULL;
int rc = pcmk_rc_no_output;
/* Print each operation */
for (gIter = op_list; gIter != NULL; gIter = gIter->next) {
xmlNode *xml_op = (xmlNode *) gIter->data;
const char *task = crm_element_value(xml_op, XML_LRM_ATTR_TASK);
const char *interval_ms_s = crm_element_value(xml_op,
XML_LRM_ATTR_INTERVAL_MS);
const char *op_rc = crm_element_value(xml_op, XML_LRM_ATTR_RC);
int op_rc_i;
pcmk__scan_min_int(op_rc, &op_rc_i, 0);
/* Display 0-interval monitors as "probe" */
if (pcmk__str_eq(task, PCMK_ACTION_MONITOR, pcmk__str_casei)
&& pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches | pcmk__str_casei)) {
task = "probe";
}
/* If this is the first printed operation, print heading for resource */
if (rc == pcmk_rc_no_output) {
time_t last_failure = 0;
int failcount = pe_get_failcount(node, rsc, &last_failure,
pcmk__fc_default, NULL);
out->message(out, "resource-history", rsc, rsc_printable_id(rsc), true,
failcount, last_failure, true);
rc = pcmk_rc_ok;
}
/* Print the operation */
out->message(out, "op-history", xml_op, task, interval_ms_s,
op_rc_i, show_opts);
}
/* Free the list we created (no need to free the individual items) */
g_list_free(op_list);
PCMK__OUTPUT_LIST_FOOTER(out, rc);
return rc;
}
PCMK__OUTPUT_ARGS("resource-util", "pcmk_resource_t *", "pcmk_node_t *",
"const char *")
static int
resource_util(pcmk__output_t *out, va_list args)
{
pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
pcmk_node_t *node = va_arg(args, pcmk_node_t *);
const char *fn = va_arg(args, const char *);
char *dump_text = crm_strdup_printf("%s: %s utilization on %s:",
fn, rsc->id, pe__node_name(node));
g_hash_table_foreach(rsc->utilization, append_dump_text, &dump_text);
out->list_item(out, NULL, "%s", dump_text);
free(dump_text);
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("resource-util", "pcmk_resource_t *", "pcmk_node_t *",
"const char *")
static int
resource_util_xml(pcmk__output_t *out, va_list args)
{
pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
pcmk_node_t *node = va_arg(args, pcmk_node_t *);
const char *fn = va_arg(args, const char *);
xmlNodePtr xml_node = pcmk__output_create_xml_node(out, "utilization",
"resource", rsc->id,
"node", node->details->uname,
"function", fn,
NULL);
g_hash_table_foreach(rsc->utilization, add_dump_node, xml_node);
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("ticket", "pcmk_ticket_t *")
static int
ticket_html(pcmk__output_t *out, va_list args) {
pcmk_ticket_t *ticket = va_arg(args, pcmk_ticket_t *);
if (ticket->last_granted > -1) {
char *epoch_str = pcmk__epoch2str(&(ticket->last_granted), 0);
out->list_item(out, NULL, "%s:\t%s%s %s=\"%s\"", ticket->id,
ticket->granted ? "granted" : "revoked",
ticket->standby ? " [standby]" : "",
"last-granted", pcmk__s(epoch_str, ""));
free(epoch_str);
} else {
out->list_item(out, NULL, "%s:\t%s%s", ticket->id,
ticket->granted ? "granted" : "revoked",
ticket->standby ? " [standby]" : "");
}
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("ticket", "pcmk_ticket_t *")
static int
ticket_text(pcmk__output_t *out, va_list args) {
pcmk_ticket_t *ticket = va_arg(args, pcmk_ticket_t *);
if (ticket->last_granted > -1) {
char *epoch_str = pcmk__epoch2str(&(ticket->last_granted), 0);
out->list_item(out, ticket->id, "%s%s %s=\"%s\"",
ticket->granted ? "granted" : "revoked",
ticket->standby ? " [standby]" : "",
"last-granted", pcmk__s(epoch_str, ""));
free(epoch_str);
} else {
out->list_item(out, ticket->id, "%s%s",
ticket->granted ? "granted" : "revoked",
ticket->standby ? " [standby]" : "");
}
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("ticket", "pcmk_ticket_t *")
static int
ticket_xml(pcmk__output_t *out, va_list args) {
pcmk_ticket_t *ticket = va_arg(args, pcmk_ticket_t *);
xmlNodePtr node = NULL;
node = pcmk__output_create_xml_node(out, "ticket",
PCMK_XA_ID, ticket->id,
"status", ticket->granted ? "granted" : "revoked",
"standby", pcmk__btoa(ticket->standby),
NULL);
if (ticket->last_granted > -1) {
char *buf = pcmk__epoch2str(&ticket->last_granted, 0);
crm_xml_add(node, "last-granted", buf);
free(buf);
}
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("ticket-list", "pcmk_scheduler_t *", "bool")
static int
ticket_list(pcmk__output_t *out, va_list args) {
pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
bool print_spacer = va_arg(args, int);
GHashTableIter iter;
gpointer key, value;
if (g_hash_table_size(scheduler->tickets) == 0) {
return pcmk_rc_no_output;
}
PCMK__OUTPUT_SPACER_IF(out, print_spacer);
/* Print section heading */
out->begin_list(out, NULL, NULL, "Tickets");
/* Print each ticket */
g_hash_table_iter_init(&iter, scheduler->tickets);
while (g_hash_table_iter_next(&iter, &key, &value)) {
pcmk_ticket_t *ticket = (pcmk_ticket_t *) value;
out->message(out, "ticket", ticket);
}
/* Close section */
out->end_list(out);
return pcmk_rc_ok;
}
static pcmk__message_entry_t fmt_functions[] = {
{ "ban", "default", ban_text },
{ "ban", "html", ban_html },
{ "ban", "xml", ban_xml },
{ "ban-list", "default", ban_list },
{ "bundle", "default", pe__bundle_text },
{ "bundle", "xml", pe__bundle_xml },
{ "bundle", "html", pe__bundle_html },
{ "clone", "default", pe__clone_default },
{ "clone", "xml", pe__clone_xml },
{ "cluster-counts", "default", cluster_counts_text },
{ "cluster-counts", "html", cluster_counts_html },
{ "cluster-counts", "xml", cluster_counts_xml },
{ "cluster-dc", "default", cluster_dc_text },
{ "cluster-dc", "html", cluster_dc_html },
{ "cluster-dc", "xml", cluster_dc_xml },
{ "cluster-options", "default", cluster_options_text },
{ "cluster-options", "html", cluster_options_html },
{ "cluster-options", "log", cluster_options_log },
{ "cluster-options", "xml", cluster_options_xml },
{ "cluster-summary", "default", cluster_summary },
{ "cluster-summary", "html", cluster_summary_html },
{ "cluster-stack", "default", cluster_stack_text },
{ "cluster-stack", "html", cluster_stack_html },
{ "cluster-stack", "xml", cluster_stack_xml },
{ "cluster-times", "default", cluster_times_text },
{ "cluster-times", "html", cluster_times_html },
{ "cluster-times", "xml", cluster_times_xml },
{ "failed-action", "default", failed_action_default },
{ "failed-action", "xml", failed_action_xml },
{ "failed-action-list", "default", failed_action_list },
{ "group", "default", pe__group_default},
{ "group", "xml", pe__group_xml },
{ "maint-mode", "text", cluster_maint_mode_text },
{ "node", "default", node_text },
{ "node", "html", node_html },
{ "node", "xml", node_xml },
{ "node-and-op", "default", node_and_op },
{ "node-and-op", "xml", node_and_op_xml },
{ "node-capacity", "default", node_capacity },
{ "node-capacity", "xml", node_capacity_xml },
{ "node-history-list", "default", node_history_list },
{ "node-list", "default", node_list_text },
{ "node-list", "html", node_list_html },
{ "node-list", "xml", node_list_xml },
{ "node-weight", "default", node_weight },
{ "node-weight", "xml", node_weight_xml },
{ "node-attribute", "default", node_attribute_text },
{ "node-attribute", "html", node_attribute_html },
{ "node-attribute", "xml", node_attribute_xml },
{ "node-attribute-list", "default", node_attribute_list },
{ "node-summary", "default", node_summary },
{ "op-history", "default", op_history_text },
{ "op-history", "xml", op_history_xml },
{ "primitive", "default", pe__resource_text },
{ "primitive", "xml", pe__resource_xml },
{ "primitive", "html", pe__resource_html },
{ "promotion-score", "default", promotion_score },
{ "promotion-score", "xml", promotion_score_xml },
{ "resource-config", "default", resource_config },
{ "resource-config", "text", resource_config_text },
{ "resource-history", "default", resource_history_text },
{ "resource-history", "xml", resource_history_xml },
{ "resource-list", "default", resource_list },
{ "resource-operation-list", "default", resource_operation_list },
{ "resource-util", "default", resource_util },
{ "resource-util", "xml", resource_util_xml },
{ "ticket", "default", ticket_text },
{ "ticket", "html", ticket_html },
{ "ticket", "xml", ticket_xml },
{ "ticket-list", "default", ticket_list },
{ NULL, NULL, NULL }
};
void
pe__register_messages(pcmk__output_t *out) {
pcmk__register_messages(out, fmt_functions);
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Apr 21, 7:21 PM (12 h, 31 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1665501
Default Alt Text
(230 KB)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment