diff options
Diffstat (limited to 'lib/pengine/rules.c')
-rw-r--r-- | lib/pengine/rules.c | 1232 |
1 files changed, 192 insertions, 1040 deletions
diff --git a/lib/pengine/rules.c b/lib/pengine/rules.c index 50f9f64..540bc0d 100644 --- a/lib/pengine/rules.c +++ b/lib/pengine/rules.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2023 the Pacemaker project contributors + * Copyright 2004-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -8,324 +8,70 @@ */ #include <crm_internal.h> -#include <crm/crm.h> -#include <crm/msg_xml.h> -#include <crm/common/xml.h> -#include <crm/common/xml_internal.h> #include <glib.h> +#include <crm/crm.h> +#include <crm/common/xml.h> #include <crm/pengine/rules.h> -#include <crm/pengine/rules_internal.h> + +#include <crm/common/iso8601_internal.h> +#include <crm/common/nvpair_internal.h> +#include <crm/common/rules_internal.h> +#include <crm/common/xml_internal.h> #include <crm/pengine/internal.h> +#include <crm/pengine/rules_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, - .role = pcmk_role_unknown, - .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, - .role = role, - .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 Resource role to use when evaluating expression - * \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, - .role = role, - .now = now, - .match_data = match_data, - .rsc_data = NULL, - .op_data = NULL - }; - - return pe_eval_subexpr(expr, &rule_data, next_change); -} - -enum expression_type -find_expression_type(xmlNode * expr) -{ - const char *attr = NULL; - - attr = crm_element_value(expr, XML_EXPR_ATTR_ATTRIBUTE); - - if (pcmk__xe_is(expr, PCMK_XE_DATE_EXPRESSION)) { - return time_expr; - - } else if (pcmk__xe_is(expr, PCMK_XE_RSC_EXPRESSION)) { - return rsc_expr; - - } else if (pcmk__xe_is(expr, PCMK_XE_OP_EXPRESSION)) { - return op_expr; - - } else if (pcmk__xe_is(expr, XML_TAG_RULE)) { - return nested_rule; - - } else if (!pcmk__xe_is(expr, XML_TAG_EXPRESSION)) { - return not_expr; - - } else if (pcmk__str_any_of(attr, CRM_ATTR_UNAME, CRM_ATTR_KIND, CRM_ATTR_ID, NULL)) { - return loc_expr; - - } else if (pcmk__str_eq(attr, CRM_ATTR_ROLE, pcmk__str_none)) { - return role_expr; - } - - return attr_expr; -} - -/* As per the nethack rules: - * - * moon period = 29.53058 days ~= 30, year = 365.2422 days - * days moon phase advances on first day of year compared to preceding year - * = 365.2422 - 12*29.53058 ~= 11 - * years in Metonic cycle (time until same phases fall on the same days of - * the month) = 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 - * 177 ~= 8 reported phases * 22 - * + 11/22 for rounding + * \internal + * \brief Map pe_rule_eval_data_t to pcmk_rule_input_t * - * 0-7, with 0: new, 4: full + * \param[out] new New data struct + * \param[in] old Old data struct */ - -static int -phase_of_the_moon(const crm_time_t *now) -{ - 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); -} - -static int -check_one(const xmlNode *cron_spec, const char *xml_field, uint32_t time_field) -{ - int rc = pcmk_rc_undetermined; - const char *value = crm_element_value(cron_spec, xml_field); - long long low, high; - - if (value == NULL) { - /* Return pe_date_result_undetermined if the field is missing. */ - goto bail; - } - - if (pcmk__parse_ll_range(value, &low, &high) != pcmk_rc_ok) { - goto bail; - } else if (low == high) { - /* A single number was given, not a range. */ - if (time_field < low) { - rc = pcmk_rc_before_range; - } else if (time_field > high) { - rc = pcmk_rc_after_range; - } else { - rc = pcmk_rc_within_range; - } - } else if (low != -1 && high != -1) { - /* This is a range with both bounds. */ - if (time_field < low) { - rc = pcmk_rc_before_range; - } else if (time_field > high) { - rc = pcmk_rc_after_range; - } else { - rc = pcmk_rc_within_range; - } - } else if (low == -1) { - /* This is a range with no starting value. */ - rc = time_field <= high ? pcmk_rc_within_range : pcmk_rc_after_range; - } else if (high == -1) { - /* This is a range with no ending value. */ - rc = time_field >= low ? pcmk_rc_within_range : pcmk_rc_before_range; - } - -bail: - if (rc == pcmk_rc_within_range) { - crm_debug("Condition '%s' in %s: passed", value, xml_field); - } else { - crm_debug("Condition '%s' in %s: failed", value, xml_field); - } - - return rc; -} - -static gboolean -check_passes(int rc) { - /* _within_range is obvious. _undetermined is a pass because - * this is the return value if a field is not given. In this - * case, we just want to ignore it and check other fields to - * see if they place some restriction on what can pass. - */ - return rc == pcmk_rc_within_range || rc == pcmk_rc_undetermined; -} - -#define CHECK_ONE(spec, name, var) do { \ - int subpart_rc = check_one(spec, name, var); \ - if (check_passes(subpart_rc) == FALSE) { \ - return subpart_rc; \ - } \ -} while (0) - -int -pe_cron_range_satisfied(const crm_time_t *now, const xmlNode *cron_spec) -{ - uint32_t h, m, s, y, d, w; - - CRM_CHECK(now != NULL, return pcmk_rc_op_unsatisfied); - - crm_time_get_gregorian(now, &y, &m, &d); - CHECK_ONE(cron_spec, "years", y); - CHECK_ONE(cron_spec, "months", m); - CHECK_ONE(cron_spec, "monthdays", d); - - crm_time_get_timeofday(now, &h, &m, &s); - CHECK_ONE(cron_spec, "hours", h); - CHECK_ONE(cron_spec, "minutes", m); - CHECK_ONE(cron_spec, "seconds", s); - - crm_time_get_ordinal(now, &y, &d); - CHECK_ONE(cron_spec, "yeardays", d); - - crm_time_get_isoweek(now, &y, &w, &d); - CHECK_ONE(cron_spec, "weekyears", y); - CHECK_ONE(cron_spec, "weeks", w); - CHECK_ONE(cron_spec, "weekdays", d); - - CHECK_ONE(cron_spec, "moon", phase_of_the_moon(now)); - if (crm_element_value(cron_spec, "moon") != NULL) { - pcmk__config_warn("Support for 'moon' in date_spec elements " - "(such as %s) is deprecated and will be removed " - "in a future release of Pacemaker", ID(cron_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. - */ - return pcmk_rc_ok; -} - static void -update_field(crm_time_t *t, const xmlNode *xml, const char *attr, - void (*time_fn)(crm_time_t *, int)) +map_rule_input(pcmk_rule_input_t *new, const pe_rule_eval_data_t *old) { - 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); + if (old == NULL) { + return; } -} - -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, "years", crm_time_add_years); - update_field(end, duration_spec, "months", crm_time_add_months); - update_field(end, duration_spec, "weeks", crm_time_add_weeks); - update_field(end, duration_spec, "days", crm_time_add_days); - update_field(end, duration_spec, "hours", crm_time_add_hours); - update_field(end, duration_spec, "minutes", crm_time_add_minutes); - update_field(end, duration_spec, "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); + new->now = old->now; + new->node_attrs = old->node_hash; + if (old->rsc_data != NULL) { + new->rsc_standard = old->rsc_data->standard; + new->rsc_provider = old->rsc_data->provider; + new->rsc_agent = old->rsc_data->agent; + } + if (old->match_data != NULL) { + new->rsc_params = old->match_data->params; + new->rsc_meta = old->match_data->meta; + if (old->match_data->re != NULL) { + new->rsc_id = old->match_data->re->string; + new->rsc_id_submatches = old->match_data->re->pmatch; + new->rsc_id_nmatches = old->match_data->re->nregs; } } + if (old->op_data != NULL) { + new->op_name = old->op_data->op_name; + new->op_interval_ms = old->op_data->interval; + } } -// 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) +sort_pairs(gconstpointer a, gconstpointer b, gpointer user_data) { - const sorted_set_t *pair_a = a; - const sorted_set_t *pair_b = b; + const xmlNode *pair_a = a; + const xmlNode *pair_b = b; + pcmk__nvpair_unpack_t *unpack_data = user_data; + + const char *score = NULL; + int score_a = 0; + int score_b = 0; if (a == NULL && b == NULL) { return 0; @@ -335,27 +81,35 @@ sort_pairs(gconstpointer a, gconstpointer b) return -1; } - if (pcmk__str_eq(pair_a->name, pair_a->special_name, pcmk__str_casei)) { + if (pcmk__str_eq(pcmk__xe_id(pair_a), unpack_data->first_id, + pcmk__str_none)) { return -1; - } else if (pcmk__str_eq(pair_b->name, pair_a->special_name, pcmk__str_casei)) { + } else if (pcmk__str_eq(pcmk__xe_id(pair_b), unpack_data->first_id, + pcmk__str_none)) { return 1; } + score = crm_element_value(pair_a, PCMK_XA_SCORE); + score_a = char2score(score); + + score = crm_element_value(pair_b, PCMK_XA_SCORE); + score_b = char2score(score); + /* 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; + if (score_a < score_b) { + return unpack_data->overwrite? -1 : 1; + } else if (score_a > score_b) { + return unpack_data->overwrite? 1 : -1; } return 0; } static void -populate_hash(xmlNode * nvpair_list, GHashTable * hash, gboolean overwrite, xmlNode * top) +populate_hash(xmlNode *nvpair_list, GHashTable *hash, bool overwrite) { const char *name = NULL; const char *value = NULL; @@ -363,24 +117,24 @@ populate_hash(xmlNode * nvpair_list, GHashTable * hash, gboolean overwrite, xmlN xmlNode *list = nvpair_list; xmlNode *an_attr = NULL; - if (pcmk__xe_is(list->children, XML_TAG_ATTRS)) { + 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)) { + for (an_attr = pcmk__xe_first_child(list, NULL, NULL, NULL); + an_attr != NULL; an_attr = pcmk__xe_next(an_attr)) { - if (pcmk__str_eq((const char *)an_attr->name, XML_CIB_TAG_NVPAIR, pcmk__str_none)) { - xmlNode *ref_nvpair = expand_idref(an_attr, top); + if (pcmk__xe_is(an_attr, PCMK_XE_NVPAIR)) { + xmlNode *ref_nvpair = expand_idref(an_attr, NULL); - name = crm_element_value(an_attr, XML_NVPAIR_ATTR_NAME); - if (name == NULL) { - name = crm_element_value(ref_nvpair, XML_NVPAIR_ATTR_NAME); + 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, XML_NVPAIR_ATTR_VALUE); - if (value == NULL) { - value = crm_element_value(ref_nvpair, XML_NVPAIR_ATTR_VALUE); + 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) { @@ -390,6 +144,11 @@ populate_hash(xmlNode * nvpair_list, GHashTable * hash, gboolean overwrite, xmlN old_value = g_hash_table_lookup(hash, name); if (pcmk__str_eq(value, "#default", pcmk__str_casei)) { + // @COMPAT Deprecated since 2.1.8 + pcmk__config_warn("Support for setting meta-attributes (such " + "as %s) to the explicit value '#default' is " + "deprecated and will be removed in a future " + "release", name); if (old_value) { crm_trace("Letting %s default (removing explicit value \"%s\")", name, value); @@ -399,95 +158,69 @@ populate_hash(xmlNode * nvpair_list, GHashTable * hash, gboolean overwrite, xmlN } else if (old_value == NULL) { crm_trace("Setting %s=\"%s\"", name, value); - g_hash_table_insert(hash, strdup(name), strdup(value)); + pcmk__insert_dup(hash, name, value); } else if (overwrite) { crm_trace("Setting %s=\"%s\" (overwriting old value \"%s\")", name, value, old_value); - g_hash_table_replace(hash, strdup(name), strdup(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; + xmlNode *pair = data; + pcmk__nvpair_unpack_t *unpack_data = user_data; - if (!pe_eval_rules(pair->attr_set, unpack_data->rule_data, - unpack_data->next_change)) { + if (pcmk__evaluate_rules(pair, &(unpack_data->rule_input), + unpack_data->next_change) != pcmk_rc_ok) { 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); + crm_trace("Adding name/value pairs from %s %s overwrite", + pcmk__xe_id(pair), (unpack_data->overwrite? "with" : "without")); + populate_hash(pair, unpack_data->values, unpack_data->overwrite); } /*! * \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 + * \return List of XML blocks of name/value pairs */ static GList * -make_pairs(xmlNode *top, const xmlNode *xml_obj, const char *set_name, - const char *always_first, gboolean overwrite) +make_pairs(const xmlNode *xml_obj, const char *set_name) { 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)) { + for (xmlNode *attr_set = pcmk__xe_first_child(xml_obj, NULL, NULL, NULL); + attr_set != NULL; attr_set = pcmk__xe_next(attr_set)) { - if (pcmk__str_eq(set_name, (const char *) attr_set->name, - pcmk__str_null_matches)) { - const char *score = NULL; - sorted_set_t *pair = NULL; - xmlNode *expanded_attr_set = expand_idref(attr_set, top); + if ((set_name == NULL) || pcmk__xe_is(attr_set, set_name)) { + xmlNode *expanded_attr_set = expand_idref(attr_set, NULL); if (expanded_attr_set == NULL) { - // Schema (if not "none") prevents this - continue; + continue; // Not possible with schema validation enabled } - - pair = calloc(1, sizeof(sorted_set_t)); - pair->name = 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, XML_RULE_ATTR_SCORE); - pair->score = char2score(score); - - unsorted = g_list_prepend(unsorted, pair); + unsorted = g_list_prepend(unsorted, expanded_attr_set); } } - return g_list_sort(unsorted, sort_pairs); + return unsorted; } /*! * \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,out] top Ignored * \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 @@ -502,26 +235,28 @@ pe_eval_nvpairs(xmlNode *top, const xmlNode *xml_obj, const char *set_name, const char *always_first, gboolean overwrite, crm_time_t *next_change) { - GList *pairs = make_pairs(top, xml_obj, set_name, always_first, overwrite); + GList *pairs = make_pairs(xml_obj, set_name); if (pairs) { - unpack_data_t data = { - .hash = hash, + pcmk__nvpair_unpack_t data = { + .values = hash, + .first_id = always_first, .overwrite = overwrite, .next_change = next_change, - .top = top, - .rule_data = rule_data }; + map_rule_input(&(data.rule_input), rule_data); + + pairs = g_list_sort_with_data(pairs, sort_pairs, &data); g_list_foreach(pairs, unpack_attr_set, &data); - g_list_free_full(pairs, free); + g_list_free(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,out] top Ignored * \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 @@ -539,709 +274,67 @@ pe_unpack_nvpairs(xmlNode *top, const xmlNode *xml_obj, const char *set_name, { pe_rule_eval_data_t rule_data = { .node_hash = node_hash, - .role = pcmk_role_unknown, .now = now, .match_data = NULL, .rsc_data = NULL, .op_data = NULL }; - pe_eval_nvpairs(top, xml_obj, set_name, &rule_data, hash, + pe_eval_nvpairs(NULL, 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++; - } +// Deprecated functions kept only for backward API compatibility +// LCOV_EXCL_START - return result; -} +#include <crm/pengine/rules_compat.h> -/*! - * \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, XML_TAG_RULE); - rule != NULL; rule = crm_next_same_xml(rule)) { - - ruleset_default = FALSE; - if (pe_eval_expr(rule, rule_data, next_change)) { - /* Only the deprecated "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; - } - } + pcmk_rule_input_t rule_input = { NULL, }; - return ruleset_default; + map_rule_input(&rule_input, rule_data); + return pcmk__evaluate_rules(ruleset, &rule_input, + next_change) == pcmk_rc_ok; } -/*! - * \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); - value = crm_element_value(rule, XML_RULE_ATTR_BOOLEAN_OP); - if (pcmk__str_eq(value, "or", pcmk__str_casei)) { - do_and = FALSE; - passed = FALSE; - } - - crm_trace("Testing rule %s", 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", ID(rule), ID(expr)); - return TRUE; - - } else if (test == FALSE && do_and) { - crm_trace("Expression %s/%s failed", ID(rule), ID(expr)); - return FALSE; - } - } - - if (empty) { - crm_err("Invalid Rule %s: rules must contain at least one expression", ID(rule)); - } - - crm_trace("Rule %s %s", 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 (find_expression_type(expr)) { - case nested_rule: - accept = pe_eval_expr(expr, rule_data, next_change); - break; - case attr_expr: - case loc_expr: - /* 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 time_expr: - 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 role_expr: - accept = pe__eval_role_expr(expr, rule_data); - break; - - case rsc_expr: - accept = pe__eval_rsc_expr(expr, rule_data); - break; - - case op_expr: - 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", - ID(expr), accept ? "passed" : "failed", uname ? 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 "string", \c "integer", \c "number", - * \c "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, "lt", "lte", "gt", "gte", NULL)) { - if (pcmk__char_in_any_str('.', l_val, r_val, NULL)) { - type = "number"; - } else { - type = "integer"; - } - - } else { - type = "string"; - } - crm_trace("Defaulting to %s based comparison for '%s' op", type, op); - } - - if (pcmk__str_eq(type, "string", pcmk__str_casei)) { - cmp = strcasecmp(l_val, r_val); - - } else if (pcmk__str_eq(type, "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, "string", op); - } - - } else if (pcmk__str_eq(type, "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, "string", op); - } - - } else if (pcmk__str_eq(type, "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 "string", \c "integer", \c "number", - * \c "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, "defined", pcmk__str_casei)) { - return (l_val != NULL); - - } else if (pcmk__str_eq(op, "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, "eq", pcmk__str_casei)) { - return (cmp == 0); - - } else if (pcmk__str_eq(op, "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, "lt", pcmk__str_casei)) { - return (cmp < 0); - - } else if (pcmk__str_eq(op, "lte", pcmk__str_casei)) { - return (cmp <= 0); - - } else if (pcmk__str_eq(op, "gt", pcmk__str_casei)) { - return (cmp > 0); - - } else if (pcmk__str_eq(op, "gte", pcmk__str_casei)) { - return (cmp >= 0); - } - - return false; // Should never reach this point -} - -/*! - * \internal - * \brief Get correct value according to value-source - * - * \param[in] value value given in rule expression - * \param[in] value_source 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 *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, "param", pcmk__str_casei)) { - table = match_data->params; - - } else if (pcmk__str_eq(value_source, "meta", pcmk__str_casei)) { - table = match_data->meta; - - } else { // literal - 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 *op = NULL; - const char *type = NULL; - const char *attr = NULL; - const char *value = NULL; - const char *value_source = NULL; - - attr = crm_element_value(expr, XML_EXPR_ATTR_ATTRIBUTE); - op = crm_element_value(expr, XML_EXPR_ATTR_OPERATION); - value = crm_element_value(expr, XML_EXPR_ATTR_VALUE); - type = crm_element_value(expr, XML_EXPR_ATTR_TYPE); - value_source = crm_element_value(expr, XML_EXPR_ATTR_VALUE_SOURCE); - - if (attr == NULL) { - pe_err("Expression %s invalid: " XML_EXPR_ATTR_ATTRIBUTE - " not specified", pcmk__s(ID(expr), "without ID")); - return FALSE; - } else if (op == NULL) { - pe_err("Expression %s invalid: " XML_EXPR_ATTR_OPERATION - " not specified", pcmk__s(ID(expr), "without ID")); - } - - 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 value-source - value = expand_value_source(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, "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", ID(expr)); - - duration_spec = first_named_child(expr, "duration"); - date_spec = first_named_child(expr, "date_spec"); - - value = crm_element_value(expr, "start"); - if (value != NULL) { - start = crm_time_new(value); - } - value = crm_element_value(expr, "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, "date_spec", pcmk__str_casei)) { - rc = pe_cron_range_satisfied(rule_data->now, date_spec); - // @TODO set next_change appropriately - - } else if (pcmk__str_eq(op, "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, "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) +pe_evaluate_rules(xmlNode *ruleset, GHashTable *node_hash, crm_time_t *now, + crm_time_t *next_change) { - const char *name = crm_element_value(expr, XML_NVPAIR_ATTR_NAME); - const char *interval_s = crm_element_value(expr, XML_LRM_ATTR_INTERVAL); - guint interval; - - crm_trace("Testing op_defaults expression: %s", ID(expr)); - - if (rule_data->op_data == NULL) { - crm_trace("No operations data provided"); - return FALSE; - } - - interval = crm_parse_interval_spec(interval_s); - if (interval == 0 && errno != 0) { - crm_trace("Could not parse interval: %s", interval_s); - return FALSE; - } - - if (interval_s != NULL && interval != rule_data->op_data->interval) { - crm_trace("Interval doesn't match: %d != %d", interval, 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; - } + pcmk_rule_input_t rule_input = { + .node_attrs = node_hash, + .now = now, + }; - return TRUE; + return pcmk__evaluate_rules(ruleset, &rule_input, next_change); } -/*! - * \internal - * \brief Evaluate a node attribute expression based on #role - * - * \param[in] expr XML of rule expression - * \param[in] rule_data Only the role member is used - * - * \return TRUE if rule_data->role satisfies the expression, FALSE otherwise - */ gboolean -pe__eval_role_expr(const xmlNode *expr, const pe_rule_eval_data_t *rule_data) +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) { - gboolean accept = FALSE; - const char *op = NULL; - const char *value = NULL; - - if (rule_data->role == pcmk_role_unknown) { - return accept; - } - - value = crm_element_value(expr, XML_EXPR_ATTR_VALUE); - op = crm_element_value(expr, XML_EXPR_ATTR_OPERATION); - - if (pcmk__str_eq(op, "defined", pcmk__str_casei)) { - if (rule_data->role > pcmk_role_started) { - accept = TRUE; - } - - } else if (pcmk__str_eq(op, "not_defined", pcmk__str_casei)) { - if ((rule_data->role > pcmk_role_unknown) - && (rule_data->role < pcmk_role_unpromoted)) { - accept = TRUE; - } - - } else if (pcmk__str_eq(op, "eq", pcmk__str_casei)) { - if (text2role(value) == rule_data->role) { - accept = TRUE; - } - - } else if (pcmk__str_eq(op, "ne", pcmk__str_casei)) { - // Test "ne" only with promotable clone roles - if ((rule_data->role > pcmk_role_unknown) - && (rule_data->role < pcmk_role_unpromoted)) { - accept = FALSE; + pcmk_rule_input_t rule_input = { + .node_attrs = node_hash, + .now = now, + }; - } else if (text2role(value) != rule_data->role) { - accept = TRUE; + if (match_data != NULL) { + rule_input.rsc_params = match_data->params; + rule_input.rsc_meta = match_data->meta; + if (match_data->re != NULL) { + rule_input.rsc_id = match_data->re->string; + rule_input.rsc_id_submatches = match_data->re->pmatch; + rule_input.rsc_id_nmatches = match_data->re->nregs; } } - return accept; + return pcmk_evaluate_rule(rule, &rule_input, next_change) == pcmk_rc_ok; } gboolean -pe__eval_rsc_expr(const xmlNode *expr, const pe_rule_eval_data_t *rule_data) -{ - const char *class = crm_element_value(expr, XML_AGENT_ATTR_CLASS); - const char *provider = crm_element_value(expr, XML_AGENT_ATTR_PROVIDER); - const char *type = crm_element_value(expr, XML_EXPR_ATTR_TYPE); - - crm_trace("Testing rsc_defaults expression: %s", 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); @@ -1272,6 +365,29 @@ pe_test_rule_full(xmlNode *rule, GHashTable *node_hash, enum rsc_role_e role, } 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) +{ + pcmk_rule_input_t rule_input = { + .now = now, + .node_attrs = node_hash, + }; + + if (match_data != NULL) { + rule_input.rsc_params = match_data->params; + rule_input.rsc_meta = match_data->meta; + if (match_data->re != NULL) { + rule_input.rsc_id = match_data->re->string; + rule_input.rsc_id_submatches = match_data->re->pmatch; + rule_input.rsc_id_nmatches = match_data->re->nregs; + } + } + return pcmk__evaluate_condition(expr, &rule_input, + next_change) == pcmk_rc_ok; +} + +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); @@ -1296,6 +412,27 @@ pe_test_expression_full(xmlNode *expr, GHashTable *node_hash, return pe_test_expression(expr, node_hash, role, now, NULL, match_data); } +gboolean +pe_eval_expr(xmlNode *rule, const pe_rule_eval_data_t *rule_data, + crm_time_t *next_change) +{ + pcmk_rule_input_t rule_input = { NULL, }; + + map_rule_input(&rule_input, rule_data); + return pcmk_evaluate_rule(rule, &rule_input, next_change) == pcmk_rc_ok; +} + +gboolean +pe_eval_subexpr(xmlNode *expr, const pe_rule_eval_data_t *rule_data, + crm_time_t *next_change) +{ + pcmk_rule_input_t rule_input = { NULL, }; + + map_rule_input(&rule_input, rule_data); + return pcmk__evaluate_condition(expr, &rule_input, + next_change) == pcmk_rc_ok; +} + void unpack_instance_attributes(xmlNode *top, xmlNode *xml_obj, const char *set_name, GHashTable *node_hash, GHashTable *hash, @@ -1304,16 +441,31 @@ unpack_instance_attributes(xmlNode *top, xmlNode *xml_obj, const char *set_name, { pe_rule_eval_data_t rule_data = { .node_hash = node_hash, - .role = pcmk_role_unknown, .now = now, .match_data = NULL, .rsc_data = NULL, .op_data = NULL }; - pe_eval_nvpairs(top, xml_obj, set_name, &rule_data, hash, always_first, + pe_eval_nvpairs(NULL, xml_obj, set_name, &rule_data, hash, always_first, overwrite, NULL); } +enum expression_type +find_expression_type(xmlNode *expr) +{ + return pcmk__condition_type(expr); +} + +char * +pe_expand_re_matches(const char *string, const pe_re_match_data_t *match_data) +{ + if (match_data == NULL) { + return NULL; + } + return pcmk__replace_submatches(string, match_data->string, + match_data->pmatch, match_data->nregs); +} + // LCOV_EXCL_STOP // End deprecated API |