diff --git a/tools/crm_resource.c b/tools/crm_resource.c index c194811bda..c1c0384421 100644 --- a/tools/crm_resource.c +++ b/tools/crm_resource.c @@ -1,2370 +1,2371 @@ /* * Copyright 2004-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include // bool, true, false #include // uint32_t #include #include #include #include #include #include #include #include #include #include #include // PCMK_RESOURCE_CLASS_* #include #include #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_list_active_ops, cmd_list_agents, cmd_list_all_ops, cmd_list_alternatives, cmd_list_instances, cmd_list_options, cmd_list_providers, cmd_list_resources, cmd_list_standards, cmd_locate, cmd_metadata, cmd_move, cmd_query_xml, cmd_query_xml_raw, cmd_refresh, cmd_restart, cmd_set_param, cmd_wait, cmd_why, // Update this when adding new commands cmd_max = cmd_why, }; /*! * \internal * \brief Handler function for a crm_resource command */ typedef crm_exit_t (*crm_resource_fn_t)(pcmk_resource_t *, pcmk_node_t *, - xmlNode *, pcmk_scheduler_t *); + cib_t *, pcmk_scheduler_t *, xmlNode *); /*! * \internal * \brief Flags to define attributes of a given command * * These attributes may include required command-line options, how to look up a * resource in the scheduler data, whether the command supports clone instances, * etc. */ enum crm_rsc_flags { //! Use \c pcmk_rsc_match_anon_basename when looking up a resource crm_rsc_find_match_anon_basename = (UINT32_C(1) << 0), //! Use \c pcmk_rsc_match_basename when looking up a resource crm_rsc_find_match_basename = (UINT32_C(1) << 1), //! Use \c pcmk_rsc_match_history when looking up a resource crm_rsc_find_match_history = (UINT32_C(1) << 2), //! Fail if \c --resource refers to a particular clone instance crm_rsc_rejects_clone_instance = (UINT32_C(1) << 3), //! Require CIB connection unless resource is specified by agent crm_rsc_requires_cib = (UINT32_C(1) << 4), //! Require controller connection crm_rsc_requires_controller = (UINT32_C(1) << 5), //! Require \c --node argument crm_rsc_requires_node = (UINT32_C(1) << 6), //! Require \c --resource argument crm_rsc_requires_resource = (UINT32_C(1) << 7), //! Require scheduler data unless resource is specified by agent crm_rsc_requires_scheduler = (UINT32_C(1) << 8), }; /*! * \internal * \brief Handler function and flags for a given command */ typedef struct { crm_resource_fn_t fn; //!< Command handler function uint32_t flags; //!< Group of enum crm_rsc_flags } crm_resource_cmd_info_t; struct { enum rsc_command rsc_cmd; // crm_resource command to perform // Command-line option values gchar *rsc_id; // Value of --resource gchar *rsc_type; // Value of --resource-type gboolean all; // --all was given 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 enum pcmk__opt_flags opt_list; // Parsed from --list-options 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) guint timeout_ms; // Parsed from --timeout value char *agent_spec; // Standard and/or provider and/or agent int check_level; // Optional value of --validate or --force-check // Resource configuration specified via command-line arguments gchar *agent; // Value of --agent gchar *class; // Value of --class gchar *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 = PCMK_XE_INSTANCE_ATTRIBUTES, .check_level = -1, .rsc_cmd = cmd_list_resources, // List all resources if no command given }; 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; #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 } }; 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) { crm_exit_t *ec = user_data; pcmk__assert(ec != NULL); switch (event_type) { case pcmk_ipc_event_disconnect: if (exit_code == CRM_EX_DISCONNECT) { // Unexpected crm_info("Connection to controller was terminated"); } *ec = exit_code; quit_main_loop(*ec); 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); *ec = status; quit_main_loop(*ec); } else { if ((pcmk_controld_api_replies_expected(api) == 0) && (mainloop != NULL) && g_main_loop_is_running(mainloop)) { out->info(out, "... got reply (done)"); crm_debug("Got all the replies we expected"); pcmk_disconnect_ipc(api); *ec = CRM_EX_OK; quit_main_loop(*ec); } else { out->info(out, "... got reply"); } } break; default: break; } } static void start_mainloop(pcmk_ipc_api_t *capi) { // @TODO See if we can avoid setting exit_code as a global variable 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); pcmk__create_timer(MESSAGE_TIMEOUT_S * 1000, resource_ipc_timeout, NULL); g_main_loop_run(mainloop); } } 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, PCMK_XE_CONSTRAINTS); xpathObj = xpath_search(cib_constraints, "//" PCMK_XE_RSC_LOCATION); for (ndx = 0; ndx < numXpathResults(xpathObj); ndx++) { xmlNode *match = getXpathResult(xpathObj, ndx); retval = g_list_insert_sorted(retval, (gpointer) pcmk__xe_id(match), (GCompareFunc) g_strcmp0); } freeXpathObject(xpathObj); return retval; } static gboolean validate_opt_list(const gchar *optarg) { if (pcmk__str_eq(optarg, PCMK_VALUE_FENCING, pcmk__str_none)) { options.opt_list = pcmk__opt_fencing; } else if (pcmk__str_eq(optarg, PCMK__VALUE_PRIMITIVE, pcmk__str_none)) { options.opt_list = pcmk__opt_primitive; } else { return FALSE; } return TRUE; } // GOptionArgFunc callback functions static 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 = PCMK_XE_META_ATTRIBUTES; } else if (pcmk__str_any_of(option_name, "-z", "--utilization", NULL)) { options.attr_set_type = PCMK_XE_UTILIZATION; } else if (pcmk__str_eq(option_name, "--element", pcmk__str_none)) { options.attr_set_type = ATTR_SET_ELEMENT; } return TRUE; } /*! * \internal * \brief Process options that set the command * * Nothing else should set \c options.rsc_cmd. * * \param[in] option_name Name of the option being parsed * \param[in] optarg Value to be parsed * \param[in] data Ignored * \param[out] error Where to store recoverable error, if any * * \return \c TRUE if the option was successfully parsed, or \c FALSE if an * error occurred, in which case \p *error is set */ static gboolean command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { // Sorted by enum rsc_command name if (pcmk__str_any_of(option_name, "-B", "--ban", NULL)) { options.rsc_cmd = cmd_ban; } else if (pcmk__str_any_of(option_name, "-C", "--cleanup", NULL)) { options.rsc_cmd = cmd_cleanup; } else if (pcmk__str_any_of(option_name, "-U", "--clear", NULL)) { options.rsc_cmd = cmd_clear; } else if (pcmk__str_any_of(option_name, "-a", "--constraints", NULL)) { options.rsc_cmd = cmd_colocations; } else if (pcmk__str_any_of(option_name, "-A", "--stack", NULL)) { options.rsc_cmd = cmd_colocations; options.recursive = TRUE; } else if (pcmk__str_any_of(option_name, "-c", "--list-cts", NULL)) { options.rsc_cmd = cmd_cts; } else if (pcmk__str_any_of(option_name, "-D", "--delete", NULL)) { options.rsc_cmd = cmd_delete; } else if (pcmk__str_any_of(option_name, "-d", "--delete-parameter", NULL)) { options.rsc_cmd = cmd_delete_param; pcmk__str_update(&options.prop_name, optarg); } else if (pcmk__str_eq(option_name, "--digests", pcmk__str_none)) { options.rsc_cmd = cmd_digests; if (options.override_params == NULL) { options.override_params = pcmk__strkey_table(g_free, g_free); } } else if (pcmk__str_any_of(option_name, "--force-demote", "--force-promote", "--force-start", "--force-stop", "--force-check", "--validate", NULL)) { options.rsc_cmd = cmd_execute_agent; g_free(options.operation); options.operation = g_strdup(option_name + 2); // skip "--" if (options.override_params == NULL) { options.override_params = pcmk__strkey_table(g_free, g_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; } } } else if (pcmk__str_any_of(option_name, "-F", "--fail", NULL)) { options.rsc_cmd = cmd_fail; } else if (pcmk__str_any_of(option_name, "-g", "--get-parameter", NULL)) { options.rsc_cmd = cmd_get_param; pcmk__str_update(&options.prop_name, optarg); } else if (pcmk__str_any_of(option_name, "-O", "--list-operations", NULL)) { options.rsc_cmd = cmd_list_active_ops; } else if (pcmk__str_eq(option_name, "--list-agents", pcmk__str_none)) { options.rsc_cmd = cmd_list_agents; pcmk__str_update(&options.agent_spec, optarg); } else if (pcmk__str_any_of(option_name, "-o", "--list-all-operations", NULL)) { options.rsc_cmd = cmd_list_all_ops; } else if (pcmk__str_eq(option_name, "--list-ocf-alternatives", pcmk__str_none)) { options.rsc_cmd = cmd_list_alternatives; pcmk__str_update(&options.agent_spec, optarg); } else if (pcmk__str_eq(option_name, "--list-options", pcmk__str_none)) { options.rsc_cmd = cmd_list_options; return validate_opt_list(optarg); } else if (pcmk__str_any_of(option_name, "-l", "--list-raw", NULL)) { options.rsc_cmd = cmd_list_instances; } else if (pcmk__str_eq(option_name, "--list-ocf-providers", pcmk__str_none)) { options.rsc_cmd = cmd_list_providers; pcmk__str_update(&options.agent_spec, optarg); } else if (pcmk__str_any_of(option_name, "-L", "--list", NULL)) { options.rsc_cmd = cmd_list_resources; } else if (pcmk__str_eq(option_name, "--list-standards", pcmk__str_none)) { options.rsc_cmd = cmd_list_standards; } else if (pcmk__str_any_of(option_name, "-W", "--locate", NULL)) { options.rsc_cmd = cmd_locate; } else if (pcmk__str_eq(option_name, "--show-metadata", pcmk__str_none)) { options.rsc_cmd = cmd_metadata; pcmk__str_update(&options.agent_spec, optarg); } else if (pcmk__str_any_of(option_name, "-M", "--move", NULL)) { options.rsc_cmd = cmd_move; } else if (pcmk__str_any_of(option_name, "-q", "--query-xml", NULL)) { options.rsc_cmd = cmd_query_xml; } else if (pcmk__str_any_of(option_name, "-w", "--query-xml-raw", NULL)) { options.rsc_cmd = cmd_query_xml_raw; } else if (pcmk__str_any_of(option_name, "-R", "--refresh", NULL)) { options.rsc_cmd = cmd_refresh; } else if (pcmk__str_eq(option_name, "--restart", pcmk__str_none)) { options.rsc_cmd = cmd_restart; } else if (pcmk__str_any_of(option_name, "-p", "--set-parameter", NULL)) { options.rsc_cmd = cmd_set_param; pcmk__str_update(&options.prop_name, optarg); } else if (pcmk__str_eq(option_name, "--wait", pcmk__str_none)) { options.rsc_cmd = cmd_wait; } else if (pcmk__str_any_of(option_name, "-Y", "--why", NULL)) { options.rsc_cmd = cmd_why; } return TRUE; } static gboolean option_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { gchar *name = NULL; gchar *value = NULL; if (pcmk__scan_nvpair(optarg, &name, &value) != pcmk_rc_ok) { return FALSE; } /* services__create_resource_action() ultimately takes ownership of * options.cmdline_params. It's not worth trying to ensure that the entire * call path uses (gchar *) strings and g_free(). So create the table for * (char *) strings, and duplicate the (gchar *) strings when inserting. */ if (options.cmdline_params == NULL) { options.cmdline_params = pcmk__strkey_table(free, free); } pcmk__insert_dup(options.cmdline_params, name, value); g_free(name); g_free(value); return TRUE; } static gboolean timeout_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { long long timeout_ms = crm_get_msec(optarg); if (timeout_ms < 0) { return FALSE; } options.timeout_ms = (guint) QB_MIN(timeout_ms, UINT_MAX); return TRUE; } // Command line option specification /* short option letters still available: eEJkKXyYZ */ static GOptionEntry query_entries[] = { { "list", 'L', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "List all cluster resources with status", NULL }, { "list-raw", 'l', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_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, command_cb, NULL, NULL }, { "list-operations", 'O', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_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, command_cb, "List all resource operations, optionally filtered by\n" INDENT "--resource and/or --node", NULL }, { "list-options", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb, "List all available options of the given type.\n" INDENT "Allowed values:\n" INDENT PCMK__VALUE_PRIMITIVE " (primitive resource meta-attributes),\n" INDENT PCMK_VALUE_FENCING " (parameters common to all fencing resources)", "TYPE" }, { "list-standards", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "List supported standards", NULL }, { "list-ocf-providers", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "List all available OCF providers", NULL }, { "list-agents", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_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, command_cb, "List all available providers for the named OCF agent", "AGENT" }, { "show-metadata", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb, "Show the metadata for the named class:provider:agent", "SPEC" }, { "query-xml", 'q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Show XML configuration of resource (after any template expansion)", NULL }, { "query-xml-raw", 'w', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Show XML configuration of resource (before any template expansion)", NULL }, { "get-parameter", 'g', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb, "Display named parameter for resource (use instance attribute\n" INDENT "unless --element, --meta, or --utilization is specified)", "PARAM" }, { "locate", 'W', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Show node(s) currently running resource", NULL }, { "constraints", 'a', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_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, command_cb, "Equivalent to --constraints --recursive", NULL }, { "why", 'Y', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_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, command_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, command_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, command_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, command_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, command_cb, "Delete named parameter for resource. Use instance attribute\n" INDENT "unless --element, --meta or, --utilization is specified.", "PARAM" }, { NULL } }; static GOptionEntry location_entries[] = { { "move", 'M', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_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, command_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 PCMK_META_PROMOTED_MAX "=1 (all other situations result in an\n" INDENT "error as there is no sane default).", NULL }, { "clear", 'U', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_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_NONE, G_OPTION_ARG_NONE, &options.clear_expired, "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, command_cb, "(Advanced) Delete a resource from the CIB. Required: -t", NULL }, { "fail", 'F', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "(Advanced) Tell the cluster this resource has failed", NULL }, { "restart", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "(Advanced) Tell the cluster to restart this resource and\n" INDENT "anything that depends on it. This temporarily modifies\n" INDENT "the CIB, and other CIB modifications should be avoided\n" INDENT "while this is in progress. If a node is fenced because\n" INDENT "the stop portion of the restart fails, CIB modifications\n" INDENT "such as target-role may remain.", NULL }, { "wait", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "(Advanced) Wait until the cluster settles into a stable state", NULL }, { "digests", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_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, command_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, command_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, command_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, command_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, command_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 0s) (with -C -r -n)", "N" }, { "class", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.class, "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_STRING, &options.agent, "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_STRING, &options.provider, "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" }, { "all", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.all, "List all options, including advanced and deprecated (with\n" INDENT "--list-options)", NULL }, { "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 }, // @COMPAT Used in resource-agents prior to v4.2.0 { "host-uname", 'H', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.host_uname, NULL, "HOST" }, { NULL } }; static int -ban_or_move(pcmk__output_t *out, pcmk_resource_t *rsc, +ban_or_move(pcmk__output_t *out, pcmk_resource_t *rsc, cib_t *cib_conn, const char *move_lifetime) { int rc = pcmk_rc_ok; pcmk_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->priv->name, move_lifetime, cib_conn, options.promoted_role_only, PCMK_ROLE_PROMOTED); } else if (pcmk_is_set(rsc->flags, pcmk__rsc_promotable)) { int count = 0; GList *iter = NULL; current = NULL; for (iter = rsc->priv->children; iter != NULL; iter = iter->next) { pcmk_resource_t *child = (pcmk_resource_t *)iter->data; enum rsc_role_e child_role = child->priv->fns->state(child, true); if (child_role == pcmk_role_promoted) { count++; current = pcmk__current_node(child); } } if(count == 1 && current) { rc = cli_resource_ban(out, options.rsc_id, current->priv->name, move_lifetime, cib_conn, options.promoted_role_only, PCMK_ROLE_PROMOTED); } 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, pcmk_resource_t *rsc, pcmk_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, ((node != NULL)? pcmk__node_name(node) : "all nodes")); rc = cli_resource_delete(controld_api, rsc, node, options.operation, options.interval_spec, true, 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); } /* @FIXME The mainloop functions in this file set exit_code. What happens to * exit_code if rc != pcmk_rc_ok here? */ if (rc == pcmk_rc_ok) { start_mainloop(controld_api); } } /*! * \internal * \brief Allocate a scheduler data object and initialize it from the CIB * * We transform the queried CIB XML to the latest schema version before using it * to populate the scheduler data. * * \param[out] scheduler Where to store scheduler data * \param[in] cib_conn CIB connection * \param[in] out Output object for new scheduler data object * \param[out] cib_xml_orig Where to store queried CIB XML from before any * schema upgrades * * \return Standard Pacemaker return code * * \note \p *scheduler and \p *cib_xml_orig must be \c NULL when this function * is called. * \note The caller is responsible for freeing \p *scheduler using * \c pcmk_free_scheduler. */ static int initialize_scheduler_data(pcmk_scheduler_t **scheduler, cib_t *cib_conn, pcmk__output_t *out, xmlNode **cib_xml_orig) { int rc = pcmk_rc_ok; pcmk__assert((scheduler != NULL) && (*scheduler == NULL) && (cib_conn != NULL) && (out != NULL) && (cib_xml_orig != NULL) && (*cib_xml_orig == NULL)); *scheduler = pcmk_new_scheduler(); if (*scheduler == NULL) { return ENOMEM; } pcmk__set_scheduler_flags(*scheduler, pcmk__sched_no_counts); (*scheduler)->priv->out = out; rc = update_scheduler_input(out, *scheduler, cib_conn, cib_xml_orig); if (rc != pcmk_rc_ok) { pcmk_free_scheduler(*scheduler); *scheduler = NULL; return rc; } cluster_status(*scheduler); return pcmk_rc_ok; } static crm_exit_t refresh(pcmk__output_t *out, const pcmk_node_t *node) { const char *node_name = NULL; const char *log_node_name = "all nodes"; const char *router_node = NULL; int attr_options = pcmk__node_attr_none; int rc = pcmk_rc_ok; if (node != NULL) { node_name = node->priv->name; log_node_name = pcmk__node_name(node); router_node = node->priv->name; } if (pcmk__is_pacemaker_remote_node(node)) { const pcmk_node_t *conn_host = pcmk__current_node(node->priv->remote); if (conn_host == NULL) { rc = ENXIO; g_set_error(&error, PCMK__RC_ERROR, rc, _("No cluster connection to Pacemaker Remote node %s " "detected"), log_node_name); return pcmk_rc2exitc(rc); } router_node = conn_host->priv->name; pcmk__set_node_attr_flags(attr_options, pcmk__node_attr_remote); } if (controld_api == NULL) { out->info(out, "Dry run: skipping clean-up of %s due to CIB_file", log_node_name); return CRM_EX_OK; } crm_debug("Re-checking the state of all resources on %s", log_node_name); // @FIXME We shouldn't discard rc here rc = pcmk__attrd_api_clear_failures(NULL, node_name, NULL, NULL, NULL, NULL, attr_options); /* @FIXME The mainloop functions in this file set exit_code. What happens to * exit_code if pcmk_controld_api_reprobe() doesn't return pcmk_rc_ok? */ if (pcmk_controld_api_reprobe(controld_api, node_name, router_node) == pcmk_rc_ok) { start_mainloop(controld_api); return exit_code; } return pcmk_rc2exitc(rc); } static void refresh_resource(pcmk__output_t *out, pcmk_resource_t *rsc, pcmk_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, ((node != NULL)? pcmk__node_name(node) : "all nodes")); rc = cli_resource_delete(controld_api, rsc, node, NULL, 0, false, 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); } /* @FIXME The mainloop functions in this file set exit_code. What happens to * exit_code if rc != pcmk_rc_ok here? */ if (rc == pcmk_rc_ok) { start_mainloop(controld_api); } } /*! * \internal * \brief Check whether a command-line resource configuration was given * * \return \c true if \c --class, \c --provider, or \c --agent was specified, or * \c false otherwise */ static inline bool has_cmdline_config(void) { return ((options.class != NULL) || (options.provider != NULL) || (options.agent != NULL)); } static void validate_cmdline_config(void) { bool is_ocf = pcmk__str_eq(options.class, PCMK_RESOURCE_CLASS_OCF, pcmk__str_none); // Sanity check before throwing any errors if (!has_cmdline_config()) { return; } // Cannot use both --resource and command-line resource configuration if (options.rsc_id != NULL) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, _("--class, --agent, and --provider cannot be used with " "-r/--resource")); return; } /* Check whether command supports command-line resource configuration * * @FIXME According to the help text, these options can only be used with * --validate. The --force-* commands are documented for resources that are * configured in Pacemaker. So this is a bug. We have two choices: * * Throw an error if --force-* commands are used with these options. * * Document that --force-* commands can be used with these options. * * An error seems safer. If a user really wants to run a non-trivial * resource action based on CLI parameters, they can do so by executing the * resource agent directly. It's unsafe to do so if Pacemaker is managing * the resource that's specified via --class, --option, etc. * * On the other hand, besides safety concerns, running other actions is * exactly the same as running a validate action, and the implementation is * already in place. */ 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-*")); return; } // Check for a valid combination of --class, --agent, and --provider if (is_ocf) { if ((options.provider == NULL) || (options.agent == NULL)) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, _("--provider and --agent are required with " "--class=ocf")); return; } } else { if (options.provider != NULL) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, _("--provider is supported only with --class=ocf")); return; } // Either --class or --agent was given if (options.agent == NULL) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, _("--agent is required with --class")); return; } if (options.class == NULL) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, _("--class is required with --agent")); return; } } // Check whether agent exists if (pcmk__str_eq(options.class, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_none)) { if (!stonith_agent_exists(options.agent, 0)) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, _("%s is not a known stonith agent"), options.agent); return; } } else if (!resources_agent_exists(options.class, options.provider, options.agent)) { if (is_ocf) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, _("%s:%s:%s is not a known resource agent"), options.class, options.provider, options.agent); } else { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, _("%s:%s is not a known resource agent"), options.class, options.agent); } return; } if (options.cmdline_params == NULL) { options.cmdline_params = pcmk__strkey_table(free, free); } } static crm_exit_t -handle_ban(pcmk_resource_t *rsc, pcmk_node_t *node, xmlNode *cib_xml_orig, - pcmk_scheduler_t *scheduler) +handle_ban(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { int rc = pcmk_rc_ok; if (node == NULL) { - rc = ban_or_move(out, rsc, options.move_lifetime); + rc = ban_or_move(out, rsc, cib_conn, options.move_lifetime); } else { rc = cli_resource_ban(out, options.rsc_id, node->priv->name, options.move_lifetime, cib_conn, options.promoted_role_only, PCMK_ROLE_PROMOTED); } if (rc == EINVAL) { return CRM_EX_USAGE; } return pcmk_rc2exitc(rc); } static crm_exit_t -handle_cleanup(pcmk_resource_t *rsc, pcmk_node_t *node, xmlNode *cib_xml_orig, - pcmk_scheduler_t *scheduler) +handle_cleanup(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { if (rsc == NULL) { int rc = cli_cleanup_all(controld_api, node, options.operation, options.interval_spec, scheduler); if (rc == pcmk_rc_ok) { start_mainloop(controld_api); } } else { cleanup(out, rsc, node); } /* @FIXME Both of the blocks above are supposed to set exit_code via * start_mainloop(). But if cli_cleanup_all() or cli_resource_delete() * fails, we never start the mainloop. It looks as if we exit with CRM_EX_OK * in those cases. */ return exit_code; } static crm_exit_t -handle_clear(pcmk_resource_t *rsc, pcmk_node_t *node, xmlNode *cib_xml_orig, - pcmk_scheduler_t *scheduler) +handle_clear(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { const char *node_name = (node != NULL)? node->priv->name : NULL; GList *before = NULL; GList *after = NULL; GList *remaining = NULL; int rc = pcmk_rc_ok; if (!out->is_quiet(out)) { before = build_constraint_list(scheduler->input); } if (options.clear_expired) { rc = cli_resource_clear_all_expired(scheduler->input, cib_conn, options.rsc_id, node_name, options.promoted_role_only); } else if (node != NULL) { rc = cli_resource_clear(options.rsc_id, node_name, NULL, cib_conn, true, options.force); } else { rc = cli_resource_clear(options.rsc_id, NULL, scheduler->nodes, cib_conn, true, options.force); } if (!out->is_quiet(out)) { xmlNode *cib_xml = NULL; rc = cib_conn->cmds->query(cib_conn, NULL, &cib_xml, 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"), pcmk_rc_str(rc)); g_list_free(before); pcmk__xml_free(cib_xml); return pcmk_rc2exitc(rc); } scheduler->input = cib_xml; cluster_status(scheduler); after = build_constraint_list(scheduler->input); remaining = pcmk__subtract_lists(before, after, (GCompareFunc) strcmp); for (const GList *iter = remaining; iter != NULL; iter = iter->next) { const char *constraint = iter->data; out->info(out, "Removing constraint: %s", constraint); } g_list_free(before); g_list_free(after); g_list_free(remaining); } return pcmk_rc2exitc(rc); } static crm_exit_t -handle_colocations(pcmk_resource_t *rsc, pcmk_node_t *node, - xmlNode *cib_xml_orig, pcmk_scheduler_t *scheduler) +handle_colocations(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { int rc = out->message(out, "locations-and-colocations", rsc, options.recursive, options.force); return pcmk_rc2exitc(rc); } static crm_exit_t -handle_cts(pcmk_resource_t *rsc, pcmk_node_t *node, xmlNode *cib_xml_orig, - pcmk_scheduler_t *scheduler) +handle_cts(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { g_list_foreach(scheduler->priv->resources, (GFunc) cli_resource_print_cts, out); cli_resource_print_cts_constraints(scheduler); return CRM_EX_OK; } static crm_exit_t -handle_delete(pcmk_resource_t *rsc, pcmk_node_t *node, xmlNode *cib_xml_orig, - pcmk_scheduler_t *scheduler) +handle_delete(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { /* rsc_id was already checked for NULL much earlier when validating command * line arguments */ int rc = pcmk_rc_ok; if (options.rsc_type == NULL) { crm_exit_t ec = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, ec, _("You need to specify a resource type with -t")); return ec; } rc = pcmk__resource_delete(cib_conn, cib_sync_call, options.rsc_id, options.rsc_type); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, _("Could not delete resource %s: %s"), options.rsc_id, pcmk_rc_str(rc)); } return pcmk_rc2exitc(rc); } static crm_exit_t -handle_delete_param(pcmk_resource_t *rsc, pcmk_node_t *node, - xmlNode *cib_xml_orig, pcmk_scheduler_t *scheduler) +handle_delete_param(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { int rc = cli_resource_delete_attribute(rsc, options.rsc_id, options.prop_set, options.attr_set_type, options.prop_id, options.prop_name, cib_conn, cib_xml_orig, options.force); return pcmk_rc2exitc(rc); } static crm_exit_t -handle_digests(pcmk_resource_t *rsc, pcmk_node_t *node, xmlNode *cib_xml_orig, - pcmk_scheduler_t *scheduler) +handle_digests(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { int rc = pcmk__resource_digests(out, rsc, node, options.override_params); return pcmk_rc2exitc(rc); } static crm_exit_t -handle_execute_agent(pcmk_resource_t *rsc, pcmk_node_t *node, - xmlNode *cib_xml_orig, pcmk_scheduler_t *scheduler) +handle_execute_agent(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { if (has_cmdline_config()) { return cli_resource_execute_from_params(out, NULL, options.class, options.provider, options.agent, options.operation, options.cmdline_params, options.override_params, options.timeout_ms, args->verbosity, options.force, options.check_level); } return cli_resource_execute(rsc, options.rsc_id, options.operation, options.override_params, options.timeout_ms, cib_conn, args->verbosity, options.force, options.check_level); } static crm_exit_t -handle_fail(pcmk_resource_t *rsc, pcmk_node_t *node, xmlNode *cib_xml_orig, - pcmk_scheduler_t *scheduler) +handle_fail(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { int rc = cli_resource_fail(controld_api, rsc, options.rsc_id, node); if (rc == pcmk_rc_ok) { // start_mainloop() sets exit_code start_mainloop(controld_api); return exit_code; } return pcmk_rc2exitc(rc);; } static crm_exit_t -handle_get_param(pcmk_resource_t *rsc, pcmk_node_t *node, xmlNode *cib_xml_orig, - pcmk_scheduler_t *scheduler) +handle_get_param(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { unsigned int count = 0; GHashTable *params = NULL; pcmk_node_t *current = rsc->priv->fns->active_node(rsc, &count, NULL); bool free_params = true; const char *value = NULL; int rc = pcmk_rc_ok; 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, PCMK_XE_INSTANCE_ATTRIBUTES, pcmk__str_none)) { params = pe_rsc_params(rsc, current, scheduler); free_params = false; value = g_hash_table_lookup(params, options.prop_name); } else if (pcmk__str_eq(options.attr_set_type, PCMK_XE_META_ATTRIBUTES, pcmk__str_none)) { params = pcmk__strkey_table(free, free); get_meta_attributes(params, rsc, NULL, scheduler); 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->priv->xml, options.prop_name); free_params = false; } else { const pcmk_rule_input_t rule_input = { .now = scheduler->priv->now, }; params = pcmk__strkey_table(free, free); pe__unpack_dataset_nvpairs(rsc->priv->xml, PCMK_XE_UTILIZATION, &rule_input, params, NULL, scheduler); 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); } return pcmk_rc2exitc(rc); } static crm_exit_t -handle_list_active_ops(pcmk_resource_t *rsc, pcmk_node_t *node, - xmlNode *cib_xml_orig, pcmk_scheduler_t *scheduler) +handle_list_active_ops(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { const char *node_name = (node != NULL)? node->priv->name : NULL; int rc = cli_resource_print_operations(options.rsc_id, node_name, true, scheduler); return pcmk_rc2exitc(rc); } static crm_exit_t -handle_list_agents(pcmk_resource_t *rsc, pcmk_node_t *node, - xmlNode *cib_xml_orig, pcmk_scheduler_t *scheduler) +handle_list_agents(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { int rc = pcmk__list_agents(out, options.agent_spec); return pcmk_rc2exitc(rc); } static crm_exit_t -handle_list_all_ops(pcmk_resource_t *rsc, pcmk_node_t *node, - xmlNode *cib_xml_orig, pcmk_scheduler_t *scheduler) +handle_list_all_ops(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { const char *node_name = (node != NULL)? node->priv->name : NULL; int rc = cli_resource_print_operations(options.rsc_id, node_name, false, scheduler); return pcmk_rc2exitc(rc); } static crm_exit_t handle_list_alternatives(pcmk_resource_t *rsc, pcmk_node_t *node, - xmlNode *cib_xml_orig, pcmk_scheduler_t *scheduler) + cib_t *cib_conn, pcmk_scheduler_t *scheduler, + xmlNode *cib_xml_orig) { int rc = pcmk__list_alternatives(out, options.agent_spec); return pcmk_rc2exitc(rc); } static crm_exit_t -handle_list_instances(pcmk_resource_t *rsc, pcmk_node_t *node, - xmlNode *cib_xml_orig, pcmk_scheduler_t *scheduler) +handle_list_instances(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { int rc = out->message(out, "resource-names-list", scheduler->priv->resources); if (rc == pcmk_rc_no_output) { // @COMPAT It seems wrong to return an error because there no resources return CRM_EX_NOSUCH; } return pcmk_rc2exitc(rc); } static crm_exit_t -handle_list_options(pcmk_resource_t *rsc, pcmk_node_t *node, - xmlNode *cib_xml_orig, pcmk_scheduler_t *scheduler) +handle_list_options(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { crm_exit_t ec = CRM_EX_OK; int rc = pcmk_rc_ok; switch (options.opt_list) { case pcmk__opt_fencing: rc = pcmk__list_fencing_params(out, options.all); return pcmk_rc2exitc(rc); case pcmk__opt_primitive: rc = pcmk__list_primitive_meta(out, options.all); return pcmk_rc2exitc(rc); default: ec = CRM_EX_SOFTWARE; g_set_error(&error, PCMK__EXITC_ERROR, ec, "Bug: Invalid option list type"); return ec; } } static crm_exit_t -handle_list_providers(pcmk_resource_t *rsc, pcmk_node_t *node, - xmlNode *cib_xml_orig, pcmk_scheduler_t *scheduler) +handle_list_providers(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { int rc = pcmk__list_providers(out, options.agent_spec); return pcmk_rc2exitc(rc); } static crm_exit_t -handle_list_resources(pcmk_resource_t *rsc, pcmk_node_t *node, - xmlNode *cib_xml_orig, pcmk_scheduler_t *scheduler) +handle_list_resources(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { GList *all = g_list_prepend(NULL, (gpointer) "*"); int rc = out->message(out, "resource-list", scheduler, 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) { // @COMPAT It seems wrong to return an error because there no resources return CRM_EX_NOSUCH; } return pcmk_rc2exitc(rc); } static crm_exit_t -handle_list_standards(pcmk_resource_t *rsc, pcmk_node_t *node, - xmlNode *cib_xml_orig, pcmk_scheduler_t *scheduler) +handle_list_standards(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { int rc = pcmk__list_standards(out); return pcmk_rc2exitc(rc); } static crm_exit_t -handle_locate(pcmk_resource_t *rsc, pcmk_node_t *node, xmlNode *cib_xml_orig, - pcmk_scheduler_t *scheduler) +handle_locate(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { GList *nodes = cli_resource_search(rsc, options.rsc_id); int rc = out->message(out, "resource-search-list", nodes, options.rsc_id); g_list_free_full(nodes, free); return pcmk_rc2exitc(rc); } static crm_exit_t -handle_metadata(pcmk_resource_t *rsc, pcmk_node_t *node, xmlNode *cib_xml_orig, - pcmk_scheduler_t *scheduler) +handle_metadata(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { 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 pcmk_rc2exitc(rc); } rc = crm_parse_agent_spec(options.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 != NULL) { out->output_xml(out, PCMK_XE_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"), options.agent_spec, pcmk_rc_str(rc)); } } else { rc = ENXIO; g_set_error(&error, PCMK__RC_ERROR, rc, _("'%s' is not a valid agent specification"), options.agent_spec); } lrmd_api_delete(lrmd_conn); return pcmk_rc2exitc(rc); } static crm_exit_t -handle_move(pcmk_resource_t *rsc, pcmk_node_t *node, xmlNode *cib_xml_orig, - pcmk_scheduler_t *scheduler) +handle_move(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { int rc = pcmk_rc_ok; if (node == NULL) { - rc = ban_or_move(out, rsc, options.move_lifetime); + rc = ban_or_move(out, rsc, cib_conn, options.move_lifetime); } else { rc = cli_resource_move(rsc, options.rsc_id, node, options.move_lifetime, cib_conn, options.promoted_role_only, options.force); } if (rc == EINVAL) { return CRM_EX_USAGE; } return pcmk_rc2exitc(rc); } static crm_exit_t -handle_query_xml(pcmk_resource_t *rsc, pcmk_node_t *node, xmlNode *cib_xml_orig, - pcmk_scheduler_t *scheduler) +handle_query_xml(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { int rc = cli_resource_print(rsc, true); return pcmk_rc2exitc(rc); } static crm_exit_t -handle_query_xml_raw(pcmk_resource_t *rsc, pcmk_node_t *node, - xmlNode *cib_xml_orig, pcmk_scheduler_t *scheduler) +handle_query_xml_raw(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { int rc = cli_resource_print(rsc, false); return pcmk_rc2exitc(rc); } static crm_exit_t -handle_refresh(pcmk_resource_t *rsc, pcmk_node_t *node, xmlNode *cib_xml_orig, - pcmk_scheduler_t *scheduler) +handle_refresh(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { if (rsc == NULL) { return refresh(out, node); } refresh_resource(out, rsc, node); /* @FIXME Both of the calls above are supposed to set exit_code via * start_mainloop(). But there appear to be cases in which we can return * from refresh() or refresh_resource() without starting the mainloop or * returning an error code. It looks as if we exit with CRM_EX_OK in those * cases. */ return exit_code; } static crm_exit_t -handle_restart(pcmk_resource_t *rsc, pcmk_node_t *node, xmlNode *cib_xml_orig, - pcmk_scheduler_t *scheduler) +handle_restart(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { /* We don't pass scheduler because rsc needs to stay valid for the entire * lifetime of cli_resource_restart(), but it will reset and update the * scheduler data multiple times, so it needs to use its own copy. */ int rc = cli_resource_restart(out, rsc, node, options.move_lifetime, options.timeout_ms, cib_conn, options.promoted_role_only, options.force); return pcmk_rc2exitc(rc); } static crm_exit_t -handle_set_param(pcmk_resource_t *rsc, pcmk_node_t *node, xmlNode *cib_xml_orig, - pcmk_scheduler_t *scheduler) +handle_set_param(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { int rc = pcmk_rc_ok; if (pcmk__str_empty(options.prop_value)) { crm_exit_t ec = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, ec, _("You need to supply a value with the -v option")); return ec; } 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, cib_xml_orig, options.force); return pcmk_rc2exitc(rc); } static crm_exit_t -handle_wait(pcmk_resource_t *rsc, pcmk_node_t *node, xmlNode *cib_xml_orig, - pcmk_scheduler_t *scheduler) +handle_wait(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { int rc = wait_till_stable(out, options.timeout_ms, cib_conn); return pcmk_rc2exitc(rc); } static crm_exit_t -handle_why(pcmk_resource_t *rsc, pcmk_node_t *node, xmlNode *cib_xml_orig, - pcmk_scheduler_t *scheduler) +handle_why(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, + pcmk_scheduler_t *scheduler, xmlNode *cib_xml_orig) { int rc = out->message(out, "resource-reasons-list", scheduler->priv->resources, rsc, node); return pcmk_rc2exitc(rc); } static const crm_resource_cmd_info_t crm_resource_command_info[] = { [cmd_none] = { NULL, 0, }, [cmd_ban] = { handle_ban, crm_rsc_find_match_anon_basename |crm_rsc_find_match_history |crm_rsc_rejects_clone_instance |crm_rsc_requires_cib |crm_rsc_requires_resource |crm_rsc_requires_scheduler, }, [cmd_cleanup] = { handle_cleanup, crm_rsc_find_match_anon_basename |crm_rsc_find_match_history |crm_rsc_requires_cib |crm_rsc_requires_controller |crm_rsc_requires_scheduler, }, [cmd_clear] = { handle_clear, crm_rsc_find_match_anon_basename |crm_rsc_find_match_history |crm_rsc_rejects_clone_instance |crm_rsc_requires_cib |crm_rsc_requires_resource // Unless options.clear_expired |crm_rsc_requires_scheduler, }, [cmd_colocations] = { handle_colocations, crm_rsc_find_match_anon_basename |crm_rsc_find_match_history |crm_rsc_requires_cib |crm_rsc_requires_resource |crm_rsc_requires_scheduler, }, [cmd_cts] = { handle_cts, crm_rsc_requires_cib |crm_rsc_requires_scheduler, }, [cmd_delete] = { handle_delete, crm_rsc_rejects_clone_instance |crm_rsc_requires_cib |crm_rsc_requires_resource, }, [cmd_delete_param] = { handle_delete_param, crm_rsc_find_match_basename |crm_rsc_find_match_history |crm_rsc_requires_cib |crm_rsc_requires_resource |crm_rsc_requires_scheduler, }, [cmd_digests] = { handle_digests, crm_rsc_find_match_anon_basename |crm_rsc_find_match_history |crm_rsc_requires_cib |crm_rsc_requires_node |crm_rsc_requires_resource |crm_rsc_requires_scheduler, }, [cmd_execute_agent] = { handle_execute_agent, crm_rsc_find_match_anon_basename |crm_rsc_find_match_history |crm_rsc_requires_cib |crm_rsc_requires_resource |crm_rsc_requires_scheduler, }, [cmd_fail] = { handle_fail, crm_rsc_find_match_history |crm_rsc_requires_cib |crm_rsc_requires_controller |crm_rsc_requires_node |crm_rsc_requires_resource |crm_rsc_requires_scheduler, }, [cmd_get_param] = { handle_get_param, crm_rsc_find_match_basename |crm_rsc_find_match_history |crm_rsc_requires_cib |crm_rsc_requires_resource |crm_rsc_requires_scheduler, }, [cmd_list_active_ops] = { handle_list_active_ops, crm_rsc_requires_cib |crm_rsc_requires_scheduler, }, [cmd_list_agents] = { handle_list_agents, 0, }, [cmd_list_all_ops] = { handle_list_all_ops, crm_rsc_requires_cib |crm_rsc_requires_scheduler, }, [cmd_list_alternatives] = { handle_list_alternatives, 0, }, [cmd_list_instances] = { handle_list_instances, crm_rsc_requires_cib |crm_rsc_requires_scheduler, }, [cmd_list_options] = { handle_list_options, 0, }, [cmd_list_providers] = { handle_list_providers, 0, }, [cmd_list_resources] = { handle_list_resources, crm_rsc_requires_cib |crm_rsc_requires_scheduler, }, [cmd_list_standards] = { handle_list_standards, 0, }, [cmd_locate] = { handle_locate, crm_rsc_find_match_anon_basename |crm_rsc_find_match_history |crm_rsc_requires_cib |crm_rsc_requires_resource |crm_rsc_requires_scheduler, }, [cmd_metadata] = { handle_metadata, 0, }, [cmd_move] = { handle_move, crm_rsc_find_match_anon_basename |crm_rsc_find_match_history |crm_rsc_rejects_clone_instance |crm_rsc_requires_cib |crm_rsc_requires_resource |crm_rsc_requires_scheduler, }, [cmd_query_xml] = { handle_query_xml, crm_rsc_find_match_basename |crm_rsc_find_match_history |crm_rsc_requires_cib |crm_rsc_requires_resource |crm_rsc_requires_scheduler, }, [cmd_query_xml_raw] = { handle_query_xml_raw, crm_rsc_find_match_basename |crm_rsc_find_match_history |crm_rsc_requires_cib |crm_rsc_requires_resource |crm_rsc_requires_scheduler, }, [cmd_refresh] = { handle_refresh, crm_rsc_find_match_anon_basename |crm_rsc_find_match_history |crm_rsc_requires_cib |crm_rsc_requires_controller |crm_rsc_requires_scheduler, }, [cmd_restart] = { handle_restart, crm_rsc_find_match_anon_basename |crm_rsc_find_match_history |crm_rsc_rejects_clone_instance |crm_rsc_requires_cib |crm_rsc_requires_resource |crm_rsc_requires_scheduler, }, [cmd_set_param] = { handle_set_param, crm_rsc_find_match_basename |crm_rsc_find_match_history |crm_rsc_requires_cib |crm_rsc_requires_resource |crm_rsc_requires_scheduler, }, [cmd_wait] = { handle_wait, crm_rsc_requires_cib, }, [cmd_why] = { handle_why, crm_rsc_find_match_anon_basename |crm_rsc_find_match_history |crm_rsc_requires_cib |crm_rsc_requires_scheduler, }, }; 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 " PCMK_META_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 " PCMK_META_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) { const crm_resource_cmd_info_t *command_info = NULL; pcmk_resource_t *rsc = NULL; pcmk_node_t *node = NULL; + cib_t *cib_conn = NULL; pcmk_scheduler_t *scheduler = NULL; xmlNode *cib_xml_orig = NULL; uint32_t find_flags = 0; 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 */ // --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) { // Commands that use positional arguments will create override_params if (options.override_params == NULL) { GString *msg = g_string_sized_new(128); guint len = g_strv_length(options.remainder); g_string_append(msg, "non-option ARGV-elements:"); for (int i = 0; i < len; i++) { g_string_append_printf(msg, "\n[%d of %u] %s", i + 1, len, options.remainder[i]); } exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "%s", msg->str); g_string_free(msg, TRUE); goto done; } for (gchar **arg = options.remainder; *arg != NULL; arg++) { gchar *name = NULL; gchar *value = NULL; int rc = pcmk__scan_nvpair(*arg, &name, &value); if (rc != pcmk_rc_ok) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("Error parsing '%s' as a name=value pair"), *arg); goto done; } g_hash_table_insert(options.override_params, name, value); } } if (pcmk__str_eq(args->output_ty, "xml", pcmk__str_none)) { switch (options.rsc_cmd) { /* These are the only commands that have historically used the * elements in their XML schema. For all others, use the simple list * argument. */ case cmd_get_param: case cmd_list_instances: case cmd_list_standards: pcmk__output_enable_list_element(out); break; default: break; } } else if (pcmk__str_eq(args->output_ty, "text", pcmk__str_null_matches)) { switch (options.rsc_cmd) { case cmd_colocations: case cmd_list_resources: pcmk__output_text_set_fancy(out, true); break; default: break; } } if (args->version) { out->version(out, false); goto done; } // Ensure command is in valid range and has a handler function if ((options.rsc_cmd >= 0) && (options.rsc_cmd <= cmd_max)) { command_info = &crm_resource_command_info[options.rsc_cmd]; } if ((command_info == NULL) || (command_info->fn == NULL)) { exit_code = CRM_EX_SOFTWARE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("Bug: Unimplemented command: %d"), (int) options.rsc_cmd); goto done; } /* If a command-line resource agent specification was given, validate it. * Otherwise, ensure --option was not given. */ if (has_cmdline_config()) { validate_cmdline_config(); if (error != NULL) { exit_code = CRM_EX_USAGE; goto done; } } else if (options.cmdline_params != NULL) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("--option must be used with --validate and without -r")); g_hash_table_destroy(options.cmdline_params); goto done; } // Ensure --resource is set if it's required if (pcmk_is_set(command_info->flags, crm_rsc_requires_resource) && !has_cmdline_config() && !options.clear_expired && (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/--resource")); goto done; } // Ensure --node is set if it's required if (pcmk_is_set(command_info->flags, crm_rsc_requires_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/--node")); goto done; } // Establish a connection to the CIB if needed if (pcmk_is_set(command_info->flags, crm_rsc_requires_cib) && !has_cmdline_config()) { 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__signon_attempts(cib_conn, cib_command, 5); 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 scheduler data from CIB query if needed if (pcmk_is_set(command_info->flags, crm_rsc_requires_scheduler) && !has_cmdline_config()) { rc = initialize_scheduler_data(&scheduler, cib_conn, out, &cib_xml_orig); if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); goto done; } } // Establish a connection to the controller if needed if (pcmk_is_set(command_info->flags, crm_rsc_requires_controller) && (getenv("CIB_file") == NULL)) { 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, &exit_code); rc = pcmk__connect_ipc(controld_api, pcmk_ipc_dispatch_main, 5); if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("Error connecting to %s: %s"), pcmk_ipc_name(controld_api, true), pcmk_rc_str(rc)); goto done; } } /* Find node if --node was given. * * @TODO Consider stricter validation. Currently we ignore the --node * argument for commands that don't require scheduler data, since we have no * way to find the node in that case. This is really a usage error, but we * don't validate strictly. We allow multiple commands (and in some cases * their options like --node) to be specified, and we use the last one in * case of conflicts. * * This isn't universally true. --expired results in a usage error unless * the final command is --clear. */ if (options.host_uname != NULL) { node = pcmk_find_node(scheduler, 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; } } /* Find resource if --resource was given and any find flags are set. * * @TODO Consider stricter validation. See comment above for --node. * @TODO Setter macro for tracing? */ if (pcmk_is_set(command_info->flags, crm_rsc_find_match_anon_basename)) { find_flags |= pcmk_rsc_match_anon_basename; } if (pcmk_is_set(command_info->flags, crm_rsc_find_match_basename)) { find_flags |= pcmk_rsc_match_basename; } if (pcmk_is_set(command_info->flags, crm_rsc_find_match_history)) { find_flags |= pcmk_rsc_match_history; } if ((find_flags != 0) && (options.rsc_id != NULL)) { pcmk__assert(scheduler != NULL); rsc = pe_find_resource_with_flags(scheduler->priv->resources, options.rsc_id, 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, --delete, --move, and --restart commands do not * support instances of clone resources */ if (pcmk_is_set(command_info->flags, crm_rsc_rejects_clone_instance) && pcmk__is_clone(rsc->priv->parent) && (strchr(options.rsc_id, ':') != NULL)) { 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; } } - exit_code = command_info->fn(rsc, node, cib_xml_orig, scheduler); + exit_code = command_info->fn(rsc, node, cib_conn, scheduler, cib_xml_orig); done: // For CRM_EX_USAGE, error is already set satisfactorily 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); g_free(options.agent); g_free(options.class); g_free(options.provider); if (options.override_params != NULL) { g_hash_table_destroy(options.override_params); } g_strfreev(options.remainder); // Don't destroy options.cmdline_params here. See comment in option_cb(). g_strfreev(processed_args); g_option_context_free(context); pcmk__xml_free(cib_xml_orig); cib__clean_up_connection(&cib_conn); pcmk_free_ipc_api(controld_api); pcmk_free_scheduler(scheduler); if (mainloop != NULL) { g_main_loop_unref(mainloop); } pcmk__output_and_clear_error(&error, out); if (out != NULL) { out->finish(out, exit_code, true, NULL); pcmk__output_free(out); } pcmk__unregister_formats(); return crm_exit(exit_code); }