Blame SOURCES/022-rhbz1872376.patch

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