diff --git a/tools/Makefile.am b/tools/Makefile.am index 9f59c9debf..e5c44e8d55 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -1,165 +1,166 @@ # # Copyright 2004-2019 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 $(top_srcdir)/mk/common.mk if BUILD_SYSTEMD systemdsystemunit_DATA = crm_mon.service endif noinst_HEADERS = crm_mon.h crm_resource.h crm_resource_controller.h pcmkdir = $(datadir)/$(PACKAGE) pcmk_DATA = report.common report.collector sbin_SCRIPTS = crm_report crm_standby crm_master crm_failcount if BUILD_CIBSECRETS sbin_SCRIPTS += cibsecret endif noinst_SCRIPTS = pcmk_simtimes EXTRA_DIST = crm_diff.8.inc \ crm_mon.sysconfig \ crm_mon.8.inc \ crm_node.8.inc \ + crm_rule.8.inc \ fix-manpages \ stonith_admin.8.inc sbin_PROGRAMS = attrd_updater \ cibadmin \ crmadmin \ crm_simulate \ crm_attribute \ crm_diff \ crm_error \ crm_mon \ crm_node \ crm_resource \ crm_rule \ crm_shadow \ crm_verify \ crm_ticket \ iso8601 \ stonith_admin if BUILD_SERVICELOG sbin_PROGRAMS += notifyServicelogEvent endif if BUILD_OPENIPMI_SERVICELOG sbin_PROGRAMS += ipmiservicelogd endif ## SOURCES # A few tools are just thin wrappers around crm_attribute. # This makes their help get updated when crm_attribute changes # (see mk/common.mk). MAN8DEPS = crm_attribute crmadmin_SOURCES = crmadmin.c crmadmin_LDADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_error_SOURCES = crm_error.c crm_error_LDADD = $(top_builddir)/lib/common/libcrmcommon.la cibadmin_SOURCES = cibadmin.c cibadmin_LDADD = $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_shadow_SOURCES = crm_shadow.c crm_shadow_LDADD = $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_node_SOURCES = crm_node.c crm_node_LDADD = $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_simulate_SOURCES = crm_simulate.c crm_simulate_LDADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_diff_SOURCES = crm_diff.c crm_diff_LDADD = $(top_builddir)/lib/common/libcrmcommon.la crm_mon_SOURCES = crm_mon.c crm_mon_curses.c crm_mon_print.c crm_mon_runtime.c crm_mon_xml.c crm_mon_LDADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/fencing/libstonithd.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la \ $(CURSESLIBS) crm_verify_SOURCES = crm_verify.c crm_verify_LDADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_attribute_SOURCES = crm_attribute.c crm_attribute_LDADD = $(top_builddir)/lib/cluster/libcrmcluster.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_resource_SOURCES = crm_resource.c \ crm_resource_ban.c \ crm_resource_controller.c \ crm_resource_print.c \ crm_resource_runtime.c crm_resource_LDADD = $(top_builddir)/lib/pengine/libpe_rules.la \ $(top_builddir)/lib/fencing/libstonithd.la \ $(top_builddir)/lib/lrmd/liblrmd.la \ $(top_builddir)/lib/services/libcrmservice.la \ $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_rule_SOURCES = crm_rule.c crm_rule_LDADD = $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/pengine/libpe_rules.la \ $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/common/libcrmcommon.la iso8601_SOURCES = iso8601.c iso8601_LDADD = $(top_builddir)/lib/common/libcrmcommon.la attrd_updater_SOURCES = attrd_updater.c attrd_updater_LDADD = $(top_builddir)/lib/common/libcrmcommon.la crm_ticket_SOURCES = crm_ticket.c crm_ticket_LDADD = $(top_builddir)/lib/pengine/libpe_rules.la \ $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la stonith_admin_SOURCES = stonith_admin.c stonith_admin_LDADD = $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/fencing/libstonithd.la \ $(top_builddir)/lib/common/libcrmcommon.la if BUILD_SERVICELOG notifyServicelogEvent_SOURCES = notifyServicelogEvent.c notifyServicelogEvent_CFLAGS = $(SERVICELOG_CFLAGS) notifyServicelogEvent_LDADD = $(top_builddir)/lib/common/libcrmcommon.la $(SERVICELOG_LIBS) endif if BUILD_OPENIPMI_SERVICELOG ipmiservicelogd_SOURCES = ipmiservicelogd.c ipmiservicelogd_CFLAGS = $(OPENIPMI_SERVICELOG_CFLAGS) $(SERVICELOG_CFLAGS) ipmiservicelogd_LDFLAGS = $(top_builddir)/lib/common/libcrmcommon.la $(OPENIPMI_SERVICELOG_LIBS) $(SERVICELOG_LIBS) endif CLEANFILES = $(man8_MANS) diff --git a/tools/crm_rule.8.inc b/tools/crm_rule.8.inc new file mode 100644 index 0000000000..2b6ac89617 --- /dev/null +++ b/tools/crm_rule.8.inc @@ -0,0 +1,8 @@ +[synopsis] +crm_rule mode [options] + +/state of rules/ +.SH OPTIONS + +/multiple times/ +.SH NOTICE diff --git a/tools/crm_rule.c b/tools/crm_rule.c index 59aec6e532..0e9a1550b0 100644 --- a/tools/crm_rule.c +++ b/tools/crm_rule.c @@ -1,349 +1,359 @@ /* * 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 #include +#define SUMMARY "evaluate rules from the Pacemaker configuration" + enum crm_rule_mode { crm_rule_mode_none, crm_rule_mode_check -} rule_mode = crm_rule_mode_none; +}; + +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 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 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; +} + 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 = get_object_root(XML_CIB_TAG_CONSTRAINTS, data_set->input); /* 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; goto bail; } else if (max > 1) { 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 does not have exactly 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) { 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; 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); rc = pe_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); } bail: 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); + 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) { 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; + pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); + + GError *error = NULL; + GOptionContext *context = NULL; + gchar **processed_args = NULL; + + context = build_arg_context(args); + 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; - } + + processed_args = pcmk__cmdline_preproc(argv, "nopNO"); + + if (!g_option_context_parse_strv(context, &processed_args, &error)) { + fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message); + exit_code = CRM_EX_USAGE; + goto bail; + } + + for (int i = 0; i < args->verbosity; i++) { + crm_bump_log_level(argc, argv); + } + + if (args->version) { + /* FIXME: When crm_rule is converted to use formatted output, this can go. */ + pcmk__cli_help('v', CRM_EX_USAGE); + } + + if (optind > argc) { + CMD_ERR("%s", g_option_context_get_help(context, TRUE, NULL)); + exit_code = CRM_EX_USAGE; + goto bail; } /* 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); - } + switch(options.mode) { + case crm_rule_mode_check: + if (options.rule == NULL) { + CMD_ERR("--check requires use of --rule="); + exit_code = CRM_EX_USAGE; + goto bail; + } + + break; + + default: + CMD_ERR("No mode operation given"); + exit_code = CRM_EX_USAGE; + goto bail; + break; } /* Set up some defaults. */ + rule_date = crm_time_new(options.date); if (rule_date == NULL) { - rule_date = crm_time_new(NULL); + CMD_ERR("No --date given and can't determine current date"); + exit_code = CRM_EX_DATAERR; + goto bail; } /* 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, "-")) { + if (safe_str_eq(options.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); + } else if (options.input_xml != NULL) { + input = string2xml(options.input_xml); if (input == NULL) { - fprintf(stderr, "Couldn't parse input string: %s\n", input_xml); + fprintf(stderr, "Couldn't parse input string: %s\n", options.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) { + switch(options.mode) { case crm_rule_mode_check: - rc = crm_rule_check(data_set, rule_id, rule_date); + 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; } bail: if (cib_conn != NULL) { cib_conn->cmds->signoff(cib_conn); cib_delete(cib_conn); } + g_strfreev(processed_args); + g_clear_error(&error); + pcmk__free_arg_context(context); pe_free_working_set(data_set); crm_exit(exit_code); }