summaryrefslogtreecommitdiffstats
path: root/tools/crm_resource.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 06:53:20 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 06:53:20 +0000
commite5a812082ae033afb1eed82c0f2df3d0f6bdc93f (patch)
treea6716c9275b4b413f6c9194798b34b91affb3cc7 /tools/crm_resource.c
parentInitial commit. (diff)
downloadpacemaker-e5a812082ae033afb1eed82c0f2df3d0f6bdc93f.tar.xz
pacemaker-e5a812082ae033afb1eed82c0f2df3d0f6bdc93f.zip
Adding upstream version 2.1.6.upstream/2.1.6
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools/crm_resource.c')
-rw-r--r--tools/crm_resource.c2182
1 files changed, 2182 insertions, 0 deletions
diff --git a/tools/crm_resource.c b/tools/crm_resource.c
new file mode 100644
index 0000000..f351c26
--- /dev/null
+++ b/tools/crm_resource.c
@@ -0,0 +1,2182 @@
+/*
+ * Copyright 2004-2023 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 <crm_internal.h>
+
+#include <crm_resource.h>
+#include <crm/lrmd_internal.h>
+#include <crm/common/cmdline_internal.h>
+#include <crm/common/ipc_attrd_internal.h>
+#include <crm/common/lists_internal.h>
+#include <crm/common/output.h>
+#include <pacemaker-internal.h>
+
+#include <sys/param.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <time.h>
+
+#include <crm/crm.h>
+#include <crm/stonith-ng.h>
+#include <crm/common/ipc_controld.h>
+#include <crm/cib/internal.h>
+
+#define SUMMARY "crm_resource - perform tasks related to Pacemaker cluster resources"
+
+enum rsc_command {
+ cmd_none = 0, // No command option given (yet)
+ cmd_ban,
+ cmd_cleanup,
+ cmd_clear,
+ cmd_colocations,
+ cmd_cts,
+ cmd_delete,
+ cmd_delete_param,
+ cmd_digests,
+ cmd_execute_agent,
+ cmd_fail,
+ cmd_get_param,
+ cmd_get_property,
+ cmd_list_active_ops,
+ cmd_list_agents,
+ cmd_list_all_ops,
+ cmd_list_alternatives,
+ cmd_list_instances,
+ cmd_list_providers,
+ cmd_list_resources,
+ cmd_list_standards,
+ cmd_locate,
+ cmd_metadata,
+ cmd_move,
+ cmd_query_raw_xml,
+ cmd_query_xml,
+ cmd_refresh,
+ cmd_restart,
+ cmd_set_param,
+ cmd_set_property,
+ cmd_wait,
+ cmd_why,
+};
+
+struct {
+ 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
+ gboolean require_node; // Whether command requires node 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; // --promoted 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, utilization, or element 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
+ int check_level; // Optional value of --validate or --force-check
+
+ // 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,
+ .check_level = -1,
+ .cib_options = cib_sync_call,
+ .require_cib = TRUE,
+ .require_dataset = TRUE,
+ .require_resource = TRUE,
+};
+
+#if 0
+// @COMPAT @TODO enable this at next backward compatibility break
+#define SET_COMMAND(cmd) do { \
+ if (options.rsc_cmd != cmd_none) { \
+ g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_USAGE, \
+ "Only one command option may be specified"); \
+ return FALSE; \
+ } \
+ options.rsc_cmd = (cmd); \
+ } while (0)
+#else
+#define SET_COMMAND(cmd) do { \
+ if (options.rsc_cmd != cmd_none) { \
+ reset_options(); \
+ } \
+ options.rsc_cmd = (cmd); \
+ } while (0)
+#endif
+
+gboolean agent_provider_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
+gboolean attr_set_type_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
+gboolean class_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
+gboolean cleanup_refresh_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
+gboolean delete_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
+gboolean expired_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
+gboolean list_agents_cb(const gchar *option_name, const gchar *optarg,
+ gpointer data, GError **error);
+gboolean list_providers_cb(const gchar *option_name, const gchar *optarg,
+ gpointer data, GError **error);
+gboolean list_standards_cb(const gchar *option_name, const gchar *optarg,
+ gpointer data, GError **error);
+gboolean list_alternatives_cb(const gchar *option_name, const gchar *optarg,
+ gpointer data, GError **error);
+gboolean metadata_cb(const gchar *option_name, const gchar *optarg,
+ gpointer data, GError **error);
+gboolean option_cb(const gchar *option_name, const gchar *optarg,
+ gpointer data, GError **error);
+gboolean fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
+gboolean flag_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
+gboolean get_param_prop_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
+gboolean list_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
+gboolean set_delete_param_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
+gboolean set_prop_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
+gboolean timeout_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
+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);
+
+static crm_exit_t exit_code = CRM_EX_OK;
+static pcmk__output_t *out = NULL;
+static pcmk__common_args_t *args = NULL;
+
+// Things that should be cleaned up on exit
+static GError *error = NULL;
+static GMainLoop *mainloop = NULL;
+static cib_t *cib_conn = NULL;
+static pcmk_ipc_api_t *controld_api = NULL;
+static pe_working_set_t *data_set = NULL;
+
+#define MESSAGE_TIMEOUT_S 60
+
+#define INDENT " "
+
+static pcmk__supported_format_t formats[] = {
+ PCMK__SUPPORTED_FORMAT_NONE,
+ PCMK__SUPPORTED_FORMAT_TEXT,
+ PCMK__SUPPORTED_FORMAT_XML,
+ { NULL, NULL, NULL }
+};
+
+// Clean up and exit
+static crm_exit_t
+bye(crm_exit_t ec)
+{
+ pcmk__output_and_clear_error(&error, out);
+
+ if (out != NULL) {
+ out->finish(out, ec, true, NULL);
+ pcmk__output_free(out);
+ }
+ pcmk__unregister_formats();
+
+ if (cib_conn != NULL) {
+ cib_t *save_cib_conn = cib_conn;
+
+ cib_conn = NULL; // Ensure we can't free this twice
+ cib__clean_up_connection(&save_cib_conn);
+ }
+
+ if (controld_api != NULL) {
+ pcmk_ipc_api_t *save_controld_api = controld_api;
+
+ controld_api = NULL; // Ensure we can't free this twice
+ pcmk_free_ipc_api(save_controld_api);
+ }
+
+ if (mainloop != NULL) {
+ g_main_loop_unref(mainloop);
+ mainloop = NULL;
+ }
+
+ pe_free_working_set(data_set);
+ data_set = NULL;
+ crm_exit(ec);
+ return ec;
+}
+
+static void
+quit_main_loop(crm_exit_t ec)
+{
+ exit_code = ec;
+ if (mainloop != NULL) {
+ GMainLoop *mloop = mainloop;
+
+ mainloop = NULL; // Don't re-enter this block
+ pcmk_quit_main_loop(mloop, 10);
+ g_main_loop_unref(mloop);
+ }
+}
+
+static gboolean
+resource_ipc_timeout(gpointer data)
+{
+ // Start with newline because "Waiting for ..." message doesn't have one
+ if (error != NULL) {
+ g_clear_error(&error);
+ }
+
+ g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_TIMEOUT,
+ _("Aborting because no messages received in %d seconds"), MESSAGE_TIMEOUT_S);
+
+ quit_main_loop(CRM_EX_TIMEOUT);
+ return FALSE;
+}
+
+static void
+controller_event_callback(pcmk_ipc_api_t *api, enum pcmk_ipc_event event_type,
+ crm_exit_t status, void *event_data, void *user_data)
+{
+ switch (event_type) {
+ case pcmk_ipc_event_disconnect:
+ if (exit_code == CRM_EX_DISCONNECT) { // Unexpected
+ crm_info("Connection to controller was terminated");
+ }
+ quit_main_loop(exit_code);
+ break;
+
+ case pcmk_ipc_event_reply:
+ if (status != CRM_EX_OK) {
+ out->err(out, "Error: bad reply from controller: %s",
+ crm_exit_str(status));
+ pcmk_disconnect_ipc(api);
+ quit_main_loop(status);
+ } else {
+ if ((pcmk_controld_api_replies_expected(api) == 0)
+ && mainloop && g_main_loop_is_running(mainloop)) {
+ out->info(out, "... got reply (done)");
+ crm_debug("Got all the replies we expected");
+ pcmk_disconnect_ipc(api);
+ quit_main_loop(CRM_EX_OK);
+ } else {
+ out->info(out, "... got reply");
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+start_mainloop(pcmk_ipc_api_t *capi)
+{
+ unsigned int count = pcmk_controld_api_replies_expected(capi);
+
+ if (count > 0) {
+ out->info(out, "Waiting for %u %s from the controller",
+ count, pcmk__plural_alt(count, "reply", "replies"));
+ exit_code = CRM_EX_DISCONNECT; // For unexpected disconnects
+ mainloop = g_main_loop_new(NULL, FALSE);
+ g_timeout_add(MESSAGE_TIMEOUT_S * 1000, resource_ipc_timeout, NULL);
+ g_main_loop_run(mainloop);
+ }
+}
+
+static int
+compare_id(gconstpointer a, gconstpointer b)
+{
+ return strcmp((const char *)a, (const char *)b);
+}
+
+static GList *
+build_constraint_list(xmlNode *root)
+{
+ GList *retval = NULL;
+ xmlNode *cib_constraints = NULL;
+ xmlXPathObjectPtr xpathObj = NULL;
+ int ndx = 0;
+
+ cib_constraints = pcmk_find_cib_element(root, XML_CIB_TAG_CONSTRAINTS);
+ xpathObj = xpath_search(cib_constraints, "//" XML_CONS_TAG_RSC_LOCATION);
+
+ for (ndx = 0; ndx < numXpathResults(xpathObj); ndx++) {
+ xmlNode *match = getXpathResult(xpathObj, ndx);
+ retval = g_list_insert_sorted(retval, (gpointer) ID(match), compare_id);
+ }
+
+ freeXpathObject(xpathObj);
+ return retval;
+}
+
+/* short option letters still available: eEJkKXyYZ */
+
+static GOptionEntry query_entries[] = {
+ { "list", 'L', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_cb,
+ "List all cluster resources with status",
+ NULL },
+ { "list-raw", 'l', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_cb,
+ "List IDs of all instantiated resources (individual members\n"
+ INDENT "rather than groups etc.)",
+ NULL },
+ { "list-cts", 'c', G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_cb,
+ NULL,
+ NULL },
+ { "list-operations", 'O', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_cb,
+ "List active resource operations, optionally filtered by\n"
+ INDENT "--resource and/or --node",
+ NULL },
+ { "list-all-operations", 'o', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_cb,
+ "List all resource operations, optionally filtered by\n"
+ INDENT "--resource and/or --node",
+ NULL },
+ { "list-standards", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
+ list_standards_cb,
+ "List supported standards",
+ NULL },
+ { "list-ocf-providers", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
+ list_providers_cb,
+ "List all available OCF providers",
+ NULL },
+ { "list-agents", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK,
+ list_agents_cb,
+ "List all agents available for the named standard and/or provider",
+ "STD:PROV" },
+ { "list-ocf-alternatives", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK,
+ list_alternatives_cb,
+ "List all available providers for the named OCF agent",
+ "AGENT" },
+ { "show-metadata", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK,
+ metadata_cb,
+ "Show the metadata for the named class:provider:agent",
+ "SPEC" },
+ { "query-xml", 'q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb,
+ "Show XML configuration of resource (after any template expansion)",
+ NULL },
+ { "query-xml-raw", 'w', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb,
+ "Show XML configuration of resource (before any template expansion)",
+ NULL },
+ { "get-parameter", 'g', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, get_param_prop_cb,
+ "Display named parameter for resource (use instance attribute\n"
+ INDENT "unless --element, --meta, or --utilization is specified)",
+ "PARAM" },
+ { "get-property", 'G', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, get_param_prop_cb,
+ "Display named property of resource ('class', 'type', or 'provider') "
+ "(requires --resource)",
+ "PROPERTY" },
+ { "locate", 'W', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb,
+ "Show node(s) currently running resource",
+ NULL },
+ { "constraints", 'a', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb,
+ "Display the location and colocation constraints that apply to a\n"
+ INDENT "resource, and if --recursive is specified, to the resources\n"
+ INDENT "directly or indirectly involved in those colocations.\n"
+ INDENT "If the named resource is part of a group, or a clone or\n"
+ INDENT "bundle instance, constraints for the collective resource\n"
+ INDENT "will be shown unless --force is given.",
+ NULL },
+ { "stack", 'A', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb,
+ "Equivalent to --constraints --recursive",
+ NULL },
+ { "why", 'Y', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, why_cb,
+ "Show why resources are not running, optionally filtered by\n"
+ INDENT "--resource and/or --node",
+ NULL },
+
+ { NULL }
+};
+
+static GOptionEntry command_entries[] = {
+ { "validate", 0, G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK,
+ validate_or_force_cb,
+ "Validate resource configuration by calling agent's validate-all\n"
+ INDENT "action. The configuration may be specified either by giving an\n"
+ INDENT "existing resource name with -r, or by specifying --class,\n"
+ INDENT "--agent, and --provider arguments, along with any number of\n"
+ INDENT "--option arguments. An optional LEVEL argument can be given\n"
+ INDENT "to control the level of checking performed.",
+ "LEVEL" },
+ { "cleanup", 'C', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, cleanup_refresh_cb,
+ "If resource has any past failures, clear its history and fail\n"
+ INDENT "count. Optionally filtered by --resource, --node, --operation\n"
+ INDENT "and --interval (otherwise all). --operation and --interval\n"
+ INDENT "apply to fail counts, but entire history is always clear, to\n"
+ INDENT "allow current state to be rechecked. If the named resource is\n"
+ INDENT "part of a group, or one numbered instance of a clone or bundled\n"
+ INDENT "resource, the clean-up applies to the whole collective resource\n"
+ INDENT "unless --force is given.",
+ NULL },
+ { "refresh", 'R', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, cleanup_refresh_cb,
+ "Delete resource's history (including failures) so its current state\n"
+ INDENT "is rechecked. Optionally filtered by --resource and --node\n"
+ INDENT "(otherwise all). If the named resource is part of a group, or one\n"
+ INDENT "numbered instance of a clone or bundled resource, the refresh\n"
+ INDENT "applies to the whole collective resource unless --force is given.",
+ NULL },
+ { "set-parameter", 'p', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, set_delete_param_cb,
+ "Set named parameter for resource (requires -v). Use instance\n"
+ INDENT "attribute unless --element, --meta, or --utilization is "
+ "specified.",
+ "PARAM" },
+ { "delete-parameter", 'd', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, set_delete_param_cb,
+ "Delete named parameter for resource. Use instance attribute\n"
+ INDENT "unless --element, --meta or, --utilization is specified.",
+ "PARAM" },
+ { "set-property", 'S', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, set_prop_cb,
+ "Set named property of resource ('class', 'type', or 'provider') "
+ "(requires -r, -t, -v)",
+ "PROPERTY" },
+
+ { NULL }
+};
+
+static GOptionEntry location_entries[] = {
+ { "move", 'M', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb,
+ "Create a constraint to move resource. If --node is specified,\n"
+ INDENT "the constraint will be to move to that node, otherwise it\n"
+ INDENT "will be to ban the current node. Unless --force is specified\n"
+ INDENT "this will return an error if the resource is already running\n"
+ INDENT "on the specified node. If --force is specified, this will\n"
+ INDENT "always ban the current node.\n"
+ INDENT "Optional: --lifetime, --promoted. NOTE: This may prevent the\n"
+ INDENT "resource from running on its previous location until the\n"
+ INDENT "implicit constraint expires or is removed with --clear.",
+ NULL },
+ { "ban", 'B', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb,
+ "Create a constraint to keep resource off a node.\n"
+ INDENT "Optional: --node, --lifetime, --promoted.\n"
+ INDENT "NOTE: This will prevent the resource from running on the\n"
+ INDENT "affected node until the implicit constraint expires or is\n"
+ INDENT "removed with --clear. If --node is not specified, it defaults\n"
+ INDENT "to the node currently running the resource for primitives\n"
+ INDENT "and groups, or the promoted instance of promotable clones with\n"
+ INDENT "promoted-max=1 (all other situations result in an error as\n"
+ INDENT "there is no sane default).",
+ NULL },
+ { "clear", 'U', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb,
+ "Remove all constraints created by the --ban and/or --move\n"
+ INDENT "commands. Requires: --resource. Optional: --node, --promoted,\n"
+ INDENT "--expired. If --node is not specified, all constraints created\n"
+ INDENT "by --ban and --move will be removed for the named resource. If\n"
+ INDENT "--node and --force are specified, any constraint created by\n"
+ INDENT "--move will be cleared, even if it is not for the specified\n"
+ INDENT "node. If --expired is specified, only those constraints whose\n"
+ INDENT "lifetimes have expired will be removed.",
+ NULL },
+ { "expired", 'e', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, expired_cb,
+ "Modifies the --clear argument to remove constraints with\n"
+ INDENT "expired lifetimes.",
+ NULL },
+ { "lifetime", 'u', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.move_lifetime,
+ "Lifespan (as ISO 8601 duration) of created constraints (with\n"
+ INDENT "-B, -M) see https://en.wikipedia.org/wiki/ISO_8601#Durations)",
+ "TIMESPEC" },
+ { "promoted", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
+ &options.promoted_role_only,
+ "Limit scope of command to promoted role (with -B, -M, -U). For\n"
+ INDENT "-B and -M, previously promoted instances may remain\n"
+ INDENT "active in the unpromoted role.",
+ NULL },
+
+ // Deprecated since 2.1.0
+ { "master", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
+ &options.promoted_role_only,
+ "Deprecated: Use --promoted instead", NULL },
+
+ { NULL }
+};
+
+static GOptionEntry advanced_entries[] = {
+ { "delete", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, delete_cb,
+ "(Advanced) Delete a resource from the CIB. Required: -t",
+ NULL },
+ { "fail", 'F', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, fail_cb,
+ "(Advanced) Tell the cluster this resource has failed",
+ NULL },
+ { "restart", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, restart_cb,
+ "(Advanced) Tell the cluster to restart this resource and\n"
+ INDENT "anything that depends on it",
+ NULL },
+ { "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"
+ INDENT "node. Unless --force is specified, this will refuse to do so if\n"
+ INDENT "the cluster believes the resource is a clone instance already\n"
+ INDENT "running on the local node.",
+ NULL },
+ { "force-stop", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
+ validate_or_force_cb,
+ "(Advanced) Bypass the cluster and stop a resource on the local node",
+ NULL },
+ { "force-start", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
+ validate_or_force_cb,
+ "(Advanced) Bypass the cluster and start a resource on the local\n"
+ INDENT "node. Unless --force is specified, this will refuse to do so if\n"
+ INDENT "the cluster believes the resource is a clone instance already\n"
+ INDENT "running on the local node.",
+ NULL },
+ { "force-promote", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
+ validate_or_force_cb,
+ "(Advanced) Bypass the cluster and promote a resource on the local\n"
+ INDENT "node. Unless --force is specified, this will refuse to do so if\n"
+ INDENT "the cluster believes the resource is a clone instance already\n"
+ INDENT "running on the local node.",
+ NULL },
+ { "force-check", 0, G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK,
+ validate_or_force_cb,
+ "(Advanced) Bypass the cluster and check the state of a resource on\n"
+ INDENT "the local node. An optional LEVEL argument can be given\n"
+ INDENT "to control the level of checking performed.",
+ "LEVEL" },
+
+ { NULL }
+};
+
+static GOptionEntry addl_entries[] = {
+ { "node", 'N', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.host_uname,
+ "Node name",
+ "NAME" },
+ { "recursive", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.recursive,
+ "Follow colocation chains when using --set-parameter or --constraints",
+ NULL },
+ { "resource-type", 't', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.rsc_type,
+ "Resource XML element (primitive, group, etc.) (with -D)",
+ "ELEMENT" },
+ { "parameter-value", 'v', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.prop_value,
+ "Value to use with -p",
+ "PARAM" },
+ { "meta", 'm', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, attr_set_type_cb,
+ "Use resource meta-attribute instead of instance attribute\n"
+ INDENT "(with -p, -g, -d)",
+ NULL },
+ { "utilization", 'z', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, attr_set_type_cb,
+ "Use resource utilization attribute instead of instance attribute\n"
+ INDENT "(with -p, -g, -d)",
+ NULL },
+ { "element", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, attr_set_type_cb,
+ "Use resource element attribute instead of instance attribute\n"
+ INDENT "(with -p, -g, -d)",
+ NULL },
+ { "operation", 'n', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.operation,
+ "Operation to clear instead of all (with -C -r)",
+ "OPERATION" },
+ { "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" },
+ { "nvpair", 'i', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.prop_id,
+ "(Advanced) XML ID of nvpair element to use (with -p, -d)",
+ "ID" },
+ { "timeout", 'T', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, timeout_cb,
+ "(Advanced) Abort if command does not finish in this time (with\n"
+ INDENT "--restart, --wait, --force-*)",
+ "N" },
+ { "force", 'f', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.force,
+ "Force the action to be performed. See help for individual commands for\n"
+ INDENT "additional behavior.",
+ NULL },
+ { "xml-file", 'x', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_FILENAME, &options.xml_file,
+ NULL,
+ "FILE" },
+ { "host-uname", 'H', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.host_uname,
+ NULL,
+ "HOST" },
+
+ { NULL }
+};
+
+static void
+reset_options(void) {
+ options.require_crmd = FALSE;
+ options.require_node = FALSE;
+
+ options.require_cib = TRUE;
+ options.require_dataset = TRUE;
+ options.require_resource = TRUE;
+
+ options.find_flags = 0;
+}
+
+gboolean
+agent_provider_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ options.cmdline_config = TRUE;
+ options.require_resource = FALSE;
+
+ if (pcmk__str_eq(option_name, "--provider", pcmk__str_casei)) {
+ pcmk__str_update(&options.v_provider, optarg);
+ } else {
+ pcmk__str_update(&options.v_agent, optarg);
+ }
+
+ return TRUE;
+}
+
+gboolean
+attr_set_type_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ if (pcmk__str_any_of(option_name, "-m", "--meta", NULL)) {
+ options.attr_set_type = XML_TAG_META_SETS;
+ } else if (pcmk__str_any_of(option_name, "-z", "--utilization", NULL)) {
+ options.attr_set_type = XML_TAG_UTILIZATION;
+ } else if (pcmk__str_eq(option_name, "--element", pcmk__str_casei)) {
+ options.attr_set_type = ATTR_SET_ELEMENT;
+ }
+ return TRUE;
+}
+
+gboolean
+class_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ pcmk__str_update(&options.v_class, optarg);
+ options.cmdline_config = TRUE;
+ options.require_resource = FALSE;
+ return TRUE;
+}
+
+gboolean
+cleanup_refresh_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ if (pcmk__str_any_of(option_name, "-C", "--cleanup", NULL)) {
+ SET_COMMAND(cmd_cleanup);
+ } else {
+ SET_COMMAND(cmd_refresh);
+ }
+
+ options.require_resource = FALSE;
+ if (getenv("CIB_file") == NULL) {
+ options.require_crmd = TRUE;
+ }
+ options.find_flags = pe_find_renamed|pe_find_anon;
+ return TRUE;
+}
+
+gboolean
+delete_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ SET_COMMAND(cmd_delete);
+ options.require_dataset = FALSE;
+ options.find_flags = pe_find_renamed|pe_find_any;
+ return TRUE;
+}
+
+gboolean
+expired_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ options.clear_expired = TRUE;
+ options.require_resource = FALSE;
+ return TRUE;
+}
+
+static void
+get_agent_spec(const gchar *optarg)
+{
+ options.require_cib = FALSE;
+ options.require_dataset = FALSE;
+ options.require_resource = FALSE;
+ pcmk__str_update(&options.agent_spec, optarg);
+}
+
+gboolean
+list_agents_cb(const gchar *option_name, const gchar *optarg, gpointer data,
+ GError **error)
+{
+ SET_COMMAND(cmd_list_agents);
+ get_agent_spec(optarg);
+ return TRUE;
+}
+
+gboolean
+list_providers_cb(const gchar *option_name, const gchar *optarg, gpointer data,
+ GError **error)
+{
+ SET_COMMAND(cmd_list_providers);
+ get_agent_spec(optarg);
+ return TRUE;
+}
+
+gboolean
+list_standards_cb(const gchar *option_name, const gchar *optarg, gpointer data,
+ GError **error)
+{
+ SET_COMMAND(cmd_list_standards);
+ options.require_cib = FALSE;
+ options.require_dataset = FALSE;
+ options.require_resource = FALSE;
+ return TRUE;
+}
+
+gboolean
+list_alternatives_cb(const gchar *option_name, const gchar *optarg,
+ gpointer data, GError **error)
+{
+ SET_COMMAND(cmd_list_alternatives);
+ get_agent_spec(optarg);
+ return TRUE;
+}
+
+gboolean
+metadata_cb(const gchar *option_name, const gchar *optarg, gpointer data,
+ GError **error)
+{
+ SET_COMMAND(cmd_metadata);
+ get_agent_spec(optarg);
+ return TRUE;
+}
+
+gboolean
+option_cb(const gchar *option_name, const gchar *optarg, gpointer data,
+ GError **error)
+{
+ char *name = NULL;
+ char *value = NULL;
+
+ if (pcmk__scan_nvpair(optarg, &name, &value) != 2) {
+ return FALSE;
+ }
+ if (options.cmdline_params == NULL) {
+ options.cmdline_params = pcmk__strkey_table(free, free);
+ }
+ g_hash_table_replace(options.cmdline_params, name, value);
+ return TRUE;
+}
+
+gboolean
+fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ SET_COMMAND(cmd_fail);
+ options.require_crmd = TRUE;
+ options.require_node = TRUE;
+ return TRUE;
+}
+
+gboolean
+flag_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ if (pcmk__str_any_of(option_name, "-U", "--clear", NULL)) {
+ SET_COMMAND(cmd_clear);
+ options.find_flags = pe_find_renamed|pe_find_anon;
+ } else if (pcmk__str_any_of(option_name, "-B", "--ban", NULL)) {
+ SET_COMMAND(cmd_ban);
+ options.find_flags = pe_find_renamed|pe_find_anon;
+ } else if (pcmk__str_any_of(option_name, "-M", "--move", NULL)) {
+ SET_COMMAND(cmd_move);
+ options.find_flags = pe_find_renamed|pe_find_anon;
+ } else if (pcmk__str_any_of(option_name, "-q", "--query-xml", NULL)) {
+ SET_COMMAND(cmd_query_xml);
+ options.find_flags = pe_find_renamed|pe_find_any;
+ } else if (pcmk__str_any_of(option_name, "-w", "--query-xml-raw", NULL)) {
+ SET_COMMAND(cmd_query_raw_xml);
+ options.find_flags = pe_find_renamed|pe_find_any;
+ } else if (pcmk__str_any_of(option_name, "-W", "--locate", NULL)) {
+ SET_COMMAND(cmd_locate);
+ options.find_flags = pe_find_renamed|pe_find_anon;
+
+ } else if (pcmk__str_any_of(option_name, "-a", "--constraints", NULL)) {
+ SET_COMMAND(cmd_colocations);
+ options.find_flags = pe_find_renamed|pe_find_anon;
+
+ } else if (pcmk__str_any_of(option_name, "-A", "--stack", NULL)) {
+ SET_COMMAND(cmd_colocations);
+ options.find_flags = pe_find_renamed|pe_find_anon;
+ options.recursive = TRUE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+get_param_prop_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ if (pcmk__str_any_of(option_name, "-g", "--get-parameter", NULL)) {
+ SET_COMMAND(cmd_get_param);
+ } else {
+ SET_COMMAND(cmd_get_property);
+ }
+
+ pcmk__str_update(&options.prop_name, optarg);
+ options.find_flags = pe_find_renamed|pe_find_any;
+ return TRUE;
+}
+
+gboolean
+list_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ if (pcmk__str_any_of(option_name, "-c", "--list-cts", NULL)) {
+ SET_COMMAND(cmd_cts);
+ } else if (pcmk__str_any_of(option_name, "-L", "--list", NULL)) {
+ SET_COMMAND(cmd_list_resources);
+ } else if (pcmk__str_any_of(option_name, "-l", "--list-raw", NULL)) {
+ SET_COMMAND(cmd_list_instances);
+ } else if (pcmk__str_any_of(option_name, "-O", "--list-operations", NULL)) {
+ SET_COMMAND(cmd_list_active_ops);
+ } else {
+ SET_COMMAND(cmd_list_all_ops);
+ }
+
+ options.require_resource = FALSE;
+ return TRUE;
+}
+
+gboolean
+set_delete_param_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ if (pcmk__str_any_of(option_name, "-p", "--set-parameter", NULL)) {
+ SET_COMMAND(cmd_set_param);
+ } else {
+ SET_COMMAND(cmd_delete_param);
+ }
+
+ pcmk__str_update(&options.prop_name, optarg);
+ options.find_flags = pe_find_renamed|pe_find_any;
+ return TRUE;
+}
+
+gboolean
+set_prop_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ SET_COMMAND(cmd_set_property);
+ options.require_dataset = FALSE;
+ pcmk__str_update(&options.prop_name, optarg);
+ options.find_flags = pe_find_renamed|pe_find_any;
+ return TRUE;
+}
+
+gboolean
+timeout_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ options.timeout_ms = crm_get_msec(optarg);
+ return TRUE;
+}
+
+gboolean
+validate_or_force_cb(const gchar *option_name, const gchar *optarg,
+ gpointer data, GError **error)
+{
+ SET_COMMAND(cmd_execute_agent);
+ if (options.operation) {
+ g_free(options.operation);
+ }
+ options.operation = g_strdup(option_name + 2); // skip "--"
+ options.find_flags = pe_find_renamed|pe_find_anon;
+ if (options.override_params == NULL) {
+ options.override_params = pcmk__strkey_table(free, free);
+ }
+
+ if (optarg != NULL) {
+ if (pcmk__scan_min_int(optarg, &options.check_level, 0) != pcmk_rc_ok) {
+ g_set_error(error, G_OPTION_ERROR, CRM_EX_INVALID_PARAM,
+ _("Invalid check level setting: %s"), optarg);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+gboolean
+restart_cb(const gchar *option_name, const gchar *optarg, gpointer data,
+ GError **error)
+{
+ SET_COMMAND(cmd_restart);
+ options.find_flags = pe_find_renamed|pe_find_anon;
+ return TRUE;
+}
+
+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 = pcmk__strkey_table(free, free);
+ }
+ 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;
+ options.require_dataset = FALSE;
+ return TRUE;
+}
+
+gboolean
+why_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ SET_COMMAND(cmd_why);
+ options.require_resource = FALSE;
+ options.find_flags = pe_find_renamed|pe_find_anon;
+ return TRUE;
+}
+
+static int
+ban_or_move(pcmk__output_t *out, pe_resource_t *rsc, const char *move_lifetime)
+{
+ int rc = pcmk_rc_ok;
+ pe_node_t *current = NULL;
+ unsigned int nactive = 0;
+
+ CRM_CHECK(rsc != NULL, return EINVAL);
+
+ current = pe__find_active_requires(rsc, &nactive);
+
+ if (nactive == 1) {
+ rc = cli_resource_ban(out, options.rsc_id, current->details->uname, move_lifetime, NULL,
+ cib_conn, options.cib_options, options.promoted_role_only);
+
+ } else if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) {
+ int count = 0;
+ GList *iter = NULL;
+
+ current = NULL;
+ for(iter = rsc->children; iter; iter = iter->next) {
+ pe_resource_t *child = (pe_resource_t *)iter->data;
+ enum rsc_role_e child_role = child->fns->state(child, TRUE);
+
+ if (child_role == RSC_ROLE_PROMOTED) {
+ count++;
+ current = pe__current_node(child);
+ }
+ }
+
+ if(count == 1 && current) {
+ rc = cli_resource_ban(out, options.rsc_id, current->details->uname, move_lifetime, NULL,
+ cib_conn, options.cib_options, options.promoted_role_only);
+
+ } else {
+ rc = EINVAL;
+ g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
+ _("Resource '%s' not moved: active in %d locations (promoted in %d).\n"
+ "To prevent '%s' from running on a specific location, "
+ "specify a node."
+ "To prevent '%s' from being promoted at a specific "
+ "location, specify a node and the --promoted option."),
+ options.rsc_id, nactive, count, options.rsc_id, options.rsc_id);
+ }
+
+ } else {
+ rc = EINVAL;
+ g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
+ _("Resource '%s' not moved: active in %d locations.\n"
+ "To prevent '%s' from running on a specific location, "
+ "specify a node."),
+ options.rsc_id, nactive, options.rsc_id);
+ }
+
+ return rc;
+}
+
+static void
+cleanup(pcmk__output_t *out, pe_resource_t *rsc, pe_node_t *node)
+{
+ int rc = pcmk_rc_ok;
+
+ if (options.force == FALSE) {
+ rsc = uber_parent(rsc);
+ }
+
+ crm_debug("Erasing failures of %s (%s requested) on %s",
+ rsc->id, options.rsc_id, (options.host_uname? options.host_uname: "all nodes"));
+ rc = cli_resource_delete(controld_api, options.host_uname, rsc, options.operation,
+ options.interval_spec, TRUE, data_set, options.force);
+
+ if ((rc == pcmk_rc_ok) && !out->is_quiet(out)) {
+ // Show any reasons why resource might stay stopped
+ cli_resource_check(out, rsc, node);
+ }
+
+ if (rc == pcmk_rc_ok) {
+ start_mainloop(controld_api);
+ }
+}
+
+static int
+clear_constraints(pcmk__output_t *out, xmlNodePtr *cib_xml_copy)
+{
+ GList *before = NULL;
+ GList *after = NULL;
+ GList *remaining = NULL;
+ GList *ele = NULL;
+ pe_node_t *dest = NULL;
+ int rc = pcmk_rc_ok;
+
+ if (!out->is_quiet(out)) {
+ before = build_constraint_list(data_set->input);
+ }
+
+ if (options.clear_expired) {
+ rc = cli_resource_clear_all_expired(data_set->input, cib_conn, options.cib_options,
+ options.rsc_id, options.host_uname,
+ options.promoted_role_only);
+
+ } else if (options.host_uname) {
+ dest = pe_find_node(data_set->nodes, options.host_uname);
+ if (dest == NULL) {
+ rc = pcmk_rc_node_unknown;
+ if (!out->is_quiet(out)) {
+ g_list_free(before);
+ }
+ return rc;
+ }
+ rc = cli_resource_clear(options.rsc_id, dest->details->uname, NULL,
+ cib_conn, options.cib_options, TRUE, options.force);
+
+ } else {
+ rc = cli_resource_clear(options.rsc_id, NULL, data_set->nodes,
+ cib_conn, options.cib_options, TRUE, options.force);
+ }
+
+ if (!out->is_quiet(out)) {
+ rc = cib_conn->cmds->query(cib_conn, NULL, cib_xml_copy, cib_scope_local | cib_sync_call);
+ rc = pcmk_legacy2rc(rc);
+
+ if (rc != pcmk_rc_ok) {
+ g_set_error(&error, PCMK__RC_ERROR, rc,
+ _("Could not get modified CIB: %s\n"), pcmk_strerror(rc));
+ g_list_free(before);
+ free_xml(*cib_xml_copy);
+ *cib_xml_copy = NULL;
+ return rc;
+ }
+
+ data_set->input = *cib_xml_copy;
+ cluster_status(data_set);
+
+ after = build_constraint_list(data_set->input);
+ remaining = pcmk__subtract_lists(before, after, (GCompareFunc) strcmp);
+
+ for (ele = remaining; ele != NULL; ele = ele->next) {
+ out->info(out, "Removing constraint: %s", (char *) ele->data);
+ }
+
+ g_list_free(before);
+ g_list_free(after);
+ g_list_free(remaining);
+ }
+
+ return rc;
+}
+
+static int
+delete(void)
+{
+ int rc = pcmk_rc_ok;
+ xmlNode *msg_data = NULL;
+
+ if (options.rsc_type == NULL) {
+ rc = ENXIO;
+ g_set_error(&error, PCMK__RC_ERROR, rc,
+ _("You need to specify a resource type with -t"));
+ return rc;
+ }
+
+ msg_data = create_xml_node(NULL, options.rsc_type);
+ crm_xml_add(msg_data, XML_ATTR_ID, options.rsc_id);
+
+ rc = cib_conn->cmds->remove(cib_conn, XML_CIB_TAG_RESOURCES, msg_data,
+ options.cib_options);
+ rc = pcmk_legacy2rc(rc);
+ free_xml(msg_data);
+ return rc;
+}
+
+static int
+list_agents(pcmk__output_t *out, const char *agent_spec)
+{
+ int rc = pcmk_rc_ok;
+ char *provider = strchr(agent_spec, ':');
+ lrmd_t *lrmd_conn = NULL;
+ lrmd_list_t *list = NULL;
+
+ rc = lrmd__new(&lrmd_conn, NULL, NULL, 0);
+ if (rc != pcmk_rc_ok) {
+ goto error;
+ }
+
+ if (provider) {
+ *provider++ = 0;
+ }
+
+ rc = lrmd_conn->cmds->list_agents(lrmd_conn, &list, agent_spec, provider);
+
+ if (rc > 0) {
+ rc = out->message(out, "agents-list", list, agent_spec, provider);
+ } else {
+ rc = pcmk_rc_error;
+ }
+
+error:
+ if (rc != pcmk_rc_ok) {
+ if (provider == NULL) {
+ g_set_error(&error, PCMK__RC_ERROR, rc,
+ _("No agents found for standard '%s'"), agent_spec);
+ } else {
+ g_set_error(&error, PCMK__RC_ERROR, rc,
+ _("No agents found for standard '%s' and provider '%s'"),
+ agent_spec, provider);
+ }
+ }
+
+ lrmd_api_delete(lrmd_conn);
+ return rc;
+}
+
+static int
+list_providers(pcmk__output_t *out, const char *agent_spec)
+{
+ int rc;
+ const char *text = NULL;
+ lrmd_t *lrmd_conn = NULL;
+ lrmd_list_t *list = NULL;
+
+ rc = lrmd__new(&lrmd_conn, NULL, NULL, 0);
+ if (rc != pcmk_rc_ok) {
+ goto error;
+ }
+
+ switch (options.rsc_cmd) {
+ case cmd_list_alternatives:
+ rc = lrmd_conn->cmds->list_ocf_providers(lrmd_conn, agent_spec, &list);
+
+ if (rc > 0) {
+ rc = out->message(out, "alternatives-list", list, agent_spec);
+ } else {
+ rc = pcmk_rc_error;
+ }
+
+ text = "OCF providers";
+ break;
+ case cmd_list_standards:
+ rc = lrmd_conn->cmds->list_standards(lrmd_conn, &list);
+
+ if (rc > 0) {
+ rc = out->message(out, "standards-list", list);
+ } else {
+ rc = pcmk_rc_error;
+ }
+
+ text = "standards";
+ break;
+ case cmd_list_providers:
+ rc = lrmd_conn->cmds->list_ocf_providers(lrmd_conn, agent_spec, &list);
+
+ if (rc > 0) {
+ rc = out->message(out, "providers-list", list, agent_spec);
+ } else {
+ rc = pcmk_rc_error;
+ }
+
+ text = "OCF providers";
+ break;
+ default:
+ g_set_error(&error, PCMK__RC_ERROR, pcmk_rc_error, "Bug");
+ lrmd_api_delete(lrmd_conn);
+ return pcmk_rc_error;
+ }
+
+error:
+ if (rc != pcmk_rc_ok) {
+ if (agent_spec != NULL) {
+ rc = ENXIO;
+ g_set_error(&error, PCMK__RC_ERROR, rc,
+ _("No %s found for %s"), text, agent_spec);
+
+ } else {
+ rc = ENXIO;
+ g_set_error(&error, PCMK__RC_ERROR, rc,
+ _("No %s found"), text);
+ }
+ }
+
+ lrmd_api_delete(lrmd_conn);
+ return rc;
+}
+
+static int
+populate_working_set(xmlNodePtr *cib_xml_copy)
+{
+ int rc = pcmk_rc_ok;
+
+ if (options.xml_file != NULL) {
+ *cib_xml_copy = filename2xml(options.xml_file);
+ if (*cib_xml_copy == NULL) {
+ rc = pcmk_rc_cib_corrupt;
+ }
+ } else {
+ rc = cib_conn->cmds->query(cib_conn, NULL, cib_xml_copy, cib_scope_local | cib_sync_call);
+ rc = pcmk_legacy2rc(rc);
+ }
+
+ if (rc == pcmk_rc_ok) {
+ data_set = pe_new_working_set();
+ if (data_set == NULL) {
+ rc = ENOMEM;
+ } else {
+ pe__set_working_set_flags(data_set,
+ pe_flag_no_counts|pe_flag_no_compat);
+ data_set->priv = out;
+ rc = update_working_set_xml(data_set, cib_xml_copy);
+ }
+ }
+
+ if (rc != pcmk_rc_ok) {
+ free_xml(*cib_xml_copy);
+ *cib_xml_copy = NULL;
+ return rc;
+ }
+
+ cluster_status(data_set);
+ return pcmk_rc_ok;
+}
+
+static int
+refresh(pcmk__output_t *out)
+{
+ int rc = pcmk_rc_ok;
+ const char *router_node = options.host_uname;
+ int attr_options = pcmk__node_attr_none;
+
+ if (options.host_uname) {
+ pe_node_t *node = pe_find_node(data_set->nodes, options.host_uname);
+
+ if (pe__is_guest_or_remote_node(node)) {
+ node = pe__current_node(node->details->remote_rsc);
+ if (node == NULL) {
+ rc = ENXIO;
+ g_set_error(&error, PCMK__RC_ERROR, rc,
+ _("No cluster connection to Pacemaker Remote node %s detected"),
+ options.host_uname);
+ return rc;
+ }
+ router_node = node->details->uname;
+ attr_options |= pcmk__node_attr_remote;
+ }
+ }
+
+ if (controld_api == NULL) {
+ out->info(out, "Dry run: skipping clean-up of %s due to CIB_file",
+ options.host_uname? options.host_uname : "all nodes");
+ rc = pcmk_rc_ok;
+ return rc;
+ }
+
+ crm_debug("Re-checking the state of all resources on %s", options.host_uname?options.host_uname:"all nodes");
+
+ rc = pcmk__attrd_api_clear_failures(NULL, options.host_uname, NULL,
+ NULL, NULL, NULL, attr_options);
+
+ if (pcmk_controld_api_reprobe(controld_api, options.host_uname,
+ router_node) == pcmk_rc_ok) {
+ start_mainloop(controld_api);
+ }
+
+ return rc;
+}
+
+static void
+refresh_resource(pcmk__output_t *out, pe_resource_t *rsc, pe_node_t *node)
+{
+ int rc = pcmk_rc_ok;
+
+ if (options.force == FALSE) {
+ rsc = uber_parent(rsc);
+ }
+
+ crm_debug("Re-checking the state of %s (%s requested) on %s",
+ rsc->id, options.rsc_id, (options.host_uname? options.host_uname: "all nodes"));
+ rc = cli_resource_delete(controld_api, options.host_uname, rsc, NULL, 0,
+ FALSE, data_set, options.force);
+
+ if ((rc == pcmk_rc_ok) && !out->is_quiet(out)) {
+ // Show any reasons why resource might stay stopped
+ cli_resource_check(out, rsc, node);
+ }
+
+ if (rc == pcmk_rc_ok) {
+ start_mainloop(controld_api);
+ }
+}
+
+static int
+set_property(void)
+{
+ int rc = pcmk_rc_ok;
+ xmlNode *msg_data = NULL;
+
+ if (pcmk__str_empty(options.rsc_type)) {
+ g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
+ _("Must specify -t with resource type"));
+ rc = ENXIO;
+ return rc;
+
+ } else if (pcmk__str_empty(options.prop_value)) {
+ g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
+ _("Must supply -v with new value"));
+ rc = ENXIO;
+ return rc;
+ }
+
+ CRM_LOG_ASSERT(options.prop_name != NULL);
+
+ msg_data = create_xml_node(NULL, options.rsc_type);
+ crm_xml_add(msg_data, XML_ATTR_ID, options.rsc_id);
+ crm_xml_add(msg_data, options.prop_name, options.prop_value);
+
+ rc = cib_conn->cmds->modify(cib_conn, XML_CIB_TAG_RESOURCES, msg_data,
+ options.cib_options);
+ rc = pcmk_legacy2rc(rc);
+ free_xml(msg_data);
+
+ return rc;
+}
+
+static int
+show_metadata(pcmk__output_t *out, const char *agent_spec)
+{
+ int rc = pcmk_rc_ok;
+ char *standard = NULL;
+ char *provider = NULL;
+ char *type = NULL;
+ char *metadata = NULL;
+ lrmd_t *lrmd_conn = NULL;
+
+ rc = lrmd__new(&lrmd_conn, NULL, NULL, 0);
+ if (rc != pcmk_rc_ok) {
+ g_set_error(&error, PCMK__RC_ERROR, rc,
+ _("Could not create executor connection"));
+ lrmd_api_delete(lrmd_conn);
+ return rc;
+ }
+
+ rc = crm_parse_agent_spec(agent_spec, &standard, &provider, &type);
+ rc = pcmk_legacy2rc(rc);
+
+ if (rc == pcmk_rc_ok) {
+ rc = lrmd_conn->cmds->get_metadata(lrmd_conn, standard,
+ provider, type,
+ &metadata, 0);
+ rc = pcmk_legacy2rc(rc);
+
+ if (metadata) {
+ out->output_xml(out, "metadata", metadata);
+ free(metadata);
+ } else {
+ /* We were given a validly formatted spec, but it doesn't necessarily
+ * match up with anything that exists. Use ENXIO as the return code
+ * here because that maps to an exit code of CRM_EX_NOSUCH, which
+ * probably is the most common reason to get here.
+ */
+ rc = ENXIO;
+ g_set_error(&error, PCMK__RC_ERROR, rc,
+ _("Metadata query for %s failed: %s"),
+ agent_spec, pcmk_rc_str(rc));
+ }
+ } else {
+ rc = ENXIO;
+ g_set_error(&error, PCMK__RC_ERROR, rc,
+ _("'%s' is not a valid agent specification"), agent_spec);
+ }
+
+ lrmd_api_delete(lrmd_conn);
+ return rc;
+}
+
+static void
+validate_cmdline_config(void)
+{
+ // 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"));
+
+ // 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 can only be used with "
+ "--validate and --force-*"));
+
+ // 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
+ // is valid.
+ } else if (pcmk__str_eq(options.v_class, "stonith", pcmk__str_none)) {
+ if (options.v_provider != NULL) {
+ g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
+ _("stonith does not support providers"));
+
+ } else if (stonith_agent_exists(options.v_agent, 0) == FALSE) {
+ g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
+ _("%s is not a known stonith agent"), options.v_agent ? options.v_agent : "");
+ }
+
+ } else if (resources_agent_exists(options.v_class, options.v_provider, options.v_agent) == FALSE) {
+ g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
+ _("%s:%s:%s is not a known resource"),
+ options.v_class ? options.v_class : "",
+ options.v_provider ? options.v_provider : "",
+ options.v_agent ? options.v_agent : "");
+ }
+
+ if (error != NULL) {
+ return;
+ }
+
+ if (options.cmdline_params == NULL) {
+ options.cmdline_params = pcmk__strkey_table(free, free);
+ }
+ options.require_resource = FALSE;
+ options.require_dataset = FALSE;
+ options.require_cib = FALSE;
+}
+
+static GOptionContext *
+build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
+ GOptionContext *context = NULL;
+
+ GOptionEntry extra_prog_entries[] = {
+ { "quiet", 'Q', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &(args->quiet),
+ "Be less descriptive in output.",
+ NULL },
+ { "resource", 'r', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.rsc_id,
+ "Resource ID",
+ "ID" },
+ { G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING_ARRAY, &options.remainder,
+ NULL,
+ NULL },
+
+ { NULL }
+ };
+
+ const char *description = "Examples:\n\n"
+ "List the available OCF agents:\n\n"
+ "\t# crm_resource --list-agents ocf\n\n"
+ "List the available OCF agents from the linux-ha project:\n\n"
+ "\t# crm_resource --list-agents ocf:heartbeat\n\n"
+ "Move 'myResource' to a specific node:\n\n"
+ "\t# crm_resource --resource myResource --move --node altNode\n\n"
+ "Allow (but not force) 'myResource' to move back to its original "
+ "location:\n\n"
+ "\t# crm_resource --resource myResource --clear\n\n"
+ "Stop 'myResource' (and anything that depends on it):\n\n"
+ "\t# crm_resource --resource myResource --set-parameter target-role "
+ "--meta --parameter-value Stopped\n\n"
+ "Tell the cluster not to manage 'myResource' (the cluster will not "
+ "attempt to start or stop the\n"
+ "resource under any circumstances; useful when performing maintenance "
+ "tasks on a resource):\n\n"
+ "\t# crm_resource --resource myResource --set-parameter is-managed "
+ "--meta --parameter-value false\n\n"
+ "Erase the operation history of 'myResource' on 'aNode' (the cluster "
+ "will 'forget' the existing\n"
+ "resource state, including any errors, and attempt to recover the"
+ "resource; useful when a resource\n"
+ "had failed permanently and has been repaired by an administrator):\n\n"
+ "\t# crm_resource --resource myResource --cleanup --node aNode\n\n";
+
+ context = pcmk__build_arg_context(args, "text (default), xml", group, NULL);
+ g_option_context_set_description(context, description);
+
+ /* Add the -Q option, which cannot be part of the globally supported options
+ * because some tools use that flag for something else.
+ */
+ pcmk__add_main_args(context, extra_prog_entries);
+
+ pcmk__add_arg_group(context, "queries", "Queries:",
+ "Show query help", query_entries);
+ pcmk__add_arg_group(context, "commands", "Commands:",
+ "Show command help", command_entries);
+ pcmk__add_arg_group(context, "locations", "Locations:",
+ "Show location help", location_entries);
+ pcmk__add_arg_group(context, "advanced", "Advanced:",
+ "Show advanced option help", advanced_entries);
+ pcmk__add_arg_group(context, "additional", "Additional Options:",
+ "Show additional options", addl_entries);
+ return context;
+}
+
+int
+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;
+
+ GOptionGroup *output_group = NULL;
+ gchar **processed_args = NULL;
+ GOptionContext *context = NULL;
+
+ /*
+ * Parse command line arguments
+ */
+
+ args = pcmk__new_common_args(SUMMARY);
+ processed_args = pcmk__cmdline_preproc(argv, "GHINSTdginpstuvx");
+ context = build_arg_context(args, &output_group);
+
+ pcmk__register_formats(output_group, formats);
+ if (!g_option_context_parse_strv(context, &processed_args, &error)) {
+ exit_code = CRM_EX_USAGE;
+ goto done;
+ }
+
+ pcmk__cli_init_logging("crm_resource", args->verbosity);
+
+ rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
+ if (rc != pcmk_rc_ok) {
+ exit_code = CRM_EX_ERROR;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("Error creating output format %s: %s"),
+ args->output_ty, pcmk_rc_str(rc));
+ goto done;
+ }
+
+ pe__register_messages(out);
+ crm_resource_register_messages(out);
+ lrmd__register_messages(out);
+ pcmk__register_lib_messages(out);
+
+ out->quiet = args->quiet;
+
+ crm_log_args(argc, argv);
+
+ /*
+ * Validate option combinations
+ */
+
+ // If the user didn't explicitly specify a command, list resources
+ if (options.rsc_cmd == cmd_none) {
+ options.rsc_cmd = cmd_list_resources;
+ options.require_resource = FALSE;
+ }
+
+ // --expired without --clear/-U doesn't make sense
+ if (options.clear_expired && (options.rsc_cmd != cmd_clear)) {
+ exit_code = CRM_EX_USAGE;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("--expired requires --clear or -U"));
+ goto done;
+ }
+
+ if ((options.remainder != NULL) && (options.override_params != NULL)) {
+ // Commands that use positional arguments will create override_params
+ for (gchar **s = options.remainder; *s; s++) {
+ char *name = calloc(1, strlen(*s));
+ char *value = calloc(1, strlen(*s));
+ int rc = sscanf(*s, "%[^=]=%s", name, value);
+
+ if (rc == 2) {
+ g_hash_table_replace(options.override_params, name, value);
+
+ } else {
+ exit_code = CRM_EX_USAGE;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ _("Error parsing '%s' as a name=value pair"),
+ argv[optind]);
+ free(value);
+ free(name);
+ goto done;
+ }
+ }
+
+ } else if (options.remainder != NULL) {
+ gchar **strv = NULL;
+ gchar *msg = NULL;
+ int i = 1;
+ int len = 0;
+
+ for (gchar **s = options.remainder; *s; s++) {
+ len++;
+ }
+
+ CRM_ASSERT(len > 0);
+
+ /* Add 1 for the strv[0] string below, and add another 1 for the NULL
+ * at the end of the array so g_strjoinv knows when to stop.
+ */
+ strv = calloc(len+2, sizeof(char *));
+ strv[0] = strdup("non-option ARGV-elements:\n");
+
+ for (gchar **s = options.remainder; *s; s++) {
+ strv[i] = crm_strdup_printf("[%d of %d] %s\n", i, len, *s);
+ i++;
+ }
+
+ strv[i] = NULL;
+
+ exit_code = CRM_EX_USAGE;
+ msg = g_strjoinv("", strv);
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "%s", msg);
+ g_free(msg);
+
+ /* Don't try to free the last element, which is just NULL. */
+ for(i = 0; i < len+1; i++) {
+ free(strv[i]);
+ }
+ free(strv);
+
+ goto done;
+ }
+
+ if (pcmk__str_eq(args->output_ty, "xml", pcmk__str_none)) {
+ /* Kind of a hack to display XML lists using a real tag instead of <list>. This just
+ * saves from having to write custom messages to build the lists around all these things
+ */
+ switch (options.rsc_cmd) {
+ case cmd_execute_agent:
+ case cmd_list_resources:
+ case cmd_query_xml:
+ case cmd_query_raw_xml:
+ case cmd_list_active_ops:
+ case cmd_list_all_ops:
+ case cmd_colocations:
+ pcmk__force_args(context, &error, "%s --xml-simple-list --xml-substitute", g_get_prgname());
+ break;
+
+ default:
+ pcmk__force_args(context, &error, "%s --xml-substitute", g_get_prgname());
+ break;
+ }
+ } else if (pcmk__str_eq(args->output_ty, "text", pcmk__str_null_matches)) {
+ if ((options.rsc_cmd == cmd_colocations) ||
+ options.rsc_cmd == cmd_list_resources) {
+ pcmk__force_args(context, &error, "%s --text-fancy", g_get_prgname());
+ }
+ }
+
+ if (args->version) {
+ out->version(out, false);
+ goto done;
+ }
+
+ 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();
+ 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 (options.require_resource && (options.rsc_id == NULL)) {
+ 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)) {
+ 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.find_flags && options.rsc_id) {
+ options.require_dataset = TRUE;
+ }
+
+ // Establish a connection to the CIB if needed
+ if (options.require_cib) {
+ cib_conn = cib_new();
+ if ((cib_conn == NULL) || (cib_conn->cmds == NULL)) {
+ exit_code = CRM_EX_DISCONNECT;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ _("Could not create CIB connection"));
+ goto done;
+ }
+ rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command);
+ rc = pcmk_legacy2rc(rc);
+ if (rc != pcmk_rc_ok) {
+ exit_code = pcmk_rc2exitc(rc);
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ _("Could not connect to the CIB: %s"), pcmk_rc_str(rc));
+ goto done;
+ }
+ }
+
+ /* Populate working set from XML file if specified or CIB query otherwise */
+ if (options.require_dataset) {
+ rc = populate_working_set(&cib_xml_copy);
+ if (rc != pcmk_rc_ok) {
+ exit_code = pcmk_rc2exitc(rc);
+ goto done;
+ }
+ }
+
+ // If command requires that resource exist if specified, find it
+ if (options.find_flags && options.rsc_id) {
+ rsc = pe_find_resource_with_flags(data_set->resources, options.rsc_id,
+ options.find_flags);
+ if (rsc == NULL) {
+ exit_code = CRM_EX_NOSUCH;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ _("Resource '%s' not found"), options.rsc_id);
+ goto done;
+ }
+
+ /* The --ban, --clear, --move, and --restart commands do not work with
+ * instances of clone resourcs.
+ */
+ if (strchr(options.rsc_id, ':') != NULL && pe_rsc_is_clone(rsc->parent) &&
+ (options.rsc_cmd == cmd_ban || options.rsc_cmd == cmd_clear ||
+ options.rsc_cmd == cmd_move || options.rsc_cmd == cmd_restart)) {
+ exit_code = CRM_EX_INVALID_PARAM;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ _("Cannot operate on clone resource instance '%s'"), options.rsc_id);
+ goto done;
+ }
+ }
+
+ // 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);
+
+ if (node == NULL) {
+ exit_code = CRM_EX_NOSUCH;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ _("Node '%s' not found"), options.host_uname);
+ goto done;
+ }
+ }
+
+ // Establish a connection to the controller if needed
+ if (options.require_crmd) {
+ rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld);
+ if (rc != pcmk_rc_ok) {
+ exit_code = pcmk_rc2exitc(rc);
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ _("Error connecting to the controller: %s"), pcmk_rc_str(rc));
+ goto done;
+ }
+ pcmk_register_ipc_callback(controld_api, controller_event_callback,
+ NULL);
+ rc = pcmk_connect_ipc(controld_api, pcmk_ipc_dispatch_main);
+ if (rc != pcmk_rc_ok) {
+ exit_code = pcmk_rc2exitc(rc);
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ _("Error connecting to the controller: %s"), pcmk_rc_str(rc));
+ goto done;
+ }
+ }
+
+ /*
+ * Handle requested command
+ */
+
+ switch (options.rsc_cmd) {
+ case cmd_list_resources: {
+ GList *all = NULL;
+ all = g_list_prepend(all, (gpointer) "*");
+ rc = out->message(out, "resource-list", data_set,
+ pcmk_show_inactive_rscs | pcmk_show_rsc_only | pcmk_show_pending,
+ true, all, all, false);
+ g_list_free(all);
+
+ if (rc == pcmk_rc_no_output) {
+ rc = ENXIO;
+ }
+ break;
+ }
+
+ case cmd_list_instances:
+ rc = out->message(out, "resource-names-list", data_set->resources);
+
+ if (rc != pcmk_rc_ok) {
+ rc = ENXIO;
+ }
+
+ break;
+
+ case cmd_list_standards:
+ case cmd_list_providers:
+ case cmd_list_alternatives:
+ rc = list_providers(out, options.agent_spec);
+ break;
+
+ case cmd_list_agents:
+ rc = list_agents(out, options.agent_spec);
+ break;
+
+ case cmd_metadata:
+ rc = show_metadata(out, options.agent_spec);
+ break;
+
+ case cmd_restart:
+ /* We don't pass data_set because rsc needs to stay valid for the
+ * entire lifetime of cli_resource_restart(), but it will reset and
+ * update the working set multiple times, so it needs to use its own
+ * copy.
+ */
+ rc = cli_resource_restart(out, rsc, node, options.move_lifetime,
+ options.timeout_ms, cib_conn,
+ options.cib_options, options.promoted_role_only,
+ options.force);
+ break;
+
+ case cmd_wait:
+ rc = wait_till_stable(out, options.timeout_ms, cib_conn);
+ break;
+
+ case cmd_execute_agent:
+ if (options.cmdline_config) {
+ exit_code = cli_resource_execute_from_params(out, NULL,
+ options.v_class, options.v_provider, options.v_agent,
+ options.operation, options.cmdline_params,
+ options.override_params, options.timeout_ms,
+ args->verbosity, options.force, options.check_level);
+ } else {
+ exit_code = cli_resource_execute(rsc, options.rsc_id,
+ options.operation, options.override_params,
+ options.timeout_ms, cib_conn, data_set,
+ args->verbosity, options.force, options.check_level);
+ }
+ goto done;
+
+ 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);
+ }
+ break;
+
+ case cmd_colocations:
+ rc = out->message(out, "locations-and-colocations", rsc, data_set,
+ options.recursive, (bool) options.force);
+ break;
+
+ case cmd_cts:
+ rc = pcmk_rc_ok;
+ g_list_foreach(data_set->resources, (GFunc) cli_resource_print_cts, out);
+ cli_resource_print_cts_constraints(data_set);
+ break;
+
+ case cmd_fail:
+ rc = cli_resource_fail(controld_api, options.host_uname,
+ options.rsc_id, data_set);
+ if (rc == pcmk_rc_ok) {
+ start_mainloop(controld_api);
+ }
+ break;
+
+ case cmd_list_active_ops:
+ rc = cli_resource_print_operations(options.rsc_id,
+ options.host_uname, TRUE,
+ data_set);
+ break;
+
+ case cmd_list_all_ops:
+ rc = cli_resource_print_operations(options.rsc_id,
+ options.host_uname, FALSE,
+ data_set);
+ break;
+
+ case cmd_locate: {
+ GList *nodes = cli_resource_search(rsc, options.rsc_id, data_set);
+ rc = out->message(out, "resource-search-list", nodes, options.rsc_id);
+ g_list_free_full(nodes, free);
+ break;
+ }
+
+ case cmd_query_xml:
+ rc = cli_resource_print(rsc, data_set, true);
+ break;
+
+ case cmd_query_raw_xml:
+ rc = cli_resource_print(rsc, data_set, false);
+ break;
+
+ case cmd_why:
+ if ((options.host_uname != NULL) && (node == NULL)) {
+ rc = pcmk_rc_node_unknown;
+ } else {
+ rc = out->message(out, "resource-reasons-list",
+ data_set->resources, rsc, node);
+ }
+ break;
+
+ case cmd_clear:
+ rc = clear_constraints(out, &cib_xml_copy);
+ break;
+
+ case cmd_move:
+ if (options.host_uname == NULL) {
+ rc = ban_or_move(out, rsc, options.move_lifetime);
+ } else {
+ rc = cli_resource_move(rsc, options.rsc_id, options.host_uname,
+ options.move_lifetime, cib_conn,
+ options.cib_options, data_set,
+ options.promoted_role_only,
+ options.force);
+ }
+
+ if (rc == EINVAL) {
+ exit_code = CRM_EX_USAGE;
+ goto done;
+ }
+
+ break;
+
+ case cmd_ban:
+ if (options.host_uname == NULL) {
+ rc = ban_or_move(out, rsc, options.move_lifetime);
+ } else if (node == NULL) {
+ rc = pcmk_rc_node_unknown;
+ } else {
+ rc = cli_resource_ban(out, options.rsc_id, node->details->uname,
+ options.move_lifetime, NULL, cib_conn,
+ options.cib_options,
+ options.promoted_role_only);
+ }
+
+ if (rc == EINVAL) {
+ exit_code = CRM_EX_USAGE;
+ goto done;
+ }
+
+ break;
+
+ case cmd_get_property:
+ rc = out->message(out, "property-list", rsc, options.prop_name);
+ if (rc == pcmk_rc_no_output) {
+ rc = ENXIO;
+ }
+
+ break;
+
+ case cmd_set_property:
+ rc = set_property();
+ break;
+
+ case cmd_get_param: {
+ unsigned int count = 0;
+ GHashTable *params = NULL;
+ pe_node_t *current = rsc->fns->active_node(rsc, &count, NULL);
+ bool free_params = true;
+ const char* value = NULL;
+
+ if (count > 1) {
+ out->err(out, "%s is active on more than one node,"
+ " returning the default value for %s", rsc->id,
+ pcmk__s(options.prop_name, "unspecified property"));
+ current = NULL;
+ }
+
+ crm_debug("Looking up %s in %s", options.prop_name, rsc->id);
+
+ if (pcmk__str_eq(options.attr_set_type, XML_TAG_ATTR_SETS, pcmk__str_none)) {
+ params = pe_rsc_params(rsc, current, data_set);
+ free_params = false;
+
+ value = g_hash_table_lookup(params, options.prop_name);
+
+ } else if (pcmk__str_eq(options.attr_set_type, XML_TAG_META_SETS, pcmk__str_none)) {
+ params = pcmk__strkey_table(free, free);
+ get_meta_attributes(params, rsc, current, data_set);
+
+ value = g_hash_table_lookup(params, options.prop_name);
+
+ } else if (pcmk__str_eq(options.attr_set_type, ATTR_SET_ELEMENT, pcmk__str_none)) {
+
+ value = crm_element_value(rsc->xml, options.prop_name);
+ free_params = false;
+
+ } else {
+ params = pcmk__strkey_table(free, free);
+ pe__unpack_dataset_nvpairs(rsc->xml, XML_TAG_UTILIZATION, NULL, params,
+ NULL, FALSE, data_set);
+
+ value = g_hash_table_lookup(params, options.prop_name);
+ }
+
+ rc = out->message(out, "attribute-list", rsc, options.prop_name, value);
+ if (free_params) {
+ g_hash_table_destroy(params);
+ }
+
+ break;
+ }
+
+ case cmd_set_param:
+ if (pcmk__str_empty(options.prop_value)) {
+ exit_code = CRM_EX_USAGE;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ _("You need to supply a value with the -v option"));
+ goto done;
+ }
+
+ /* coverity[var_deref_model] False positive */
+ rc = cli_resource_update_attribute(rsc, options.rsc_id,
+ options.prop_set,
+ options.attr_set_type,
+ options.prop_id,
+ options.prop_name,
+ options.prop_value,
+ options.recursive, cib_conn,
+ options.cib_options,
+ options.force);
+ break;
+
+ case cmd_delete_param:
+ /* coverity[var_deref_model] False positive */
+ rc = cli_resource_delete_attribute(rsc, options.rsc_id,
+ options.prop_set,
+ options.attr_set_type,
+ options.prop_id,
+ options.prop_name, cib_conn,
+ options.cib_options,
+ options.force);
+ break;
+
+ case cmd_cleanup:
+ if (rsc == NULL) {
+ rc = cli_cleanup_all(controld_api, options.host_uname,
+ options.operation, options.interval_spec,
+ data_set);
+ if (rc == pcmk_rc_ok) {
+ start_mainloop(controld_api);
+ }
+ } else {
+ cleanup(out, rsc, node);
+ }
+ break;
+
+ case cmd_refresh:
+ if (rsc == NULL) {
+ rc = refresh(out);
+ } else {
+ refresh_resource(out, rsc, node);
+ }
+ break;
+
+ case cmd_delete:
+ rc = delete();
+ break;
+
+ default:
+ exit_code = CRM_EX_USAGE;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ _("Unimplemented command: %d"), (int) options.rsc_cmd);
+ goto done;
+ }
+
+ /* Convert rc into an exit code. */
+ if (rc != pcmk_rc_ok && rc != pcmk_rc_no_output) {
+ exit_code = pcmk_rc2exitc(rc);
+ }
+
+ /*
+ * Clean up and exit
+ */
+
+done:
+ /* When we get here, exit_code has been set one of two ways - either at one of
+ * the spots where there's a "goto done" (which itself could have happened either
+ * directly or by calling pcmk_rc2exitc), or just up above after any of the break
+ * statements.
+ *
+ * Thus, we can use just exit_code here to decide what to do.
+ */
+ if (exit_code != CRM_EX_OK && exit_code != CRM_EX_USAGE) {
+ if (error != NULL) {
+ char *msg = crm_strdup_printf("%s\nError performing operation: %s",
+ error->message, crm_exit_str(exit_code));
+ g_clear_error(&error);
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "%s", msg);
+ free(msg);
+ } else {
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ _("Error performing operation: %s"), crm_exit_str(exit_code));
+ }
+ }
+
+ g_free(options.host_uname);
+ g_free(options.interval_spec);
+ g_free(options.move_lifetime);
+ g_free(options.operation);
+ g_free(options.prop_id);
+ free(options.prop_name);
+ g_free(options.prop_set);
+ g_free(options.prop_value);
+ g_free(options.rsc_id);
+ g_free(options.rsc_type);
+ free(options.agent_spec);
+ free(options.v_agent);
+ free(options.v_class);
+ free(options.v_provider);
+ g_free(options.xml_file);
+ g_strfreev(options.remainder);
+
+ if (options.override_params != NULL) {
+ g_hash_table_destroy(options.override_params);
+ }
+
+ /* options.cmdline_params does not need to be destroyed here. See the
+ * comments in cli_resource_execute_from_params.
+ */
+
+ g_strfreev(processed_args);
+ g_option_context_free(context);
+
+ return bye(exit_code);
+}