diff --git a/tools/crm_rule.c b/tools/crm_rule.c index 98b0993a84..05bb534595 100644 --- a/tools/crm_rule.c +++ b/tools/crm_rule.c @@ -1,358 +1,358 @@ /* - * Copyright 2019-2021 the Pacemaker project contributors + * Copyright 2019-2022 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 #include #include #include #define SUMMARY "evaluate rules from the Pacemaker configuration" +GError *error = NULL; + enum crm_rule_mode { crm_rule_mode_none, crm_rule_mode_check }; struct { char *date; char *input_xml; enum crm_rule_mode mode; char *rule; } options = { .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 gboolean mode_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); static GOptionEntry mode_entries[] = { { "check", 'c', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, mode_cb, "Check whether a rule is in effect", NULL }, { NULL } }; static GOptionEntry data_entries[] = { { "xml-text", 'X', 0, G_OPTION_ARG_STRING, &options.input_xml, "Use argument for XML (or stdin if '-')", NULL }, { NULL } }; static GOptionEntry addl_entries[] = { { "date", 'd', 0, G_OPTION_ARG_STRING, &options.date, "Whether the rule is in effect on a given date", NULL }, { "rule", 'r', 0, G_OPTION_ARG_STRING, &options.rule, "The ID of the rule to check", NULL }, { NULL } }; static gboolean mode_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (strcmp(option_name, "c")) { options.mode = crm_rule_mode_check; } return TRUE; } /*! * \internal * \brief Evaluate a date expression for a specific time * * \param[in] time_expr date_expression XML * \param[in] now Time for which to evaluate expression * \param[out] next_change If not NULL, set to when evaluation will change * * \return Standard Pacemaker return code */ static int eval_date_expression(xmlNode *expr, crm_time_t *now, crm_time_t *next_change) { pe_rule_eval_data_t rule_data = { .node_hash = NULL, .role = RSC_ROLE_UNKNOWN, .now = now, .match_data = NULL, .rsc_data = NULL, .op_data = NULL }; return pe__eval_date_expr(expr, &rule_data, next_change); } 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; char *xpath = NULL; int rc = pcmk_rc_ok; int max = 0; /* Rules are under the constraints node in the XML, so first find that. */ cib_constraints = pcmk_find_cib_element(data_set->input, XML_CIB_TAG_CONSTRAINTS); /* 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, or * - Have a date_spec operation that contains years= but does not contain moon=. * * 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']", rule_id); xpathObj = xpath_search(cib_constraints, xpath); max = numXpathResults(xpathObj); if (max == 0) { - CMD_ERR("No rule found with ID=%s", rule_id); rc = ENXIO; + g_set_error(&error, PCMK__RC_ERROR, rc, "No rule found with ID=%s", rule_id); goto done; } else if (max > 1) { - CMD_ERR("More than one rule with ID=%s found", rule_id); rc = ENXIO; + g_set_error(&error, PCMK__RC_ERROR, rc, "More than one rule with ID=%s found", rule_id); goto done; } 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 does not have exactly one date_expression", rule_id); rc = EOPNOTSUPP; + g_set_error(&error, PCMK__RC_ERROR, rc, + "Can't check rule %s because it does not have exactly one date_expression", rule_id); goto done; } 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) { free(xpath); freeXpathObject(xpathObj); xpath = crm_strdup_printf("//rule[@id='%s']//date_expression[@operation='date_spec' and date_spec/@years and not(date_spec/@moon)]", rule_id); xpathObj = xpath_search(cib_constraints, xpath); max = numXpathResults(xpathObj); if (max == 0) { - CMD_ERR("Rule either must not use date_spec, or use date_spec with years= but not moon="); rc = ENXIO; + g_set_error(&error, PCMK__RC_ERROR, rc, + "Rule either must not use date_spec, or use date_spec with years= but not moon="); goto done; } } 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); rc = eval_date_expression(match, effective_date, NULL); if (rc == pcmk_rc_within_range) { printf("Rule %s is still in effect\n", rule_id); rc = pcmk_rc_ok; } else if (rc == pcmk_rc_ok) { printf("Rule %s satisfies conditions\n", rule_id); } else if (rc == pcmk_rc_after_range) { printf("Rule %s is expired\n", rule_id); } else if (rc == pcmk_rc_before_range) { printf("Rule %s has not yet taken effect\n", rule_id); } else if (rc == pcmk_rc_op_unsatisfied) { printf("Rule %s does not satisfy conditions\n", rule_id); } else { printf("Could not determine whether rule %s is expired\n", rule_id); } done: free(xpath); freeXpathObject(xpathObj); return rc; } static GOptionContext * build_arg_context(pcmk__common_args_t *args) { GOptionContext *context = NULL; const char *description = "This tool is currently experimental.\n" "The interface, behavior, and output may change " "with any version of Pacemaker."; context = pcmk__build_arg_context(args, NULL, NULL, NULL); g_option_context_set_description(context, description); pcmk__add_arg_group(context, "modes", "Modes (mutually exclusive):", "Show modes of operation", mode_entries); pcmk__add_arg_group(context, "data", "Data:", "Show data options", data_entries); pcmk__add_arg_group(context, "additional", "Additional Options:", "Show additional options", addl_entries); return context; } int main(int argc, char **argv) { pe_working_set_t *data_set = NULL; crm_time_t *rule_date = NULL; xmlNode *input = NULL; int rc = pcmk_ok; crm_exit_t exit_code = CRM_EX_OK; - GError *error = NULL; pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); GOptionContext *context = build_arg_context(args); gchar **processed_args = pcmk__cmdline_preproc(argv, "drX"); if (!g_option_context_parse_strv(context, &processed_args, &error)) { exit_code = CRM_EX_USAGE; goto done; } pcmk__cli_init_logging("crm_rule", args->verbosity); if (args->version) { g_strfreev(processed_args); pcmk__free_arg_context(context); /* FIXME: When crm_rule is converted to use formatted output, this can go. */ pcmk__cli_help('v', CRM_EX_OK); } /* Check command line arguments before opening a connection to * the CIB manager or doing anything else important. */ switch(options.mode) { case crm_rule_mode_check: if (options.rule == NULL) { - CMD_ERR("--check requires use of --rule="); exit_code = CRM_EX_USAGE; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "--check requires use of --rule="); goto done; } break; default: - CMD_ERR("No mode operation given"); exit_code = CRM_EX_USAGE; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "No mode operation given"); goto done; break; } /* Set up some defaults. */ rule_date = crm_time_new(options.date); if (rule_date == NULL) { - CMD_ERR("No --date given and can't determine current date"); exit_code = CRM_EX_DATAERR; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "No --date given and can't determine current date"); goto done; } /* 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 (pcmk__str_eq(options.input_xml, "-", pcmk__str_casei)) { input = stdin2xml(); if (input == NULL) { - CMD_ERR("Couldn't parse input from STDIN\n"); exit_code = CRM_EX_DATAERR; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Couldn't parse input from STDIN\n"); goto done; } } else if (options.input_xml != NULL) { input = string2xml(options.input_xml); if (input == NULL) { - CMD_ERR("Couldn't parse input string: %s\n", options.input_xml); - exit_code = CRM_EX_DATAERR; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "Couldn't parse input string: %s\n", options.input_xml); goto done; } } else { rc = cib__signon_query(NULL, &input); if (rc != pcmk_rc_ok) { - CMD_ERR("CIB query failed: %s", pcmk_rc_str(rc)); exit_code = pcmk_rc2exitc(rc); + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "CIB query failed: %s", pcmk_rc_str(rc)); goto done; } } /* Populate the working set instance */ data_set = pe_new_working_set(); if (data_set == NULL) { exit_code = crm_errno2exit(ENOMEM); goto done; } pe__set_working_set_flags(data_set, pe_flag_no_counts|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(options.mode) { case crm_rule_mode_check: rc = crm_rule_check(data_set, options.rule, rule_date); - - if (rc > 0) { - CMD_ERR("Error checking rule: %s", pcmk_rc_str(rc)); - } - exit_code = pcmk_rc2exitc(rc); break; default: break; } done: g_strfreev(processed_args); pcmk__free_arg_context(context); pe_free_working_set(data_set); pcmk__output_and_clear_error(error, NULL); crm_exit(exit_code); }