Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F3686388
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
15 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/common/rules.c b/lib/common/rules.c
index ace9cc51c6..4374e4ad5f 100644
--- a/lib/common/rules.c
+++ b/lib/common/rules.c
@@ -1,217 +1,233 @@
/*
* 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] id XML ID for logging purposes
* \param[in] attr Name of XML attribute with range to check against
* \param[in] value Value to compare against range
*
* \return Standard Pacemaker return code (specifically, pcmk_rc_before_range,
* pcmk_rc_after_range, or pcmk_rc_ok to indicate that result is either
* within range or undetermined)
* \note We return pcmk_rc_ok for an undetermined result so we can continue
* checking the next range attribute.
*/
static int
-check_range(const xmlNode *date_spec, const char *attr, uint32_t value)
+check_range(const xmlNode *date_spec, const char *id, const char *attr,
+ uint32_t value)
{
int rc = pcmk_rc_ok;
const char *range = crm_element_value(date_spec, attr);
long long low, high;
if (range == NULL) { // Attribute not present
goto bail;
}
if (pcmk__parse_ll_range(range, &low, &high) != pcmk_rc_ok) {
// Invalid range
/* @COMPAT When we can break behavioral backward compatibility, treat
* the entire rule as not passing.
*/
pcmk__config_err("Ignoring " PCMK_XE_DATE_SPEC
" %s attribute %s because '%s' is not a valid range",
- pcmk__xe_id(date_spec), attr, range);
+ id, attr, range);
} else if ((low != -1) && (value < low)) {
rc = pcmk_rc_before_range;
} else if ((high != -1) && (value > high)) {
rc = pcmk_rc_after_range;
}
bail:
crm_trace("Checked " PCMK_XE_DATE_SPEC " %s %s='%s' for %" PRIu32 ": %s",
- pcmk__xe_id(date_spec), attr, pcmk__s(range, ""), value,
- pcmk_rc_str(rc));
+ 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, pcmk_rc_ok,
- * pcmk_rc_before_range, pcmk_rc_after_range, or pcmk_rc_op_unsatisfied)
+ * \return Standard Pacemaker return code (specifically, EINVAL for NULL
+ * arguments, pcmk_rc_ok if time matches specification, or
+ * pcmk_rc_before_range, pcmk_rc_after_range, or pcmk_rc_op_unsatisfied
+ * as appropriate to how time relates to specification)
*/
int
pcmk__evaluate_date_spec(const xmlNode *date_spec, const crm_time_t *now)
{
+ const char *id = NULL;
+
// 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);
+ if ((date_spec == NULL) || (now == NULL)) {
+ return EINVAL;
+ }
+
+ // Get specification ID (for logging)
+ id = pcmk__xe_id(date_spec);
+ if (pcmk__str_empty(id)) { // Not possible with schema validation enabled
+ /* @COMPAT When we can break behavioral backward compatibility,
+ * fail the specification
+ */
+ pcmk__config_warn(PCMK_XE_DATE_SPEC " element has no " PCMK_XA_ID);
+ id = "without ID"; // for logging
+ }
// Year, month, day
crm_time_get_gregorian(now, &(ranges[0].value), &(ranges[1].value),
&(ranges[2].value));
// Hour, minute, second
crm_time_get_timeofday(now, &(ranges[3].value), &(ranges[4].value),
&(ranges[5].value));
// Year (redundant) and day of year
crm_time_get_ordinal(now, &(ranges[0].value), &(ranges[6].value));
// Week year, week of week year, day of week
crm_time_get_isoweek(now, &(ranges[7].value), &(ranges[8].value),
&(ranges[9].value));
// Moon phase (deprecated)
ranges[10].value = phase_of_the_moon(now);
if (crm_element_value(date_spec, PCMK__XA_MOON) != NULL) {
pcmk__config_warn("Support for '" PCMK__XA_MOON "' in "
PCMK_XE_DATE_SPEC " elements (such as %s) is "
"deprecated and will be removed in a future release "
- "of Pacemaker",
- pcmk__xe_id(date_spec));
+ "of Pacemaker", id);
}
for (int i = 0; i < PCMK__NELEM(ranges); ++i) {
- int rc = check_range(date_spec, ranges[i].attr, ranges[i].value);
+ int rc = check_range(date_spec, id, ranges[i].attr, ranges[i].value);
if (rc != pcmk_rc_ok) {
return rc;
}
}
// All specified ranges passed, or none were given (also considered a pass)
return pcmk_rc_ok;
}
diff --git a/lib/common/tests/rules/pcmk__evaluate_date_spec_test.c b/lib/common/tests/rules/pcmk__evaluate_date_spec_test.c
index ce22469593..f203b2479f 100644
--- a/lib/common/tests/rules/pcmk__evaluate_date_spec_test.c
+++ b/lib/common/tests/rules/pcmk__evaluate_date_spec_test.c
@@ -1,197 +1,196 @@
/*
* 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(pcmk__evaluate_date_spec(xml, tm), expected);
crm_time_free(tm);
free_xml(xml);
}
static void
-no_time_given(void **state) {
- assert_int_equal(pcmk__evaluate_date_spec(NULL, NULL),
- pcmk_rc_op_unsatisfied);
-}
-
-static void
-any_time_satisfies_empty_spec(void **state) {
+null_invalid(void **state) {
+ xmlNodePtr xml = string2xml("<" PCMK_XE_DATE_SPEC " "
+ PCMK_XA_ID "='spec' "
+ PCMK_XA_YEARS "='2019'/>");
crm_time_t *tm = crm_time_new(NULL);
- assert_int_equal(pcmk__evaluate_date_spec(NULL, tm), pcmk_rc_ok);
+ assert_int_equal(pcmk__evaluate_date_spec(NULL, NULL), EINVAL);
+ assert_int_equal(pcmk__evaluate_date_spec(xml, NULL), EINVAL);
+ assert_int_equal(pcmk__evaluate_date_spec(NULL, tm), EINVAL);
crm_time_free(tm);
+ free_xml(xml);
}
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(null_invalid),
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))
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Apr 21, 9:25 AM (1 d, 4 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1657360
Default Alt Text
(15 KB)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment