diff --git a/include/crm/pengine/rules_internal.h b/include/crm/pengine/rules_internal.h index ceabbfb9ca..f2f0a4727e 100644 --- a/include/crm/pengine/rules_internal.h +++ b/include/crm/pengine/rules_internal.h @@ -1,40 +1,43 @@ /* * Copyright 2015-2019 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 RULES_INTERNAL_H #define RULES_INTERNAL_H #include #include #include #include #include typedef enum { pe_date_before_range, pe_date_within_range, pe_date_after_range, pe_date_result_undetermined, pe_date_op_satisfied, pe_date_op_unsatisfied } pe_eval_date_result_t; GListPtr pe_unpack_alerts(xmlNode *alerts); void pe_free_alert_list(GListPtr alert_list); crm_time_t *pe_parse_xml_duration(crm_time_t * start, xmlNode * duration_spec); -pe_eval_date_result_t pe_eval_date_expression(xmlNode * time_expr, crm_time_t * now); -gboolean pe_test_date_expression(xmlNode * time_expr, crm_time_t * now); +pe_eval_date_result_t pe_eval_date_expression(xmlNode *time_expr, + crm_time_t *now, + crm_time_t *next_change); +gboolean pe_test_date_expression(xmlNode *time_expr, crm_time_t *now, + crm_time_t *next_change); gboolean pe_cron_range_satisfied(crm_time_t * now, xmlNode * cron_spec); gboolean pe_test_attr_expression(xmlNode *expr, GHashTable *hash, crm_time_t *now, pe_match_data_t *match_data); gboolean pe_test_role_expression(xmlNode * expr, enum rsc_role_e role, crm_time_t * now); #endif diff --git a/lib/pengine/rules.c b/lib/pengine/rules.c index cf00439b85..c8c0445454 100644 --- a/lib/pengine/rules.c +++ b/lib/pengine/rules.c @@ -1,1016 +1,1053 @@ /* * Copyright 2004-2019 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 #include #include #include #include #include #include #include #include #include #include CRM_TRACE_INIT_DATA(pe_rules); gboolean test_ruleset(xmlNode * ruleset, GHashTable * node_hash, crm_time_t * now) { gboolean ruleset_default = TRUE; xmlNode *rule = NULL; for (rule = __xml_first_child_element(ruleset); rule != NULL; rule = __xml_next_element(rule)) { if (crm_str_eq((const char *)rule->name, XML_TAG_RULE, TRUE)) { ruleset_default = FALSE; if (pe_test_rule_full(rule, node_hash, RSC_ROLE_UNKNOWN, now, NULL)) { return TRUE; } } } return ruleset_default; } gboolean test_rule(xmlNode * rule, GHashTable * node_hash, enum rsc_role_e role, crm_time_t * now) { return pe_test_rule_full(rule, node_hash, role, now, 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_full(rule, node_hash, role, now, &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) { 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 (safe_str_eq(value, "or")) { do_and = FALSE; passed = FALSE; } crm_trace("Testing rule %s", ID(rule)); for (expr = __xml_first_child_element(rule); expr != NULL; expr = __xml_next_element(expr)) { test = pe_test_expression_full(expr, node_hash, role, now, match_data); 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; } gboolean test_expression(xmlNode * expr, GHashTable * node_hash, enum rsc_role_e role, crm_time_t * now) { return pe_test_expression_full(expr, node_hash, role, now, 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_full(expr, node_hash, role, now, &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) { gboolean accept = FALSE; const char *uname = NULL; switch (find_expression_type(expr)) { case nested_rule: accept = pe_test_rule_full(expr, node_hash, role, now, match_data); break; case attr_expr: case loc_expr: /* these expressions can never succeed if there is * no node to compare with */ if (node_hash != NULL) { accept = pe_test_attr_expression(expr, node_hash, now, match_data); } break; case time_expr: - accept = pe_test_date_expression(expr, now); + accept = pe_test_date_expression(expr, now, NULL); break; case role_expr: accept = pe_test_role_expression(expr, role, now); break; #ifdef ENABLE_VERSIONED_ATTRS case version_expr: if (node_hash && g_hash_table_lookup_extended(node_hash, CRM_ATTR_RA_VERSION, NULL, NULL)) { accept = pe_test_attr_expression(expr, node_hash, now, NULL); } else { // we are going to test it when we have ra-version accept = TRUE; } break; #endif default: CRM_CHECK(FALSE /* bad type */ , return FALSE); accept = FALSE; } if (node_hash) { uname = g_hash_table_lookup(node_hash, CRM_ATTR_UNAME); } crm_trace("Expression %s %s on %s", ID(expr), accept ? "passed" : "failed", uname ? uname : "all nodes"); return accept; } enum expression_type find_expression_type(xmlNode * expr) { const char *tag = NULL; const char *attr = NULL; attr = crm_element_value(expr, XML_EXPR_ATTR_ATTRIBUTE); tag = crm_element_name(expr); if (safe_str_eq(tag, "date_expression")) { return time_expr; } else if (safe_str_eq(tag, XML_TAG_RULE)) { return nested_rule; } else if (safe_str_neq(tag, "expression")) { return not_expr; } else if (safe_str_eq(attr, CRM_ATTR_UNAME) || safe_str_eq(attr, CRM_ATTR_KIND) || safe_str_eq(attr, CRM_ATTR_ID)) { return loc_expr; } else if (safe_str_eq(attr, CRM_ATTR_ROLE)) { return role_expr; #ifdef ENABLE_VERSIONED_ATTRS } else if (safe_str_eq(attr, CRM_ATTR_RA_VERSION)) { return version_expr; #endif } return attr_expr; } gboolean pe_test_role_expression(xmlNode * expr, enum rsc_role_e role, crm_time_t * now) { gboolean accept = FALSE; const char *op = NULL; const char *value = NULL; if (role == RSC_ROLE_UNKNOWN) { return accept; } value = crm_element_value(expr, XML_EXPR_ATTR_VALUE); op = crm_element_value(expr, XML_EXPR_ATTR_OPERATION); if (safe_str_eq(op, "defined")) { if (role > RSC_ROLE_STARTED) { accept = TRUE; } } else if (safe_str_eq(op, "not_defined")) { if (role < RSC_ROLE_SLAVE && role > RSC_ROLE_UNKNOWN) { accept = TRUE; } } else if (safe_str_eq(op, "eq")) { if (text2role(value) == role) { accept = TRUE; } } else if (safe_str_eq(op, "ne")) { // Test "ne" only with promotable clone roles if (role < RSC_ROLE_SLAVE && role > RSC_ROLE_UNKNOWN) { accept = FALSE; } else if (text2role(value) != role) { accept = TRUE; } } return accept; } gboolean pe_test_attr_expression(xmlNode *expr, GHashTable *hash, crm_time_t *now, pe_match_data_t *match_data) { gboolean accept = FALSE; gboolean attr_allocated = FALSE; int cmp = 0; const char *h_val = NULL; GHashTable *table = 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 || op == NULL) { pe_err("Invalid attribute or operation in expression" " (\'%s\' \'%s\' \'%s\')", crm_str(attr), crm_str(op), crm_str(value)); return FALSE; } if (match_data) { if (match_data->re) { char *resolved_attr = pe_expand_re_matches(attr, match_data->re); if (resolved_attr) { attr = (const char *) resolved_attr; attr_allocated = TRUE; } } if (safe_str_eq(value_source, "param")) { table = match_data->params; } else if (safe_str_eq(value_source, "meta")) { table = match_data->meta; } } if (table) { const char *param_name = value; const char *param_value = NULL; if (param_name && param_name[0]) { if ((param_value = (const char *)g_hash_table_lookup(table, param_name))) { value = param_value; } } } if (hash != NULL) { h_val = (const char *)g_hash_table_lookup(hash, attr); } if (attr_allocated) { free((char *)attr); attr = NULL; } if (value != NULL && h_val != NULL) { if (type == NULL) { if (safe_str_eq(op, "lt") || safe_str_eq(op, "lte") || safe_str_eq(op, "gt") || safe_str_eq(op, "gte")) { type = "number"; } else { type = "string"; } crm_trace("Defaulting to %s based comparison for '%s' op", type, op); } if (safe_str_eq(type, "string")) { cmp = strcasecmp(h_val, value); } else if (safe_str_eq(type, "number")) { int h_val_f = crm_parse_int(h_val, NULL); int value_f = crm_parse_int(value, NULL); if (h_val_f < value_f) { cmp = -1; } else if (h_val_f > value_f) { cmp = 1; } else { cmp = 0; } } else if (safe_str_eq(type, "version")) { cmp = compare_version(h_val, value); } } else if (value == NULL && h_val == NULL) { cmp = 0; } else if (value == NULL) { cmp = 1; } else { cmp = -1; } if (safe_str_eq(op, "defined")) { if (h_val != NULL) { accept = TRUE; } } else if (safe_str_eq(op, "not_defined")) { if (h_val == NULL) { accept = TRUE; } } else if (safe_str_eq(op, "eq")) { if ((h_val == value) || cmp == 0) { accept = TRUE; } } else if (safe_str_eq(op, "ne")) { if ((h_val == NULL && value != NULL) || (h_val != NULL && value == NULL) || cmp != 0) { accept = TRUE; } } else if (value == NULL || h_val == NULL) { // The comparison is meaningless from this point on accept = FALSE; } else if (safe_str_eq(op, "lt")) { if (cmp < 0) { accept = TRUE; } } else if (safe_str_eq(op, "lte")) { if (cmp <= 0) { accept = TRUE; } } else if (safe_str_eq(op, "gt")) { if (cmp > 0) { accept = TRUE; } } else if (safe_str_eq(op, "gte")) { if (cmp >= 0) { accept = TRUE; } } return accept; } /* 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 * * 0-7, with 0: new, 4: full */ static int phase_of_the_moon(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 gboolean decodeNVpair(const char *srcstring, char separator, char **name, char **value) { const char *seploc = NULL; CRM_ASSERT(name != NULL && value != NULL); *name = NULL; *value = NULL; crm_trace("Attempting to decode: [%s]", srcstring); if (srcstring != NULL) { seploc = strchr(srcstring, separator); if (seploc) { *name = strndup(srcstring, seploc - srcstring); if (*(seploc + 1)) { *value = strdup(seploc + 1); } return TRUE; } } return FALSE; } #define cron_check(xml_field, time_field) \ value = crm_element_value(cron_spec, xml_field); \ if(value != NULL) { \ gboolean pass = TRUE; \ decodeNVpair(value, '-', &value_low, &value_high); \ if(value_low == NULL) { \ value_low = strdup(value); \ } \ value_low_i = crm_parse_int(value_low, "0"); \ value_high_i = crm_parse_int(value_high, "-1"); \ if(value_high_i < 0) { \ if(value_low_i != time_field) { \ pass = FALSE; \ } \ } else if(value_low_i > time_field) { \ pass = FALSE; \ } else if(value_high_i < time_field) { \ pass = FALSE; \ } \ free(value_low); \ free(value_high); \ if(pass == FALSE) { \ crm_debug("Condition '%s' in %s: failed", value, xml_field); \ return pass; \ } \ crm_debug("Condition '%s' in %s: passed", value, xml_field); \ } gboolean pe_cron_range_satisfied(crm_time_t * now, xmlNode * cron_spec) { const char *value = NULL; char *value_low = NULL; char *value_high = NULL; int value_low_i = 0; int value_high_i = 0; uint32_t h, m, s, y, d, w; CRM_CHECK(now != NULL, return FALSE); crm_time_get_timeofday(now, &h, &m, &s); cron_check("seconds", s); cron_check("minutes", m); cron_check("hours", h); crm_time_get_gregorian(now, &y, &m, &d); cron_check("monthdays", d); cron_check("months", m); cron_check("years", y); crm_time_get_ordinal(now, &y, &d); cron_check("yeardays", d); crm_time_get_isoweek(now, &y, &w, &d); cron_check("weekyears", y); cron_check("weeks", w); cron_check("weekdays", d); cron_check("moon", phase_of_the_moon(now)); return TRUE; } #define update_field(xml_field, time_fn) \ value = crm_element_value(duration_spec, xml_field); \ if(value != NULL) { \ int value_i = crm_parse_int(value, "0"); \ time_fn(end, value_i); \ } crm_time_t * pe_parse_xml_duration(crm_time_t * start, xmlNode * duration_spec) { crm_time_t *end = NULL; const char *value = NULL; end = crm_time_new(NULL); crm_time_set(end, start); update_field("years", crm_time_add_years); update_field("months", crm_time_add_months); update_field("weeks", crm_time_add_weeks); update_field("days", crm_time_add_days); update_field("hours", crm_time_add_hours); update_field("minutes", crm_time_add_minutes); update_field("seconds", crm_time_add_seconds); return end; } +/*! + * \internal + * \brief Test a date expression (pass/fail) for a specific time + * + * \param[in] time_expr date_expression XML + * \param[in] now Time for which to evaluate expression + * \param[out] next_change If not NULL, set to when evaluation will change + * + * \return TRUE if date expression is in effect at given time, FALSE otherwise + */ gboolean -pe_test_date_expression(xmlNode * time_expr, crm_time_t * now) +pe_test_date_expression(xmlNode *time_expr, crm_time_t *now, + crm_time_t *next_change) { - switch (pe_eval_date_expression(time_expr, now)) { + switch (pe_eval_date_expression(time_expr, now, next_change)) { case pe_date_within_range: case pe_date_op_satisfied: return TRUE; default: return FALSE; } } +// 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); + } + } +} + /*! * \internal * \brief Evaluate a date expression for a specific time * * \param[in] time_expr date_expression XML * \param[in] now Time for which to evaluate expression + * \param[out] next_change If not NULL, set to when evaluation will change * * \return Evaluation result */ pe_eval_date_result_t -pe_eval_date_expression(xmlNode * time_expr, crm_time_t * now) +pe_eval_date_expression(xmlNode *time_expr, crm_time_t *now, + 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(time_expr, "operation"); xmlNode *duration_spec = NULL; xmlNode *date_spec = NULL; // "undetermined" will also be returned for parsing errors pe_eval_date_result_t rc = pe_date_result_undetermined; crm_trace("Testing expression: %s", ID(time_expr)); duration_spec = first_named_child(time_expr, "duration"); date_spec = first_named_child(time_expr, "date_spec"); value = crm_element_value(time_expr, "start"); if (value != NULL) { start = crm_time_new(value); } value = crm_element_value(time_expr, "end"); if (value != NULL) { end = crm_time_new(value); } if (start != NULL && end == NULL && duration_spec != NULL) { end = pe_parse_xml_duration(start, duration_spec); } if ((op == NULL) || safe_str_eq(op, "in_range")) { if ((start == NULL) && (end == NULL)) { // in_range requires at least one of start or end } else if ((start != NULL) && (crm_time_compare(now, start) < 0)) { rc = pe_date_before_range; + crm_time_set_if_earlier(next_change, start); } else if ((end != NULL) && (crm_time_compare(now, end) > 0)) { rc = pe_date_after_range; } else { rc = pe_date_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 (safe_str_eq(op, "date_spec")) { rc = pe_cron_range_satisfied(now, date_spec) ? pe_date_op_satisfied : pe_date_op_unsatisfied; + // @TODO set next_change appropriately } else if (safe_str_eq(op, "gt")) { if (start == NULL) { // gt requires start } else if (crm_time_compare(now, start) > 0) { rc = pe_date_within_range; } else { rc = pe_date_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 (safe_str_eq(op, "lt")) { if (end == NULL) { // lt requires end } else if (crm_time_compare(now, end) < 0) { rc = pe_date_within_range; + crm_time_set_if_earlier(next_change, end); } else { rc = pe_date_after_range; } } crm_time_free(start); crm_time_free(end); return rc; } typedef struct sorted_set_s { int score; const char *name; const char *special_name; xmlNode *attr_set; } 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 (safe_str_eq(pair_a->name, pair_a->special_name)) { return -1; } else if (safe_str_eq(pair_b->name, pair_a->special_name)) { return 1; } if (pair_a->score < pair_b->score) { return 1; } else if (pair_a->score > pair_b->score) { return -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; name = crm_element_name(list->children); if (safe_str_eq(XML_TAG_ATTRS, name)) { list = list->children; } for (an_attr = __xml_first_child_element(list); an_attr != NULL; an_attr = __xml_next_element(an_attr)) { if (crm_str_eq((const char *)an_attr->name, XML_CIB_TAG_NVPAIR, TRUE)) { xmlNode *ref_nvpair = expand_idref(an_attr, top); name = crm_element_value(an_attr, XML_NVPAIR_ATTR_NAME); if (name == NULL) { name = crm_element_value(ref_nvpair, XML_NVPAIR_ATTR_NAME); } crm_trace("Setting attribute: %s", name); value = crm_element_value(an_attr, XML_NVPAIR_ATTR_VALUE); if (value == NULL) { value = crm_element_value(ref_nvpair, XML_NVPAIR_ATTR_VALUE); } if (name == NULL || value == NULL) { continue; } old_value = g_hash_table_lookup(hash, name); if (safe_str_eq(value, "#default")) { if (old_value) { crm_trace("Removing value for %s (%s)", name, value); g_hash_table_remove(hash, name); } continue; } else if (old_value == NULL) { g_hash_table_insert(hash, strdup(name), strdup(value)); } else if (overwrite) { crm_debug("Overwriting value of %s: %s -> %s", name, old_value, value); g_hash_table_replace(hash, strdup(name), strdup(value)); } } } } #ifdef ENABLE_VERSIONED_ATTRS static xmlNode* get_versioned_rule(xmlNode * attr_set) { xmlNode * rule = NULL; xmlNode * expr = NULL; for (rule = __xml_first_child_element(attr_set); rule != NULL; rule = __xml_next_element(rule)) { if (crm_str_eq((const char *)rule->name, XML_TAG_RULE, TRUE)) { for (expr = __xml_first_child_element(rule); expr != NULL; expr = __xml_next_element(expr)) { if (find_expression_type(expr) == version_expr) { return rule; } } } } return NULL; } static void add_versioned_attributes(xmlNode * attr_set, xmlNode * versioned_attrs) { xmlNode *attr_set_copy = NULL; xmlNode *rule = NULL; xmlNode *expr = NULL; if (!attr_set || !versioned_attrs) { return; } attr_set_copy = copy_xml(attr_set); rule = get_versioned_rule(attr_set_copy); if (!rule) { free_xml(attr_set_copy); return; } expr = __xml_first_child_element(rule); while (expr != NULL) { if (find_expression_type(expr) != version_expr) { xmlNode *node = expr; expr = __xml_next_element(expr); free_xml(node); } else { expr = __xml_next_element(expr); } } add_node_nocopy(versioned_attrs, NULL, attr_set_copy); } #endif typedef struct unpack_data_s { gboolean overwrite; GHashTable *node_hash; void *hash; crm_time_t *now; 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 (test_ruleset(pair->attr_set, unpack_data->node_hash, unpack_data->now) == FALSE) { return; } #ifdef ENABLE_VERSIONED_ATTRS if (get_versioned_rule(pair->attr_set) && !(unpack_data->node_hash && g_hash_table_lookup_extended(unpack_data->node_hash, CRM_ATTR_RA_VERSION, NULL, NULL))) { // we haven't actually tested versioned expressions yet return; } #endif crm_trace("Adding attributes from %s", pair->name); populate_hash(pair->attr_set, unpack_data->hash, unpack_data->overwrite, unpack_data->top); } #ifdef ENABLE_VERSIONED_ATTRS static void unpack_versioned_attr_set(gpointer data, gpointer user_data) { sorted_set_t *pair = data; unpack_data_t *unpack_data = user_data; if (test_ruleset(pair->attr_set, unpack_data->node_hash, unpack_data->now) == FALSE) { return; } add_versioned_attributes(pair->attr_set, unpack_data->hash); } #endif static GListPtr make_pairs_and_populate_data(xmlNode * top, xmlNode * xml_obj, const char *set_name, GHashTable * node_hash, void * hash, const char *always_first, gboolean overwrite, crm_time_t * now, unpack_data_t * data) { GListPtr unsorted = NULL; const char *score = NULL; sorted_set_t *pair = NULL; xmlNode *attr_set = NULL; if (xml_obj == NULL) { crm_trace("No instance attributes"); return NULL; } crm_trace("Checking for attributes"); for (attr_set = __xml_first_child_element(xml_obj); attr_set != NULL; attr_set = __xml_next_element(attr_set)) { /* Uncertain if set_name == NULL check is strictly necessary here */ if (set_name == NULL || crm_str_eq((const char *)attr_set->name, set_name, TRUE)) { pair = NULL; attr_set = expand_idref(attr_set, top); if (attr_set == NULL) { continue; } pair = calloc(1, sizeof(sorted_set_t)); pair->name = ID(attr_set); pair->special_name = always_first; pair->attr_set = attr_set; score = crm_element_value(attr_set, XML_RULE_ATTR_SCORE); pair->score = char2score(score); unsorted = g_list_prepend(unsorted, pair); } } if (pair != NULL) { data->hash = hash; data->node_hash = node_hash; data->now = now; data->overwrite = overwrite; data->top = top; } if (unsorted) { return g_list_sort(unsorted, sort_pairs); } return NULL; } 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) { unpack_data_t data; GListPtr pairs = make_pairs_and_populate_data(top, xml_obj, set_name, node_hash, hash, always_first, overwrite, now, &data); if (pairs) { g_list_foreach(pairs, unpack_attr_set, &data); g_list_free_full(pairs, free); } } #ifdef ENABLE_VERSIONED_ATTRS void pe_unpack_versioned_attributes(xmlNode * top, xmlNode * xml_obj, const char *set_name, GHashTable * node_hash, xmlNode * hash, crm_time_t * now) { unpack_data_t data; GListPtr pairs = make_pairs_and_populate_data(top, xml_obj, set_name, node_hash, hash, NULL, FALSE, now, &data); if (pairs) { g_list_foreach(pairs, unpack_versioned_attr_set, &data); g_list_free_full(pairs, free); } } #endif char * pe_expand_re_matches(const char *string, 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 (!string || string[0] == '\0' || !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; } #ifdef ENABLE_VERSIONED_ATTRS GHashTable* pe_unpack_versioned_parameters(xmlNode *versioned_params, const char *ra_version) { GHashTable *hash = crm_str_table_new(); if (versioned_params && ra_version) { GHashTable *node_hash = crm_str_table_new(); xmlNode *attr_set = __xml_first_child_element(versioned_params); if (attr_set) { g_hash_table_insert(node_hash, strdup(CRM_ATTR_RA_VERSION), strdup(ra_version)); unpack_instance_attributes(NULL, versioned_params, crm_element_name(attr_set), node_hash, hash, NULL, FALSE, NULL); } g_hash_table_destroy(node_hash); } return hash; } #endif diff --git a/tools/crm_rule.c b/tools/crm_rule.c index 340a691e04..0ff20cdade 100644 --- a/tools/crm_rule.c +++ b/tools/crm_rule.c @@ -1,280 +1,280 @@ /* * Copyright 2019 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 #include #include #include #include #include #include #include enum crm_rule_mode { crm_rule_mode_none, crm_rule_mode_check } rule_mode = crm_rule_mode_none; static int crm_rule_check(pe_working_set_t *data_set, const char *rule_id, crm_time_t *effective_date); static struct crm_option long_options[] = { /* Top-level Options */ {"help", no_argument, NULL, '?', "\tThis text"}, {"version", no_argument, NULL, '$', "\tVersion information" }, {"verbose", no_argument, NULL, 'V', "\tIncrease debug output"}, {"-spacer-", required_argument, NULL, '-', "\nModes (mutually exclusive):" }, {"check", no_argument, NULL, 'c', "\tCheck whether a rule is in effect" }, {"-spacer-", required_argument, NULL, '-', "\nAdditional options:" }, {"date", required_argument, NULL, 'd', "Whether the rule is in effect on a given date" }, {"rule", required_argument, NULL, 'r', "The ID of the rule to check" }, {"-spacer-", no_argument, NULL, '-', "\nData:"}, {"xml-text", required_argument, NULL, 'X', "Use argument for XML (or stdin if '-')"}, {"-spacer-", required_argument, NULL, '-', "\n\nThis tool is currently experimental.", pcmk_option_paragraph}, {"-spacer-", required_argument, NULL, '-', "The interface, behavior, and output may " "change with any version of pacemaker.", pcmk_option_paragraph}, {0, 0, 0, 0} }; static int crm_rule_check(pe_working_set_t *data_set, const char *rule_id, crm_time_t *effective_date) { xmlNode *cib_constraints = NULL; xmlNode *match = NULL; xmlXPathObjectPtr xpathObj = NULL; pe_eval_date_result_t result; char *xpath = NULL; int rc = pcmk_ok; int max = 0; /* Rules are under the constraints node in the XML, so first find that. */ cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, data_set->input); /* Get all rules matching the given ID, which also have only a single date_expression * child whose operation is not 'date_spec'. This is fairly limited, but it's hard * to check expressions more complicated than that. */ xpath = crm_strdup_printf("//rule[@id='%s']/date_expression[@operation!='date_spec']", rule_id); xpathObj = xpath_search(cib_constraints, xpath); max = numXpathResults(xpathObj); if (max == 0) { CMD_ERR("No rule found with ID=%s containing a date_expression", rule_id); rc = -ENXIO; goto bail; } else if (max > 1) { CMD_ERR("More than one date_expression in %s is not supported", rule_id); rc = -EOPNOTSUPP; goto bail; } match = getXpathResult(xpathObj, 0); /* We should have ensured both of these pass with the xpath query above, but * double checking can't hurt. */ CRM_ASSERT(match != NULL); CRM_ASSERT(find_expression_type(match) == time_expr); - result = pe_eval_date_expression(match, effective_date); + result = pe_eval_date_expression(match, effective_date, NULL); if (result == pe_date_within_range) { printf("Rule %s is still in effect\n", rule_id); rc = 0; } else if (result == pe_date_after_range) { printf("Rule %s is expired\n", rule_id); rc = 1; } else if (result == pe_date_before_range) { printf("Rule %s has not yet taken effect\n", rule_id); rc = 2; } else { printf("Could not determine whether rule %s is expired\n", rule_id); rc = 3; } bail: free(xpath); freeXpathObject(xpathObj); return rc; } int main(int argc, char **argv) { cib_t *cib_conn = NULL; pe_working_set_t *data_set = NULL; int flag = 0; int option_index = 0; char *rule_id = NULL; crm_time_t *rule_date = NULL; xmlNode *input = NULL; char *input_xml = NULL; int rc = pcmk_ok; crm_exit_t exit_code = CRM_EX_OK; crm_log_cli_init("crm_rule"); crm_set_options(NULL, "[options]", long_options, "Tool for querying the state of rules"); while (flag >= 0) { flag = crm_get_option(argc, argv, &option_index); switch (flag) { case -1: break; case 'V': crm_bump_log_level(argc, argv); break; case '$': case '?': crm_help(flag, CRM_EX_OK); break; case 'c': rule_mode = crm_rule_mode_check; break; case 'd': rule_date = crm_time_new(optarg); if (rule_date == NULL) { exit_code = CRM_EX_DATAERR; goto bail; } break; case 'X': input_xml = optarg; break; case 'r': rule_id = strdup(optarg); break; default: crm_help(flag, CRM_EX_OK); break; } } /* Check command line arguments before opening a connection to * the CIB manager or doing anything else important. */ if (rule_mode == crm_rule_mode_check) { if (rule_id == NULL) { CMD_ERR("--check requires use of --rule=\n"); crm_help(flag, CRM_EX_USAGE); } } /* Set up some defaults. */ if (rule_date == NULL) { rule_date = crm_time_new(NULL); } /* Where does the XML come from? If one of various command line options were * given, use those. Otherwise, connect to the CIB and use that. */ if (safe_str_eq(input_xml, "-")) { input = stdin2xml(); if (input == NULL) { fprintf(stderr, "Couldn't parse input from STDIN\n"); exit_code = CRM_EX_DATAERR; goto bail; } } else if (input_xml != NULL) { input = string2xml(input_xml); if (input == NULL) { fprintf(stderr, "Couldn't parse input string: %s\n", input_xml); exit_code = CRM_EX_DATAERR; goto bail; } } else { /* Establish a connection to the CIB manager */ cib_conn = cib_new(); rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command); if (rc != pcmk_ok) { CMD_ERR("Error connecting to the CIB manager: %s", pcmk_strerror(rc)); exit_code = crm_errno2exit(rc); goto bail; } } /* Populate working set from CIB query */ if (input == NULL) { rc = cib_conn->cmds->query(cib_conn, NULL, &input, cib_scope_local | cib_sync_call); if (rc != pcmk_ok) { exit_code = crm_errno2exit(rc); goto bail; } } /* Populate the working set instance */ data_set = pe_new_working_set(); if (data_set == NULL) { exit_code = crm_errno2exit(ENOMEM); goto bail; } data_set->input = input; data_set->now = rule_date; /* Unpack everything. */ cluster_status(data_set); /* Now do whichever operation mode was asked for. There's only one at the * moment so this looks a little silly, but I expect there will be more * modes in the future. */ switch(rule_mode) { case crm_rule_mode_check: rc = crm_rule_check(data_set, rule_id, rule_date); if (rc < 0) { CMD_ERR("Error checking rule: %s", pcmk_strerror(rc)); exit_code = crm_errno2exit(rc); } else if (rc == 1) { exit_code = CRM_EX_EXPIRED; } else if (rc == 2) { exit_code = CRM_EX_NOT_YET_IN_EFFECT; } else if (rc == 3) { exit_code = CRM_EX_INDETERMINATE; } else { exit_code = rc; } break; default: break; } bail: if (cib_conn != NULL) { cib_conn->cmds->signoff(cib_conn); cib_delete(cib_conn); } pe_free_working_set(data_set); crm_exit(exit_code); }