Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/cts/schemas/test-3/ref/moon.ref-1 b/cts/schemas/test-3/ref/moon.ref-1
index b35f4304ad..981e14f3bd 100644
--- a/cts/schemas/test-3/ref/moon.ref-1
+++ b/cts/schemas/test-3/ref/moon.ref-1
@@ -1,33 +1,33 @@
<cib crm_feature_set="3.19.7" validate-with="pacemaker-4.0" epoch="16" num_updates="0" admin_epoch="0" original="1">
<configuration original="1">
<!-- The essential elements of this test are:
* There is a rule containing a date_spec with every possible attribute
specified, including phase of the moon.
* There is a rule containing a date_spec with only phase of the moon
specified.
In this situation, the moon attribute should be deleted from the
date_spec of each rule. No other attributes should change.
-->
<crm_config original="1">
<cluster_property_set id="cluster-properties1" original="1">
<rule id="cluster-properties1-rule" original="1">
<date_expression id="cluster-properties1-rule-expr" operation="date_spec" original="1">
- <date_spec id="cluster-properties1-rule-expr-date_spec" seconds="0-59" minutes="0-59" hours="0-23" monthdays="1-31" weekdays="1-7" yeardays="1-366" months="1-12" weeks="1-53" years="2024" weekyears="2024" moon="0-7" original="1"/>
+ <date_spec id="cluster-properties1-rule-expr-date_spec" seconds="0-59" minutes="0-59" hours="0-23" monthdays="1-31" weekdays="1-7" yeardays="1-366" months="1-12" weeks="1-53" years="2024" weekyears="2024" original="1"/>
</date_expression>
</rule>
</cluster_property_set>
<cluster_property_set id="cluster-properties2" original="1">
<rule id="cluster-properties2-rule" original="1">
<date_expression id="cluster-properties2-rule-expr" operation="date_spec" original="1">
- <date_spec id="cluster-properties2-rule-expr-date_spec" moon="0-7" original="1"/>
+ <date_spec id="cluster-properties2-rule-expr-date_spec" original="1"/>
</date_expression>
</rule>
</cluster_property_set>
</crm_config>
<nodes original="1"/>
<resources original="1"/>
<constraints original="1"/>
</configuration>
<status original="1"/>
</cib>
diff --git a/cts/schemas/test-3/ref/moon.ref-2 b/cts/schemas/test-3/ref/moon.ref-2
index b35f4304ad..981e14f3bd 100644
--- a/cts/schemas/test-3/ref/moon.ref-2
+++ b/cts/schemas/test-3/ref/moon.ref-2
@@ -1,33 +1,33 @@
<cib crm_feature_set="3.19.7" validate-with="pacemaker-4.0" epoch="16" num_updates="0" admin_epoch="0" original="1">
<configuration original="1">
<!-- The essential elements of this test are:
* There is a rule containing a date_spec with every possible attribute
specified, including phase of the moon.
* There is a rule containing a date_spec with only phase of the moon
specified.
In this situation, the moon attribute should be deleted from the
date_spec of each rule. No other attributes should change.
-->
<crm_config original="1">
<cluster_property_set id="cluster-properties1" original="1">
<rule id="cluster-properties1-rule" original="1">
<date_expression id="cluster-properties1-rule-expr" operation="date_spec" original="1">
- <date_spec id="cluster-properties1-rule-expr-date_spec" seconds="0-59" minutes="0-59" hours="0-23" monthdays="1-31" weekdays="1-7" yeardays="1-366" months="1-12" weeks="1-53" years="2024" weekyears="2024" moon="0-7" original="1"/>
+ <date_spec id="cluster-properties1-rule-expr-date_spec" seconds="0-59" minutes="0-59" hours="0-23" monthdays="1-31" weekdays="1-7" yeardays="1-366" months="1-12" weeks="1-53" years="2024" weekyears="2024" original="1"/>
</date_expression>
</rule>
</cluster_property_set>
<cluster_property_set id="cluster-properties2" original="1">
<rule id="cluster-properties2-rule" original="1">
<date_expression id="cluster-properties2-rule-expr" operation="date_spec" original="1">
- <date_spec id="cluster-properties2-rule-expr-date_spec" moon="0-7" original="1"/>
+ <date_spec id="cluster-properties2-rule-expr-date_spec" original="1"/>
</date_expression>
</rule>
</cluster_property_set>
</crm_config>
<nodes original="1"/>
<resources original="1"/>
<constraints original="1"/>
</configuration>
<status original="1"/>
</cib>
diff --git a/cts/schemas/test-3/ref/moon.ref-3 b/cts/schemas/test-3/ref/moon.ref-3
index b35f4304ad..981e14f3bd 100644
--- a/cts/schemas/test-3/ref/moon.ref-3
+++ b/cts/schemas/test-3/ref/moon.ref-3
@@ -1,33 +1,33 @@
<cib crm_feature_set="3.19.7" validate-with="pacemaker-4.0" epoch="16" num_updates="0" admin_epoch="0" original="1">
<configuration original="1">
<!-- The essential elements of this test are:
* There is a rule containing a date_spec with every possible attribute
specified, including phase of the moon.
* There is a rule containing a date_spec with only phase of the moon
specified.
In this situation, the moon attribute should be deleted from the
date_spec of each rule. No other attributes should change.
-->
<crm_config original="1">
<cluster_property_set id="cluster-properties1" original="1">
<rule id="cluster-properties1-rule" original="1">
<date_expression id="cluster-properties1-rule-expr" operation="date_spec" original="1">
- <date_spec id="cluster-properties1-rule-expr-date_spec" seconds="0-59" minutes="0-59" hours="0-23" monthdays="1-31" weekdays="1-7" yeardays="1-366" months="1-12" weeks="1-53" years="2024" weekyears="2024" moon="0-7" original="1"/>
+ <date_spec id="cluster-properties1-rule-expr-date_spec" seconds="0-59" minutes="0-59" hours="0-23" monthdays="1-31" weekdays="1-7" yeardays="1-366" months="1-12" weeks="1-53" years="2024" weekyears="2024" original="1"/>
</date_expression>
</rule>
</cluster_property_set>
<cluster_property_set id="cluster-properties2" original="1">
<rule id="cluster-properties2-rule" original="1">
<date_expression id="cluster-properties2-rule-expr" operation="date_spec" original="1">
- <date_spec id="cluster-properties2-rule-expr-date_spec" moon="0-7" original="1"/>
+ <date_spec id="cluster-properties2-rule-expr-date_spec" original="1"/>
</date_expression>
</rule>
</cluster_property_set>
</crm_config>
<nodes original="1"/>
<resources original="1"/>
<constraints original="1"/>
</configuration>
<status original="1"/>
</cib>
diff --git a/cts/schemas/test-3/ref/moon.ref-4 b/cts/schemas/test-3/ref/moon.ref-4
index b35f4304ad..981e14f3bd 100644
--- a/cts/schemas/test-3/ref/moon.ref-4
+++ b/cts/schemas/test-3/ref/moon.ref-4
@@ -1,33 +1,33 @@
<cib crm_feature_set="3.19.7" validate-with="pacemaker-4.0" epoch="16" num_updates="0" admin_epoch="0" original="1">
<configuration original="1">
<!-- The essential elements of this test are:
* There is a rule containing a date_spec with every possible attribute
specified, including phase of the moon.
* There is a rule containing a date_spec with only phase of the moon
specified.
In this situation, the moon attribute should be deleted from the
date_spec of each rule. No other attributes should change.
-->
<crm_config original="1">
<cluster_property_set id="cluster-properties1" original="1">
<rule id="cluster-properties1-rule" original="1">
<date_expression id="cluster-properties1-rule-expr" operation="date_spec" original="1">
- <date_spec id="cluster-properties1-rule-expr-date_spec" seconds="0-59" minutes="0-59" hours="0-23" monthdays="1-31" weekdays="1-7" yeardays="1-366" months="1-12" weeks="1-53" years="2024" weekyears="2024" moon="0-7" original="1"/>
+ <date_spec id="cluster-properties1-rule-expr-date_spec" seconds="0-59" minutes="0-59" hours="0-23" monthdays="1-31" weekdays="1-7" yeardays="1-366" months="1-12" weeks="1-53" years="2024" weekyears="2024" original="1"/>
</date_expression>
</rule>
</cluster_property_set>
<cluster_property_set id="cluster-properties2" original="1">
<rule id="cluster-properties2-rule" original="1">
<date_expression id="cluster-properties2-rule-expr" operation="date_spec" original="1">
- <date_spec id="cluster-properties2-rule-expr-date_spec" moon="0-7" original="1"/>
+ <date_spec id="cluster-properties2-rule-expr-date_spec" original="1"/>
</date_expression>
</rule>
</cluster_property_set>
</crm_config>
<nodes original="1"/>
<resources original="1"/>
<constraints original="1"/>
</configuration>
<status original="1"/>
</cib>
diff --git a/cts/schemas/test-3/ref/moon.ref-99 b/cts/schemas/test-3/ref/moon.ref-99
index 74669edf48..b50df09fbd 100644
--- a/cts/schemas/test-3/ref/moon.ref-99
+++ b/cts/schemas/test-3/ref/moon.ref-99
@@ -1,33 +1,33 @@
<cib crm_feature_set="3.19.7" validate-with="pacemaker-4.0" epoch="16" num_updates="0" admin_epoch="0">
<configuration>
<!-- The essential elements of this test are:
* There is a rule containing a date_spec with every possible attribute
specified, including phase of the moon.
* There is a rule containing a date_spec with only phase of the moon
specified.
In this situation, the moon attribute should be deleted from the
date_spec of each rule. No other attributes should change.
-->
<crm_config>
<cluster_property_set id="cluster-properties1">
<rule id="cluster-properties1-rule">
<date_expression id="cluster-properties1-rule-expr" operation="date_spec">
- <date_spec id="cluster-properties1-rule-expr-date_spec" seconds="0-59" minutes="0-59" hours="0-23" monthdays="1-31" weekdays="1-7" yeardays="1-366" months="1-12" weeks="1-53" years="2024" weekyears="2024" moon="0-7"/>
+ <date_spec id="cluster-properties1-rule-expr-date_spec" seconds="0-59" minutes="0-59" hours="0-23" monthdays="1-31" weekdays="1-7" yeardays="1-366" months="1-12" weeks="1-53" years="2024" weekyears="2024"/>
</date_expression>
</rule>
</cluster_property_set>
<cluster_property_set id="cluster-properties2">
<rule id="cluster-properties2-rule">
<date_expression id="cluster-properties2-rule-expr" operation="date_spec">
- <date_spec id="cluster-properties2-rule-expr-date_spec" moon="0-7"/>
+ <date_spec id="cluster-properties2-rule-expr-date_spec"/>
</date_expression>
</rule>
</cluster_property_set>
</crm_config>
<nodes/>
<resources/>
<constraints/>
</configuration>
<status/>
</cib>
diff --git a/lib/common/rules.c b/lib/common/rules.c
index 691df868b1..5ccc35e615 100644
--- a/lib/common/rules.c
+++ b/lib/common/rules.c
@@ -1,1421 +1,1422 @@
/*
* 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 of parent date expression 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
} else if (pcmk__parse_ll_range(range, &low, &high) != pcmk_rc_ok) {
// Invalid range
pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s "
"as not passing because '%s' is not a valid range "
"for " PCMK_XE_DATE_SPEC " attribute %s",
id, range, attr);
rc = pcmk_rc_unpack_error;
} else if ((low != -1) && (value < low)) {
rc = pcmk_rc_before_range;
} else if ((high != -1) && (value > high)) {
rc = pcmk_rc_after_range;
}
crm_trace(PCMK_XE_DATE_EXPRESSION " %s: " PCMK_XE_DATE_SPEC
" %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_unpack_error if the specification XML is invalid,
* \c pcmk_rc_ok if \p now is within the specification's ranges, or
* \c pcmk_rc_before_range or \c pcmk_rc_after_range as appropriate)
*/
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
pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
"passing because " PCMK_XE_DATE_SPEC
" subelement has no " PCMK_XA_ID, parent_id);
return pcmk_rc_unpack_error;
}
// 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) {
+ // @COMPAT Not possible with schema validation enabled
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, parent_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 rc = pcmk__add_time_from_xml(*end, component, duration); \
if (rc != pcmk_rc_ok) { \
pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s " \
"as not passing because " PCMK_XE_DURATION \
" %s attribute %s is invalid: %s", \
parent_id, id, \
pcmk__time_component_attr(component), \
pcmk_rc_str(rc)); \
crm_time_free(*end); \
*end = NULL; \
return 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)
{
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
pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s "
"as not passing because " PCMK_XE_DURATION
" subelement has no " PCMK_XA_ID, parent_id);
return pcmk_rc_unpack_error;
}
*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 pcmk_rc_ok;
}
/*!
* \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) {
pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
"passing because " PCMK_XA_START " is invalid", id);
return pcmk_rc_unpack_error;
}
if (pcmk__xe_get_datetime(date_expression, PCMK_XA_END,
&end) != pcmk_rc_ok) {
pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
"passing because " PCMK_XA_END " is invalid", id);
crm_time_free(start);
return pcmk_rc_unpack_error;
}
if ((start == NULL) && (end == NULL)) {
// Not possible with schema validation enabled
pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
"passing because " PCMK_VALUE_IN_RANGE
" requires at least one of " PCMK_XA_START " or "
PCMK_XA_END, id);
return pcmk_rc_unpack_error;
}
if (end == NULL) {
xmlNode *duration = pcmk__xe_first_child(date_expression,
PCMK_XE_DURATION, NULL, NULL);
if (duration != NULL) {
int rc = pcmk__unpack_duration(duration, start, &end);
if (rc != pcmk_rc_ok) {
pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION
" %s as not passing because duration "
"is invalid", id);
crm_time_free(start);
return rc;
}
}
}
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) {
pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
"passing because " PCMK_XA_START " is invalid",
id);
return pcmk_rc_unpack_error;
}
if (start == NULL) { // Not possible with schema validation enabled
pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
"passing because " PCMK_VALUE_GT " requires "
PCMK_XA_START, id);
return pcmk_rc_unpack_error;
}
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) {
pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
"passing because " PCMK_XA_END " is invalid", id);
return pcmk_rc_unpack_error;
}
if (end == NULL) { // Not possible with schema validation enabled
pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
"passing because " PCMK_VALUE_GT " requires "
PCMK_XA_END, id);
return pcmk_rc_unpack_error;
}
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_ok;
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
pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " without "
PCMK_XA_ID " as not passing");
return pcmk_rc_unpack_error;
}
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
pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s "
"as not passing because " PCMK_VALUE_DATE_SPEC
" operations require a " PCMK_XE_DATE_SPEC
" subelement", id);
return pcmk_rc_unpack_error;
}
// @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
pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION
" %s as not passing because '%s' is not a valid "
PCMK_XE_OPERATION, id, op);
return pcmk_rc_unpack_error;
}
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;
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)) {
pcmk__config_err("Treating " PCMK_XE_EXPRESSION " without " PCMK_XA_ID
" as not passing");
return pcmk_rc_unpack_error;
}
/* 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
pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not passing "
"because '%s' is not a valid " PCMK_XA_VALUE_SOURCE,
id, source_s);
rc = pcmk_rc_unpack_error;
goto done;
}
// 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_err("Treating " PCMK_XE_EXPRESSION " %s as not "
"passing because " PCMK_XA_VALUE " is not "
"allowed when " PCMK_XA_OPERATION " is %s",
id, op);
rc = pcmk_rc_unpack_error;
goto done;
}
break;
default:
if (value == NULL) {
pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not "
"passing because " PCMK_XA_VALUE " is "
"required when " PCMK_XA_OPERATION " is %s",
id, op);
rc = pcmk_rc_unpack_error;
goto done;
}
reference = value_from_source(value, source, rule_input);
break;
}
// 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
pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not passing "
"because '%s' is not a valid type", id, type_s);
rc = pcmk_rc_unpack_error;
goto done;
}
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
pcmk__config_err("Treating " PCMK_XE_RSC_EXPRESSION " without "
PCMK_XA_ID " as not passing");
return pcmk_rc_unpack_error;
}
// 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
pcmk__config_err("Treating " PCMK_XE_OP_EXPRESSION " without "
PCMK_XA_ID " as not passing");
return pcmk_rc_unpack_error;
}
// Validate operation name
name = crm_element_value(op_expression, PCMK_XA_NAME);
if (name == NULL) { // Not possible with schema validation enabled
pcmk__config_err("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_err("Treating " PCMK_XE_OP_EXPRESSION " %s as not "
"passing because '%s' is not a valid "
PCMK_META_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 = pcmk__xe_resolve_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)) { // Not possible with schema validation enabled
pcmk__config_err("Treating " PCMK_XE_RULE " without " PCMK_XA_ID
" as not passing");
return pcmk_rc_unpack_error;
}
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: // Not possible with schema validation enabled
pcmk__config_err("Treating " PCMK_XE_RULE " %s as not passing "
"because '%s' is not a valid " PCMK_XA_BOOLEAN_OP,
id, value);
return pcmk_rc_unpack_error;
}
// 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
pcmk__config_warn("Ignoring rule %s because it contains no conditions",
id);
rc = pcmk_rc_ok;
}
crm_trace("Rule %s is %ssatisfied", id, ((rc == pcmk_rc_ok)? "" : "not "));
return rc;
}
diff --git a/xml/rule-4.0.rng b/xml/rule-4.0.rng
index 9c5f8c534f..cd25c22850 100644
--- a/xml/rule-4.0.rng
+++ b/xml/rule-4.0.rng
@@ -1,433 +1,430 @@
<?xml version="1.0" encoding="UTF-8"?>
<grammar xmlns="http://relaxng.org/ns/structure/1.0"
xmlns:ann="http://relaxng.org/ns/compatibility/annotations/1.0"
datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
<start>
<ref name="element-rule"/>
</start>
<!--
Rule elements allow different syntax depending on their context, each of
which gets their own rule definition below:
1. rsc_location
2. meta_attributes within rsc_defaults
3. meta_attributes within op_defaults
4. all other contexts that allow node attribute expressions
(instance_attributes elements within bundle, clone, group, op, primitive,
and template elements; and meta_attributes elements within op elements)
5. all other contexts (cluster_property_set elements; instance_attributes
within alert, node, and recipient elements; meta_attributes within alert,
bundle, clone, group, primitive, recipient, and template elements; and
utilization elements within node, primitive, and template elements)
The COMPAT comments below mark items that are invalid in their given context
and should be removed at a compatibility break.
-->
<!-- 1. rule element within rsc_location -->
<define name="element-rule-location">
<element name="rule">
<choice>
<attribute name="id-ref"><data type="IDREF"/></attribute>
<group>
<ref name="rule-common"/>
<oneOrMore>
<choice>
<ref name="expression-location"/>
<ref name="date_expression"/>
<ref name="element-rule-location"/>
<!-- @COMPAT: The below expression types are invalid here -->
<ref name="rsc_expression"/>
<ref name="op_expression"/>
</choice>
</oneOrMore>
<optional>
<choice>
<externalRef href="score.rng"/>
<attribute name="score-attribute"><text/></attribute>
</choice>
</optional>
</group>
</choice>
</element>
</define>
<!-- 2. rule element within rsc_defaults -->
<define name="element-rule-rsc_defaults">
<element name="rule">
<choice>
<attribute name="id-ref"><data type="IDREF"/></attribute>
<group>
<ref name="rule-common"/>
<oneOrMore>
<choice>
<ref name="date_expression"/>
<ref name="rsc_expression"/>
<ref name="element-rule-rsc_defaults"/>
<!-- @COMPAT: The below expression type is deprecated here -->
<ref name="expression"/>
<!-- @COMPAT: The below expression type is invalid here -->
<ref name="op_expression"/>
</choice>
</oneOrMore>
<!-- @COMPAT: The below score attributes are invalid here -->
<optional>
<choice>
<externalRef href="score.rng"/>
<attribute name="score-attribute"><text/></attribute>
</choice>
</optional>
</group>
</choice>
</element>
</define>
<!-- 3. rule element within op_defaults -->
<define name="element-rule-op_defaults">
<element name="rule">
<choice>
<attribute name="id-ref"><data type="IDREF"/></attribute>
<group>
<ref name="rule-common"/>
<oneOrMore>
<choice>
<ref name="date_expression"/>
<ref name="rsc_expression"/>
<ref name="op_expression"/>
<ref name="element-rule-op_defaults"/>
<!-- @COMPAT: The below expression type is deprecated here -->
<ref name="expression"/>
</choice>
</oneOrMore>
<!-- @COMPAT: The below score attributes are invalid here -->
<optional>
<choice>
<externalRef href="score.rng"/>
<attribute name="score-attribute"><text/></attribute>
</choice>
</optional>
</group>
</choice>
</element>
</define>
<!-- 4. rule element in other contexts allowing node attribute expressions -->
<define name="element-rule-node-allowed">
<element name="rule">
<choice>
<attribute name="id-ref"><data type="IDREF"/></attribute>
<group>
<ref name="rule-common"/>
<oneOrMore>
<choice>
<ref name="expression"/>
<ref name="date_expression"/>
<ref name="element-rule-node-allowed"/>
<!-- @COMPAT: The below expression types are invalid here -->
<ref name="rsc_expression"/>
<ref name="op_expression"/>
</choice>
</oneOrMore>
<!-- @COMPAT: The below score attributes are invalid here -->
<optional>
<choice>
<externalRef href="score.rng"/>
<attribute name="score-attribute"><text/></attribute>
</choice>
</optional>
</group>
</choice>
</element>
</define>
<!-- 5. rule element within all other contexts -->
<define name="element-rule">
<element name="rule">
<choice>
<attribute name="id-ref"><data type="IDREF"/></attribute>
<group>
<ref name="rule-common"/>
<oneOrMore>
<choice>
<ref name="date_expression"/>
<ref name="element-rule"/>
<!-- @COMPAT: The below expression types are invalid here -->
<ref name="expression"/>
<ref name="rsc_expression"/>
<ref name="op_expression"/>
</choice>
</oneOrMore>
<!-- @COMPAT: The below score attributes are invalid here -->
<optional>
<choice>
<externalRef href="score.rng"/>
<attribute name="score-attribute"><text/></attribute>
</choice>
</optional>
</group>
</choice>
</element>
</define>
<!-- Attributes that are common to all rule elements -->
<define name="rule-common">
<attribute name="id"><data type="ID"/></attribute>
<optional>
<attribute name="boolean-op">
<choice>
<value>or</value>
<value>and</value>
</choice>
</attribute>
</optional>
<!--
@COMPAT Role applies only to rules within location constraints. When we can
break behavioral backward compatibility, move this to
rule-element-location.
-->
<optional>
<attribute name="role"><text/></attribute>
</optional>
</define>
<!-- A node attribute expression -->
<define name="expression">
<element name="expression">
<ref name="expression-common"/>
<optional>
<attribute name="value-source" ann:defaultValue="literal">
<choice>
<value>literal</value>
<!-- @COMPAT: These value-source choices are invalid here -->
<value>param</value>
<value>meta</value>
</choice>
</attribute>
</optional>
</element>
</define>
<!-- A node attribute expression in a location constraint -->
<define name="expression-location">
<element name="expression">
<ref name="expression-common"/>
<optional>
<attribute name="value-source" ann:defaultValue="literal">
<choice>
<value>literal</value>
<value>param</value>
<value>meta</value>
</choice>
</attribute>
</optional>
</element>
</define>
<!-- Attributes that are common to all <expression> elements -->
<define name="expression-common">
<attribute name="id"><data type="ID"/></attribute>
<attribute name="attribute"><text/></attribute>
<choice>
<group>
<attribute name="operation">
<choice>
<value>defined</value>
<value>not_defined</value>
</choice>
</attribute>
<!-- @COMPAT value attribute should be prohibited here -->
<optional>
<attribute name="value"><text/></attribute>
</optional>
</group>
<group>
<attribute name="operation">
<choice>
<value>lt</value>
<value>gt</value>
<value>lte</value>
<value>gte</value>
<value>eq</value>
<value>ne</value>
</choice>
</attribute>
<!-- @COMPAT value attribute should be required here -->
<optional>
<attribute name="value"><text/></attribute>
</optional>
</group>
</choice>
<optional>
<attribute name="type" ann:defaultValue="string">
<choice>
<value>string</value>
<value>integer</value>
<value>number</value>
<value>version</value>
</choice>
</attribute>
</optional>
</define>
<define name="date_expression">
<element name="date_expression">
<attribute name="id"><data type="ID"/></attribute>
<choice>
<group>
<attribute name="operation"><value>in_range</value></attribute>
<choice>
<attribute name="start"><text/></attribute>
<attribute name="end"><text/></attribute>
<group>
<attribute name="start"><text/></attribute>
<attribute name="end"><text/></attribute>
</group>
</choice>
<optional>
<ref name="duration"/>
</optional>
</group>
<group>
<attribute name="operation"><value>gt</value></attribute>
<attribute name="start"><text/></attribute>
</group>
<group>
<attribute name="operation"><value>lt</value></attribute>
<attribute name="end"><text/></attribute>
</group>
<group>
<attribute name="operation"><value>date_spec</value></attribute>
<ref name="date_spec"/>
</group>
</choice>
</element>
</define>
<define name="rsc_expression">
<element name="rsc_expression">
<attribute name="id"><data type="ID"/></attribute>
<optional>
<attribute name="class"><text/></attribute>
</optional>
<optional>
<attribute name="provider"><text/></attribute>
</optional>
<optional>
<attribute name="type"><text/></attribute>
</optional>
</element>
</define>
<define name="op_expression">
<element name="op_expression">
<attribute name="id"><data type="ID"/></attribute>
<attribute name="name"><text/></attribute>
<optional>
<attribute name="interval"><text/></attribute>
</optional>
</element>
</define>
<define name="duration">
<element name="duration">
<attribute name="id"><data type="ID"/></attribute>
<optional>
<attribute name="years"><text/></attribute>
</optional>
<optional>
<attribute name="months"><text/></attribute>
</optional>
<optional>
<attribute name="weeks"><text/></attribute>
</optional>
<optional>
<attribute name="days"><text/></attribute>
</optional>
<optional>
<attribute name="hours"><text/></attribute>
</optional>
<optional>
<attribute name="minutes"><text/></attribute>
</optional>
<optional>
<attribute name="seconds"><text/></attribute>
</optional>
<!-- @COMPAT: The below attributes are invalid here -->
<optional>
<attribute name="monthdays"><text/></attribute>
</optional>
<optional>
<attribute name="weekdays"><text/></attribute>
</optional>
<optional>
<attribute name="yearsdays"><text/></attribute>
</optional>
<optional>
<attribute name="weekyears"><text/></attribute>
</optional>
<optional>
<attribute name="moon"><text/></attribute>
</optional>
</element>
</define>
<define name="date_spec">
<element name="date_spec">
<attribute name="id"><data type="ID"/></attribute>
<optional>
<attribute name="years"><text/></attribute>
</optional>
<optional>
<attribute name="months"><text/></attribute>
</optional>
<optional>
<attribute name="monthdays"><text/></attribute>
</optional>
<optional>
<attribute name="hours"><text/></attribute>
</optional>
<optional>
<attribute name="minutes"><text/></attribute>
</optional>
<optional>
<attribute name="seconds"><text/></attribute>
</optional>
<optional>
<attribute name="yeardays"><text/></attribute>
</optional>
<optional>
<attribute name="weekyears"><text/></attribute>
</optional>
<optional>
<attribute name="weeks"><text/></attribute>
</optional>
<optional>
<attribute name="weekdays"><text/></attribute>
</optional>
- <optional>
- <attribute name="moon"><text/></attribute>
- </optional>
<!-- @COMPAT: The below attributes are invalid here -->
<optional>
<attribute name="yearsdays"><text/></attribute>
</optional>
</element>
</define>
</grammar>
diff --git a/xml/upgrade-3.10-1.xsl b/xml/upgrade-3.10-1.xsl
index 10aab967c8..5b5565e6c1 100644
--- a/xml/upgrade-3.10-1.xsl
+++ b/xml/upgrade-3.10-1.xsl
@@ -1,156 +1,165 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Use comments liberally as future maintainers may be unfamiliar with XSLT.
-->
<!--
upgrade-3.10-1.xsl
Guarantees after this transformation:
* The validate-with attribute of the cib element is set to "pacemaker-4.0".
* All nvset elements are sorted by score within their respective parent
elements (remaining in document order in the case of a tie) and placed below
all non-nvset siblings. Exception: a cluster_property_set with id
"cib-bootstrap-options" always sorts first relative to its siblings.
* Each nvpair has a value attribute. If an nvpair did not have a value
attribute prior to this transformation, it is dropped.
+ * There are no "moon" attributes in date_spec elements of rules. If there were
+ any prior to this transformation, the attributes are now removed and the rest
+ of the date_spec is unchanged.
* The crmd-finalization-timeout cluster property has been renamed to
"join-finalization-timeout".
* The crmd-integration-timeout cluster property has been renamed to
"join-integration-timeout".
* The crmd-transition-delay cluster property has been renamed to
"transition-delay".
* The remove-after-stop cluster property is not present.
* The stonith-action cluster property is set to "off" if it was previously set
to "poweroff".
nvset elements include the following:
* cluster_property_set
* instance_attributes
* meta_attributes
* utilization
Any template that matches an element (for example, "primitive") that may
contain an nvset should be placed in a later stylesheet. If such a template is
placed in this stylesheet, its nvsets will not be sorted. We could avoid this
with some refactoring, but it's cleaner this way.
-->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:import href="upgrade-3.10-common.xsl"/>
<!--
Copy everything unaltered by default, except sort nvset elements by score.
This doesn't affect behavior. nvset elements of a given type within a given
parent element are processed in order of their score attributes, with a
nonexistent score treated as 0. In the event of a tie, elements are processed
in document order. The sort here preserves document order in the event of a
tie.
The order of nvset elements relative to non-nvset elements does not matter. So
this template puts existing nvset elements after existing non-nvset elements.
This facilitates later transformations, allowing us to more easily drop nvpairs
with unsupported values without changing behavior.
-->
<xsl:template match="/|@*|node()">
<xsl:copy>
<xsl:variable name="nvsets"
select="cluster_property_set
|instance_attributes
|meta_attributes
|utilization"/>
<!-- XPath 1.0 set difference idiom -->
<xsl:variable name="non_nvsets"
select="@*|node()[count(.|$nvsets) != count($nvsets)]"/>
<xsl:apply-templates select="$non_nvsets"/>
<xsl:apply-templates select="$nvsets">
<!--
Order cluster_property_set with id "cib-bootstrap-options" before
siblings
-->
<xsl:sort select="self::cluster_property_set
and (@id = 'cib-bootstrap-options')"
order="descending"/>
<!--
Sort remaining elements by score.
First, score="INFINITY" (including "+INFINITY").
-->
<xsl:sort select="@score[. = 'INFINITY'] or @score[. = '+INFINITY']"
order="descending"/>
<!-- Then finite positive scores -->
<xsl:sort select="@score[. &gt; 0]" data-type="number"
order="descending"/>
<!-- Then score 0 (including implicit) -->
<xsl:sort select="number(not(@score) or @score[. = 0])"
data-type="number" order="descending"/>
<!-- Then finite negative scores -->
<xsl:sort select="@score[. &lt; 0]" data-type="number"
order="descending"/>
<!-- Then score="-INFINITY" -->
<xsl:sort select="@score[. = '-INFINITY']" order="descending"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<!--
Bump cib/@validate-with, or set it if not already set. Pacemaker does this, but
doing it in the transformation is helpful for testing.
-->
<xsl:template match="cib">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:attribute name="validate-with">pacemaker-4.0</xsl:attribute>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
<!-- Name/value pairs -->
<!-- Drop any nvpair that does not have a value attribute -->
<xsl:template match="nvpair[not(@value)]"/>
+<!-- Rules -->
+
+<!-- Drop the moon attribute from date_spec elements -->
+<xsl:template match="date_spec/@moon"/>
+
+
<!-- Cluster properties -->
<!-- Rename crmd-finalization-timeout property to join-finalization-timeout -->
<xsl:template match="cluster_property_set
/nvpair[@name = 'crmd-finalization-timeout']/@name">
<xsl:attribute name="name">join-finalization-timeout</xsl:attribute>
</xsl:template>
<!-- Rename crmd-integration-timeout property to join-integration-timeout -->
<xsl:template match="cluster_property_set
/nvpair[@name = 'crmd-integration-timeout']/@name">
<xsl:attribute name="name">join-integration-timeout</xsl:attribute>
</xsl:template>
<!-- Rename crmd-transition-delay property to transition-delay -->
<xsl:template match="cluster_property_set
/nvpair[@name = 'crmd-transition-delay']/@name">
<xsl:attribute name="name">transition-delay</xsl:attribute>
</xsl:template>
<!-- Drop remove-after-stop property -->
<xsl:template match="cluster_property_set/nvpair[@name = 'remove-after-stop']"/>
<!-- Replace stonith-action="poweroff" with stonith-action="off" -->
<xsl:template match="cluster_property_set/nvpair[@name = 'stonith-action']
/@value[. = 'poweroff']">
<xsl:attribute name="value">off</xsl:attribute>
</xsl:template>
</xsl:stylesheet>

File Metadata

Mime Type
text/x-diff
Expires
Thu, Jul 10, 1:33 AM (16 h, 4 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2009506
Default Alt Text
(81 KB)

Event Timeline