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