From ea636bc7b290325a8d11f56c4ca461d4d010643d Mon Sep 17 00:00:00 2001 From: Ken Gaillot Date: Mon, 14 Sep 2020 16:29:19 -0500 Subject: [PATCH 1/6] Refactor: tools: restructure crm_resource command-line resource configuration ... to allow (future) options other than --validate to use the command-line resource configuration options. I had planned to use this for a project but went in different direction, so nothing more is expected to use it for now, but I think it's still worthwhile to help isolate different parts of code. --- tools/crm_resource.c | 189 ++++++++++++++++++++++++++------------------------- 1 file changed, 98 insertions(+), 91 deletions(-) diff --git a/tools/crm_resource.c b/tools/crm_resource.c index 2fc9a86..1dcb0f0 100644 --- a/tools/crm_resource.c +++ b/tools/crm_resource.c @@ -1,5 +1,5 @@ /* - * 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. * @@ -66,39 +66,46 @@ enum rsc_command { }; struct { - enum rsc_command rsc_cmd; // The crm_resource command to perform - const char *attr_set_type; - int cib_options; - gboolean clear_expired; - int find_flags; /* Flags to use when searching for resource */ - gboolean force; - gchar *host_uname; - gchar *interval_spec; - gchar *move_lifetime; - gchar *operation; - GHashTable *override_params; - gchar *prop_id; - char *prop_name; - gchar *prop_set; - gchar *prop_value; - gboolean recursive; - gchar **remainder; - gboolean require_cib; // Whether command requires CIB connection - gboolean require_crmd; /* whether command requires controller connection */ - gboolean require_dataset; /* whether command requires populated dataset instance */ - gboolean require_resource; /* whether command requires that resource be specified */ - int resource_verbose; - gchar *rsc_id; - gchar *rsc_type; - gboolean promoted_role_only; - int timeout_ms; - char *agent_spec; // Standard and/or provider and/or agent - char *v_agent; - char *v_class; - char *v_provider; - gboolean validate_cmdline; /* whether we are just validating based on command line options */ - GHashTable *validate_options; - gchar *xml_file; + enum rsc_command rsc_cmd; // crm_resource command to perform + + // Infrastructure that given command needs to work + gboolean require_cib; // Whether command requires CIB IPC + int cib_options; // Options to use with CIB IPC calls + gboolean require_crmd; // Whether command requires controller IPC + gboolean require_dataset; // Whether command requires populated data set + gboolean require_resource; // Whether command requires resource specified + int find_flags; // Flags to use when searching for resource + + // Command-line option values + gchar *rsc_id; // Value of --resource + gchar *rsc_type; // Value of --resource-type + gboolean force; // --force was given + gboolean clear_expired; // --expired was given + gboolean recursive; // --recursive was given + gboolean promoted_role_only; // --master was given + gchar *host_uname; // Value of --node + gchar *interval_spec; // Value of --interval + gchar *move_lifetime; // Value of --lifetime + gchar *operation; // Value of --operation + const char *attr_set_type; // Instance, meta, or utilization attribute + gchar *prop_id; // --nvpair (attribute XML ID) + char *prop_name; // Attribute name + gchar *prop_set; // --set-name (attribute block XML ID) + gchar *prop_value; // --parameter-value (attribute value) + int timeout_ms; // Parsed from --timeout value + char *agent_spec; // Standard and/or provider and/or agent + gchar *xml_file; // Value of (deprecated) --xml-file + + // Resource configuration specified via command-line arguments + gboolean cmdline_config; // Resource configuration was via arguments + char *v_agent; // Value of --agent + char *v_class; // Value of --class + char *v_provider; // Value of --provider + GHashTable *cmdline_params; // Resource parameters specified + + // Positional command-line arguments + gchar **remainder; // Positional arguments as given + GHashTable *override_params; // Resource parameter values that override config } options = { .attr_set_type = XML_TAG_ATTR_SETS, .cib_options = cib_sync_call, @@ -533,28 +540,6 @@ static GOptionEntry advanced_entries[] = { { NULL } }; -static GOptionEntry validate_entries[] = { - { "class", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, class_cb, - "The standard the resource agent confirms to (for example, ocf).\n" - INDENT "Use with --agent, --provider, --option, and --validate.", - "CLASS" }, - { "agent", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, agent_provider_cb, - "The agent to use (for example, IPaddr). Use with --class,\n" - INDENT "--provider, --option, and --validate.", - "AGENT" }, - { "provider", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, agent_provider_cb, - "The vendor that supplies the resource agent (for example,\n" - INDENT "heartbeat). Use with --class, --agent, --option, and --validate.", - "PROVIDER" }, - { "option", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, option_cb, - "Specify a device configuration parameter as NAME=VALUE (may be\n" - INDENT "specified multiple times). Use with --validate and without the\n" - INDENT "-r option.", - "PARAM" }, - - { NULL } -}; - static GOptionEntry addl_entries[] = { { "node", 'N', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.host_uname, "Node name", @@ -582,6 +567,23 @@ static GOptionEntry addl_entries[] = { { "interval", 'I', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.interval_spec, "Interval of operation to clear (default 0) (with -C -r -n)", "N" }, + { "class", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, class_cb, + "The standard the resource agent conforms to (for example, ocf).\n" + INDENT "Use with --agent, --provider, --option, and --validate.", + "CLASS" }, + { "agent", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, agent_provider_cb, + "The agent to use (for example, IPaddr). Use with --class,\n" + INDENT "--provider, --option, and --validate.", + "AGENT" }, + { "provider", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, agent_provider_cb, + "The vendor that supplies the resource agent (for example,\n" + INDENT "heartbeat). Use with --class, --agent, --option, and --validate.", + "PROVIDER" }, + { "option", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, option_cb, + "Specify a device configuration parameter as NAME=VALUE (may be\n" + INDENT "specified multiple times). Use with --validate and without the\n" + INDENT "-r option.", + "PARAM" }, { "set-name", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.prop_set, "(Advanced) XML ID of attributes element to use (with -p, -d)", "ID" }, @@ -608,7 +610,7 @@ static GOptionEntry addl_entries[] = { gboolean agent_provider_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { - options.validate_cmdline = TRUE; + options.cmdline_config = TRUE; options.require_resource = FALSE; if (pcmk__str_eq(option_name, "--provider", pcmk__str_casei)) { @@ -654,7 +656,7 @@ class_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError ** options.v_class = strdup(optarg); } - options.validate_cmdline = TRUE; + options.cmdline_config = TRUE; options.require_resource = FALSE; return TRUE; } @@ -762,10 +764,10 @@ option_cb(const gchar *option_name, const gchar *optarg, gpointer data, if (pcmk_scan_nvpair(optarg, &name, &value) != 2) { return FALSE; } - if (options.validate_options == NULL) { - options.validate_options = crm_str_table_new(); + if (options.cmdline_params == NULL) { + options.cmdline_params = crm_str_table_new(); } - g_hash_table_replace(options.validate_options, name, value); + g_hash_table_replace(options.cmdline_params, name, value); return TRUE; } @@ -1365,17 +1367,18 @@ show_metadata(pcmk__output_t *out, const char *agent_spec, crm_exit_t *exit_code } static void -validate_cmdline(crm_exit_t *exit_code) +validate_cmdline_config(void) { - // -r cannot be used with any of --class, --agent, or --provider + // Cannot use both --resource and command-line resource configuration if (options.rsc_id != NULL) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "--resource cannot be used with --class, --agent, and --provider"); - // If --class, --agent, or --provider are given, --validate must also be given. + // Not all commands support command-line resource configuration } else if (options.rsc_cmd != cmd_execute_agent) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, - "--class, --agent, and --provider require --validate"); + "--class, --agent, and --provider can only be used with " + "--validate"); // Not all of --class, --agent, and --provider need to be given. Not all // classes support the concept of a provider. Check that what we were given @@ -1398,15 +1401,16 @@ validate_cmdline(crm_exit_t *exit_code) options.v_agent ? options.v_agent : ""); } - if (error == NULL) { - if (options.validate_options == NULL) { - options.validate_options = crm_str_table_new(); - } - *exit_code = cli_resource_execute_from_params(out, "test", options.v_class, options.v_provider, options.v_agent, - "validate-all", options.validate_options, - options.override_params, options.timeout_ms, - options.resource_verbose, options.force); + if (error != NULL) { + return; + } + + if (options.cmdline_params == NULL) { + options.cmdline_params = crm_str_table_new(); } + options.require_resource = FALSE; + options.require_dataset = FALSE; + options.require_cib = FALSE; } static GOptionContext * @@ -1467,8 +1471,6 @@ build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) { "Show command help", command_entries); pcmk__add_arg_group(context, "locations", "Locations:", "Show location help", location_entries); - pcmk__add_arg_group(context, "validate", "Validate:", - "Show validate help", validate_entries); pcmk__add_arg_group(context, "advanced", "Advanced:", "Show advanced option help", advanced_entries); pcmk__add_arg_group(context, "additional", "Additional Options:", @@ -1512,7 +1514,6 @@ main(int argc, char **argv) goto done; } - options.resource_verbose = args->verbosity; out->quiet = args->quiet; crm_log_args(argc, argv); @@ -1628,15 +1629,15 @@ main(int argc, char **argv) goto done; } - // Sanity check validating from command line parameters. If everything checks out, - // go ahead and run the validation. This way we don't need a CIB connection. - if (options.validate_cmdline) { - validate_cmdline(&exit_code); - goto done; - } else if (options.validate_options != NULL) { + if (options.cmdline_config) { + /* A resource configuration was given on the command line. Sanity-check + * the values and set error if they don't make sense. + */ + validate_cmdline_config(); + } else if (options.cmdline_params != NULL) { // @COMPAT @TODO error out here when we can break backward compatibility - g_hash_table_destroy(options.validate_options); - options.validate_options = NULL; + g_hash_table_destroy(options.cmdline_params); + options.cmdline_params = NULL; } if (error != NULL) { @@ -1773,12 +1774,18 @@ main(int argc, char **argv) break; case cmd_execute_agent: - exit_code = cli_resource_execute(out, rsc, options.rsc_id, - options.operation, - options.override_params, - options.timeout_ms, cib_conn, - data_set, options.resource_verbose, - options.force); + if (options.cmdline_config) { + exit_code = cli_resource_execute_from_params(out, "test", + options.v_class, options.v_provider, options.v_agent, + "validate-all", options.cmdline_params, + options.override_params, options.timeout_ms, + args->verbosity, options.force); + } else { + exit_code = cli_resource_execute(out, rsc, options.rsc_id, + options.operation, options.override_params, + options.timeout_ms, cib_conn, data_set, + args->verbosity, options.force); + } break; case cmd_colocations: @@ -2038,7 +2045,7 @@ done: g_hash_table_destroy(options.override_params); } - /* options.validate_options does not need to be destroyed here. See the + /* options.cmdline_params does not need to be destroyed here. See the * comments in cli_resource_execute_from_params. */ -- 1.8.3.1 From e140bd1bc35a20f027f054b4575808bd0ef547fc Mon Sep 17 00:00:00 2001 From: Ken Gaillot Date: Wed, 16 Sep 2020 15:40:16 -0500 Subject: [PATCH 2/6] Low: tools: handle required node names better in crm_resource Currently, --fail is the only option that requires a node name to be specified, but generalize the handling so future options can reuse it. This also makes the error handling closer to what's done for required resource names, both in error message and exit status. --- tools/crm_resource.c | 87 ++++++++++++++++++++++++++++---------------- tools/crm_resource_runtime.c | 8 +--- 2 files changed, 57 insertions(+), 38 deletions(-) diff --git a/tools/crm_resource.c b/tools/crm_resource.c index 1dcb0f0..2717a62 100644 --- a/tools/crm_resource.c +++ b/tools/crm_resource.c @@ -74,6 +74,7 @@ struct { gboolean require_crmd; // Whether command requires controller IPC gboolean require_dataset; // Whether command requires populated data set gboolean require_resource; // Whether command requires resource specified + gboolean require_node; // Whether command requires node specified int find_flags; // Flags to use when searching for resource // Command-line option values @@ -774,6 +775,7 @@ option_cb(const gchar *option_name, const gchar *optarg, gpointer data, gboolean fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.require_crmd = TRUE; + options.require_node = TRUE; SET_COMMAND(cmd_fail); return TRUE; } @@ -1483,9 +1485,13 @@ main(int argc, char **argv) { xmlNode *cib_xml_copy = NULL; pe_resource_t *rsc = NULL; - + pe_node_t *node = NULL; int rc = pcmk_rc_ok; + /* + * Parse command line arguments + */ + pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); GOptionContext *context = NULL; GOptionGroup *output_group = NULL; @@ -1502,6 +1508,10 @@ main(int argc, char **argv) goto done; } + /* + * Set verbosity + */ + for (int i = 0; i < args->verbosity; i++) { crm_bump_log_level(argc, argv); } @@ -1518,9 +1528,9 @@ main(int argc, char **argv) crm_log_args(argc, argv); - if (options.host_uname) { - crm_trace("Option host => %s", options.host_uname); - } + /* + * Validate option combinations + */ // If the user didn't explicitly specify a command, list resources if (options.rsc_cmd == cmd_none) { @@ -1634,30 +1644,42 @@ main(int argc, char **argv) * the values and set error if they don't make sense. */ validate_cmdline_config(); + if (error != NULL) { + exit_code = CRM_EX_USAGE; + goto done; + } + } else if (options.cmdline_params != NULL) { // @COMPAT @TODO error out here when we can break backward compatibility g_hash_table_destroy(options.cmdline_params); options.cmdline_params = NULL; } - if (error != NULL) { + if (options.require_resource && (options.rsc_id == NULL)) { + rc = ENXIO; + exit_code = CRM_EX_USAGE; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "Must supply a resource id with -r"); + goto done; + } + if (options.require_node && (options.host_uname == NULL)) { + rc = ENXIO; exit_code = CRM_EX_USAGE; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "Must supply a node name with -N"); goto done; } + /* + * Set up necessary connections + */ + if (options.force) { crm_debug("Forcing..."); cib__set_call_options(options.cib_options, crm_system_name, cib_quorum_override); } - if (options.require_resource && !options.rsc_id) { - rc = ENXIO; - g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, - "Must supply a resource id with -r"); - goto done; - } - if (options.find_flags && options.rsc_id) { options.require_dataset = TRUE; } @@ -1700,6 +1722,11 @@ main(int argc, char **argv) } } + // If user supplied a node name, check whether it exists + if ((options.host_uname != NULL) && (data_set != NULL)) { + node = pe_find_node(data_set->nodes, options.host_uname); + } + // Establish a connection to the controller if needed if (options.require_crmd) { rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld); @@ -1718,6 +1745,10 @@ main(int argc, char **argv) } } + /* + * Handle requested command + */ + switch (options.rsc_cmd) { case cmd_list_resources: { GListPtr all = NULL; @@ -1844,18 +1875,11 @@ main(int argc, char **argv) break; case cmd_why: - { - pe_node_t *dest = NULL; - - if (options.host_uname) { - dest = pe_find_node(data_set->nodes, options.host_uname); - if (dest == NULL) { - rc = pcmk_rc_node_unknown; - goto done; - } - } - out->message(out, "resource-reasons-list", cib_conn, data_set->resources, rsc, dest); - rc = pcmk_rc_ok; + if ((options.host_uname != NULL) && (node == NULL)) { + rc = pcmk_rc_node_unknown; + } else { + rc = out->message(out, "resource-reasons-list", cib_conn, + data_set->resources, rsc, node); } break; @@ -1878,15 +1902,10 @@ main(int argc, char **argv) case cmd_ban: if (options.host_uname == NULL) { rc = ban_or_move(out, rsc, options.move_lifetime, &exit_code); + } else if (node == NULL) { + rc = pcmk_rc_node_unknown; } else { - pe_node_t *dest = pe_find_node(data_set->nodes, - options.host_uname); - - if (dest == NULL) { - rc = pcmk_rc_node_unknown; - goto done; - } - rc = cli_resource_ban(out, options.rsc_id, dest->details->uname, + rc = cli_resource_ban(out, options.rsc_id, node->details->uname, options.move_lifetime, NULL, cib_conn, options.cib_options, options.promoted_role_only); @@ -2002,6 +2021,10 @@ main(int argc, char **argv) break; } + /* + * Clean up and exit + */ + done: if (rc != pcmk_rc_ok) { if (rc == pcmk_rc_no_quorum) { diff --git a/tools/crm_resource_runtime.c b/tools/crm_resource_runtime.c index 3f28c7b..de5e807 100644 --- a/tools/crm_resource_runtime.c +++ b/tools/crm_resource_runtime.c @@ -1,5 +1,5 @@ /* - * 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. * @@ -511,11 +511,7 @@ send_lrm_rsc_op(pcmk__output_t *out, pcmk_ipc_api_t *controld_api, bool do_fail_ return EINVAL; } - if (host_uname == NULL) { - out->err(out, "Please specify a node name"); - return EINVAL; - - } else { + { pe_node_t *node = pe_find_node(data_set->nodes, host_uname); if (node == NULL) { -- 1.8.3.1 From 31bda91470487790d6e17b6f2cbed282bafd11d0 Mon Sep 17 00:00:00 2001 From: Ken Gaillot Date: Tue, 15 Sep 2020 15:00:53 -0500 Subject: [PATCH 3/6] Refactor: libpacemaker: add files for resource-related API --- include/pacemaker-internal.h | 3 ++- include/pcmki/Makefile.am | 3 ++- include/pcmki/pcmki_resource.h | 14 ++++++++++++++ lib/pacemaker/Makefile.am | 3 ++- lib/pacemaker/pcmk_resource.c | 21 +++++++++++++++++++++ 5 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 include/pcmki/pcmki_resource.h create mode 100644 lib/pacemaker/pcmk_resource.c diff --git a/include/pacemaker-internal.h b/include/pacemaker-internal.h index 2e75d09..bf33f3e 100644 --- a/include/pacemaker-internal.h +++ b/include/pacemaker-internal.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 the Pacemaker project contributors + * Copyright 2019-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -14,6 +14,7 @@ # include # include # include +# include # include # include # include diff --git a/include/pcmki/Makefile.am b/include/pcmki/Makefile.am index 7aa64c7..446c801 100644 --- a/include/pcmki/Makefile.am +++ b/include/pcmki/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2019 the Pacemaker project contributors +# Copyright 2019-2021 the Pacemaker project contributors # # The version control history for this file may have further details. # @@ -13,6 +13,7 @@ noinst_HEADERS = pcmki_error.h \ pcmki_cluster_queries.h \ pcmki_fence.h \ pcmki_output.h \ + pcmki_resource.h \ pcmki_sched_allocate.h \ pcmki_sched_notif.h \ pcmki_sched_utils.h \ diff --git a/include/pcmki/pcmki_resource.h b/include/pcmki/pcmki_resource.h new file mode 100644 index 0000000..effa945 --- /dev/null +++ b/include/pcmki/pcmki_resource.h @@ -0,0 +1,14 @@ +/* + * Copyright 2021 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ +#ifndef PCMKI_RESOURCE__H +#define PCMKI_RESOURCE__H + +#include + +#endif /* PCMK_RESOURCE__H */ diff --git a/lib/pacemaker/Makefile.am b/lib/pacemaker/Makefile.am index 4129ade..760c04a 100644 --- a/lib/pacemaker/Makefile.am +++ b/lib/pacemaker/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2004-2019 the Pacemaker project contributors +# Copyright 2004-2021 the Pacemaker project contributors # # The version control history for this file may have further details. # @@ -31,6 +31,7 @@ libpacemaker_la_SOURCES = libpacemaker_la_SOURCES += pcmk_cluster_queries.c libpacemaker_la_SOURCES += pcmk_fence.c libpacemaker_la_SOURCES += pcmk_output.c +libpacemaker_la_SOURCES += pcmk_resource.c libpacemaker_la_SOURCES += pcmk_sched_allocate.c libpacemaker_la_SOURCES += pcmk_sched_bundle.c libpacemaker_la_SOURCES += pcmk_sched_clone.c diff --git a/lib/pacemaker/pcmk_resource.c b/lib/pacemaker/pcmk_resource.c new file mode 100644 index 0000000..05614fc --- /dev/null +++ b/lib/pacemaker/pcmk_resource.c @@ -0,0 +1,21 @@ +/* + * Copyright 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 -- 1.8.3.1 From e45fe95cc6f526ab67ab6f718aa1364861ca525b Mon Sep 17 00:00:00 2001 From: Ken Gaillot Date: Tue, 15 Sep 2020 15:32:25 -0500 Subject: [PATCH 4/6] API: libpacemaker: new API pcmk_resource_digests() --- include/pacemaker.h | 22 +++++++- include/pcmki/pcmki_resource.h | 7 +++ lib/pacemaker/pcmk_output.c | 107 +++++++++++++++++++++++++++++++++++- lib/pacemaker/pcmk_resource.c | 119 +++++++++++++++++++++++++++++++++++++++++ xml/Makefile.am | 10 +++- xml/api/digests-2.6.rng | 33 ++++++++++++ 6 files changed, 293 insertions(+), 5 deletions(-) create mode 100644 xml/api/digests-2.6.rng diff --git a/include/pacemaker.h b/include/pacemaker.h index b2a73cd..51bf585 100644 --- a/include/pacemaker.h +++ b/include/pacemaker.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 the Pacemaker project contributors + * Copyright 2019-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -20,8 +20,11 @@ extern "C" { * \ingroup pacemaker */ -# include +# include # include +# include + +# include /*! * \brief Get controller status @@ -55,6 +58,21 @@ int pcmk_designated_controller(xmlNodePtr *xml, unsigned int message_timeout_ms) */ int pcmk_pacemakerd_status(xmlNodePtr *xml, char *ipc_name, unsigned int message_timeout_ms); +/*! + * \brief Calculate and output resource operation digests + * + * \param[out] xml Where to store XML with result + * \param[in] rsc Resource to calculate digests for + * \param[in] node Node whose operation history should be used + * \param[in] overrides Hash table of configuration parameters to override + * \param[in] data_set Cluster working set (with status) + * + * \return Standard Pacemaker return code + */ +int pcmk_resource_digests(xmlNodePtr *xml, pe_resource_t *rsc, + pe_node_t *node, GHashTable *overrides, + pe_working_set_t *data_set); + #ifdef BUILD_PUBLIC_LIBPACEMAKER /*! diff --git a/include/pcmki/pcmki_resource.h b/include/pcmki/pcmki_resource.h index effa945..9d2afb5 100644 --- a/include/pcmki/pcmki_resource.h +++ b/include/pcmki/pcmki_resource.h @@ -9,6 +9,13 @@ #ifndef PCMKI_RESOURCE__H #define PCMKI_RESOURCE__H +#include + #include +#include + +int pcmk__resource_digests(pcmk__output_t *out, pe_resource_t *rsc, + pe_node_t *node, GHashTable *overrides, + pe_working_set_t *data_set); #endif /* PCMK_RESOURCE__H */ diff --git a/lib/pacemaker/pcmk_output.c b/lib/pacemaker/pcmk_output.c index 500afd1..bc4b91a 100644 --- a/lib/pacemaker/pcmk_output.c +++ b/lib/pacemaker/pcmk_output.c @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the Pacemaker project contributors + * Copyright 2019-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -539,6 +540,108 @@ crmadmin_node_xml(pcmk__output_t *out, va_list args) return pcmk_rc_ok; } +PCMK__OUTPUT_ARGS("digests", "pe_resource_t *", "pe_node_t *", "const char *", + "guint", "op_digest_cache_t *") +static int +digests_text(pcmk__output_t *out, va_list args) +{ + pe_resource_t *rsc = va_arg(args, pe_resource_t *); + pe_node_t *node = va_arg(args, pe_node_t *); + const char *task = va_arg(args, const char *); + guint interval_ms = va_arg(args, guint); + op_digest_cache_t *digests = va_arg(args, op_digest_cache_t *); + + char *action_desc = NULL; + const char *rsc_desc = "unknown resource"; + const char *node_desc = "unknown node"; + + if (interval_ms != 0) { + action_desc = crm_strdup_printf("%ums-interval %s action", interval_ms, + ((task == NULL)? "unknown" : task)); + } else if (pcmk__str_eq(task, "monitor", pcmk__str_none)) { + action_desc = strdup("probe action"); + } else { + action_desc = crm_strdup_printf("%s action", + ((task == NULL)? "unknown" : task)); + } + if ((rsc != NULL) && (rsc->id != NULL)) { + rsc_desc = rsc->id; + } + if ((node != NULL) && (node->details->uname != NULL)) { + node_desc = node->details->uname; + } + out->begin_list(out, NULL, NULL, "Digests for %s %s on %s", + rsc_desc, action_desc, node_desc); + free(action_desc); + + if (digests == NULL) { + out->list_item(out, NULL, "none"); + out->end_list(out); + return pcmk_rc_ok; + } + if (digests->digest_all_calc != NULL) { + out->list_item(out, NULL, "%s (all parameters)", + digests->digest_all_calc); + } + if (digests->digest_secure_calc != NULL) { + out->list_item(out, NULL, "%s (non-private parameters)", + digests->digest_secure_calc); + } + if (digests->digest_restart_calc != NULL) { + out->list_item(out, NULL, "%s (non-reloadable parameters)", + digests->digest_restart_calc); + } + out->end_list(out); + return pcmk_rc_ok; +} + +static void +add_digest_xml(xmlNode *parent, const char *type, const char *digest, + xmlNode *digest_source) +{ + if (digest != NULL) { + xmlNodePtr digest_xml = create_xml_node(parent, "digest"); + + crm_xml_add(digest_xml, "type", ((type == NULL)? "unspecified" : type)); + crm_xml_add(digest_xml, "hash", digest); + if (digest_source != NULL) { + add_node_copy(digest_xml, digest_source); + } + } +} + +PCMK__OUTPUT_ARGS("digests", "pe_resource_t *", "pe_node_t *", "const char *", + "guint", "op_digest_cache_t *") +static int +digests_xml(pcmk__output_t *out, va_list args) +{ + pe_resource_t *rsc = va_arg(args, pe_resource_t *); + pe_node_t *node = va_arg(args, pe_node_t *); + const char *task = va_arg(args, const char *); + guint interval_ms = va_arg(args, guint); + op_digest_cache_t *digests = va_arg(args, op_digest_cache_t *); + + char *interval_s = crm_strdup_printf("%ums", interval_ms); + xmlNode *xml = NULL; + + xml = pcmk__output_create_xml_node(out, "digests", + "resource", crm_str(rsc->id), + "node", crm_str(node->details->uname), + "task", crm_str(task), + "interval", interval_s, + NULL); + free(interval_s); + if (digests != NULL) { + add_digest_xml(xml, "all", digests->digest_all_calc, + digests->params_all); + add_digest_xml(xml, "nonprivate", digests->digest_secure_calc, + digests->params_secure); + add_digest_xml(xml, "nonreloadable", digests->digest_restart_calc, + digests->params_restart); + } + return pcmk_rc_ok; +} + static pcmk__message_entry_t fmt_functions[] = { { "rsc-is-colocated-with-list", "default", rsc_is_colocated_with_list }, { "rsc-is-colocated-with-list", "xml", rsc_is_colocated_with_list_xml }, @@ -557,6 +660,8 @@ static pcmk__message_entry_t fmt_functions[] = { { "crmadmin-node-list", "default", crmadmin_node_list }, { "crmadmin-node", "default", crmadmin_node_text }, { "crmadmin-node", "xml", crmadmin_node_xml }, + { "digests", "default", digests_text }, + { "digests", "xml", digests_xml }, { NULL, NULL, NULL } }; diff --git a/lib/pacemaker/pcmk_resource.c b/lib/pacemaker/pcmk_resource.c index 05614fc..197edf8 100644 --- a/lib/pacemaker/pcmk_resource.c +++ b/lib/pacemaker/pcmk_resource.c @@ -9,6 +9,7 @@ #include +#include #include #include @@ -19,3 +20,121 @@ #include #include + +// Search path for resource operation history (takes node name and resource ID) +#define XPATH_OP_HISTORY "//" XML_CIB_TAG_STATUS \ + "/" XML_CIB_TAG_STATE "[@" XML_ATTR_UNAME "='%s']" \ + "/" XML_CIB_TAG_LRM "/" XML_LRM_TAG_RESOURCES \ + "/" XML_LRM_TAG_RESOURCE "[@" XML_ATTR_ID "='%s']" + +static xmlNode * +best_op(pe_resource_t *rsc, pe_node_t *node, pe_working_set_t *data_set) +{ + char *xpath = NULL; + xmlNode *history = NULL; + xmlNode *best = NULL; + + // Find node's resource history + xpath = crm_strdup_printf(XPATH_OP_HISTORY, node->details->uname, rsc->id); + history = get_xpath_object(xpath, data_set->input, LOG_NEVER); + free(xpath); + + // Examine each history entry + for (xmlNode *lrm_rsc_op = first_named_child(history, XML_LRM_TAG_RSC_OP); + lrm_rsc_op != NULL; lrm_rsc_op = crm_next_same_xml(lrm_rsc_op)) { + + const char *digest = crm_element_value(lrm_rsc_op, + XML_LRM_ATTR_RESTART_DIGEST); + guint interval_ms = 0; + + crm_element_value_ms(lrm_rsc_op, XML_LRM_ATTR_INTERVAL, &interval_ms); + + if (pcmk__ends_with(ID(lrm_rsc_op), "_last_failure_0") + || (interval_ms != 0)) { + + // Only use last failure or recurring op if nothing else available + if (best == NULL) { + best = lrm_rsc_op; + } + continue; + } + + best = lrm_rsc_op; + if (digest != NULL) { + // Any non-recurring action with a restart digest is sufficient + break; + } + } + return best; +} + +/*! + * \internal + * \brief Calculate and output resource operation digests + * + * \param[in] out Output object + * \param[in] rsc Resource to calculate digests for + * \param[in] node Node whose operation history should be used + * \param[in] overrides Hash table of configuration parameters to override + * \param[in] data_set Cluster working set (with status) + * + * \return Standard Pacemaker return code + */ +int +pcmk__resource_digests(pcmk__output_t *out, pe_resource_t *rsc, + pe_node_t *node, GHashTable *overrides, + pe_working_set_t *data_set) +{ + const char *task = NULL; + xmlNode *xml_op = NULL; + op_digest_cache_t *digests = NULL; + guint interval_ms = 0; + int rc = pcmk_rc_ok; + + if ((out == NULL) || (rsc == NULL) || (node == NULL) || (data_set == NULL)) { + return EINVAL; + } + if (rsc->variant != pe_native) { + // Only primitives get operation digests + return EOPNOTSUPP; + } + + // Find XML of operation history to use + xml_op = best_op(rsc, node, data_set); + + // Generate an operation key + if (xml_op != NULL) { + task = crm_element_value(xml_op, XML_LRM_ATTR_TASK); + crm_element_value_ms(xml_op, XML_LRM_ATTR_INTERVAL_MS, &interval_ms); + } + if (task == NULL) { // Assume start if no history is available + task = RSC_START; + interval_ms = 0; + } + + // Calculate and show digests + digests = pe__calculate_digests(rsc, task, &interval_ms, node, xml_op, + overrides, true, data_set); + rc = out->message(out, "digests", rsc, node, task, interval_ms, digests); + + pe__free_digests(digests); + return rc; +} + +int +pcmk_resource_digests(xmlNodePtr *xml, pe_resource_t *rsc, + pe_node_t *node, GHashTable *overrides, + pe_working_set_t *data_set) +{ + pcmk__output_t *out = NULL; + int rc = pcmk_rc_ok; + + rc = pcmk__out_prologue(&out, xml); + if (rc != pcmk_rc_ok) { + return rc; + } + pcmk__register_lib_messages(out); + rc = pcmk__resource_digests(out, rsc, node, overrides, data_set); + pcmk__out_epilogue(out, xml, rc); + return rc; +} diff --git a/xml/Makefile.am b/xml/Makefile.am index e7b9a51..cb6cfa0 100644 --- a/xml/Makefile.am +++ b/xml/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2004-2019 the Pacemaker project contributors +# Copyright 2004-2021 the Pacemaker project contributors # # The version control history for this file may have further details. # @@ -50,7 +50,13 @@ version_pairs_last = $(wordlist \ # problems. # Names of API schemas that form the choices for pacemaker-result content -API_request_base = command-output crm_mon crm_resource crmadmin stonith_admin version +API_request_base = command-output \ + crm_mon \ + crm_resource \ + crmadmin \ + digests \ + stonith_admin \ + version # Names of CIB schemas that form the choices for cib/configuration content CIB_cfg_base = options nodes resources constraints fencing acls tags alerts diff --git a/xml/api/digests-2.6.rng b/xml/api/digests-2.6.rng new file mode 100644 index 0000000..7e843d4 --- /dev/null +++ b/xml/api/digests-2.6.rng @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- 1.8.3.1 From 4e726eb67c8eed255ee83706ed13cd7ea31f9864 Mon Sep 17 00:00:00 2001 From: Ken Gaillot Date: Mon, 14 Sep 2020 16:29:41 -0500 Subject: [PATCH 5/6] Feature: tools: add crm_resource --digests option This is not particularly useful for end users but can help during development, and can be used by higher-level tools to bypass Pacemaker's configuration change detection (with obvious risks). --- tools/crm_resource.c | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/tools/crm_resource.c b/tools/crm_resource.c index 2717a62..8c7247a 100644 --- a/tools/crm_resource.c +++ b/tools/crm_resource.c @@ -40,6 +40,7 @@ enum rsc_command { cmd_cts, cmd_delete, cmd_delete_param, + cmd_digests, cmd_execute_agent, cmd_fail, cmd_get_param, @@ -158,6 +159,8 @@ gboolean validate_or_force_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean restart_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); +gboolean digests_cb(const gchar *option_name, const gchar *optarg, + gpointer data, GError **error); gboolean wait_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean why_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); @@ -507,6 +510,14 @@ static GOptionEntry advanced_entries[] = { { "wait", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, wait_cb, "(Advanced) Wait until the cluster settles into a stable state", NULL }, + { "digests", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, digests_cb, + "(Advanced) Show parameter hashes that Pacemaker uses to detect\n" + INDENT "configuration changes (only accurate if there is resource\n" + INDENT "history on the specified node). Required: --resource, --node.\n" + INDENT "Optional: any NAME=VALUE parameters will be used to override\n" + INDENT "the configuration (to see what the hash would be with those\n" + INDENT "changes).", + NULL }, { "force-demote", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, validate_or_force_cb, "(Advanced) Bypass the cluster and demote a resource on the local\n" @@ -893,7 +904,9 @@ validate_or_force_cb(const gchar *option_name, const gchar *optarg, } options.operation = g_strdup(option_name + 2); // skip "--" options.find_flags = pe_find_renamed|pe_find_anon; - options.override_params = crm_str_table_new(); + if (options.override_params == NULL) { + options.override_params = crm_str_table_new(); + } return TRUE; } @@ -907,6 +920,20 @@ restart_cb(const gchar *option_name, const gchar *optarg, gpointer data, } gboolean +digests_cb(const gchar *option_name, const gchar *optarg, gpointer data, + GError **error) +{ + SET_COMMAND(cmd_digests); + options.find_flags = pe_find_renamed|pe_find_anon; + if (options.override_params == NULL) { + options.override_params = crm_str_table_new(); + } + options.require_node = TRUE; + options.require_dataset = TRUE; + return TRUE; +} + +gboolean wait_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { SET_COMMAND(cmd_wait); options.require_resource = FALSE; @@ -1819,6 +1846,16 @@ main(int argc, char **argv) } break; + case cmd_digests: + node = pe_find_node(data_set->nodes, options.host_uname); + if (node == NULL) { + rc = pcmk_rc_node_unknown; + } else { + rc = pcmk__resource_digests(out, rsc, node, + options.override_params, data_set); + } + break; + case cmd_colocations: rc = out->message(out, "stacks-constraints", rsc, data_set, false); break; -- 1.8.3.1 From bb34d07013bed2e71ac9cedb4b1631ad5e2825bf Mon Sep 17 00:00:00 2001 From: Ken Gaillot Date: Mon, 23 Nov 2020 12:17:31 -0600 Subject: [PATCH 6/6] Test: cts-cli: add regression tests for crm_resource --digests --- cts/Makefile.am | 3 +- cts/cli/crm_resource_digests.xml | 143 +++++++++++++++++++++++++++++++++++++++ cts/cli/regression.tools.exp | 34 ++++++++++ cts/cts-cli.in | 14 +++- 4 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 cts/cli/crm_resource_digests.xml diff --git a/cts/Makefile.am b/cts/Makefile.am index 5666a9f..de02aed 100644 --- a/cts/Makefile.am +++ b/cts/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2001-2019 the Pacemaker project contributors +# Copyright 2001-2021 the Pacemaker project contributors # # The version control history for this file may have further details. # @@ -65,6 +65,7 @@ dist_cli_DATA = cli/constraints.xml \ cli/crm_diff_old.xml \ cli/crm_mon.xml \ cli/crm_mon-partial.xml \ + cli/crm_resource_digests.xml \ cli/regression.acls.exp \ cli/regression.crm_mon.exp \ cli/regression.dates.exp \ diff --git a/cts/cli/crm_resource_digests.xml b/cts/cli/crm_resource_digests.xml new file mode 100644 index 0000000..074ca3d --- /dev/null +++ b/cts/cli/crm_resource_digests.xml @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cts/cli/regression.tools.exp b/cts/cli/regression.tools.exp index a85b7d6..510cc0a 100644 --- a/cts/cli/regression.tools.exp +++ b/cts/cli/regression.tools.exp @@ -4019,3 +4019,37 @@ Resources colocated with clone: =#=#=#= End test: Recursively check locations and constraints for clone in XML - OK (0) =#=#=#= * Passed: crm_resource - Recursively check locations and constraints for clone in XML +=#=#=#= Begin test: Show resource digests =#=#=#= + + + + + + + + + + + + + + +=#=#=#= End test: Show resource digests - OK (0) =#=#=#= +* Passed: crm_resource - Show resource digests +=#=#=#= Begin test: Show resource digests with overrides =#=#=#= + + + + + + + + + + + + + + +=#=#=#= End test: Show resource digests with overrides - OK (0) =#=#=#= +* Passed: crm_resource - Show resource digests with overrides diff --git a/cts/cts-cli.in b/cts/cts-cli.in index dfdd3de..96f5386 100755 --- a/cts/cts-cli.in +++ b/cts/cts-cli.in @@ -1,6 +1,6 @@ #!@BASH_PATH@ # -# Copyright 2008-2020 the Pacemaker project contributors +# Copyright 2008-2021 the Pacemaker project contributors # # The version control history for this file may have further details. # @@ -791,6 +791,18 @@ function test_tools() { done unset CIB_file + + export CIB_file="$test_home/cli/crm_resource_digests.xml" + + desc="Show resource digests" + cmd="crm_resource --digests -r rsc1 -N node1 --output-as=xml" + test_assert $CRM_EX_OK 0 + + desc="Show resource digests with overrides" + cmd="$cmd CRM_meta_interval=10000 CRM_meta_timeout=20000" + test_assert $CRM_EX_OK 0 + + unset CIB_file } INVALID_PERIODS=( -- 1.8.3.1