Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F2825450
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
54 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/include/crm/common/rules_internal.h b/include/crm/common/rules_internal.h
index b37e2134c5..623cfe5bd2 100644
--- a/include/crm/common/rules_internal.h
+++ b/include/crm/common/rules_internal.h
@@ -1,21 +1,21 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef PCMK__CRM_COMMON_RULES_INTERNAL__H
#define PCMK__CRM_COMMON_RULES_INTERNAL__H
#include <libxml/tree.h> // xmlNode
#include <crm/common/rules.h> // enum expression_type
#include <crm/common/iso8601.h> // crm_time_t
enum expression_type pcmk__expression_type(const xmlNode *expr);
-int pe_cron_range_satisfied(const crm_time_t *now, const xmlNode *cron_spec);
+int pcmk__evaluate_date_spec(const xmlNode *date_spec, const crm_time_t *now);
#endif // PCMK__CRM_COMMON_RULES_INTERNAL__H
diff --git a/lib/common/rules.c b/lib/common/rules.c
index 54a8e4b4d8..ace9cc51c6 100644
--- a/lib/common/rules.c
+++ b/lib/common/rules.c
@@ -1,195 +1,217 @@
/*
* 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
#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/scheduler_internal.h>
/*!
* \internal
* \brief Get the expression type corresponding to given expression XML
*
* \param[in] expr Rule expression XML
*
* \return Expression type corresponding to \p expr
*/
enum expression_type
pcmk__expression_type(const xmlNode *expr)
{
const char *name = NULL;
// Expression types based on element name
if (pcmk__xe_is(expr, PCMK_XE_DATE_EXPRESSION)) {
return pcmk__subexpr_datetime;
} else if (pcmk__xe_is(expr, PCMK_XE_RSC_EXPRESSION)) {
return pcmk__subexpr_resource;
} else if (pcmk__xe_is(expr, PCMK_XE_OP_EXPRESSION)) {
return pcmk__subexpr_operation;
} else if (pcmk__xe_is(expr, PCMK_XE_RULE)) {
return pcmk__subexpr_rule;
} else if (!pcmk__xe_is(expr, PCMK_XE_EXPRESSION)) {
return pcmk__subexpr_unknown;
}
// Expression types based on node attribute name
name = crm_element_value(expr, PCMK_XA_ATTRIBUTE);
if (pcmk__str_any_of(name, CRM_ATTR_UNAME, CRM_ATTR_KIND, CRM_ATTR_ID,
NULL)) {
return pcmk__subexpr_location;
}
return pcmk__subexpr_attribute;
}
/*!
* \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] 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 *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",
pcmk__xe_id(date_spec), 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",
pcmk__xe_id(date_spec), attr, pcmk__s(range, ""), value,
pcmk_rc_str(rc));
return rc;
}
-#define CHECK_ONE(spec, name, var) do { \
- int subpart_rc = check_range(spec, name, var); \
- if (subpart_rc != pcmk_rc_ok) { \
- return subpart_rc; \
- } \
-} while (0)
-
+/*!
+ * \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, pcmk_rc_ok,
+ * pcmk_rc_before_range, pcmk_rc_after_range, or pcmk_rc_op_unsatisfied)
+ */
int
-pe_cron_range_satisfied(const crm_time_t *now, const xmlNode *cron_spec)
+pcmk__evaluate_date_spec(const xmlNode *date_spec, const crm_time_t *now)
{
- uint32_t h, m, s, y, d, w;
+ // 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 },
+ };
CRM_CHECK(now != NULL, return pcmk_rc_op_unsatisfied);
- crm_time_get_gregorian(now, &y, &m, &d);
- CHECK_ONE(cron_spec, PCMK_XA_YEARS, y);
- CHECK_ONE(cron_spec, PCMK_XA_MONTHS, m);
- CHECK_ONE(cron_spec, PCMK_XA_MONTHDAYS, d);
+ // Year, month, day
+ crm_time_get_gregorian(now, &(ranges[0].value), &(ranges[1].value),
+ &(ranges[2].value));
- crm_time_get_timeofday(now, &h, &m, &s);
- CHECK_ONE(cron_spec, PCMK_XA_HOURS, h);
- CHECK_ONE(cron_spec, PCMK_XA_MINUTES, m);
- CHECK_ONE(cron_spec, PCMK_XA_SECONDS, s);
+ // Hour, minute, second
+ crm_time_get_timeofday(now, &(ranges[3].value), &(ranges[4].value),
+ &(ranges[5].value));
- crm_time_get_ordinal(now, &y, &d);
- CHECK_ONE(cron_spec, PCMK_XA_YEARDAYS, d);
+ // Year (redundant) and day of year
+ crm_time_get_ordinal(now, &(ranges[0].value), &(ranges[6].value));
- crm_time_get_isoweek(now, &y, &w, &d);
- CHECK_ONE(cron_spec, PCMK_XA_WEEKYEARS, y);
- CHECK_ONE(cron_spec, PCMK_XA_WEEKS, w);
- CHECK_ONE(cron_spec, PCMK_XA_WEEKDAYS, d);
+ // Week year, week of week year, day of week
+ crm_time_get_isoweek(now, &(ranges[7].value), &(ranges[8].value),
+ &(ranges[9].value));
- CHECK_ONE(cron_spec, PCMK__XA_MOON, phase_of_the_moon(now));
- if (crm_element_value(cron_spec, PCMK__XA_MOON) != NULL) {
+ // 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",
- pcmk__xe_id(cron_spec));
+ pcmk__xe_id(date_spec));
}
- /* If we get here, either no fields were specified (which is success), or all
- * the fields that were specified had their conditions met (which is also a
- * success). Thus, the result is success.
- */
+ for (int i = 0; i < PCMK__NELEM(ranges); ++i) {
+ int rc = check_range(date_spec, 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;
}
diff --git a/lib/common/tests/rules/Makefile.am b/lib/common/tests/rules/Makefile.am
index 028a6d4955..3620f230e4 100644
--- a/lib/common/tests/rules/Makefile.am
+++ b/lib/common/tests/rules/Makefile.am
@@ -1,16 +1,16 @@
#
# Copyright 2020-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 $(top_srcdir)/mk/tap.mk
include $(top_srcdir)/mk/unittest.mk
# Add "_test" to the end of all test program names to simplify .gitignore.
-check_PROGRAMS = pe_cron_range_satisfied_test
+check_PROGRAMS = pcmk__evaluate_date_spec_test
TESTS = $(check_PROGRAMS)
diff --git a/lib/common/tests/rules/pe_cron_range_satisfied_test.c b/lib/common/tests/rules/pcmk__evaluate_date_spec_test.c
similarity index 96%
rename from lib/common/tests/rules/pe_cron_range_satisfied_test.c
rename to lib/common/tests/rules/pcmk__evaluate_date_spec_test.c
index 8f5b3d0889..ce22469593 100644
--- a/lib/common/tests/rules/pe_cron_range_satisfied_test.c
+++ b/lib/common/tests/rules/pcmk__evaluate_date_spec_test.c
@@ -1,196 +1,197 @@
/*
* Copyright 2020-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 <glib.h>
#include <crm/common/xml.h>
#include <crm/common/rules_internal.h>
#include <crm/common/unittest_internal.h>
static void
run_one_test(const char *t, const char *x, int expected) {
crm_time_t *tm = crm_time_new(t);
xmlNodePtr xml = string2xml(x);
- assert_int_equal(pe_cron_range_satisfied(tm, xml), expected);
+ assert_int_equal(pcmk__evaluate_date_spec(xml, tm), expected);
crm_time_free(tm);
free_xml(xml);
}
static void
no_time_given(void **state) {
- assert_int_equal(pe_cron_range_satisfied(NULL, NULL), pcmk_rc_op_unsatisfied);
+ assert_int_equal(pcmk__evaluate_date_spec(NULL, NULL),
+ pcmk_rc_op_unsatisfied);
}
static void
any_time_satisfies_empty_spec(void **state) {
crm_time_t *tm = crm_time_new(NULL);
- assert_int_equal(pe_cron_range_satisfied(tm, NULL), pcmk_rc_ok);
+ assert_int_equal(pcmk__evaluate_date_spec(NULL, tm), pcmk_rc_ok);
crm_time_free(tm);
}
static void
time_satisfies_year_spec(void **state) {
run_one_test("2020-01-01",
"<date_spec " PCMK_XA_ID "='spec' years='2020'/>",
pcmk_rc_ok);
}
static void
time_after_year_spec(void **state) {
run_one_test("2020-01-01",
"<" PCMK_XE_DATE_SPEC " "
PCMK_XA_ID "='spec' "
PCMK_XA_YEARS "='2019'/>",
pcmk_rc_after_range);
}
static void
time_satisfies_year_range(void **state) {
run_one_test("2020-01-01",
"<" PCMK_XE_DATE_SPEC " "
PCMK_XA_ID "='spec' "
PCMK_XA_YEARS "='2010-2030'/>",
pcmk_rc_ok);
}
static void
time_before_year_range(void **state) {
run_one_test("2000-01-01",
"<" PCMK_XE_DATE_SPEC " "
PCMK_XA_ID "='spec' "
PCMK_XA_YEARS "='2010-2030'/>",
pcmk_rc_before_range);
}
static void
time_after_year_range(void **state) {
run_one_test("2020-01-01",
"<" PCMK_XE_DATE_SPEC " "
PCMK_XA_ID "='spec' "
PCMK_XA_YEARS "='2010-2015'/>",
pcmk_rc_after_range);
}
static void
range_without_start_year_passes(void **state) {
run_one_test("2010-01-01",
"<" PCMK_XE_DATE_SPEC " "
PCMK_XA_ID "='spec' "
PCMK_XA_YEARS "='-2020'/>",
pcmk_rc_ok);
}
static void
range_without_end_year_passes(void **state) {
run_one_test("2010-01-01",
"<" PCMK_XE_DATE_SPEC " "
PCMK_XA_ID "='spec' "
PCMK_XA_YEARS "='2000-'/>",
pcmk_rc_ok);
run_one_test("2000-10-01",
"<" PCMK_XE_DATE_SPEC " "
PCMK_XA_ID "='spec' "
PCMK_XA_YEARS "='2000-'/>",
pcmk_rc_ok);
}
static void
yeardays_satisfies(void **state) {
run_one_test("2020-01-30",
"<" PCMK_XE_DATE_SPEC " "
PCMK_XA_ID "='spec' "
PCMK_XA_YEARDAYS "='30'/>",
pcmk_rc_ok);
}
static void
time_after_yeardays_spec(void **state) {
run_one_test("2020-02-15",
"<" PCMK_XE_DATE_SPEC " "
PCMK_XA_ID "='spec' "
PCMK_XA_YEARDAYS "='40'/>",
pcmk_rc_after_range);
}
static void
yeardays_feb_29_satisfies(void **state) {
run_one_test("2016-02-29",
"<" PCMK_XE_DATE_SPEC " "
PCMK_XA_ID "='spec' "
PCMK_XA_YEARDAYS "='60'/>",
pcmk_rc_ok);
}
static void
exact_ymd_satisfies(void **state) {
run_one_test("2001-12-31",
"<" PCMK_XE_DATE_SPEC " "
PCMK_XA_ID "='spec' "
PCMK_XA_YEARS "='2001' "
PCMK_XA_MONTHS "='12' "
PCMK_XA_MONTHDAYS "='31'/>",
pcmk_rc_ok);
}
static void
range_in_month_satisfies(void **state) {
run_one_test("2001-06-10",
"<" PCMK_XE_DATE_SPEC " "
PCMK_XA_ID "='spec' "
PCMK_XA_YEARS "='2001' "
PCMK_XA_MONTHS "='6' "
PCMK_XA_MONTHDAYS "='1-10'/>",
pcmk_rc_ok);
}
static void
exact_ymd_after_range(void **state) {
run_one_test("2001-12-31",
"<" PCMK_XE_DATE_SPEC " "
PCMK_XA_ID "='spec' "
PCMK_XA_YEARS "='2001' "
PCMK_XA_MONTHS "='12' "
PCMK_XA_MONTHDAYS "='30'/>",
pcmk_rc_after_range);
}
static void
time_after_monthdays_range(void **state) {
run_one_test("2001-06-10",
"<" PCMK_XE_DATE_SPEC " "
PCMK_XA_ID "='spec' "
PCMK_XA_YEARS "='2001' "
PCMK_XA_MONTHS "='6' "
PCMK_XA_MONTHDAYS "='11-15'/>",
pcmk_rc_before_range);
}
PCMK__UNIT_TEST(NULL, NULL,
cmocka_unit_test(no_time_given),
cmocka_unit_test(any_time_satisfies_empty_spec),
cmocka_unit_test(time_satisfies_year_spec),
cmocka_unit_test(time_after_year_spec),
cmocka_unit_test(time_satisfies_year_range),
cmocka_unit_test(time_before_year_range),
cmocka_unit_test(time_after_year_range),
cmocka_unit_test(range_without_start_year_passes),
cmocka_unit_test(range_without_end_year_passes),
cmocka_unit_test(yeardays_satisfies),
cmocka_unit_test(time_after_yeardays_spec),
cmocka_unit_test(yeardays_feb_29_satisfies),
cmocka_unit_test(exact_ymd_satisfies),
cmocka_unit_test(range_in_month_satisfies),
cmocka_unit_test(exact_ymd_after_range),
cmocka_unit_test(time_after_monthdays_range))
diff --git a/lib/pengine/rules.c b/lib/pengine/rules.c
index 72eede89a8..167b662c12 100644
--- a/lib/pengine/rules.c
+++ b/lib/pengine/rules.c
@@ -1,1114 +1,1114 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <crm/crm.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h>
#include <glib.h>
#include <crm/pengine/rules.h>
#include <crm/pengine/rules_internal.h>
#include <crm/pengine/internal.h>
#include <sys/types.h>
#include <regex.h>
#include <ctype.h>
CRM_TRACE_INIT_DATA(pe_rules);
/*!
* \brief Evaluate any rules contained by given XML element
*
* \param[in,out] xml XML element to check for rules
* \param[in] node_hash Node attributes to use to evaluate expressions
* \param[in] now Time to use when evaluating expressions
* \param[out] next_change If not NULL, set to when evaluation will change
*
* \return TRUE if no rules, or any of rules present is in effect, else FALSE
*/
gboolean
pe_evaluate_rules(xmlNode *ruleset, GHashTable *node_hash, crm_time_t *now,
crm_time_t *next_change)
{
pe_rule_eval_data_t rule_data = {
.node_hash = node_hash,
.now = now,
.match_data = NULL,
.rsc_data = NULL,
.op_data = NULL
};
return pe_eval_rules(ruleset, &rule_data, next_change);
}
gboolean
pe_test_rule(xmlNode *rule, GHashTable *node_hash, enum rsc_role_e role,
crm_time_t *now, crm_time_t *next_change,
pe_match_data_t *match_data)
{
pe_rule_eval_data_t rule_data = {
.node_hash = node_hash,
.now = now,
.match_data = match_data,
.rsc_data = NULL,
.op_data = NULL
};
return pe_eval_expr(rule, &rule_data, next_change);
}
/*!
* \brief Evaluate one rule subelement (pass/fail)
*
* A rule element may contain another rule, a node attribute expression, or a
* date expression. Given any one of those, evaluate it and return whether it
* passed.
*
* \param[in,out] expr Rule subelement XML
* \param[in] node_hash Node attributes to use when evaluating expression
* \param[in] role Ignored (deprecated)
* \param[in] now Time to use when evaluating expression
* \param[out] next_change If not NULL, set to when evaluation will change
* \param[in] match_data If not NULL, resource back-references and params
*
* \return TRUE if expression is in effect under given conditions, else FALSE
*/
gboolean
pe_test_expression(xmlNode *expr, GHashTable *node_hash, enum rsc_role_e role,
crm_time_t *now, crm_time_t *next_change,
pe_match_data_t *match_data)
{
pe_rule_eval_data_t rule_data = {
.node_hash = node_hash,
.now = now,
.match_data = match_data,
.rsc_data = NULL,
.op_data = NULL
};
return pe_eval_subexpr(expr, &rule_data, next_change);
}
static void
update_field(crm_time_t *t, const xmlNode *xml, const char *attr,
void (*time_fn)(crm_time_t *, int))
{
long long value;
if ((pcmk__scan_ll(crm_element_value(xml, attr), &value, 0LL) == pcmk_rc_ok)
&& (value != 0LL) && (value >= INT_MIN) && (value <= INT_MAX)) {
time_fn(t, (int) value);
}
}
static crm_time_t *
parse_xml_duration(const crm_time_t *start, const xmlNode *duration_spec)
{
crm_time_t *end = pcmk_copy_time(start);
update_field(end, duration_spec, PCMK_XA_YEARS, crm_time_add_years);
update_field(end, duration_spec, PCMK_XA_MONTHS, crm_time_add_months);
update_field(end, duration_spec, PCMK_XA_WEEKS, crm_time_add_weeks);
update_field(end, duration_spec, PCMK_XA_DAYS, crm_time_add_days);
update_field(end, duration_spec, PCMK_XA_HOURS, crm_time_add_hours);
update_field(end, duration_spec, PCMK_XA_MINUTES, crm_time_add_minutes);
update_field(end, duration_spec, PCMK_XA_SECONDS, crm_time_add_seconds);
return end;
}
// Set next_change to t if t is earlier
static void
crm_time_set_if_earlier(crm_time_t *next_change, crm_time_t *t)
{
if ((next_change != NULL) && (t != NULL)) {
if (!crm_time_is_defined(next_change)
|| (crm_time_compare(t, next_change) < 0)) {
crm_time_set(next_change, t);
}
}
}
// Information about a block of nvpair elements
typedef struct sorted_set_s {
int score; // This block's score for sorting
const char *name; // This block's ID
const char *special_name; // ID that should sort first
xmlNode *attr_set; // This block
gboolean overwrite; // Whether existing values will be overwritten
} sorted_set_t;
static gint
sort_pairs(gconstpointer a, gconstpointer b)
{
const sorted_set_t *pair_a = a;
const sorted_set_t *pair_b = b;
if (a == NULL && b == NULL) {
return 0;
} else if (a == NULL) {
return 1;
} else if (b == NULL) {
return -1;
}
if (pcmk__str_eq(pair_a->name, pair_a->special_name, pcmk__str_casei)) {
return -1;
} else if (pcmk__str_eq(pair_b->name, pair_a->special_name, pcmk__str_casei)) {
return 1;
}
/* If we're overwriting values, we want lowest score first, so the highest
* score is processed last; if we're not overwriting values, we want highest
* score first, so nothing else overwrites it.
*/
if (pair_a->score < pair_b->score) {
return pair_a->overwrite? -1 : 1;
} else if (pair_a->score > pair_b->score) {
return pair_a->overwrite? 1 : -1;
}
return 0;
}
static void
populate_hash(xmlNode * nvpair_list, GHashTable * hash, gboolean overwrite, xmlNode * top)
{
const char *name = NULL;
const char *value = NULL;
const char *old_value = NULL;
xmlNode *list = nvpair_list;
xmlNode *an_attr = NULL;
if (pcmk__xe_is(list->children, PCMK__XE_ATTRIBUTES)) {
list = list->children;
}
for (an_attr = pcmk__xe_first_child(list); an_attr != NULL;
an_attr = pcmk__xe_next(an_attr)) {
if (pcmk__xe_is(an_attr, PCMK_XE_NVPAIR)) {
xmlNode *ref_nvpair = expand_idref(an_attr, top);
name = crm_element_value(an_attr, PCMK_XA_NAME);
if ((name == NULL) && (ref_nvpair != NULL)) {
name = crm_element_value(ref_nvpair, PCMK_XA_NAME);
}
value = crm_element_value(an_attr, PCMK_XA_VALUE);
if ((value == NULL) && (ref_nvpair != NULL)) {
value = crm_element_value(ref_nvpair, PCMK_XA_VALUE);
}
if (name == NULL || value == NULL) {
continue;
}
old_value = g_hash_table_lookup(hash, name);
if (pcmk__str_eq(value, "#default", pcmk__str_casei)) {
if (old_value) {
crm_trace("Letting %s default (removing explicit value \"%s\")",
name, value);
g_hash_table_remove(hash, name);
}
continue;
} else if (old_value == NULL) {
crm_trace("Setting %s=\"%s\"", name, value);
pcmk__insert_dup(hash, name, value);
} else if (overwrite) {
crm_trace("Setting %s=\"%s\" (overwriting old value \"%s\")",
name, value, old_value);
pcmk__insert_dup(hash, name, value);
}
}
}
}
typedef struct unpack_data_s {
gboolean overwrite;
void *hash;
crm_time_t *next_change;
const pe_rule_eval_data_t *rule_data;
xmlNode *top;
} unpack_data_t;
static void
unpack_attr_set(gpointer data, gpointer user_data)
{
sorted_set_t *pair = data;
unpack_data_t *unpack_data = user_data;
if (!pe_eval_rules(pair->attr_set, unpack_data->rule_data,
unpack_data->next_change)) {
return;
}
crm_trace("Adding attributes from %s (score %d) %s overwrite",
pair->name, pair->score,
(unpack_data->overwrite? "with" : "without"));
populate_hash(pair->attr_set, unpack_data->hash, unpack_data->overwrite, unpack_data->top);
}
/*!
* \internal
* \brief Create a sorted list of nvpair blocks
*
* \param[in,out] top XML document root (used to expand id-ref's)
* \param[in] xml_obj XML element containing blocks of nvpair elements
* \param[in] set_name If not NULL, only get blocks of this element
* \param[in] always_first If not NULL, sort block with this ID as first
*
* \return List of sorted_set_t entries for nvpair blocks
*/
static GList *
make_pairs(xmlNode *top, const xmlNode *xml_obj, const char *set_name,
const char *always_first, gboolean overwrite)
{
GList *unsorted = NULL;
if (xml_obj == NULL) {
return NULL;
}
for (xmlNode *attr_set = pcmk__xe_first_child(xml_obj); attr_set != NULL;
attr_set = pcmk__xe_next(attr_set)) {
if ((set_name == NULL) || pcmk__xe_is(attr_set, set_name)) {
const char *score = NULL;
sorted_set_t *pair = NULL;
xmlNode *expanded_attr_set = expand_idref(attr_set, top);
if (expanded_attr_set == NULL) {
continue; // Not possible with schema validation enabled
}
pair = calloc(1, sizeof(sorted_set_t));
pair->name = pcmk__xe_id(expanded_attr_set);
pair->special_name = always_first;
pair->attr_set = expanded_attr_set;
pair->overwrite = overwrite;
score = crm_element_value(expanded_attr_set, PCMK_XA_SCORE);
pair->score = char2score(score);
unsorted = g_list_prepend(unsorted, pair);
}
}
return g_list_sort(unsorted, sort_pairs);
}
/*!
* \brief Extract nvpair blocks contained by an XML element into a hash table
*
* \param[in,out] top XML document root (used to expand id-ref's)
* \param[in] xml_obj XML element containing blocks of nvpair elements
* \param[in] set_name If not NULL, only use blocks of this element
* \param[in] rule_data Matching parameters to use when unpacking
* \param[out] hash Where to store extracted name/value pairs
* \param[in] always_first If not NULL, process block with this ID first
* \param[in] overwrite Whether to replace existing values with same name
* \param[out] next_change If not NULL, set to when evaluation will change
*/
void
pe_eval_nvpairs(xmlNode *top, const xmlNode *xml_obj, const char *set_name,
const pe_rule_eval_data_t *rule_data, GHashTable *hash,
const char *always_first, gboolean overwrite,
crm_time_t *next_change)
{
GList *pairs = make_pairs(top, xml_obj, set_name, always_first, overwrite);
if (pairs) {
unpack_data_t data = {
.hash = hash,
.overwrite = overwrite,
.next_change = next_change,
.top = top,
.rule_data = rule_data
};
g_list_foreach(pairs, unpack_attr_set, &data);
g_list_free_full(pairs, free);
}
}
/*!
* \brief Extract nvpair blocks contained by an XML element into a hash table
*
* \param[in,out] top XML document root (used to expand id-ref's)
* \param[in] xml_obj XML element containing blocks of nvpair elements
* \param[in] set_name Element name to identify nvpair blocks
* \param[in] node_hash Node attributes to use when evaluating rules
* \param[out] hash Where to store extracted name/value pairs
* \param[in] always_first If not NULL, process block with this ID first
* \param[in] overwrite Whether to replace existing values with same name
* \param[in] now Time to use when evaluating rules
* \param[out] next_change If not NULL, set to when evaluation will change
*/
void
pe_unpack_nvpairs(xmlNode *top, const xmlNode *xml_obj, const char *set_name,
GHashTable *node_hash, GHashTable *hash,
const char *always_first, gboolean overwrite,
crm_time_t *now, crm_time_t *next_change)
{
pe_rule_eval_data_t rule_data = {
.node_hash = node_hash,
.now = now,
.match_data = NULL,
.rsc_data = NULL,
.op_data = NULL
};
pe_eval_nvpairs(top, xml_obj, set_name, &rule_data, hash,
always_first, overwrite, next_change);
}
/*!
* \brief Expand any regular expression submatches (%0-%9) in a string
*
* \param[in] string String possibly containing submatch variables
* \param[in] match_data If not NULL, regular expression matches
*
* \return Newly allocated string identical to \p string with submatches
* expanded, or NULL if there were no matches
*/
char *
pe_expand_re_matches(const char *string, const pe_re_match_data_t *match_data)
{
size_t len = 0;
int i;
const char *p, *last_match_index;
char *p_dst, *result = NULL;
if (pcmk__str_empty(string) || !match_data) {
return NULL;
}
p = last_match_index = string;
while (*p) {
if (*p == '%' && *(p + 1) && isdigit(*(p + 1))) {
i = *(p + 1) - '0';
if (match_data->nregs >= i && match_data->pmatch[i].rm_so != -1 &&
match_data->pmatch[i].rm_eo > match_data->pmatch[i].rm_so) {
len += p - last_match_index + (match_data->pmatch[i].rm_eo - match_data->pmatch[i].rm_so);
last_match_index = p + 2;
}
p++;
}
p++;
}
len += p - last_match_index + 1;
/* FIXME: Excessive? */
if (len - 1 <= 0) {
return NULL;
}
p_dst = result = calloc(1, len);
p = string;
while (*p) {
if (*p == '%' && *(p + 1) && isdigit(*(p + 1))) {
i = *(p + 1) - '0';
if (match_data->nregs >= i && match_data->pmatch[i].rm_so != -1 &&
match_data->pmatch[i].rm_eo > match_data->pmatch[i].rm_so) {
/* rm_eo can be equal to rm_so, but then there is nothing to do */
int match_len = match_data->pmatch[i].rm_eo - match_data->pmatch[i].rm_so;
memcpy(p_dst, match_data->string + match_data->pmatch[i].rm_so, match_len);
p_dst += match_len;
}
p++;
} else {
*(p_dst) = *(p);
p_dst++;
}
p++;
}
return result;
}
/*!
* \brief Evaluate rules
*
* \param[in,out] ruleset XML possibly containing rule sub-elements
* \param[in] rule_data
* \param[out] next_change If not NULL, set to when evaluation will change
*
* \return TRUE if there are no rules or
*/
gboolean
pe_eval_rules(xmlNode *ruleset, const pe_rule_eval_data_t *rule_data,
crm_time_t *next_change)
{
// If there are no rules, pass by default
gboolean ruleset_default = TRUE;
for (xmlNode *rule = first_named_child(ruleset, PCMK_XE_RULE);
rule != NULL; rule = crm_next_same_xml(rule)) {
ruleset_default = FALSE;
if (pe_eval_expr(rule, rule_data, next_change)) {
/* Only the deprecated PCMK__XE_LIFETIME element of location
* constraints may contain more than one rule at the top level --
* the schema limits a block of nvpairs to a single top-level rule.
* So, this effectively means that a lifetime is active if any rule
* it contains is active.
*/
return TRUE;
}
}
return ruleset_default;
}
/*!
* \brief Evaluate all of a rule's expressions
*
* \param[in,out] rule XML containing a rule definition or its id-ref
* \param[in] rule_data Matching parameters to check against rule
* \param[out] next_change If not NULL, set to when evaluation will change
*
* \return TRUE if \p rule_data passes \p rule, otherwise FALSE
*/
gboolean
pe_eval_expr(xmlNode *rule, const pe_rule_eval_data_t *rule_data,
crm_time_t *next_change)
{
xmlNode *expr = NULL;
gboolean test = TRUE;
gboolean empty = TRUE;
gboolean passed = TRUE;
gboolean do_and = TRUE;
const char *value = NULL;
rule = expand_idref(rule, NULL);
if (rule == NULL) {
return FALSE; // Not possible with schema validation enabled
}
value = crm_element_value(rule, PCMK_XA_BOOLEAN_OP);
if (pcmk__str_eq(value, PCMK_VALUE_OR, pcmk__str_casei)) {
do_and = FALSE;
passed = FALSE;
} else if (!pcmk__str_eq(value, PCMK_VALUE_AND,
pcmk__str_null_matches|pcmk__str_casei)) {
pcmk__config_warn("Rule %s has invalid " PCMK_XA_BOOLEAN_OP
" value '%s', using default ('" PCMK_VALUE_AND "')",
pcmk__xe_id(rule), value);
}
crm_trace("Testing rule %s", pcmk__xe_id(rule));
for (expr = pcmk__xe_first_child(rule); expr != NULL;
expr = pcmk__xe_next(expr)) {
test = pe_eval_subexpr(expr, rule_data, next_change);
empty = FALSE;
if (test && do_and == FALSE) {
crm_trace("Expression %s/%s passed",
pcmk__xe_id(rule), pcmk__xe_id(expr));
return TRUE;
} else if (test == FALSE && do_and) {
crm_trace("Expression %s/%s failed",
pcmk__xe_id(rule), pcmk__xe_id(expr));
return FALSE;
}
}
if (empty) {
pcmk__config_err("Ignoring rule %s because it contains no expressions",
pcmk__xe_id(rule));
}
crm_trace("Rule %s %s", pcmk__xe_id(rule), passed ? "passed" : "failed");
return passed;
}
/*!
* \brief Evaluate a single rule expression, including any subexpressions
*
* \param[in,out] expr XML containing a rule expression
* \param[in] rule_data Matching parameters to check against expression
* \param[out] next_change If not NULL, set to when evaluation will change
*
* \return TRUE if \p rule_data passes \p expr, otherwise FALSE
*/
gboolean
pe_eval_subexpr(xmlNode *expr, const pe_rule_eval_data_t *rule_data,
crm_time_t *next_change)
{
gboolean accept = FALSE;
const char *uname = NULL;
switch (pcmk__expression_type(expr)) {
case pcmk__subexpr_rule:
accept = pe_eval_expr(expr, rule_data, next_change);
break;
case pcmk__subexpr_attribute:
case pcmk__subexpr_location:
/* these expressions can never succeed if there is
* no node to compare with
*/
if (rule_data->node_hash != NULL) {
accept = pe__eval_attr_expr(expr, rule_data);
}
break;
case pcmk__subexpr_datetime:
switch (pe__eval_date_expr(expr, rule_data, next_change)) {
case pcmk_rc_within_range:
case pcmk_rc_ok:
accept = TRUE;
break;
default:
accept = FALSE;
break;
}
break;
case pcmk__subexpr_resource:
accept = pe__eval_rsc_expr(expr, rule_data);
break;
case pcmk__subexpr_operation:
accept = pe__eval_op_expr(expr, rule_data);
break;
default:
CRM_CHECK(FALSE /* bad type */ , return FALSE);
accept = FALSE;
}
if (rule_data->node_hash) {
uname = g_hash_table_lookup(rule_data->node_hash, CRM_ATTR_UNAME);
}
crm_trace("Expression %s %s on %s",
pcmk__xe_id(expr), (accept? "passed" : "failed"),
pcmk__s(uname, "all nodes"));
return accept;
}
/*!
* \internal
* \brief Compare two values in a rule's node attribute expression
*
* \param[in] l_val Value on left-hand side of comparison
* \param[in] r_val Value on right-hand side of comparison
* \param[in] type How to interpret the values (allowed values:
* \c PCMK_VALUE_STRING, \c PCMK_VALUE_INTEGER,
* \c PCMK_VALUE_NUMBER, \c PCMK_VALUE_VERSION, \c NULL)
* \param[in] op Type of comparison
*
* \return -1 if <tt>(l_val < r_val)</tt>,
* 0 if <tt>(l_val == r_val)</tt>,
* 1 if <tt>(l_val > r_val)</tt>
*/
static int
compare_attr_expr_vals(const char *l_val, const char *r_val, const char *type,
const char *op)
{
int cmp = 0;
if (l_val != NULL && r_val != NULL) {
if (type == NULL) {
if (pcmk__strcase_any_of(op,
PCMK_VALUE_LT, PCMK_VALUE_LTE,
PCMK_VALUE_GT, PCMK_VALUE_GTE, NULL)) {
if (pcmk__char_in_any_str('.', l_val, r_val, NULL)) {
type = PCMK_VALUE_NUMBER;
} else {
type = PCMK_VALUE_INTEGER;
}
} else {
type = PCMK_VALUE_STRING;
}
crm_trace("Defaulting to %s based comparison for '%s' op", type, op);
}
if (pcmk__str_eq(type, PCMK_VALUE_STRING, pcmk__str_casei)) {
cmp = strcasecmp(l_val, r_val);
} else if (pcmk__str_eq(type, PCMK_VALUE_INTEGER, pcmk__str_casei)) {
long long l_val_num;
int rc1 = pcmk__scan_ll(l_val, &l_val_num, 0LL);
long long r_val_num;
int rc2 = pcmk__scan_ll(r_val, &r_val_num, 0LL);
if ((rc1 == pcmk_rc_ok) && (rc2 == pcmk_rc_ok)) {
if (l_val_num < r_val_num) {
cmp = -1;
} else if (l_val_num > r_val_num) {
cmp = 1;
} else {
cmp = 0;
}
} else {
crm_debug("Integer parse error. Comparing %s and %s as strings",
l_val, r_val);
cmp = compare_attr_expr_vals(l_val, r_val, PCMK_VALUE_STRING,
op);
}
} else if (pcmk__str_eq(type, PCMK_VALUE_NUMBER, pcmk__str_casei)) {
double l_val_num;
double r_val_num;
int rc1 = pcmk__scan_double(l_val, &l_val_num, NULL, NULL);
int rc2 = pcmk__scan_double(r_val, &r_val_num, NULL, NULL);
if (rc1 == pcmk_rc_ok && rc2 == pcmk_rc_ok) {
if (l_val_num < r_val_num) {
cmp = -1;
} else if (l_val_num > r_val_num) {
cmp = 1;
} else {
cmp = 0;
}
} else {
crm_debug("Floating-point parse error. Comparing %s and %s as "
"strings", l_val, r_val);
cmp = compare_attr_expr_vals(l_val, r_val, PCMK_VALUE_STRING,
op);
}
} else if (pcmk__str_eq(type, PCMK_VALUE_VERSION, pcmk__str_casei)) {
cmp = compare_version(l_val, r_val);
}
} else if (l_val == NULL && r_val == NULL) {
cmp = 0;
} else if (r_val == NULL) {
cmp = 1;
} else { // l_val == NULL && r_val != NULL
cmp = -1;
}
return cmp;
}
/*!
* \internal
* \brief Check whether an attribute expression evaluates to \c true
*
* \param[in] l_val Value on left-hand side of comparison
* \param[in] r_val Value on right-hand side of comparison
* \param[in] type How to interpret the values (allowed values:
* \c PCMK_VALUE_STRING, \c PCMK_VALUE_INTEGER,
* \c PCMK_VALUE_NUMBER, \c PCMK_VALUE_VERSION, \c NULL)
* \param[in] op Type of comparison.
*
* \return \c true if expression evaluates to \c true, \c false
* otherwise
*/
static bool
accept_attr_expr(const char *l_val, const char *r_val, const char *type,
const char *op)
{
int cmp;
if (pcmk__str_eq(op, PCMK_VALUE_DEFINED, pcmk__str_casei)) {
return (l_val != NULL);
} else if (pcmk__str_eq(op, PCMK_VALUE_NOT_DEFINED, pcmk__str_casei)) {
return (l_val == NULL);
}
cmp = compare_attr_expr_vals(l_val, r_val, type, op);
if (pcmk__str_eq(op, PCMK_VALUE_EQ, pcmk__str_casei)) {
return (cmp == 0);
} else if (pcmk__str_eq(op, PCMK_VALUE_NE, pcmk__str_casei)) {
return (cmp != 0);
} else if (l_val == NULL || r_val == NULL) {
// The comparison is meaningless from this point on
return false;
} else if (pcmk__str_eq(op, PCMK_VALUE_LT, pcmk__str_casei)) {
return (cmp < 0);
} else if (pcmk__str_eq(op, PCMK_VALUE_LTE, pcmk__str_casei)) {
return (cmp <= 0);
} else if (pcmk__str_eq(op, PCMK_VALUE_GT, pcmk__str_casei)) {
return (cmp > 0);
} else if (pcmk__str_eq(op, PCMK_VALUE_GTE, pcmk__str_casei)) {
return (cmp >= 0);
}
return false; // Should never reach this point
}
/*!
* \internal
* \brief Get correct value according to \c PCMK_XA_VALUE_SOURCE
*
* \param[in] expr_id Rule expression ID (for logging only)
* \param[in] value value given in rule expression
* \param[in] value_source \c PCMK_XA_VALUE_SOURCE given in rule expressions
* \param[in] match_data If not NULL, resource back-references and params
*/
static const char *
expand_value_source(const char *expr_id, const char *value,
const char *value_source, const pe_match_data_t *match_data)
{
GHashTable *table = NULL;
if (pcmk__str_empty(value)) {
return NULL; // value_source is irrelevant
} else if (pcmk__str_eq(value_source, PCMK_VALUE_PARAM, pcmk__str_casei)) {
table = match_data->params;
} else if (pcmk__str_eq(value_source, PCMK_VALUE_META, pcmk__str_casei)) {
table = match_data->meta;
} else { // literal
if (!pcmk__str_eq(value_source, PCMK_VALUE_LITERAL,
pcmk__str_null_matches|pcmk__str_casei)) {
pcmk__config_warn("Expression %s has invalid " PCMK_XA_VALUE_SOURCE
" value '%s', using default "
"('" PCMK_VALUE_LITERAL "')",
pcmk__s(expr_id, "without ID"), value_source);
}
return value;
}
if (table == NULL) {
return NULL;
}
return (const char *) g_hash_table_lookup(table, value);
}
/*!
* \internal
* \brief Evaluate a node attribute expression based on #uname, #id, #kind,
* or a generic node attribute
*
* \param[in] expr XML of rule expression
* \param[in] rule_data The match_data and node_hash members are used
*
* \return TRUE if rule_data satisfies the expression, FALSE otherwise
*/
gboolean
pe__eval_attr_expr(const xmlNode *expr, const pe_rule_eval_data_t *rule_data)
{
gboolean attr_allocated = FALSE;
const char *h_val = NULL;
const char *id = pcmk__xe_id(expr);
const char *attr = crm_element_value(expr, PCMK_XA_ATTRIBUTE);
const char *op = crm_element_value(expr, PCMK_XA_OPERATION);
const char *type = crm_element_value(expr, PCMK_XA_TYPE);
const char *value = crm_element_value(expr, PCMK_XA_VALUE);
const char *value_source = crm_element_value(expr, PCMK_XA_VALUE_SOURCE);
if (attr == NULL) {
pcmk__config_err("Expression %s invalid: " PCMK_XA_ATTRIBUTE
" not specified", pcmk__s(id, "without ID"));
return FALSE;
} else if (op == NULL) {
pcmk__config_err("Expression %s invalid: " PCMK_XA_OPERATION
" not specified", pcmk__s(id, "without ID"));
return FALSE;
}
if (rule_data->match_data != NULL) {
// Expand any regular expression submatches (%0-%9) in attribute name
if (rule_data->match_data->re != NULL) {
char *resolved_attr = pe_expand_re_matches(attr, rule_data->match_data->re);
if (resolved_attr != NULL) {
attr = (const char *) resolved_attr;
attr_allocated = TRUE;
}
}
// Get value appropriate to PCMK_XA_VALUE_SOURCE
value = expand_value_source(id, value, value_source,
rule_data->match_data);
}
if (rule_data->node_hash != NULL) {
h_val = (const char *)g_hash_table_lookup(rule_data->node_hash, attr);
}
if (attr_allocated) {
free((char *)attr);
attr = NULL;
}
return accept_attr_expr(h_val, value, type, op);
}
/*!
* \internal
* \brief Evaluate a date_expression
*
* \param[in] expr XML of rule expression
* \param[in] rule_data Only the now member is used
* \param[out] next_change If not NULL, set to when evaluation will change
*
* \return Standard Pacemaker return code
*/
int
pe__eval_date_expr(const xmlNode *expr, const pe_rule_eval_data_t *rule_data,
crm_time_t *next_change)
{
crm_time_t *start = NULL;
crm_time_t *end = NULL;
const char *value = NULL;
const char *op = crm_element_value(expr, PCMK_XA_OPERATION);
xmlNode *duration_spec = NULL;
xmlNode *date_spec = NULL;
// "undetermined" will also be returned for parsing errors
int rc = pcmk_rc_undetermined;
crm_trace("Testing expression: %s", pcmk__xe_id(expr));
duration_spec = first_named_child(expr, PCMK_XE_DURATION);
date_spec = first_named_child(expr, PCMK_XE_DATE_SPEC);
value = crm_element_value(expr, PCMK_XA_START);
if (value != NULL) {
start = crm_time_new(value);
}
value = crm_element_value(expr, PCMK_XA_END);
if (value != NULL) {
end = crm_time_new(value);
}
if (start != NULL && end == NULL && duration_spec != NULL) {
end = parse_xml_duration(start, duration_spec);
}
if (pcmk__str_eq(op, "in_range", pcmk__str_null_matches | pcmk__str_casei)) {
if ((start == NULL) && (end == NULL)) {
// in_range requires at least one of start or end
} else if ((start != NULL) && (crm_time_compare(rule_data->now, start) < 0)) {
rc = pcmk_rc_before_range;
crm_time_set_if_earlier(next_change, start);
} else if ((end != NULL) && (crm_time_compare(rule_data->now, end) > 0)) {
rc = pcmk_rc_after_range;
} else {
rc = pcmk_rc_within_range;
if (end && next_change) {
// Evaluation doesn't change until second after end
crm_time_add_seconds(end, 1);
crm_time_set_if_earlier(next_change, end);
}
}
} else if (pcmk__str_eq(op, PCMK_VALUE_DATE_SPEC, pcmk__str_casei)) {
- rc = pe_cron_range_satisfied(rule_data->now, date_spec);
+ rc = pcmk__evaluate_date_spec(date_spec, rule_data->now);
// @TODO set next_change appropriately
} else if (pcmk__str_eq(op, PCMK_VALUE_GT, pcmk__str_casei)) {
if (start == NULL) {
// gt requires start
} else if (crm_time_compare(rule_data->now, start) > 0) {
rc = pcmk_rc_within_range;
} else {
rc = pcmk_rc_before_range;
// Evaluation doesn't change until second after start
crm_time_add_seconds(start, 1);
crm_time_set_if_earlier(next_change, start);
}
} else if (pcmk__str_eq(op, PCMK_VALUE_LT, pcmk__str_casei)) {
if (end == NULL) {
// lt requires end
} else if (crm_time_compare(rule_data->now, end) < 0) {
rc = pcmk_rc_within_range;
crm_time_set_if_earlier(next_change, end);
} else {
rc = pcmk_rc_after_range;
}
}
crm_time_free(start);
crm_time_free(end);
return rc;
}
gboolean
pe__eval_op_expr(const xmlNode *expr, const pe_rule_eval_data_t *rule_data)
{
const char *name = crm_element_value(expr, PCMK_XA_NAME);
const char *interval_s = crm_element_value(expr, PCMK_META_INTERVAL);
guint interval_ms = 0U;
crm_trace("Testing op_defaults expression: %s", pcmk__xe_id(expr));
if (rule_data->op_data == NULL) {
crm_trace("No operations data provided");
return FALSE;
}
if (pcmk_parse_interval_spec(interval_s, &interval_ms) != pcmk_rc_ok) {
crm_trace("Could not parse interval: %s", interval_s);
return FALSE;
}
if ((interval_s != NULL) && (interval_ms != rule_data->op_data->interval)) {
crm_trace("Interval doesn't match: %d != %d",
interval_ms, rule_data->op_data->interval);
return FALSE;
}
if (!pcmk__str_eq(name, rule_data->op_data->op_name, pcmk__str_none)) {
crm_trace("Name doesn't match: %s != %s", name, rule_data->op_data->op_name);
return FALSE;
}
return TRUE;
}
gboolean
pe__eval_rsc_expr(const xmlNode *expr, const pe_rule_eval_data_t *rule_data)
{
const char *class = crm_element_value(expr, PCMK_XA_CLASS);
const char *provider = crm_element_value(expr, PCMK_XA_PROVIDER);
const char *type = crm_element_value(expr, PCMK_XA_TYPE);
crm_trace("Testing rsc_defaults expression: %s", pcmk__xe_id(expr));
if (rule_data->rsc_data == NULL) {
crm_trace("No resource data provided");
return FALSE;
}
if (class != NULL &&
!pcmk__str_eq(class, rule_data->rsc_data->standard, pcmk__str_none)) {
crm_trace("Class doesn't match: %s != %s", class, rule_data->rsc_data->standard);
return FALSE;
}
if ((provider == NULL && rule_data->rsc_data->provider != NULL) ||
(provider != NULL && rule_data->rsc_data->provider == NULL) ||
!pcmk__str_eq(provider, rule_data->rsc_data->provider, pcmk__str_none)) {
crm_trace("Provider doesn't match: %s != %s", provider, rule_data->rsc_data->provider);
return FALSE;
}
if (type != NULL &&
!pcmk__str_eq(type, rule_data->rsc_data->agent, pcmk__str_none)) {
crm_trace("Agent doesn't match: %s != %s", type, rule_data->rsc_data->agent);
return FALSE;
}
return TRUE;
}
// Deprecated functions kept only for backward API compatibility
// LCOV_EXCL_START
#include <crm/pengine/rules_compat.h>
gboolean
test_ruleset(xmlNode *ruleset, GHashTable *node_hash, crm_time_t *now)
{
return pe_evaluate_rules(ruleset, node_hash, now, NULL);
}
gboolean
test_rule(xmlNode * rule, GHashTable * node_hash, enum rsc_role_e role, crm_time_t * now)
{
return pe_test_rule(rule, node_hash, role, now, NULL, NULL);
}
gboolean
pe_test_rule_re(xmlNode * rule, GHashTable * node_hash, enum rsc_role_e role, crm_time_t * now, pe_re_match_data_t * re_match_data)
{
pe_match_data_t match_data = {
.re = re_match_data,
.params = NULL,
.meta = NULL,
};
return pe_test_rule(rule, node_hash, role, now, NULL, &match_data);
}
gboolean
pe_test_rule_full(xmlNode *rule, GHashTable *node_hash, enum rsc_role_e role,
crm_time_t *now, pe_match_data_t *match_data)
{
return pe_test_rule(rule, node_hash, role, now, NULL, match_data);
}
gboolean
test_expression(xmlNode * expr, GHashTable * node_hash, enum rsc_role_e role, crm_time_t * now)
{
return pe_test_expression(expr, node_hash, role, now, NULL, NULL);
}
gboolean
pe_test_expression_re(xmlNode * expr, GHashTable * node_hash, enum rsc_role_e role, crm_time_t * now, pe_re_match_data_t * re_match_data)
{
pe_match_data_t match_data = {
.re = re_match_data,
.params = NULL,
.meta = NULL,
};
return pe_test_expression(expr, node_hash, role, now, NULL, &match_data);
}
gboolean
pe_test_expression_full(xmlNode *expr, GHashTable *node_hash,
enum rsc_role_e role, crm_time_t *now,
pe_match_data_t *match_data)
{
return pe_test_expression(expr, node_hash, role, now, NULL, match_data);
}
void
unpack_instance_attributes(xmlNode *top, xmlNode *xml_obj, const char *set_name,
GHashTable *node_hash, GHashTable *hash,
const char *always_first, gboolean overwrite,
crm_time_t *now)
{
pe_rule_eval_data_t rule_data = {
.node_hash = node_hash,
.now = now,
.match_data = NULL,
.rsc_data = NULL,
.op_data = NULL
};
pe_eval_nvpairs(top, xml_obj, set_name, &rule_data, hash, always_first,
overwrite, NULL);
}
enum expression_type
find_expression_type(xmlNode *expr)
{
return pcmk__expression_type(expr);
}
// LCOV_EXCL_STOP
// End deprecated API
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Jan 25, 12:33 PM (13 h, 12 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1318363
Default Alt Text
(54 KB)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment