pacemaker 3.0.1-16e74fc4da
Scalable High-Availability cluster resource manager
Loading...
Searching...
No Matches
rules.c
Go to the documentation of this file.
1/*
2 * Copyright 2004-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 <stdio.h> // NULL, size_t
13#include <stdbool.h> // bool
14#include <ctype.h> // isdigit()
15#include <regex.h> // regmatch_t
16#include <stdint.h> // uint32_t
17#include <inttypes.h> // PRIu32
18#include <glib.h> // gboolean, FALSE
19#include <libxml/tree.h> // xmlNode
20
22
27#include "crmcommon_private.h"
28
38pcmk__condition_type(const xmlNode *condition)
39{
40 const char *name = NULL;
41
42 // Expression types based on element name
43
44 if (pcmk__xe_is(condition, PCMK_XE_DATE_EXPRESSION)) {
46
47 } else if (pcmk__xe_is(condition, PCMK_XE_RSC_EXPRESSION)) {
49
50 } else if (pcmk__xe_is(condition, PCMK_XE_OP_EXPRESSION)) {
52
53 } else if (pcmk__xe_is(condition, PCMK_XE_RULE)) {
55
56 } else if (!pcmk__xe_is(condition, PCMK_XE_EXPRESSION)) {
58 }
59
60 // Expression types based on node attribute name
61
63
65 NULL)) {
67 }
68
70}
71
80static const char *
81loggable_parent_id(const xmlNode *xml)
82{
83 // Default if called without parent (likely for unit testing)
84 const char *parent_id = "implied";
85
86 if ((xml != NULL) && (xml->parent != NULL)) {
87 parent_id = pcmk__xe_id(xml->parent);
88 if (parent_id == NULL) { // Not possible with schema validation enabled
89 parent_id = "without ID";
90 }
91 }
92 return parent_id;
93}
94
110static int
111check_range(const xmlNode *date_spec, const char *id, const char *attr,
112 uint32_t value)
113{
114 int rc = pcmk_rc_ok;
115 const char *range = crm_element_value(date_spec, attr);
116 long long low, high;
117
118 if (range == NULL) {
119 // Attribute not present
120
121 } else if (pcmk__parse_ll_range(range, &low, &high) != pcmk_rc_ok) {
122 // Invalid range
124 "as not passing because '%s' is not a valid range "
125 "for " PCMK_XE_DATE_SPEC " attribute %s",
126 id, range, attr);
128
129 } else if ((low != -1) && (value < low)) {
131
132 } else if ((high != -1) && (value > high)) {
134 }
135
137 " %s='%s' for %" PRIu32 ": %s",
138 id, attr, pcmk__s(range, ""), value, pcmk_rc_str(rc));
139 return rc;
140}
141
154int
155pcmk__evaluate_date_spec(const xmlNode *date_spec, const crm_time_t *now)
156{
157 const char *id = NULL;
158 const char *parent_id = loggable_parent_id(date_spec);
159
160 // Range attributes that can be specified for a PCMK_XE_DATE_SPEC element
161 struct range {
162 const char *attr;
163 uint32_t value;
164 } ranges[] = {
165 { PCMK_XA_YEARS, 0U },
166 { PCMK_XA_MONTHS, 0U },
167 { PCMK_XA_MONTHDAYS, 0U },
168 { PCMK_XA_HOURS, 0U },
169 { PCMK_XA_MINUTES, 0U },
170 { PCMK_XA_SECONDS, 0U },
171 { PCMK_XA_YEARDAYS, 0U },
172 { PCMK_XA_WEEKYEARS, 0U },
173 { PCMK_XA_WEEKS, 0U },
174 { PCMK_XA_WEEKDAYS, 0U },
175 };
176
177 if ((date_spec == NULL) || (now == NULL)) {
178 return EINVAL;
179 }
180
181 // Get specification ID (for logging)
182 id = pcmk__xe_id(date_spec);
183 if (pcmk__str_empty(id)) { // Not possible with schema validation enabled
184 pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
185 "passing because " PCMK_XE_DATE_SPEC
186 " subelement has no " PCMK_XA_ID, parent_id);
188 }
189
190 // Year, month, day
191 crm_time_get_gregorian(now, &(ranges[0].value), &(ranges[1].value),
192 &(ranges[2].value));
193
194 // Hour, minute, second
195 crm_time_get_timeofday(now, &(ranges[3].value), &(ranges[4].value),
196 &(ranges[5].value));
197
198 // Year (redundant) and day of year
199 crm_time_get_ordinal(now, &(ranges[0].value), &(ranges[6].value));
200
201 // Week year, week of week year, day of week
202 crm_time_get_isoweek(now, &(ranges[7].value), &(ranges[8].value),
203 &(ranges[9].value));
204
205 for (int i = 0; i < PCMK__NELEM(ranges); ++i) {
206 int rc = check_range(date_spec, parent_id, ranges[i].attr,
207 ranges[i].value);
208
209 if (rc != pcmk_rc_ok) {
210 return rc;
211 }
212 }
213
214 // All specified ranges passed, or none were given (also considered a pass)
215 return pcmk_rc_ok;
216}
217
218#define ADD_COMPONENT(component) do { \
219 int rc = pcmk__add_time_from_xml(*end, component, duration); \
220 if (rc != pcmk_rc_ok) { \
221 pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s " \
222 "as not passing because " PCMK_XE_DURATION \
223 " %s attribute %s is invalid: %s", \
224 parent_id, id, \
225 pcmk__time_component_attr(component), \
226 pcmk_rc_str(rc)); \
227 crm_time_free(*end); \
228 *end = NULL; \
229 return rc; \
230 } \
231 } while (0)
232
245int
246pcmk__unpack_duration(const xmlNode *duration, const crm_time_t *start,
247 crm_time_t **end)
248{
249 const char *id = NULL;
250 const char *parent_id = loggable_parent_id(duration);
251
252 if ((start == NULL) || (duration == NULL)
253 || (end == NULL) || (*end != NULL)) {
254 return EINVAL;
255 }
256
257 // Get duration ID (for logging)
258 id = pcmk__xe_id(duration);
259 if (pcmk__str_empty(id)) { // Not possible with schema validation enabled
261 "as not passing because " PCMK_XE_DURATION
262 " subelement has no " PCMK_XA_ID, parent_id);
264 }
265
266 *end = pcmk_copy_time(start);
267
275
276 return pcmk_rc_ok;
277}
278
292static int
293evaluate_in_range(const xmlNode *date_expression, const char *id,
294 const crm_time_t *now, crm_time_t *next_change)
295{
296 crm_time_t *start = NULL;
297 crm_time_t *end = NULL;
298
299 if (pcmk__xe_get_datetime(date_expression, PCMK_XA_START,
300 &start) != pcmk_rc_ok) {
301 pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
302 "passing because " PCMK_XA_START " is invalid", id);
304 }
305
306 if (pcmk__xe_get_datetime(date_expression, PCMK_XA_END,
307 &end) != pcmk_rc_ok) {
308 pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
309 "passing because " PCMK_XA_END " is invalid", id);
310 crm_time_free(start);
312 }
313
314 if ((start == NULL) && (end == NULL)) {
315 // Not possible with schema validation enabled
316 pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
317 "passing because " PCMK_VALUE_IN_RANGE
318 " requires at least one of " PCMK_XA_START " or "
319 PCMK_XA_END, id);
321 }
322
323 if (end == NULL) {
324 xmlNode *duration = pcmk__xe_first_child(date_expression,
325 PCMK_XE_DURATION, NULL, NULL);
326
327 if (duration != NULL) {
328 int rc = pcmk__unpack_duration(duration, start, &end);
329
330 if (rc != pcmk_rc_ok) {
332 " %s as not passing because duration "
333 "is invalid", id);
334 crm_time_free(start);
335 return rc;
336 }
337 }
338 }
339
340 if ((start != NULL) && (crm_time_compare(now, start) < 0)) {
341 pcmk__set_time_if_earlier(next_change, start);
342 crm_time_free(start);
343 crm_time_free(end);
345 }
346
347 if (end != NULL) {
348 if (crm_time_compare(now, end) > 0) {
349 crm_time_free(start);
350 crm_time_free(end);
351 return pcmk_rc_after_range;
352 }
353
354 // Evaluation doesn't change until second after end
355 if (next_change != NULL) {
356 crm_time_add_seconds(end, 1);
357 pcmk__set_time_if_earlier(next_change, end);
358 }
359 }
360
361 crm_time_free(start);
362 crm_time_free(end);
364}
365
379static int
380evaluate_gt(const xmlNode *date_expression, const char *id,
381 const crm_time_t *now, crm_time_t *next_change)
382{
383 crm_time_t *start = NULL;
384
385 if (pcmk__xe_get_datetime(date_expression, PCMK_XA_START,
386 &start) != pcmk_rc_ok) {
387 pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
388 "passing because " PCMK_XA_START " is invalid",
389 id);
391 }
392
393 if (start == NULL) { // Not possible with schema validation enabled
394 pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
395 "passing because " PCMK_VALUE_GT " requires "
396 PCMK_XA_START, id);
398 }
399
400 if (crm_time_compare(now, start) > 0) {
401 crm_time_free(start);
403 }
404
405 // Evaluation doesn't change until second after start time
406 crm_time_add_seconds(start, 1);
407 pcmk__set_time_if_earlier(next_change, start);
408 crm_time_free(start);
410}
411
425static int
426evaluate_lt(const xmlNode *date_expression, const char *id,
427 const crm_time_t *now, crm_time_t *next_change)
428{
429 crm_time_t *end = NULL;
430
431 if (pcmk__xe_get_datetime(date_expression, PCMK_XA_END,
432 &end) != pcmk_rc_ok) {
433 pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
434 "passing because " PCMK_XA_END " is invalid", id);
436 }
437
438 if (end == NULL) { // Not possible with schema validation enabled
439 pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
440 "passing because " PCMK_VALUE_GT " requires "
441 PCMK_XA_END, id);
443 }
444
445 if (crm_time_compare(now, end) < 0) {
446 pcmk__set_time_if_earlier(next_change, end);
447 crm_time_free(end);
449 }
450
451 crm_time_free(end);
452 return pcmk_rc_after_range;
453}
454
469int
470pcmk__evaluate_date_expression(const xmlNode *date_expression,
471 const crm_time_t *now, crm_time_t *next_change)
472{
473 const char *id = NULL;
474 const char *op = NULL;
475 int rc = pcmk_rc_ok;
476
477 if ((date_expression == NULL) || (now == NULL)) {
478 return EINVAL;
479 }
480
481 // Get expression ID (for logging)
482 id = pcmk__xe_id(date_expression);
483 if (pcmk__str_empty(id)) { // Not possible with schema validation enabled
484 pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " without "
485 PCMK_XA_ID " as not passing");
487 }
488
489 op = crm_element_value(date_expression, PCMK_XA_OPERATION);
490 if (pcmk__str_eq(op, PCMK_VALUE_IN_RANGE,
492 rc = evaluate_in_range(date_expression, id, now, next_change);
493
494 } else if (pcmk__str_eq(op, PCMK_VALUE_DATE_SPEC, pcmk__str_casei)) {
495 xmlNode *date_spec = pcmk__xe_first_child(date_expression,
496 PCMK_XE_DATE_SPEC, NULL,
497 NULL);
498
499 if (date_spec == NULL) { // Not possible with schema validation enabled
501 "as not passing because " PCMK_VALUE_DATE_SPEC
502 " operations require a " PCMK_XE_DATE_SPEC
503 " subelement", id);
505 }
506
507 // @TODO set next_change appropriately
508 rc = pcmk__evaluate_date_spec(date_spec, now);
509
510 } else if (pcmk__str_eq(op, PCMK_VALUE_GT, pcmk__str_casei)) {
511 rc = evaluate_gt(date_expression, id, now, next_change);
512
513 } else if (pcmk__str_eq(op, PCMK_VALUE_LT, pcmk__str_casei)) {
514 rc = evaluate_lt(date_expression, id, now, next_change);
515
516 } else { // Not possible with schema validation enabled
518 " %s as not passing because '%s' is not a valid "
519 PCMK_XE_OPERATION, id, op);
521 }
522
523 crm_trace(PCMK_XE_DATE_EXPRESSION " %s (%s): %s (%d)",
524 id, op, pcmk_rc_str(rc), rc);
525 return rc;
526}
527
544static bool
545process_submatches(const char *string, const char *match,
546 const regmatch_t submatches[], int nmatches,
547 char *expansion, size_t *nbytes)
548{
549 bool expanded = false;
550 const char *src = string;
551
552 if (nbytes != NULL) {
553 *nbytes = 1; // Include space for terminator
554 }
555
556 while (*src != '\0') {
557 int submatch = 0;
558 size_t match_len = 0;
559
560 if ((src[0] != '%') || !isdigit(src[1])) {
561 /* src does not point to the first character of a %N sequence,
562 * so expand this character as-is
563 */
564 if (expansion != NULL) {
565 *expansion++ = *src;
566 }
567 if (nbytes != NULL) {
568 ++(*nbytes);
569 }
570 ++src;
571 continue;
572 }
573
574 submatch = src[1] - '0';
575 src += 2; // Skip over %N sequence in source string
576 expanded = true; // Expansion will be different from source
577
578 // Omit sequence from expansion unless it has a non-empty match
579 if ((nmatches <= submatch) // Not enough submatches
580 || (submatches[submatch].rm_so < 0) // Pattern did not match
581 || (submatches[submatch].rm_eo
582 <= submatches[submatch].rm_so)) { // Match was empty
583 continue;
584 }
585
586 match_len = submatches[submatch].rm_eo - submatches[submatch].rm_so;
587 if (nbytes != NULL) {
588 *nbytes += match_len;
589 }
590 if (expansion != NULL) {
591 memcpy(expansion, match + submatches[submatch].rm_so,
592 match_len);
593 expansion += match_len;
594 }
595 }
596
597 return expanded;
598}
599
613char *
614pcmk__replace_submatches(const char *string, const char *match,
615 const regmatch_t submatches[], int nmatches)
616{
617 size_t nbytes = 0;
618 char *result = NULL;
619
620 if (pcmk__str_empty(string) || pcmk__str_empty(match)) {
621 return NULL; // Nothing to expand
622 }
623
624 // Calculate how much space will be needed for expanded string
625 if (!process_submatches(string, match, submatches, nmatches, NULL,
626 &nbytes)) {
627 return NULL; // No expansions needed
628 }
629
630 // Allocate enough space for expanded string
631 result = pcmk__assert_alloc(nbytes, sizeof(char));
632
633 // Expand submatches
634 (void) process_submatches(string, match, submatches, nmatches, result,
635 NULL);
636 return result;
637}
638
653{
654 if (pcmk__str_eq(op, PCMK_VALUE_DEFINED, pcmk__str_casei)) {
656
657 } else if (pcmk__str_eq(op, PCMK_VALUE_NOT_DEFINED, pcmk__str_casei)) {
659
660 } else if (pcmk__str_eq(op, PCMK_VALUE_EQ, pcmk__str_casei)) {
661 return pcmk__comparison_eq;
662
663 } else if (pcmk__str_eq(op, PCMK_VALUE_NE, pcmk__str_casei)) {
664 return pcmk__comparison_ne;
665
666 } else if (pcmk__str_eq(op, PCMK_VALUE_LT, pcmk__str_casei)) {
667 return pcmk__comparison_lt;
668
669 } else if (pcmk__str_eq(op, PCMK_VALUE_LTE, pcmk__str_casei)) {
671
672 } else if (pcmk__str_eq(op, PCMK_VALUE_GT, pcmk__str_casei)) {
673 return pcmk__comparison_gt;
674
675 } else if (pcmk__str_eq(op, PCMK_VALUE_GTE, pcmk__str_casei)) {
677 }
678
680}
681
693enum pcmk__type
695 const char *value1, const char *value2)
696{
697 if (type == NULL) {
698 switch (op) {
703 if (((value1 != NULL) && (strchr(value1, '.') != NULL))
704 || ((value2 != NULL) && (strchr(value2, '.') != NULL))) {
705 return pcmk__type_number;
706 }
707 return pcmk__type_integer;
708
709 default:
710 return pcmk__type_string;
711 }
712 }
713
714 if (pcmk__str_eq(type, PCMK_VALUE_STRING, pcmk__str_casei)) {
715 return pcmk__type_string;
716
717 } else if (pcmk__str_eq(type, PCMK_VALUE_INTEGER, pcmk__str_casei)) {
718 return pcmk__type_integer;
719
720 } else if (pcmk__str_eq(type, PCMK_VALUE_NUMBER, pcmk__str_casei)) {
721 return pcmk__type_number;
722
723 } else if (pcmk__str_eq(type, PCMK_VALUE_VERSION, pcmk__str_casei)) {
724 return pcmk__type_version;
725 }
726
727 return pcmk__type_unknown;
728}
729
742int
743pcmk__cmp_by_type(const char *value1, const char *value2, enum pcmk__type type)
744{
745 // NULL compares as less than non-NULL
746 if (value2 == NULL) {
747 return (value1 == NULL)? 0 : 1;
748 }
749 if (value1 == NULL) {
750 return -1;
751 }
752
753 switch (type) {
755 return strcasecmp(value1, value2);
756
758 {
759 long long integer1;
760 long long integer2;
761
762 if ((pcmk__scan_ll(value1, &integer1, 0LL) != pcmk_rc_ok)
763 || (pcmk__scan_ll(value2, &integer2, 0LL) != pcmk_rc_ok)) {
764 crm_warn("Comparing '%s' and '%s' as strings because "
765 "invalid as integers", value1, value2);
766 return strcasecmp(value1, value2);
767 }
768 return (integer1 < integer2)? -1 : (integer1 > integer2)? 1 : 0;
769 }
770 break;
771
773 {
774 double num1;
775 double num2;
776
777 if ((pcmk__scan_double(value1, &num1, NULL, NULL) != pcmk_rc_ok)
778 || (pcmk__scan_double(value2, &num2, NULL,
779 NULL) != pcmk_rc_ok)) {
780 crm_warn("Comparing '%s' and '%s' as strings because invalid as "
781 "numbers", value1, value2);
782 return strcasecmp(value1, value2);
783 }
784 return (num1 < num2)? -1 : (num1 > num2)? 1 : 0;
785 }
786 break;
787
789 return compare_version(value1, value2);
790
791 default: // Invalid type
792 return 0;
793 }
794}
795
805pcmk__parse_source(const char *source)
806{
807 if (pcmk__str_eq(source, PCMK_VALUE_LITERAL,
810
811 } else if (pcmk__str_eq(source, PCMK_VALUE_PARAM, pcmk__str_casei)) {
813
814 } else if (pcmk__str_eq(source, PCMK_VALUE_META, pcmk__str_casei)) {
816
817 } else {
819 }
820}
821
830enum pcmk__combine
831pcmk__parse_combine(const char *combine)
832{
833 if (pcmk__str_eq(combine, PCMK_VALUE_AND,
835 return pcmk__combine_and;
836
837 } else if (pcmk__str_eq(combine, PCMK_VALUE_OR, pcmk__str_casei)) {
838 return pcmk__combine_or;
839
840 } else {
842 }
843}
844
859static int
860evaluate_attr_comparison(const char *actual, const char *reference,
861 enum pcmk__type type, enum pcmk__comparison comparison)
862{
863 int cmp = 0;
864
865 switch (comparison) {
867 return (actual != NULL)? pcmk_rc_ok : pcmk_rc_op_unsatisfied;
868
870 return (actual == NULL)? pcmk_rc_ok : pcmk_rc_op_unsatisfied;
871
872 default:
873 break;
874 }
875
876 cmp = pcmk__cmp_by_type(actual, reference, type);
877
878 switch (comparison) {
880 return (cmp == 0)? pcmk_rc_ok : pcmk_rc_op_unsatisfied;
881
883 return (cmp != 0)? pcmk_rc_ok : pcmk_rc_op_unsatisfied;
884
885 default:
886 break;
887 }
888
889 if ((actual == NULL) || (reference == NULL)) {
890 return pcmk_rc_op_unsatisfied; // Comparison would be meaningless
891 }
892
893 switch (comparison) {
895 return (cmp < 0)? pcmk_rc_ok : pcmk_rc_after_range;
896
898 return (cmp <= 0)? pcmk_rc_ok : pcmk_rc_after_range;
899
901 return (cmp > 0)? pcmk_rc_ok : pcmk_rc_before_range;
902
904 return (cmp >= 0)? pcmk_rc_ok : pcmk_rc_before_range;
905
906 default: // Not possible with schema validation enabled
908 }
909}
910
919static const char *
920value_from_source(const char *value, enum pcmk__reference_source source,
921 const pcmk_rule_input_t *rule_input)
922{
923 GHashTable *table = NULL;
924
925 switch (source) {
927 return value;
928
930 table = rule_input->rsc_params;
931 break;
932
934 table = rule_input->rsc_meta;
935 break;
936
937 default:
938 return NULL; // Not possible
939 }
940
941 if (table == NULL) {
942 return NULL;
943 }
944 return (const char *) g_hash_table_lookup(table, value);
945}
946
957int
958pcmk__evaluate_attr_expression(const xmlNode *expression,
959 const pcmk_rule_input_t *rule_input)
960{
961 const char *id = NULL;
962 const char *op = NULL;
963 const char *attr = NULL;
964 const char *type_s = NULL;
965 const char *value = NULL;
966 const char *actual = NULL;
967 const char *source_s = NULL;
968 const char *reference = NULL;
969 char *expanded_attr = NULL;
970 int rc = pcmk_rc_ok;
971
975
976 if ((expression == NULL) || (rule_input == NULL)) {
977 return EINVAL;
978 }
979
980 // Get expression ID (for logging)
981 id = pcmk__xe_id(expression);
982 if (pcmk__str_empty(id)) {
983 pcmk__config_err("Treating " PCMK_XE_EXPRESSION " without " PCMK_XA_ID
984 " as not passing");
986 }
987
988 /* Get name of node attribute to compare (expanding any %0-%9 to
989 * regular expression submatches)
990 */
991 attr = crm_element_value(expression, PCMK_XA_ATTRIBUTE);
992 if (attr == NULL) {
993 pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not passing "
994 "because " PCMK_XA_ATTRIBUTE " was not specified", id);
996 }
997 expanded_attr = pcmk__replace_submatches(attr, rule_input->rsc_id,
998 rule_input->rsc_id_submatches,
999 rule_input->rsc_id_nmatches);
1000 if (expanded_attr != NULL) {
1001 attr = expanded_attr;
1002 }
1003
1004 // Get and validate operation
1005 op = crm_element_value(expression, PCMK_XA_OPERATION);
1006 comparison = pcmk__parse_comparison(op);
1007 if (comparison == pcmk__comparison_unknown) {
1008 // Not possible with schema validation enabled
1009 if (op == NULL) {
1010 pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not "
1011 "passing because it has no " PCMK_XA_OPERATION,
1012 id);
1013 } else {
1014 pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not "
1015 "passing because '%s' is not a valid "
1016 PCMK_XA_OPERATION, id, op);
1017 }
1019 goto done;
1020 }
1021
1022 // How reference value is obtained (literal, resource meta-attribute, etc.)
1023 source_s = crm_element_value(expression, PCMK_XA_VALUE_SOURCE);
1024 source = pcmk__parse_source(source_s);
1025 if (source == pcmk__source_unknown) {
1026 // Not possible with schema validation enabled
1027 pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not passing "
1028 "because '%s' is not a valid " PCMK_XA_VALUE_SOURCE,
1029 id, source_s);
1031 goto done;
1032 }
1033
1034 // Get and validate reference value
1035 value = crm_element_value(expression, PCMK_XA_VALUE);
1036 switch (comparison) {
1039 if (value != NULL) {
1040 pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not "
1041 "passing because " PCMK_XA_VALUE " is not "
1042 "allowed when " PCMK_XA_OPERATION " is %s",
1043 id, op);
1045 goto done;
1046 }
1047 break;
1048
1049 default:
1050 if (value == NULL) {
1051 pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not "
1052 "passing because " PCMK_XA_VALUE " is "
1053 "required when " PCMK_XA_OPERATION " is %s",
1054 id, op);
1056 goto done;
1057 }
1058 reference = value_from_source(value, source, rule_input);
1059 break;
1060 }
1061
1062 // Get actual value of node attribute
1063 if (rule_input->node_attrs != NULL) {
1064 actual = g_hash_table_lookup(rule_input->node_attrs, attr);
1065 }
1066
1067 // Get and validate value type (after expanding reference value)
1068 type_s = crm_element_value(expression, PCMK_XA_TYPE);
1069 type = pcmk__parse_type(type_s, comparison, actual, reference);
1070 if (type == pcmk__type_unknown) {
1071 // Not possible with schema validation enabled
1072 pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not passing "
1073 "because '%s' is not a valid type", id, type_s);
1075 goto done;
1076 }
1077
1078 rc = evaluate_attr_comparison(actual, reference, type, comparison);
1079 switch (comparison) {
1082 crm_trace(PCMK_XE_EXPRESSION " %s result: %s (for attribute %s %s)",
1083 id, pcmk_rc_str(rc), attr, op);
1084 break;
1085
1086 default:
1087 crm_trace(PCMK_XE_EXPRESSION " %s result: "
1088 "%s (attribute %s %s '%s' via %s source as %s type)",
1089 id, pcmk_rc_str(rc), attr, op, pcmk__s(reference, ""),
1090 pcmk__s(source_s, "default"), pcmk__s(type_s, "default"));
1091 break;
1092 }
1093
1094done:
1095 free(expanded_attr);
1096 return rc;
1097}
1098
1109int
1110pcmk__evaluate_rsc_expression(const xmlNode *rsc_expression,
1111 const pcmk_rule_input_t *rule_input)
1112{
1113 const char *id = NULL;
1114 const char *standard = NULL;
1115 const char *provider = NULL;
1116 const char *type = NULL;
1117
1118 if ((rsc_expression == NULL) || (rule_input == NULL)) {
1119 return EINVAL;
1120 }
1121
1122 // Validate XML ID
1123 id = pcmk__xe_id(rsc_expression);
1124 if (pcmk__str_empty(id)) {
1125 // Not possible with schema validation enabled
1126 pcmk__config_err("Treating " PCMK_XE_RSC_EXPRESSION " without "
1127 PCMK_XA_ID " as not passing");
1128 return pcmk_rc_unpack_error;
1129 }
1130
1131 // Compare resource standard
1132 standard = crm_element_value(rsc_expression, PCMK_XA_CLASS);
1133 if ((standard != NULL)
1134 && !pcmk__str_eq(standard, rule_input->rsc_standard, pcmk__str_none)) {
1135 crm_trace(PCMK_XE_RSC_EXPRESSION " %s is unsatisfied because "
1136 "actual standard '%s' doesn't match '%s'",
1137 id, pcmk__s(rule_input->rsc_standard, ""), standard);
1139 }
1140
1141 // Compare resource provider
1142 provider = crm_element_value(rsc_expression, PCMK_XA_PROVIDER);
1143 if ((provider != NULL)
1144 && !pcmk__str_eq(provider, rule_input->rsc_provider, pcmk__str_none)) {
1145 crm_trace(PCMK_XE_RSC_EXPRESSION " %s is unsatisfied because "
1146 "actual provider '%s' doesn't match '%s'",
1147 id, pcmk__s(rule_input->rsc_provider, ""), provider);
1149 }
1150
1151 // Compare resource agent type
1152 type = crm_element_value(rsc_expression, PCMK_XA_TYPE);
1153 if ((type != NULL)
1154 && !pcmk__str_eq(type, rule_input->rsc_agent, pcmk__str_none)) {
1155 crm_trace(PCMK_XE_RSC_EXPRESSION " %s is unsatisfied because "
1156 "actual agent '%s' doesn't match '%s'",
1157 id, pcmk__s(rule_input->rsc_agent, ""), type);
1159 }
1160
1161 crm_trace(PCMK_XE_RSC_EXPRESSION " %s is satisfied by %s%s%s:%s",
1162 id, pcmk__s(standard, ""),
1163 ((provider == NULL)? "" : ":"), pcmk__s(provider, ""),
1164 pcmk__s(type, ""));
1165 return pcmk_rc_ok;
1166}
1167
1178int
1179pcmk__evaluate_op_expression(const xmlNode *op_expression,
1180 const pcmk_rule_input_t *rule_input)
1181{
1182 const char *id = NULL;
1183 const char *name = NULL;
1184 const char *interval_s = NULL;
1185 guint interval_ms = 0U;
1186
1187 if ((op_expression == NULL) || (rule_input == NULL)) {
1188 return EINVAL;
1189 }
1190
1191 // Get operation expression ID (for logging)
1192 id = pcmk__xe_id(op_expression);
1193 if (pcmk__str_empty(id)) { // Not possible with schema validation enabled
1194 pcmk__config_err("Treating " PCMK_XE_OP_EXPRESSION " without "
1195 PCMK_XA_ID " as not passing");
1196 return pcmk_rc_unpack_error;
1197 }
1198
1199 // Validate operation name
1200 name = crm_element_value(op_expression, PCMK_XA_NAME);
1201 if (name == NULL) { // Not possible with schema validation enabled
1202 pcmk__config_err("Treating " PCMK_XE_OP_EXPRESSION " %s as not "
1203 "passing because it has no " PCMK_XA_NAME, id);
1204 return pcmk_rc_unpack_error;
1205 }
1206
1207 // Validate operation interval
1208 interval_s = crm_element_value(op_expression, PCMK_META_INTERVAL);
1209 if (pcmk_parse_interval_spec(interval_s, &interval_ms) != pcmk_rc_ok) {
1210 pcmk__config_err("Treating " PCMK_XE_OP_EXPRESSION " %s as not "
1211 "passing because '%s' is not a valid "
1213 id, interval_s);
1214 return pcmk_rc_unpack_error;
1215 }
1216
1217 // Compare operation name
1218 if (!pcmk__str_eq(name, rule_input->op_name, pcmk__str_none)) {
1219 crm_trace(PCMK_XE_OP_EXPRESSION " %s is unsatisfied because "
1220 "actual name '%s' doesn't match '%s'",
1221 id, pcmk__s(rule_input->op_name, ""), name);
1223 }
1224
1225 // Compare operation interval (unspecified interval matches all)
1226 if ((interval_s != NULL) && (interval_ms != rule_input->op_interval_ms)) {
1227 crm_trace(PCMK_XE_OP_EXPRESSION " %s is unsatisfied because "
1228 "actual interval %s doesn't match %s",
1229 id, pcmk__readable_interval(rule_input->op_interval_ms),
1230 pcmk__readable_interval(interval_ms));
1232 }
1233
1234 crm_trace(PCMK_XE_OP_EXPRESSION " %s is satisfied (name %s, interval %s)",
1235 id, name, pcmk__readable_interval(rule_input->op_interval_ms));
1236 return pcmk_rc_ok;
1237}
1238
1251int
1252pcmk__evaluate_condition(xmlNode *condition,
1253 const pcmk_rule_input_t *rule_input,
1254 crm_time_t *next_change)
1255{
1256
1257 if ((condition == NULL) || (rule_input == NULL)) {
1258 return EINVAL;
1259 }
1260
1261 switch (pcmk__condition_type(condition)) {
1263 return pcmk_evaluate_rule(condition, rule_input, next_change);
1264
1267 return pcmk__evaluate_attr_expression(condition, rule_input);
1268
1270 {
1271 int rc = pcmk__evaluate_date_expression(condition,
1272 rule_input->now,
1273 next_change);
1274
1275 return (rc == pcmk_rc_within_range)? pcmk_rc_ok : rc;
1276 }
1277
1279 return pcmk__evaluate_rsc_expression(condition, rule_input);
1280
1282 return pcmk__evaluate_op_expression(condition, rule_input);
1283
1284 default: // Not possible with schema validation enabled
1285 pcmk__config_err("Treating rule condition %s as not passing "
1286 "because %s is not a valid condition type",
1287 pcmk__s(pcmk__xe_id(condition), "without ID"),
1288 (const char *) condition->name);
1289 return pcmk_rc_unpack_error;
1290 }
1291}
1292
1303int
1304pcmk_evaluate_rule(xmlNode *rule, const pcmk_rule_input_t *rule_input,
1305 crm_time_t *next_change)
1306{
1307 bool empty = true;
1308 int rc = pcmk_rc_ok;
1309 const char *id = NULL;
1310 const char *value = NULL;
1311 enum pcmk__combine combine = pcmk__combine_unknown;
1312
1313 if ((rule == NULL) || (rule_input == NULL)) {
1314 return EINVAL;
1315 }
1316
1317 rule = pcmk__xe_resolve_idref(rule, NULL);
1318 if (rule == NULL) {
1319 // Not possible with schema validation enabled; message already logged
1320 return pcmk_rc_unpack_error;
1321 }
1322
1323 // Validate XML ID
1324 id = pcmk__xe_id(rule);
1325 if (pcmk__str_empty(id)) { // Not possible with schema validation enabled
1326 pcmk__config_err("Treating " PCMK_XE_RULE " without " PCMK_XA_ID
1327 " as not passing");
1328 return pcmk_rc_unpack_error;
1329 }
1330
1332 combine = pcmk__parse_combine(value);
1333 switch (combine) {
1334 case pcmk__combine_and:
1335 // For "and", rc defaults to success (reset on failure below)
1336 break;
1337
1338 case pcmk__combine_or:
1339 // For "or", rc defaults to failure (reset on success below)
1341 break;
1342
1343 default: // Not possible with schema validation enabled
1344 pcmk__config_err("Treating " PCMK_XE_RULE " %s as not passing "
1345 "because '%s' is not a valid " PCMK_XA_BOOLEAN_OP,
1346 id, value);
1347 return pcmk_rc_unpack_error;
1348 }
1349
1350 // Evaluate each condition
1351 for (xmlNode *condition = pcmk__xe_first_child(rule, NULL, NULL, NULL);
1352 condition != NULL; condition = pcmk__xe_next(condition, NULL)) {
1353
1354 empty = false;
1355 if (pcmk__evaluate_condition(condition, rule_input,
1356 next_change) == pcmk_rc_ok) {
1357 if (combine == pcmk__combine_or) {
1358 rc = pcmk_rc_ok; // Any pass is final for "or"
1359 break;
1360 }
1361 } else if (combine == pcmk__combine_and) {
1362 rc = pcmk_rc_op_unsatisfied; // Any failure is final for "and"
1363 break;
1364 }
1365 }
1366
1367 if (empty) { // Not possible with schema validation enabled
1368 pcmk__config_warn("Ignoring rule %s because it contains no conditions",
1369 id);
1370 rc = pcmk_rc_ok;
1371 }
1372
1373 crm_trace("Rule %s is %ssatisfied", id, ((rc == pcmk_rc_ok)? "" : "not "));
1374 return rc;
1375}
const char * name
Definition cib.c:26
#define PCMK__NELEM(a)
Definition internal.h:50
#define pcmk__assert_alloc(nmemb, size)
Definition internal.h:246
expression_type
Definition rules.h:33
@ pcmk__condition_rule
Definition rules.h:35
@ pcmk__condition_location
Definition rules.h:37
@ pcmk__condition_unknown
Definition rules.h:34
@ pcmk__condition_attribute
Definition rules.h:36
@ pcmk__condition_datetime
Definition rules.h:38
@ pcmk__condition_resource
Definition rules.h:39
@ pcmk__condition_operation
Definition rules.h:40
int compare_version(const char *version1, const char *version2)
Definition utils.c:206
enum pcmk_ipc_server type
Definition cpg.c:3
#define CRM_ATTR_KIND
Definition crm.h:94
#define CRM_ATTR_UNAME
Definition crm.h:92
#define CRM_ATTR_ID
Definition crm.h:93
G_GNUC_INTERNAL void pcmk__set_time_if_earlier(crm_time_t *target, const crm_time_t *source)
Definition iso8601.c:1461
@ pcmk__type_integer
@ pcmk__type_unknown
@ pcmk__type_version
@ pcmk__type_number
@ pcmk__type_string
pcmk__reference_source
@ pcmk__source_unknown
@ pcmk__source_literal
@ pcmk__source_meta_attrs
@ pcmk__source_instance_attrs
pcmk__comparison
@ pcmk__comparison_gt
@ pcmk__comparison_lte
@ pcmk__comparison_unknown
@ pcmk__comparison_ne
@ pcmk__comparison_eq
@ pcmk__comparison_defined
@ pcmk__comparison_gte
@ pcmk__comparison_undefined
@ pcmk__comparison_lt
@ pcmk__time_hours
@ pcmk__time_minutes
@ pcmk__time_seconds
@ pcmk__time_days
@ pcmk__time_years
@ pcmk__time_months
@ pcmk__time_weeks
void crm_time_add_seconds(crm_time_t *dt, int value)
Add a given number of seconds to a date/time or duration.
Definition iso8601.c:1766
void crm_time_free(crm_time_t *dt)
Definition iso8601.c:150
int crm_time_get_ordinal(const crm_time_t *dt, uint32_t *y, uint32_t *d)
Definition iso8601.c:413
crm_time_t * pcmk_copy_time(const crm_time_t *source)
Definition iso8601.c:1471
int crm_time_get_timeofday(const crm_time_t *dt, uint32_t *h, uint32_t *m, uint32_t *s)
Definition iso8601.c:314
struct crm_time_s crm_time_t
Definition iso8601.h:32
int crm_time_get_gregorian(const crm_time_t *dt, uint32_t *y, uint32_t *m, uint32_t *d)
Definition iso8601.c:380
int crm_time_compare(const crm_time_t *a, const crm_time_t *b)
Definition iso8601.c:1736
int crm_time_get_isoweek(const crm_time_t *dt, uint32_t *y, uint32_t *w, uint32_t *d)
Definition iso8601.c:421
const char * pcmk__readable_interval(guint interval_ms)
Definition iso8601.c:2210
#define crm_warn(fmt, args...)
Definition logging.h:360
#define crm_trace(fmt, args...)
Definition logging.h:370
#define pcmk__config_warn(fmt...)
#define pcmk__config_err(fmt...)
#define PCMK_VALUE_NOT_DEFINED
Definition options.h:182
#define PCMK_META_INTERVAL
Definition options.h:92
#define PCMK_VALUE_NE
Definition options.h:178
#define PCMK_VALUE_INTEGER
Definition options.h:166
#define PCMK_VALUE_IN_RANGE
Definition options.h:164
#define PCMK_VALUE_LT
Definition options.h:168
#define PCMK_VALUE_META
Definition options.h:172
#define PCMK_VALUE_EQ
Definition options.h:151
#define PCMK_VALUE_GTE
Definition options.h:161
#define PCMK_VALUE_DEFINED
Definition options.h:145
#define PCMK_VALUE_AND
Definition options.h:134
#define PCMK_VALUE_OR
Definition options.h:190
#define PCMK_VALUE_GT
Definition options.h:160
#define PCMK_VALUE_PARAM
Definition options.h:192
#define PCMK_VALUE_NUMBER
Definition options.h:184
#define PCMK_VALUE_DATE_SPEC
Definition options.h:143
#define PCMK_VALUE_LITERAL
Definition options.h:167
#define PCMK_VALUE_VERSION
Definition options.h:223
#define PCMK_VALUE_STRING
Definition options.h:212
#define PCMK_VALUE_LTE
Definition options.h:169
pcmk__action_result_t result
Definition pcmk_fence.c:37
const char * pcmk_rc_str(int rc)
Get a user-friendly description of a return code.
Definition results.c:617
@ pcmk_rc_before_range
Definition results.h:131
@ pcmk_rc_op_unsatisfied
Definition results.h:133
@ pcmk_rc_ok
Definition results.h:159
@ pcmk_rc_within_range
Definition results.h:130
@ pcmk_rc_after_range
Definition results.h:129
@ pcmk_rc_unpack_error
Definition results.h:122
int pcmk__unpack_duration(const xmlNode *duration, const crm_time_t *start, crm_time_t **end)
Definition rules.c:246
int pcmk_evaluate_rule(xmlNode *rule, const pcmk_rule_input_t *rule_input, crm_time_t *next_change)
Evaluate a single rule, including all its conditions.
Definition rules.c:1304
#define ADD_COMPONENT(component)
Definition rules.c:218
enum pcmk__reference_source pcmk__parse_source(const char *source)
Definition rules.c:805
int pcmk__evaluate_date_spec(const xmlNode *date_spec, const crm_time_t *now)
Definition rules.c:155
int pcmk__evaluate_date_expression(const xmlNode *date_expression, const crm_time_t *now, crm_time_t *next_change)
Definition rules.c:470
enum expression_type pcmk__condition_type(const xmlNode *condition)
Definition rules.c:38
enum pcmk__comparison pcmk__parse_comparison(const char *op)
Definition rules.c:652
int pcmk__evaluate_attr_expression(const xmlNode *expression, const pcmk_rule_input_t *rule_input)
Definition rules.c:958
int pcmk__evaluate_rsc_expression(const xmlNode *rsc_expression, const pcmk_rule_input_t *rule_input)
Definition rules.c:1110
int pcmk__evaluate_op_expression(const xmlNode *op_expression, const pcmk_rule_input_t *rule_input)
Definition rules.c:1179
enum pcmk__type pcmk__parse_type(const char *type, enum pcmk__comparison op, const char *value1, const char *value2)
Definition rules.c:694
char * pcmk__replace_submatches(const char *string, const char *match, const regmatch_t submatches[], int nmatches)
Definition rules.c:614
int pcmk__evaluate_condition(xmlNode *condition, const pcmk_rule_input_t *rule_input, crm_time_t *next_change)
Definition rules.c:1252
enum pcmk__combine pcmk__parse_combine(const char *combine)
Definition rules.c:831
int pcmk__cmp_by_type(const char *value1, const char *value2, enum pcmk__type type)
Definition rules.c:743
pcmk__combine
@ pcmk__combine_or
@ pcmk__combine_unknown
@ pcmk__combine_and
Scheduler API.
int pcmk_parse_interval_spec(const char *input, guint *result_ms)
Parse milliseconds from a Pacemaker interval specification.
Definition strings.c:452
int pcmk__scan_double(const char *text, double *result, const char *default_text, char **end_text)
Definition strings.c:191
int pcmk__scan_ll(const char *text, long long *result, long long default_value)
Definition strings.c:92
@ pcmk__str_none
@ pcmk__str_null_matches
@ pcmk__str_casei
bool pcmk__str_any_of(const char *s,...) G_GNUC_NULL_TERMINATED
Definition strings.c:1053
int pcmk__parse_ll_range(const char *srcstring, long long *start, long long *end)
Definition strings.c:905
Data used to evaluate a rule (any NULL items are ignored)
Definition rules.h:57
const char * rsc_standard
Resource standard that rule applies to.
Definition rules.h:62
const char * op_name
Operation name that rule applies to.
Definition rules.h:67
const char * rsc_provider
Resource provider that rule applies to.
Definition rules.h:63
const regmatch_t * rsc_id_submatches
Resource pattern submatches (as set by regexec()) for rsc_id.
Definition rules.h:99
int rsc_id_nmatches
Number of entries in rsc_id_submatches.
Definition rules.h:102
GHashTable * node_attrs
Definition rules.h:77
GHashTable * rsc_meta
Definition rules.h:93
const crm_time_t * now
Current time for rule evaluation purposes.
Definition rules.h:59
const char * rsc_agent
Resource agent that rule applies to.
Definition rules.h:64
const char * rsc_id
Resource ID to compare against a location constraint's resource pattern.
Definition rules.h:96
guint op_interval_ms
Operation interval that rule applies to.
Definition rules.h:68
GHashTable * rsc_params
Definition rules.h:86
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
xmlNode * pcmk__xe_first_child(const xmlNode *parent, const char *node_name, const char *attr_n, const char *attr_v)
Definition xml_element.c:43
xmlNode * pcmk__xe_next(const xmlNode *node, const char *element_name)
int pcmk__xe_get_datetime(const xmlNode *xml, const char *attr, crm_time_t **t)
xmlNode * pcmk__xe_resolve_idref(xmlNode *xml, xmlNode *search)
Definition xml_idref.c:85
#define PCMK_XA_VALUE_SOURCE
Definition xml_names.h:443
#define PCMK_XA_START
Definition xml_names.h:408
#define PCMK_XA_CLASS
Definition xml_names.h:246
#define PCMK_XA_MONTHS
Definition xml_names.h:328
#define PCMK_XE_EXPRESSION
Definition xml_names.h:109
#define PCMK_XA_WEEKDAYS
Definition xml_names.h:446
#define PCMK_XE_RULE
Definition xml_names.h:191
#define PCMK_XA_YEARS
Definition xml_names.h:456
#define PCMK_XA_END
Definition xml_names.h:267
#define PCMK_XA_OPERATION
Definition xml_names.h:349
#define PCMK_XA_MINUTES
Definition xml_names.h:325
#define PCMK_XE_OP_EXPRESSION
Definition xml_names.h:148
#define PCMK_XA_ID
Definition xml_names.h:301
#define PCMK_XE_DATE_SPEC
Definition xml_names.h:97
#define PCMK_XA_SECONDS
Definition xml_names.h:399
#define PCMK_XA_PROVIDER
Definition xml_names.h:364
#define PCMK_XA_BOOLEAN_OP
Definition xml_names.h:240
#define PCMK_XA_WEEKS
Definition xml_names.h:447
#define PCMK_XE_RSC_EXPRESSION
Definition xml_names.h:187
#define PCMK_XA_VALUE
Definition xml_names.h:442
#define PCMK_XA_YEARDAYS
Definition xml_names.h:455
#define PCMK_XA_ATTRIBUTE
Definition xml_names.h:236
#define PCMK_XA_TYPE
Definition xml_names.h:430
#define PCMK_XA_WEEKYEARS
Definition xml_names.h:448
#define PCMK_XE_OPERATION
Definition xml_names.h:149
#define PCMK_XA_MONTHDAYS
Definition xml_names.h:327
#define PCMK_XE_DATE_EXPRESSION
Definition xml_names.h:96
#define PCMK_XA_NAME
Definition xml_names.h:330
#define PCMK_XE_DURATION
Definition xml_names.h:104
#define PCMK_XA_HOURS
Definition xml_names.h:300