root/lib/pacemaker/pcmk_rule.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. eval_date_expression
  2. init_rule_check
  3. eval_rule
  4. pcmk__check_rules
  5. pcmk_check_rules

   1 /*
   2  * Copyright 2022-2023 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU Lesser General Public License
   7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 
  12 #include <crm/cib/internal.h>
  13 #include <crm/common/cib.h>
  14 #include <crm/common/iso8601.h>
  15 #include <crm/msg_xml.h>
  16 #include <crm/pengine/internal.h>
  17 #include <crm/pengine/rules_internal.h>
  18 #include <pacemaker-internal.h>
  19 
  20 /*!
  21  * \internal
  22  * \brief Evaluate a date expression for a specific time
  23  *
  24  * \param[in]  expr         date_expression XML
  25  * \param[in]  now          Time for which to evaluate expression
  26  *
  27  * \return Standard Pacemaker return code
  28  */
  29 static int
  30 eval_date_expression(const xmlNode *expr, crm_time_t *now)
     /* [previous][next][first][last][top][bottom][index][help] */
  31 {
  32     pe_rule_eval_data_t rule_data = {
  33         .node_hash = NULL,
  34         .role = pcmk_role_unknown,
  35         .now = now,
  36         .match_data = NULL,
  37         .rsc_data = NULL,
  38         .op_data = NULL
  39     };
  40 
  41     return pe__eval_date_expr(expr, &rule_data, NULL);
  42 }
  43 
  44 /*!
  45  * \internal
  46  * \brief Initialize scheduler data for checking rules
  47  *
  48  * Make our own copies of the CIB XML and date/time object, if they're not
  49  * \c NULL. This way we don't have to take ownership of the objects passed via
  50  * the API.
  51  *
  52  * \param[in,out] out        Output object
  53  * \param[in]     input      The CIB XML to check (if \c NULL, use current CIB)
  54  * \param[in]     date       Check whether the rule is in effect at this date
  55  *                           and time (if \c NULL, use current date and time)
  56  * \param[out]    scheduler  Where to store initialized scheduler data
  57  *
  58  * \return Standard Pacemaker return code
  59  */
  60 static int
  61 init_rule_check(pcmk__output_t *out, xmlNodePtr input, const crm_time_t *date,
     /* [previous][next][first][last][top][bottom][index][help] */
  62                 pcmk_scheduler_t **scheduler)
  63 {
  64     // Allows for cleaner syntax than dereferencing the scheduler argument
  65     pcmk_scheduler_t *new_scheduler = NULL;
  66 
  67     new_scheduler = pe_new_working_set();
  68     if (new_scheduler == NULL) {
  69         return ENOMEM;
  70     }
  71 
  72     pe__set_working_set_flags(new_scheduler,
  73                               pcmk_sched_no_counts|pcmk_sched_no_compat);
  74 
  75     // Populate the scheduler data
  76 
  77     // Make our own copy of the given input or fetch the CIB and use that
  78     if (input != NULL) {
  79         new_scheduler->input = copy_xml(input);
  80         if (new_scheduler->input == NULL) {
  81             out->err(out, "Failed to copy input XML");
  82             pe_free_working_set(new_scheduler);
  83             return ENOMEM;
  84         }
  85 
  86     } else {
  87         int rc = cib__signon_query(out, NULL, &(new_scheduler->input));
  88 
  89         if (rc != pcmk_rc_ok) {
  90             pe_free_working_set(new_scheduler);
  91             return rc;
  92         }
  93     }
  94 
  95     // Make our own copy of the given crm_time_t object; otherwise
  96     // cluster_status() populates with the current time
  97     if (date != NULL) {
  98         // pcmk_copy_time() guarantees non-NULL
  99         new_scheduler->now = pcmk_copy_time(date);
 100     }
 101 
 102     // Unpack everything
 103     cluster_status(new_scheduler);
 104     *scheduler = new_scheduler;
 105 
 106     return pcmk_rc_ok;
 107 }
 108 
 109 #define XPATH_NODE_RULE "//" XML_TAG_RULE "[@" XML_ATTR_ID "='%s']"
 110 
 111 /*!
 112  * \internal
 113  * \brief Check whether a given rule is in effect
 114  *
 115  * \param[in]     scheduler  Scheduler data
 116  * \param[in]     rule_id    The ID of the rule to check
 117  * \param[out]    error      Where to store a rule evaluation error message
 118  *
 119  * \return Standard Pacemaker return code
 120  */
 121 static int
 122 eval_rule(pcmk_scheduler_t *scheduler, const char *rule_id, const char **error)
     /* [previous][next][first][last][top][bottom][index][help] */
 123 {
 124     xmlNodePtr cib_constraints = NULL;
 125     xmlNodePtr match = NULL;
 126     xmlXPathObjectPtr xpath_obj = NULL;
 127     char *xpath = NULL;
 128     int rc = pcmk_rc_ok;
 129     int num_results = 0;
 130 
 131     *error = NULL;
 132 
 133     /* Rules are under the constraints node in the XML, so first find that. */
 134     cib_constraints = pcmk_find_cib_element(scheduler->input,
 135                                             XML_CIB_TAG_CONSTRAINTS);
 136 
 137     /* Get all rules matching the given ID that are also simple enough for us
 138      * to check. For the moment, these rules must only have a single
 139      * date_expression child and:
 140      * - Do not have a date_spec operation, or
 141      * - Have a date_spec operation that contains years= but does not contain
 142      *   moon=.
 143      *
 144      * We do this in steps to provide better error messages. First, check that
 145      * there's any rule with the given ID.
 146      */
 147     xpath = crm_strdup_printf(XPATH_NODE_RULE, rule_id);
 148     xpath_obj = xpath_search(cib_constraints, xpath);
 149     num_results = numXpathResults(xpath_obj);
 150 
 151     free(xpath);
 152     freeXpathObject(xpath_obj);
 153 
 154     if (num_results == 0) {
 155         *error = "Rule not found";
 156         return ENXIO;
 157     }
 158 
 159     if (num_results > 1) {
 160         // Should not be possible; schema prevents this
 161         *error = "Found more than one rule with matching ID";
 162         return pcmk_rc_duplicate_id;
 163     }
 164 
 165     /* Next, make sure it has exactly one date_expression. */
 166     xpath = crm_strdup_printf(XPATH_NODE_RULE "//date_expression", rule_id);
 167     xpath_obj = xpath_search(cib_constraints, xpath);
 168     num_results = numXpathResults(xpath_obj);
 169 
 170     free(xpath);
 171     freeXpathObject(xpath_obj);
 172 
 173     if (num_results != 1) {
 174         if (num_results == 0) {
 175             *error = "Rule does not have a date expression";
 176         } else {
 177             *error = "Rule has more than one date expression";
 178         }
 179         return EOPNOTSUPP;
 180     }
 181 
 182     /* Then, check that it's something we actually support. */
 183     xpath = crm_strdup_printf(XPATH_NODE_RULE "//date_expression["
 184                               "@" XML_EXPR_ATTR_OPERATION "!='date_spec']",
 185                               rule_id);
 186     xpath_obj = xpath_search(cib_constraints, xpath);
 187     num_results = numXpathResults(xpath_obj);
 188 
 189     free(xpath);
 190 
 191     if (num_results == 0) {
 192         freeXpathObject(xpath_obj);
 193 
 194         xpath = crm_strdup_printf(XPATH_NODE_RULE "//date_expression["
 195                                   "@" XML_EXPR_ATTR_OPERATION "='date_spec' "
 196                                   "and date_spec/@years "
 197                                   "and not(date_spec/@moon)]", rule_id);
 198         xpath_obj = xpath_search(cib_constraints, xpath);
 199         num_results = numXpathResults(xpath_obj);
 200 
 201         free(xpath);
 202 
 203         if (num_results == 0) {
 204             freeXpathObject(xpath_obj);
 205             *error = "Rule must either not use date_spec, or use date_spec "
 206                      "with years= but not moon=";
 207             return EOPNOTSUPP;
 208         }
 209     }
 210 
 211     match = getXpathResult(xpath_obj, 0);
 212 
 213     /* We should have ensured this with the xpath query above, but double-
 214      * checking can't hurt.
 215      */
 216     CRM_ASSERT(match != NULL);
 217     CRM_ASSERT(find_expression_type(match) == time_expr);
 218 
 219     rc = eval_date_expression(match, scheduler->now);
 220     if (rc == pcmk_rc_undetermined) {
 221         /* pe__eval_date_expr() should return this only if something is
 222          * malformed or missing
 223          */
 224         *error = "Error parsing rule";
 225     }
 226 
 227     freeXpathObject(xpath_obj);
 228     return rc;
 229 }
 230 
 231 /*!
 232  * \internal
 233  * \brief Check whether each rule in a list is in effect
 234  *
 235  * \param[in,out] out       Output object
 236  * \param[in]     input     The CIB XML to check (if \c NULL, use current CIB)
 237  * \param[in]     date      Check whether the rule is in effect at this date and
 238  *                          time (if \c NULL, use current date and time)
 239  * \param[in]     rule_ids  The IDs of the rules to check, as a <tt>NULL</tt>-
 240  *                          terminated list.
 241  *
 242  * \return Standard Pacemaker return code
 243  */
 244 int
 245 pcmk__check_rules(pcmk__output_t *out, xmlNodePtr input, const crm_time_t *date,
     /* [previous][next][first][last][top][bottom][index][help] */
 246                   const char **rule_ids)
 247 {
 248     pcmk_scheduler_t *scheduler = NULL;
 249     int rc = pcmk_rc_ok;
 250 
 251     CRM_ASSERT(out != NULL);
 252 
 253     if (rule_ids == NULL) {
 254         // Trivial case; every rule specified is in effect
 255         return pcmk_rc_ok;
 256     }
 257 
 258     rc = init_rule_check(out, input, date, &scheduler);
 259     if (rc != pcmk_rc_ok) {
 260         return rc;
 261     }
 262 
 263     for (const char **rule_id = rule_ids; *rule_id != NULL; rule_id++) {
 264         const char *error = NULL;
 265         int last_rc = eval_rule(scheduler, *rule_id, &error);
 266 
 267         out->message(out, "rule-check", *rule_id, last_rc, error);
 268 
 269         if (last_rc != pcmk_rc_ok) {
 270             rc = last_rc;
 271         }
 272     }
 273 
 274     pe_free_working_set(scheduler);
 275     return rc;
 276 }
 277 
 278 // Documented in pacemaker.h
 279 int
 280 pcmk_check_rules(xmlNodePtr *xml, xmlNodePtr input, const crm_time_t *date,
     /* [previous][next][first][last][top][bottom][index][help] */
 281                  const char **rule_ids)
 282 {
 283     pcmk__output_t *out = NULL;
 284     int rc = pcmk_rc_ok;
 285 
 286     rc = pcmk__xml_output_new(&out, xml);
 287     if (rc != pcmk_rc_ok) {
 288         return rc;
 289     }
 290 
 291     pcmk__register_lib_messages(out);
 292 
 293     rc = pcmk__check_rules(out, input, date, rule_ids);
 294     pcmk__xml_output_finish(out, xml);
 295     return rc;
 296 }

/* [previous][next][first][last][top][bottom][index][help] */