This source file includes following definitions.
- pcmk__condition_type
- loggable_parent_id
- phase_of_the_moon
- check_range
- pcmk__evaluate_date_spec
- pcmk__unpack_duration
- evaluate_in_range
- evaluate_gt
- evaluate_lt
- pcmk__evaluate_date_expression
- process_submatches
- pcmk__replace_submatches
- pcmk__parse_comparison
- pcmk__parse_type
- pcmk__cmp_by_type
- pcmk__parse_source
- pcmk__parse_combine
- evaluate_attr_comparison
- value_from_source
- pcmk__evaluate_attr_expression
- pcmk__evaluate_rsc_expression
- pcmk__evaluate_op_expression
- pcmk__evaluate_condition
- pcmk_evaluate_rule
- pcmk__evaluate_rules
1
2
3
4
5
6
7
8
9
10 #include <crm_internal.h>
11
12 #include <stdio.h>
13 #include <stdbool.h>
14 #include <ctype.h>
15 #include <regex.h>
16 #include <stdint.h>
17 #include <inttypes.h>
18 #include <glib.h>
19 #include <libxml/tree.h>
20
21 #include <crm/common/scheduler.h>
22
23 #include <crm/common/iso8601_internal.h>
24 #include <crm/common/nvpair_internal.h>
25 #include <crm/common/scheduler_internal.h>
26 #include "crmcommon_private.h"
27
28
29
30
31
32
33
34
35
36 enum expression_type
37 pcmk__condition_type(const xmlNode *condition)
38 {
39 const char *name = NULL;
40
41
42
43 if (pcmk__xe_is(condition, PCMK_XE_DATE_EXPRESSION)) {
44 return pcmk__condition_datetime;
45
46 } else if (pcmk__xe_is(condition, PCMK_XE_RSC_EXPRESSION)) {
47 return pcmk__condition_resource;
48
49 } else if (pcmk__xe_is(condition, PCMK_XE_OP_EXPRESSION)) {
50 return pcmk__condition_operation;
51
52 } else if (pcmk__xe_is(condition, PCMK_XE_RULE)) {
53 return pcmk__condition_rule;
54
55 } else if (!pcmk__xe_is(condition, PCMK_XE_EXPRESSION)) {
56 return pcmk__condition_unknown;
57 }
58
59
60
61 name = crm_element_value(condition, PCMK_XA_ATTRIBUTE);
62
63 if (pcmk__str_any_of(name, CRM_ATTR_UNAME, CRM_ATTR_KIND, CRM_ATTR_ID,
64 NULL)) {
65 return pcmk__condition_location;
66 }
67
68 return pcmk__condition_attribute;
69 }
70
71
72
73
74
75
76
77
78
79 static const char *
80 loggable_parent_id(const xmlNode *xml)
81 {
82
83 const char *parent_id = "implied";
84
85 if ((xml != NULL) && (xml->parent != NULL)) {
86 parent_id = pcmk__xe_id(xml->parent);
87 if (parent_id == NULL) {
88 parent_id = "without ID";
89 }
90 }
91 return parent_id;
92 }
93
94
95
96
97
98
99
100
101
102
103
104 static int
105 phase_of_the_moon(const crm_time_t *now)
106 {
107
108
109
110
111
112
113
114
115
116
117
118
119 uint32_t epact, diy, goldn;
120 uint32_t y;
121
122 crm_time_get_ordinal(now, &y, &diy);
123 goldn = (y % 19) + 1;
124 epact = (11 * goldn + 18) % 30;
125 if (((epact == 25) && (goldn > 11)) || (epact == 24)) {
126 epact++;
127 }
128 return (((((diy + epact) * 6) + 11) % 177) / 22) & 7;
129 }
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146 static int
147 check_range(const xmlNode *date_spec, const char *id, const char *attr,
148 uint32_t value)
149 {
150 int rc = pcmk_rc_ok;
151 const char *range = crm_element_value(date_spec, attr);
152 long long low, high;
153
154 if (range == NULL) {
155 goto bail;
156 }
157
158 if (pcmk__parse_ll_range(range, &low, &high) != pcmk_rc_ok) {
159
160
161
162
163 pcmk__config_err("Ignoring " PCMK_XE_DATE_SPEC
164 " %s attribute %s because '%s' is not a valid range",
165 id, attr, range);
166
167 } else if ((low != -1) && (value < low)) {
168 rc = pcmk_rc_before_range;
169
170 } else if ((high != -1) && (value > high)) {
171 rc = pcmk_rc_after_range;
172 }
173
174 bail:
175 crm_trace("Checked " PCMK_XE_DATE_SPEC " %s %s='%s' for %" PRIu32 ": %s",
176 id, attr, pcmk__s(range, ""), value, pcmk_rc_str(rc));
177 return rc;
178 }
179
180
181
182
183
184
185
186
187
188
189
190
191
192 int
193 pcmk__evaluate_date_spec(const xmlNode *date_spec, const crm_time_t *now)
194 {
195 const char *id = NULL;
196 const char *parent_id = loggable_parent_id(date_spec);
197
198
199 struct range {
200 const char *attr;
201 uint32_t value;
202 } ranges[] = {
203 { PCMK_XA_YEARS, 0U },
204 { PCMK_XA_MONTHS, 0U },
205 { PCMK_XA_MONTHDAYS, 0U },
206 { PCMK_XA_HOURS, 0U },
207 { PCMK_XA_MINUTES, 0U },
208 { PCMK_XA_SECONDS, 0U },
209 { PCMK_XA_YEARDAYS, 0U },
210 { PCMK_XA_WEEKYEARS, 0U },
211 { PCMK_XA_WEEKS, 0U },
212 { PCMK_XA_WEEKDAYS, 0U },
213 { PCMK__XA_MOON, 0U },
214 };
215
216 if ((date_spec == NULL) || (now == NULL)) {
217 return EINVAL;
218 }
219
220
221 id = pcmk__xe_id(date_spec);
222 if (pcmk__str_empty(id)) {
223
224
225
226 pcmk__config_warn(PCMK_XE_DATE_SPEC " subelement of "
227 PCMK_XE_DATE_EXPRESSION " %s has no " PCMK_XA_ID,
228 parent_id);
229 id = "without ID";
230 }
231
232
233 crm_time_get_gregorian(now, &(ranges[0].value), &(ranges[1].value),
234 &(ranges[2].value));
235
236
237 crm_time_get_timeofday(now, &(ranges[3].value), &(ranges[4].value),
238 &(ranges[5].value));
239
240
241 crm_time_get_ordinal(now, &(ranges[0].value), &(ranges[6].value));
242
243
244 crm_time_get_isoweek(now, &(ranges[7].value), &(ranges[8].value),
245 &(ranges[9].value));
246
247
248 ranges[10].value = phase_of_the_moon(now);
249 if (crm_element_value(date_spec, PCMK__XA_MOON) != NULL) {
250 pcmk__config_warn("Support for '" PCMK__XA_MOON "' in "
251 PCMK_XE_DATE_SPEC " elements (such as %s) is "
252 "deprecated and will be removed in a future release "
253 "of Pacemaker", id);
254 }
255
256 for (int i = 0; i < PCMK__NELEM(ranges); ++i) {
257 int rc = check_range(date_spec, id, ranges[i].attr, ranges[i].value);
258
259 if (rc != pcmk_rc_ok) {
260 return rc;
261 }
262 }
263
264
265 return pcmk_rc_ok;
266 }
267
268 #define ADD_COMPONENT(component) do { \
269 int sub_rc = pcmk__add_time_from_xml(*end, component, duration); \
270 if (sub_rc != pcmk_rc_ok) { \
271 \
272 pcmk__config_warn("Ignoring %s in " PCMK_XE_DURATION " %s " \
273 "because it is invalid: %s", \
274 pcmk__time_component_attr(component), id, \
275 pcmk_rc_str(sub_rc)); \
276 rc = sub_rc; \
277 } \
278 } while (0)
279
280
281
282
283
284
285
286
287
288
289
290
291
292 int
293 pcmk__unpack_duration(const xmlNode *duration, const crm_time_t *start,
294 crm_time_t **end)
295 {
296 int rc = pcmk_rc_ok;
297 const char *id = NULL;
298 const char *parent_id = loggable_parent_id(duration);
299
300 if ((start == NULL) || (duration == NULL)
301 || (end == NULL) || (*end != NULL)) {
302 return EINVAL;
303 }
304
305
306 id = pcmk__xe_id(duration);
307 if (pcmk__str_empty(id)) {
308
309
310
311 pcmk__config_warn(PCMK_XE_DURATION " subelement of "
312 PCMK_XE_DATE_EXPRESSION " %s has no " PCMK_XA_ID,
313 parent_id);
314 id = "without ID";
315 }
316
317 *end = pcmk_copy_time(start);
318
319 ADD_COMPONENT(pcmk__time_years);
320 ADD_COMPONENT(pcmk__time_months);
321 ADD_COMPONENT(pcmk__time_weeks);
322 ADD_COMPONENT(pcmk__time_days);
323 ADD_COMPONENT(pcmk__time_hours);
324 ADD_COMPONENT(pcmk__time_minutes);
325 ADD_COMPONENT(pcmk__time_seconds);
326
327 return rc;
328 }
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343 static int
344 evaluate_in_range(const xmlNode *date_expression, const char *id,
345 const crm_time_t *now, crm_time_t *next_change)
346 {
347 crm_time_t *start = NULL;
348 crm_time_t *end = NULL;
349
350 if (pcmk__xe_get_datetime(date_expression, PCMK_XA_START,
351 &start) != pcmk_rc_ok) {
352
353
354
355 pcmk__config_warn("Ignoring " PCMK_XA_START " in "
356 PCMK_XE_DATE_EXPRESSION " %s because it is invalid",
357 id);
358 }
359
360 if (pcmk__xe_get_datetime(date_expression, PCMK_XA_END,
361 &end) != pcmk_rc_ok) {
362
363
364
365 pcmk__config_warn("Ignoring " PCMK_XA_END " in "
366 PCMK_XE_DATE_EXPRESSION " %s because it is invalid",
367 id);
368 }
369
370 if ((start == NULL) && (end == NULL)) {
371
372
373
374
375 pcmk__config_warn("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
376 "passing because in_range requires at least one of "
377 PCMK_XA_START " or " PCMK_XA_END, id);
378 return pcmk_rc_undetermined;
379 }
380
381 if (end == NULL) {
382 xmlNode *duration = pcmk__xe_first_child(date_expression,
383 PCMK_XE_DURATION, NULL, NULL);
384
385 if (duration != NULL) {
386
387
388
389 pcmk__unpack_duration(duration, start, &end);
390 }
391 }
392
393 if ((start != NULL) && (crm_time_compare(now, start) < 0)) {
394 pcmk__set_time_if_earlier(next_change, start);
395 crm_time_free(start);
396 crm_time_free(end);
397 return pcmk_rc_before_range;
398 }
399
400 if (end != NULL) {
401 if (crm_time_compare(now, end) > 0) {
402 crm_time_free(start);
403 crm_time_free(end);
404 return pcmk_rc_after_range;
405 }
406
407
408 if (next_change != NULL) {
409 crm_time_add_seconds(end, 1);
410 pcmk__set_time_if_earlier(next_change, end);
411 }
412 }
413
414 crm_time_free(start);
415 crm_time_free(end);
416 return pcmk_rc_within_range;
417 }
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432 static int
433 evaluate_gt(const xmlNode *date_expression, const char *id,
434 const crm_time_t *now, crm_time_t *next_change)
435 {
436 crm_time_t *start = NULL;
437
438 if (pcmk__xe_get_datetime(date_expression, PCMK_XA_START,
439 &start) != pcmk_rc_ok) {
440
441
442
443 pcmk__config_warn("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
444 "passing because " PCMK_XA_START " is invalid",
445 id);
446 return pcmk_rc_undetermined;
447 }
448
449 if (start == NULL) {
450
451
452
453 pcmk__config_warn("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
454 "passing because " PCMK_VALUE_GT " requires "
455 PCMK_XA_START, id);
456 return pcmk_rc_undetermined;
457 }
458
459 if (crm_time_compare(now, start) > 0) {
460 crm_time_free(start);
461 return pcmk_rc_within_range;
462 }
463
464
465 crm_time_add_seconds(start, 1);
466 pcmk__set_time_if_earlier(next_change, start);
467 crm_time_free(start);
468 return pcmk_rc_before_range;
469 }
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484 static int
485 evaluate_lt(const xmlNode *date_expression, const char *id,
486 const crm_time_t *now, crm_time_t *next_change)
487 {
488 crm_time_t *end = NULL;
489
490 if (pcmk__xe_get_datetime(date_expression, PCMK_XA_END,
491 &end) != pcmk_rc_ok) {
492
493
494
495 pcmk__config_warn("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
496 "passing because " PCMK_XA_END " is invalid", id);
497 return pcmk_rc_undetermined;
498 }
499
500 if (end == NULL) {
501
502
503
504 pcmk__config_warn("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
505 "passing because " PCMK_VALUE_GT " requires "
506 PCMK_XA_END, id);
507 return pcmk_rc_undetermined;
508 }
509
510 if (crm_time_compare(now, end) < 0) {
511 pcmk__set_time_if_earlier(next_change, end);
512 crm_time_free(end);
513 return pcmk_rc_within_range;
514 }
515
516 crm_time_free(end);
517 return pcmk_rc_after_range;
518 }
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534 int
535 pcmk__evaluate_date_expression(const xmlNode *date_expression,
536 const crm_time_t *now, crm_time_t *next_change)
537 {
538 const char *id = NULL;
539 const char *op = NULL;
540 int rc = pcmk_rc_undetermined;
541
542 if ((date_expression == NULL) || (now == NULL)) {
543 return EINVAL;
544 }
545
546
547 id = pcmk__xe_id(date_expression);
548 if (pcmk__str_empty(id)) {
549
550
551
552 pcmk__config_warn(PCMK_XE_DATE_EXPRESSION " element has no "
553 PCMK_XA_ID);
554 id = "without ID";
555 }
556
557 op = crm_element_value(date_expression, PCMK_XA_OPERATION);
558 if (pcmk__str_eq(op, PCMK_VALUE_IN_RANGE,
559 pcmk__str_null_matches|pcmk__str_casei)) {
560 rc = evaluate_in_range(date_expression, id, now, next_change);
561
562 } else if (pcmk__str_eq(op, PCMK_VALUE_DATE_SPEC, pcmk__str_casei)) {
563 xmlNode *date_spec = pcmk__xe_first_child(date_expression,
564 PCMK_XE_DATE_SPEC, NULL,
565 NULL);
566
567 if (date_spec == NULL) {
568
569
570
571 pcmk__config_warn("Treating " PCMK_XE_DATE_EXPRESSION " %s "
572 "as not passing because " PCMK_VALUE_DATE_SPEC
573 " operations require a " PCMK_XE_DATE_SPEC
574 " subelement", id);
575 } else {
576
577 rc = pcmk__evaluate_date_spec(date_spec, now);
578 }
579
580 } else if (pcmk__str_eq(op, PCMK_VALUE_GT, pcmk__str_casei)) {
581 rc = evaluate_gt(date_expression, id, now, next_change);
582
583 } else if (pcmk__str_eq(op, PCMK_VALUE_LT, pcmk__str_casei)) {
584 rc = evaluate_lt(date_expression, id, now, next_change);
585
586 } else {
587
588
589
590 pcmk__config_warn("Treating " PCMK_XE_DATE_EXPRESSION
591 " %s as not passing because '%s' is not a valid "
592 PCMK_XE_OPERATION, id, op);
593 }
594
595 crm_trace(PCMK_XE_DATE_EXPRESSION " %s (%s): %s (%d)",
596 id, op, pcmk_rc_str(rc), rc);
597 return rc;
598 }
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616 static bool
617 process_submatches(const char *string, const char *match,
618 const regmatch_t submatches[], int nmatches,
619 char *expansion, size_t *nbytes)
620 {
621 bool expanded = false;
622 const char *src = string;
623
624 if (nbytes != NULL) {
625 *nbytes = 1;
626 }
627
628 while (*src != '\0') {
629 int submatch = 0;
630 size_t match_len = 0;
631
632 if ((src[0] != '%') || !isdigit(src[1])) {
633
634
635
636 if (expansion != NULL) {
637 *expansion++ = *src;
638 }
639 if (nbytes != NULL) {
640 ++(*nbytes);
641 }
642 ++src;
643 continue;
644 }
645
646 submatch = src[1] - '0';
647 src += 2;
648 expanded = true;
649
650
651 if ((nmatches <= submatch)
652 || (submatches[submatch].rm_so < 0)
653 || (submatches[submatch].rm_eo
654 <= submatches[submatch].rm_so)) {
655 continue;
656 }
657
658 match_len = submatches[submatch].rm_eo - submatches[submatch].rm_so;
659 if (nbytes != NULL) {
660 *nbytes += match_len;
661 }
662 if (expansion != NULL) {
663 memcpy(expansion, match + submatches[submatch].rm_so,
664 match_len);
665 expansion += match_len;
666 }
667 }
668
669 return expanded;
670 }
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685 char *
686 pcmk__replace_submatches(const char *string, const char *match,
687 const regmatch_t submatches[], int nmatches)
688 {
689 size_t nbytes = 0;
690 char *result = NULL;
691
692 if (pcmk__str_empty(string) || pcmk__str_empty(match)) {
693 return NULL;
694 }
695
696
697 if (!process_submatches(string, match, submatches, nmatches, NULL,
698 &nbytes)) {
699 return NULL;
700 }
701
702
703 result = pcmk__assert_alloc(nbytes, sizeof(char));
704
705
706 (void) process_submatches(string, match, submatches, nmatches, result,
707 NULL);
708 return result;
709 }
710
711
712
713
714
715
716
717
718
719
720
721
722
723 enum pcmk__comparison
724 pcmk__parse_comparison(const char *op)
725 {
726 if (pcmk__str_eq(op, PCMK_VALUE_DEFINED, pcmk__str_casei)) {
727 return pcmk__comparison_defined;
728
729 } else if (pcmk__str_eq(op, PCMK_VALUE_NOT_DEFINED, pcmk__str_casei)) {
730 return pcmk__comparison_undefined;
731
732 } else if (pcmk__str_eq(op, PCMK_VALUE_EQ, pcmk__str_casei)) {
733 return pcmk__comparison_eq;
734
735 } else if (pcmk__str_eq(op, PCMK_VALUE_NE, pcmk__str_casei)) {
736 return pcmk__comparison_ne;
737
738 } else if (pcmk__str_eq(op, PCMK_VALUE_LT, pcmk__str_casei)) {
739 return pcmk__comparison_lt;
740
741 } else if (pcmk__str_eq(op, PCMK_VALUE_LTE, pcmk__str_casei)) {
742 return pcmk__comparison_lte;
743
744 } else if (pcmk__str_eq(op, PCMK_VALUE_GT, pcmk__str_casei)) {
745 return pcmk__comparison_gt;
746
747 } else if (pcmk__str_eq(op, PCMK_VALUE_GTE, pcmk__str_casei)) {
748 return pcmk__comparison_gte;
749 }
750
751 return pcmk__comparison_unknown;
752 }
753
754
755
756
757
758
759
760
761
762
763
764
765 enum pcmk__type
766 pcmk__parse_type(const char *type, enum pcmk__comparison op,
767 const char *value1, const char *value2)
768 {
769 if (type == NULL) {
770 switch (op) {
771 case pcmk__comparison_lt:
772 case pcmk__comparison_lte:
773 case pcmk__comparison_gt:
774 case pcmk__comparison_gte:
775 if (((value1 != NULL) && (strchr(value1, '.') != NULL))
776 || ((value2 != NULL) && (strchr(value2, '.') != NULL))) {
777 return pcmk__type_number;
778 }
779 return pcmk__type_integer;
780
781 default:
782 return pcmk__type_string;
783 }
784 }
785
786 if (pcmk__str_eq(type, PCMK_VALUE_STRING, pcmk__str_casei)) {
787 return pcmk__type_string;
788
789 } else if (pcmk__str_eq(type, PCMK_VALUE_INTEGER, pcmk__str_casei)) {
790 return pcmk__type_integer;
791
792 } else if (pcmk__str_eq(type, PCMK_VALUE_NUMBER, pcmk__str_casei)) {
793 return pcmk__type_number;
794
795 } else if (pcmk__str_eq(type, PCMK_VALUE_VERSION, pcmk__str_casei)) {
796 return pcmk__type_version;
797 }
798
799 return pcmk__type_unknown;
800 }
801
802
803
804
805
806
807
808
809
810
811
812
813
814 int
815 pcmk__cmp_by_type(const char *value1, const char *value2, enum pcmk__type type)
816 {
817
818 if (value2 == NULL) {
819 return (value1 == NULL)? 0 : 1;
820 }
821 if (value1 == NULL) {
822 return -1;
823 }
824
825 switch (type) {
826 case pcmk__type_string:
827 return strcasecmp(value1, value2);
828
829 case pcmk__type_integer:
830 {
831 long long integer1;
832 long long integer2;
833
834 if ((pcmk__scan_ll(value1, &integer1, 0LL) != pcmk_rc_ok)
835 || (pcmk__scan_ll(value2, &integer2, 0LL) != pcmk_rc_ok)) {
836 crm_warn("Comparing '%s' and '%s' as strings because "
837 "invalid as integers", value1, value2);
838 return strcasecmp(value1, value2);
839 }
840 return (integer1 < integer2)? -1 : (integer1 > integer2)? 1 : 0;
841 }
842 break;
843
844 case pcmk__type_number:
845 {
846 double num1;
847 double num2;
848
849 if ((pcmk__scan_double(value1, &num1, NULL, NULL) != pcmk_rc_ok)
850 || (pcmk__scan_double(value2, &num2, NULL,
851 NULL) != pcmk_rc_ok)) {
852 crm_warn("Comparing '%s' and '%s' as strings because invalid as "
853 "numbers", value1, value2);
854 return strcasecmp(value1, value2);
855 }
856 return (num1 < num2)? -1 : (num1 > num2)? 1 : 0;
857 }
858 break;
859
860 case pcmk__type_version:
861 return compare_version(value1, value2);
862
863 default:
864 return 0;
865 }
866 }
867
868
869
870
871
872
873
874
875
876 enum pcmk__reference_source
877 pcmk__parse_source(const char *source)
878 {
879 if (pcmk__str_eq(source, PCMK_VALUE_LITERAL,
880 pcmk__str_casei|pcmk__str_null_matches)) {
881 return pcmk__source_literal;
882
883 } else if (pcmk__str_eq(source, PCMK_VALUE_PARAM, pcmk__str_casei)) {
884 return pcmk__source_instance_attrs;
885
886 } else if (pcmk__str_eq(source, PCMK_VALUE_META, pcmk__str_casei)) {
887 return pcmk__source_meta_attrs;
888
889 } else {
890 return pcmk__source_unknown;
891 }
892 }
893
894
895
896
897
898
899
900
901
902 enum pcmk__combine
903 pcmk__parse_combine(const char *combine)
904 {
905 if (pcmk__str_eq(combine, PCMK_VALUE_AND,
906 pcmk__str_null_matches|pcmk__str_casei)) {
907 return pcmk__combine_and;
908
909 } else if (pcmk__str_eq(combine, PCMK_VALUE_OR, pcmk__str_casei)) {
910 return pcmk__combine_or;
911
912 } else {
913 return pcmk__combine_unknown;
914 }
915 }
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931 static int
932 evaluate_attr_comparison(const char *actual, const char *reference,
933 enum pcmk__type type, enum pcmk__comparison comparison)
934 {
935 int cmp = 0;
936
937 switch (comparison) {
938 case pcmk__comparison_defined:
939 return (actual != NULL)? pcmk_rc_ok : pcmk_rc_op_unsatisfied;
940
941 case pcmk__comparison_undefined:
942 return (actual == NULL)? pcmk_rc_ok : pcmk_rc_op_unsatisfied;
943
944 default:
945 break;
946 }
947
948 cmp = pcmk__cmp_by_type(actual, reference, type);
949
950 switch (comparison) {
951 case pcmk__comparison_eq:
952 return (cmp == 0)? pcmk_rc_ok : pcmk_rc_op_unsatisfied;
953
954 case pcmk__comparison_ne:
955 return (cmp != 0)? pcmk_rc_ok : pcmk_rc_op_unsatisfied;
956
957 default:
958 break;
959 }
960
961 if ((actual == NULL) || (reference == NULL)) {
962 return pcmk_rc_op_unsatisfied;
963 }
964
965 switch (comparison) {
966 case pcmk__comparison_lt:
967 return (cmp < 0)? pcmk_rc_ok : pcmk_rc_after_range;
968
969 case pcmk__comparison_lte:
970 return (cmp <= 0)? pcmk_rc_ok : pcmk_rc_after_range;
971
972 case pcmk__comparison_gt:
973 return (cmp > 0)? pcmk_rc_ok : pcmk_rc_before_range;
974
975 case pcmk__comparison_gte:
976 return (cmp >= 0)? pcmk_rc_ok : pcmk_rc_before_range;
977
978 default:
979 return pcmk_rc_op_unsatisfied;
980 }
981 }
982
983
984
985
986
987
988
989
990
991 static const char *
992 value_from_source(const char *value, enum pcmk__reference_source source,
993 const pcmk_rule_input_t *rule_input)
994 {
995 GHashTable *table = NULL;
996
997 if (pcmk__str_empty(value)) {
998
999
1000
1001
1002
1003 return NULL;
1004 }
1005
1006 switch (source) {
1007 case pcmk__source_literal:
1008 return value;
1009
1010 case pcmk__source_instance_attrs:
1011 table = rule_input->rsc_params;
1012 break;
1013
1014 case pcmk__source_meta_attrs:
1015 table = rule_input->rsc_meta;
1016 break;
1017
1018 default:
1019 return NULL;
1020 }
1021
1022 if (table == NULL) {
1023 return NULL;
1024 }
1025 return (const char *) g_hash_table_lookup(table, value);
1026 }
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038 int
1039 pcmk__evaluate_attr_expression(const xmlNode *expression,
1040 const pcmk_rule_input_t *rule_input)
1041 {
1042 const char *id = NULL;
1043 const char *op = NULL;
1044 const char *attr = NULL;
1045 const char *type_s = NULL;
1046 const char *value = NULL;
1047 const char *actual = NULL;
1048 const char *source_s = NULL;
1049 const char *reference = NULL;
1050 char *expanded_attr = NULL;
1051 int rc = pcmk_rc_ok;
1052
1053 enum pcmk__type type = pcmk__type_unknown;
1054 enum pcmk__reference_source source = pcmk__source_unknown;
1055 enum pcmk__comparison comparison = pcmk__comparison_unknown;
1056
1057 if ((expression == NULL) || (rule_input == NULL)) {
1058 return EINVAL;
1059 }
1060
1061
1062 id = pcmk__xe_id(expression);
1063 if (pcmk__str_empty(id)) {
1064
1065
1066
1067 pcmk__config_warn(PCMK_XE_EXPRESSION " element has no " PCMK_XA_ID);
1068 id = "without ID";
1069 }
1070
1071
1072
1073
1074 attr = crm_element_value(expression, PCMK_XA_ATTRIBUTE);
1075 if (attr == NULL) {
1076 pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not passing "
1077 "because " PCMK_XA_ATTRIBUTE " was not specified", id);
1078 return pcmk_rc_unpack_error;
1079 }
1080 expanded_attr = pcmk__replace_submatches(attr, rule_input->rsc_id,
1081 rule_input->rsc_id_submatches,
1082 rule_input->rsc_id_nmatches);
1083 if (expanded_attr != NULL) {
1084 attr = expanded_attr;
1085 }
1086
1087
1088 op = crm_element_value(expression, PCMK_XA_OPERATION);
1089 comparison = pcmk__parse_comparison(op);
1090 if (comparison == pcmk__comparison_unknown) {
1091
1092 if (op == NULL) {
1093 pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not "
1094 "passing because it has no " PCMK_XA_OPERATION,
1095 id);
1096 } else {
1097 pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not "
1098 "passing because '%s' is not a valid "
1099 PCMK_XA_OPERATION, id, op);
1100 }
1101 rc = pcmk_rc_unpack_error;
1102 goto done;
1103 }
1104
1105
1106 source_s = crm_element_value(expression, PCMK_XA_VALUE_SOURCE);
1107 source = pcmk__parse_source(source_s);
1108 if (source == pcmk__source_unknown) {
1109
1110
1111 pcmk__config_warn("Expression %s has invalid " PCMK_XA_VALUE_SOURCE
1112 " value '%s', using default "
1113 "('" PCMK_VALUE_LITERAL "')", id, source_s);
1114 source = pcmk__source_literal;
1115 }
1116
1117
1118 value = crm_element_value(expression, PCMK_XA_VALUE);
1119 switch (comparison) {
1120 case pcmk__comparison_defined:
1121 case pcmk__comparison_undefined:
1122 if (value != NULL) {
1123 pcmk__config_warn("Ignoring " PCMK_XA_VALUE " in "
1124 PCMK_XE_EXPRESSION " %s because it is unused "
1125 "when " PCMK_XA_BOOLEAN_OP " is %s", id, op);
1126 }
1127 break;
1128
1129 default:
1130 if (value == NULL) {
1131 pcmk__config_warn(PCMK_XE_EXPRESSION " %s has no "
1132 PCMK_XA_VALUE, id);
1133 }
1134 break;
1135 }
1136 reference = value_from_source(value, source, rule_input);
1137
1138
1139 if (rule_input->node_attrs != NULL) {
1140 actual = g_hash_table_lookup(rule_input->node_attrs, attr);
1141 }
1142
1143
1144 type_s = crm_element_value(expression, PCMK_XA_TYPE);
1145 type = pcmk__parse_type(type_s, comparison, actual, reference);
1146 if (type == pcmk__type_unknown) {
1147
1148
1149
1150
1151
1152 pcmk__config_warn("Non-empty node attribute values will be treated as "
1153 "equal for " PCMK_XE_EXPRESSION " %s because '%s' "
1154 "is not a valid type", id, type_s);
1155 }
1156
1157 rc = evaluate_attr_comparison(actual, reference, type, comparison);
1158 switch (comparison) {
1159 case pcmk__comparison_defined:
1160 case pcmk__comparison_undefined:
1161 crm_trace(PCMK_XE_EXPRESSION " %s result: %s (for attribute %s %s)",
1162 id, pcmk_rc_str(rc), attr, op);
1163 break;
1164
1165 default:
1166 crm_trace(PCMK_XE_EXPRESSION " %s result: "
1167 "%s (attribute %s %s '%s' via %s source as %s type)",
1168 id, pcmk_rc_str(rc), attr, op, pcmk__s(reference, ""),
1169 pcmk__s(source_s, "default"), pcmk__s(type_s, "default"));
1170 break;
1171 }
1172
1173 done:
1174 free(expanded_attr);
1175 return rc;
1176 }
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188 int
1189 pcmk__evaluate_rsc_expression(const xmlNode *rsc_expression,
1190 const pcmk_rule_input_t *rule_input)
1191 {
1192 const char *id = NULL;
1193 const char *standard = NULL;
1194 const char *provider = NULL;
1195 const char *type = NULL;
1196
1197 if ((rsc_expression == NULL) || (rule_input == NULL)) {
1198 return EINVAL;
1199 }
1200
1201
1202 id = pcmk__xe_id(rsc_expression);
1203 if (pcmk__str_empty(id)) {
1204
1205
1206
1207
1208 pcmk__config_warn(PCMK_XE_RSC_EXPRESSION " has no " PCMK_XA_ID);
1209 id = "without ID";
1210 }
1211
1212
1213 standard = crm_element_value(rsc_expression, PCMK_XA_CLASS);
1214 if ((standard != NULL)
1215 && !pcmk__str_eq(standard, rule_input->rsc_standard, pcmk__str_none)) {
1216 crm_trace(PCMK_XE_RSC_EXPRESSION " %s is unsatisfied because "
1217 "actual standard '%s' doesn't match '%s'",
1218 id, pcmk__s(rule_input->rsc_standard, ""), standard);
1219 return pcmk_rc_op_unsatisfied;
1220 }
1221
1222
1223 provider = crm_element_value(rsc_expression, PCMK_XA_PROVIDER);
1224 if ((provider != NULL)
1225 && !pcmk__str_eq(provider, rule_input->rsc_provider, pcmk__str_none)) {
1226 crm_trace(PCMK_XE_RSC_EXPRESSION " %s is unsatisfied because "
1227 "actual provider '%s' doesn't match '%s'",
1228 id, pcmk__s(rule_input->rsc_provider, ""), provider);
1229 return pcmk_rc_op_unsatisfied;
1230 }
1231
1232
1233 type = crm_element_value(rsc_expression, PCMK_XA_TYPE);
1234 if ((type != NULL)
1235 && !pcmk__str_eq(type, rule_input->rsc_agent, pcmk__str_none)) {
1236 crm_trace(PCMK_XE_RSC_EXPRESSION " %s is unsatisfied because "
1237 "actual agent '%s' doesn't match '%s'",
1238 id, pcmk__s(rule_input->rsc_agent, ""), type);
1239 return pcmk_rc_op_unsatisfied;
1240 }
1241
1242 crm_trace(PCMK_XE_RSC_EXPRESSION " %s is satisfied by %s%s%s:%s",
1243 id, pcmk__s(standard, ""),
1244 ((provider == NULL)? "" : ":"), pcmk__s(provider, ""),
1245 pcmk__s(type, ""));
1246 return pcmk_rc_ok;
1247 }
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259 int
1260 pcmk__evaluate_op_expression(const xmlNode *op_expression,
1261 const pcmk_rule_input_t *rule_input)
1262 {
1263 const char *id = NULL;
1264 const char *name = NULL;
1265 const char *interval_s = NULL;
1266 guint interval_ms = 0U;
1267
1268 if ((op_expression == NULL) || (rule_input == NULL)) {
1269 return EINVAL;
1270 }
1271
1272
1273 id = pcmk__xe_id(op_expression);
1274 if (pcmk__str_empty(id)) {
1275
1276
1277
1278 pcmk__config_warn(PCMK_XE_OP_EXPRESSION " element has no " PCMK_XA_ID);
1279 id = "without ID";
1280 }
1281
1282
1283 name = crm_element_value(op_expression, PCMK_XA_NAME);
1284 if (name == NULL) {
1285 pcmk__config_warn("Treating " PCMK_XE_OP_EXPRESSION " %s as not "
1286 "passing because it has no " PCMK_XA_NAME, id);
1287 return pcmk_rc_unpack_error;
1288 }
1289
1290
1291 interval_s = crm_element_value(op_expression, PCMK_META_INTERVAL);
1292 if (pcmk_parse_interval_spec(interval_s, &interval_ms) != pcmk_rc_ok) {
1293 pcmk__config_warn("Treating " PCMK_XE_OP_EXPRESSION " %s as not "
1294 "passing because '%s' is not a valid interval",
1295 id, interval_s);
1296 return pcmk_rc_unpack_error;
1297 }
1298
1299
1300 if (!pcmk__str_eq(name, rule_input->op_name, pcmk__str_none)) {
1301 crm_trace(PCMK_XE_OP_EXPRESSION " %s is unsatisfied because "
1302 "actual name '%s' doesn't match '%s'",
1303 id, pcmk__s(rule_input->op_name, ""), name);
1304 return pcmk_rc_op_unsatisfied;
1305 }
1306
1307
1308 if ((interval_s != NULL) && (interval_ms != rule_input->op_interval_ms)) {
1309 crm_trace(PCMK_XE_OP_EXPRESSION " %s is unsatisfied because "
1310 "actual interval %s doesn't match %s",
1311 id, pcmk__readable_interval(rule_input->op_interval_ms),
1312 pcmk__readable_interval(interval_ms));
1313 return pcmk_rc_op_unsatisfied;
1314 }
1315
1316 crm_trace(PCMK_XE_OP_EXPRESSION " %s is satisfied (name %s, interval %s)",
1317 id, name, pcmk__readable_interval(rule_input->op_interval_ms));
1318 return pcmk_rc_ok;
1319 }
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333 int
1334 pcmk__evaluate_condition(xmlNode *condition,
1335 const pcmk_rule_input_t *rule_input,
1336 crm_time_t *next_change)
1337 {
1338
1339 if ((condition == NULL) || (rule_input == NULL)) {
1340 return EINVAL;
1341 }
1342
1343 switch (pcmk__condition_type(condition)) {
1344 case pcmk__condition_rule:
1345 return pcmk_evaluate_rule(condition, rule_input, next_change);
1346
1347 case pcmk__condition_attribute:
1348 case pcmk__condition_location:
1349 return pcmk__evaluate_attr_expression(condition, rule_input);
1350
1351 case pcmk__condition_datetime:
1352 {
1353 int rc = pcmk__evaluate_date_expression(condition,
1354 rule_input->now,
1355 next_change);
1356
1357 return (rc == pcmk_rc_within_range)? pcmk_rc_ok : rc;
1358 }
1359
1360 case pcmk__condition_resource:
1361 return pcmk__evaluate_rsc_expression(condition, rule_input);
1362
1363 case pcmk__condition_operation:
1364 return pcmk__evaluate_op_expression(condition, rule_input);
1365
1366 default:
1367 pcmk__config_err("Treating rule condition %s as not passing "
1368 "because %s is not a valid condition type",
1369 pcmk__s(pcmk__xe_id(condition), "without ID"),
1370 (const char *) condition->name);
1371 return pcmk_rc_unpack_error;
1372 }
1373 }
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385 int
1386 pcmk_evaluate_rule(xmlNode *rule, const pcmk_rule_input_t *rule_input,
1387 crm_time_t *next_change)
1388 {
1389 bool empty = true;
1390 int rc = pcmk_rc_ok;
1391 const char *id = NULL;
1392 const char *value = NULL;
1393 enum pcmk__combine combine = pcmk__combine_unknown;
1394
1395 if ((rule == NULL) || (rule_input == NULL)) {
1396 return EINVAL;
1397 }
1398
1399 rule = expand_idref(rule, NULL);
1400 if (rule == NULL) {
1401
1402 return pcmk_rc_unpack_error;
1403 }
1404
1405
1406 id = pcmk__xe_id(rule);
1407 if (pcmk__str_empty(id)) {
1408
1409
1410
1411 pcmk__config_warn(PCMK_XE_RULE " has no " PCMK_XA_ID);
1412 id = "without ID";
1413 }
1414
1415 value = crm_element_value(rule, PCMK_XA_BOOLEAN_OP);
1416 combine = pcmk__parse_combine(value);
1417 switch (combine) {
1418 case pcmk__combine_and:
1419
1420 break;
1421
1422 case pcmk__combine_or:
1423
1424 rc = pcmk_rc_op_unsatisfied;
1425 break;
1426
1427 default:
1428
1429
1430
1431 pcmk__config_warn("Rule %s has invalid " PCMK_XA_BOOLEAN_OP
1432 " value '%s', using default '" PCMK_VALUE_AND "'",
1433 pcmk__xe_id(rule), value);
1434 combine = pcmk__combine_and;
1435 break;
1436 }
1437
1438
1439 for (xmlNode *condition = pcmk__xe_first_child(rule, NULL, NULL, NULL);
1440 condition != NULL; condition = pcmk__xe_next(condition)) {
1441
1442 empty = false;
1443 if (pcmk__evaluate_condition(condition, rule_input,
1444 next_change) == pcmk_rc_ok) {
1445 if (combine == pcmk__combine_or) {
1446 rc = pcmk_rc_ok;
1447 break;
1448 }
1449 } else if (combine == pcmk__combine_and) {
1450 rc = pcmk_rc_op_unsatisfied;
1451 break;
1452 }
1453 }
1454
1455 if (empty) {
1456
1457
1458
1459
1460 pcmk__config_warn("Ignoring rule %s because it contains no conditions",
1461 id);
1462 }
1463
1464 crm_trace("Rule %s is %ssatisfied", id, ((rc == pcmk_rc_ok)? "" : "not "));
1465 return rc;
1466 }
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486 int
1487 pcmk__evaluate_rules(xmlNode *xml, const pcmk_rule_input_t *rule_input,
1488 crm_time_t *next_change)
1489 {
1490
1491 int rc = pcmk_rc_ok;
1492 bool have_rule = false;
1493
1494 for (xmlNode *rule = pcmk__xe_first_child(xml, PCMK_XE_RULE, NULL, NULL);
1495 rule != NULL; rule = pcmk__xe_next_same(rule)) {
1496
1497 if (have_rule) {
1498 pcmk__warn_once(pcmk__wo_multiple_rules,
1499 "Support for multiple top-level rules is "
1500 "deprecated (replace with a single rule containing "
1501 "the existing rules with " PCMK_XA_BOOLEAN_OP
1502 "set to " PCMK_VALUE_OR " instead)");
1503 } else {
1504 have_rule = true;
1505 }
1506
1507 rc = pcmk_evaluate_rule(rule, rule_input, next_change);
1508 if (rc == pcmk_rc_ok) {
1509 break;
1510 }
1511 }
1512 return rc;
1513 }