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