diff --git a/tools/Makefile.am b/tools/Makefile.am index de64c93c2f..a7f35cf387 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -1,170 +1,171 @@ # # 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 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_error.8.inc \ crm_mon.sysconfig \ crm_mon.8.inc \ crm_node.8.inc \ crm_resource.8.inc \ crm_rule.8.inc \ crm_simulate.8.inc \ + crm_verify.8.inc \ crmadmin.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 \ $(top_builddir)/lib/pacemaker/libpacemaker.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_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_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_verify.8.inc b/tools/crm_verify.8.inc new file mode 100644 index 0000000000..27b0a55fb8 --- /dev/null +++ b/tools/crm_verify.8.inc @@ -0,0 +1,5 @@ +[=synopsis] +crm_verify [] + +/reported as warnings./ +.SH OPTIONS diff --git a/tools/crm_verify.c b/tools/crm_verify.c index c0c5f2fec0..1a9c5fc3f8 100644 --- a/tools/crm_verify.c +++ b/tools/crm_verify.c @@ -1,318 +1,301 @@ /* - * Copyright 2004-2020 the Pacemaker project contributors + * Copyright 2004-2021 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 #include #include #include #include #include #include #include -gboolean USE_LIVE_CIB = FALSE; -char *cib_save = NULL; +const char *SUMMARY = "Check a Pacemaker configuration for errors\n\n" + "Check the well-formedness of a complete Pacemaker XML configuration,\n" + "its conformance to the configured schema, and the presence of common\n" + "misconfigurations. Problems reported as errors must be fixed before the\n" + "cluster will work properly. It is left to the administrator to decide\n" + "whether to fix problems reported as warnings."; + +struct { + char *cib_save; + gboolean use_live_cib; + char *xml_file; + gboolean xml_stdin; + char *xml_string; +} options; + extern gboolean stage0(pe_working_set_t * data_set); -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', - "\tSpecify multiple times to increase debug output\n", - pcmk__option_default - }, - { - "-spacer-", no_argument, NULL, '-', - "\nData sources:", pcmk__option_default - }, - { - "live-check", no_argument, NULL, 'L', - "Check the configuration used by the running cluster\n", - pcmk__option_default - }, - { - "xml-file", required_argument, NULL, 'x', - "Check the configuration in the named file", pcmk__option_default - }, - { - "xml-text", required_argument, NULL, 'X', - "Check the configuration in the supplied string", pcmk__option_default - }, - { - "xml-pipe", no_argument, NULL, 'p', - "Check the configuration piped in via stdin", pcmk__option_default - }, - { - "-spacer-", no_argument, NULL, '-', - "\nAdditional Options:", pcmk__option_default - }, - { - "save-xml", required_argument, 0, 'S', - "Save verified XML to named file (most useful with -L)", - pcmk__option_default - }, - { - "-spacer-", no_argument, NULL, '-', - "\nExamples:", pcmk__option_paragraph - }, - { - "-spacer-", no_argument, NULL, '-', - "Check the consistency of the configuration in the running cluster:", - pcmk__option_paragraph - }, - { - "-spacer-", no_argument, NULL, '-', - " crm_verify --live-check", pcmk__option_example - }, - { - "-spacer-", no_argument, NULL, '-', - "Check the consistency of the configuration in a given file and " - "produce verbose output:", - pcmk__option_paragraph - }, - { - "-spacer-", no_argument, NULL, '-', - " crm_verify --xml-file file.xml --verbose", pcmk__option_example - }, - {0, 0, 0, 0} +static GOptionEntry data_entries[] = { + { "live-check", 'L', 0, G_OPTION_ARG_NONE, + &options.use_live_cib, "Check the configuration used by the running cluster", + NULL }, + { "xml-file", 'x', 0, G_OPTION_ARG_FILENAME, + &options.xml_file, "Check the configuration in the named file", + "FILE" }, + { "xml-pipe", 'p', 0, G_OPTION_ARG_NONE, + &options.xml_string, "Check the configuration piped in via stdin", + NULL }, + { "xml-text", 'X', 0, G_OPTION_ARG_STRING, + &options.xml_string, "Check the configuration in the supplied string", + "XML" }, + + { NULL } +}; + +static GOptionEntry addl_entries[] = { + { "save-xml", 'S', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, + &options.cib_save, "Save verified XML to named file (most useful with -L)", + "FILE" }, + + { NULL } }; +static pcmk__supported_format_t formats[] = { + PCMK__SUPPORTED_FORMAT_NONE, + PCMK__SUPPORTED_FORMAT_TEXT, + PCMK__SUPPORTED_FORMAT_XML, + { NULL, NULL, NULL } +}; + +static GOptionContext * +build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) { + GOptionContext *context = NULL; + + const char *description = "Examples:\n\n" + "Check the consistency of the configuration in the running cluster:\n\n" + "\tcrm_verify --live-check\n\n" + "Check the consistency of the configuration in a given file and " + "produce verbose output:\n\n" + "\tcrm_verify --xml-file file.xml --verbose\n\n"; + + context = pcmk__build_arg_context(args, "text (default), xml", group, NULL); + g_option_context_set_description(context, description); + + pcmk__add_arg_group(context, "data", "Data sources:", + "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) { xmlNode *cib_object = NULL; xmlNode *status = NULL; - int argerr = 0; - int flag; - int option_index = 0; pe_working_set_t *data_set = NULL; cib_t *cib_conn = NULL; - int rc = pcmk_ok; - - bool verbose = FALSE; - gboolean xml_stdin = FALSE; const char *xml_tag = NULL; - const char *xml_file = NULL; - const char *xml_string = NULL; - - pcmk__cli_init_logging("crm_verify", 0); - pcmk__set_cli_options(NULL, "[options]", long_options, - "check a Pacemaker configuration for errors\n\n" - "Check the well-formedness of a complete Pacemaker " - "XML configuration,\nits conformance to the " - "configured schema, and the presence of common\n" - "misconfigurations. Problems reported as errors " - "must be fixed before the\ncluster will work " - "properly. It is left to the administrator to decide" - "\nwhether to fix problems reported as warnings."); - - while (1) { - flag = pcmk__next_cli_option(argc, argv, &option_index, NULL); - if (flag == -1) - break; - - switch (flag) { - case 'X': - crm_trace("Option %c => %s", flag, optarg); - xml_string = optarg; - break; - case 'x': - crm_trace("Option %c => %s", flag, optarg); - xml_file = optarg; - break; - case 'p': - xml_stdin = TRUE; - break; - case 'S': - cib_save = optarg; - break; - case 'V': - verbose = TRUE; - crm_bump_log_level(argc, argv); - break; - case 'L': - USE_LIVE_CIB = TRUE; - break; - case '$': - case '?': - pcmk__cli_help(flag, CRM_EX_OK); - break; - default: - fprintf(stderr, "Option -%c is not yet supported\n", flag); - ++argerr; - break; - } - } - if (optind < argc) { - printf("non-option ARGV-elements: "); - while (optind < argc) { - printf("%s ", argv[optind++]); - } - printf("\n"); + int rc = pcmk_rc_ok; + crm_exit_t exit_code = CRM_EX_OK; + + GError *error = NULL; + + pcmk__output_t *out = NULL; + + GOptionGroup *output_group = NULL; + pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); + gchar **processed_args = pcmk__cmdline_preproc(argv, "xSX"); + GOptionContext *context = build_arg_context(args, &output_group); + + pcmk__register_formats(output_group, formats); + if (!g_option_context_parse_strv(context, &processed_args, &error)) { + exit_code = CRM_EX_USAGE; + goto done; } - if (optind > argc) { - ++argerr; + pcmk__cli_init_logging("crm_verify", args->verbosity); + + rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv); + if (rc != pcmk_rc_ok) { + exit_code = CRM_EX_ERROR; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Error creating output format %s: %s", + args->output_ty, pcmk_rc_str(rc)); + goto done; } - if (argerr) { - crm_err("%d errors in option parsing", argerr); - pcmk__cli_help(flag, CRM_EX_USAGE); + if (args->version) { + out->version(out, false); + goto done; } crm_info("=#=#=#=#= Getting XML =#=#=#=#="); - if (USE_LIVE_CIB) { + if (options.use_live_cib) { cib_conn = cib_new(); rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command); + rc = pcmk_legacy2rc(rc); } - if (USE_LIVE_CIB) { - if (rc == pcmk_ok) { + if (options.use_live_cib) { + if (rc == pcmk_rc_ok) { int options = cib_scope_local | cib_sync_call; crm_info("Reading XML from: live cluster"); rc = cib_conn->cmds->query(cib_conn, NULL, &cib_object, options); + rc = pcmk_legacy2rc(rc); } - if (rc != pcmk_ok) { - fprintf(stderr, "Live CIB query failed: %s\n", pcmk_strerror(rc)); + if (rc != pcmk_rc_ok) { + g_set_error(&error, PCMK__RC_ERROR, rc, "Live CIB query failed: %s", pcmk_rc_str(rc)); goto done; } if (cib_object == NULL) { - fprintf(stderr, "Live CIB query failed: empty result\n"); - rc = -ENOMSG; + rc = ENOMSG; + g_set_error(&error, PCMK__RC_ERROR, rc, "Live CIB query failed: empty result"); goto done; } - } else if (xml_file != NULL) { - cib_object = filename2xml(xml_file); + } else if (options.xml_file != NULL) { + cib_object = filename2xml(options.xml_file); if (cib_object == NULL) { - fprintf(stderr, "Couldn't parse input file: %s\n", xml_file); - rc = -ENODATA; + rc = ENODATA; + g_set_error(&error, PCMK__RC_ERROR, rc, "Couldn't parse input file: %s", options.xml_file); goto done; } - } else if (xml_string != NULL) { - cib_object = string2xml(xml_string); + } else if (options.xml_string != NULL) { + cib_object = string2xml(options.xml_string); if (cib_object == NULL) { - fprintf(stderr, "Couldn't parse input string: %s\n", xml_string); - rc = -ENODATA; + rc = ENODATA; + g_set_error(&error, PCMK__RC_ERROR, rc, "Couldn't parse input string: %s", options.xml_string); goto done; } - } else if (xml_stdin) { + } else if (options.xml_stdin) { cib_object = stdin2xml(); if (cib_object == NULL) { - fprintf(stderr, "Couldn't parse input from STDIN.\n"); - rc = -ENODATA; + rc = ENODATA; + g_set_error(&error, PCMK__RC_ERROR, rc, "Couldn't parse input from STDIN."); goto done; } } else { - fprintf(stderr, "No configuration source specified." - " Use --help for usage information.\n"); - rc = -ENODATA; + rc = ENODATA; + g_set_error(&error, PCMK__RC_ERROR, rc, + "No configuration source specified. Use --help for usage information."); goto done; } xml_tag = crm_element_name(cib_object); if (!pcmk__str_eq(xml_tag, XML_TAG_CIB, pcmk__str_casei)) { - fprintf(stderr, - "This tool can only check complete configurations (i.e. those starting with ).\n"); - rc = -EBADMSG; + rc = EBADMSG; + g_set_error(&error, PCMK__RC_ERROR, rc, + "This tool can only check complete configurations (i.e. those starting with )."); goto done; } - if (cib_save != NULL) { - write_xml_file(cib_object, cib_save, FALSE); + if (options.cib_save != NULL) { + write_xml_file(cib_object, options.cib_save, FALSE); } status = get_object_root(XML_CIB_TAG_STATUS, cib_object); if (status == NULL) { create_xml_node(cib_object, XML_CIB_TAG_STATUS); } if (validate_xml(cib_object, NULL, FALSE) == FALSE) { pcmk__config_err("CIB did not pass schema validation"); free_xml(cib_object); cib_object = NULL; } else if (cli_config_update(&cib_object, NULL, FALSE) == FALSE) { pcmk__config_error = true; free_xml(cib_object); cib_object = NULL; - fprintf(stderr, "The cluster will NOT be able to use this configuration.\n"); - fprintf(stderr, "Please manually update the configuration to conform to the %s syntax.\n", - xml_latest_schema()); + out->err(out, "The cluster will NOT be able to use this configuration.\n" + "Please manually update the configuration to conform to the %s syntax.", + xml_latest_schema()); } data_set = pe_new_working_set(); if (data_set == NULL) { - rc = -errno; + rc = errno; crm_perror(LOG_CRIT, "Unable to allocate working set"); goto done; } pe__set_working_set_flags(data_set, pe_flag_no_counts|pe_flag_no_compat); if (cib_object == NULL) { - } else if (status != NULL || USE_LIVE_CIB) { + } else if (status != NULL || options.use_live_cib) { /* live queries will always have a status section and can do a full simulation */ pcmk__schedule_actions(data_set, cib_object, NULL); } else { data_set->now = crm_time_new(NULL); data_set->input = cib_object; stage0(data_set); } pe_free_working_set(data_set); if (pcmk__config_error) { - fprintf(stderr, "Errors found during check: config not valid\n"); - if (verbose == FALSE) { - fprintf(stderr, " -V may provide more details\n"); + rc = pcmk_rc_schema_validation; + + if (args->verbosity > 0) { + g_set_error(&error, PCMK__RC_ERROR, rc, + "Errors found during check: config not valid"); + } else { + g_set_error(&error, PCMK__RC_ERROR, rc, + "Errors found during check: config not valid\n-V may provide more details"); } - rc = -pcmk_err_schema_validation; } else if (pcmk__config_warning) { - fprintf(stderr, "Warnings found during check: config may not be valid\n"); - if (verbose == FALSE) { - fprintf(stderr, " Use -V -V for more details\n"); + rc = pcmk_rc_schema_validation; + + if (args->verbosity > 0) { + g_set_error(&error, PCMK__RC_ERROR, rc, + "Warnings found during check: config may not be valid"); + } else { + g_set_error(&error, PCMK__RC_ERROR, rc, + "Warnings found during check: config may not be valid\n-V may provide more details"); } - rc = -pcmk_err_schema_validation; } - if (USE_LIVE_CIB && cib_conn) { + if (options.use_live_cib && cib_conn) { cib_conn->cmds->signoff(cib_conn); cib_delete(cib_conn); } done: - crm_exit(crm_errno2exit(rc)); + g_strfreev(processed_args); + pcmk__free_arg_context(context); + free(options.cib_save); + free(options.xml_file); + free(options.xml_string); + + if (exit_code == CRM_EX_OK) { + exit_code = pcmk_rc2exitc(rc); + } + + pcmk__output_and_clear_error(error, NULL); + + if (out != NULL) { + out->finish(out, exit_code, true, NULL); + pcmk__output_free(out); + } + + crm_exit(exit_code); }