Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/include/crm/common/resources.h b/include/crm/common/resources.h
index bce34fc2b3..ebd6c1c92e 100644
--- a/include/crm/common/resources.h
+++ b/include/crm/common/resources.h
@@ -1,342 +1,339 @@
/*
* 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.
*/
#ifndef PCMK__CRM_COMMON_RESOURCES__H
#define PCMK__CRM_COMMON_RESOURCES__H
#include <stdbool.h> // bool
#include <sys/types.h> // time_t
#include <libxml/tree.h> // xmlNode
#include <glib.h> // gboolean, guint, GList, GHashTable
#include <crm/common/roles.h> // enum rsc_role_e
#include <crm/common/scheduler_types.h> // pcmk_resource_t, etc.
#ifdef __cplusplus
extern "C" {
#endif
/*!
* \file
* \brief Scheduler API for resources
* \ingroup core
*/
// Resource variants supported by Pacemaker
//!@{
//! \deprecated Do not use
enum pe_obj_types {
// Order matters: some code compares greater or lesser than
pcmk_rsc_variant_unknown = -1, // Unknown resource variant
pcmk_rsc_variant_primitive = 0, // Primitive resource
pcmk_rsc_variant_group = 1, // Group resource
pcmk_rsc_variant_clone = 2, // Clone resource
pcmk_rsc_variant_bundle = 3, // Bundle resource
#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
pe_unknown = pcmk_rsc_variant_unknown,
pe_native = pcmk_rsc_variant_primitive,
pe_group = pcmk_rsc_variant_group,
pe_clone = pcmk_rsc_variant_clone,
pe_container = pcmk_rsc_variant_bundle,
#endif
};
// What resource needs before it can be recovered from a failed node
enum rsc_start_requirement {
pcmk_requires_nothing = 0, // Resource can be recovered immediately
pcmk_requires_quorum = 1, // Resource can be recovered if quorate
pcmk_requires_fencing = 2, // Resource can be recovered after fencing
#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
rsc_req_nothing = pcmk_requires_nothing,
rsc_req_quorum = pcmk_requires_quorum,
rsc_req_stonith = pcmk_requires_fencing,
#endif
};
// How to recover a resource that is incorrectly active on multiple nodes
enum rsc_recovery_type {
pcmk_multiply_active_restart = 0, // Stop on all, start on desired
pcmk_multiply_active_stop = 1, // Stop on all and leave stopped
pcmk_multiply_active_block = 2, // Do nothing to resource
pcmk_multiply_active_unexpected = 3, // Stop unexpected instances
#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
recovery_stop_start = pcmk_multiply_active_restart,
recovery_stop_only = pcmk_multiply_active_stop,
recovery_block = pcmk_multiply_active_block,
recovery_stop_unexpected = pcmk_multiply_active_unexpected,
#endif
};
// Resource scheduling flags
enum pcmk_rsc_flags {
// No resource flags set (compare with equality rather than bit set)
pcmk_no_rsc_flags = 0ULL,
// Whether resource has been removed from the configuration
pcmk_rsc_removed = (1ULL << 0),
// Whether resource is managed
pcmk_rsc_managed = (1ULL << 1),
// Whether resource is blocked from further action
pcmk_rsc_blocked = (1ULL << 2),
// Whether resource has been removed but has a container
pcmk_rsc_removed_filler = (1ULL << 3),
// Whether resource has clone notifications enabled
pcmk_rsc_notify = (1ULL << 4),
// Whether resource is not an anonymous clone instance
pcmk_rsc_unique = (1ULL << 5),
// Whether resource's class is "stonith"
pcmk_rsc_fence_device = (1ULL << 6),
// Whether resource can be promoted and demoted
pcmk_rsc_promotable = (1ULL << 7),
// Whether resource has not yet been assigned to a node
pcmk_rsc_unassigned = (1ULL << 8),
// Whether resource is in the process of being assigned to a node
pcmk_rsc_assigning = (1ULL << 9),
// Whether resource is in the process of modifying allowed node scores
pcmk_rsc_updating_nodes = (1ULL << 10),
// Whether resource is in the process of scheduling actions to restart
pcmk_rsc_restarting = (1ULL << 11),
// Whether resource must be stopped (instead of demoted) if it is failed
pcmk_rsc_stop_if_failed = (1ULL << 12),
// Whether a reload action has been scheduled for resource
pcmk_rsc_reload = (1ULL << 13),
// Whether resource is a remote connection allowed to run on a remote node
pcmk_rsc_remote_nesting_allowed = (1ULL << 14),
// Whether resource has \c PCMK_META_CRITICAL meta-attribute enabled
pcmk_rsc_critical = (1ULL << 15),
// Whether resource is considered failed
pcmk_rsc_failed = (1ULL << 16),
// Flag for non-scheduler code to use to detect recursion loops
pcmk_rsc_detect_loop = (1ULL << 17),
// \deprecated Do not use
pcmk_rsc_runnable = (1ULL << 18),
// Whether resource has pending start action in history
pcmk_rsc_start_pending = (1ULL << 19),
// \deprecated Do not use
pcmk_rsc_starting = (1ULL << 20),
// \deprecated Do not use
pcmk_rsc_stopping = (1ULL << 21),
/*
* Whether resource is multiply active with recovery set to
* \c PCMK_VALUE_STOP_UNEXPECTED
*/
pcmk_rsc_stop_unexpected = (1ULL << 22),
// Whether resource is allowed to live-migrate
pcmk_rsc_migratable = (1ULL << 23),
// Whether resource has an ignorable failure
pcmk_rsc_ignore_failure = (1ULL << 24),
// Whether resource is an implicit container resource for a bundle replica
pcmk_rsc_replica_container = (1ULL << 25),
// Whether resource, its node, or entire cluster is in maintenance mode
pcmk_rsc_maintenance = (1ULL << 26),
// \deprecated Do not use
pcmk_rsc_has_filler = (1ULL << 27),
// Whether resource can be started or promoted only on quorate nodes
pcmk_rsc_needs_quorum = (1ULL << 28),
// Whether resource requires fencing before recovery if on unclean node
pcmk_rsc_needs_fencing = (1ULL << 29),
// Whether resource can be started or promoted only on unfenced nodes
pcmk_rsc_needs_unfencing = (1ULL << 30),
};
//!@}
//! Search options for resources (exact resource ID always matches)
enum pe_find {
//! Also match clone instance ID from resource history
pcmk_rsc_match_history = (1 << 0),
//! Also match anonymous clone instances by base name
pcmk_rsc_match_anon_basename = (1 << 1),
//! Match only clones and their instances, by either clone or instance ID
pcmk_rsc_match_clone_only = (1 << 2),
//! If matching by node, compare current node instead of assigned node
pcmk_rsc_match_current_node = (1 << 3),
//! \deprecated Do not use
pe_find_inactive = (1 << 4),
//! Match clone instances (even unique) by base name as well as exact ID
pcmk_rsc_match_basename = (1 << 5),
#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
//! \deprecated Use pcmk_rsc_match_history instead
pe_find_renamed = pcmk_rsc_match_history,
//! \deprecated Use pcmk_rsc_match_anon_basename instead
pe_find_anon = pcmk_rsc_match_anon_basename,
//! \deprecated Use pcmk_rsc_match_clone_only instead
pe_find_clone = pcmk_rsc_match_clone_only,
//! \deprecated Use pcmk_rsc_match_current_node instead
pe_find_current = pcmk_rsc_match_current_node,
//! \deprecated Use pcmk_rsc_match_basename instead
pe_find_any = pcmk_rsc_match_basename,
#endif
};
//! \deprecated Do not use
enum pe_restart {
pe_restart_restart,
pe_restart_ignore,
};
//! \internal Do not use
typedef struct pcmk__resource_private pcmk__resource_private_t;
// Implementation of pcmk_resource_t
// @COMPAT Make this internal when we can break API backward compatibility
//!@{
//! \deprecated Do not use (public access will be removed in a future release)
struct pe_resource_s {
/* @COMPAT Once all members are moved to pcmk__resource_private_t,
* We can make that the pcmk_resource_t implementation and drop this
* struct altogether, leaving pcmk_resource_t as an opaque public type.
*/
pcmk__resource_private_t *private;
// NOTE: sbd (as of at least 1.5.2) uses this
//! \deprecated Call pcmk_resource_id() instead
char *id; // Resource ID in configuration
- // Original resource configuration, if using template
- xmlNode *orig_xml;
-
// Configuration of resource operations (possibly expanded from template)
xmlNode *ops_xml;
pcmk_scheduler_t *cluster; // Cluster that resource is part of
pcmk_resource_t *parent; // Resource's parent resource, if any
enum pe_obj_types variant; // Resource variant
void *variant_opaque; // Variant-specific (and private) data
enum rsc_recovery_type recovery_type; // How to recover if failed
enum pe_restart restart_type; // \deprecated Do not use
int priority; // Configured priority
int stickiness; // Extra preference for current node
int sort_index; // Promotion score on assigned node
int failure_timeout; // Failure timeout
int migration_threshold; // Migration threshold
guint remote_reconnect_ms; // Retry interval for remote connections
char *pending_task; // Pending action in history, if any
// NOTE: sbd (as of at least 1.5.2) uses this
//! \deprecated Call pcmk_resource_is_managed() instead
unsigned long long flags; // Group of enum pcmk_rsc_flags
// @TODO Merge these into flags
gboolean is_remote_node; // Whether this is a remote connection
gboolean exclusive_discover; // Whether exclusive probing is enabled
/* Pay special attention to whether you want to use rsc_cons_lhs and
* rsc_cons directly, which include only colocations explicitly involving
* this resource, or call libpacemaker's pcmk__with_this_colocations() and
* pcmk__this_with_colocations() functions, which may return relevant
* colocations involving the resource's ancestors as well.
*/
GList *rsc_cons_lhs; // Colocations of other resources with this one
GList *rsc_cons; // Colocations of this resource with others
GList *rsc_location; // Location constraints for resource
GList *actions; // Actions scheduled for resource
GList *rsc_tickets; // Ticket constraints for resource
pcmk_node_t *allocated_to; // Node resource is assigned to
// The destination node, if migrate_to completed but migrate_from has not
pcmk_node_t *partial_migration_target;
// The source node, if migrate_to completed but migrate_from has not
pcmk_node_t *partial_migration_source;
// Nodes where resource may be active
GList *running_on;
// Nodes where resource has been probed (key is node ID, not name)
GHashTable *known_on;
// Nodes where resource may run (key is node ID, not name)
GHashTable *allowed_nodes;
enum rsc_role_e role; // Resource's current role
enum rsc_role_e next_role; // Resource's scheduled next role
GHashTable *meta; // Resource's meta-attributes
GHashTable *parameters; // \deprecated Use pe_rsc_params() instead
GHashTable *utilization; // Resource's utilization attributes
GList *children; // Resource's child resources, if any
// Source nodes where stop is needed after migrate_from and migrate_to
GList *dangling_migrations;
pcmk_resource_t *container; // Resource containing this one, if any
GList *fillers; // Resources contained by this one, if any
// @COMPAT These should be made const at next API compatibility break
pcmk_node_t *pending_node; // Node on which pending_task is happening
pcmk_node_t *lock_node; // Resource shutdown-locked to this node
time_t lock_time; // When shutdown lock started
/*
* Resource parameters may have node-attribute-based rules, which means the
* values can vary by node. This table has node names as keys and parameter
* name/value tables as values. Use pe_rsc_params() to get the table for a
* given node rather than use this directly.
*/
GHashTable *parameter_cache;
};
//!@}
const char *pcmk_resource_id(const pcmk_resource_t *rsc);
bool pcmk_resource_is_managed(const pcmk_resource_t *rsc);
#ifdef __cplusplus
}
#endif
#endif // PCMK__CRM_COMMON_RESOURCES__H
diff --git a/include/crm/common/resources_internal.h b/include/crm/common/resources_internal.h
index 8e9cbeabfa..1fb7132b04 100644
--- a/include/crm/common/resources_internal.h
+++ b/include/crm/common/resources_internal.h
@@ -1,225 +1,228 @@
/*
* Copyright 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.
*/
#ifndef PCMK__CRM_COMMON_RESOURCES_INTERNAL__H
#define PCMK__CRM_COMMON_RESOURCES_INTERNAL__H
#include <glib.h> // gboolean, GList
#include <crm/common/resources.h> // enum rsc_recovery_type
#include <crm/common/roles.h> // enum rsc_role_e
#include <crm/common/scheduler_types.h> // pcmk_node_t, pcmk_resource_t, etc.
#ifdef __cplusplus
extern "C" {
#endif
/*!
* \internal
* \brief Set resource flags
*
* \param[in,out] resource Resource to set flags for
* \param[in] flags_to_set Group of enum pcmk_rsc_flags to set
*/
#define pcmk__set_rsc_flags(resource, flags_to_set) do { \
(resource)->flags = pcmk__set_flags_as(__func__, __LINE__, \
LOG_TRACE, "Resource", (resource)->id, (resource)->flags, \
(flags_to_set), #flags_to_set); \
} while (0)
/*!
* \internal
* \brief Clear resource flags
*
* \param[in,out] resource Resource to clear flags for
* \param[in] flags_to_clear Group of enum pcmk_rsc_flags to clear
*/
#define pcmk__clear_rsc_flags(resource, flags_to_clear) do { \
(resource)->flags = pcmk__clear_flags_as(__func__, __LINE__, \
LOG_TRACE, "Resource", (resource)->id, (resource)->flags, \
(flags_to_clear), #flags_to_clear); \
} while (0)
//! Resource assignment methods (implementation defined by libpacemaker)
typedef struct pcmk__assignment_methods pcmk__assignment_methods_t;
//! Resource object methods
typedef struct {
/*!
* \internal
* \brief Parse variant-specific resource XML from CIB into struct members
*
* \param[in,out] rsc Partially unpacked resource
* \param[in,out] scheduler Scheduler data
*
* \return TRUE if resource was unpacked successfully, otherwise FALSE
*/
gboolean (*unpack)(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler);
/*!
* \internal
* \brief Search for a resource ID in a resource and its children
*
* \param[in] rsc Search this resource and its children
* \param[in] id Search for this resource ID
* \param[in] on_node If not NULL, limit search to resources on this node
* \param[in] flags Group of enum pe_find flags
*
* \return Resource that matches search criteria if any, otherwise NULL
*/
pcmk_resource_t *(*find_rsc)(pcmk_resource_t *rsc, const char *search,
const pcmk_node_t *node, int flags);
/*!
* \internal
* \brief Get value of a resource instance attribute
*
* \param[in,out] rsc Resource to check
* \param[in] node Node to use to evaluate rules
* \param[in] create Ignored
* \param[in] name Name of instance attribute to check
* \param[in,out] scheduler Scheduler data
*
* \return Value of requested attribute if available, otherwise NULL
* \note The caller is responsible for freeing the result using free().
*/
char *(*parameter)(pcmk_resource_t *rsc, pcmk_node_t *node, gboolean create,
const char *name, pcmk_scheduler_t *scheduler);
/*!
* \internal
* \brief Check whether a resource is active
*
* \param[in] rsc Resource to check
* \param[in] all If \p rsc is collective, all instances must be active
*
* \return TRUE if \p rsc is active, otherwise FALSE
*/
gboolean (*active)(pcmk_resource_t *rsc, gboolean all);
/*!
* \internal
* \brief Get resource's current or assigned role
*
* \param[in] rsc Resource to check
* \param[in] current If TRUE, check current role, otherwise assigned role
*
* \return Current or assigned role of \p rsc
*/
enum rsc_role_e (*state)(const pcmk_resource_t *rsc, gboolean current);
/*!
* \internal
* \brief List nodes where a resource (or any of its children) is
*
* \param[in] rsc Resource to check
* \param[out] list List to add result to
* \param[in] current If 0, list nodes where \p rsc is assigned;
* if 1, where active; if 2, where active or pending
*
* \return If list contains only one node, that node, otherwise NULL
*/
pcmk_node_t *(*location)(const pcmk_resource_t *rsc, GList **list,
int current);
/*!
* \internal
* \brief Free all memory used by a resource
*
* \param[in,out] rsc Resource to free
*/
void (*free)(pcmk_resource_t *rsc);
/*!
* \internal
* \brief Increment cluster's instance counts for a resource
*
* Given a resource, increment its cluster's ninstances, disabled_resources,
* and blocked_resources counts for the resource and its descendants.
*
* \param[in,out] rsc Resource to count
*/
void (*count)(pcmk_resource_t *rsc);
/*!
* \internal
* \brief Check whether a given resource is in a list of resources
*
* \param[in] rsc Resource ID to check for
* \param[in] only_rsc List of resource IDs to check
* \param[in] check_parent If TRUE, check top ancestor as well
*
* \return TRUE if \p rsc, its top parent if requested, or '*' is in
* \p only_rsc, otherwise FALSE
*/
gboolean (*is_filtered)(const pcmk_resource_t *rsc, GList *only_rsc,
gboolean check_parent);
/*!
* \internal
* \brief Find a node (and optionally count all) where resource is active
*
* \param[in] rsc Resource to check
* \param[out] count_all If not NULL, set this to count of active nodes
* \param[out] count_clean If not NULL, set this to count of clean nodes
*
* \return A node where the resource is active, preferring the source node
* if the resource is involved in a partial migration, or a clean,
* online node if the resource's \c PCMK_META_REQUIRES is
* \c PCMK_VALUE_QUORUM or \c PCMK_VALUE_NOTHING, otherwise \c NULL.
*/
pcmk_node_t *(*active_node)(const pcmk_resource_t *rsc,
unsigned int *count_all,
unsigned int *count_clean);
/*!
* \internal
* \brief Get maximum resource instances per node
*
* \param[in] rsc Resource to check
*
* \return Maximum number of \p rsc instances that can be active on one node
*/
unsigned int (*max_per_node)(const pcmk_resource_t *rsc);
} pcmk__rsc_methods_t;
// Implementation of pcmk__resource_private_t
struct pcmk__resource_private {
char *history_id; // Resource instance ID in history
// Resource configuration (possibly expanded from template)
xmlNode *xml;
+ // Original resource configuration, if using template
+ xmlNode *orig_xml;
+
const pcmk__rsc_methods_t *fns; // Resource object methods
const pcmk__assignment_methods_t *cmds; // Resource assignment methods
};
const char *pcmk__multiply_active_text(enum rsc_recovery_type recovery);
/*!
* \internal
* \brief Get node where resource is currently active (if any)
*
* \param[in] rsc Resource to check
*
* \return Node that \p rsc is active on, if any, otherwise NULL
*/
static inline pcmk_node_t *
pcmk__current_node(const pcmk_resource_t *rsc)
{
if (rsc == NULL) {
return NULL;
}
return rsc->private->fns->active_node(rsc, NULL, NULL);
}
#ifdef __cplusplus
}
#endif
#endif // PCMK__CRM_COMMON_RESOURCES_INTERNAL__H
diff --git a/lib/pengine/clone.c b/lib/pengine/clone.c
index f769d0ee71..9019ac712d 100644
--- a/lib/pengine/clone.c
+++ b/lib/pengine/clone.c
@@ -1,1266 +1,1266 @@
/*
* 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 <stdint.h>
#include <crm/pengine/rules.h>
#include <crm/pengine/status.h>
#include <crm/pengine/internal.h>
#include <pe_status_private.h>
#include <crm/common/xml.h>
#include <crm/common/output.h>
#include <crm/common/xml_internal.h>
#include <crm/common/scheduler_internal.h>
#ifdef PCMK__COMPAT_2_0
#define PROMOTED_INSTANCES PCMK__ROLE_PROMOTED_LEGACY "s"
#define UNPROMOTED_INSTANCES PCMK__ROLE_UNPROMOTED_LEGACY "s"
#else
#define PROMOTED_INSTANCES PCMK_ROLE_PROMOTED
#define UNPROMOTED_INSTANCES PCMK_ROLE_UNPROMOTED
#endif
typedef struct clone_variant_data_s {
int clone_max;
int clone_node_max;
int promoted_max;
int promoted_node_max;
int total_clones;
uint32_t flags; // Group of enum pcmk__clone_flags
notify_data_t *stop_notify;
notify_data_t *start_notify;
notify_data_t *demote_notify;
notify_data_t *promote_notify;
xmlNode *xml_obj_child;
} clone_variant_data_t;
#define get_clone_variant_data(data, rsc) \
CRM_ASSERT(pcmk__is_clone(rsc) && (rsc->variant_opaque != NULL)); \
data = (clone_variant_data_t *) rsc->variant_opaque;
/*!
* \internal
* \brief Return the maximum number of clone instances allowed to be run
*
* \param[in] clone Clone or clone instance to check
*
* \return Maximum instances for \p clone
*/
int
pe__clone_max(const pcmk_resource_t *clone)
{
const clone_variant_data_t *clone_data = NULL;
get_clone_variant_data(clone_data, pe__const_top_resource(clone, false));
return clone_data->clone_max;
}
/*!
* \internal
* \brief Return the maximum number of clone instances allowed per node
*
* \param[in] clone Promotable clone or clone instance to check
*
* \return Maximum allowed instances per node for \p clone
*/
int
pe__clone_node_max(const pcmk_resource_t *clone)
{
const clone_variant_data_t *clone_data = NULL;
get_clone_variant_data(clone_data, pe__const_top_resource(clone, false));
return clone_data->clone_node_max;
}
/*!
* \internal
* \brief Return the maximum number of clone instances allowed to be promoted
*
* \param[in] clone Promotable clone or clone instance to check
*
* \return Maximum promoted instances for \p clone
*/
int
pe__clone_promoted_max(const pcmk_resource_t *clone)
{
clone_variant_data_t *clone_data = NULL;
get_clone_variant_data(clone_data, pe__const_top_resource(clone, false));
return clone_data->promoted_max;
}
/*!
* \internal
* \brief Return the maximum number of clone instances allowed to be promoted
*
* \param[in] clone Promotable clone or clone instance to check
*
* \return Maximum promoted instances for \p clone
*/
int
pe__clone_promoted_node_max(const pcmk_resource_t *clone)
{
clone_variant_data_t *clone_data = NULL;
get_clone_variant_data(clone_data, pe__const_top_resource(clone, false));
return clone_data->promoted_node_max;
}
static GList *
sorted_hash_table_values(GHashTable *table)
{
GList *retval = NULL;
GHashTableIter iter;
gpointer key, value;
g_hash_table_iter_init(&iter, table);
while (g_hash_table_iter_next(&iter, &key, &value)) {
if (!g_list_find_custom(retval, value, (GCompareFunc) strcmp)) {
retval = g_list_prepend(retval, (char *) value);
}
}
retval = g_list_sort(retval, (GCompareFunc) strcmp);
return retval;
}
static GList *
nodes_with_status(GHashTable *table, const char *status)
{
GList *retval = NULL;
GHashTableIter iter;
gpointer key, value;
g_hash_table_iter_init(&iter, table);
while (g_hash_table_iter_next(&iter, &key, &value)) {
if (!strcmp((char *) value, status)) {
retval = g_list_prepend(retval, key);
}
}
retval = g_list_sort(retval, (GCompareFunc) pcmk__numeric_strcasecmp);
return retval;
}
static GString *
node_list_to_str(const GList *list)
{
GString *retval = NULL;
for (const GList *iter = list; iter != NULL; iter = iter->next) {
pcmk__add_word(&retval, 1024, (const char *) iter->data);
}
return retval;
}
static void
clone_header(pcmk__output_t *out, int *rc, const pcmk_resource_t *rsc,
clone_variant_data_t *clone_data, const char *desc)
{
GString *attrs = NULL;
if (pcmk_is_set(rsc->flags, pcmk_rsc_promotable)) {
pcmk__add_separated_word(&attrs, 64, "promotable", ", ");
}
if (pcmk_is_set(rsc->flags, pcmk_rsc_unique)) {
pcmk__add_separated_word(&attrs, 64, "unique", ", ");
}
if (pe__resource_is_disabled(rsc)) {
pcmk__add_separated_word(&attrs, 64, "disabled", ", ");
}
if (pcmk_is_set(rsc->flags, pcmk_rsc_maintenance)) {
pcmk__add_separated_word(&attrs, 64, "maintenance", ", ");
} else if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
pcmk__add_separated_word(&attrs, 64, "unmanaged", ", ");
}
if (attrs != NULL) {
PCMK__OUTPUT_LIST_HEADER(out, FALSE, *rc, "Clone Set: %s [%s] (%s)%s%s%s",
rsc->id,
pcmk__xe_id(clone_data->xml_obj_child),
(const char *) attrs->str, desc ? " (" : "",
desc ? desc : "", desc ? ")" : "");
g_string_free(attrs, TRUE);
} else {
PCMK__OUTPUT_LIST_HEADER(out, FALSE, *rc, "Clone Set: %s [%s]%s%s%s",
rsc->id,
pcmk__xe_id(clone_data->xml_obj_child),
desc ? " (" : "", desc ? desc : "",
desc ? ")" : "");
}
}
void
pe__force_anon(const char *standard, pcmk_resource_t *rsc, const char *rid,
pcmk_scheduler_t *scheduler)
{
if (pcmk__is_clone(rsc)) {
clone_variant_data_t *clone_data = rsc->variant_opaque;
pcmk__config_warn("Ignoring " PCMK_META_GLOBALLY_UNIQUE " for %s "
"because %s resources such as %s can be used only as "
"anonymous clones", rsc->id, standard, rid);
clone_data->clone_node_max = 1;
clone_data->clone_max = QB_MIN(clone_data->clone_max,
g_list_length(scheduler->nodes));
}
}
pcmk_resource_t *
find_clone_instance(const pcmk_resource_t *rsc, const char *sub_id)
{
char *child_id = NULL;
pcmk_resource_t *child = NULL;
const char *child_base = NULL;
clone_variant_data_t *clone_data = NULL;
get_clone_variant_data(clone_data, rsc);
child_base = pcmk__xe_id(clone_data->xml_obj_child);
child_id = crm_strdup_printf("%s:%s", child_base, sub_id);
child = pe_find_resource(rsc->children, child_id);
free(child_id);
return child;
}
pcmk_resource_t *
pe__create_clone_child(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler)
{
gboolean as_orphan = FALSE;
char *inc_num = NULL;
char *inc_max = NULL;
pcmk_resource_t *child_rsc = NULL;
xmlNode *child_copy = NULL;
clone_variant_data_t *clone_data = NULL;
get_clone_variant_data(clone_data, rsc);
CRM_CHECK(clone_data->xml_obj_child != NULL, return FALSE);
if (clone_data->total_clones >= clone_data->clone_max) {
// If we've already used all available instances, this is an orphan
as_orphan = TRUE;
}
// Allocate instance numbers in numerical order (starting at 0)
inc_num = pcmk__itoa(clone_data->total_clones);
inc_max = pcmk__itoa(clone_data->clone_max);
child_copy = pcmk__xml_copy(NULL, clone_data->xml_obj_child);
crm_xml_add(child_copy, PCMK__META_CLONE, inc_num);
if (pe__unpack_resource(child_copy, &child_rsc, rsc,
scheduler) != pcmk_rc_ok) {
goto bail;
}
/* child_rsc->globally_unique = rsc->globally_unique; */
CRM_ASSERT(child_rsc);
clone_data->total_clones += 1;
pcmk__rsc_trace(child_rsc, "Setting clone attributes for: %s",
child_rsc->id);
rsc->children = g_list_append(rsc->children, child_rsc);
if (as_orphan) {
pe__set_resource_flags_recursive(child_rsc, pcmk_rsc_removed);
}
pcmk__insert_meta(child_rsc, PCMK_META_CLONE_MAX, inc_max);
pcmk__rsc_trace(rsc, "Added %s instance %s", rsc->id, child_rsc->id);
bail:
free(inc_num);
free(inc_max);
return child_rsc;
}
/*!
* \internal
* \brief Unpack a nonnegative integer value from a resource meta-attribute
*
* \param[in] rsc Resource with meta-attribute
* \param[in] meta_name Name of meta-attribute to unpack
* \param[in] deprecated_name If not NULL, try unpacking this
* if \p meta_name is unset
* \param[in] default_value Value to use if unset
*
* \return Integer parsed from resource's specified meta-attribute if a valid
* nonnegative integer, \p default_value if unset, or 0 if invalid
*/
static int
unpack_meta_int(const pcmk_resource_t *rsc, const char *meta_name,
const char *deprecated_name, int default_value)
{
int integer = default_value;
const char *value = g_hash_table_lookup(rsc->meta, meta_name);
if ((value == NULL) && (deprecated_name != NULL)) {
value = g_hash_table_lookup(rsc->meta, deprecated_name);
if (value != NULL) {
if (pcmk__str_eq(deprecated_name, PCMK__META_PROMOTED_MAX_LEGACY,
pcmk__str_none)) {
pcmk__warn_once(pcmk__wo_clone_master_max,
"Support for the " PCMK__META_PROMOTED_MAX_LEGACY
" meta-attribute (such as in %s) is deprecated "
"and will be removed in a future release. Use the "
PCMK_META_PROMOTED_MAX " meta-attribute instead.",
rsc->id);
} else if (pcmk__str_eq(deprecated_name, PCMK__META_PROMOTED_NODE_MAX_LEGACY,
pcmk__str_none)) {
pcmk__warn_once(pcmk__wo_clone_master_node_max,
"Support for the " PCMK__META_PROMOTED_NODE_MAX_LEGACY
" meta-attribute (such as in %s) is deprecated "
"and will be removed in a future release. Use the "
PCMK_META_PROMOTED_NODE_MAX " meta-attribute instead.",
rsc->id);
}
}
}
if (value != NULL) {
pcmk__scan_min_int(value, &integer, 0);
}
return integer;
}
gboolean
clone_unpack(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler)
{
int lpc = 0;
xmlNode *a_child = NULL;
xmlNode *xml_obj = rsc->private->xml;
clone_variant_data_t *clone_data = NULL;
pcmk__rsc_trace(rsc, "Processing resource %s...", rsc->id);
clone_data = pcmk__assert_alloc(1, sizeof(clone_variant_data_t));
rsc->variant_opaque = clone_data;
if (pcmk_is_set(rsc->flags, pcmk_rsc_promotable)) {
// Use 1 as default but 0 for minimum and invalid
// @COMPAT PCMK__META_PROMOTED_MAX_LEGACY deprecated since 2.0.0
clone_data->promoted_max =
unpack_meta_int(rsc, PCMK_META_PROMOTED_MAX,
PCMK__META_PROMOTED_MAX_LEGACY, 1);
// Use 1 as default but 0 for minimum and invalid
// @COMPAT PCMK__META_PROMOTED_NODE_MAX_LEGACY deprecated since 2.0.0
clone_data->promoted_node_max =
unpack_meta_int(rsc, PCMK_META_PROMOTED_NODE_MAX,
PCMK__META_PROMOTED_NODE_MAX_LEGACY, 1);
}
// Use 1 as default but 0 for minimum and invalid
clone_data->clone_node_max = unpack_meta_int(rsc, PCMK_META_CLONE_NODE_MAX,
NULL, 1);
/* Use number of nodes (but always at least 1, which is handy for crm_verify
* for a CIB without nodes) as default, but 0 for minimum and invalid
*/
clone_data->clone_max = unpack_meta_int(rsc, PCMK_META_CLONE_MAX, NULL,
QB_MAX(1, g_list_length(scheduler->nodes)));
if (crm_is_true(g_hash_table_lookup(rsc->meta, PCMK_META_ORDERED))) {
clone_data->flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE,
"Clone", rsc->id,
clone_data->flags,
pcmk__clone_ordered,
"pcmk__clone_ordered");
}
if (!pcmk_is_set(rsc->flags, pcmk_rsc_unique)
&& (clone_data->clone_node_max > 1)) {
pcmk__config_err("Ignoring " PCMK_META_CLONE_NODE_MAX " of %d for %s "
"because anonymous clones support only one instance "
"per node", clone_data->clone_node_max, rsc->id);
clone_data->clone_node_max = 1;
}
pcmk__rsc_trace(rsc, "Options for %s", rsc->id);
pcmk__rsc_trace(rsc, "\tClone max: %d", clone_data->clone_max);
pcmk__rsc_trace(rsc, "\tClone node max: %d", clone_data->clone_node_max);
pcmk__rsc_trace(rsc, "\tClone is unique: %s",
pcmk__flag_text(rsc->flags, pcmk_rsc_unique));
pcmk__rsc_trace(rsc, "\tClone is promotable: %s",
pcmk__flag_text(rsc->flags, pcmk_rsc_promotable));
// Clones may contain a single group or primitive
for (a_child = pcmk__xe_first_child(xml_obj, NULL, NULL, NULL);
a_child != NULL; a_child = pcmk__xe_next(a_child)) {
if (pcmk__str_any_of((const char *) a_child->name,
PCMK_XE_PRIMITIVE, PCMK_XE_GROUP, NULL)) {
clone_data->xml_obj_child = a_child;
break;
}
}
if (clone_data->xml_obj_child == NULL) {
pcmk__config_err("%s has nothing to clone", rsc->id);
return FALSE;
}
/*
* Make clones ever so slightly sticky by default
*
* This helps ensure clone instances are not shuffled around the cluster
* for no benefit in situations when pre-allocation is not appropriate
*/
if (g_hash_table_lookup(rsc->meta, PCMK_META_RESOURCE_STICKINESS) == NULL) {
pcmk__insert_meta(rsc, PCMK_META_RESOURCE_STICKINESS, "1");
}
/* This ensures that the PCMK_META_GLOBALLY_UNIQUE value always exists for
* children to inherit when being unpacked, as well as in resource agents'
* environment.
*/
pcmk__insert_meta(rsc, PCMK_META_GLOBALLY_UNIQUE,
pcmk__flag_text(rsc->flags, pcmk_rsc_unique));
if (clone_data->clone_max <= 0) {
/* Create one child instance so that unpack_find_resource() will hook up
* any orphans up to the parent correctly.
*/
if (pe__create_clone_child(rsc, scheduler) == NULL) {
return FALSE;
}
} else {
// Create a child instance for each available instance number
for (lpc = 0; lpc < clone_data->clone_max; lpc++) {
if (pe__create_clone_child(rsc, scheduler) == NULL) {
return FALSE;
}
}
}
pcmk__rsc_trace(rsc, "Added %d children to resource %s...",
clone_data->clone_max, rsc->id);
return TRUE;
}
gboolean
clone_active(pcmk_resource_t * rsc, gboolean all)
{
GList *gIter = rsc->children;
for (; gIter != NULL; gIter = gIter->next) {
pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
gboolean child_active = child_rsc->private->fns->active(child_rsc, all);
if (all == FALSE && child_active) {
return TRUE;
} else if (all && child_active == FALSE) {
return FALSE;
}
}
if (all) {
return TRUE;
} else {
return FALSE;
}
}
static const char *
configured_role_str(pcmk_resource_t * rsc)
{
const char *target_role = g_hash_table_lookup(rsc->meta,
PCMK_META_TARGET_ROLE);
if ((target_role == NULL) && rsc->children && rsc->children->data) {
pcmk_resource_t *instance = rsc->children->data; // Any instance will do
target_role = g_hash_table_lookup(instance->meta,
PCMK_META_TARGET_ROLE);
}
return target_role;
}
static enum rsc_role_e
configured_role(pcmk_resource_t *rsc)
{
enum rsc_role_e role = pcmk_role_unknown;
const char *target_role = configured_role_str(rsc);
if (target_role != NULL) {
role = pcmk_parse_role(target_role);
if (role == pcmk_role_unknown) {
pcmk__config_err("Invalid " PCMK_META_TARGET_ROLE
" for resource %s", rsc->id);
}
}
return role;
}
bool
is_set_recursive(const pcmk_resource_t *rsc, long long flag, bool any)
{
GList *gIter;
bool all = !any;
if (pcmk_is_set(rsc->flags, flag)) {
if(any) {
return TRUE;
}
} else if(all) {
return FALSE;
}
for (gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
if(is_set_recursive(gIter->data, flag, any)) {
if(any) {
return TRUE;
}
} else if(all) {
return FALSE;
}
}
if(all) {
return TRUE;
}
return FALSE;
}
PCMK__OUTPUT_ARGS("clone", "uint32_t", "pcmk_resource_t *", "GList *",
"GList *")
int
pe__clone_xml(pcmk__output_t *out, va_list args)
{
uint32_t show_opts = va_arg(args, uint32_t);
pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
GList *only_node = va_arg(args, GList *);
GList *only_rsc = va_arg(args, GList *);
GList *gIter = rsc->children;
GList *all = NULL;
int rc = pcmk_rc_no_output;
gboolean printed_header = FALSE;
gboolean print_everything = TRUE;
if (rsc->private->fns->is_filtered(rsc, only_rsc, TRUE)) {
return rc;
}
print_everything = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches) ||
(strstr(rsc->id, ":") != NULL && pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches));
all = g_list_prepend(all, (gpointer) "*");
for (; gIter != NULL; gIter = gIter->next) {
pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
if (pcmk__rsc_filtered_by_node(child_rsc, only_node)) {
continue;
}
if (child_rsc->private->fns->is_filtered(child_rsc, only_rsc,
print_everything)) {
continue;
}
if (!printed_header) {
const char *multi_state = pcmk__flag_text(rsc->flags,
pcmk_rsc_promotable);
const char *unique = pcmk__flag_text(rsc->flags, pcmk_rsc_unique);
const char *maintenance = pcmk__flag_text(rsc->flags,
pcmk_rsc_maintenance);
const char *managed = pcmk__flag_text(rsc->flags, pcmk_rsc_managed);
const char *disabled = pcmk__btoa(pe__resource_is_disabled(rsc));
const char *failed = pcmk__flag_text(rsc->flags, pcmk_rsc_failed);
const char *ignored = pcmk__flag_text(rsc->flags,
pcmk_rsc_ignore_failure);
const char *target_role = configured_role_str(rsc);
const char *desc = pe__resource_description(rsc, show_opts);
printed_header = TRUE;
rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_CLONE,
PCMK_XA_ID, rsc->id,
PCMK_XA_MULTI_STATE, multi_state,
PCMK_XA_UNIQUE, unique,
PCMK_XA_MAINTENANCE, maintenance,
PCMK_XA_MANAGED, managed,
PCMK_XA_DISABLED, disabled,
PCMK_XA_FAILED, failed,
PCMK_XA_FAILURE_IGNORED, ignored,
PCMK_XA_TARGET_ROLE, target_role,
PCMK_XA_DESCRIPTION, desc,
NULL);
CRM_ASSERT(rc == pcmk_rc_ok);
}
out->message(out, (const char *) child_rsc->private->xml->name,
show_opts, child_rsc, only_node, all);
}
if (printed_header) {
pcmk__output_xml_pop_parent(out);
}
g_list_free(all);
return rc;
}
PCMK__OUTPUT_ARGS("clone", "uint32_t", "pcmk_resource_t *", "GList *",
"GList *")
int
pe__clone_default(pcmk__output_t *out, va_list args)
{
uint32_t show_opts = va_arg(args, uint32_t);
pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
GList *only_node = va_arg(args, GList *);
GList *only_rsc = va_arg(args, GList *);
GHashTable *stopped = NULL;
GString *list_text = NULL;
GList *promoted_list = NULL;
GList *started_list = NULL;
GList *gIter = rsc->children;
const char *desc = NULL;
clone_variant_data_t *clone_data = NULL;
int active_instances = 0;
int rc = pcmk_rc_no_output;
gboolean print_everything = TRUE;
desc = pe__resource_description(rsc, show_opts);
get_clone_variant_data(clone_data, rsc);
if (rsc->private->fns->is_filtered(rsc, only_rsc, TRUE)) {
return rc;
}
print_everything = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches) ||
(strstr(rsc->id, ":") != NULL && pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches));
for (; gIter != NULL; gIter = gIter->next) {
gboolean print_full = FALSE;
pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
gboolean partially_active = child_rsc->private->fns->active(child_rsc,
FALSE);
if (pcmk__rsc_filtered_by_node(child_rsc, only_node)) {
continue;
}
if (child_rsc->private->fns->is_filtered(child_rsc, only_rsc,
print_everything)) {
continue;
}
if (pcmk_is_set(show_opts, pcmk_show_clone_detail)) {
print_full = TRUE;
}
if (pcmk_is_set(rsc->flags, pcmk_rsc_unique)) {
// Print individual instance when unique (except stopped orphans)
if (partially_active
|| !pcmk_is_set(rsc->flags, pcmk_rsc_removed)) {
print_full = TRUE;
}
// Everything else in this block is for anonymous clones
} else if (pcmk_is_set(show_opts, pcmk_show_pending)
&& (child_rsc->pending_task != NULL)
&& strcmp(child_rsc->pending_task, "probe")) {
// Print individual instance when non-probe action is pending
print_full = TRUE;
} else if (partially_active == FALSE) {
// List stopped instances when requested (except orphans)
if (!pcmk_is_set(child_rsc->flags, pcmk_rsc_removed)
&& !pcmk_is_set(show_opts, pcmk_show_clone_detail)
&& pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
if (stopped == NULL) {
stopped = pcmk__strkey_table(free, free);
}
pcmk__insert_dup(stopped, child_rsc->id, "Stopped");
}
} else if (is_set_recursive(child_rsc, pcmk_rsc_removed, TRUE)
|| !is_set_recursive(child_rsc, pcmk_rsc_managed, FALSE)
|| is_set_recursive(child_rsc, pcmk_rsc_failed, TRUE)) {
// Print individual instance when active orphaned/unmanaged/failed
print_full = TRUE;
} else if (child_rsc->private->fns->active(child_rsc, TRUE)) {
// Instance of fully active anonymous clone
pcmk_node_t *location = NULL;
location = child_rsc->private->fns->location(child_rsc, NULL, TRUE);
if (location) {
// Instance is active on a single node
enum rsc_role_e a_role;
a_role = child_rsc->private->fns->state(child_rsc, TRUE);
if (location->details->online == FALSE && location->details->unclean) {
print_full = TRUE;
} else if (a_role > pcmk_role_unpromoted) {
promoted_list = g_list_append(promoted_list, location);
} else {
started_list = g_list_append(started_list, location);
}
} else {
/* uncolocated group - bleh */
print_full = TRUE;
}
} else {
// Instance of partially active anonymous clone
print_full = TRUE;
}
if (print_full) {
GList *all = NULL;
clone_header(out, &rc, rsc, clone_data, desc);
/* Print every resource that's a child of this clone. */
all = g_list_prepend(all, (gpointer) "*");
out->message(out, (const char *) child_rsc->private->xml->name,
show_opts, child_rsc, only_node, all);
g_list_free(all);
}
}
if (pcmk_is_set(show_opts, pcmk_show_clone_detail)) {
PCMK__OUTPUT_LIST_FOOTER(out, rc);
return pcmk_rc_ok;
}
/* Promoted */
promoted_list = g_list_sort(promoted_list, pe__cmp_node_name);
for (gIter = promoted_list; gIter; gIter = gIter->next) {
pcmk_node_t *host = gIter->data;
if (!pcmk__str_in_list(host->details->uname, only_node,
pcmk__str_star_matches|pcmk__str_casei)) {
continue;
}
pcmk__add_word(&list_text, 1024, host->details->uname);
active_instances++;
}
g_list_free(promoted_list);
if ((list_text != NULL) && (list_text->len > 0)) {
clone_header(out, &rc, rsc, clone_data, desc);
out->list_item(out, NULL, PROMOTED_INSTANCES ": [ %s ]",
(const char *) list_text->str);
g_string_truncate(list_text, 0);
}
/* Started/Unpromoted */
started_list = g_list_sort(started_list, pe__cmp_node_name);
for (gIter = started_list; gIter; gIter = gIter->next) {
pcmk_node_t *host = gIter->data;
if (!pcmk__str_in_list(host->details->uname, only_node,
pcmk__str_star_matches|pcmk__str_casei)) {
continue;
}
pcmk__add_word(&list_text, 1024, host->details->uname);
active_instances++;
}
g_list_free(started_list);
if ((list_text != NULL) && (list_text->len > 0)) {
clone_header(out, &rc, rsc, clone_data, desc);
if (pcmk_is_set(rsc->flags, pcmk_rsc_promotable)) {
enum rsc_role_e role = configured_role(rsc);
if (role == pcmk_role_unpromoted) {
out->list_item(out, NULL,
UNPROMOTED_INSTANCES
" (" PCMK_META_TARGET_ROLE "): [ %s ]",
(const char *) list_text->str);
} else {
out->list_item(out, NULL, UNPROMOTED_INSTANCES ": [ %s ]",
(const char *) list_text->str);
}
} else {
out->list_item(out, NULL, "Started: [ %s ]",
(const char *) list_text->str);
}
}
if (list_text != NULL) {
g_string_free(list_text, TRUE);
}
if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
if (!pcmk_is_set(rsc->flags, pcmk_rsc_unique)
&& (clone_data->clone_max > active_instances)) {
GList *nIter;
GList *list = g_hash_table_get_values(rsc->allowed_nodes);
/* Custom stopped table for non-unique clones */
if (stopped != NULL) {
g_hash_table_destroy(stopped);
stopped = NULL;
}
if (list == NULL) {
/* Clusters with PCMK_OPT_SYMMETRIC_CLUSTER=false haven't
* calculated allowed_nodes yet. If we've not probed for them
* yet, the Stopped list will be empty.
*/
list = g_hash_table_get_values(rsc->known_on);
}
list = g_list_sort(list, pe__cmp_node_name);
for (nIter = list; nIter != NULL; nIter = nIter->next) {
pcmk_node_t *node = (pcmk_node_t *) nIter->data;
if ((pcmk__find_node_in_list(rsc->running_on,
node->details->uname) == NULL)
&& pcmk__str_in_list(node->details->uname, only_node,
pcmk__str_star_matches|pcmk__str_casei)) {
xmlNode *probe_op = pe__failed_probe_for_rsc(rsc, node->details->uname);
const char *state = "Stopped";
if (configured_role(rsc) == pcmk_role_stopped) {
state = "Stopped (disabled)";
}
if (stopped == NULL) {
stopped = pcmk__strkey_table(free, free);
}
if (probe_op != NULL) {
int rc;
pcmk__scan_min_int(crm_element_value(probe_op,
PCMK__XA_RC_CODE),
&rc, 0);
g_hash_table_insert(stopped, strdup(node->details->uname),
crm_strdup_printf("Stopped (%s)", services_ocf_exitcode_str(rc)));
} else {
pcmk__insert_dup(stopped, node->details->uname, state);
}
}
}
g_list_free(list);
}
if (stopped != NULL) {
GList *list = sorted_hash_table_values(stopped);
clone_header(out, &rc, rsc, clone_data, desc);
for (GList *status_iter = list; status_iter != NULL; status_iter = status_iter->next) {
const char *status = status_iter->data;
GList *nodes = nodes_with_status(stopped, status);
GString *nodes_str = node_list_to_str(nodes);
if (nodes_str != NULL) {
if (nodes_str->len > 0) {
out->list_item(out, NULL, "%s: [ %s ]", status,
(const char *) nodes_str->str);
}
g_string_free(nodes_str, TRUE);
}
g_list_free(nodes);
}
g_list_free(list);
g_hash_table_destroy(stopped);
/* If there are no instances of this clone (perhaps because there are no
* nodes configured), simply output the clone header by itself. This can
* come up in PCS testing.
*/
} else if (active_instances == 0) {
clone_header(out, &rc, rsc, clone_data, desc);
PCMK__OUTPUT_LIST_FOOTER(out, rc);
return rc;
}
}
PCMK__OUTPUT_LIST_FOOTER(out, rc);
return rc;
}
void
clone_free(pcmk_resource_t * rsc)
{
clone_variant_data_t *clone_data = NULL;
get_clone_variant_data(clone_data, rsc);
pcmk__rsc_trace(rsc, "Freeing %s", rsc->id);
for (GList *gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
CRM_ASSERT(child_rsc);
pcmk__rsc_trace(child_rsc, "Freeing child %s", child_rsc->id);
pcmk__xml_free(child_rsc->private->xml);
child_rsc->private->xml = NULL;
/* There could be a saved unexpanded xml */
- pcmk__xml_free(child_rsc->orig_xml);
- child_rsc->orig_xml = NULL;
+ pcmk__xml_free(child_rsc->private->orig_xml);
+ child_rsc->private->orig_xml = NULL;
child_rsc->private->fns->free(child_rsc);
}
g_list_free(rsc->children);
if (clone_data) {
CRM_ASSERT(clone_data->demote_notify == NULL);
CRM_ASSERT(clone_data->stop_notify == NULL);
CRM_ASSERT(clone_data->start_notify == NULL);
CRM_ASSERT(clone_data->promote_notify == NULL);
}
common_free(rsc);
}
enum rsc_role_e
clone_resource_state(const pcmk_resource_t * rsc, gboolean current)
{
enum rsc_role_e clone_role = pcmk_role_unknown;
GList *gIter = rsc->children;
for (; gIter != NULL; gIter = gIter->next) {
pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
enum rsc_role_e a_role = child_rsc->private->fns->state(child_rsc,
current);
if (a_role > clone_role) {
clone_role = a_role;
}
}
pcmk__rsc_trace(rsc, "%s role: %s", rsc->id, pcmk_role_text(clone_role));
return clone_role;
}
/*!
* \internal
* \brief Check whether a clone has an instance for every node
*
* \param[in] rsc Clone to check
* \param[in] scheduler Scheduler data
*/
bool
pe__is_universal_clone(const pcmk_resource_t *rsc,
const pcmk_scheduler_t *scheduler)
{
if (pcmk__is_clone(rsc)) {
clone_variant_data_t *clone_data = rsc->variant_opaque;
if (clone_data->clone_max == g_list_length(scheduler->nodes)) {
return TRUE;
}
}
return FALSE;
}
gboolean
pe__clone_is_filtered(const pcmk_resource_t *rsc, GList *only_rsc,
gboolean check_parent)
{
gboolean passes = FALSE;
clone_variant_data_t *clone_data = NULL;
if (pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches)) {
passes = TRUE;
} else {
get_clone_variant_data(clone_data, rsc);
passes = pcmk__str_in_list(pcmk__xe_id(clone_data->xml_obj_child),
only_rsc, pcmk__str_star_matches);
if (!passes) {
for (const GList *iter = rsc->children;
iter != NULL; iter = iter->next) {
const pcmk_resource_t *child_rsc = NULL;
child_rsc = (const pcmk_resource_t *) iter->data;
if (!child_rsc->private->fns->is_filtered(child_rsc, only_rsc,
FALSE)) {
passes = TRUE;
break;
}
}
}
}
return !passes;
}
const char *
pe__clone_child_id(const pcmk_resource_t *rsc)
{
clone_variant_data_t *clone_data = NULL;
get_clone_variant_data(clone_data, rsc);
return pcmk__xe_id(clone_data->xml_obj_child);
}
/*!
* \internal
* \brief Check whether a clone is ordered
*
* \param[in] clone Clone resource to check
*
* \return true if clone is ordered, otherwise false
*/
bool
pe__clone_is_ordered(const pcmk_resource_t *clone)
{
clone_variant_data_t *clone_data = NULL;
get_clone_variant_data(clone_data, clone);
return pcmk_is_set(clone_data->flags, pcmk__clone_ordered);
}
/*!
* \internal
* \brief Set a clone flag
*
* \param[in,out] clone Clone resource to set flag for
* \param[in] flag Clone flag to set
*
* \return Standard Pacemaker return code (either pcmk_rc_ok if flag was not
* already set or pcmk_rc_already if it was)
*/
int
pe__set_clone_flag(pcmk_resource_t *clone, enum pcmk__clone_flags flag)
{
clone_variant_data_t *clone_data = NULL;
get_clone_variant_data(clone_data, clone);
if (pcmk_is_set(clone_data->flags, flag)) {
return pcmk_rc_already;
}
clone_data->flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE,
"Clone", clone->id,
clone_data->flags, flag, "flag");
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Check whether a clone flag is set
*
* \param[in] group Clone resource to check
* \param[in] flags Flag or flags to check
*
* \return \c true if all \p flags are set for \p clone, otherwise \c false
*/
bool
pe__clone_flag_is_set(const pcmk_resource_t *clone, uint32_t flags)
{
clone_variant_data_t *clone_data = NULL;
get_clone_variant_data(clone_data, clone);
CRM_ASSERT(clone_data != NULL);
return pcmk_all_flags_set(clone_data->flags, flags);
}
/*!
* \internal
* \brief Create pseudo-actions needed for promotable clones
*
* \param[in,out] clone Promotable clone to create actions for
* \param[in] any_promoting Whether any instances will be promoted
* \param[in] any_demoting Whether any instance will be demoted
*/
void
pe__create_promotable_pseudo_ops(pcmk_resource_t *clone, bool any_promoting,
bool any_demoting)
{
pcmk_action_t *action = NULL;
pcmk_action_t *action_complete = NULL;
clone_variant_data_t *clone_data = NULL;
get_clone_variant_data(clone_data, clone);
// Create a "promote" action for the clone itself
action = pe__new_rsc_pseudo_action(clone, PCMK_ACTION_PROMOTE,
!any_promoting, true);
// Create a "promoted" action for when all promotions are done
action_complete = pe__new_rsc_pseudo_action(clone, PCMK_ACTION_PROMOTED,
!any_promoting, true);
action_complete->priority = PCMK_SCORE_INFINITY;
// Create notification pseudo-actions for promotion
if (clone_data->promote_notify == NULL) {
clone_data->promote_notify = pe__action_notif_pseudo_ops(clone,
PCMK_ACTION_PROMOTE,
action,
action_complete);
}
// Create a "demote" action for the clone itself
action = pe__new_rsc_pseudo_action(clone, PCMK_ACTION_DEMOTE,
!any_demoting, true);
// Create a "demoted" action for when all demotions are done
action_complete = pe__new_rsc_pseudo_action(clone, PCMK_ACTION_DEMOTED,
!any_demoting, true);
action_complete->priority = PCMK_SCORE_INFINITY;
// Create notification pseudo-actions for demotion
if (clone_data->demote_notify == NULL) {
clone_data->demote_notify = pe__action_notif_pseudo_ops(clone,
PCMK_ACTION_DEMOTE,
action,
action_complete);
if (clone_data->promote_notify != NULL) {
order_actions(clone_data->stop_notify->post_done,
clone_data->promote_notify->pre, pcmk__ar_ordered);
order_actions(clone_data->start_notify->post_done,
clone_data->promote_notify->pre, pcmk__ar_ordered);
order_actions(clone_data->demote_notify->post_done,
clone_data->promote_notify->pre, pcmk__ar_ordered);
order_actions(clone_data->demote_notify->post_done,
clone_data->start_notify->pre, pcmk__ar_ordered);
order_actions(clone_data->demote_notify->post_done,
clone_data->stop_notify->pre, pcmk__ar_ordered);
}
}
}
/*!
* \internal
* \brief Create all notification data and actions for a clone
*
* \param[in,out] clone Clone to create notifications for
*/
void
pe__create_clone_notifications(pcmk_resource_t *clone)
{
clone_variant_data_t *clone_data = NULL;
get_clone_variant_data(clone_data, clone);
pe__create_action_notifications(clone, clone_data->start_notify);
pe__create_action_notifications(clone, clone_data->stop_notify);
pe__create_action_notifications(clone, clone_data->promote_notify);
pe__create_action_notifications(clone, clone_data->demote_notify);
}
/*!
* \internal
* \brief Free all notification data for a clone
*
* \param[in,out] clone Clone to free notification data for
*/
void
pe__free_clone_notification_data(pcmk_resource_t *clone)
{
clone_variant_data_t *clone_data = NULL;
get_clone_variant_data(clone_data, clone);
pe__free_action_notification_data(clone_data->demote_notify);
clone_data->demote_notify = NULL;
pe__free_action_notification_data(clone_data->stop_notify);
clone_data->stop_notify = NULL;
pe__free_action_notification_data(clone_data->start_notify);
clone_data->start_notify = NULL;
pe__free_action_notification_data(clone_data->promote_notify);
clone_data->promote_notify = NULL;
}
/*!
* \internal
* \brief Create pseudo-actions for clone start/stop notifications
*
* \param[in,out] clone Clone to create pseudo-actions for
* \param[in,out] start Start action for \p clone
* \param[in,out] stop Stop action for \p clone
* \param[in,out] started Started action for \p clone
* \param[in,out] stopped Stopped action for \p clone
*/
void
pe__create_clone_notif_pseudo_ops(pcmk_resource_t *clone,
pcmk_action_t *start, pcmk_action_t *started,
pcmk_action_t *stop, pcmk_action_t *stopped)
{
clone_variant_data_t *clone_data = NULL;
get_clone_variant_data(clone_data, clone);
if (clone_data->start_notify == NULL) {
clone_data->start_notify = pe__action_notif_pseudo_ops(clone,
PCMK_ACTION_START,
start, started);
}
if (clone_data->stop_notify == NULL) {
clone_data->stop_notify = pe__action_notif_pseudo_ops(clone,
PCMK_ACTION_STOP,
stop, stopped);
if ((clone_data->start_notify != NULL)
&& (clone_data->stop_notify != NULL)) {
order_actions(clone_data->stop_notify->post_done,
clone_data->start_notify->pre, pcmk__ar_ordered);
}
}
}
/*!
* \internal
* \brief Get maximum clone resource instances per node
*
* \param[in] rsc Clone resource to check
*
* \return Maximum number of \p rsc instances that can be active on one node
*/
unsigned int
pe__clone_max_per_node(const pcmk_resource_t *rsc)
{
const clone_variant_data_t *clone_data = NULL;
get_clone_variant_data(clone_data, rsc);
return clone_data->clone_node_max;
}
diff --git a/lib/pengine/complex.c b/lib/pengine/complex.c
index e839bd0757..65640d4a5c 100644
--- a/lib/pengine/complex.c
+++ b/lib/pengine/complex.c
@@ -1,1277 +1,1277 @@
/*
* 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/common/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);
static pcmk__rsc_methods_t resource_class_functions[] = {
{
native_unpack,
native_find_rsc,
native_parameter,
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_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_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__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, PCMK_XE_PRIMITIVE, pcmk__str_casei)) {
return pcmk_rsc_variant_primitive;
} else if (pcmk__str_eq(name, PCMK_XE_GROUP, pcmk__str_casei)) {
return pcmk_rsc_variant_group;
} else if (pcmk__str_eq(name, PCMK_XE_CLONE, 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, PCMK_XE_BUNDLE, pcmk__str_casei)) {
return pcmk_rsc_variant_bundle;
}
return pcmk_rsc_variant_unknown;
}
/*!
* \internal
* \brief Insert a meta-attribute if not already present
*
* \param[in] key Meta-attribute name
* \param[in] value Meta-attribute value to add if not already present
* \param[in,out] table Meta-attribute hash table to insert into
*
* \note This is like pcmk__insert_meta() except it won't overwrite existing
* values.
*/
static void
dup_attr(gpointer key, gpointer value, gpointer user_data)
{
GHashTable *table = user_data;
CRM_CHECK((key != NULL) && (table != NULL), return);
if (pcmk__str_eq((const char *) value, "#default", pcmk__str_casei)) {
// @COMPAT Deprecated since 2.1.8
pcmk__config_warn("Support for setting meta-attributes (such as %s) to "
"the explicit value '#default' is deprecated and "
"will be removed in a future release",
(const char *) key);
} else if ((value != NULL) && (g_hash_table_lookup(table, key) == NULL)) {
pcmk__insert_dup(table, (const char *) key, (const char *) 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
* PCMK_XE_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->private->xml, PCMK_XE_META_ATTRIBUTES,
rule_data, parent_orig_meta, NULL, FALSE,
scheduler);
p = p->parent;
}
if (parent_orig_meta != NULL) {
// This will not overwrite any values already existing for child
g_hash_table_foreach(parent_orig_meta, dup_attr, 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->private->xml, PCMK_XA_CLASS),
.provider = crm_element_value(rsc->private->xml, PCMK_XA_PROVIDER),
.agent = crm_element_value(rsc->private->xml, PCMK_XA_TYPE)
};
pe_rule_eval_data_t rule_data = {
.node_hash = NULL,
.now = scheduler->now,
.match_data = NULL,
.rsc_data = &rsc_rule_data,
.op_data = NULL
};
if (node) {
/* @COMPAT Support for node attribute expressions in rules for
* meta-attributes is deprecated. When we can break behavioral backward
* compatibility, drop this block.
*/
rule_data.node_hash = node->details->attrs;
}
for (xmlAttrPtr a = pcmk__xe_first_attr(rsc->private->xml);
a != NULL; a = a->next) {
if (a->children != NULL) {
dup_attr((gpointer) a->name, (gpointer) a->children->content,
meta_hash);
}
}
pe__unpack_dataset_nvpairs(rsc->private->xml, PCMK_XE_META_ATTRIBUTES,
&rule_data, meta_hash, NULL, FALSE, scheduler);
/* Set the PCMK_XE_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, PCMK_XE_META_ATTRIBUTES,
&rule_data, meta_hash, NULL, FALSE, scheduler);
/* If there is PCMK_XE_META_ATTRIBUTES that the parent resource has not
* explicitly set, set a value that is not set from PCMK_XE_RSC_DEFAULTS
* 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,
.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->private->xml, PCMK_XE_INSTANCE_ATTRIBUTES,
&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 {
if (pcmk__xe_first_child(scheduler->rsc_defaults,
PCMK_XE_INSTANCE_ATTRIBUTES, NULL,
NULL) != NULL) {
/* Not possible with schema validation enabled
*
* @COMPAT Drop support when we can break behavioral
* backward compatibility
*/
pcmk__warn_once(pcmk__wo_instance_defaults,
"Support for " PCMK_XE_INSTANCE_ATTRIBUTES " in "
PCMK_XE_RSC_DEFAULTS " is deprecated and will be "
"removed in a future release");
}
/* and finally check the defaults */
pe__unpack_dataset_nvpairs(scheduler->rsc_defaults,
PCMK_XE_INSTANCE_ATTRIBUTES, &rule_data,
meta_hash, NULL, FALSE, scheduler);
}
}
static char *
template_op_key(xmlNode * op)
{
const char *name = crm_element_value(op, PCMK_XA_NAME);
const char *role = crm_element_value(op, PCMK_XA_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, PCMK_XA_TEMPLATE);
if (template_ref == NULL) {
return TRUE;
}
id = pcmk__xe_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("//" PCMK_XE_RESOURCES, scheduler->input,
LOG_TRACE);
if (cib_resources == NULL) {
pcmk__config_err("No resources configured");
return FALSE;
}
template = pcmk__xe_first_child(cib_resources, PCMK_XE_TEMPLATE,
PCMK_XA_ID, template_ref);
if (template == NULL) {
pcmk__config_err("No template named '%s'", template_ref);
return FALSE;
}
new_xml = pcmk__xml_copy(NULL, template);
xmlNodeSetName(new_xml, xml_obj->name);
crm_xml_add(new_xml, PCMK_XA_ID, id);
crm_xml_add(new_xml, PCMK__META_CLONE,
crm_element_value(xml_obj, PCMK__META_CLONE));
template_ops = pcmk__xe_first_child(new_xml, PCMK_XE_OPERATIONS, NULL,
NULL);
for (child_xml = pcmk__xe_first_child(xml_obj, NULL, NULL, NULL);
child_xml != NULL; child_xml = pcmk__xe_next(child_xml)) {
xmlNode *new_child = pcmk__xml_copy(new_xml, child_xml);
if (pcmk__xe_is(new_child, PCMK_XE_OPERATIONS)) {
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, NULL, NULL, NULL); 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, NULL, NULL, NULL);
op != NULL; op = pcmk__xe_next(op)) {
char *key = template_op_key(op);
if (g_hash_table_lookup(rsc_ops_hash, key) == NULL) {
pcmk__xml_copy(rsc_ops, op);
}
free(key);
}
if (rsc_ops_hash) {
g_hash_table_destroy(rsc_ops_hash);
}
pcmk__xml_free(template_ops);
}
/*pcmk__xml_free(*expanded_xml); */
*expanded_xml = new_xml;
#if 0 /* Disable multi-level templates for now */
if (!unpack_template(new_xml, expanded_xml, scheduler)) {
pcmk__xml_free(*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, PCMK_XA_TEMPLATE);
if (template_ref == NULL) {
return TRUE;
}
id = pcmk__xe_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->private->xml, PCMK__XE_PROMOTABLE_LEGACY)) {
pcmk__warn_once(pcmk__wo_master_element,
"Support for <" PCMK__XE_PROMOTABLE_LEGACY "> (such "
"as in %s) is deprecated and will be removed in a "
"future release. Use <" PCMK_XE_CLONE "> with a "
PCMK_META_PROMOTABLE " meta-attribute instead.",
rsc->id);
pcmk__insert_dup(rsc->meta, PCMK_META_PROMOTABLE, PCMK_VALUE_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 \c PCMK_META_REQUIRES meta-attribute
*
* \param[in,out] rsc Resource being unpacked
* \param[in] value Value of \c PCMK_META_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)) {
pcmk__set_rsc_flags(rsc, pcmk_rsc_needs_quorum);
} else if (pcmk__str_eq(value, PCMK_VALUE_FENCING, pcmk__str_casei)) {
pcmk__set_rsc_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 {
pcmk__set_rsc_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 (pcmk__is_primitive(rsc)
&& xml_contains_remote_node(rsc->private->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)" : ""));
}
static void
warn_about_deprecated_classes(pcmk_resource_t *rsc)
{
const char *std = crm_element_value(rsc->private->xml, PCMK_XA_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);
}
}
/*!
* \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;
pcmk__resource_private_t *rsc_private = NULL;
pe_rule_eval_data_t rule_data = {
.node_hash = NULL,
.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)->private = calloc(1, sizeof(pcmk__resource_private_t));
if ((*rsc)->private == NULL) {
pcmk__sched_err("Unable to allocate memory for resource '%s'", id);
free(*rsc);
return ENOMEM;
}
rsc_private = (*rsc)->private;
(*rsc)->cluster = scheduler;
if (expanded_xml) {
crm_log_xml_trace(expanded_xml, "[expanded XML]");
rsc_private->xml = expanded_xml;
- (*rsc)->orig_xml = xml_obj;
+ rsc_private->orig_xml = xml_obj;
} else {
rsc_private->xml = xml_obj;
- (*rsc)->orig_xml = NULL;
+ rsc_private->orig_xml = NULL;
}
/* Do not use xml_obj from here on, use (*rsc)->xml in case templates are involved */
(*rsc)->parent = parent;
ops = pcmk__xe_first_child(rsc_private->xml, PCMK_XE_OPERATIONS, NULL,
NULL);
(*rsc)->ops_xml = pcmk__xe_resolve_idref(ops, scheduler->input);
(*rsc)->variant = get_resource_type((const char *) rsc_private->xml->name);
if ((*rsc)->variant == pcmk_rsc_variant_unknown) {
pcmk__config_err("Ignoring resource '%s' of unknown type '%s'",
id, rsc_private->xml->name);
common_free(*rsc);
*rsc = NULL;
return pcmk_rc_unpack_error;
}
(*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_private->xml, PCMK__META_CLONE);
if (value) {
(*rsc)->id = crm_strdup_printf("%s:%s", id, value);
pcmk__insert_meta(*rsc, PCMK__META_CLONE, value);
} else {
(*rsc)->id = strdup(id);
}
warn_about_deprecated_classes(*rsc);
rsc_private->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;
pcmk__set_rsc_flags(*rsc, pcmk_rsc_runnable|pcmk_rsc_unassigned);
if (!pcmk_is_set(scheduler->flags, pcmk_sched_in_maintenance)) {
pcmk__set_rsc_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 = PCMK_SCORE_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)) {
pcmk__set_rsc_flags(*rsc, pcmk_rsc_critical);
}
value = g_hash_table_lookup((*rsc)->meta, PCMK_META_NOTIFY);
if (crm_is_true(value)) {
pcmk__set_rsc_flags(*rsc, pcmk_rsc_notify);
}
if (xml_contains_remote_node(rsc_private->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)) {
pcmk__set_rsc_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
* PCMK_META_ALLOW_MIGRATE=false.
*/
pcmk__set_rsc_flags(*rsc, pcmk_rsc_migratable);
}
value = g_hash_table_lookup((*rsc)->meta, PCMK_META_IS_MANAGED);
if (value != NULL) {
if (pcmk__str_eq(PCMK_VALUE_DEFAULT, value, pcmk__str_casei)) {
// @COMPAT Deprecated since 2.1.8
pcmk__config_warn("Support for setting " PCMK_META_IS_MANAGED
" to the explicit value '" PCMK_VALUE_DEFAULT
"' is deprecated and will be removed in a "
"future release (just leave it unset)");
} else if (crm_is_true(value)) {
pcmk__set_rsc_flags(*rsc, pcmk_rsc_managed);
} else {
pcmk__clear_rsc_flags(*rsc, pcmk_rsc_managed);
}
}
value = g_hash_table_lookup((*rsc)->meta, PCMK_META_MAINTENANCE);
if (crm_is_true(value)) {
pcmk__clear_rsc_flags(*rsc, pcmk_rsc_managed);
pcmk__set_rsc_flags(*rsc, pcmk_rsc_maintenance);
}
if (pcmk_is_set(scheduler->flags, pcmk_sched_in_maintenance)) {
pcmk__clear_rsc_flags(*rsc, pcmk_rsc_managed);
pcmk__set_rsc_flags(*rsc, pcmk_rsc_maintenance);
}
if (pcmk__is_clone(pe__const_top_resource(*rsc, false))) {
value = g_hash_table_lookup((*rsc)->meta, PCMK_META_GLOBALLY_UNIQUE);
if (crm_is_true(value)) {
pcmk__set_rsc_flags(*rsc, pcmk_rsc_unique);
}
if (detect_promotable(*rsc)) {
pcmk__set_rsc_flags(*rsc, pcmk_rsc_promotable);
}
} else {
pcmk__set_rsc_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, PCMK_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, PCMK_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, PCMK_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, PCMK_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 { // PCMK_VALUE_STOP_START
if (!pcmk__str_eq(value, PCMK_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 "
"\"" PCMK_VALUE_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) {
if (pcmk__str_eq(PCMK_VALUE_DEFAULT, value, pcmk__str_casei)) {
// @COMPAT Deprecated since 2.1.8
pcmk__config_warn("Support for setting "
PCMK_META_RESOURCE_STICKINESS
" to the explicit value '" PCMK_VALUE_DEFAULT
"' is deprecated and will be removed in a "
"future release (just leave it unset)");
} else {
(*rsc)->stickiness = char2score(value);
}
}
value = g_hash_table_lookup((*rsc)->meta, PCMK_META_MIGRATION_THRESHOLD);
if (value != NULL) {
if (pcmk__str_eq(PCMK_VALUE_DEFAULT, value, pcmk__str_casei)) {
// @COMPAT Deprecated since 2.1.8
pcmk__config_warn("Support for setting "
PCMK_META_MIGRATION_THRESHOLD
" to the explicit value '" PCMK_VALUE_DEFAULT
"' is deprecated and will be removed in a "
"future release (just leave it unset)");
} else {
(*rsc)->migration_threshold = char2score(value);
if ((*rsc)->migration_threshold < 0) {
/* @COMPAT 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_private->xml, PCMK_XA_CLASS),
PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
pcmk__set_scheduler_flags(scheduler, pcmk_sched_have_fencing);
pcmk__set_rsc_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
* PCMK_REMOTE_RA_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
* PCMK_REMOTE_RA_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)?
"default" : pcmk_role_text((*rsc)->next_role));
if (rsc_private->fns->unpack(*rsc, scheduler) == FALSE) {
rsc_private->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_private->xml, PCMK_XE_UTILIZATION,
&rule_data, (*rsc)->utilization, NULL, FALSE,
scheduler);
if (expanded_xml) {
if (add_template_rsc(xml_obj, scheduler) == FALSE) {
rsc_private->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) && !pcmk__is_bundle(parent->parent)) {
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 && pcmk__is_bundle(parent->parent)) {
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)) {
pcmk__xml_free(rsc->private->xml);
rsc->private->xml = NULL;
- pcmk__xml_free(rsc->orig_xml);
- rsc->orig_xml = NULL;
+ pcmk__xml_free(rsc->private->orig_xml);
+ rsc->private->orig_xml = NULL;
- } else if (rsc->orig_xml) {
+ } else if (rsc->private->orig_xml != NULL) {
// rsc->private->xml was expanded from a template
pcmk__xml_free(rsc->private->xml);
rsc->private->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);
free(rsc->id);
free(rsc->allocated_to);
free(rsc->variant_opaque);
free(rsc->pending_task);
free(rsc->private->history_id);
free(rsc->private);
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 (pcmk__same_node(node, rsc->partial_migration_source)) {
*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 \c PCMK_META_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
* \c PCMK_META_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;
}
if (pcmk_is_set(rsc->flags, pcmk_rsc_needs_fencing)) {
return rsc->private->fns->active_node(rsc, count, NULL);
} else {
return rsc->private->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 *child = item->data;
child->private->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, pcmk_role_text(rsc->next_role),
pcmk_role_text(role), why);
rsc->next_role = role;
}
}
diff --git a/lib/pengine/pe_output.c b/lib/pengine/pe_output.c
index 1075d70ebb..070f440008 100644
--- a/lib/pengine/pe_output.c
+++ b/lib/pengine/pe_output.c
@@ -1,3440 +1,3441 @@
/*
* 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/common/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->private->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, \
PCMK__NODE_ATTR_SHUTDOWN, PCMK_NODE_ATTR_TERMINATE, \
PCMK_NODE_ATTR_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, PCMK_XA_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, PCMK_XA_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, NULL, NULL, NULL);
rsc_op != NULL; rsc_op = pcmk__xe_next(rsc_op)) {
const char *task = crm_element_value(rsc_op, PCMK_XA_OPERATION);
const char *interval_ms_s = crm_element_value(rsc_op,
PCMK_META_INTERVAL);
const char *op_rc = crm_element_value(rsc_op, PCMK__XA_RC_CODE);
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__xe_is(rsc_op, PCMK__XE_LRM_RSC_OP)) {
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;
node = pcmk__xe_create(node, (const char *) key);
pcmk__xe_set_content(node, "%s", (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 "//" PCMK_XE_NVPAIR \
"[@" PCMK_XA_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);
if (stack != NULL) {
return crm_element_value(stack, PCMK_XA_VALUE);
}
return PCMK_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, PCMK__XA_CALL_ID);
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(PCMK_XA_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, PCMK_XA_LAST_RC_CHANGE,
&epoch) == pcmk_ok)
&& (epoch > 0)) {
char *epoch_str = pcmk__epoch2str(&epoch, 0);
last_change_str = crm_strdup_printf(" %s=\"%s\"",
PCMK_XA_LAST_RC_CHANGE,
pcmk__s(epoch_str, ""));
free(epoch_str);
}
value = crm_element_value(xml_op, PCMK_XA_EXEC_TIME);
if (value) {
char *pair = pcmk__format_nvpair(PCMK_XA_EXEC_TIME, value, "ms");
exec_str = crm_strdup_printf(" %s", pair);
free(pair);
}
value = crm_element_value(xml_op, PCMK_XA_QUEUE_TIME);
if (value) {
char *pair = pcmk__format_nvpair(PCMK_XA_QUEUE_TIME, 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_XA_FAIL_COUNT, failcount);
} else {
failcount_s = strdup("");
}
if (last_failure > 0) {
buf = pcmk__epoch2str(&last_failure, 0);
lastfail_s = crm_strdup_printf(" %s='%s'",
PCMK_XA_LAST_FAILURE, buf);
free(buf);
}
buf = crm_strdup_printf("%s: " PCMK_META_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
&& !pcmk__is_pacemaker_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 void
formatted_xml_buf(const pcmk_resource_t *rsc, GString *xml_buf, bool raw)
{
- if (raw && (rsc->orig_xml != NULL)) {
- pcmk__xml_string(rsc->orig_xml, pcmk__xml_fmt_pretty, xml_buf, 0);
+ if (raw && (rsc->private->orig_xml != NULL)) {
+ pcmk__xml_string(rsc->private->orig_xml, pcmk__xml_fmt_pretty, xml_buf,
+ 0);
} else {
pcmk__xml_string(rsc->private->xml, pcmk__xml_fmt_pretty, xml_buf, 0);
}
}
#define XPATH_DC_VERSION "//" PCMK_XE_NVPAIR \
"[@" PCMK_XA_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, PCMK_XA_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,
PCMK_XA_UPDATE_USER);
const char *client = crm_element_value(scheduler->input,
PCMK_XA_UPDATE_CLIENT);
const char *origin = crm_element_value(scheduler->input,
PCMK_XA_UPDATE_ORIGIN);
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, PCMK_XA_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,
PCMK_XA_UPDATE_USER);
const char *client = crm_element_value(scheduler->input,
PCMK_XA_UPDATE_CLIENT);
const char *origin = crm_element_value(scheduler->input,
PCMK_XA_UPDATE_ORIGIN);
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 && pcmk__is_guest_or_bundle_node(node)) {
const pcmk_resource_t *container = node->details->remote_rsc->container;
const pcmk_node_t *host_node = pcmk__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 = pcmk__assert_alloc(name_len, sizeof(char));
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,
...)
{
xmlNodePtr xml_node = NULL;
va_list pairs;
CRM_ASSERT(tag_name != NULL);
xml_node = pcmk__output_xml_peek_parent(out);
CRM_ASSERT(xml_node != NULL);
xml_node = pcmk__xe_create(xml_node, tag_name);
va_start(pairs, tag_name);
pcmk__xe_set_propv(xml_node, pairs);
va_end(pairs);
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, PCMK_XE_BAN,
PCMK_XA_ID, location->id,
PCMK_XA_RESOURCE, location->rsc->id,
PCMK_XA_NODE, pe_node->details->uname,
PCMK_XA_WEIGHT, weight_s,
PCMK_XA_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.
*/
PCMK__XA_PROMOTED_ONLY_LEGACY, 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);
xmlNode *child = NULL;
child = pcmk__html_create(nodes_node, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child, "%d node%s configured",
nnodes, pcmk__plural_s(nnodes));
if (ndisabled && nblocked) {
child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child, "%d resource instance%s configured (%d ",
nresources, pcmk__plural_s(nresources), ndisabled);
child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL,
PCMK__VALUE_BOLD);
pcmk__xe_set_content(child, "DISABLED");
child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child, ", %d ", nblocked);
child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL,
PCMK__VALUE_BOLD);
pcmk__xe_set_content(child, "BLOCKED");
child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child, " from further action due to failure)");
} else if (ndisabled && !nblocked) {
child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child, "%d resource instance%s configured (%d ",
nresources, pcmk__plural_s(nresources),
ndisabled);
child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL,
PCMK__VALUE_BOLD);
pcmk__xe_set_content(child, "DISABLED");
child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child, ")");
} else if (!ndisabled && nblocked) {
child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child, "%d resource instance%s configured (%d ",
nresources, pcmk__plural_s(nresources),
nblocked);
child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL,
PCMK__VALUE_BOLD);
pcmk__xe_set_content(child, "BLOCKED");
child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child, " from further action due to failure)");
} else {
child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child, "%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_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 = NULL;
xmlNodePtr resources_node = NULL;
char *s = NULL;
nodes_node = pcmk__output_create_xml_node(out, PCMK_XE_NODES_CONFIGURED,
NULL);
resources_node = pcmk__output_create_xml_node(out,
PCMK_XE_RESOURCES_CONFIGURED,
NULL);
s = pcmk__itoa(nnodes);
crm_xml_add(nodes_node, PCMK_XA_NUMBER, s);
free(s);
s = pcmk__itoa(nresources);
crm_xml_add(resources_node, PCMK_XA_NUMBER, s);
free(s);
s = pcmk__itoa(ndisabled);
crm_xml_add(resources_node, PCMK_XA_DISABLED, s);
free(s);
s = pcmk__itoa(nblocked);
crm_xml_add(resources_node, PCMK_XA_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);
xmlNode *child = NULL;
child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD);
pcmk__xe_set_content(child, "Current DC: ");
if (dc) {
child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child, "%s (version %s) -",
dc_name, pcmk__s(dc_version_s, "unknown"));
if (mixed_version) {
child = pcmk__html_create(node, PCMK__XE_SPAN, NULL,
PCMK__VALUE_WARNING);
pcmk__xe_set_content(child, " MIXED-VERSION");
}
child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child, " partition");
if (crm_is_true(quorum)) {
child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child, " with");
} else {
child = pcmk__html_create(node, PCMK__XE_SPAN, NULL,
PCMK__VALUE_WARNING);
pcmk__xe_set_content(child, " WITHOUT");
}
child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child, " quorum");
} else {
child = pcmk__html_create(node, PCMK__XE_SPAN, NULL,
PCMK__VALUE_WARNING);
pcmk__xe_set_content(child, "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) {
const char *with_quorum = pcmk__btoa(crm_is_true(quorum));
const char *mixed_version_s = pcmk__btoa(mixed_version);
pcmk__output_create_xml_node(out, PCMK_XE_CURRENT_DC,
PCMK_XA_PRESENT, PCMK_VALUE_TRUE,
PCMK_XA_VERSION, pcmk__s(dc_version_s, ""),
PCMK_XA_NAME, dc->details->uname,
PCMK_XA_ID, dc->details->id,
PCMK_XA_WITH_QUORUM, with_quorum,
PCMK_XA_MIXED_VERSION, mixed_version_s,
NULL);
} else {
pcmk__output_create_xml_node(out, PCMK_XE_CURRENT_DC,
PCMK_XA_PRESENT, PCMK_VALUE_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);
xmlNode *child = NULL;
child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child, "Resource management: ");
child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD);
pcmk__xe_set_content(child, "DISABLED");
child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child,
" (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);
xmlNode *child = NULL;
child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child, "Resource management: ");
child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD);
pcmk__xe_set_content(child, "STOPPED");
child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child,
" (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;
}
/*!
* \internal
* \brief Get readable string representation of a no-quorum policy
*
* \param[in] policy No-quorum policy
*
* \return String representation of \p policy
*/
static const char *
no_quorum_policy_text(enum pe_quorum_policy policy)
{
switch (policy) {
case pcmk_no_quorum_freeze:
return PCMK_VALUE_FREEZE;
case pcmk_no_quorum_stop:
return PCMK_VALUE_STOP;
case pcmk_no_quorum_demote:
return PCMK_VALUE_DEMOTE;
case pcmk_no_quorum_ignore:
return PCMK_VALUE_IGNORE;
case pcmk_no_quorum_fence:
return PCMK_VALUE_FENCE_LEGACY;
default:
return PCMK_VALUE_UNKNOWN;
}
}
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 *stonith_enabled = pcmk__flag_text(scheduler->flags,
pcmk_sched_fencing_enabled);
const char *symmetric_cluster =
pcmk__flag_text(scheduler->flags, pcmk_sched_symmetric_cluster);
const char *no_quorum_policy =
no_quorum_policy_text(scheduler->no_quorum_policy);
const char *maintenance_mode = pcmk__flag_text(scheduler->flags,
pcmk_sched_in_maintenance);
const char *stop_all_resources = pcmk__flag_text(scheduler->flags,
pcmk_sched_stop_all);
char *stonith_timeout_ms_s = pcmk__itoa(scheduler->stonith_timeout);
char *priority_fencing_delay_ms_s =
pcmk__itoa(scheduler->priority_fencing_delay * 1000);
pcmk__output_create_xml_node(out, PCMK_XE_CLUSTER_OPTIONS,
PCMK_XA_STONITH_ENABLED, stonith_enabled,
PCMK_XA_SYMMETRIC_CLUSTER, symmetric_cluster,
PCMK_XA_NO_QUORUM_POLICY, no_quorum_policy,
PCMK_XA_MAINTENANCE_MODE, maintenance_mode,
PCMK_XA_STOP_ALL_RESOURCES, stop_all_resources,
PCMK_XA_STONITH_TIMEOUT_MS,
stonith_timeout_ms_s,
PCMK_XA_PRIORITY_FENCING_DELAY_MS,
priority_fencing_delay_ms_s,
NULL);
free(stonith_timeout_ms_s);
free(priority_fencing_delay_ms_s);
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);
xmlNode *child = NULL;
child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD);
pcmk__xe_set_content(child, "Stack: ");
child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child, "%s", stack_s);
if (pcmkd_state != pcmk_pacemakerd_state_invalid) {
child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child, " (");
child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child, "%s",
pcmk__pcmkd_state_enum2friendly(pcmkd_state));
child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child, ")");
}
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, PCMK_XE_STACK,
PCMK_XA_TYPE, stack_s,
PCMK_XA_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);
xmlNode *child = NULL;
char *time_s = NULL;
child = pcmk__html_create(updated_node, PCMK__XE_SPAN, NULL,
PCMK__VALUE_BOLD);
pcmk__xe_set_content(child, "Last updated: ");
child = pcmk__html_create(updated_node, PCMK__XE_SPAN, NULL, NULL);
time_s = pcmk__epoch2str(NULL, 0);
pcmk__xe_set_content(child, "%s", time_s);
free(time_s);
if (our_nodename != NULL) {
child = pcmk__html_create(updated_node, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child, " on ");
child = pcmk__html_create(updated_node, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child, "%s", our_nodename);
}
child = pcmk__html_create(changed_node, PCMK__XE_SPAN, NULL,
PCMK__VALUE_BOLD);
pcmk__xe_set_content(child, "Last change: ");
child = pcmk__html_create(changed_node, PCMK__XE_SPAN, NULL, NULL);
time_s = last_changed_string(last_written, user, client, origin);
pcmk__xe_set_content(child, "%s", 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, PCMK_XE_LAST_UPDATE,
PCMK_XA_TIME, time_s,
PCMK_XA_ORIGIN, our_nodename,
NULL);
pcmk__output_create_xml_node(out, PCMK_XE_LAST_CHANGE,
PCMK_XA_TIME, pcmk__s(last_written, ""),
PCMK_XA_USER, pcmk__s(user, ""),
PCMK_XA_CLIENT, pcmk__s(client, ""),
PCMK_XA_ORIGIN, pcmk__s(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)) {
pcmk__str_update(&rsc_id, "unknown resource");
pcmk__str_update(&task, "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, PCMK_XA_LAST_RC_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, PCMK__XA_CALL_ID);
const char *queue_time = crm_element_value(xml_op, PCMK_XA_QUEUE_TIME);
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, PCMK_XA_LAST_RC_CHANGE,
&last_change_epoch) == pcmk_ok) {
char *last_change_str = pcmk__epoch2str(&last_change_epoch, 0);
pcmk__g_strcat(str,
", " PCMK_XA_LAST_RC_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", "xmlNode *", "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 = pcmk__xe_history_key(xml_op);
const char *node_name = crm_element_value(xml_op, PCMK_XA_UNAME);
const char *exit_reason = crm_element_value(xml_op, PCMK_XA_EXIT_REASON);
const char *exec_time = crm_element_value(xml_op, PCMK_XA_EXEC_TIME);
int rc;
int status;
pcmk__scan_min_int(crm_element_value(xml_op, PCMK__XA_RC_CODE), &rc, 0);
pcmk__scan_min_int(crm_element_value(xml_op, PCMK__XA_OP_STATUS), &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", "xmlNode *", "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 = pcmk__xe_history_key(xml_op);
const char *op_key_name = PCMK_XA_OP_KEY;
int rc;
int status;
const char *uname = crm_element_value(xml_op, PCMK_XA_UNAME);
const char *call_id = crm_element_value(xml_op, PCMK__XA_CALL_ID);
const char *exitstatus = NULL;
const char *exit_reason = pcmk__s(crm_element_value(xml_op,
PCMK_XA_EXIT_REASON),
"none");
const char *status_s = NULL;
time_t epoch = 0;
gchar *exit_reason_esc = NULL;
char *rc_s = NULL;
xmlNodePtr node = NULL;
if (pcmk__xml_needs_escape(exit_reason, pcmk__xml_escape_attr)) {
exit_reason_esc = pcmk__xml_escape(exit_reason, pcmk__xml_escape_attr);
exit_reason = exit_reason_esc;
}
pcmk__scan_min_int(crm_element_value(xml_op, PCMK__XA_RC_CODE), &rc, 0);
pcmk__scan_min_int(crm_element_value(xml_op, PCMK__XA_OP_STATUS), &status,
0);
if (crm_element_value(xml_op, PCMK__XA_OPERATION_KEY) == NULL) {
op_key_name = PCMK_XA_ID;
}
exitstatus = services_ocf_exitcode_str(rc);
rc_s = pcmk__itoa(rc);
status_s = pcmk_exec_status_str(status);
node = pcmk__output_create_xml_node(out, PCMK_XE_FAILURE,
op_key_name, op_key,
PCMK_XA_NODE, uname,
PCMK_XA_EXITSTATUS, exitstatus,
PCMK_XA_EXITREASON, exit_reason,
PCMK_XA_EXITCODE, rc_s,
PCMK_XA_CALL, call_id,
PCMK_XA_STATUS, status_s,
NULL);
free(rc_s);
if ((crm_element_value_epoch(xml_op, PCMK_XA_LAST_RC_CHANGE,
&epoch) == pcmk_ok) && (epoch > 0)) {
const char *queue_time = crm_element_value(xml_op, PCMK_XA_QUEUE_TIME);
const char *exec = crm_element_value(xml_op, PCMK_XA_EXEC_TIME);
const char *task = crm_element_value(xml_op, PCMK_XA_OPERATION);
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, PCMK_META_INTERVAL, &interval_ms);
interval_ms_s = crm_strdup_printf("%u", interval_ms);
pcmk__xe_set_props(node,
PCMK_XA_LAST_RC_CHANGE, rc_change,
PCMK_XA_QUEUED, queue_time,
PCMK_XA_EXEC, exec,
PCMK_XA_INTERVAL, interval_ms_s,
PCMK_XA_TASK, task,
NULL);
free(interval_ms_s);
free(rc_change);
}
g_free(exit_reason_esc);
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__xe_first_child(scheduler->failed, NULL, NULL, NULL);
xml_op != NULL; xml_op = pcmk__xe_next(xml_op)) {
char *rsc = NULL;
if (!pcmk__str_in_list(crm_element_value(xml_op, PCMK_XA_UNAME),
only_node,
pcmk__str_star_matches|pcmk__str_casei)) {
continue;
}
if (pcmk_xe_mask_probe_failure(xml_op)) {
continue;
}
if (!parse_op_key(pcmk__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);
xmlNode *child = NULL;
// Cluster membership
if (node->details->online) {
child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL,
PCMK_VALUE_ONLINE);
pcmk__xe_set_content(child, " online");
} else {
child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL,
PCMK_VALUE_OFFLINE);
pcmk__xe_set_content(child, " OFFLINE");
}
// Standby mode
if (node->details->standby_onfail && (node->details->running_rsc != NULL)) {
child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL,
PCMK_VALUE_STANDBY);
pcmk__xe_set_content(child,
" (in standby due to " PCMK_META_ON_FAIL ","
" with active resources)");
} else if (node->details->standby_onfail) {
child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL,
PCMK_VALUE_STANDBY);
pcmk__xe_set_content(child,
" (in standby due to " PCMK_META_ON_FAIL ")");
} else if (node->details->standby && (node->details->running_rsc != NULL)) {
child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL,
PCMK_VALUE_STANDBY);
pcmk__xe_set_content(child,
" (in standby, with active resources)");
} else if (node->details->standby) {
child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL,
PCMK_VALUE_STANDBY);
pcmk__xe_set_content(child, " (in standby)");
}
// Maintenance mode
if (node->details->maintenance) {
child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL,
PCMK__VALUE_MAINT);
pcmk__xe_set_content(child, " (in maintenance mode)");
}
// Node health
if (health < 0) {
child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL,
PCMK__VALUE_HEALTH_RED);
pcmk__xe_set_content(child, " (health is RED)");
} else if (health == 0) {
child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL,
PCMK__VALUE_HEALTH_YELLOW);
pcmk__xe_set_content(child, " (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) {
child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child, ", feature set %s", feature_set);
}
}
}
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) {
xmlNode *item_node = NULL;
xmlNode *child = NULL;
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);
child = pcmk__html_create(item_node, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child, "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);
child = pcmk__html_create(item_node, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child, "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, pcmk__map_element_name(rsc->private->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 {
item_node = pcmk__output_create_xml_node(out, "li", NULL);
child = pcmk__html_create(item_node, PCMK__XE_SPAN, NULL,
PCMK__VALUE_BOLD);
pcmk__xe_set_content(child, "%s:", node_name);
status_node(node, item_node, show_opts);
}
} 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 (" PCMK_META_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 (pcmk__is_guest_or_bundle_node(node)) {
g_string_append(str, "GuestNode");
} else if (pcmk__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, pcmk__map_element_name(rsc->private->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;
}
/*!
* \internal
* \brief Convert an integer health value to a string representation
*
* \param[in] health Integer health value
*
* \retval \c PCMK_VALUE_RED if \p health is less than 0
* \retval \c PCMK_VALUE_YELLOW if \p health is equal to 0
* \retval \c PCMK_VALUE_GREEN if \p health is greater than 0
*/
static const char *
health_text(int health)
{
if (health < 0) {
return PCMK_VALUE_RED;
} else if (health == 0) {
return PCMK_VALUE_YELLOW;
} else {
return PCMK_VALUE_GREEN;
}
}
/*!
* \internal
* \brief Convert a node type to a string representation
*
* \param[in] type Node type
*
* \retval \c PCMK_VALUE_MEMBER if \p node_type is \c pcmk_node_variant_cluster
* \retval \c PCMK_VALUE_REMOTE if \p node_type is \c pcmk_node_variant_remote
* \retval \c PCMK__VALUE_PING if \p node_type is \c node_ping
* \retval \c PCMK_VALUE_UNKNOWN otherwise
*/
static const char *
node_type_str(enum node_type type)
{
switch (type) {
case pcmk_node_variant_cluster:
return PCMK_VALUE_MEMBER;
case pcmk_node_variant_remote:
return PCMK_VALUE_REMOTE;
case node_ping:
return PCMK__VALUE_PING;
default:
return PCMK_VALUE_UNKNOWN;
}
}
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 *online = pcmk__btoa(node->details->online);
const char *standby = pcmk__btoa(node->details->standby);
const char *standby_onfail = pcmk__btoa(node->details->standby_onfail);
const char *maintenance = pcmk__btoa(node->details->maintenance);
const char *pending = pcmk__btoa(node->details->pending);
const char *unclean = pcmk__btoa(node->details->unclean);
const char *health = health_text(pe__node_health(node));
const char *feature_set = get_node_feature_set(node);
const char *shutdown = pcmk__btoa(node->details->shutdown);
const char *expected_up = pcmk__btoa(node->details->expected_up);
const char *is_dc = pcmk__btoa(node->details->is_dc);
int length = g_list_length(node->details->running_rsc);
char *resources_running = pcmk__itoa(length);
const char *node_type = node_type_str(node->details->type);
int rc = pcmk_rc_ok;
rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_NODE,
PCMK_XA_NAME, node->details->uname,
PCMK_XA_ID, node->details->id,
PCMK_XA_ONLINE, online,
PCMK_XA_STANDBY, standby,
PCMK_XA_STANDBY_ONFAIL, standby_onfail,
PCMK_XA_MAINTENANCE, maintenance,
PCMK_XA_PENDING, pending,
PCMK_XA_UNCLEAN, unclean,
PCMK_XA_HEALTH, health,
PCMK_XA_FEATURE_SET, feature_set,
PCMK_XA_SHUTDOWN, shutdown,
PCMK_XA_EXPECTED_UP, expected_up,
PCMK_XA_IS_DC, is_dc,
PCMK_XA_RESOURCES_RUNNING, resources_running,
PCMK_XA_TYPE, node_type,
NULL);
free(resources_running);
CRM_ASSERT(rc == pcmk_rc_ok);
if (pcmk__is_guest_or_bundle_node(node)) {
xmlNodePtr xml_node = pcmk__output_xml_peek_parent(out);
crm_xml_add(xml_node, PCMK_XA_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, pcmk__map_element_name(rsc->private->xml),
show_opts, rsc, only_node, only_rsc);
}
}
out->end_list(out);
} else {
pcmk__output_xml_create_parent(out, PCMK_XE_NODE,
PCMK_XA_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 = 0;
xmlNodePtr item_node = pcmk__output_create_xml_node(out, "li", NULL);
xmlNode *child = NULL;
if (value != NULL) {
pcmk__scan_min_int(value, &v, INT_MIN);
}
child = pcmk__html_create(item_node, PCMK__XE_SPAN, NULL, NULL);
pcmk__xe_set_content(child, "%s: %s", name, value);
if (v <= 0) {
child = pcmk__html_create(item_node, PCMK__XE_SPAN, NULL,
PCMK__VALUE_BOLD);
pcmk__xe_set_content(child, "(connectivity is lost)");
} else if (v < expected_score) {
child = pcmk__html_create(item_node, PCMK__XE_SPAN, NULL,
PCMK__VALUE_BOLD);
pcmk__xe_set_content(child,
"(connectivity is degraded -- expected %d)",
expected_score);
}
} else {
out->list_item(out, NULL, "%s: %s", name, value);
}
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("node-and-op", "pcmk_scheduler_t *", "xmlNode *")
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, PCMK_XA_RESOURCE);
int status;
time_t last_change = 0;
pcmk__scan_min_int(crm_element_value(xml_op, PCMK__XA_OP_STATUS), &status,
PCMK_EXEC_UNKNOWN);
rsc = pe_find_resource(scheduler->resources, op_rsc);
if (rsc) {
const pcmk_node_t *node = pcmk__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, PCMK_XA_LAST_RC_CHANGE,
&last_change) == pcmk_ok) {
const char *exec_time = crm_element_value(xml_op, PCMK_XA_EXEC_TIME);
last_change_str = crm_strdup_printf(", %s='%s', exec=%sms",
PCMK_XA_LAST_RC_CHANGE,
pcmk__trim(ctime(&last_change)),
exec_time);
}
out->list_item(out, NULL, "%s: %s (node=%s, call=%s, rc=%s%s): %s",
node_str, pcmk__xe_history_key(xml_op),
crm_element_value(xml_op, PCMK_XA_UNAME),
crm_element_value(xml_op, PCMK__XA_CALL_ID),
crm_element_value(xml_op, PCMK__XA_RC_CODE),
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 *", "xmlNode *")
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 *uname = crm_element_value(xml_op, PCMK_XA_UNAME);
const char *call_id = crm_element_value(xml_op, PCMK__XA_CALL_ID);
const char *rc_s = crm_element_value(xml_op, PCMK__XA_RC_CODE);
const char *status_s = NULL;
const char *op_rsc = crm_element_value(xml_op, PCMK_XA_RESOURCE);
int status;
time_t last_change = 0;
xmlNode *node = NULL;
pcmk__scan_min_int(crm_element_value(xml_op, PCMK__XA_OP_STATUS),
&status, PCMK_EXEC_UNKNOWN);
status_s = pcmk_exec_status_str(status);
node = pcmk__output_create_xml_node(out, PCMK_XE_OPERATION,
PCMK_XA_OP, pcmk__xe_history_key(xml_op),
PCMK_XA_NODE, uname,
PCMK_XA_CALL, call_id,
PCMK_XA_RC, rc_s,
PCMK_XA_STATUS, status_s,
NULL);
rsc = pe_find_resource(scheduler->resources, op_rsc);
if (rsc) {
const char *class = crm_element_value(rsc->private->xml, PCMK_XA_CLASS);
const char *provider = crm_element_value(rsc->private->xml,
PCMK_XA_PROVIDER);
const char *kind = crm_element_value(rsc->private->xml, PCMK_XA_TYPE);
bool has_provider = pcmk_is_set(pcmk_get_ra_caps(class),
pcmk_ra_cap_provider);
char *agent_tuple = crm_strdup_printf("%s:%s:%s",
class,
(has_provider? provider : ""),
kind);
pcmk__xe_set_props(node,
PCMK_XA_RSC, rsc_printable_id(rsc),
PCMK_XA_AGENT, agent_tuple,
NULL);
free(agent_tuple);
}
if (crm_element_value_epoch(xml_op, PCMK_XA_LAST_RC_CHANGE,
&last_change) == pcmk_ok) {
const char *last_rc_change = pcmk__trim(ctime(&last_change));
const char *exec_time = crm_element_value(xml_op, PCMK_XA_EXEC_TIME);
pcmk__xe_set_props(node,
PCMK_XA_LAST_RC_CHANGE, last_rc_change,
PCMK_XA_EXEC_TIME, exec_time,
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, PCMK_XE_ATTRIBUTE,
PCMK_XA_NAME, name,
PCMK_XA_VALUE, value,
NULL);
if (add_extra) {
char *buf = pcmk__itoa(expected_score);
crm_xml_add(node, PCMK_XA_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 = pcmk__node_attr(node, name, NULL, pcmk__rsc_node_current);
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, pcmk__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 *uname = node->details->uname;
const char *comment = va_arg(args, const char *);
xmlNodePtr xml_node = pcmk__output_create_xml_node(out, PCMK_XE_CAPACITY,
PCMK_XA_NODE, uname,
PCMK_XA_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 *",
"xmlNode *", "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 = pcmk__xe_first_child(node_state, PCMK__XE_LRM, NULL, NULL);
lrm_rsc = pcmk__xe_first_child(lrm_rsc, PCMK__XE_LRM_RESOURCES, NULL, NULL);
/* Print history of each of the node's resources */
for (rsc_entry = pcmk__xe_first_child(lrm_rsc, PCMK__XE_LRM_RESOURCE, NULL,
NULL);
rsc_entry != NULL; rsc_entry = pcmk__xe_next_same(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 (pcmk__is_group(parent)) {
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->private->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 (pcmk__is_guest_or_bundle_node(node)) {
pcmk__add_word(&online_guest_nodes, 1024, node_name);
} else if (pcmk__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 (pcmk__is_remote_node(node)) {
pcmk__add_word(&offline_remote_nodes, 1024, node_name);
} else if (pcmk__is_guest_or_bundle_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);
/* PCMK_XE_NODES acts as the list's element name for CLI tools that use
* pcmk__output_enable_list_element. Otherwise PCMK_XE_NODES is the
* value of the list's PCMK_XA_NAME attribute.
*/
out->begin_list(out, NULL, NULL, PCMK_XE_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,
PCMK_XE_STATUS);
int rc = pcmk_rc_no_output;
if (xmlChildElementCount(cib_status) == 0) {
return rc;
}
for (node_state = pcmk__xe_first_child(cib_status, PCMK__XE_NODE_STATE,
NULL, NULL);
node_state != NULL; node_state = pcmk__xe_next_same(node_state)) {
pcmk_node_t *node = pe_find_node_id(scheduler->nodes,
pcmk__xe_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, PCMK_XE_NODE_WEIGHT,
PCMK_XA_FUNCTION, prefix,
PCMK_XA_NODE, uname,
PCMK_XA_SCORE, score,
NULL);
if (rsc) {
crm_xml_add(node, PCMK_XA_ID, rsc->id);
}
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("op-history", "xmlNode *", "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", "xmlNode *", "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);
const char *call_id = crm_element_value(xml_op, PCMK__XA_CALL_ID);
char *rc_s = pcmk__itoa(rc);
const char *rc_text = services_ocf_exitcode_str(rc);
xmlNodePtr node = NULL;
node = pcmk__output_create_xml_node(out, PCMK_XE_OPERATION_HISTORY,
PCMK_XA_CALL, call_id,
PCMK_XA_TASK, task,
PCMK_XA_RC, rc_s,
PCMK_XA_RC_TEXT, rc_text,
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, PCMK_XA_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, PCMK_XA_LAST_RC_CHANGE,
&epoch) == pcmk_ok) && (epoch > 0)) {
char *s = pcmk__epoch2str(&epoch, 0);
crm_xml_add(node, PCMK_XA_LAST_RC_CHANGE, s);
free(s);
}
value = crm_element_value(xml_op, PCMK_XA_EXEC_TIME);
if (value) {
char *s = crm_strdup_printf("%sms", value);
crm_xml_add(node, PCMK_XA_EXEC_TIME, s);
free(s);
}
value = crm_element_value(xml_op, PCMK_XA_QUEUE_TIME);
if (value) {
char *s = crm_strdup_printf("%sms", value);
crm_xml_add(node, PCMK_XA_QUEUE_TIME, 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, PCMK_XE_PROMOTION_SCORE,
PCMK_XA_ID, child_rsc->id,
PCMK_XA_SCORE, score,
NULL);
if (chosen) {
crm_xml_add(node, PCMK_XA_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 *);
GString *xml_buf = g_string_sized_new(1024);
bool raw = va_arg(args, int);
formatted_xml_buf(rsc, xml_buf, raw);
out->output_xml(out, PCMK_XE_XML, xml_buf->str);
g_string_free(xml_buf, TRUE);
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) {
pcmk__formatted_printf(out, "Resource XML:\n");
return resource_config(out, args);
}
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,
PCMK_XE_RESOURCE_HISTORY,
PCMK_XA_ID, rsc_id,
NULL);
if (rsc == NULL) {
pcmk__xe_set_bool_attr(node, PCMK_XA_ORPHAN, true);
} else if (all || failcount || last_failure > 0) {
char *migration_s = pcmk__itoa(rsc->migration_threshold);
pcmk__xe_set_props(node,
PCMK_XA_ORPHAN, PCMK_VALUE_FALSE,
PCMK_META_MIGRATION_THRESHOLD, migration_s,
NULL);
free(migration_s);
if (failcount > 0) {
char *s = pcmk__itoa(failcount);
crm_xml_add(node, PCMK_XA_FAIL_COUNT, s);
free(s);
}
if (last_failure > 0) {
char *s = pcmk__epoch2str(&last_failure, 0);
crm_xml_add(node, PCMK_XA_LAST_FAILURE, 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->private->fns->active(rsc, TRUE);
gboolean partially_active = rsc->private->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)
&& pcmk__is_primitive(rsc)) {
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, pcmk__map_element_name(rsc->private->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, PCMK_XA_OPERATION);
const char *interval_ms_s = crm_element_value(xml_op,
PCMK_META_INTERVAL);
const char *op_rc = crm_element_value(xml_op, PCMK__XA_RC_CODE);
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, pcmk__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 *uname = node->details->uname;
const char *fn = va_arg(args, const char *);
xmlNodePtr xml_node = NULL;
xml_node = pcmk__output_create_xml_node(out, PCMK_XE_UTILIZATION,
PCMK_XA_RESOURCE, rsc->id,
PCMK_XA_NODE, uname,
PCMK_XA_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 *", "bool", "bool")
static int
ticket_default(pcmk__output_t *out, va_list args) {
pcmk_ticket_t *ticket = va_arg(args, pcmk_ticket_t *);
bool raw = va_arg(args, int);
bool details = va_arg(args, int);
GString *detail_str = NULL;
if (raw) {
out->list_item(out, ticket->id, "%s", ticket->id);
return pcmk_rc_ok;
}
if (details && g_hash_table_size(ticket->state) > 0) {
GHashTableIter iter;
const char *name = NULL;
const char *value = NULL;
bool already_added = false;
detail_str = g_string_sized_new(100);
pcmk__g_strcat(detail_str, "\t(", NULL);
g_hash_table_iter_init(&iter, ticket->state);
while (g_hash_table_iter_next(&iter, (void **) &name, (void **) &value)) {
if (already_added) {
g_string_append_printf(detail_str, ", %s=", name);
} else {
g_string_append_printf(detail_str, "%s=", name);
already_added = true;
}
if (pcmk__str_any_of(name, PCMK_XA_LAST_GRANTED, "expires", NULL)) {
char *epoch_str = NULL;
long long time_ll;
pcmk__scan_ll(value, &time_ll, 0);
epoch_str = pcmk__epoch2str((const time_t *) &time_ll, 0);
pcmk__g_strcat(detail_str, epoch_str, NULL);
free(epoch_str);
} else {
pcmk__g_strcat(detail_str, value, NULL);
}
}
pcmk__g_strcat(detail_str, ")", NULL);
}
if (ticket->last_granted > -1) {
/* Prior to the introduction of the details & raw arguments to this
* function, last-granted would always be added in this block. We need
* to preserve that behavior. At the same time, we also need to preserve
* the existing behavior from crm_ticket, which would include last-granted
* as part of the (...) detail string.
*
* Luckily we can check detail_str - if it's NULL, either there were no
* details, or we are preserving the previous behavior of this function.
* If it's not NULL, we are either preserving the previous behavior of
* crm_ticket or we were given details=true as an argument.
*/
if (detail_str == NULL) {
char *epoch_str = pcmk__epoch2str(&(ticket->last_granted), 0);
out->list_item(out, NULL, "%s\t%s%s last-granted=\"%s\"",
ticket->id,
(ticket->granted? "granted" : "revoked"),
(ticket->standby? " [standby]" : ""),
pcmk__s(epoch_str, ""));
free(epoch_str);
} else {
out->list_item(out, NULL, "%s\t%s%s %s",
ticket->id,
(ticket->granted? "granted" : "revoked"),
(ticket->standby? " [standby]" : ""),
detail_str->str);
}
} else {
out->list_item(out, NULL, "%s\t%s%s%s", ticket->id,
ticket->granted ? "granted" : "revoked",
ticket->standby ? " [standby]" : "",
detail_str != NULL ? detail_str->str : "");
}
if (detail_str != NULL) {
g_string_free(detail_str, TRUE);
}
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("ticket", "pcmk_ticket_t *", "bool", "bool")
static int
ticket_xml(pcmk__output_t *out, va_list args) {
pcmk_ticket_t *ticket = va_arg(args, pcmk_ticket_t *);
bool raw G_GNUC_UNUSED = va_arg(args, int);
bool details G_GNUC_UNUSED = va_arg(args, int);
const char *status = NULL;
const char *standby = pcmk__btoa(ticket->standby);
xmlNodePtr node = NULL;
GHashTableIter iter;
const char *name = NULL;
const char *value = NULL;
status = ticket->granted? PCMK_VALUE_GRANTED : PCMK_VALUE_REVOKED;
node = pcmk__output_create_xml_node(out, PCMK_XE_TICKET,
PCMK_XA_ID, ticket->id,
PCMK_XA_STATUS, status,
PCMK_XA_STANDBY, standby,
NULL);
if (ticket->last_granted > -1) {
char *buf = pcmk__epoch2str(&ticket->last_granted, 0);
crm_xml_add(node, PCMK_XA_LAST_GRANTED, buf);
free(buf);
}
g_hash_table_iter_init(&iter, ticket->state);
while (g_hash_table_iter_next(&iter, (void **) &name, (void **) &value)) {
/* PCMK_XA_LAST_GRANTED and "expires" are already added by the check
* for ticket->last_granted above.
*/
if (pcmk__str_any_of(name, PCMK_XA_LAST_GRANTED, PCMK_XA_EXPIRES,
NULL)) {
continue;
}
crm_xml_add(node, name, value);
}
return pcmk_rc_ok;
}
PCMK__OUTPUT_ARGS("ticket-list", "GHashTable *", "bool", "bool", "bool")
static int
ticket_list(pcmk__output_t *out, va_list args) {
GHashTable *tickets = va_arg(args, GHashTable *);
bool print_spacer = va_arg(args, int);
bool raw = va_arg(args, int);
bool details = va_arg(args, int);
GHashTableIter iter;
gpointer value;
if (g_hash_table_size(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, tickets);
while (g_hash_table_iter_next(&iter, NULL, &value)) {
pcmk_ticket_t *ticket = (pcmk_ticket_t *) value;
out->message(out, "ticket", ticket, raw, details);
}
/* 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_default },
{ "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

Mime Type
text/x-diff
Expires
Mon, Apr 21, 7:27 PM (10 h, 45 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1665549
Default Alt Text
(236 KB)

Event Timeline