Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/include/crm/common/logging_internal.h b/include/crm/common/logging_internal.h
index 3bfd504714..53e5848a20 100644
--- a/include/crm/common/logging_internal.h
+++ b/include/crm/common/logging_internal.h
@@ -1,241 +1,243 @@
/*
* Copyright 2015-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#ifdef __cplusplus
extern "C" {
#endif
#ifndef PCMK__LOGGING_INTERNAL_H
#define PCMK__LOGGING_INTERNAL_H
#include <glib.h>
#include <crm/common/logging.h>
#include <crm/common/output_internal.h>
/* Some warnings are too noisy when logged every time a given function is called
* (for example, using a deprecated feature). As an alternative, we allow
* warnings to be logged once per invocation of the calling program. Each of
* those warnings needs a flag defined here.
*/
enum pcmk__warnings {
pcmk__wo_blind = (1 << 0),
pcmk__wo_restart_type = (1 << 1),
pcmk__wo_role_after = (1 << 2),
pcmk__wo_poweroff = (1 << 3),
pcmk__wo_require_all = (1 << 4),
pcmk__wo_order_score = (1 << 5),
pcmk__wo_neg_threshold = (1 << 6),
pcmk__wo_remove_after = (1 << 7),
pcmk__wo_ping_node = (1 << 8),
pcmk__wo_order_inst = (1 << 9),
pcmk__wo_coloc_inst = (1 << 10),
pcmk__wo_group_order = (1 << 11),
pcmk__wo_group_coloc = (1 << 12),
pcmk__wo_upstart = (1 << 13),
pcmk__wo_nagios = (1 << 14),
pcmk__wo_set_ordering = (1 << 15),
pcmk__wo_rdisc_enabled = (1 << 16),
pcmk__wo_rkt = (1 << 17),
pcmk__wo_location_rules = (1 << 18),
pcmk__wo_op_attr_expr = (1 << 19),
pcmk__wo_instance_defaults = (1 << 20),
pcmk__wo_multiple_rules = (1 << 21),
pcmk__wo_master_element = (1 << 22),
pcmk__wo_clone_master_max = (1 << 23),
pcmk__wo_clone_master_node_max = (1 << 24),
pcmk__wo_bundle_master = (1 << 25),
pcmk__wo_master_role = (1 << 26),
pcmk__wo_slave_role = (1 << 27),
};
/*!
* \internal
* \brief Log a warning once per invocation of calling program
*
* \param[in] wo_flag enum pcmk__warnings value for this warning
* \param[in] fmt... printf(3)-style format and arguments
*/
#define pcmk__warn_once(wo_flag, fmt...) do { \
if (!pcmk_is_set(pcmk__warnings, wo_flag)) { \
if (wo_flag == pcmk__wo_blind) { \
crm_warn(fmt); \
} else { \
pcmk__config_warn(fmt); \
} \
pcmk__warnings = pcmk__set_flags_as(__func__, __LINE__, \
LOG_TRACE, \
"Warn-once", "logging", \
pcmk__warnings, \
(wo_flag), #wo_flag); \
} \
} while (0)
-typedef void (*pcmk__config_error_func) (void *ctx, const char *msg, ...);
-typedef void (*pcmk__config_warning_func) (void *ctx, const char *msg, ...);
+typedef void (*pcmk__config_error_func) (void *ctx, const char *msg, ...)
+ G_GNUC_PRINTF(2, 3);
+typedef void (*pcmk__config_warning_func) (void *ctx, const char *msg, ...)
+ G_GNUC_PRINTF(2, 3);
extern pcmk__config_error_func pcmk__config_error_handler;
extern pcmk__config_warning_func pcmk__config_warning_handler;
extern void *pcmk__config_error_context;
extern void *pcmk__config_warning_context;
void pcmk__set_config_error_handler(pcmk__config_error_func error_handler, void *error_context);
void pcmk__set_config_warning_handler(pcmk__config_warning_func warning_handler, void *warning_context);
/*!
* \internal
* \brief Log an error and make crm_verify return failure status
*
* \param[in] fmt... printf(3)-style format string and arguments
*/
#define pcmk__config_err(fmt...) do { \
crm_config_error = TRUE; \
if (pcmk__config_error_handler == NULL) { \
crm_err(fmt); \
} else { \
pcmk__config_error_handler(pcmk__config_error_context, fmt); \
} \
} while (0)
/*!
* \internal
* \brief Log a warning and make crm_verify return failure status
*
* \param[in] fmt... printf(3)-style format string and arguments
*/
#define pcmk__config_warn(fmt...) do { \
crm_config_warning = TRUE; \
if (pcmk__config_warning_handler == NULL) { \
crm_warn(fmt); \
} else { \
pcmk__config_warning_handler(pcmk__config_warning_context, fmt);\
} \
} while (0)
/*!
* \internal
* \brief Execute code depending on whether trace logging is enabled
*
* This is similar to \p do_crm_log_unlikely() except instead of logging, it
* selects one of two code blocks to execute.
*
* \param[in] if_action Code block to execute if trace logging is enabled
* \param[in] else_action Code block to execute if trace logging is not enabled
*
* \note Neither \p if_action nor \p else_action can contain a \p break or
* \p continue statement.
*/
#define pcmk__if_tracing(if_action, else_action) do { \
static struct qb_log_callsite *trace_cs = NULL; \
\
if (trace_cs == NULL) { \
trace_cs = qb_log_callsite_get(__func__, __FILE__, \
"if_tracing", LOG_TRACE, \
__LINE__, crm_trace_nonlog); \
} \
if (crm_is_callsite_active(trace_cs, LOG_TRACE, \
crm_trace_nonlog)) { \
if_action; \
} else { \
else_action; \
} \
} while (0)
/*!
* \internal
* \brief Log XML changes line-by-line in a formatted fashion
*
* \param[in] level Priority at which to log the messages
* \param[in] xml XML to log
*
* \note This does nothing when \p level is \c LOG_STDOUT.
*/
#define pcmk__log_xml_changes(level, xml) do { \
uint8_t _level = pcmk__clip_log_level(level); \
static struct qb_log_callsite *xml_cs = NULL; \
\
switch (_level) { \
case LOG_STDOUT: \
case LOG_NEVER: \
break; \
default: \
if (xml_cs == NULL) { \
xml_cs = qb_log_callsite_get(__func__, __FILE__, \
"xml-changes", _level, \
__LINE__, 0); \
} \
if (crm_is_callsite_active(xml_cs, _level, 0)) { \
pcmk__log_xml_changes_as(__FILE__, __func__, __LINE__, \
0, _level, xml); \
} \
break; \
} \
} while(0)
/*!
* \internal
* \brief Log an XML patchset line-by-line in a formatted fashion
*
* \param[in] level Priority at which to log the messages
* \param[in] patchset XML patchset to log
*
* \note This does nothing when \p level is \c LOG_STDOUT.
*/
#define pcmk__log_xml_patchset(level, patchset) do { \
uint8_t _level = pcmk__clip_log_level(level); \
static struct qb_log_callsite *xml_cs = NULL; \
\
switch (_level) { \
case LOG_STDOUT: \
case LOG_NEVER: \
break; \
default: \
if (xml_cs == NULL) { \
xml_cs = qb_log_callsite_get(__func__, __FILE__, \
"xml-patchset", _level, \
__LINE__, 0); \
} \
if (crm_is_callsite_active(xml_cs, _level, 0)) { \
pcmk__log_xml_patchset_as(__FILE__, __func__, __LINE__, \
0, _level, patchset); \
} \
break; \
} \
} while(0)
void pcmk__log_xml_changes_as(const char *file, const char *function,
uint32_t line, uint32_t tags, uint8_t level,
const xmlNode *xml);
void pcmk__log_xml_patchset_as(const char *file, const char *function,
uint32_t line, uint32_t tags, uint8_t level,
const xmlNode *patchset);
/*!
* \internal
* \brief Initialize logging for command line tools
*
* \param[in] name The name of the program
* \param[in] verbosity How verbose to be in logging
*
* \note \p verbosity is not the same as the logging level (LOG_ERR, etc.).
*/
void pcmk__cli_init_logging(const char *name, unsigned int verbosity);
int pcmk__add_logfile(const char *filename);
void pcmk__add_logfiles(gchar **log_files, pcmk__output_t *out);
void pcmk__free_common_logger(void);
#ifdef __cplusplus
}
#endif
#endif
diff --git a/lib/common/rules.c b/lib/common/rules.c
index 32af83510d..c32df1f277 100644
--- a/lib/common/rules.c
+++ b/lib/common/rules.c
@@ -1,1512 +1,1512 @@
/*
* 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 <stdio.h> // NULL, size_t
#include <stdbool.h> // bool
#include <ctype.h> // isdigit()
#include <regex.h> // regmatch_t
#include <stdint.h> // uint32_t
#include <inttypes.h> // PRIu32
#include <glib.h> // gboolean, FALSE
#include <libxml/tree.h> // xmlNode
#include <crm/common/scheduler.h>
#include <crm/common/iso8601_internal.h>
#include <crm/common/nvpair_internal.h>
#include <crm/common/scheduler_internal.h>
#include "crmcommon_private.h"
/*!
* \internal
* \brief Get the condition type corresponding to given condition XML
*
* \param[in] condition Rule condition XML
*
* \return Condition type corresponding to \p condition
*/
enum expression_type
pcmk__condition_type(const xmlNode *condition)
{
const char *name = NULL;
// Expression types based on element name
if (pcmk__xe_is(condition, PCMK_XE_DATE_EXPRESSION)) {
return pcmk__condition_datetime;
} else if (pcmk__xe_is(condition, PCMK_XE_RSC_EXPRESSION)) {
return pcmk__condition_resource;
} else if (pcmk__xe_is(condition, PCMK_XE_OP_EXPRESSION)) {
return pcmk__condition_operation;
} else if (pcmk__xe_is(condition, PCMK_XE_RULE)) {
return pcmk__condition_rule;
} else if (!pcmk__xe_is(condition, PCMK_XE_EXPRESSION)) {
return pcmk__condition_unknown;
}
// Expression types based on node attribute name
name = crm_element_value(condition, PCMK_XA_ATTRIBUTE);
if (pcmk__str_any_of(name, CRM_ATTR_UNAME, CRM_ATTR_KIND, CRM_ATTR_ID,
NULL)) {
return pcmk__condition_location;
}
return pcmk__condition_attribute;
}
/*!
* \internal
* \brief Get parent XML element's ID for logging purposes
*
* \param[in] xml XML of a subelement
*
* \return ID of \p xml's parent for logging purposes (guaranteed non-NULL)
*/
static const char *
loggable_parent_id(const xmlNode *xml)
{
// Default if called without parent (likely for unit testing)
const char *parent_id = "implied";
if ((xml != NULL) && (xml->parent != NULL)) {
parent_id = pcmk__xe_id(xml->parent);
if (parent_id == NULL) { // Not possible with schema validation enabled
parent_id = "without ID";
}
}
return parent_id;
}
/*!
* \internal
* \brief Get the moon phase corresponding to a given date/time
*
* \param[in] now Date/time to get moon phase for
*
* \return Phase of the moon corresponding to \p now, where 0 is the new moon
* and 7 is the full moon
* \deprecated This feature has been deprecated since 2.1.6.
*/
static int
phase_of_the_moon(const crm_time_t *now)
{
/* As per the nethack rules:
* - A moon period is 29.53058 days ~= 30
* - A year is 365.2422 days
* - Number of days moon phase advances on first day of year compared to
* preceding year is (365.2422 - 12 * 29.53058) ~= 11
* - Number of years until same phases fall on the same days of the month
* is 18.6 ~= 19
* - Moon phase on first day of year (epact) ~= (11 * (year%19) + 29) % 30
* (29 as initial condition)
* - Current phase in days = first day phase + days elapsed in year
* - 6 moons ~= 177 days ~= 8 reported phases * 22 (+ 11/22 for rounding)
*/
uint32_t epact, diy, goldn;
uint32_t y;
crm_time_get_ordinal(now, &y, &diy);
goldn = (y % 19) + 1;
epact = (11 * goldn + 18) % 30;
if (((epact == 25) && (goldn > 11)) || (epact == 24)) {
epact++;
}
return (((((diy + epact) * 6) + 11) % 177) / 22) & 7;
}
/*!
* \internal
* \brief Check an integer value against a range from a date specification
*
* \param[in] date_spec XML of PCMK_XE_DATE_SPEC element to check
* \param[in] id XML ID for logging purposes
* \param[in] attr Name of XML attribute with range to check against
* \param[in] value Value to compare against range
*
* \return Standard Pacemaker return code (specifically, pcmk_rc_before_range,
* pcmk_rc_after_range, or pcmk_rc_ok to indicate that result is either
* within range or undetermined)
* \note We return pcmk_rc_ok for an undetermined result so we can continue
* checking the next range attribute.
*/
static int
check_range(const xmlNode *date_spec, const char *id, const char *attr,
uint32_t value)
{
int rc = pcmk_rc_ok;
const char *range = crm_element_value(date_spec, attr);
long long low, high;
if (range == NULL) { // Attribute not present
goto bail;
}
if (pcmk__parse_ll_range(range, &low, &high) != pcmk_rc_ok) {
// Invalid range
/* @COMPAT When we can break behavioral backward compatibility, treat
* the entire rule as not passing.
*/
pcmk__config_err("Ignoring " PCMK_XE_DATE_SPEC
" %s attribute %s because '%s' is not a valid range",
id, attr, range);
} else if ((low != -1) && (value < low)) {
rc = pcmk_rc_before_range;
} else if ((high != -1) && (value > high)) {
rc = pcmk_rc_after_range;
}
bail:
crm_trace("Checked " PCMK_XE_DATE_SPEC " %s %s='%s' for %" PRIu32 ": %s",
id, attr, pcmk__s(range, ""), value, pcmk_rc_str(rc));
return rc;
}
/*!
* \internal
* \brief Evaluate a date specification for a given date/time
*
* \param[in] date_spec XML of PCMK_XE_DATE_SPEC element to evaluate
* \param[in] now Time to check
*
* \return Standard Pacemaker return code (specifically, EINVAL for NULL
* arguments, pcmk_rc_ok if time matches specification, or
* pcmk_rc_before_range, pcmk_rc_after_range, or pcmk_rc_op_unsatisfied
* as appropriate to how time relates to specification)
*/
int
pcmk__evaluate_date_spec(const xmlNode *date_spec, const crm_time_t *now)
{
const char *id = NULL;
const char *parent_id = loggable_parent_id(date_spec);
// Range attributes that can be specified for a PCMK_XE_DATE_SPEC element
struct range {
const char *attr;
uint32_t value;
} ranges[] = {
{ PCMK_XA_YEARS, 0U },
{ PCMK_XA_MONTHS, 0U },
{ PCMK_XA_MONTHDAYS, 0U },
{ PCMK_XA_HOURS, 0U },
{ PCMK_XA_MINUTES, 0U },
{ PCMK_XA_SECONDS, 0U },
{ PCMK_XA_YEARDAYS, 0U },
{ PCMK_XA_WEEKYEARS, 0U },
{ PCMK_XA_WEEKS, 0U },
{ PCMK_XA_WEEKDAYS, 0U },
{ PCMK__XA_MOON, 0U },
};
if ((date_spec == NULL) || (now == NULL)) {
return EINVAL;
}
// Get specification ID (for logging)
id = pcmk__xe_id(date_spec);
if (pcmk__str_empty(id)) { // Not possible with schema validation enabled
/* @COMPAT When we can break behavioral backward compatibility,
* fail the specification
*/
pcmk__config_warn(PCMK_XE_DATE_SPEC " subelement of "
PCMK_XE_DATE_EXPRESSION " %s has no " PCMK_XA_ID,
parent_id);
id = "without ID"; // for logging
}
// Year, month, day
crm_time_get_gregorian(now, &(ranges[0].value), &(ranges[1].value),
&(ranges[2].value));
// Hour, minute, second
crm_time_get_timeofday(now, &(ranges[3].value), &(ranges[4].value),
&(ranges[5].value));
// Year (redundant) and day of year
crm_time_get_ordinal(now, &(ranges[0].value), &(ranges[6].value));
// Week year, week of week year, day of week
crm_time_get_isoweek(now, &(ranges[7].value), &(ranges[8].value),
&(ranges[9].value));
// Moon phase (deprecated)
ranges[10].value = phase_of_the_moon(now);
if (crm_element_value(date_spec, PCMK__XA_MOON) != NULL) {
pcmk__config_warn("Support for '" PCMK__XA_MOON "' in "
PCMK_XE_DATE_SPEC " elements (such as %s) is "
"deprecated and will be removed in a future release "
"of Pacemaker", id);
}
for (int i = 0; i < PCMK__NELEM(ranges); ++i) {
int rc = check_range(date_spec, id, ranges[i].attr, ranges[i].value);
if (rc != pcmk_rc_ok) {
return rc;
}
}
// All specified ranges passed, or none were given (also considered a pass)
return pcmk_rc_ok;
}
#define ADD_COMPONENT(component) do { \
int sub_rc = pcmk__add_time_from_xml(*end, component, duration); \
if (sub_rc != pcmk_rc_ok) { \
/* @COMPAT return sub_rc when we can break compatibility */ \
pcmk__config_warn("Ignoring %s in " PCMK_XE_DURATION " %s " \
"because it is invalid", \
pcmk__time_component_attr(component), id); \
rc = sub_rc; \
} \
} while (0)
/*!
* \internal
* \brief Given a duration and a start time, calculate the end time
*
* \param[in] duration XML of PCMK_XE_DURATION element
* \param[in] start Start time
* \param[out] end Where to store end time (\p *end must be NULL
* initially)
*
* \return Standard Pacemaker return code
* \note The caller is responsible for freeing \p *end using crm_time_free().
*/
int
pcmk__unpack_duration(const xmlNode *duration, const crm_time_t *start,
crm_time_t **end)
{
int rc = pcmk_rc_ok;
const char *id = NULL;
const char *parent_id = loggable_parent_id(duration);
if ((start == NULL) || (duration == NULL)
|| (end == NULL) || (*end != NULL)) {
return EINVAL;
}
// Get duration ID (for logging)
id = pcmk__xe_id(duration);
if (pcmk__str_empty(id)) { // Not possible with schema validation enabled
/* @COMPAT When we can break behavioral backward compatibility,
* return pcmk_rc_unpack_error instead
*/
pcmk__config_warn(PCMK_XE_DURATION " subelement of "
PCMK_XE_DATE_EXPRESSION " %s has no " PCMK_XA_ID,
parent_id);
id = "without ID";
}
*end = pcmk_copy_time(start);
ADD_COMPONENT(pcmk__time_years);
ADD_COMPONENT(pcmk__time_months);
ADD_COMPONENT(pcmk__time_weeks);
ADD_COMPONENT(pcmk__time_days);
ADD_COMPONENT(pcmk__time_hours);
ADD_COMPONENT(pcmk__time_minutes);
ADD_COMPONENT(pcmk__time_seconds);
return rc;
}
/*!
* \internal
* \brief Evaluate a range check for a given date/time
*
* \param[in] date_expression XML of PCMK_XE_DATE_EXPRESSION element
* \param[in] id Expression ID for logging purposes
* \param[in] now Date/time to compare
* \param[in,out] next_change If not NULL, set this to when the evaluation
* will change, if known and earlier than the
* original value
*
* \return Standard Pacemaker return code
*/
static int
evaluate_in_range(const xmlNode *date_expression, const char *id,
const crm_time_t *now, crm_time_t *next_change)
{
crm_time_t *start = NULL;
crm_time_t *end = NULL;
if (pcmk__xe_get_datetime(date_expression, PCMK_XA_START,
&start) != pcmk_rc_ok) {
/* @COMPAT When we can break behavioral backward compatibility,
* return pcmk_rc_unpack_error
*/
pcmk__config_warn("Ignoring " PCMK_XA_START " in "
PCMK_XE_DATE_EXPRESSION " %s because it is invalid",
id);
}
if (pcmk__xe_get_datetime(date_expression, PCMK_XA_END,
&end) != pcmk_rc_ok) {
/* @COMPAT When we can break behavioral backward compatibility,
* return pcmk_rc_unpack_error
*/
pcmk__config_warn("Ignoring " PCMK_XA_END " in "
PCMK_XE_DATE_EXPRESSION " %s because it is invalid",
id);
}
if ((start == NULL) && (end == NULL)) {
// Not possible with schema validation enabled
/* @COMPAT When we can break behavioral backward compatibility,
* return pcmk_rc_unpack_error
*/
pcmk__config_warn("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
"passing because in_range requires at least one of "
PCMK_XA_START " or " PCMK_XA_END, id);
return pcmk_rc_undetermined;
}
if (end == NULL) {
xmlNode *duration = pcmk__xe_first_child(date_expression,
PCMK_XE_DURATION, NULL, NULL);
if (duration != NULL) {
/* @COMPAT When we can break behavioral backward compatibility,
* return the result of this if not OK
*/
pcmk__unpack_duration(duration, start, &end);
}
}
if ((start != NULL) && (crm_time_compare(now, start) < 0)) {
pcmk__set_time_if_earlier(next_change, start);
crm_time_free(start);
crm_time_free(end);
return pcmk_rc_before_range;
}
if (end != NULL) {
if (crm_time_compare(now, end) > 0) {
crm_time_free(start);
crm_time_free(end);
return pcmk_rc_after_range;
}
// Evaluation doesn't change until second after end
if (next_change != NULL) {
crm_time_add_seconds(end, 1);
pcmk__set_time_if_earlier(next_change, end);
}
}
crm_time_free(start);
crm_time_free(end);
return pcmk_rc_within_range;
}
/*!
* \internal
* \brief Evaluate a greater-than check for a given date/time
*
* \param[in] date_expression XML of PCMK_XE_DATE_EXPRESSION element
* \param[in] id Expression ID for logging purposes
* \param[in] now Date/time to compare
* \param[in,out] next_change If not NULL, set this to when the evaluation
* will change, if known and earlier than the
* original value
*
* \return Standard Pacemaker return code
*/
static int
evaluate_gt(const xmlNode *date_expression, const char *id,
const crm_time_t *now, crm_time_t *next_change)
{
crm_time_t *start = NULL;
if (pcmk__xe_get_datetime(date_expression, PCMK_XA_START,
&start) != pcmk_rc_ok) {
/* @COMPAT When we can break behavioral backward compatibility,
* return pcmk_rc_unpack_error
*/
pcmk__config_warn("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
"passing because " PCMK_XA_START " is invalid",
id);
return pcmk_rc_undetermined;
}
if (start == NULL) { // Not possible with schema validation enabled
/* @COMPAT When we can break behavioral backward compatibility,
* return pcmk_rc_unpack_error
*/
pcmk__config_warn("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
"passing because " PCMK_VALUE_GT " requires "
PCMK_XA_START, id);
return pcmk_rc_undetermined;
}
if (crm_time_compare(now, start) > 0) {
crm_time_free(start);
return pcmk_rc_within_range;
}
// Evaluation doesn't change until second after start time
crm_time_add_seconds(start, 1);
pcmk__set_time_if_earlier(next_change, start);
crm_time_free(start);
return pcmk_rc_before_range;
}
/*!
* \internal
* \brief Evaluate a less-than check for a given date/time
*
* \param[in] date_expression XML of PCMK_XE_DATE_EXPRESSION element
* \param[in] id Expression ID for logging purposes
* \param[in] now Date/time to compare
* \param[in,out] next_change If not NULL, set this to when the evaluation
* will change, if known and earlier than the
* original value
*
* \return Standard Pacemaker return code
*/
static int
evaluate_lt(const xmlNode *date_expression, const char *id,
const crm_time_t *now, crm_time_t *next_change)
{
crm_time_t *end = NULL;
if (pcmk__xe_get_datetime(date_expression, PCMK_XA_END,
&end) != pcmk_rc_ok) {
/* @COMPAT When we can break behavioral backward compatibility,
* return pcmk_rc_unpack_error
*/
pcmk__config_warn("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
"passing because " PCMK_XA_END " is invalid", id);
return pcmk_rc_undetermined;
}
if (end == NULL) { // Not possible with schema validation enabled
/* @COMPAT When we can break behavioral backward compatibility,
* return pcmk_rc_unpack_error
*/
pcmk__config_warn("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
"passing because " PCMK_VALUE_GT " requires "
PCMK_XA_END, id);
return pcmk_rc_undetermined;
}
if (crm_time_compare(now, end) < 0) {
pcmk__set_time_if_earlier(next_change, end);
crm_time_free(end);
return pcmk_rc_within_range;
}
crm_time_free(end);
return pcmk_rc_after_range;
}
/*!
* \internal
* \brief Evaluate a rule's date expression for a given date/time
*
* \param[in] date_expression XML of a PCMK_XE_DATE_EXPRESSION element
* \param[in] now Time to use for evaluation
* \param[in,out] next_change If not NULL, set this to when the evaluation
* will change, if known and earlier than the
* original value
*
* \return Standard Pacemaker return code (unlike most other evaluation
* functions, this can return either pcmk_rc_ok or pcmk_rc_within_range
* on success)
*/
int
pcmk__evaluate_date_expression(const xmlNode *date_expression,
const crm_time_t *now, crm_time_t *next_change)
{
const char *id = NULL;
const char *op = NULL;
int rc = pcmk_rc_undetermined;
if ((date_expression == NULL) || (now == NULL)) {
return EINVAL;
}
// Get expression ID (for logging)
id = pcmk__xe_id(date_expression);
if (pcmk__str_empty(id)) { // Not possible with schema validation enabled
/* @COMPAT When we can break behavioral backward compatibility,
* return pcmk_rc_unpack_error
*/
pcmk__config_warn(PCMK_XE_DATE_EXPRESSION " element has no "
PCMK_XA_ID);
id = "without ID"; // for logging
}
op = crm_element_value(date_expression, PCMK_XA_OPERATION);
if (pcmk__str_eq(op, PCMK_VALUE_IN_RANGE,
pcmk__str_null_matches|pcmk__str_casei)) {
rc = evaluate_in_range(date_expression, id, now, next_change);
} else if (pcmk__str_eq(op, PCMK_VALUE_DATE_SPEC, pcmk__str_casei)) {
xmlNode *date_spec = pcmk__xe_first_child(date_expression,
PCMK_XE_DATE_SPEC, NULL,
NULL);
if (date_spec == NULL) { // Not possible with schema validation enabled
/* @COMPAT When we can break behavioral backward compatibility,
* return pcmk_rc_unpack_error
*/
pcmk__config_warn("Treating " PCMK_XE_DATE_EXPRESSION " %s "
"as not passing because " PCMK_VALUE_DATE_SPEC
" operations require a " PCMK_XE_DATE_SPEC
" subelement", id);
} else {
// @TODO set next_change appropriately
rc = pcmk__evaluate_date_spec(date_spec, now);
}
} else if (pcmk__str_eq(op, PCMK_VALUE_GT, pcmk__str_casei)) {
rc = evaluate_gt(date_expression, id, now, next_change);
} else if (pcmk__str_eq(op, PCMK_VALUE_LT, pcmk__str_casei)) {
rc = evaluate_lt(date_expression, id, now, next_change);
} else { // Not possible with schema validation enabled
/* @COMPAT When we can break behavioral backward compatibility,
* return pcmk_rc_unpack_error
*/
pcmk__config_warn("Treating " PCMK_XE_DATE_EXPRESSION
" %s as not passing because '%s' is not a valid "
- PCMK_XE_OPERATION, op);
+ PCMK_XE_OPERATION, id, op);
}
crm_trace(PCMK_XE_DATE_EXPRESSION " %s (%s): %s (%d)",
id, op, pcmk_rc_str(rc), rc);
return rc;
}
/*!
* \internal
* \brief Go through submatches in a string, either counting how many bytes
* would be needed for the expansion, or performing the expansion,
* as requested
*
* \param[in] string String possibly containing submatch variables
* \param[in] match String that matched the regular expression
* \param[in] submatches Regular expression submatches (as set by regexec())
* \param[in] nmatches Number of entries in \p submatches[]
* \param[out] expansion If not NULL, expand string here (must be
* pre-allocated to appropriate size)
* \param[out] nbytes If not NULL, set to size needed for expansion
*
* \return true if any expansion is needed, otherwise false
*/
static bool
process_submatches(const char *string, const char *match,
const regmatch_t submatches[], int nmatches,
char *expansion, size_t *nbytes)
{
bool expanded = false;
const char *src = string;
if (nbytes != NULL) {
*nbytes = 1; // Include space for terminator
}
while (*src != '\0') {
int submatch = 0;
size_t match_len = 0;
if ((src[0] != '%') || !isdigit(src[1])) {
/* src does not point to the first character of a %N sequence,
* so expand this character as-is
*/
if (expansion != NULL) {
*expansion++ = *src;
}
if (nbytes != NULL) {
++(*nbytes);
}
++src;
continue;
}
submatch = src[1] - '0';
src += 2; // Skip over %N sequence in source string
expanded = true; // Expansion will be different from source
// Omit sequence from expansion unless it has a non-empty match
if ((nmatches <= submatch) // Not enough submatches
|| (submatches[submatch].rm_so < 0) // Pattern did not match
|| (submatches[submatch].rm_eo
<= submatches[submatch].rm_so)) { // Match was empty
continue;
}
match_len = submatches[submatch].rm_eo - submatches[submatch].rm_so;
if (nbytes != NULL) {
*nbytes += match_len;
}
if (expansion != NULL) {
memcpy(expansion, match + submatches[submatch].rm_so,
match_len);
expansion += match_len;
}
}
return expanded;
}
/*!
* \internal
* \brief Expand any regular expression submatches (%0-%9) in a string
*
* \param[in] string String possibly containing submatch variables
* \param[in] match String that matched the regular expression
* \param[in] submatches Regular expression submatches (as set by regexec())
* \param[in] nmatches Number of entries in \p submatches[]
*
* \return Newly allocated string identical to \p string with submatches
* expanded on success, or NULL if no expansions were needed
* \note The caller is responsible for freeing the result with free()
*/
char *
pcmk__replace_submatches(const char *string, const char *match,
const regmatch_t submatches[], int nmatches)
{
size_t nbytes = 0;
char *result = NULL;
if (pcmk__str_empty(string) || pcmk__str_empty(match)) {
return NULL; // Nothing to expand
}
// Calculate how much space will be needed for expanded string
if (!process_submatches(string, match, submatches, nmatches, NULL,
&nbytes)) {
return NULL; // No expansions needed
}
// Allocate enough space for expanded string
result = pcmk__assert_alloc(nbytes, sizeof(char));
// Expand submatches
(void) process_submatches(string, match, submatches, nmatches, result,
NULL);
return result;
}
/*!
* \internal
* \brief Parse a comparison type from a string
*
* \param[in] op String with comparison type (valid values are
* \c PCMK_VALUE_DEFINED, \c PCMK_VALUE_NOT_DEFINED,
* \c PCMK_VALUE_EQ, \c PCMK_VALUE_NE,
* \c PCMK_VALUE_LT, \c PCMK_VALUE_LTE,
* \c PCMK_VALUE_GT, or \c PCMK_VALUE_GTE)
*
* \return Comparison type corresponding to \p op
*/
enum pcmk__comparison
pcmk__parse_comparison(const char *op)
{
if (pcmk__str_eq(op, PCMK_VALUE_DEFINED, pcmk__str_casei)) {
return pcmk__comparison_defined;
} else if (pcmk__str_eq(op, PCMK_VALUE_NOT_DEFINED, pcmk__str_casei)) {
return pcmk__comparison_undefined;
} else if (pcmk__str_eq(op, PCMK_VALUE_EQ, pcmk__str_casei)) {
return pcmk__comparison_eq;
} else if (pcmk__str_eq(op, PCMK_VALUE_NE, pcmk__str_casei)) {
return pcmk__comparison_ne;
} else if (pcmk__str_eq(op, PCMK_VALUE_LT, pcmk__str_casei)) {
return pcmk__comparison_lt;
} else if (pcmk__str_eq(op, PCMK_VALUE_LTE, pcmk__str_casei)) {
return pcmk__comparison_lte;
} else if (pcmk__str_eq(op, PCMK_VALUE_GT, pcmk__str_casei)) {
return pcmk__comparison_gt;
} else if (pcmk__str_eq(op, PCMK_VALUE_GTE, pcmk__str_casei)) {
return pcmk__comparison_gte;
}
return pcmk__comparison_unknown;
}
/*!
* \internal
* \brief Parse a value type from a string
*
* \param[in] type String with value type (valid values are NULL,
* \c PCMK_VALUE_STRING, \c PCMK_VALUE_INTEGER,
* \c PCMK_VALUE_NUMBER, and \c PCMK_VALUE_VERSION)
* \param[in] op Operation type (used only to select default)
* \param[in] value1 First value being compared (used only to select default)
* \param[in] value2 Second value being compared (used only to select default)
*/
enum pcmk__type
pcmk__parse_type(const char *type, enum pcmk__comparison op,
const char *value1, const char *value2)
{
if (type == NULL) {
switch (op) {
case pcmk__comparison_lt:
case pcmk__comparison_lte:
case pcmk__comparison_gt:
case pcmk__comparison_gte:
if (((value1 != NULL) && (strchr(value1, '.') != NULL))
|| ((value2 != NULL) && (strchr(value2, '.') != NULL))) {
return pcmk__type_number;
}
return pcmk__type_integer;
default:
return pcmk__type_string;
}
}
if (pcmk__str_eq(type, PCMK_VALUE_STRING, pcmk__str_casei)) {
return pcmk__type_string;
} else if (pcmk__str_eq(type, PCMK_VALUE_INTEGER, pcmk__str_casei)) {
return pcmk__type_integer;
} else if (pcmk__str_eq(type, PCMK_VALUE_NUMBER, pcmk__str_casei)) {
return pcmk__type_number;
} else if (pcmk__str_eq(type, PCMK_VALUE_VERSION, pcmk__str_casei)) {
return pcmk__type_version;
}
return pcmk__type_unknown;
}
/*!
* \internal
* \brief Compare two strings according to a given type
*
* \param[in] value1 String with first value to compare
* \param[in] value2 String with second value to compare
* \param[in] type How to interpret the values
*
* \return Standard comparison result (a negative integer if \p value1 is
* lesser, 0 if the values are equal, and a positive integer if
* \p value1 is greater)
*/
int
pcmk__cmp_by_type(const char *value1, const char *value2, enum pcmk__type type)
{
// NULL compares as less than non-NULL
if (value2 == NULL) {
return (value1 == NULL)? 0 : 1;
}
if (value1 == NULL) {
return -1;
}
switch (type) {
case pcmk__type_string:
return strcasecmp(value1, value2);
case pcmk__type_integer:
{
long long integer1;
long long integer2;
if ((pcmk__scan_ll(value1, &integer1, 0LL) != pcmk_rc_ok)
|| (pcmk__scan_ll(value2, &integer2, 0LL) != pcmk_rc_ok)) {
crm_warn("Comparing '%s' and '%s' as strings because "
"invalid as integers", value1, value2);
return strcasecmp(value1, value2);
}
return (integer1 < integer2)? -1 : (integer1 > integer2)? 1 : 0;
}
break;
case pcmk__type_number:
{
double num1;
double num2;
if ((pcmk__scan_double(value1, &num1, NULL, NULL) != pcmk_rc_ok)
|| (pcmk__scan_double(value2, &num2, NULL,
NULL) != pcmk_rc_ok)) {
crm_warn("Comparing '%s' and '%s' as strings because invalid as "
"numbers", value1, value2);
return strcasecmp(value1, value2);
}
return (num1 < num2)? -1 : (num1 > num2)? 1 : 0;
}
break;
case pcmk__type_version:
return compare_version(value1, value2);
default: // Invalid type
return 0;
}
}
/*!
* \internal
* \brief Parse a reference value source from a string
*
* \param[in] source String indicating reference value source
*
* \return Reference value source corresponding to \p source
*/
enum pcmk__reference_source
pcmk__parse_source(const char *source)
{
if (pcmk__str_eq(source, PCMK_VALUE_LITERAL,
pcmk__str_casei|pcmk__str_null_matches)) {
return pcmk__source_literal;
} else if (pcmk__str_eq(source, PCMK_VALUE_PARAM, pcmk__str_casei)) {
return pcmk__source_instance_attrs;
} else if (pcmk__str_eq(source, PCMK_VALUE_META, pcmk__str_casei)) {
return pcmk__source_meta_attrs;
} else {
return pcmk__source_unknown;
}
}
/*!
* \internal
* \brief Parse a boolean operator from a string
*
* \param[in] combine String indicating boolean operator
*
* \return Enumeration value corresponding to \p combine
*/
enum pcmk__combine
pcmk__parse_combine(const char *combine)
{
if (pcmk__str_eq(combine, PCMK_VALUE_AND,
pcmk__str_null_matches|pcmk__str_casei)) {
return pcmk__combine_and;
} else if (pcmk__str_eq(combine, PCMK_VALUE_OR, pcmk__str_casei)) {
return pcmk__combine_or;
} else {
return pcmk__combine_unknown;
}
}
/*!
* \internal
* \brief Get the result of a node attribute comparison for rule evaluation
*
* \param[in] actual Actual node attribute value
* \param[in] reference Node attribute value from rule (ignored for
* \p comparison of \c pcmk__comparison_defined or
* \c pcmk__comparison_undefined)
* \param[in] type How to interpret the values
* \param[in] comparison How to compare the values
*
* \return Standard Pacemaker return code (specifically, \c pcmk_rc_ok if the
* comparison passes, and some other value if it does not)
*/
static int
evaluate_attr_comparison(const char *actual, const char *reference,
enum pcmk__type type, enum pcmk__comparison comparison)
{
int cmp = 0;
switch (comparison) {
case pcmk__comparison_defined:
return (actual != NULL)? pcmk_rc_ok : pcmk_rc_op_unsatisfied;
case pcmk__comparison_undefined:
return (actual == NULL)? pcmk_rc_ok : pcmk_rc_op_unsatisfied;
default:
break;
}
cmp = pcmk__cmp_by_type(actual, reference, type);
switch (comparison) {
case pcmk__comparison_eq:
return (cmp == 0)? pcmk_rc_ok : pcmk_rc_op_unsatisfied;
case pcmk__comparison_ne:
return (cmp != 0)? pcmk_rc_ok : pcmk_rc_op_unsatisfied;
default:
break;
}
if ((actual == NULL) || (reference == NULL)) {
return pcmk_rc_op_unsatisfied; // Comparison would be meaningless
}
switch (comparison) {
case pcmk__comparison_lt:
return (cmp < 0)? pcmk_rc_ok : pcmk_rc_after_range;
case pcmk__comparison_lte:
return (cmp <= 0)? pcmk_rc_ok : pcmk_rc_after_range;
case pcmk__comparison_gt:
return (cmp > 0)? pcmk_rc_ok : pcmk_rc_before_range;
case pcmk__comparison_gte:
return (cmp >= 0)? pcmk_rc_ok : pcmk_rc_before_range;
default: // Not possible with schema validation enabled
return pcmk_rc_op_unsatisfied;
}
}
/*!
* \internal
* \brief Get a reference value from a configured source
*
* \param[in] value Value given in rule expression
* \param[in] source Reference value source
* \param[in] rule_input Values used to evaluate rule criteria
*/
static const char *
value_from_source(const char *value, enum pcmk__reference_source source,
const pcmk_rule_input_t *rule_input)
{
GHashTable *table = NULL;
if (pcmk__str_empty(value)) {
/* @COMPAT When we can break backward compatibility, drop this block so
* empty strings are treated as such (there should never be an empty
* string as an instance attribute or meta-attribute name, so those will
* get NULL anyway, but it could matter for literal comparisons)
*/
return NULL;
}
switch (source) {
case pcmk__source_literal:
return value;
case pcmk__source_instance_attrs:
table = rule_input->rsc_params;
break;
case pcmk__source_meta_attrs:
table = rule_input->rsc_meta;
break;
default:
return NULL; // Not possible
}
if (table == NULL) {
return NULL;
}
return (const char *) g_hash_table_lookup(table, value);
}
/*!
* \internal
* \brief Evaluate a node attribute rule expression
*
* \param[in] expression XML of a rule's PCMK_XE_EXPRESSION subelement
* \param[in] rule_input Values used to evaluate rule criteria
*
* \return Standard Pacemaker return code (\c pcmk_rc_ok if the expression
* passes, some other value if it does not)
*/
int
pcmk__evaluate_attr_expression(const xmlNode *expression,
const pcmk_rule_input_t *rule_input)
{
const char *id = NULL;
const char *op = NULL;
const char *attr = NULL;
const char *type_s = NULL;
const char *value = NULL;
const char *actual = NULL;
const char *source_s = NULL;
const char *reference = NULL;
char *expanded_attr = NULL;
int rc = pcmk_rc_ok;
enum pcmk__type type = pcmk__type_unknown;
enum pcmk__reference_source source = pcmk__source_unknown;
enum pcmk__comparison comparison = pcmk__comparison_unknown;
if ((expression == NULL) || (rule_input == NULL)) {
return EINVAL;
}
// Get expression ID (for logging)
id = pcmk__xe_id(expression);
if (pcmk__str_empty(id)) {
/* @COMPAT When we can break behavioral backward compatibility,
* fail the expression
*/
pcmk__config_warn(PCMK_XE_EXPRESSION " element has no " PCMK_XA_ID);
id = "without ID"; // for logging
}
/* Get name of node attribute to compare (expanding any %0-%9 to
* regular expression submatches)
*/
attr = crm_element_value(expression, PCMK_XA_ATTRIBUTE);
if (attr == NULL) {
pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not passing "
"because " PCMK_XA_ATTRIBUTE " was not specified", id);
return pcmk_rc_unpack_error;
}
expanded_attr = pcmk__replace_submatches(attr, rule_input->rsc_id,
rule_input->rsc_id_submatches,
rule_input->rsc_id_nmatches);
if (expanded_attr != NULL) {
attr = expanded_attr;
}
// Get and validate operation
op = crm_element_value(expression, PCMK_XA_OPERATION);
comparison = pcmk__parse_comparison(op);
if (comparison == pcmk__comparison_unknown) {
// Not possible with schema validation enabled
if (op == NULL) {
pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not "
"passing because it has no " PCMK_XA_OPERATION,
id);
} else {
pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not "
"passing because '%s' is not a valid "
PCMK_XA_OPERATION, id, op);
}
rc = pcmk_rc_unpack_error;
goto done;
}
// How reference value is obtained (literal, resource meta-attribute, etc.)
source_s = crm_element_value(expression, PCMK_XA_VALUE_SOURCE);
source = pcmk__parse_source(source_s);
if (source == pcmk__source_unknown) {
// Not possible with schema validation enabled
// @COMPAT Fail expression once we can break backward compatibility
pcmk__config_warn("Expression %s has invalid " PCMK_XA_VALUE_SOURCE
" value '%s', using default "
"('" PCMK_VALUE_LITERAL "')", id, source_s);
source = pcmk__source_literal;
}
// Get and validate reference value
value = crm_element_value(expression, PCMK_XA_VALUE);
switch (comparison) {
case pcmk__comparison_defined:
case pcmk__comparison_undefined:
if (value != NULL) {
pcmk__config_warn("Ignoring " PCMK_XA_VALUE " in "
PCMK_XE_EXPRESSION " %s because it is unused "
"when " PCMK_XA_BOOLEAN_OP " is %s", id, op);
}
break;
default:
if (value == NULL) {
pcmk__config_warn(PCMK_XE_EXPRESSION " %s has no "
PCMK_XA_VALUE, id);
}
break;
}
reference = value_from_source(value, source, rule_input);
// Get actual value of node attribute
if (rule_input->node_attrs != NULL) {
actual = g_hash_table_lookup(rule_input->node_attrs, attr);
}
// Get and validate value type (after expanding reference value)
type_s = crm_element_value(expression, PCMK_XA_TYPE);
type = pcmk__parse_type(type_s, comparison, actual, reference);
if (type == pcmk__type_unknown) {
/* Not possible with schema validation enabled
*
* @COMPAT When we can break behavioral backward compatibility, treat
* the expression as not passing.
*/
pcmk__config_warn("Non-empty node attribute values will be treated as "
"equal for " PCMK_XE_EXPRESSION " %s because '%s' "
- "is not a valid type", id, type);
+ "is not a valid type", id, type_s);
}
rc = evaluate_attr_comparison(actual, reference, type, comparison);
switch (comparison) {
case pcmk__comparison_defined:
case pcmk__comparison_undefined:
crm_trace(PCMK_XE_EXPRESSION " %s result: %s (for attribute %s %s)",
id, pcmk_rc_str(rc), attr, op);
break;
default:
crm_trace(PCMK_XE_EXPRESSION " %s result: "
"%s (attribute %s %s '%s' via %s source as %s type)",
id, pcmk_rc_str(rc), attr, op, pcmk__s(reference, ""),
pcmk__s(source_s, "default"), pcmk__s(type_s, "default"));
break;
}
done:
free(expanded_attr);
return rc;
}
/*!
* \internal
* \brief Evaluate a resource rule expression
*
* \param[in] rsc_expression XML of rule's \c PCMK_XE_RSC_EXPRESSION subelement
* \param[in] rule_input Values used to evaluate rule criteria
*
* \return Standard Pacemaker return code (\c pcmk_rc_ok if the expression
* passes, some other value if it does not)
*/
int
pcmk__evaluate_rsc_expression(const xmlNode *rsc_expression,
const pcmk_rule_input_t *rule_input)
{
const char *id = NULL;
const char *standard = NULL;
const char *provider = NULL;
const char *type = NULL;
if ((rsc_expression == NULL) || (rule_input == NULL)) {
return EINVAL;
}
// Validate XML ID
id = pcmk__xe_id(rsc_expression);
if (pcmk__str_empty(id)) {
// Not possible with schema validation enabled
/* @COMPAT When we can break behavioral backward compatibility,
* fail the expression
*/
pcmk__config_warn(PCMK_XE_RSC_EXPRESSION " has no " PCMK_XA_ID);
id = "without ID"; // for logging
}
// Compare resource standard
standard = crm_element_value(rsc_expression, PCMK_XA_CLASS);
if ((standard != NULL)
&& !pcmk__str_eq(standard, rule_input->rsc_standard, pcmk__str_none)) {
crm_trace(PCMK_XE_RSC_EXPRESSION " %s is unsatisfied because "
"actual standard '%s' doesn't match '%s'",
id, pcmk__s(rule_input->rsc_standard, ""), standard);
return pcmk_rc_op_unsatisfied;
}
// Compare resource provider
provider = crm_element_value(rsc_expression, PCMK_XA_PROVIDER);
if ((provider != NULL)
&& !pcmk__str_eq(provider, rule_input->rsc_provider, pcmk__str_none)) {
crm_trace(PCMK_XE_RSC_EXPRESSION " %s is unsatisfied because "
"actual provider '%s' doesn't match '%s'",
id, pcmk__s(rule_input->rsc_provider, ""), provider);
return pcmk_rc_op_unsatisfied;
}
// Compare resource agent type
type = crm_element_value(rsc_expression, PCMK_XA_TYPE);
if ((type != NULL)
&& !pcmk__str_eq(type, rule_input->rsc_agent, pcmk__str_none)) {
crm_trace(PCMK_XE_RSC_EXPRESSION " %s is unsatisfied because "
"actual agent '%s' doesn't match '%s'",
id, pcmk__s(rule_input->rsc_agent, ""), type);
return pcmk_rc_op_unsatisfied;
}
crm_trace(PCMK_XE_RSC_EXPRESSION " %s is satisfied by %s%s%s:%s",
id, pcmk__s(standard, ""),
((provider == NULL)? "" : ":"), pcmk__s(provider, ""),
pcmk__s(type, ""));
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Evaluate an operation rule expression
*
* \param[in] op_expression XML of a rule's \c PCMK_XE_OP_EXPRESSION subelement
* \param[in] rule_input Values used to evaluate rule criteria
*
* \return Standard Pacemaker return code (\c pcmk_rc_ok if the expression
* is satisfied, some other value if it is not)
*/
int
pcmk__evaluate_op_expression(const xmlNode *op_expression,
const pcmk_rule_input_t *rule_input)
{
const char *id = NULL;
const char *name = NULL;
const char *interval_s = NULL;
guint interval_ms = 0U;
if ((op_expression == NULL) || (rule_input == NULL)) {
return EINVAL;
}
// Get operation expression ID (for logging)
id = pcmk__xe_id(op_expression);
if (pcmk__str_empty(id)) { // Not possible with schema validation enabled
/* @COMPAT When we can break behavioral backward compatibility,
* return pcmk_rc_op_unsatisfied
*/
pcmk__config_warn(PCMK_XE_OP_EXPRESSION " element has no " PCMK_XA_ID);
id = "without ID"; // for logging
}
// Validate operation name
name = crm_element_value(op_expression, PCMK_XA_NAME);
if (name == NULL) { // Not possible with schema validation enabled
pcmk__config_warn("Treating " PCMK_XE_OP_EXPRESSION " %s as not "
"passing because it has no " PCMK_XA_NAME, id);
return pcmk_rc_unpack_error;
}
// Validate operation interval
interval_s = crm_element_value(op_expression, PCMK_META_INTERVAL);
if (pcmk_parse_interval_spec(interval_s, &interval_ms) != pcmk_rc_ok) {
pcmk__config_warn("Treating " PCMK_XE_OP_EXPRESSION " %s as not "
"passing because '%s' is not a valid interval",
id, interval_s);
return pcmk_rc_unpack_error;
}
// Compare operation name
if (!pcmk__str_eq(name, rule_input->op_name, pcmk__str_none)) {
crm_trace(PCMK_XE_OP_EXPRESSION " %s is unsatisfied because "
"actual name '%s' doesn't match '%s'",
id, pcmk__s(rule_input->op_name, ""), name);
return pcmk_rc_op_unsatisfied;
}
// Compare operation interval (unspecified interval matches all)
if ((interval_s != NULL) && (interval_ms != rule_input->op_interval_ms)) {
crm_trace(PCMK_XE_OP_EXPRESSION " %s is unsatisfied because "
"actual interval %s doesn't match %s",
id, pcmk__readable_interval(rule_input->op_interval_ms),
pcmk__readable_interval(interval_ms));
return pcmk_rc_op_unsatisfied;
}
crm_trace(PCMK_XE_OP_EXPRESSION " %s is satisfied (name %s, interval %s)",
id, name, pcmk__readable_interval(rule_input->op_interval_ms));
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Evaluate a rule condition
*
* \param[in,out] condition XML containing a rule condition (a subrule, or an
* expression of any type)
* \param[in] rule_input Values used to evaluate rule criteria
* \param[out] next_change If not NULL, set to when evaluation will change
*
* \return Standard Pacemaker return code (\c pcmk_rc_ok if the condition
* passes, some other value if it does not)
*/
int
pcmk__evaluate_condition(xmlNode *condition,
const pcmk_rule_input_t *rule_input,
crm_time_t *next_change)
{
if ((condition == NULL) || (rule_input == NULL)) {
return EINVAL;
}
switch (pcmk__condition_type(condition)) {
case pcmk__condition_rule:
return pcmk_evaluate_rule(condition, rule_input, next_change);
case pcmk__condition_attribute:
case pcmk__condition_location:
return pcmk__evaluate_attr_expression(condition, rule_input);
case pcmk__condition_datetime:
{
int rc = pcmk__evaluate_date_expression(condition,
rule_input->now,
next_change);
return (rc == pcmk_rc_within_range)? pcmk_rc_ok : rc;
}
case pcmk__condition_resource:
return pcmk__evaluate_rsc_expression(condition, rule_input);
case pcmk__condition_operation:
return pcmk__evaluate_op_expression(condition, rule_input);
default: // Not possible with schema validation enabled
pcmk__config_err("Treating rule condition %s as not passing "
"because %s is not a valid condition type",
pcmk__s(pcmk__xe_id(condition), "without ID"),
(const char *) condition->name);
return pcmk_rc_unpack_error;
}
}
/*!
* \brief Evaluate a single rule, including all its conditions
*
* \param[in,out] rule XML containing a rule definition or its id-ref
* \param[in] rule_input Values used to evaluate rule criteria
* \param[out] next_change If not NULL, set to when evaluation will change
*
* \return Standard Pacemaker return code (\c pcmk_rc_ok if the rule is
* satisfied, some other value if it is not)
*/
int
pcmk_evaluate_rule(xmlNode *rule, const pcmk_rule_input_t *rule_input,
crm_time_t *next_change)
{
bool empty = true;
int rc = pcmk_rc_ok;
const char *id = NULL;
const char *value = NULL;
enum pcmk__combine combine = pcmk__combine_unknown;
if ((rule == NULL) || (rule_input == NULL)) {
return EINVAL;
}
rule = expand_idref(rule, NULL);
if (rule == NULL) {
// Not possible with schema validation enabled; message already logged
return pcmk_rc_unpack_error;
}
// Validate XML ID
id = pcmk__xe_id(rule);
if (pcmk__str_empty(id)) {
/* @COMPAT When we can break behavioral backward compatibility,
* fail the rule
*/
pcmk__config_warn(PCMK_XE_RULE " has no " PCMK_XA_ID);
id = "without ID"; // for logging
}
value = crm_element_value(rule, PCMK_XA_BOOLEAN_OP);
combine = pcmk__parse_combine(value);
switch (combine) {
case pcmk__combine_and:
// For "and", rc defaults to success (reset on failure below)
break;
case pcmk__combine_or:
// For "or", rc defaults to failure (reset on success below)
rc = pcmk_rc_op_unsatisfied;
break;
default:
/* @COMPAT When we can break behavioral backward compatibility,
* return pcmk_rc_unpack_error
*/
pcmk__config_warn("Rule %s has invalid " PCMK_XA_BOOLEAN_OP
" value '%s', using default '" PCMK_VALUE_AND "'",
pcmk__xe_id(rule), value);
combine = pcmk__combine_and;
break;
}
// Evaluate each condition
for (xmlNode *condition = pcmk__xe_first_child(rule, NULL, NULL, NULL);
condition != NULL; condition = pcmk__xe_next(condition)) {
empty = false;
if (pcmk__evaluate_condition(condition, rule_input,
next_change) == pcmk_rc_ok) {
if (combine == pcmk__combine_or) {
rc = pcmk_rc_ok; // Any pass is final for "or"
break;
}
} else if (combine == pcmk__combine_and) {
rc = pcmk_rc_op_unsatisfied; // Any failure is final for "and"
break;
}
}
if (empty) { // Not possible with schema validation enabled
/* @COMPAT Currently, we don't actually ignore "or" rules because
* rc is initialized to failure above in that case. When we can break
* backward compatibility, reset rc to pcmk_rc_ok here.
*/
pcmk__config_warn("Ignoring rule %s because it contains no conditions",
id);
}
crm_trace("Rule %s is %ssatisfied", id, ((rc == pcmk_rc_ok)? "" : "not "));
return rc;
}
/*!
* \internal
* \brief Evaluate all rules contained within an element
*
* \param[in,out] xml XML element possibly containing rule subelements
* \param[in] rule_input Values used to evaluate rule criteria
* \param[out] next_change If not NULL, set to when evaluation will change
*
* \return Standard Pacemaker return code (pcmk_rc_ok if there are no contained
* rules or any contained rule passes, otherwise the result of the last
* rule)
* \deprecated On code paths leading to this function, the schema allows
* multiple top-level rules only in the deprecated lifetime element
* of location constraints. The code also allows multiple top-level
* rules when unpacking attribute sets, but this is deprecated and
* already prevented by schema validation. This function can be
* dropped when support for those is dropped.
*/
int
pcmk__evaluate_rules(xmlNode *xml, const pcmk_rule_input_t *rule_input,
crm_time_t *next_change)
{
// If there are no rules, pass by default
int rc = pcmk_rc_ok;
bool have_rule = false;
for (xmlNode *rule = pcmk__xe_first_child(xml, PCMK_XE_RULE, NULL, NULL);
rule != NULL; rule = pcmk__xe_next_same(rule)) {
if (have_rule) {
pcmk__warn_once(pcmk__wo_multiple_rules,
"Support for multiple top-level rules is "
"deprecated (replace with a single rule containing "
"the existing rules with " PCMK_XA_BOOLEAN_OP
"set to " PCMK_VALUE_OR " instead)");
} else {
have_rule = true;
}
rc = pcmk_evaluate_rule(rule, rule_input, next_change);
if (rc == pcmk_rc_ok) {
break;
}
}
return rc;
}
diff --git a/lib/pacemaker/pcmk_sched_colocation.c b/lib/pacemaker/pcmk_sched_colocation.c
index 00155b7c4e..43edebc03c 100644
--- a/lib/pacemaker/pcmk_sched_colocation.c
+++ b/lib/pacemaker/pcmk_sched_colocation.c
@@ -1,1994 +1,1994 @@
/*
* 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 General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdbool.h>
#include <glib.h>
#include <crm/crm.h>
#include <crm/common/scheduler_internal.h>
#include <crm/pengine/status.h>
#include <pacemaker-internal.h>
#include "crm/common/util.h"
#include "crm/common/xml_internal.h"
#include "crm/common/xml.h"
#include "libpacemaker_private.h"
// Used to temporarily mark a node as unusable
#define INFINITY_HACK (PCMK_SCORE_INFINITY * -100)
/*!
* \internal
* \brief Compare two colocations according to priority
*
* Compare two colocations according to the order in which they should be
* considered, based on either their dependent resources or their primary
* resources -- preferring (in order):
* * Colocation that is not \c NULL
* * Colocation whose resource has higher priority
* * Colocation whose resource is of a higher-level variant
* (bundle > clone > group > primitive)
* * Colocation whose resource is promotable, if both are clones
* * Colocation whose resource has lower ID in lexicographic order
*
* \param[in] colocation1 First colocation to compare
* \param[in] colocation2 Second colocation to compare
* \param[in] dependent If \c true, compare colocations by dependent
* priority; otherwise compare them by primary priority
*
* \return A negative number if \p colocation1 should be considered first,
* a positive number if \p colocation2 should be considered first,
* or 0 if order doesn't matter
*/
static gint
cmp_colocation_priority(const pcmk__colocation_t *colocation1,
const pcmk__colocation_t *colocation2, bool dependent)
{
const pcmk_resource_t *rsc1 = NULL;
const pcmk_resource_t *rsc2 = NULL;
if (colocation1 == NULL) {
return 1;
}
if (colocation2 == NULL) {
return -1;
}
if (dependent) {
rsc1 = colocation1->dependent;
rsc2 = colocation2->dependent;
CRM_ASSERT(colocation1->primary != NULL);
} else {
rsc1 = colocation1->primary;
rsc2 = colocation2->primary;
CRM_ASSERT(colocation1->dependent != NULL);
}
CRM_ASSERT((rsc1 != NULL) && (rsc2 != NULL));
if (rsc1->priority > rsc2->priority) {
return -1;
}
if (rsc1->priority < rsc2->priority) {
return 1;
}
// Process clones before primitives and groups
if (rsc1->variant > rsc2->variant) {
return -1;
}
if (rsc1->variant < rsc2->variant) {
return 1;
}
/* @COMPAT scheduler <2.0.0: Process promotable clones before nonpromotable
* clones (probably unnecessary, but avoids having to update regression
* tests)
*/
if (pcmk__is_clone(rsc1)) {
if (pcmk_is_set(rsc1->flags, pcmk_rsc_promotable)
&& !pcmk_is_set(rsc2->flags, pcmk_rsc_promotable)) {
return -1;
}
if (!pcmk_is_set(rsc1->flags, pcmk_rsc_promotable)
&& pcmk_is_set(rsc2->flags, pcmk_rsc_promotable)) {
return 1;
}
}
return strcmp(rsc1->id, rsc2->id);
}
/*!
* \internal
* \brief Compare two colocations according to priority based on dependents
*
* Compare two colocations according to the order in which they should be
* considered, based on their dependent resources -- preferring (in order):
* * Colocation that is not \c NULL
* * Colocation whose resource has higher priority
* * Colocation whose resource is of a higher-level variant
* (bundle > clone > group > primitive)
* * Colocation whose resource is promotable, if both are clones
* * Colocation whose resource has lower ID in lexicographic order
*
* \param[in] a First colocation to compare
* \param[in] b Second colocation to compare
*
* \return A negative number if \p a should be considered first,
* a positive number if \p b should be considered first,
* or 0 if order doesn't matter
*/
static gint
cmp_dependent_priority(gconstpointer a, gconstpointer b)
{
return cmp_colocation_priority(a, b, true);
}
/*!
* \internal
* \brief Compare two colocations according to priority based on primaries
*
* Compare two colocations according to the order in which they should be
* considered, based on their primary resources -- preferring (in order):
* * Colocation that is not \c NULL
* * Colocation whose primary has higher priority
* * Colocation whose primary is of a higher-level variant
* (bundle > clone > group > primitive)
* * Colocation whose primary is promotable, if both are clones
* * Colocation whose primary has lower ID in lexicographic order
*
* \param[in] a First colocation to compare
* \param[in] b Second colocation to compare
*
* \return A negative number if \p a should be considered first,
* a positive number if \p b should be considered first,
* or 0 if order doesn't matter
*/
static gint
cmp_primary_priority(gconstpointer a, gconstpointer b)
{
return cmp_colocation_priority(a, b, false);
}
/*!
* \internal
* \brief Add a "this with" colocation constraint to a sorted list
*
* \param[in,out] list List of constraints to add \p colocation to
* \param[in] colocation Colocation constraint to add to \p list
* \param[in] rsc Resource whose colocations we're getting (for
* logging only)
*
* \note The list will be sorted using cmp_primary_priority().
*/
void
pcmk__add_this_with(GList **list, const pcmk__colocation_t *colocation,
const pcmk_resource_t *rsc)
{
CRM_ASSERT((list != NULL) && (colocation != NULL) && (rsc != NULL));
pcmk__rsc_trace(rsc,
"Adding colocation %s (%s with %s using %s @%s) to "
"'this with' list for %s",
colocation->id, colocation->dependent->id,
colocation->primary->id, colocation->node_attribute,
pcmk_readable_score(colocation->score), rsc->id);
*list = g_list_insert_sorted(*list, (gpointer) colocation,
cmp_primary_priority);
}
/*!
* \internal
* \brief Add a list of "this with" colocation constraints to a list
*
* \param[in,out] list List of constraints to add \p addition to
* \param[in] addition List of colocation constraints to add to \p list
* \param[in] rsc Resource whose colocations we're getting (for
* logging only)
*
* \note The lists must be pre-sorted by cmp_primary_priority().
*/
void
pcmk__add_this_with_list(GList **list, GList *addition,
const pcmk_resource_t *rsc)
{
CRM_ASSERT((list != NULL) && (rsc != NULL));
pcmk__if_tracing(
{}, // Always add each colocation individually if tracing
{
if (*list == NULL) {
// Trivial case for efficiency if not tracing
*list = g_list_copy(addition);
return;
}
}
);
for (const GList *iter = addition; iter != NULL; iter = iter->next) {
pcmk__add_this_with(list, addition->data, rsc);
}
}
/*!
* \internal
* \brief Add a "with this" colocation constraint to a sorted list
*
* \param[in,out] list List of constraints to add \p colocation to
* \param[in] colocation Colocation constraint to add to \p list
* \param[in] rsc Resource whose colocations we're getting (for
* logging only)
*
* \note The list will be sorted using cmp_dependent_priority().
*/
void
pcmk__add_with_this(GList **list, const pcmk__colocation_t *colocation,
const pcmk_resource_t *rsc)
{
CRM_ASSERT((list != NULL) && (colocation != NULL) && (rsc != NULL));
pcmk__rsc_trace(rsc,
"Adding colocation %s (%s with %s using %s @%s) to "
"'with this' list for %s",
colocation->id, colocation->dependent->id,
colocation->primary->id, colocation->node_attribute,
pcmk_readable_score(colocation->score), rsc->id);
*list = g_list_insert_sorted(*list, (gpointer) colocation,
cmp_dependent_priority);
}
/*!
* \internal
* \brief Add a list of "with this" colocation constraints to a list
*
* \param[in,out] list List of constraints to add \p addition to
* \param[in] addition List of colocation constraints to add to \p list
* \param[in] rsc Resource whose colocations we're getting (for
* logging only)
*
* \note The lists must be pre-sorted by cmp_dependent_priority().
*/
void
pcmk__add_with_this_list(GList **list, GList *addition,
const pcmk_resource_t *rsc)
{
CRM_ASSERT((list != NULL) && (rsc != NULL));
pcmk__if_tracing(
{}, // Always add each colocation individually if tracing
{
if (*list == NULL) {
// Trivial case for efficiency if not tracing
*list = g_list_copy(addition);
return;
}
}
);
for (const GList *iter = addition; iter != NULL; iter = iter->next) {
pcmk__add_with_this(list, addition->data, rsc);
}
}
/*!
* \internal
* \brief Add orderings necessary for an anti-colocation constraint
*
* \param[in,out] first_rsc One resource in an anti-colocation
* \param[in] first_role Anti-colocation role of \p first_rsc
* \param[in] then_rsc Other resource in the anti-colocation
* \param[in] then_role Anti-colocation role of \p then_rsc
*/
static void
anti_colocation_order(pcmk_resource_t *first_rsc, int first_role,
pcmk_resource_t *then_rsc, int then_role)
{
const char *first_tasks[] = { NULL, NULL };
const char *then_tasks[] = { NULL, NULL };
/* Actions to make first_rsc lose first_role */
if (first_role == pcmk_role_promoted) {
first_tasks[0] = PCMK_ACTION_DEMOTE;
} else {
first_tasks[0] = PCMK_ACTION_STOP;
if (first_role == pcmk_role_unpromoted) {
first_tasks[1] = PCMK_ACTION_PROMOTE;
}
}
/* Actions to make then_rsc gain then_role */
if (then_role == pcmk_role_promoted) {
then_tasks[0] = PCMK_ACTION_PROMOTE;
} else {
then_tasks[0] = PCMK_ACTION_START;
if (then_role == pcmk_role_unpromoted) {
then_tasks[1] = PCMK_ACTION_DEMOTE;
}
}
for (int first_lpc = 0;
(first_lpc <= 1) && (first_tasks[first_lpc] != NULL); first_lpc++) {
for (int then_lpc = 0;
(then_lpc <= 1) && (then_tasks[then_lpc] != NULL); then_lpc++) {
pcmk__order_resource_actions(first_rsc, first_tasks[first_lpc],
then_rsc, then_tasks[then_lpc],
pcmk__ar_if_required_on_same_node);
}
}
}
/*!
* \internal
* \brief Add a new colocation constraint to scheduler data
*
* \param[in] id XML ID for this constraint
* \param[in] node_attr Colocate by this attribute (NULL for #uname)
* \param[in] score Constraint score
* \param[in,out] dependent Resource to be colocated
* \param[in,out] primary Resource to colocate \p dependent with
* \param[in] dependent_role Current role of \p dependent
* \param[in] primary_role Current role of \p primary
* \param[in] flags Group of enum pcmk__coloc_flags
*/
void
pcmk__new_colocation(const char *id, const char *node_attr, int score,
pcmk_resource_t *dependent, pcmk_resource_t *primary,
const char *dependent_role, const char *primary_role,
uint32_t flags)
{
pcmk__colocation_t *new_con = NULL;
CRM_CHECK(id != NULL, return);
if ((dependent == NULL) || (primary == NULL)) {
pcmk__config_err("Ignoring colocation '%s' because resource "
"does not exist", id);
return;
}
if (score == 0) {
pcmk__rsc_trace(dependent,
"Ignoring colocation '%s' (%s with %s) because score is 0",
id, dependent->id, primary->id);
return;
}
new_con = pcmk__assert_alloc(1, sizeof(pcmk__colocation_t));
if (pcmk__str_eq(dependent_role, PCMK_ROLE_STARTED,
pcmk__str_null_matches|pcmk__str_casei)) {
dependent_role = PCMK__ROLE_UNKNOWN;
}
if (pcmk__str_eq(primary_role, PCMK_ROLE_STARTED,
pcmk__str_null_matches|pcmk__str_casei)) {
primary_role = PCMK__ROLE_UNKNOWN;
}
new_con->id = id;
new_con->dependent = dependent;
new_con->primary = primary;
new_con->score = score;
new_con->dependent_role = pcmk_parse_role(dependent_role);
new_con->primary_role = pcmk_parse_role(primary_role);
new_con->node_attribute = pcmk__s(node_attr, CRM_ATTR_UNAME);
new_con->flags = flags;
pcmk__add_this_with(&(dependent->rsc_cons), new_con, dependent);
pcmk__add_with_this(&(primary->rsc_cons_lhs), new_con, primary);
dependent->cluster->colocation_constraints = g_list_prepend(
dependent->cluster->colocation_constraints, new_con);
if (score <= -PCMK_SCORE_INFINITY) {
anti_colocation_order(dependent, new_con->dependent_role, primary,
new_con->primary_role);
anti_colocation_order(primary, new_con->primary_role, dependent,
new_con->dependent_role);
}
}
/*!
* \internal
* \brief Return the boolean influence corresponding to configuration
*
* \param[in] coloc_id Colocation XML ID (for error logging)
* \param[in] rsc Resource involved in constraint (for default)
* \param[in] influence_s String value of \c PCMK_XA_INFLUENCE option
*
* \return \c pcmk__coloc_influence if string evaluates true, or string is
* \c NULL or invalid and resource's \c PCMK_META_CRITICAL option
* evaluates true, otherwise \c pcmk__coloc_none
*/
static uint32_t
unpack_influence(const char *coloc_id, const pcmk_resource_t *rsc,
const char *influence_s)
{
if (influence_s != NULL) {
int influence_i = 0;
if (crm_str_to_boolean(influence_s, &influence_i) < 0) {
pcmk__config_err("Constraint '%s' has invalid value for "
PCMK_XA_INFLUENCE " (using default)",
coloc_id);
} else {
return (influence_i == 0)? pcmk__coloc_none : pcmk__coloc_influence;
}
}
if (pcmk_is_set(rsc->flags, pcmk_rsc_critical)) {
return pcmk__coloc_influence;
}
return pcmk__coloc_none;
}
static void
unpack_colocation_set(xmlNode *set, int score, const char *coloc_id,
const char *influence_s, pcmk_scheduler_t *scheduler)
{
xmlNode *xml_rsc = NULL;
pcmk_resource_t *other = NULL;
pcmk_resource_t *resource = NULL;
const char *set_id = pcmk__xe_id(set);
const char *role = crm_element_value(set, PCMK_XA_ROLE);
bool with_previous = false;
int local_score = score;
bool sequential = false;
uint32_t flags = pcmk__coloc_none;
const char *xml_rsc_id = NULL;
const char *score_s = crm_element_value(set, PCMK_XA_SCORE);
if (score_s) {
local_score = char2score(score_s);
}
if (local_score == 0) {
crm_trace("Ignoring colocation '%s' for set '%s' because score is 0",
coloc_id, set_id);
return;
}
/* @COMPAT The deprecated PCMK__XA_ORDERING attribute specifies whether
* resources in a positive-score set are colocated with the previous or next
* resource.
*/
if (pcmk__str_eq(crm_element_value(set, PCMK__XA_ORDERING),
PCMK__VALUE_GROUP,
pcmk__str_null_matches|pcmk__str_casei)) {
with_previous = true;
} else {
pcmk__warn_once(pcmk__wo_set_ordering,
"Support for '" PCMK__XA_ORDERING "' other than"
" '" PCMK__VALUE_GROUP "' in " PCMK_XE_RESOURCE_SET
" (such as %s) is deprecated and will be removed in a"
" future release",
set_id);
}
if ((pcmk__xe_get_bool_attr(set, PCMK_XA_SEQUENTIAL,
&sequential) == pcmk_rc_ok)
&& !sequential) {
return;
}
if (local_score > 0) {
for (xml_rsc = pcmk__xe_first_child(set, PCMK_XE_RESOURCE_REF, NULL,
NULL);
xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
xml_rsc_id = pcmk__xe_id(xml_rsc);
resource = pcmk__find_constraint_resource(scheduler->resources,
xml_rsc_id);
if (resource == NULL) {
// Should be possible only with validation disabled
pcmk__config_err("Ignoring %s and later resources in set %s: "
"No such resource", xml_rsc_id, set_id);
return;
}
if (other != NULL) {
flags = pcmk__coloc_explicit
| unpack_influence(coloc_id, resource, influence_s);
if (with_previous) {
pcmk__rsc_trace(resource, "Colocating %s with %s in set %s",
resource->id, other->id, set_id);
pcmk__new_colocation(set_id, NULL, local_score, resource,
other, role, role, flags);
} else {
pcmk__rsc_trace(resource, "Colocating %s with %s in set %s",
other->id, resource->id, set_id);
pcmk__new_colocation(set_id, NULL, local_score, other,
resource, role, role, flags);
}
}
other = resource;
}
} else {
/* Anti-colocating with every prior resource is
* the only way to ensure the intuitive result
* (i.e. that no one in the set can run with anyone else in the set)
*/
for (xml_rsc = pcmk__xe_first_child(set, PCMK_XE_RESOURCE_REF, NULL,
NULL);
xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
xmlNode *xml_rsc_with = NULL;
xml_rsc_id = pcmk__xe_id(xml_rsc);
resource = pcmk__find_constraint_resource(scheduler->resources,
xml_rsc_id);
if (resource == NULL) {
// Should be possible only with validation disabled
pcmk__config_err("Ignoring %s and later resources in set %s: "
"No such resource", xml_rsc_id, set_id);
return;
}
flags = pcmk__coloc_explicit
| unpack_influence(coloc_id, resource, influence_s);
for (xml_rsc_with = pcmk__xe_first_child(set, PCMK_XE_RESOURCE_REF,
NULL, NULL);
xml_rsc_with != NULL;
xml_rsc_with = pcmk__xe_next_same(xml_rsc_with)) {
xml_rsc_id = pcmk__xe_id(xml_rsc_with);
if (pcmk__str_eq(resource->id, xml_rsc_id, pcmk__str_none)) {
break;
}
other = pcmk__find_constraint_resource(scheduler->resources,
xml_rsc_id);
CRM_ASSERT(other != NULL); // We already processed it
pcmk__new_colocation(set_id, NULL, local_score,
resource, other, role, role, flags);
}
}
}
}
/*!
* \internal
* \brief Colocate two resource sets relative to each other
*
* \param[in] id Colocation XML ID
* \param[in] set1 Dependent set
* \param[in] set2 Primary set
* \param[in] score Colocation score
* \param[in] influence_s Value of colocation's \c PCMK_XA_INFLUENCE
* attribute
* \param[in,out] scheduler Scheduler data
*/
static void
colocate_rsc_sets(const char *id, const xmlNode *set1, const xmlNode *set2,
int score, const char *influence_s,
pcmk_scheduler_t *scheduler)
{
xmlNode *xml_rsc = NULL;
pcmk_resource_t *rsc_1 = NULL;
pcmk_resource_t *rsc_2 = NULL;
const char *xml_rsc_id = NULL;
const char *role_1 = crm_element_value(set1, PCMK_XA_ROLE);
const char *role_2 = crm_element_value(set2, PCMK_XA_ROLE);
int rc = pcmk_rc_ok;
bool sequential = false;
uint32_t flags = pcmk__coloc_none;
if (score == 0) {
crm_trace("Ignoring colocation '%s' between sets %s and %s "
"because score is 0",
id, pcmk__xe_id(set1), pcmk__xe_id(set2));
return;
}
rc = pcmk__xe_get_bool_attr(set1, PCMK_XA_SEQUENTIAL, &sequential);
if ((rc != pcmk_rc_ok) || sequential) {
// Get the first one
xml_rsc = pcmk__xe_first_child(set1, PCMK_XE_RESOURCE_REF, NULL, NULL);
if (xml_rsc != NULL) {
xml_rsc_id = pcmk__xe_id(xml_rsc);
rsc_1 = pcmk__find_constraint_resource(scheduler->resources,
xml_rsc_id);
if (rsc_1 == NULL) {
// Should be possible only with validation disabled
pcmk__config_err("Ignoring colocation of set %s with set %s "
"because first resource %s not found",
pcmk__xe_id(set1), pcmk__xe_id(set2),
xml_rsc_id);
return;
}
}
}
rc = pcmk__xe_get_bool_attr(set2, PCMK_XA_SEQUENTIAL, &sequential);
if ((rc != pcmk_rc_ok) || sequential) {
// Get the last one
for (xml_rsc = pcmk__xe_first_child(set2, PCMK_XE_RESOURCE_REF, NULL,
NULL);
xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
xml_rsc_id = pcmk__xe_id(xml_rsc);
}
rsc_2 = pcmk__find_constraint_resource(scheduler->resources,
xml_rsc_id);
if (rsc_2 == NULL) {
// Should be possible only with validation disabled
pcmk__config_err("Ignoring colocation of set %s with set %s "
"because last resource %s not found",
pcmk__xe_id(set1), pcmk__xe_id(set2), xml_rsc_id);
return;
}
}
if ((rsc_1 != NULL) && (rsc_2 != NULL)) { // Both sets are sequential
flags = pcmk__coloc_explicit | unpack_influence(id, rsc_1, influence_s);
pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2, role_1, role_2,
flags);
} else if (rsc_1 != NULL) { // Only set1 is sequential
flags = pcmk__coloc_explicit | unpack_influence(id, rsc_1, influence_s);
for (xml_rsc = pcmk__xe_first_child(set2, PCMK_XE_RESOURCE_REF, NULL,
NULL);
xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
xml_rsc_id = pcmk__xe_id(xml_rsc);
rsc_2 = pcmk__find_constraint_resource(scheduler->resources,
xml_rsc_id);
if (rsc_2 == NULL) {
// Should be possible only with validation disabled
pcmk__config_err("Ignoring set %s colocation with resource %s "
"in set %s: No such resource",
pcmk__xe_id(set1), xml_rsc_id,
pcmk__xe_id(set2));
continue;
}
pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2, role_1,
role_2, flags);
}
} else if (rsc_2 != NULL) { // Only set2 is sequential
for (xml_rsc = pcmk__xe_first_child(set1, PCMK_XE_RESOURCE_REF, NULL,
NULL);
xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
xml_rsc_id = pcmk__xe_id(xml_rsc);
rsc_1 = pcmk__find_constraint_resource(scheduler->resources,
xml_rsc_id);
if (rsc_1 == NULL) {
// Should be possible only with validation disabled
pcmk__config_err("Ignoring colocation of set %s resource %s "
"with set %s: No such resource",
pcmk__xe_id(set1), xml_rsc_id,
pcmk__xe_id(set2));
continue;
}
flags = pcmk__coloc_explicit
| unpack_influence(id, rsc_1, influence_s);
pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2, role_1,
role_2, flags);
}
} else { // Neither set is sequential
for (xml_rsc = pcmk__xe_first_child(set1, PCMK_XE_RESOURCE_REF, NULL,
NULL);
xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
xmlNode *xml_rsc_2 = NULL;
xml_rsc_id = pcmk__xe_id(xml_rsc);
rsc_1 = pcmk__find_constraint_resource(scheduler->resources,
xml_rsc_id);
if (rsc_1 == NULL) {
// Should be possible only with validation disabled
pcmk__config_err("Ignoring colocation of set %s resource %s "
"with set %s: No such resource",
pcmk__xe_id(set1), xml_rsc_id,
pcmk__xe_id(set2));
continue;
}
flags = pcmk__coloc_explicit
| unpack_influence(id, rsc_1, influence_s);
for (xml_rsc_2 = pcmk__xe_first_child(set2, PCMK_XE_RESOURCE_REF,
NULL, NULL);
xml_rsc_2 != NULL; xml_rsc_2 = pcmk__xe_next_same(xml_rsc_2)) {
xml_rsc_id = pcmk__xe_id(xml_rsc_2);
rsc_2 = pcmk__find_constraint_resource(scheduler->resources,
xml_rsc_id);
if (rsc_2 == NULL) {
// Should be possible only with validation disabled
pcmk__config_err("Ignoring colocation of set %s resource "
"%s with set %s resource %s: No such "
"resource",
pcmk__xe_id(set1), pcmk__xe_id(xml_rsc),
pcmk__xe_id(set2), xml_rsc_id);
continue;
}
pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2,
role_1, role_2, flags);
}
}
}
}
static void
unpack_simple_colocation(xmlNode *xml_obj, const char *id,
const char *influence_s, pcmk_scheduler_t *scheduler)
{
int score_i = 0;
uint32_t flags = pcmk__coloc_none;
const char *score = crm_element_value(xml_obj, PCMK_XA_SCORE);
const char *dependent_id = crm_element_value(xml_obj, PCMK_XA_RSC);
const char *primary_id = crm_element_value(xml_obj, PCMK_XA_WITH_RSC);
const char *dependent_role = crm_element_value(xml_obj, PCMK_XA_RSC_ROLE);
const char *primary_role = crm_element_value(xml_obj,
PCMK_XA_WITH_RSC_ROLE);
const char *attr = crm_element_value(xml_obj, PCMK_XA_NODE_ATTRIBUTE);
const char *primary_instance = NULL;
const char *dependent_instance = NULL;
pcmk_resource_t *primary = NULL;
pcmk_resource_t *dependent = NULL;
primary = pcmk__find_constraint_resource(scheduler->resources, primary_id);
dependent = pcmk__find_constraint_resource(scheduler->resources,
dependent_id);
// @COMPAT: Deprecated since 2.1.5
primary_instance = crm_element_value(xml_obj, PCMK__XA_WITH_RSC_INSTANCE);
dependent_instance = crm_element_value(xml_obj, PCMK__XA_RSC_INSTANCE);
if (dependent_instance != NULL) {
pcmk__warn_once(pcmk__wo_coloc_inst,
"Support for " PCMK__XA_RSC_INSTANCE " is deprecated "
"and will be removed in a future release");
}
if (primary_instance != NULL) {
pcmk__warn_once(pcmk__wo_coloc_inst,
"Support for " PCMK__XA_WITH_RSC_INSTANCE " is "
"deprecated and will be removed in a future release");
}
if (dependent == NULL) {
pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
"does not exist", id, dependent_id);
return;
} else if (primary == NULL) {
pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
"does not exist", id, primary_id);
return;
} else if ((dependent_instance != NULL) && !pcmk__is_clone(dependent)) {
pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
"is not a clone but instance '%s' was requested",
id, dependent_id, dependent_instance);
return;
} else if ((primary_instance != NULL) && !pcmk__is_clone(primary)) {
pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
"is not a clone but instance '%s' was requested",
id, primary_id, primary_instance);
return;
}
if (dependent_instance != NULL) {
dependent = find_clone_instance(dependent, dependent_instance);
if (dependent == NULL) {
pcmk__config_warn("Ignoring constraint '%s' because resource '%s' "
"does not have an instance '%s'",
id, dependent_id, dependent_instance);
return;
}
}
if (primary_instance != NULL) {
primary = find_clone_instance(primary, primary_instance);
if (primary == NULL) {
pcmk__config_warn("Ignoring constraint '%s' because resource '%s' "
"does not have an instance '%s'",
- "'%s'", id, primary_id, primary_instance);
+ id, primary_id, primary_instance);
return;
}
}
if (pcmk__xe_attr_is_true(xml_obj, PCMK_XA_SYMMETRICAL)) {
pcmk__config_warn("The colocation constraint "
"'" PCMK_XA_SYMMETRICAL "' attribute has been "
"removed");
}
if (score) {
score_i = char2score(score);
}
flags = pcmk__coloc_explicit | unpack_influence(id, dependent, influence_s);
pcmk__new_colocation(id, attr, score_i, dependent, primary,
dependent_role, primary_role, flags);
}
// \return Standard Pacemaker return code
static int
unpack_colocation_tags(xmlNode *xml_obj, xmlNode **expanded_xml,
pcmk_scheduler_t *scheduler)
{
const char *id = NULL;
const char *dependent_id = NULL;
const char *primary_id = NULL;
const char *dependent_role = NULL;
const char *primary_role = NULL;
pcmk_resource_t *dependent = NULL;
pcmk_resource_t *primary = NULL;
pcmk_tag_t *dependent_tag = NULL;
pcmk_tag_t *primary_tag = NULL;
xmlNode *dependent_set = NULL;
xmlNode *primary_set = NULL;
bool any_sets = false;
*expanded_xml = NULL;
CRM_CHECK(xml_obj != NULL, return EINVAL);
id = pcmk__xe_id(xml_obj);
if (id == NULL) {
pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID,
xml_obj->name);
return pcmk_rc_unpack_error;
}
// Check whether there are any resource sets with template or tag references
*expanded_xml = pcmk__expand_tags_in_sets(xml_obj, scheduler);
if (*expanded_xml != NULL) {
crm_log_xml_trace(*expanded_xml, "Expanded " PCMK_XE_RSC_COLOCATION);
return pcmk_rc_ok;
}
dependent_id = crm_element_value(xml_obj, PCMK_XA_RSC);
primary_id = crm_element_value(xml_obj, PCMK_XA_WITH_RSC);
if ((dependent_id == NULL) || (primary_id == NULL)) {
return pcmk_rc_ok;
}
if (!pcmk__valid_resource_or_tag(scheduler, dependent_id, &dependent,
&dependent_tag)) {
pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
"valid resource or tag", id, dependent_id);
return pcmk_rc_unpack_error;
}
if (!pcmk__valid_resource_or_tag(scheduler, primary_id, &primary,
&primary_tag)) {
pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
"valid resource or tag", id, primary_id);
return pcmk_rc_unpack_error;
}
if ((dependent != NULL) && (primary != NULL)) {
/* Neither side references any template/tag. */
return pcmk_rc_ok;
}
if ((dependent_tag != NULL) && (primary_tag != NULL)) {
// A colocation constraint between two templates/tags makes no sense
pcmk__config_err("Ignoring constraint '%s' because two templates or "
"tags cannot be colocated", id);
return pcmk_rc_unpack_error;
}
dependent_role = crm_element_value(xml_obj, PCMK_XA_RSC_ROLE);
primary_role = crm_element_value(xml_obj, PCMK_XA_WITH_RSC_ROLE);
*expanded_xml = pcmk__xml_copy(NULL, xml_obj);
/* Convert dependent's template/tag reference into constraint
* PCMK_XE_RESOURCE_SET
*/
if (!pcmk__tag_to_set(*expanded_xml, &dependent_set, PCMK_XA_RSC, true,
scheduler)) {
free_xml(*expanded_xml);
*expanded_xml = NULL;
return pcmk_rc_unpack_error;
}
if (dependent_set != NULL) {
if (dependent_role != NULL) {
/* Move PCMK_XA_RSC_ROLE into converted PCMK_XE_RESOURCE_SET as
* PCMK_XA_ROLE
*/
crm_xml_add(dependent_set, PCMK_XA_ROLE, dependent_role);
pcmk__xe_remove_attr(*expanded_xml, PCMK_XA_RSC_ROLE);
}
any_sets = true;
}
/* Convert primary's template/tag reference into constraint
* PCMK_XE_RESOURCE_SET
*/
if (!pcmk__tag_to_set(*expanded_xml, &primary_set, PCMK_XA_WITH_RSC, true,
scheduler)) {
free_xml(*expanded_xml);
*expanded_xml = NULL;
return pcmk_rc_unpack_error;
}
if (primary_set != NULL) {
if (primary_role != NULL) {
/* Move PCMK_XA_WITH_RSC_ROLE into converted PCMK_XE_RESOURCE_SET as
* PCMK_XA_ROLE
*/
crm_xml_add(primary_set, PCMK_XA_ROLE, primary_role);
pcmk__xe_remove_attr(*expanded_xml, PCMK_XA_WITH_RSC_ROLE);
}
any_sets = true;
}
if (any_sets) {
crm_log_xml_trace(*expanded_xml, "Expanded " PCMK_XE_RSC_COLOCATION);
} else {
free_xml(*expanded_xml);
*expanded_xml = NULL;
}
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Parse a colocation constraint from XML into scheduler data
*
* \param[in,out] xml_obj Colocation constraint XML to unpack
* \param[in,out] scheduler Scheduler data to add constraint to
*/
void
pcmk__unpack_colocation(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
{
int score_i = 0;
xmlNode *set = NULL;
xmlNode *last = NULL;
xmlNode *orig_xml = NULL;
xmlNode *expanded_xml = NULL;
const char *id = crm_element_value(xml_obj, PCMK_XA_ID);
const char *score = NULL;
const char *influence_s = NULL;
if (pcmk__str_empty(id)) {
pcmk__config_err("Ignoring " PCMK_XE_RSC_COLOCATION
" without " CRM_ATTR_ID);
return;
}
if (unpack_colocation_tags(xml_obj, &expanded_xml,
scheduler) != pcmk_rc_ok) {
return;
}
if (expanded_xml != NULL) {
orig_xml = xml_obj;
xml_obj = expanded_xml;
}
score = crm_element_value(xml_obj, PCMK_XA_SCORE);
if (score != NULL) {
score_i = char2score(score);
}
influence_s = crm_element_value(xml_obj, PCMK_XA_INFLUENCE);
for (set = pcmk__xe_first_child(xml_obj, PCMK_XE_RESOURCE_SET, NULL, NULL);
set != NULL; set = pcmk__xe_next_same(set)) {
set = expand_idref(set, scheduler->input);
if (set == NULL) { // Configuration error, message already logged
if (expanded_xml != NULL) {
free_xml(expanded_xml);
}
return;
}
if (pcmk__str_empty(pcmk__xe_id(set))) {
pcmk__config_err("Ignoring " PCMK_XE_RESOURCE_SET
" without " CRM_ATTR_ID);
continue;
}
unpack_colocation_set(set, score_i, id, influence_s, scheduler);
if (last != NULL) {
colocate_rsc_sets(id, last, set, score_i, influence_s, scheduler);
}
last = set;
}
if (expanded_xml) {
free_xml(expanded_xml);
xml_obj = orig_xml;
}
if (last == NULL) {
unpack_simple_colocation(xml_obj, id, influence_s, scheduler);
}
}
/*!
* \internal
* \brief Make actions of a given type unrunnable for a given resource
*
* \param[in,out] rsc Resource whose actions should be blocked
* \param[in] task Name of action to block
* \param[in] reason Unrunnable start action causing the block
*/
static void
mark_action_blocked(pcmk_resource_t *rsc, const char *task,
const pcmk_resource_t *reason)
{
GList *iter = NULL;
char *reason_text = crm_strdup_printf("colocation with %s", reason->id);
for (iter = rsc->actions; iter != NULL; iter = iter->next) {
pcmk_action_t *action = iter->data;
if (pcmk_is_set(action->flags, pcmk_action_runnable)
&& pcmk__str_eq(action->task, task, pcmk__str_none)) {
pcmk__clear_action_flags(action, pcmk_action_runnable);
pe_action_set_reason(action, reason_text, false);
pcmk__block_colocation_dependents(action);
pcmk__update_action_for_orderings(action, rsc->cluster);
}
}
// If parent resource can't perform an action, neither can any children
for (iter = rsc->children; iter != NULL; iter = iter->next) {
mark_action_blocked((pcmk_resource_t *) (iter->data), task, reason);
}
free(reason_text);
}
/*!
* \internal
* \brief If an action is unrunnable, block any relevant dependent actions
*
* If a given action is an unrunnable start or promote, block the start or
* promote actions of resources colocated with it, as appropriate to the
* colocations' configured roles.
*
* \param[in,out] action Action to check
*/
void
pcmk__block_colocation_dependents(pcmk_action_t *action)
{
GList *iter = NULL;
GList *colocations = NULL;
pcmk_resource_t *rsc = NULL;
bool is_start = false;
if (pcmk_is_set(action->flags, pcmk_action_runnable)) {
return; // Only unrunnable actions block dependents
}
is_start = pcmk__str_eq(action->task, PCMK_ACTION_START, pcmk__str_none);
if (!is_start
&& !pcmk__str_eq(action->task, PCMK_ACTION_PROMOTE, pcmk__str_none)) {
return; // Only unrunnable starts and promotes block dependents
}
CRM_ASSERT(action->rsc != NULL); // Start and promote are resource actions
/* If this resource is part of a collective resource, dependents are blocked
* only if all instances of the collective are unrunnable, so check the
* collective resource.
*/
rsc = uber_parent(action->rsc);
if (rsc->parent != NULL) {
rsc = rsc->parent; // Bundle
}
// Colocation fails only if entire primary can't reach desired role
for (iter = rsc->children; iter != NULL; iter = iter->next) {
pcmk_resource_t *child = iter->data;
pcmk_action_t *child_action = find_first_action(child->actions, NULL,
action->task, NULL);
if ((child_action == NULL)
|| pcmk_is_set(child_action->flags, pcmk_action_runnable)) {
crm_trace("Not blocking %s colocation dependents because "
"at least %s has runnable %s",
rsc->id, child->id, action->task);
return; // At least one child can reach desired role
}
}
crm_trace("Blocking %s colocation dependents due to unrunnable %s %s",
rsc->id, action->rsc->id, action->task);
// Check each colocation where this resource is primary
colocations = pcmk__with_this_colocations(rsc);
for (iter = colocations; iter != NULL; iter = iter->next) {
pcmk__colocation_t *colocation = iter->data;
if (colocation->score < PCMK_SCORE_INFINITY) {
continue; // Only mandatory colocations block dependent
}
/* If the primary can't start, the dependent can't reach its colocated
* role, regardless of what the primary or dependent colocation role is.
*
* If the primary can't be promoted, the dependent can't reach its
* colocated role if the primary's colocation role is promoted.
*/
if (!is_start && (colocation->primary_role != pcmk_role_promoted)) {
continue;
}
// Block the dependent from reaching its colocated role
if (colocation->dependent_role == pcmk_role_promoted) {
mark_action_blocked(colocation->dependent, PCMK_ACTION_PROMOTE,
action->rsc);
} else {
mark_action_blocked(colocation->dependent, PCMK_ACTION_START,
action->rsc);
}
}
g_list_free(colocations);
}
/*!
* \internal
* \brief Get the resource to use for role comparisons
*
* A bundle replica includes a container and possibly an instance of the bundled
* resource. The dependent in a "with bundle" colocation is colocated with a
* particular bundle container. However, if the colocation includes a role, then
* the role must be checked on the bundled resource instance inside the
* container. The container itself will never be promoted; the bundled resource
* may be.
*
* If the given resource is a bundle replica container, return the resource
* inside it, if any. Otherwise, return the resource itself.
*
* \param[in] rsc Resource to check
*
* \return Resource to use for role comparisons
*/
static const pcmk_resource_t *
get_resource_for_role(const pcmk_resource_t *rsc)
{
if (pcmk_is_set(rsc->flags, pcmk_rsc_replica_container)) {
const pcmk_resource_t *child = pe__get_rsc_in_container(rsc);
if (child != NULL) {
return child;
}
}
return rsc;
}
/*!
* \internal
* \brief Determine how a colocation constraint should affect a resource
*
* Colocation constraints have different effects at different points in the
* scheduler sequence. Initially, they affect a resource's location; once that
* is determined, then for promotable clones they can affect a resource
* instance's role; after both are determined, the constraints no longer matter.
* Given a specific colocation constraint, check what has been done so far to
* determine what should be affected at the current point in the scheduler.
*
* \param[in] dependent Dependent resource in colocation
* \param[in] primary Primary resource in colocation
* \param[in] colocation Colocation constraint
* \param[in] preview If true, pretend resources have already been assigned
*
* \return How colocation constraint should be applied at this point
*/
enum pcmk__coloc_affects
pcmk__colocation_affects(const pcmk_resource_t *dependent,
const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation, bool preview)
{
const pcmk_resource_t *dependent_role_rsc = NULL;
const pcmk_resource_t *primary_role_rsc = NULL;
CRM_ASSERT((dependent != NULL) && (primary != NULL)
&& (colocation != NULL));
if (!preview && pcmk_is_set(primary->flags, pcmk_rsc_unassigned)) {
// Primary resource has not been assigned yet, so we can't do anything
return pcmk__coloc_affects_nothing;
}
dependent_role_rsc = get_resource_for_role(dependent);
primary_role_rsc = get_resource_for_role(primary);
if ((colocation->dependent_role >= pcmk_role_unpromoted)
&& (dependent_role_rsc->parent != NULL)
&& pcmk_is_set(dependent_role_rsc->parent->flags, pcmk_rsc_promotable)
&& !pcmk_is_set(dependent_role_rsc->flags, pcmk_rsc_unassigned)) {
/* This is a colocation by role, and the dependent is a promotable clone
* that has already been assigned, so the colocation should now affect
* the role.
*/
return pcmk__coloc_affects_role;
}
if (!preview && !pcmk_is_set(dependent->flags, pcmk_rsc_unassigned)) {
/* The dependent resource has already been through assignment, so the
* constraint no longer has any effect. Log an error if a mandatory
* colocation constraint has been violated.
*/
const pcmk_node_t *primary_node = primary->allocated_to;
if (dependent->allocated_to == NULL) {
crm_trace("Skipping colocation '%s': %s will not run anywhere",
colocation->id, dependent->id);
} else if (colocation->score >= PCMK_SCORE_INFINITY) {
// Dependent resource must colocate with primary resource
if (!pcmk__same_node(primary_node, dependent->allocated_to)) {
pcmk__sched_err("%s must be colocated with %s but is not "
"(%s vs. %s)",
dependent->id, primary->id,
pcmk__node_name(dependent->allocated_to),
pcmk__node_name(primary_node));
}
} else if (colocation->score <= -PCMK_SCORE_INFINITY) {
// Dependent resource must anti-colocate with primary resource
if (pcmk__same_node(dependent->allocated_to, primary_node)) {
pcmk__sched_err("%s and %s must be anti-colocated but are "
"assigned to the same node (%s)",
dependent->id, primary->id,
pcmk__node_name(primary_node));
}
}
return pcmk__coloc_affects_nothing;
}
if ((colocation->dependent_role != pcmk_role_unknown)
&& (colocation->dependent_role != dependent_role_rsc->next_role)) {
crm_trace("Skipping %scolocation '%s': dependent limited to %s role "
"but %s next role is %s",
((colocation->score < 0)? "anti-" : ""),
colocation->id, pcmk_role_text(colocation->dependent_role),
dependent_role_rsc->id,
pcmk_role_text(dependent_role_rsc->next_role));
return pcmk__coloc_affects_nothing;
}
if ((colocation->primary_role != pcmk_role_unknown)
&& (colocation->primary_role != primary_role_rsc->next_role)) {
crm_trace("Skipping %scolocation '%s': primary limited to %s role "
"but %s next role is %s",
((colocation->score < 0)? "anti-" : ""),
colocation->id, pcmk_role_text(colocation->primary_role),
primary_role_rsc->id,
pcmk_role_text(primary_role_rsc->next_role));
return pcmk__coloc_affects_nothing;
}
return pcmk__coloc_affects_location;
}
/*!
* \internal
* \brief Apply colocation to dependent for assignment purposes
*
* Update the allowed node scores of the dependent resource in a colocation,
* for the purposes of assigning it to a node.
*
* \param[in,out] dependent Dependent resource in colocation
* \param[in] primary Primary resource in colocation
* \param[in] colocation Colocation constraint
*/
void
pcmk__apply_coloc_to_scores(pcmk_resource_t *dependent,
const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation)
{
const char *attr = colocation->node_attribute;
const char *value = NULL;
GHashTable *work = NULL;
GHashTableIter iter;
pcmk_node_t *node = NULL;
if (primary->allocated_to != NULL) {
value = pcmk__colocation_node_attr(primary->allocated_to, attr,
primary);
} else if (colocation->score < 0) {
// Nothing to do (anti-colocation with something that is not running)
return;
}
work = pcmk__copy_node_table(dependent->allowed_nodes);
g_hash_table_iter_init(&iter, work);
while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
if (primary->allocated_to == NULL) {
node->weight = pcmk__add_scores(-colocation->score, node->weight);
pcmk__rsc_trace(dependent,
"Applied %s to %s score on %s (now %s after "
"subtracting %s because primary %s inactive)",
colocation->id, dependent->id,
pcmk__node_name(node),
pcmk_readable_score(node->weight),
pcmk_readable_score(colocation->score), primary->id);
continue;
}
if (pcmk__str_eq(pcmk__colocation_node_attr(node, attr, dependent),
value, pcmk__str_casei)) {
/* Add colocation score only if optional (or minus infinity). A
* mandatory colocation is a requirement rather than a preference,
* so we don't need to consider it for relative assignment purposes.
* The resource will simply be forbidden from running on the node if
* the primary isn't active there (via the condition above).
*/
if (colocation->score < PCMK_SCORE_INFINITY) {
node->weight = pcmk__add_scores(colocation->score,
node->weight);
pcmk__rsc_trace(dependent,
"Applied %s to %s score on %s (now %s after "
"adding %s)",
colocation->id, dependent->id,
pcmk__node_name(node),
pcmk_readable_score(node->weight),
pcmk_readable_score(colocation->score));
}
continue;
}
if (colocation->score >= PCMK_SCORE_INFINITY) {
/* Only mandatory colocations are relevant when the colocation
* attribute doesn't match, because an attribute not matching is not
* a negative preference -- the colocation is simply relevant only
* where it matches.
*/
node->weight = -PCMK_SCORE_INFINITY;
pcmk__rsc_trace(dependent,
"Banned %s from %s because colocation %s attribute %s "
"does not match",
dependent->id, pcmk__node_name(node),
colocation->id, attr);
}
}
if ((colocation->score <= -PCMK_SCORE_INFINITY)
|| (colocation->score >= PCMK_SCORE_INFINITY)
|| pcmk__any_node_available(work)) {
g_hash_table_destroy(dependent->allowed_nodes);
dependent->allowed_nodes = work;
work = NULL;
} else {
pcmk__rsc_info(dependent,
"%s: Rolling back scores from %s (no available nodes)",
dependent->id, primary->id);
}
if (work != NULL) {
g_hash_table_destroy(work);
}
}
/*!
* \internal
* \brief Apply colocation to dependent for role purposes
*
* Update the priority of the dependent resource in a colocation, for the
* purposes of selecting its role
*
* \param[in,out] dependent Dependent resource in colocation
* \param[in] primary Primary resource in colocation
* \param[in] colocation Colocation constraint
*
* \return The score added to the dependent's priority
*/
int
pcmk__apply_coloc_to_priority(pcmk_resource_t *dependent,
const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation)
{
const char *dependent_value = NULL;
const char *primary_value = NULL;
const char *attr = colocation->node_attribute;
int score_multiplier = 1;
int priority_delta = 0;
CRM_ASSERT((dependent != NULL) && (primary != NULL)
&& (colocation != NULL));
if (dependent->allocated_to == NULL) {
return 0;
}
if ((primary->allocated_to != NULL)
&& (colocation->primary_role != pcmk_role_unknown)) {
/* Colocation applies only if the primary's next role matches.
*
* If primary->allocated_to == NULL, we want to proceed past this block,
* so that dependent->allocated_to is marked ineligible for promotion.
*
* @TODO Why ignore a mandatory colocation in this case when we apply
* its negation in the mismatched value case?
*/
const pcmk_resource_t *role_rsc = get_resource_for_role(primary);
if (colocation->primary_role != role_rsc->next_role) {
return 0;
}
}
dependent_value = pcmk__colocation_node_attr(dependent->allocated_to, attr,
dependent);
primary_value = pcmk__colocation_node_attr(primary->allocated_to, attr,
primary);
if (!pcmk__str_eq(dependent_value, primary_value, pcmk__str_casei)) {
if ((colocation->score == PCMK_SCORE_INFINITY)
&& (colocation->dependent_role == pcmk_role_promoted)) {
/* For a mandatory promoted-role colocation, mark the dependent node
* ineligible to promote the dependent if its attribute value
* doesn't match the primary node's
*/
score_multiplier = -1;
} else {
// Otherwise, ignore the colocation if attribute values don't match
return 0;
}
} else if (colocation->dependent_role == pcmk_role_unpromoted) {
/* Node attribute values matched, so we want to avoid promoting the
* dependent on this node
*/
score_multiplier = -1;
}
priority_delta = score_multiplier * colocation->score;
dependent->priority = pcmk__add_scores(priority_delta, dependent->priority);
pcmk__rsc_trace(dependent,
"Applied %s to %s promotion priority (now %s after %s %d)",
colocation->id, dependent->id,
pcmk_readable_score(dependent->priority),
((score_multiplier == 1)? "adding" : "subtracting"),
colocation->score);
return priority_delta;
}
/*!
* \internal
* \brief Find score of highest-scored node that matches colocation attribute
*
* \param[in] colocation Colocation constraint being applied
* \param[in,out] rsc Resource whose allowed nodes should be searched
* \param[in] attr Colocation attribute name (must not be NULL)
* \param[in] value Colocation attribute value to require
*/
static int
best_node_score_matching_attr(const pcmk__colocation_t *colocation,
pcmk_resource_t *rsc, const char *attr,
const char *value)
{
GHashTable *allowed_nodes_orig = NULL;
GHashTableIter iter;
pcmk_node_t *node = NULL;
int best_score = -PCMK_SCORE_INFINITY;
const char *best_node = NULL;
if ((colocation != NULL) && (rsc == colocation->dependent)
&& pcmk_is_set(colocation->flags, pcmk__coloc_explicit)
&& pcmk__is_group(rsc->parent)
&& (rsc != rsc->parent->children->data)) {
/* The resource is a user-configured colocation's explicit dependent,
* and a group member other than the first, which means the group's
* location constraint scores were not applied to it (see
* pcmk__group_apply_location()). Explicitly consider those scores now.
*
* @TODO This does leave one suboptimal case: if the group itself or
* another member other than the first is explicitly colocated with
* the same primary, the primary will count the group's location scores
* multiple times. This is much less likely than a single member being
* explicitly colocated, so it's an acceptable tradeoff for now.
*/
allowed_nodes_orig = rsc->allowed_nodes;
rsc->allowed_nodes = pcmk__copy_node_table(allowed_nodes_orig);
for (GList *loc_iter = rsc->cluster->placement_constraints;
loc_iter != NULL; loc_iter = loc_iter->next) {
pcmk__location_t *location = loc_iter->data;
if (location->rsc == rsc->parent) {
rsc->cmds->apply_location(rsc, location);
}
}
}
// Find best allowed node with matching attribute
g_hash_table_iter_init(&iter, rsc->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
if ((node->weight > best_score)
&& pcmk__node_available(node, false, false)
&& pcmk__str_eq(value, pcmk__colocation_node_attr(node, attr, rsc),
pcmk__str_casei)) {
best_score = node->weight;
best_node = node->details->uname;
}
}
if (!pcmk__str_eq(attr, CRM_ATTR_UNAME, pcmk__str_none)) {
if (best_node == NULL) {
crm_info("No allowed node for %s matches node attribute %s=%s",
rsc->id, attr, value);
} else {
crm_info("Allowed node %s for %s had best score (%d) "
"of those matching node attribute %s=%s",
best_node, rsc->id, best_score, attr, value);
}
}
if (allowed_nodes_orig != NULL) {
g_hash_table_destroy(rsc->allowed_nodes);
rsc->allowed_nodes = allowed_nodes_orig;
}
return best_score;
}
/*!
* \internal
* \brief Check whether a resource is allowed only on a single node
*
* \param[in] rsc Resource to check
*
* \return \c true if \p rsc is allowed only on one node, otherwise \c false
*/
static bool
allowed_on_one(const pcmk_resource_t *rsc)
{
GHashTableIter iter;
pcmk_node_t *allowed_node = NULL;
int allowed_nodes = 0;
g_hash_table_iter_init(&iter, rsc->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &allowed_node)) {
if ((allowed_node->weight >= 0) && (++allowed_nodes > 1)) {
pcmk__rsc_trace(rsc, "%s is allowed on multiple nodes", rsc->id);
return false;
}
}
pcmk__rsc_trace(rsc, "%s is allowed %s", rsc->id,
((allowed_nodes == 1)? "on a single node" : "nowhere"));
return (allowed_nodes == 1);
}
/*!
* \internal
* \brief Add resource's colocation matches to current node assignment scores
*
* For each node in a given table, if any of a given resource's allowed nodes
* have a matching value for the colocation attribute, add the highest of those
* nodes' scores to the node's score.
*
* \param[in,out] nodes Table of nodes with assignment scores so far
* \param[in,out] source_rsc Resource whose node scores to add
* \param[in] target_rsc Resource on whose behalf to update \p nodes
* \param[in] colocation Original colocation constraint (used to get
* configured primary resource's stickiness, and
* to get colocation node attribute; pass NULL to
* ignore stickiness and use default attribute)
* \param[in] factor Factor by which to multiply scores being added
* \param[in] only_positive Whether to add only positive scores
*/
static void
add_node_scores_matching_attr(GHashTable *nodes,
pcmk_resource_t *source_rsc,
const pcmk_resource_t *target_rsc,
const pcmk__colocation_t *colocation,
float factor, bool only_positive)
{
GHashTableIter iter;
pcmk_node_t *node = NULL;
const char *attr = colocation->node_attribute;
// Iterate through each node
g_hash_table_iter_init(&iter, nodes);
while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
float delta_f = 0;
int delta = 0;
int score = 0;
int new_score = 0;
const char *value = pcmk__colocation_node_attr(node, attr, target_rsc);
score = best_node_score_matching_attr(colocation, source_rsc, attr, value);
if ((factor < 0) && (score < 0)) {
/* If the dependent is anti-colocated, we generally don't want the
* primary to prefer nodes that the dependent avoids. That could
* lead to unnecessary shuffling of the primary when the dependent
* hits its migration threshold somewhere, for example.
*
* However, there are cases when it is desirable. If the dependent
* can't run anywhere but where the primary is, it would be
* worthwhile to move the primary for the sake of keeping the
* dependent active.
*
* We can't know that exactly at this point since we don't know
* where the primary will be assigned, but we can limit considering
* the preference to when the dependent is allowed only on one node.
* This is less than ideal for multiple reasons:
*
* - the dependent could be allowed on more than one node but have
* anti-colocation primaries on each;
* - the dependent could be a clone or bundle with multiple
* instances, and the dependent as a whole is allowed on multiple
* nodes but some instance still can't run
* - the dependent has considered node-specific criteria such as
* location constraints and stickiness by this point, but might
* have other factors that end up disallowing a node
*
* but the alternative is making the primary move when it doesn't
* need to.
*
* We also consider the primary's stickiness and influence, so the
* user has some say in the matter. (This is the configured primary,
* not a particular instance of the primary, but that doesn't matter
* unless stickiness uses a rule to vary by node, and that seems
* acceptable to ignore.)
*/
if ((colocation->primary->stickiness >= -score)
|| !pcmk__colocation_has_influence(colocation, NULL)
|| !allowed_on_one(colocation->dependent)) {
crm_trace("%s: Filtering %d + %f * %d "
"(double negative disallowed)",
pcmk__node_name(node), node->weight, factor, score);
continue;
}
}
if (node->weight == INFINITY_HACK) {
crm_trace("%s: Filtering %d + %f * %d (node was marked unusable)",
pcmk__node_name(node), node->weight, factor, score);
continue;
}
delta_f = factor * score;
// Round the number; see http://c-faq.com/fp/round.html
delta = (int) ((delta_f < 0)? (delta_f - 0.5) : (delta_f + 0.5));
/* Small factors can obliterate the small scores that are often actually
* used in configurations. If the score and factor are nonzero, ensure
* that the result is nonzero as well.
*/
if ((delta == 0) && (score != 0)) {
if (factor > 0.0) {
delta = 1;
} else if (factor < 0.0) {
delta = -1;
}
}
new_score = pcmk__add_scores(delta, node->weight);
if (only_positive && (new_score < 0) && (node->weight > 0)) {
crm_trace("%s: Filtering %d + %f * %d = %d "
"(negative disallowed, marking node unusable)",
pcmk__node_name(node), node->weight, factor, score,
new_score);
node->weight = INFINITY_HACK;
continue;
}
if (only_positive && (new_score < 0) && (node->weight == 0)) {
crm_trace("%s: Filtering %d + %f * %d = %d (negative disallowed)",
pcmk__node_name(node), node->weight, factor, score,
new_score);
continue;
}
crm_trace("%s: %d + %f * %d = %d", pcmk__node_name(node),
node->weight, factor, score, new_score);
node->weight = new_score;
}
}
/*!
* \internal
* \brief Update nodes with scores of colocated resources' nodes
*
* Given a table of nodes and a resource, update the nodes' scores with the
* scores of the best nodes matching the attribute used for each of the
* resource's relevant colocations.
*
* \param[in,out] source_rsc Resource whose node scores to add
* \param[in] target_rsc Resource on whose behalf to update \p *nodes
* \param[in] log_id Resource ID for logs (if \c NULL, use
* \p source_rsc ID)
* \param[in,out] nodes Nodes to update (set initial contents to \c NULL
* to copy allowed nodes from \p source_rsc)
* \param[in] colocation Original colocation constraint (used to get
* configured primary resource's stickiness, and
* to get colocation node attribute; if \c NULL,
* <tt>source_rsc</tt>'s own matching node scores
* will not be added, and \p *nodes must be \c NULL
* as well)
* \param[in] factor Incorporate scores multiplied by this factor
* \param[in] flags Bitmask of enum pcmk__coloc_select values
*
* \note \c NULL \p target_rsc, \c NULL \p *nodes, \c NULL \p colocation, and
* the \c pcmk__coloc_select_this_with flag are used together (and only by
* \c cmp_resources()).
* \note The caller remains responsible for freeing \p *nodes.
* \note This is the shared implementation of
* \c pcmk_assignment_methods_t:add_colocated_node_scores().
*/
void
pcmk__add_colocated_node_scores(pcmk_resource_t *source_rsc,
const pcmk_resource_t *target_rsc,
const char *log_id,
GHashTable **nodes,
const pcmk__colocation_t *colocation,
float factor, uint32_t flags)
{
GHashTable *work = NULL;
CRM_ASSERT((source_rsc != NULL) && (nodes != NULL)
&& ((colocation != NULL)
|| ((target_rsc == NULL) && (*nodes == NULL))));
if (log_id == NULL) {
log_id = source_rsc->id;
}
// Avoid infinite recursion
if (pcmk_is_set(source_rsc->flags, pcmk_rsc_updating_nodes)) {
pcmk__rsc_info(source_rsc, "%s: Breaking dependency loop at %s",
log_id, source_rsc->id);
return;
}
pcmk__set_rsc_flags(source_rsc, pcmk_rsc_updating_nodes);
if (*nodes == NULL) {
work = pcmk__copy_node_table(source_rsc->allowed_nodes);
target_rsc = source_rsc;
} else {
const bool pos = pcmk_is_set(flags, pcmk__coloc_select_nonnegative);
pcmk__rsc_trace(source_rsc, "%s: Merging %s scores from %s (at %.6f)",
log_id, (pos? "positive" : "all"), source_rsc->id, factor);
work = pcmk__copy_node_table(*nodes);
add_node_scores_matching_attr(work, source_rsc, target_rsc, colocation,
factor, pos);
}
if (work == NULL) {
pcmk__clear_rsc_flags(source_rsc, pcmk_rsc_updating_nodes);
return;
}
if (pcmk__any_node_available(work)) {
GList *colocations = NULL;
if (pcmk_is_set(flags, pcmk__coloc_select_this_with)) {
colocations = pcmk__this_with_colocations(source_rsc);
pcmk__rsc_trace(source_rsc,
"Checking additional %d optional '%s with' "
"constraints",
g_list_length(colocations), source_rsc->id);
} else {
colocations = pcmk__with_this_colocations(source_rsc);
pcmk__rsc_trace(source_rsc,
"Checking additional %d optional 'with %s' "
"constraints",
g_list_length(colocations), source_rsc->id);
}
flags |= pcmk__coloc_select_active;
for (GList *iter = colocations; iter != NULL; iter = iter->next) {
pcmk__colocation_t *constraint = iter->data;
pcmk_resource_t *other = NULL;
float other_factor = factor * constraint->score
/ (float) PCMK_SCORE_INFINITY;
if (pcmk_is_set(flags, pcmk__coloc_select_this_with)) {
other = constraint->primary;
} else if (!pcmk__colocation_has_influence(constraint, NULL)) {
continue;
} else {
other = constraint->dependent;
}
pcmk__rsc_trace(source_rsc,
"Optionally merging score of '%s' constraint "
"(%s with %s)",
constraint->id, constraint->dependent->id,
constraint->primary->id);
other->cmds->add_colocated_node_scores(other, target_rsc, log_id,
&work, constraint,
other_factor, flags);
pe__show_node_scores(true, NULL, log_id, work, source_rsc->cluster);
}
g_list_free(colocations);
} else if (pcmk_is_set(flags, pcmk__coloc_select_active)) {
pcmk__rsc_info(source_rsc, "%s: Rolling back optional scores from %s",
log_id, source_rsc->id);
g_hash_table_destroy(work);
pcmk__clear_rsc_flags(source_rsc, pcmk_rsc_updating_nodes);
return;
}
if (pcmk_is_set(flags, pcmk__coloc_select_nonnegative)) {
pcmk_node_t *node = NULL;
GHashTableIter iter;
g_hash_table_iter_init(&iter, work);
while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
if (node->weight == INFINITY_HACK) {
node->weight = 1;
}
}
}
if (*nodes != NULL) {
g_hash_table_destroy(*nodes);
}
*nodes = work;
pcmk__clear_rsc_flags(source_rsc, pcmk_rsc_updating_nodes);
}
/*!
* \internal
* \brief Apply a "with this" colocation to a resource's allowed node scores
*
* \param[in,out] data Colocation to apply
* \param[in,out] user_data Resource being assigned
*/
void
pcmk__add_dependent_scores(gpointer data, gpointer user_data)
{
pcmk__colocation_t *colocation = data;
pcmk_resource_t *target_rsc = user_data;
pcmk_resource_t *source_rsc = colocation->dependent;
const float factor = colocation->score / (float) PCMK_SCORE_INFINITY;
uint32_t flags = pcmk__coloc_select_active;
if (!pcmk__colocation_has_influence(colocation, NULL)) {
return;
}
if (pcmk__is_clone(target_rsc)) {
flags |= pcmk__coloc_select_nonnegative;
}
pcmk__rsc_trace(target_rsc,
"%s: Incorporating attenuated %s assignment scores due "
"to colocation %s",
target_rsc->id, source_rsc->id, colocation->id);
source_rsc->cmds->add_colocated_node_scores(source_rsc, target_rsc,
source_rsc->id,
&target_rsc->allowed_nodes,
colocation, factor, flags);
}
/*!
* \internal
* \brief Exclude nodes from a dependent's node table if not in a given list
*
* Given a dependent resource in a colocation and a list of nodes where the
* primary resource will run, set a node's score to \c -INFINITY in the
* dependent's node table if not found in the primary nodes list.
*
* \param[in,out] dependent Dependent resource
* \param[in] primary Primary resource (for logging only)
* \param[in] colocation Colocation constraint (for logging only)
* \param[in] primary_nodes List of nodes where the primary will have
* unblocked instances in a suitable role
* \param[in] merge_scores If \c true and a node is found in both \p table
* and \p list, add the node's score in \p list to
* the node's score in \p table
*/
void
pcmk__colocation_intersect_nodes(pcmk_resource_t *dependent,
const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation,
const GList *primary_nodes, bool merge_scores)
{
GHashTableIter iter;
pcmk_node_t *dependent_node = NULL;
CRM_ASSERT((dependent != NULL) && (primary != NULL)
&& (colocation != NULL));
g_hash_table_iter_init(&iter, dependent->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &dependent_node)) {
const pcmk_node_t *primary_node = NULL;
primary_node = pe_find_node_id(primary_nodes,
dependent_node->details->id);
if (primary_node == NULL) {
dependent_node->weight = -PCMK_SCORE_INFINITY;
pcmk__rsc_trace(dependent,
"Banning %s from %s (no primary instance) for %s",
dependent->id, pcmk__node_name(dependent_node),
colocation->id);
} else if (merge_scores) {
dependent_node->weight = pcmk__add_scores(dependent_node->weight,
primary_node->weight);
pcmk__rsc_trace(dependent,
"Added %s's score %s to %s's score for %s (now %s) "
"for colocation %s",
primary->id, pcmk_readable_score(primary_node->weight),
dependent->id, pcmk__node_name(dependent_node),
pcmk_readable_score(dependent_node->weight),
colocation->id);
}
}
}
/*!
* \internal
* \brief Get all colocations affecting a resource as the primary
*
* \param[in] rsc Resource to get colocations for
*
* \return Newly allocated list of colocations affecting \p rsc as primary
*
* \note This is a convenience wrapper for the with_this_colocations() method.
*/
GList *
pcmk__with_this_colocations(const pcmk_resource_t *rsc)
{
GList *list = NULL;
rsc->cmds->with_this_colocations(rsc, rsc, &list);
return list;
}
/*!
* \internal
* \brief Get all colocations affecting a resource as the dependent
*
* \param[in] rsc Resource to get colocations for
*
* \return Newly allocated list of colocations affecting \p rsc as dependent
*
* \note This is a convenience wrapper for the this_with_colocations() method.
*/
GList *
pcmk__this_with_colocations(const pcmk_resource_t *rsc)
{
GList *list = NULL;
rsc->cmds->this_with_colocations(rsc, rsc, &list);
return list;
}
diff --git a/lib/pacemaker/pcmk_sched_tickets.c b/lib/pacemaker/pcmk_sched_tickets.c
index a22ed4595a..ca476003f9 100644
--- a/lib/pacemaker/pcmk_sched_tickets.c
+++ b/lib/pacemaker/pcmk_sched_tickets.c
@@ -1,533 +1,533 @@
/*
* 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 General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdbool.h>
#include <glib.h>
#include <crm/crm.h>
#include <crm/common/scheduler_internal.h>
#include <crm/pengine/status.h>
#include <pacemaker-internal.h>
#include "libpacemaker_private.h"
enum loss_ticket_policy {
loss_ticket_stop,
loss_ticket_demote,
loss_ticket_fence,
loss_ticket_freeze
};
typedef struct {
const char *id;
pcmk_resource_t *rsc;
pcmk_ticket_t *ticket;
enum loss_ticket_policy loss_policy;
int role;
} rsc_ticket_t;
/*!
* \brief Check whether a ticket constraint matches a resource by role
*
* \param[in] rsc_ticket Ticket constraint
* \param[in] rsc Resource to compare with ticket
*
* \param[in] true if constraint has no role or resource's role matches
* constraint's, otherwise false
*/
static bool
ticket_role_matches(const pcmk_resource_t *rsc, const rsc_ticket_t *rsc_ticket)
{
if ((rsc_ticket->role == pcmk_role_unknown)
|| (rsc_ticket->role == rsc->role)) {
return true;
}
pcmk__rsc_trace(rsc, "Skipping constraint: \"%s\" state filter",
pcmk_role_text(rsc_ticket->role));
return false;
}
/*!
* \brief Create location constraints and fencing as needed for a ticket
*
* \param[in,out] rsc Resource affected by ticket
* \param[in] rsc_ticket Ticket
*/
static void
constraints_for_ticket(pcmk_resource_t *rsc, const rsc_ticket_t *rsc_ticket)
{
GList *iter = NULL;
CRM_CHECK((rsc != NULL) && (rsc_ticket != NULL), return);
if (rsc_ticket->ticket->granted && !rsc_ticket->ticket->standby) {
return;
}
if (rsc->children) {
pcmk__rsc_trace(rsc, "Processing ticket dependencies from %s", rsc->id);
for (iter = rsc->children; iter != NULL; iter = iter->next) {
constraints_for_ticket((pcmk_resource_t *) iter->data, rsc_ticket);
}
return;
}
pcmk__rsc_trace(rsc, "%s: Processing ticket dependency on %s (%s, %s)",
rsc->id, rsc_ticket->ticket->id, rsc_ticket->id,
pcmk_role_text(rsc_ticket->role));
if (!rsc_ticket->ticket->granted && (rsc->running_on != NULL)) {
switch (rsc_ticket->loss_policy) {
case loss_ticket_stop:
resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
"__loss_of_ticket__", rsc->cluster);
break;
case loss_ticket_demote:
// Promotion score will be set to -INFINITY in promotion_order()
if (rsc_ticket->role != pcmk_role_promoted) {
resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
"__loss_of_ticket__", rsc->cluster);
}
break;
case loss_ticket_fence:
if (!ticket_role_matches(rsc, rsc_ticket)) {
return;
}
resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
"__loss_of_ticket__", rsc->cluster);
for (iter = rsc->running_on; iter != NULL; iter = iter->next) {
pe_fence_node(rsc->cluster, (pcmk_node_t *) iter->data,
"deadman ticket was lost", FALSE);
}
break;
case loss_ticket_freeze:
if (!ticket_role_matches(rsc, rsc_ticket)) {
return;
}
if (rsc->running_on != NULL) {
pcmk__clear_rsc_flags(rsc, pcmk_rsc_managed);
pcmk__set_rsc_flags(rsc, pcmk_rsc_blocked);
}
break;
}
} else if (!rsc_ticket->ticket->granted) {
if ((rsc_ticket->role != pcmk_role_promoted)
|| (rsc_ticket->loss_policy == loss_ticket_stop)) {
resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
"__no_ticket__", rsc->cluster);
}
} else if (rsc_ticket->ticket->standby) {
if ((rsc_ticket->role != pcmk_role_promoted)
|| (rsc_ticket->loss_policy == loss_ticket_stop)) {
resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
"__ticket_standby__", rsc->cluster);
}
}
}
static void
rsc_ticket_new(const char *id, pcmk_resource_t *rsc, pcmk_ticket_t *ticket,
const char *state, const char *loss_policy)
{
rsc_ticket_t *new_rsc_ticket = NULL;
if (rsc == NULL) {
pcmk__config_err("Ignoring ticket '%s' because resource "
"does not exist", id);
return;
}
new_rsc_ticket = calloc(1, sizeof(rsc_ticket_t));
if (new_rsc_ticket == NULL) {
return;
}
if (pcmk__str_eq(state, PCMK_ROLE_STARTED,
pcmk__str_null_matches|pcmk__str_casei)) {
state = PCMK__ROLE_UNKNOWN;
}
new_rsc_ticket->id = id;
new_rsc_ticket->ticket = ticket;
new_rsc_ticket->rsc = rsc;
new_rsc_ticket->role = pcmk_parse_role(state);
if (pcmk__str_eq(loss_policy, PCMK_VALUE_FENCE, pcmk__str_casei)) {
if (pcmk_is_set(rsc->cluster->flags, pcmk_sched_fencing_enabled)) {
new_rsc_ticket->loss_policy = loss_ticket_fence;
} else {
pcmk__config_err("Resetting '" PCMK_XA_LOSS_POLICY "' "
"for ticket '%s' to '" PCMK_VALUE_STOP "' "
"because fencing is not configured", ticket->id);
loss_policy = PCMK_VALUE_STOP;
}
}
if (new_rsc_ticket->loss_policy == loss_ticket_fence) {
crm_debug("On loss of ticket '%s': Fence the nodes running %s (%s)",
new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
pcmk_role_text(new_rsc_ticket->role));
} else if (pcmk__str_eq(loss_policy, PCMK_VALUE_FREEZE, pcmk__str_casei)) {
crm_debug("On loss of ticket '%s': Freeze %s (%s)",
new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
pcmk_role_text(new_rsc_ticket->role));
new_rsc_ticket->loss_policy = loss_ticket_freeze;
} else if (pcmk__str_eq(loss_policy, PCMK_VALUE_DEMOTE, pcmk__str_casei)) {
crm_debug("On loss of ticket '%s': Demote %s (%s)",
new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
pcmk_role_text(new_rsc_ticket->role));
new_rsc_ticket->loss_policy = loss_ticket_demote;
} else if (pcmk__str_eq(loss_policy, PCMK_VALUE_STOP, pcmk__str_casei)) {
crm_debug("On loss of ticket '%s': Stop %s (%s)",
new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
pcmk_role_text(new_rsc_ticket->role));
new_rsc_ticket->loss_policy = loss_ticket_stop;
} else {
if (new_rsc_ticket->role == pcmk_role_promoted) {
crm_debug("On loss of ticket '%s': Default to demote %s (%s)",
new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
pcmk_role_text(new_rsc_ticket->role));
new_rsc_ticket->loss_policy = loss_ticket_demote;
} else {
crm_debug("On loss of ticket '%s': Default to stop %s (%s)",
new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
pcmk_role_text(new_rsc_ticket->role));
new_rsc_ticket->loss_policy = loss_ticket_stop;
}
}
pcmk__rsc_trace(rsc, "%s (%s) ==> %s",
rsc->id, pcmk_role_text(new_rsc_ticket->role), ticket->id);
rsc->rsc_tickets = g_list_append(rsc->rsc_tickets, new_rsc_ticket);
rsc->cluster->ticket_constraints = g_list_append(
rsc->cluster->ticket_constraints, new_rsc_ticket);
if (!(new_rsc_ticket->ticket->granted) || new_rsc_ticket->ticket->standby) {
constraints_for_ticket(rsc, new_rsc_ticket);
}
}
// \return Standard Pacemaker return code
static int
unpack_rsc_ticket_set(xmlNode *set, pcmk_ticket_t *ticket,
const char *loss_policy, pcmk_scheduler_t *scheduler)
{
const char *set_id = NULL;
const char *role = NULL;
CRM_CHECK(set != NULL, return EINVAL);
CRM_CHECK(ticket != NULL, return EINVAL);
set_id = pcmk__xe_id(set);
if (set_id == NULL) {
pcmk__config_err("Ignoring <" PCMK_XE_RESOURCE_SET "> without "
PCMK_XA_ID);
return pcmk_rc_unpack_error;
}
role = crm_element_value(set, PCMK_XA_ROLE);
for (xmlNode *xml_rsc = pcmk__xe_first_child(set, PCMK_XE_RESOURCE_REF,
NULL, NULL);
xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
pcmk_resource_t *resource = NULL;
resource = pcmk__find_constraint_resource(scheduler->resources,
pcmk__xe_id(xml_rsc));
if (resource == NULL) {
pcmk__config_err("%s: No resource found for %s",
set_id, pcmk__xe_id(xml_rsc));
return pcmk_rc_unpack_error;
}
pcmk__rsc_trace(resource, "Resource '%s' depends on ticket '%s'",
resource->id, ticket->id);
rsc_ticket_new(set_id, resource, ticket, role, loss_policy);
}
return pcmk_rc_ok;
}
static void
unpack_simple_rsc_ticket(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
{
const char *id = NULL;
const char *ticket_str = crm_element_value(xml_obj, PCMK_XA_TICKET);
const char *loss_policy = crm_element_value(xml_obj, PCMK_XA_LOSS_POLICY);
pcmk_ticket_t *ticket = NULL;
const char *rsc_id = crm_element_value(xml_obj, PCMK_XA_RSC);
const char *state = crm_element_value(xml_obj, PCMK_XA_RSC_ROLE);
// @COMPAT: Deprecated since 2.1.5
const char *instance = crm_element_value(xml_obj, PCMK__XA_RSC_INSTANCE);
pcmk_resource_t *rsc = NULL;
if (instance != NULL) {
pcmk__warn_once(pcmk__wo_coloc_inst,
"Support for " PCMK__XA_RSC_INSTANCE " is deprecated "
"and will be removed in a future release");
}
CRM_CHECK(xml_obj != NULL, return);
id = pcmk__xe_id(xml_obj);
if (id == NULL) {
pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID,
xml_obj->name);
return;
}
if (ticket_str == NULL) {
pcmk__config_err("Ignoring constraint '%s' without ticket specified",
id);
return;
} else {
ticket = g_hash_table_lookup(scheduler->tickets, ticket_str);
}
if (ticket == NULL) {
pcmk__config_err("Ignoring constraint '%s' because ticket '%s' "
"does not exist", id, ticket_str);
return;
}
if (rsc_id == NULL) {
pcmk__config_err("Ignoring constraint '%s' without resource", id);
return;
} else {
rsc = pcmk__find_constraint_resource(scheduler->resources, rsc_id);
}
if (rsc == NULL) {
pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
"does not exist", id, rsc_id);
return;
} else if ((instance != NULL) && !pcmk__is_clone(rsc)) {
pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
"is not a clone but instance '%s' was requested",
id, rsc_id, instance);
return;
}
if (instance != NULL) {
rsc = find_clone_instance(rsc, instance);
if (rsc == NULL) {
pcmk__config_warn("Ignoring constraint '%s' because resource '%s' "
"does not have an instance '%s'",
- "'%s'", id, rsc_id, instance);
+ id, rsc_id, instance);
return;
}
}
rsc_ticket_new(id, rsc, ticket, state, loss_policy);
}
// \return Standard Pacemaker return code
static int
unpack_rsc_ticket_tags(xmlNode *xml_obj, xmlNode **expanded_xml,
pcmk_scheduler_t *scheduler)
{
const char *id = NULL;
const char *rsc_id = NULL;
const char *state = NULL;
pcmk_resource_t *rsc = NULL;
pcmk_tag_t *tag = NULL;
xmlNode *rsc_set = NULL;
*expanded_xml = NULL;
CRM_CHECK(xml_obj != NULL, return EINVAL);
id = pcmk__xe_id(xml_obj);
if (id == NULL) {
pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID,
xml_obj->name);
return pcmk_rc_unpack_error;
}
// Check whether there are any resource sets with template or tag references
*expanded_xml = pcmk__expand_tags_in_sets(xml_obj, scheduler);
if (*expanded_xml != NULL) {
crm_log_xml_trace(*expanded_xml, "Expanded rsc_ticket");
return pcmk_rc_ok;
}
rsc_id = crm_element_value(xml_obj, PCMK_XA_RSC);
if (rsc_id == NULL) {
return pcmk_rc_ok;
}
if (!pcmk__valid_resource_or_tag(scheduler, rsc_id, &rsc, &tag)) {
pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
"valid resource or tag", id, rsc_id);
return pcmk_rc_unpack_error;
} else if (rsc != NULL) {
// No template or tag is referenced
return pcmk_rc_ok;
}
state = crm_element_value(xml_obj, PCMK_XA_RSC_ROLE);
*expanded_xml = pcmk__xml_copy(NULL, xml_obj);
/* Convert any template or tag reference in "rsc" into ticket
* PCMK_XE_RESOURCE_SET
*/
if (!pcmk__tag_to_set(*expanded_xml, &rsc_set, PCMK_XA_RSC, false,
scheduler)) {
free_xml(*expanded_xml);
*expanded_xml = NULL;
return pcmk_rc_unpack_error;
}
if (rsc_set != NULL) {
if (state != NULL) {
/* Move PCMK_XA_RSC_ROLE into converted PCMK_XE_RESOURCE_SET as a
* PCMK_XA_ROLE attribute
*/
crm_xml_add(rsc_set, PCMK_XA_ROLE, state);
pcmk__xe_remove_attr(*expanded_xml, PCMK_XA_RSC_ROLE);
}
} else {
free_xml(*expanded_xml);
*expanded_xml = NULL;
}
return pcmk_rc_ok;
}
void
pcmk__unpack_rsc_ticket(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
{
xmlNode *set = NULL;
bool any_sets = false;
const char *id = NULL;
const char *ticket_str = NULL;
pcmk_ticket_t *ticket = NULL;
xmlNode *orig_xml = NULL;
xmlNode *expanded_xml = NULL;
CRM_CHECK(xml_obj != NULL, return);
id = pcmk__xe_id(xml_obj);
if (id == NULL) {
pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID,
xml_obj->name);
return;
}
if (scheduler->tickets == NULL) {
scheduler->tickets = pcmk__strkey_table(free, destroy_ticket);
}
ticket_str = crm_element_value(xml_obj, PCMK_XA_TICKET);
if (ticket_str == NULL) {
pcmk__config_err("Ignoring constraint '%s' without ticket", id);
return;
} else {
ticket = g_hash_table_lookup(scheduler->tickets, ticket_str);
}
if (ticket == NULL) {
ticket = ticket_new(ticket_str, scheduler);
if (ticket == NULL) {
return;
}
}
if (unpack_rsc_ticket_tags(xml_obj, &expanded_xml,
scheduler) != pcmk_rc_ok) {
return;
}
if (expanded_xml != NULL) {
orig_xml = xml_obj;
xml_obj = expanded_xml;
}
for (set = pcmk__xe_first_child(xml_obj, PCMK_XE_RESOURCE_SET, NULL, NULL);
set != NULL; set = pcmk__xe_next_same(set)) {
const char *loss_policy = NULL;
any_sets = true;
set = expand_idref(set, scheduler->input);
loss_policy = crm_element_value(xml_obj, PCMK_XA_LOSS_POLICY);
if ((set == NULL) // Configuration error, message already logged
|| (unpack_rsc_ticket_set(set, ticket, loss_policy,
scheduler) != pcmk_rc_ok)) {
if (expanded_xml != NULL) {
free_xml(expanded_xml);
}
return;
}
}
if (expanded_xml) {
free_xml(expanded_xml);
xml_obj = orig_xml;
}
if (!any_sets) {
unpack_simple_rsc_ticket(xml_obj, scheduler);
}
}
/*!
* \internal
* \brief Ban resource from a node if it doesn't have a promotion ticket
*
* If a resource has tickets for the promoted role, and the ticket is either not
* granted or set to standby, then ban the resource from all nodes.
*
* \param[in,out] rsc Resource to check
*/
void
pcmk__require_promotion_tickets(pcmk_resource_t *rsc)
{
for (GList *item = rsc->rsc_tickets; item != NULL; item = item->next) {
rsc_ticket_t *rsc_ticket = (rsc_ticket_t *) item->data;
if ((rsc_ticket->role == pcmk_role_promoted)
&& (!rsc_ticket->ticket->granted || rsc_ticket->ticket->standby)) {
resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
"__stateful_without_ticket__", rsc->cluster);
}
}
}
diff --git a/lib/pacemaker/pcmk_sched_utilization.c b/lib/pacemaker/pcmk_sched_utilization.c
index 05e3cf6bae..6c7a30fb90 100644
--- a/lib/pacemaker/pcmk_sched_utilization.c
+++ b/lib/pacemaker/pcmk_sched_utilization.c
@@ -1,469 +1,469 @@
/*
* Copyright 2014-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <crm/common/xml.h>
#include <pacemaker-internal.h>
#include "libpacemaker_private.h"
/*!
* \internal
* \brief Get integer utilization from a string
*
* \param[in] s String representation of a node utilization value
*
* \return Integer equivalent of \p s
* \todo It would make sense to restrict utilization values to nonnegative
* integers, but the documentation just says "integers" and we didn't
* restrict them initially, so for backward compatibility, allow any
* integer.
*/
static int
utilization_value(const char *s)
{
int value = 0;
if ((s != NULL) && (pcmk__scan_min_int(s, &value, INT_MIN) == EINVAL)) {
pcmk__config_warn("Using 0 for utilization instead of "
- "invalid value '%s'", value);
+ "invalid value '%s'", s);
value = 0;
}
return value;
}
/*
* Functions for comparing node capacities
*/
struct compare_data {
const pcmk_node_t *node1;
const pcmk_node_t *node2;
bool node2_only;
int result;
};
/*!
* \internal
* \brief Compare a single utilization attribute for two nodes
*
* Compare one utilization attribute for two nodes, decrementing the result if
* the first node has greater capacity, and incrementing it if the second node
* has greater capacity.
*
* \param[in] key Utilization attribute name to compare
* \param[in] value Utilization attribute value to compare
* \param[in,out] user_data Comparison data (as struct compare_data*)
*/
static void
compare_utilization_value(gpointer key, gpointer value, gpointer user_data)
{
int node1_capacity = 0;
int node2_capacity = 0;
struct compare_data *data = user_data;
const char *node2_value = NULL;
if (data->node2_only) {
if (g_hash_table_lookup(data->node1->details->utilization, key)) {
return; // We've already compared this attribute
}
} else {
node1_capacity = utilization_value((const char *) value);
}
node2_value = g_hash_table_lookup(data->node2->details->utilization, key);
node2_capacity = utilization_value(node2_value);
if (node1_capacity > node2_capacity) {
data->result--;
} else if (node1_capacity < node2_capacity) {
data->result++;
}
}
/*!
* \internal
* \brief Compare utilization capacities of two nodes
*
* \param[in] node1 First node to compare
* \param[in] node2 Second node to compare
*
* \return Negative integer if node1 has more free capacity,
* 0 if the capacities are equal, or a positive integer
* if node2 has more free capacity
*/
int
pcmk__compare_node_capacities(const pcmk_node_t *node1,
const pcmk_node_t *node2)
{
struct compare_data data = {
.node1 = node1,
.node2 = node2,
.node2_only = false,
.result = 0,
};
// Compare utilization values that node1 and maybe node2 have
g_hash_table_foreach(node1->details->utilization, compare_utilization_value,
&data);
// Compare utilization values that only node2 has
data.node2_only = true;
g_hash_table_foreach(node2->details->utilization, compare_utilization_value,
&data);
return data.result;
}
/*
* Functions for updating node capacities
*/
struct calculate_data {
GHashTable *current_utilization;
bool plus;
};
/*!
* \internal
* \brief Update a single utilization attribute with a new value
*
* \param[in] key Name of utilization attribute to update
* \param[in] value Value to add or substract
* \param[in,out] user_data Calculation data (as struct calculate_data *)
*/
static void
update_utilization_value(gpointer key, gpointer value, gpointer user_data)
{
int result = 0;
const char *current = NULL;
struct calculate_data *data = user_data;
current = g_hash_table_lookup(data->current_utilization, key);
if (data->plus) {
result = utilization_value(current) + utilization_value(value);
} else if (current) {
result = utilization_value(current) - utilization_value(value);
}
g_hash_table_replace(data->current_utilization,
strdup(key), pcmk__itoa(result));
}
/*!
* \internal
* \brief Subtract a resource's utilization from node capacity
*
* \param[in,out] current_utilization Current node utilization attributes
* \param[in] rsc Resource with utilization to subtract
*/
void
pcmk__consume_node_capacity(GHashTable *current_utilization,
const pcmk_resource_t *rsc)
{
struct calculate_data data = {
.current_utilization = current_utilization,
.plus = false,
};
g_hash_table_foreach(rsc->utilization, update_utilization_value, &data);
}
/*!
* \internal
* \brief Add a resource's utilization to node capacity
*
* \param[in,out] current_utilization Current node utilization attributes
* \param[in] rsc Resource with utilization to add
*/
void
pcmk__release_node_capacity(GHashTable *current_utilization,
const pcmk_resource_t *rsc)
{
struct calculate_data data = {
.current_utilization = current_utilization,
.plus = true,
};
g_hash_table_foreach(rsc->utilization, update_utilization_value, &data);
}
/*
* Functions for checking for sufficient node capacity
*/
struct capacity_data {
const pcmk_node_t *node;
const char *rsc_id;
bool is_enough;
};
/*!
* \internal
* \brief Check whether a single utilization attribute has sufficient capacity
*
* \param[in] key Name of utilization attribute to check
* \param[in] value Amount of utilization required
* \param[in,out] user_data Capacity data (as struct capacity_data *)
*/
static void
check_capacity(gpointer key, gpointer value, gpointer user_data)
{
int required = 0;
int remaining = 0;
const char *node_value_s = NULL;
struct capacity_data *data = user_data;
node_value_s = g_hash_table_lookup(data->node->details->utilization, key);
required = utilization_value(value);
remaining = utilization_value(node_value_s);
if (required > remaining) {
crm_debug("Remaining capacity for %s on %s (%d) is insufficient "
"for resource %s usage (%d)",
(const char *) key, pcmk__node_name(data->node), remaining,
data->rsc_id, required);
data->is_enough = false;
}
}
/*!
* \internal
* \brief Check whether a node has sufficient capacity for a resource
*
* \param[in] node Node to check
* \param[in] rsc_id ID of resource to check (for debug logs only)
* \param[in] utilization Required utilization amounts
*
* \return true if node has sufficient capacity for resource, otherwise false
*/
static bool
have_enough_capacity(const pcmk_node_t *node, const char *rsc_id,
GHashTable *utilization)
{
struct capacity_data data = {
.node = node,
.rsc_id = rsc_id,
.is_enough = true,
};
g_hash_table_foreach(utilization, check_capacity, &data);
return data.is_enough;
}
/*!
* \internal
* \brief Sum the utilization requirements of a list of resources
*
* \param[in] orig_rsc Resource being assigned (for logging purposes)
* \param[in] rscs Resources whose utilization should be summed
*
* \return Newly allocated hash table with sum of all utilization values
* \note It is the caller's responsibility to free the return value using
* g_hash_table_destroy().
*/
static GHashTable *
sum_resource_utilization(const pcmk_resource_t *orig_rsc, GList *rscs)
{
GHashTable *utilization = pcmk__strkey_table(free, free);
for (GList *iter = rscs; iter != NULL; iter = iter->next) {
pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data;
rsc->cmds->add_utilization(rsc, orig_rsc, rscs, utilization);
}
return utilization;
}
/*!
* \internal
* \brief Ban resource from nodes with insufficient utilization capacity
*
* \param[in,out] rsc Resource to check
*
* \return Allowed node for \p rsc with most spare capacity, if there are no
* nodes with enough capacity for \p rsc and all its colocated resources
*/
const pcmk_node_t *
pcmk__ban_insufficient_capacity(pcmk_resource_t *rsc)
{
bool any_capable = false;
char *rscs_id = NULL;
pcmk_node_t *node = NULL;
const pcmk_node_t *most_capable_node = NULL;
GList *colocated_rscs = NULL;
GHashTable *unassigned_utilization = NULL;
GHashTableIter iter;
CRM_CHECK(rsc != NULL, return NULL);
// The default placement strategy ignores utilization
if (pcmk__str_eq(rsc->cluster->placement_strategy, PCMK_VALUE_DEFAULT,
pcmk__str_casei)) {
return NULL;
}
// Check whether any resources are colocated with this one
colocated_rscs = rsc->cmds->colocated_resources(rsc, NULL, NULL);
if (colocated_rscs == NULL) {
return NULL;
}
rscs_id = crm_strdup_printf("%s and its colocated resources", rsc->id);
// If rsc isn't in the list, add it so we include its utilization
if (g_list_find(colocated_rscs, rsc) == NULL) {
colocated_rscs = g_list_append(colocated_rscs, rsc);
}
// Sum utilization of colocated resources that haven't been assigned yet
unassigned_utilization = sum_resource_utilization(rsc, colocated_rscs);
// Check whether any node has enough capacity for all the resources
g_hash_table_iter_init(&iter, rsc->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
if (!pcmk__node_available(node, true, false)) {
continue;
}
if (have_enough_capacity(node, rscs_id, unassigned_utilization)) {
any_capable = true;
}
// Keep track of node with most free capacity
if ((most_capable_node == NULL)
|| (pcmk__compare_node_capacities(node, most_capable_node) < 0)) {
most_capable_node = node;
}
}
if (any_capable) {
// If so, ban resource from any node with insufficient capacity
g_hash_table_iter_init(&iter, rsc->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
if (pcmk__node_available(node, true, false)
&& !have_enough_capacity(node, rscs_id,
unassigned_utilization)) {
pcmk__rsc_debug(rsc, "%s does not have enough capacity for %s",
pcmk__node_name(node), rscs_id);
resource_location(rsc, node, -PCMK_SCORE_INFINITY,
"__limit_utilization__", rsc->cluster);
}
}
most_capable_node = NULL;
} else {
// Otherwise, ban from nodes with insufficient capacity for rsc alone
g_hash_table_iter_init(&iter, rsc->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
if (pcmk__node_available(node, true, false)
&& !have_enough_capacity(node, rsc->id, rsc->utilization)) {
pcmk__rsc_debug(rsc, "%s does not have enough capacity for %s",
pcmk__node_name(node), rsc->id);
resource_location(rsc, node, -PCMK_SCORE_INFINITY,
"__limit_utilization__", rsc->cluster);
}
}
}
g_hash_table_destroy(unassigned_utilization);
g_list_free(colocated_rscs);
free(rscs_id);
pe__show_node_scores(true, rsc, "Post-utilization", rsc->allowed_nodes,
rsc->cluster);
return most_capable_node;
}
/*!
* \internal
* \brief Create a new load_stopped pseudo-op for a node
*
* \param[in,out] node Node to create op for
*
* \return Newly created load_stopped op
*/
static pcmk_action_t *
new_load_stopped_op(pcmk_node_t *node)
{
char *load_stopped_task = crm_strdup_printf(PCMK_ACTION_LOAD_STOPPED "_%s",
node->details->uname);
pcmk_action_t *load_stopped = get_pseudo_op(load_stopped_task,
node->details->data_set);
if (load_stopped->node == NULL) {
load_stopped->node = pe__copy_node(node);
pcmk__clear_action_flags(load_stopped, pcmk_action_optional);
}
free(load_stopped_task);
return load_stopped;
}
/*!
* \internal
* \brief Create utilization-related internal constraints for a resource
*
* \param[in,out] rsc Resource to create constraints for
* \param[in] allowed_nodes List of allowed next nodes for \p rsc
*/
void
pcmk__create_utilization_constraints(pcmk_resource_t *rsc,
const GList *allowed_nodes)
{
const GList *iter = NULL;
pcmk_action_t *load_stopped = NULL;
pcmk__rsc_trace(rsc,
"Creating utilization constraints for %s - strategy: %s",
rsc->id, rsc->cluster->placement_strategy);
// "stop rsc then load_stopped" constraints for current nodes
for (iter = rsc->running_on; iter != NULL; iter = iter->next) {
load_stopped = new_load_stopped_op(iter->data);
pcmk__new_ordering(rsc, stop_key(rsc), NULL, NULL, NULL, load_stopped,
pcmk__ar_if_on_same_node_or_target, rsc->cluster);
}
// "load_stopped then start/migrate_to rsc" constraints for allowed nodes
for (iter = allowed_nodes; iter; iter = iter->next) {
load_stopped = new_load_stopped_op(iter->data);
pcmk__new_ordering(NULL, NULL, load_stopped, rsc, start_key(rsc), NULL,
pcmk__ar_if_on_same_node_or_target, rsc->cluster);
pcmk__new_ordering(NULL, NULL, load_stopped,
rsc,
pcmk__op_key(rsc->id, PCMK_ACTION_MIGRATE_TO, 0),
NULL,
pcmk__ar_if_on_same_node_or_target, rsc->cluster);
}
}
/*!
* \internal
* \brief Output node capacities if enabled
*
* \param[in] desc Prefix for output
* \param[in,out] scheduler Scheduler data
*/
void
pcmk__show_node_capacities(const char *desc, pcmk_scheduler_t *scheduler)
{
if (!pcmk_is_set(scheduler->flags, pcmk_sched_show_utilization)) {
return;
}
for (const GList *iter = scheduler->nodes;
iter != NULL; iter = iter->next) {
const pcmk_node_t *node = (const pcmk_node_t *) iter->data;
pcmk__output_t *out = scheduler->priv;
out->message(out, "node-capacity", node, desc);
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Nov 23, 4:47 PM (17 h, 3 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1018940
Default Alt Text
(176 KB)

Event Timeline