diff --git a/cts/cli/regression.rules.exp b/cts/cli/regression.rules.exp index e667be28a9..4363c50ab7 100644 --- a/cts/cli/regression.rules.exp +++ b/cts/cli/regression.rules.exp @@ -1,161 +1,161 @@ Created new pacemaker configuration Setting up shadow instance A new shadow instance was created. To begin using it paste the following into your shell: CIB_shadow=cts-cli ; export CIB_shadow =#=#=#= Begin test: Try to check a rule that doesn't exist =#=#=#= unpack_resources error: Resource start-up disabled since no STONITH resources have been defined unpack_resources error: Either configure some or disable STONITH with the stonith-enabled option unpack_resources error: NOTE: Clusters with shared data need STONITH to ensure data integrity -No rule found with ID=blahblah containing a date_expression +No rule found with ID=blahblah Error checking rule: No such device or address =#=#=#= Current cib after: Try to check a rule that doesn't exist =#=#=#= =#=#=#= End test: Try to check a rule that doesn't exist - No such object (105) =#=#=#= * Passed: crm_rule - Try to check a rule that doesn't exist =#=#=#= Begin test: Try to check a rule that has no date_expression =#=#=#= unpack_resources error: Resource start-up disabled since no STONITH resources have been defined unpack_resources error: Either configure some or disable STONITH with the stonith-enabled option unpack_resources error: NOTE: Clusters with shared data need STONITH to ensure data integrity -No rule found with ID=no-date-expression containing a date_expression +No rule found with ID=no-date-expression Error checking rule: No such device or address =#=#=#= Current cib after: Try to check a rule that has no date_expression =#=#=#= =#=#=#= End test: Try to check a rule that has no date_expression - No such object (105) =#=#=#= * Passed: crm_rule - Try to check a rule that has no date_expression =#=#=#= Begin test: Verify basic rule is expired =#=#=#= unpack_resources error: Resource start-up disabled since no STONITH resources have been defined unpack_resources error: Either configure some or disable STONITH with the stonith-enabled option unpack_resources error: NOTE: Clusters with shared data need STONITH to ensure data integrity Rule cli-prefer-rule-dummy-expired is expired =#=#=#= Current cib after: Verify basic rule is expired =#=#=#= =#=#=#= End test: Verify basic rule is expired - Requested item has expired (110) =#=#=#= * Passed: crm_rule - Verify basic rule is expired =#=#=#= Begin test: Verify basic rule worked in the past =#=#=#= unpack_resources error: Resource start-up disabled since no STONITH resources have been defined unpack_resources error: Either configure some or disable STONITH with the stonith-enabled option unpack_resources error: NOTE: Clusters with shared data need STONITH to ensure data integrity Rule cli-prefer-rule-dummy-expired is still in effect =#=#=#= Current cib after: Verify basic rule worked in the past =#=#=#= =#=#=#= End test: Verify basic rule worked in the past - OK (0) =#=#=#= * Passed: crm_rule - Verify basic rule worked in the past =#=#=#= Begin test: Verify basic rule is not yet in effect =#=#=#= unpack_resources error: Resource start-up disabled since no STONITH resources have been defined unpack_resources error: Either configure some or disable STONITH with the stonith-enabled option unpack_resources error: NOTE: Clusters with shared data need STONITH to ensure data integrity Rule cli-prefer-rule-dummy-not-yet has not yet taken effect =#=#=#= Current cib after: Verify basic rule is not yet in effect =#=#=#= =#=#=#= End test: Verify basic rule is not yet in effect - Requested item is not yet in effect (111) =#=#=#= * Passed: crm_rule - Verify basic rule is not yet in effect diff --git a/tools/crm_rule.c b/tools/crm_rule.c index 19a8c3badc..02ce8ac366 100644 --- a/tools/crm_rule.c +++ b/tools/crm_rule.c @@ -1,314 +1,345 @@ /* * Copyright 2019-2020 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include enum crm_rule_mode { crm_rule_mode_none, crm_rule_mode_check } rule_mode = crm_rule_mode_none; static int crm_rule_check(pe_working_set_t *data_set, const char *rule_id, crm_time_t *effective_date); static pcmk__cli_option_t long_options[] = { // long option, argument type, storage, short option, description, flags { "help", no_argument, NULL, '?', "\tThis text", pcmk__option_default }, { "version", no_argument, NULL, '$', "\tVersion information", pcmk__option_default }, { "verbose", no_argument, NULL, 'V', "\tIncrease debug output", pcmk__option_default }, { "-spacer-", no_argument, NULL, '-', "\nModes (mutually exclusive):", pcmk__option_default }, { "check", no_argument, NULL, 'c', "\tCheck whether a rule is in effect", pcmk__option_default }, { "-spacer-", no_argument, NULL, '-', "\nAdditional options:", pcmk__option_default }, { "date", required_argument, NULL, 'd', "Whether the rule is in effect on a given date", pcmk__option_default }, { "rule", required_argument, NULL, 'r', "The ID of the rule to check", pcmk__option_default }, { "-spacer-", no_argument, NULL, '-', "\nData:", pcmk__option_default }, { "xml-text", required_argument, NULL, 'X', "Use argument for XML (or stdin if '-')", pcmk__option_default }, { "-spacer-", no_argument, NULL, '-', "\n\nThis tool is currently experimental.", pcmk__option_paragraph }, { "-spacer-", no_argument, NULL, '-', "The interface, behavior, and output may change with any version of " "pacemaker.", pcmk__option_paragraph }, { 0, 0, 0, 0 } }; static int crm_rule_check(pe_working_set_t *data_set, const char *rule_id, crm_time_t *effective_date) { xmlNode *cib_constraints = NULL; xmlNode *match = NULL; xmlXPathObjectPtr xpathObj = NULL; pe_eval_date_result_t result; char *xpath = NULL; int rc = pcmk_ok; int max = 0; /* Rules are under the constraints node in the XML, so first find that. */ cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, data_set->input); - /* Get all rules matching the given ID, which also have only a single date_expression - * child whose operation is not 'date_spec'. This is fairly limited, but it's hard - * to check expressions more complicated than that. + /* Get all rules matching the given ID which are also simple enough for us to check. + * For the moment, these rules must only have a single date_expression child and: + * - Do not have a date_spec operation + * + * We do this in steps to provide better error messages. First, check that there's + * any rule with the given ID. */ - xpath = crm_strdup_printf("//rule[@id='%s']/date_expression[@operation!='date_spec']", rule_id); + xpath = crm_strdup_printf("//rule[@id='%s']", rule_id); xpathObj = xpath_search(cib_constraints, xpath); - max = numXpathResults(xpathObj); + if (max == 0) { - CMD_ERR("No rule found with ID=%s containing a date_expression", rule_id); + CMD_ERR("No rule found with ID=%s", rule_id); rc = -ENXIO; goto bail; } else if (max > 1) { - CMD_ERR("More than one date_expression in %s is not supported", rule_id); + CMD_ERR("More than one rule with ID=%s found", rule_id); + rc = -ENXIO; + goto bail; + } + + free(xpath); + freeXpathObject(xpathObj); + + /* Next, make sure it has exactly one date_expression. */ + xpath = crm_strdup_printf("//rule[@id='%s']//date_expression", rule_id); + xpathObj = xpath_search(cib_constraints, xpath); + max = numXpathResults(xpathObj); + + if (max != 1) { + CMD_ERR("Can't check rule %s because it has more than one date_expression", rule_id); rc = -EOPNOTSUPP; goto bail; } + free(xpath); + freeXpathObject(xpathObj); + + /* Then, check that it's something we actually support. */ + xpath = crm_strdup_printf("//rule[@id='%s']//date_expression[@operation!='date_spec']", rule_id); + xpathObj = xpath_search(cib_constraints, xpath); + max = numXpathResults(xpathObj); + + if (max == 0) { + CMD_ERR("Rule must not use date_spec"); + rc = -ENXIO; + goto bail; + } + match = getXpathResult(xpathObj, 0); /* We should have ensured both of these pass with the xpath query above, but * double checking can't hurt. */ CRM_ASSERT(match != NULL); CRM_ASSERT(find_expression_type(match) == time_expr); result = pe_eval_date_expression(match, effective_date, NULL); if (result == pe_date_within_range) { printf("Rule %s is still in effect\n", rule_id); rc = 0; } else if (result == pe_date_after_range) { printf("Rule %s is expired\n", rule_id); rc = 1; } else if (result == pe_date_before_range) { printf("Rule %s has not yet taken effect\n", rule_id); rc = 2; } else { printf("Could not determine whether rule %s is expired\n", rule_id); rc = 3; } bail: free(xpath); freeXpathObject(xpathObj); return rc; } int main(int argc, char **argv) { cib_t *cib_conn = NULL; pe_working_set_t *data_set = NULL; int flag = 0; int option_index = 0; char *rule_id = NULL; crm_time_t *rule_date = NULL; xmlNode *input = NULL; char *input_xml = NULL; int rc = pcmk_ok; crm_exit_t exit_code = CRM_EX_OK; crm_log_cli_init("crm_rule"); pcmk__set_cli_options(NULL, "[options]", long_options, "evaluate rules from the Pacemaker configuration"); while (flag >= 0) { flag = pcmk__next_cli_option(argc, argv, &option_index, NULL); switch (flag) { case -1: break; case 'V': crm_bump_log_level(argc, argv); break; case '$': case '?': pcmk__cli_help(flag, CRM_EX_OK); break; case 'c': rule_mode = crm_rule_mode_check; break; case 'd': rule_date = crm_time_new(optarg); if (rule_date == NULL) { exit_code = CRM_EX_DATAERR; goto bail; } break; case 'X': input_xml = optarg; break; case 'r': rule_id = strdup(optarg); break; default: pcmk__cli_help(flag, CRM_EX_OK); break; } } /* Check command line arguments before opening a connection to * the CIB manager or doing anything else important. */ if (rule_mode == crm_rule_mode_check) { if (rule_id == NULL) { CMD_ERR("--check requires use of --rule=\n"); pcmk__cli_help(flag, CRM_EX_USAGE); } } /* Set up some defaults. */ if (rule_date == NULL) { rule_date = crm_time_new(NULL); } /* Where does the XML come from? If one of various command line options were * given, use those. Otherwise, connect to the CIB and use that. */ if (safe_str_eq(input_xml, "-")) { input = stdin2xml(); if (input == NULL) { fprintf(stderr, "Couldn't parse input from STDIN\n"); exit_code = CRM_EX_DATAERR; goto bail; } } else if (input_xml != NULL) { input = string2xml(input_xml); if (input == NULL) { fprintf(stderr, "Couldn't parse input string: %s\n", input_xml); exit_code = CRM_EX_DATAERR; goto bail; } } else { /* Establish a connection to the CIB manager */ cib_conn = cib_new(); rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command); if (rc != pcmk_ok) { CMD_ERR("Error connecting to the CIB manager: %s", pcmk_strerror(rc)); exit_code = crm_errno2exit(rc); goto bail; } } /* Populate working set from CIB query */ if (input == NULL) { rc = cib_conn->cmds->query(cib_conn, NULL, &input, cib_scope_local | cib_sync_call); if (rc != pcmk_ok) { exit_code = crm_errno2exit(rc); goto bail; } } /* Populate the working set instance */ data_set = pe_new_working_set(); if (data_set == NULL) { exit_code = crm_errno2exit(ENOMEM); goto bail; } set_bit(data_set->flags, pe_flag_no_counts); set_bit(data_set->flags, pe_flag_no_compat); data_set->input = input; data_set->now = rule_date; /* Unpack everything. */ cluster_status(data_set); /* Now do whichever operation mode was asked for. There's only one at the * moment so this looks a little silly, but I expect there will be more * modes in the future. */ switch(rule_mode) { case crm_rule_mode_check: rc = crm_rule_check(data_set, rule_id, rule_date); if (rc < 0) { CMD_ERR("Error checking rule: %s", pcmk_strerror(rc)); exit_code = crm_errno2exit(rc); } else if (rc == 1) { exit_code = CRM_EX_EXPIRED; } else if (rc == 2) { exit_code = CRM_EX_NOT_YET_IN_EFFECT; } else if (rc == 3) { exit_code = CRM_EX_INDETERMINATE; } else { exit_code = rc; } break; default: break; } bail: if (cib_conn != NULL) { cib_conn->cmds->signoff(cib_conn); cib_delete(cib_conn); } pe_free_working_set(data_set); crm_exit(exit_code); }