pacemaker  2.1.7-0f7f88312f
Scalable High-Availability cluster resource manager
pcmk_rule.c
Go to the documentation of this file.
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>
18 #include <pacemaker-internal.h>
19 
29 static int
30 eval_date_expression(const xmlNode *expr, crm_time_t *now)
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 
60 static int
61 init_rule_check(pcmk__output_t *out, xmlNodePtr input, const crm_time_t *date,
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,
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 
121 static int
122 eval_rule(pcmk_scheduler_t *scheduler, const char *rule_id, const char **error)
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,
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);
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 
244 int
245 pcmk__check_rules(pcmk__output_t *out, xmlNodePtr input, const crm_time_t *date,
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 
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,
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 
292 
293  rc = pcmk__check_rules(out, input, date, rule_ids);
294  pcmk__xml_output_finish(out, xml);
295  return rc;
296 }
int pe__eval_date_expr(const xmlNode *expr, const pe_rule_eval_data_t *rule_data, crm_time_t *next_change)
Definition: rules.c:1036
int cib__signon_query(pcmk__output_t *out, cib_t **cib, xmlNode **cib_object)
Definition: cib_utils.c:1022
int(* message)(pcmk__output_t *out, const char *message_id,...)
struct crm_time_s crm_time_t
Definition: iso8601.h:32
#define XML_CIB_TAG_CONSTRAINTS
Definition: msg_xml.h:207
crm_time_t * pcmk_copy_time(const crm_time_t *source)
Definition: iso8601.c:1374
#define pe__set_working_set_flags(scheduler, flags_to_set)
Definition: internal.h:52
Implementation of pcmk_scheduler_t.
Definition: scheduler.h:172
gboolean cluster_status(pcmk_scheduler_t *scheduler)
Definition: status.c:71
enum expression_type find_expression_type(xmlNode *expr)
Definition: rules.c:105
xmlNode * copy_xml(xmlNode *src_node)
Definition: xml.c:789
int pcmk__check_rules(pcmk__output_t *out, xmlNodePtr input, const crm_time_t *date, const char **rule_ids)
Definition: pcmk_rule.c:245
xmlNode * pcmk_find_cib_element(xmlNode *cib, const char *element_name)
Find an element in the CIB.
Definition: cib.c:172
int pcmk__xml_output_new(pcmk__output_t **out, xmlNodePtr *xml)
Definition: output.c:236
#define XPATH_NODE_RULE
Definition: pcmk_rule.c:109
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
int(*) int(*) void(* err)(pcmk__output_t *out, const char *format,...) G_GNUC_PRINTF(2
void pcmk__register_lib_messages(pcmk__output_t *out)
Definition: pcmk_output.c:2403
ISO_8601 Date handling.
#define XML_EXPR_ATTR_OPERATION
Definition: msg_xml.h:348
xmlNode * input
CIB XML.
Definition: scheduler.h:175
pcmk_scheduler_t * pe_new_working_set(void)
Create a new object to hold scheduler data.
Definition: status.c:34
void pe_free_working_set(pcmk_scheduler_t *scheduler)
Free scheduler data.
Definition: status.c:50
xmlXPathObjectPtr xpath_search(const xmlNode *xml_top, const char *path)
Definition: xpath.c:139
int pcmk_check_rules(xmlNodePtr *xml, xmlNodePtr input, const crm_time_t *date, const char **rule_ids)
Check whether each rule in a list is in effect.
Definition: pcmk_rule.c:280
pcmk_scheduler_t * scheduler
#define CRM_ASSERT(expr)
Definition: results.h:42
GHashTable * node_hash
Definition: common.h:80
xmlNode * input
xmlNode * getXpathResult(xmlXPathObjectPtr xpathObj, int index)
Definition: xpath.c:58
This structure contains everything that makes up a single output formatter.
Skip counting of total, disabled, and blocked resource instances.
Definition: scheduler.h:149
void pcmk__xml_output_finish(pcmk__output_t *out, xmlNodePtr *xml)
Definition: output.c:258
Resource role is unknown.
Definition: roles.h:28
void freeXpathObject(xmlXPathObjectPtr xpathObj)
Definition: xpath.c:39
crm_time_t * now
Current time for evaluation purposes.
Definition: scheduler.h:176