pacemaker  2.1.6-802a72226b
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>
17 #include <pacemaker-internal.h>
18 
28 static int
29 eval_date_expression(const xmlNode *expr, crm_time_t *now)
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 
59 static int
60 init_rule_check(pcmk__output_t *out, xmlNodePtr input, const crm_time_t *date,
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,
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 
120 static int
121 eval_rule(pe_working_set_t *data_set, const char *rule_id, const char **error)
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,
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);
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 
243 int
244 pcmk__check_rules(pcmk__output_t *out, xmlNodePtr input, const crm_time_t *date,
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 
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,
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 
291 
292  rc = pcmk__check_rules(out, input, date, rule_ids);
293  pcmk__xml_output_finish(out, xml);
294  return rc;
295 }
int pe__eval_date_expr(const xmlNode *expr, const pe_rule_eval_data_t *rule_data, crm_time_t *next_change)
Definition: rules.c:1033
int cib__signon_query(pcmk__output_t *out, cib_t **cib, xmlNode **cib_object)
Definition: cib_utils.c:745
int(* message)(pcmk__output_t *out, const char *message_id,...)
void pe_free_working_set(pe_working_set_t *data_set)
Free a working set.
Definition: status.c:50
struct crm_time_s crm_time_t
Definition: iso8601.h:32
#define XML_CIB_TAG_CONSTRAINTS
Definition: msg_xml.h:202
pe_working_set_t * pe_new_working_set(void)
Create a new working set.
Definition: status.c:34
#define pe_flag_no_compat
Definition: pe_types.h:148
crm_time_t * pcmk_copy_time(const crm_time_t *source)
Definition: iso8601.c:1374
#define pe_flag_no_counts
Don&#39;t count total, disabled and blocked resource instances.
Definition: pe_types.h:143
enum expression_type find_expression_type(xmlNode *expr)
Definition: rules.c:105
xmlNode * copy_xml(xmlNode *src_node)
Definition: xml.c:819
int pcmk__check_rules(pcmk__output_t *out, xmlNodePtr input, const crm_time_t *date, const char **rule_ids)
Definition: pcmk_rule.c:244
xmlNode * pcmk_find_cib_element(xmlNode *cib, const char *element_name)
Find an element in the CIB.
Definition: cib.c:153
int pcmk__xml_output_new(pcmk__output_t **out, xmlNodePtr *xml)
Definition: output.c:236
#define XPATH_NODE_RULE
Definition: pcmk_rule.c:108
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:2329
pe_working_set_t * data_set
ISO_8601 Date handling.
#define XML_EXPR_ATTR_OPERATION
Definition: msg_xml.h:358
xmlNode * input
Definition: pe_types.h:160
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:279
#define CRM_ASSERT(expr)
Definition: results.h:42
xmlXPathObjectPtr xpath_search(xmlNode *xml_top, const char *path)
Definition: xpath.c:139
GHashTable * node_hash
Definition: common.h:193
gboolean cluster_status(pe_working_set_t *data_set)
Definition: status.c:71
xmlNode * input
xmlNode * getXpathResult(xmlXPathObjectPtr xpathObj, int index)
Definition: xpath.c:58
This structure contains everything that makes up a single output formatter.
#define pe__set_working_set_flags(working_set, flags_to_set)
Definition: internal.h:65
void pcmk__xml_output_finish(pcmk__output_t *out, xmlNodePtr *xml)
Definition: output.c:258
void freeXpathObject(xmlXPathObjectPtr xpathObj)
Definition: xpath.c:39
crm_time_t * now
Definition: pe_types.h:161