Line data Source code
1 : /*
2 : * Copyright 2022-2024 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/common/xml.h>
16 : #include <crm/pengine/internal.h>
17 : #include <crm/pengine/rules_internal.h>
18 : #include <pacemaker-internal.h>
19 :
20 : #include "libpacemaker_private.h"
21 :
22 : #define XPATH_NODE_RULE "//" PCMK_XE_RULE "[@" PCMK_XA_ID "='%s']"
23 :
24 : /*!
25 : * \internal
26 : * \brief Check whether a given rule is in effect
27 : *
28 : * \param[in] scheduler Scheduler data
29 : * \param[in] rule_id The ID of the rule to check
30 : * \param[out] error Where to store a rule evaluation error message
31 : *
32 : * \return Standard Pacemaker return code
33 : */
34 : static int
35 0 : eval_rule(pcmk_scheduler_t *scheduler, const char *rule_id, const char **error)
36 : {
37 0 : xmlNodePtr cib_constraints = NULL;
38 0 : xmlNodePtr match = NULL;
39 0 : xmlXPathObjectPtr xpath_obj = NULL;
40 0 : char *xpath = NULL;
41 0 : int rc = pcmk_rc_ok;
42 0 : int num_results = 0;
43 :
44 0 : *error = NULL;
45 :
46 : /* Rules are under the constraints node in the XML, so first find that. */
47 0 : cib_constraints = pcmk_find_cib_element(scheduler->input,
48 : PCMK_XE_CONSTRAINTS);
49 :
50 : /* Get all rules matching the given ID that are also simple enough for us
51 : * to check. For the moment, these rules must only have a single
52 : * date_expression child and:
53 : * - Do not have a date_spec operation, or
54 : * - Have a date_spec operation that contains years= but does not contain
55 : * moon=.
56 : *
57 : * We do this in steps to provide better error messages. First, check that
58 : * there's any rule with the given ID.
59 : */
60 0 : xpath = crm_strdup_printf(XPATH_NODE_RULE, rule_id);
61 0 : xpath_obj = xpath_search(cib_constraints, xpath);
62 0 : num_results = numXpathResults(xpath_obj);
63 :
64 0 : free(xpath);
65 0 : freeXpathObject(xpath_obj);
66 :
67 0 : if (num_results == 0) {
68 0 : *error = "Rule not found";
69 0 : return ENXIO;
70 : }
71 :
72 0 : if (num_results > 1) {
73 : // Should not be possible; schema prevents this
74 0 : *error = "Found more than one rule with matching ID";
75 0 : return pcmk_rc_duplicate_id;
76 : }
77 :
78 : /* Next, make sure it has exactly one date_expression. */
79 0 : xpath = crm_strdup_printf(XPATH_NODE_RULE "//date_expression", rule_id);
80 0 : xpath_obj = xpath_search(cib_constraints, xpath);
81 0 : num_results = numXpathResults(xpath_obj);
82 :
83 0 : free(xpath);
84 0 : freeXpathObject(xpath_obj);
85 :
86 0 : if (num_results != 1) {
87 0 : if (num_results == 0) {
88 0 : *error = "Rule does not have a date expression";
89 : } else {
90 0 : *error = "Rule has more than one date expression";
91 : }
92 0 : return EOPNOTSUPP;
93 : }
94 :
95 : /* Then, check that it's something we actually support. */
96 0 : xpath = crm_strdup_printf(XPATH_NODE_RULE
97 : "//" PCMK_XE_DATE_EXPRESSION
98 : "[@" PCMK_XA_OPERATION
99 : "!='" PCMK_VALUE_DATE_SPEC "']",
100 : rule_id);
101 0 : xpath_obj = xpath_search(cib_constraints, xpath);
102 0 : num_results = numXpathResults(xpath_obj);
103 :
104 0 : free(xpath);
105 :
106 0 : if (num_results == 0) {
107 0 : freeXpathObject(xpath_obj);
108 :
109 0 : xpath = crm_strdup_printf(XPATH_NODE_RULE
110 : "//" PCMK_XE_DATE_EXPRESSION
111 : "[@" PCMK_XA_OPERATION
112 : "='" PCMK_VALUE_DATE_SPEC "' "
113 : "and " PCMK_XE_DATE_SPEC
114 : "/@" PCMK_XA_YEARS " "
115 : "and not(" PCMK_XE_DATE_SPEC
116 : "/@" PCMK__XA_MOON ")]",
117 : rule_id);
118 0 : xpath_obj = xpath_search(cib_constraints, xpath);
119 0 : num_results = numXpathResults(xpath_obj);
120 :
121 0 : free(xpath);
122 :
123 0 : if (num_results == 0) {
124 0 : freeXpathObject(xpath_obj);
125 0 : *error = "Rule must either not use " PCMK_XE_DATE_SPEC ", or use "
126 : PCMK_XE_DATE_SPEC " with " PCMK_XA_YEARS "= but not "
127 : PCMK__XA_MOON "=";
128 0 : return EOPNOTSUPP;
129 : }
130 : }
131 :
132 0 : match = getXpathResult(xpath_obj, 0);
133 :
134 : /* We should have ensured this with the xpath query above, but double-
135 : * checking can't hurt.
136 : */
137 0 : CRM_ASSERT(match != NULL);
138 0 : CRM_ASSERT(pcmk__condition_type(match) == pcmk__condition_datetime);
139 :
140 0 : rc = pcmk__evaluate_date_expression(match, scheduler->now, NULL);
141 0 : if (rc == pcmk_rc_undetermined) { // Malformed or missing
142 0 : *error = "Error parsing rule";
143 : }
144 :
145 0 : freeXpathObject(xpath_obj);
146 0 : return rc;
147 : }
148 :
149 : /*!
150 : * \internal
151 : * \brief Check whether each rule in a list is in effect
152 : *
153 : * \param[in,out] out Output object
154 : * \param[in] input The CIB XML to check (if \c NULL, use current CIB)
155 : * \param[in] date Check whether the rule is in effect at this date and
156 : * time (if \c NULL, use current date and time)
157 : * \param[in] rule_ids The IDs of the rules to check, as a <tt>NULL</tt>-
158 : * terminated list.
159 : *
160 : * \return Standard Pacemaker return code
161 : */
162 : int
163 0 : pcmk__check_rules(pcmk__output_t *out, xmlNodePtr input, const crm_time_t *date,
164 : const char **rule_ids)
165 : {
166 0 : pcmk_scheduler_t *scheduler = NULL;
167 0 : int rc = pcmk_rc_ok;
168 :
169 0 : CRM_ASSERT(out != NULL);
170 :
171 0 : if (rule_ids == NULL) {
172 : // Trivial case; every rule specified is in effect
173 0 : return pcmk_rc_ok;
174 : }
175 :
176 0 : rc = pcmk__init_scheduler(out, input, date, &scheduler);
177 0 : if (rc != pcmk_rc_ok) {
178 0 : return rc;
179 : }
180 :
181 0 : for (const char **rule_id = rule_ids; *rule_id != NULL; rule_id++) {
182 0 : const char *error = NULL;
183 0 : int last_rc = eval_rule(scheduler, *rule_id, &error);
184 :
185 0 : out->message(out, "rule-check", *rule_id, last_rc, error);
186 :
187 0 : if (last_rc != pcmk_rc_ok) {
188 0 : rc = last_rc;
189 : }
190 : }
191 :
192 0 : pe_free_working_set(scheduler);
193 0 : return rc;
194 : }
195 :
196 : // Documented in pacemaker.h
197 : int
198 0 : pcmk_check_rules(xmlNodePtr *xml, xmlNodePtr input, const crm_time_t *date,
199 : const char **rule_ids)
200 : {
201 0 : pcmk__output_t *out = NULL;
202 0 : int rc = pcmk_rc_ok;
203 :
204 0 : rc = pcmk__xml_output_new(&out, xml);
205 0 : if (rc != pcmk_rc_ok) {
206 0 : return rc;
207 : }
208 :
209 0 : pcmk__register_lib_messages(out);
210 :
211 0 : rc = pcmk__check_rules(out, input, date, rule_ids);
212 0 : pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
213 0 : return rc;
214 : }
|