You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
heartbeat/lib/crm/pengine/rules.c

621 lines
15 KiB

/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <lha_internal.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <glib.h>
#include <crm/pengine/rules.h>
ha_time_t *parse_xml_duration(ha_time_t *start, crm_data_t *duration_spec);
gboolean test_date_expression(crm_data_t *time_expr, ha_time_t *now);
gboolean cron_range_satisfied(ha_time_t *now, crm_data_t *cron_spec);
gboolean test_attr_expression(
crm_data_t *expr, GHashTable *hash, ha_time_t *now);
gboolean test_role_expression(
crm_data_t *expr, enum rsc_role_e role, ha_time_t *now);
gboolean
test_ruleset(crm_data_t *ruleset, GHashTable *node_hash, ha_time_t *now)
{
gboolean ruleset_default = TRUE;
xml_child_iter_filter(
ruleset, rule, XML_TAG_RULE,
ruleset_default = FALSE;
if(test_rule(rule, node_hash, RSC_ROLE_UNKNOWN, now)) {
return TRUE;
}
);
return ruleset_default;
}
gboolean
test_rule(crm_data_t *rule, GHashTable *node_hash, enum rsc_role_e role,
ha_time_t *now)
{
gboolean test = TRUE;
gboolean empty = TRUE;
gboolean passed = TRUE;
gboolean do_and = TRUE;
const char *value = crm_element_value(rule, "boolean_op");
if(safe_str_eq(value, "or")) {
do_and = FALSE;
passed = FALSE;
}
crm_debug_2("Testing rule %s", ID(rule));
xml_child_iter(
rule, expr,
test = test_expression(expr, node_hash, role, now);
empty = FALSE;
if(test && do_and == FALSE) {
crm_debug_3("Expression %s/%s passed",
ID(rule), ID(expr));
return TRUE;
} else if(test == FALSE && do_and) {
crm_debug_3("Expression %s/%s failed",
ID(rule), ID(expr));
return FALSE;
}
);
if(empty) {
crm_err("Invalid Rule %s: rules must contain at least one expression", ID(rule));
}
crm_debug_2("Rule %s %s", ID(rule), passed?"passed":"failed");
return passed;
}
gboolean
test_expression(crm_data_t *expr, GHashTable *node_hash, enum rsc_role_e role,
ha_time_t *now)
{
gboolean accept = FALSE;
const char *uname = NULL;
switch(find_expression_type(expr)) {
case nested_rule:
accept = test_rule(expr, node_hash, role, now);
break;
case attr_expr:
case loc_expr:
/* these expressions can never succeed if there is
* no node to compare with
*/
if(node_hash != NULL) {
accept = test_attr_expression(expr, node_hash, now);
}
break;
case time_expr:
accept = test_date_expression(expr, now);
break;
case role_expr:
accept = test_role_expression(expr, role, now);
break;
default:
CRM_CHECK(FALSE /* bad type */, return FALSE);
accept = FALSE;
}
if(node_hash) {
uname = g_hash_table_lookup(node_hash, "#uname");
}
crm_debug_2("Expression %s %s on %s",
ID(expr), accept?"passed":"failed",
uname?uname:"all ndoes");
return accept;
}
enum expression_type
find_expression_type(crm_data_t *expr)
{
const char *tag = NULL;
const char *attr = NULL;
attr = crm_element_value(expr, XML_EXPR_ATTR_ATTRIBUTE);
tag = crm_element_name(expr);
if(safe_str_eq(tag, "date_expression")) {
return time_expr;
} else if(safe_str_eq(tag, XML_TAG_RULE)) {
return nested_rule;
} else if(safe_str_neq(tag, "expression")) {
return not_expr;
} else if(safe_str_eq(attr, "#uname") || safe_str_eq(attr, "#id")) {
return loc_expr;
} else if(safe_str_eq(attr, "#role")) {
return role_expr;
}
return attr_expr;
}
gboolean
test_role_expression(
crm_data_t *expr, enum rsc_role_e role, ha_time_t *now)
{
gboolean accept = FALSE;
const char *op = NULL;
const char *value = NULL;
if(role == RSC_ROLE_UNKNOWN) {
return accept;
}
value = crm_element_value(expr, XML_EXPR_ATTR_VALUE);
op = crm_element_value(expr, XML_EXPR_ATTR_OPERATION);
if(safe_str_eq(op, "defined")) {
if(role > RSC_ROLE_STARTED) {
accept = TRUE;
}
} else if(safe_str_eq(op, "not_defined")) {
if(role < RSC_ROLE_SLAVE && role > RSC_ROLE_UNKNOWN) {
accept = TRUE;
}
} else if(safe_str_eq(op, "eq")) {
if(text2role(value) == role) {
accept = TRUE;
}
} else if(safe_str_eq(op, "ne")) {
/* we will only test "ne" wtih master/slave roles style */
if(role < RSC_ROLE_SLAVE && role > RSC_ROLE_UNKNOWN) {
accept = FALSE;
} else if(text2role(value) != role) {
accept = TRUE;
}
}
return accept;
}
gboolean
test_attr_expression(crm_data_t *expr, GHashTable *hash, ha_time_t *now)
{
gboolean accept = FALSE;
int cmp = 0;
const char *h_val = NULL;
const char *op = NULL;
const char *type = NULL;
const char *attr = NULL;
const char *value = NULL;
attr = crm_element_value(expr, XML_EXPR_ATTR_ATTRIBUTE);
op = crm_element_value(expr, XML_EXPR_ATTR_OPERATION);
value = crm_element_value(expr, XML_EXPR_ATTR_VALUE);
type = crm_element_value(expr, XML_EXPR_ATTR_TYPE);
if(attr == NULL || op == NULL) {
pe_err("Invlaid attribute or operation in expression"
" (\'%s\' \'%s\' \'%s\')",
crm_str(attr), crm_str(op), crm_str(value));
return FALSE;
}
if(hash != NULL) {
h_val = (const char*)g_hash_table_lookup(hash, attr);
}
if(value != NULL && h_val != NULL) {
if(type == NULL || (safe_str_eq(type, "string"))) {
cmp = strcasecmp(h_val, value);
} else if(safe_str_eq(type, "number")) {
int h_val_f = crm_parse_int(h_val, NULL);
int value_f = crm_parse_int(value, NULL);
if(h_val_f < value_f) {
cmp = -1;
} else if(h_val_f > value_f) {
cmp = 1;
} else {
cmp = 0;
}
} else if(safe_str_eq(type, "version")) {
cmp = compare_version(h_val, value);
}
} else if(value == NULL && h_val == NULL) {
cmp = 0;
} else if(value == NULL) {
cmp = 1;
} else {
cmp = -1;
}
if(safe_str_eq(op, "defined")) {
if(h_val != NULL) { accept = TRUE; }
} else if(safe_str_eq(op, "not_defined")) {
if(h_val == NULL) { accept = TRUE; }
} else if(safe_str_eq(op, "eq")) {
if((h_val == value) || cmp == 0) {
accept = TRUE;
}
} else if(safe_str_eq(op, "ne")) {
if((h_val == NULL && value != NULL)
|| (h_val != NULL && value == NULL)
|| cmp != 0) {
accept = TRUE;
}
} else if(value == NULL || h_val == NULL) {
/* the comparision is meaningless from this point on */
accept = FALSE;
} else if(safe_str_eq(op, "lt")) {
if(cmp < 0) { accept = TRUE; }
} else if(safe_str_eq(op, "lte")) {
if(cmp <= 0) { accept = TRUE; }
} else if(safe_str_eq(op, "gt")) {
if(cmp > 0) { accept = TRUE; }
} else if(safe_str_eq(op, "gte")) {
if(cmp >= 0) { accept = TRUE; }
}
return accept;
}
/* As per the nethack rules:
*
* moon period = 29.53058 days ~= 30, year = 365.2422 days
* days moon phase advances on first day of year compared to preceding year
* = 365.2422 - 12*29.53058 ~= 11
* years in Metonic cycle (time until same phases fall on the same days of
* the month) = 18.6 ~= 19
* moon phase on first day of year (epact) ~= (11*(year%19) + 29) % 30
* (29 as initial condition)
* current phase in days = first day phase + days elapsed in year
* 6 moons ~= 177 days
* 177 ~= 8 reported phases * 22
* + 11/22 for rounding
*
* 0-7, with 0: new, 4: full
*/
static int
phase_of_the_moon(ha_time_t *now)
{
int epact, diy, goldn;
diy = now->yeardays;
goldn = (now->years % 19) + 1;
epact = (11 * goldn + 18) % 30;
if ((epact == 25 && goldn > 11) || epact == 24)
epact++;
return( (((((diy + epact) * 6) + 11) % 177) / 22) & 7 );
}
#define cron_check(xml_field, time_field) \
value = crm_element_value(cron_spec, xml_field); \
if(value != NULL) { \
gboolean pass = TRUE; \
decodeNVpair(value, '-', &value_low, &value_high); \
if(value_low == NULL) { \
value_low = crm_strdup(value); \
} \
value_low_i = crm_parse_int(value_low, "0"); \
value_high_i = crm_parse_int(value_high, "-1"); \
if(value_high_i < 0) { \
if(value_low_i != time_field) { \
pass = FALSE; \
} \
} else if(value_low_i > time_field) { \
pass = FALSE; \
} else if(value_high_i < time_field) { \
pass = FALSE; \
} \
crm_free(value_low); \
crm_free(value_high); \
if(pass == FALSE) { \
crm_debug("Condition '%s' in %s: failed", value, xml_field); \
return pass; \
} \
crm_debug("Condition '%s' in %s: passed", value, xml_field); \
}
gboolean
cron_range_satisfied(ha_time_t *now, crm_data_t *cron_spec)
{
const char *value = NULL;
char *value_low = NULL;
char *value_high = NULL;
int value_low_i = 0;
int value_high_i = 0;
cron_check("seconds", now->seconds);
cron_check("minutes", now->minutes);
cron_check("hours", now->hours);
cron_check("monthdays", now->days);
cron_check("weekdays", now->weekdays);
cron_check("yeardays", now->yeardays);
cron_check("weeks", now->weeks);
cron_check("months", now->months);
cron_check("years", now->years);
cron_check("weekyears", now->weekyears);
cron_check("moon", phase_of_the_moon(now));
return TRUE;
}
#define update_field(xml_field, time_fn) \
value = crm_element_value(duration_spec, xml_field); \
if(value != NULL) { \
int value_i = crm_parse_int(value, "0"); \
time_fn(end, value_i); \
}
ha_time_t *
parse_xml_duration(ha_time_t *start, crm_data_t *duration_spec)
{
ha_time_t *end = NULL;
const char *value = NULL;
end = new_ha_date(FALSE);
ha_set_time(end, start, TRUE);
update_field("years", add_years);
update_field("months", add_months);
update_field("weeks", add_weeks);
update_field("days", add_days);
update_field("hours", add_hours);
update_field("minutes", add_minutes);
update_field("seconds", add_seconds);
return end;
}
gboolean
test_date_expression(crm_data_t *time_expr, ha_time_t *now)
{
ha_time_t *start = NULL;
ha_time_t *end = NULL;
const char *value = NULL;
char *value_copy = NULL;
char *value_copy_start = NULL;
const char *op = crm_element_value(time_expr, "operation");
crm_data_t *duration_spec = NULL;
crm_data_t *date_spec = NULL;
gboolean passed = FALSE;
crm_debug_2("Testing expression: %s", ID(time_expr));
duration_spec = cl_get_struct(time_expr, "duration");
date_spec = cl_get_struct(time_expr, "date_spec");
value = crm_element_value(time_expr, "start");
if(value != NULL) {
value_copy = crm_strdup(value);
value_copy_start = value_copy;
start = parse_date(&value_copy);
crm_free(value_copy_start);
}
value = crm_element_value(time_expr, "end");
if(value != NULL) {
value_copy = crm_strdup(value);
value_copy_start = value_copy;
end = parse_date(&value_copy);
crm_free(value_copy_start);
}
if(start != NULL && end == NULL) {
end = parse_xml_duration(start, duration_spec);
}
if(op == NULL) {
op = "in_range";
}
if(safe_str_eq(op, "date_spec") || safe_str_eq(op, "in_range")) {
if(start != NULL && compare_date(start, now) > 0) {
passed = FALSE;
} else if(end != NULL && compare_date(end, now) < 0) {
passed = FALSE;
} else if(safe_str_eq(op, "in_range")) {
passed = TRUE;
} else {
passed = cron_range_satisfied(now, date_spec);
}
} else if(safe_str_eq(op, "gt") && compare_date(start, now) < 0) {
passed = TRUE;
} else if(safe_str_eq(op, "lt") && compare_date(end, now) > 0) {
passed = TRUE;
} else if(safe_str_eq(op, "eq") && compare_date(start, now) == 0) {
passed = TRUE;
} else if(safe_str_eq(op, "neq") && compare_date(start, now) != 0) {
passed = TRUE;
}
free_ha_date(start);
free_ha_date(end);
return passed;
}
typedef struct sorted_set_s
{
const char *name;
const char *special_name;
int score;
crm_data_t *attr_set;
GHashTable *node_hash;
GHashTable *hash;
ha_time_t *now;
} sorted_set_t;
static gint
sort_pairs(gconstpointer a, gconstpointer b)
{
const sorted_set_t *pair_a = a;
const sorted_set_t *pair_b = b;
if(a == NULL && b == NULL) {
return 0;
} else if(a == NULL) {
return 1;
} else if(b == NULL) {
return -1;
}
if(safe_str_eq(pair_a->name, pair_a->special_name)) {
return -1;
} else if(safe_str_eq(pair_b->name, pair_a->special_name)) {
return 1;
}
if(pair_a->score < pair_b->score) {
return 1;
} else if(pair_a->score > pair_b->score) {
return -1;
}
return 0;
}
static void
populate_hash(crm_data_t *nvpair_list, GHashTable *hash)
{
const char *name = NULL;
const char *value = NULL;
xml_child_iter_filter(
nvpair_list, an_attr, XML_CIB_TAG_NVPAIR,
name = crm_element_value(an_attr, XML_NVPAIR_ATTR_NAME);
crm_debug_4("Setting attribute: %s", name);
value = crm_element_value(
an_attr, XML_NVPAIR_ATTR_VALUE);
if(name == NULL || value == NULL) {
continue;
} else if(safe_str_eq(value, "#default")) {
continue;
} else if(g_hash_table_lookup(hash, name) == NULL) {
g_hash_table_insert(
hash, crm_strdup(name), crm_strdup(value));
}
);
}
static void
unpack_attr_set(gpointer data, gpointer user_data)
{
sorted_set_t *pair = data;
sorted_set_t *unpack_data = user_data;
crm_data_t *attributes = NULL;
if(test_ruleset(pair->attr_set,
unpack_data->node_hash, unpack_data->now) == FALSE) {
return;
}
crm_debug_3("Adding attributes from %s", pair->name);
attributes = cl_get_struct(pair->attr_set, XML_TAG_ATTRS);
populate_hash(attributes, unpack_data->hash);
}
static void
free_pair(gpointer data, gpointer user_data)
{
sorted_set_t *pair = data;
crm_free(pair);
}
void
unpack_instance_attributes(
crm_data_t *xml_obj, const char *set_name, GHashTable *node_hash,
GHashTable *hash, const char *always_first, ha_time_t *now)
{
GListPtr sorted = NULL;
const char *score = NULL;
sorted_set_t *pair = NULL;
if(xml_obj == NULL) {
crm_debug_4("No instance attributes");
return;
}
crm_debug_4("Checking for attributes");
xml_child_iter_filter(
xml_obj, attr_set, set_name,
pair = NULL;
crm_malloc0(pair, sizeof(sorted_set_t));
pair->name = ID(attr_set);
pair->special_name = always_first;
pair->attr_set = attr_set;
score = crm_element_value(attr_set, XML_RULE_ATTR_SCORE);
pair->score = char2score(score);
sorted = g_list_prepend(sorted, pair);
);
if(pair != NULL) {
pair->hash = hash;
pair->node_hash = node_hash;
pair->now = now;
}
sorted = g_list_sort(sorted, sort_pairs);
g_list_foreach(sorted, unpack_attr_set, pair);
g_list_foreach(sorted, free_pair, NULL);
g_list_free(sorted);
}