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

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