diff options
Diffstat (limited to '')
-rw-r--r-- | tools/crm_attribute.c | 316 |
1 files changed, 211 insertions, 105 deletions
diff --git a/tools/crm_attribute.c b/tools/crm_attribute.c index defe294..f9b1434 100644 --- a/tools/crm_attribute.c +++ b/tools/crm_attribute.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2023 the Pacemaker project contributors + * Copyright 2004-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -22,7 +22,6 @@ #include <sys/types.h> #include <crm/crm.h> -#include <crm/msg_xml.h> #include <crm/common/xml.h> #include <crm/common/ipc.h> #include <crm/common/util.h> @@ -30,7 +29,7 @@ #include <crm/cib.h> #include <crm/cib/internal.h> -#include <crm/common/attrd_internal.h> +#include <crm/common/attrs_internal.h> #include <crm/common/cmdline_internal.h> #include <crm/common/ipc_attrd_internal.h> #include <crm/common/ipc_controld.h> @@ -41,36 +40,18 @@ #define SUMMARY "crm_attribute - query and update Pacemaker cluster options and node attributes" +enum attr_cmd { + attr_cmd_none, + attr_cmd_delete, + attr_cmd_list, + attr_cmd_query, + attr_cmd_update, +}; + GError *error = NULL; crm_exit_t exit_code = CRM_EX_OK; uint64_t cib_opts = cib_sync_call; -PCMK__OUTPUT_ARGS("attribute", "const char *", "const char *", "const char *", - "const char *", "const char *") -static int -attribute_text(pcmk__output_t *out, va_list args) -{ - const char *scope = va_arg(args, const char *); - const char *instance = va_arg(args, const char *); - const char *name = va_arg(args, const char *); - const char *value = va_arg(args, const char *); - const char *host G_GNUC_UNUSED = va_arg(args, const char *); - - if (out->quiet) { - if (value != NULL) { - pcmk__formatted_printf(out, "%s\n", value); - } - } else { - out->info(out, "%s%s %s%s %s%s value=%s", - scope ? "scope=" : "", scope ? scope : "", - instance ? "id=" : "", instance ? instance : "", - name ? "name=" : "", name ? name : "", - value ? value : "(null)"); - } - - return pcmk_rc_ok; -} - static pcmk__supported_format_t formats[] = { PCMK__SUPPORTED_FORMAT_NONE, PCMK__SUPPORTED_FORMAT_TEXT, @@ -78,14 +59,8 @@ static pcmk__supported_format_t formats[] = { { NULL, NULL, NULL } }; -static pcmk__message_entry_t fmt_functions[] = { - { "attribute", "text", attribute_text }, - - { NULL, NULL, NULL } -}; - struct { - char command; + enum attr_cmd command; gchar *attr_default; gchar *attr_id; gchar *attr_name; @@ -98,26 +73,49 @@ struct { gchar *set_name; char *set_type; gchar *type; - gboolean promotion_score; + char *opt_list; + gboolean all; + bool promotion_score; + gboolean score_update; } options = { - .command = 'G', - .promotion_score = FALSE + .command = attr_cmd_query, }; #define INDENT " " static gboolean +list_cb(const gchar *option_name, const gchar *optarg, gpointer data, + GError **error) { + options.command = attr_cmd_list; + pcmk__str_update(&options.opt_list, optarg); + return TRUE; +} + +static gboolean delete_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { - options.command = 'D'; + options.command = attr_cmd_delete; pcmk__str_update(&options.attr_value, NULL); return TRUE; } static gboolean +attr_name_cb(const gchar *option_name, const gchar *optarg, gpointer data, + GError **error) +{ + options.promotion_score = false; + + if (options.attr_name != NULL) { + g_free(options.attr_name); + } + options.attr_name = g_strdup(optarg); + return TRUE; +} + +static gboolean promotion_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { char *score_name = NULL; - options.promotion_score = TRUE; + options.promotion_score = true; if (options.attr_name) { g_free(options.attr_name); @@ -136,7 +134,7 @@ promotion_cb(const gchar *option_name, const gchar *optarg, gpointer data, GErro static gboolean update_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { - options.command = 'u'; + options.command = attr_cmd_update; pcmk__str_update(&options.attr_value, optarg); return TRUE; } @@ -147,14 +145,14 @@ utilization_cb(const gchar *option_name, const gchar *optarg, gpointer data, GEr g_free(options.type); } - options.type = g_strdup(XML_CIB_TAG_NODES); - pcmk__str_update(&options.set_type, XML_TAG_UTILIZATION); + options.type = g_strdup(PCMK_XE_NODES); + pcmk__str_update(&options.set_type, PCMK_XE_UTILIZATION); return TRUE; } static gboolean value_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { - options.command = 'G'; + options.command = attr_cmd_query; pcmk__str_update(&options.attr_value, NULL); return TRUE; } @@ -180,13 +178,20 @@ wait_cb (const gchar *option_name, const gchar *optarg, gpointer data, GError ** } static GOptionEntry selecting_entries[] = { + { "all", 'a', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.all, + "With -L/--list-options, include advanced and deprecated options in the\n" + INDENT "output. This is always treated as true when --output-as=xml is\n" + INDENT "specified.", + NULL, + }, + { "id", 'i', 0, G_OPTION_ARG_STRING, &options.attr_id, "(Advanced) Operate on instance of specified attribute with this\n" INDENT "XML ID", "XML_ID" }, - { "name", 'n', 0, G_OPTION_ARG_STRING, &options.attr_name, + { "name", 'n', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, attr_name_cb, "Operate on attribute or option with this name. For queries, this\n" INDENT "is optional, in which case all matching attributes will be\n" INDENT "returned.", @@ -217,8 +222,14 @@ static GOptionEntry selecting_entries[] = { }; static GOptionEntry command_entries[] = { + { "list-options", 'L', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, list_cb, + "List all available options of the given type.\n" + INDENT "Allowed values: " PCMK__VALUE_CLUSTER, + "TYPE" + }, + { "delete", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, delete_cb, - "Delete the attribute/option", + "Delete the attribute/option (with -n or -P)", NULL }, @@ -229,7 +240,7 @@ static GOptionEntry command_entries[] = { }, { "update", 'v', 0, G_OPTION_ARG_CALLBACK, update_cb, - "Update the value of the attribute/option", + "Update the value of the attribute/option (with -n or -P)", "VALUE" }, @@ -260,6 +271,35 @@ static GOptionEntry addl_entries[] = { "SECTION" }, + { "score", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.score_update, + "Treat new attribute values as atomic score updates where possible\n" + INDENT "(with --update/-v, when running against a CIB file or updating\n" + INDENT "an attribute outside the " PCMK_XE_STATUS " section; enabled\n" + INDENT "by default if --promotion/-p is specified)\n\n" + + INDENT "This currently happens by default and cannot be disabled, but\n" + INDENT "this default behavior is deprecated and will be removed in a\n" + INDENT "future release (exception: this will remain the default with\n" + INDENT "--promotion/-p). Set this flag if this behavior is desired.\n\n" + + INDENT "This option takes effect when updating XML attributes. For an\n" + INDENT "attribute named \"name\", if the new value is \"name++\" or\n" + INDENT "\"name+=X\" for some score X, the new value is set as follows:\n" + INDENT " * If attribute \"name\" is not already set to some value in\n" + INDENT " the element being updated, the new value is set as a literal\n" + INDENT " string.\n" + INDENT " * If the new value is \"name++\", then the attribute is set to\n" + INDENT " its existing value (parsed as a score) plus 1.\n" + INDENT " * If the new value is \"name+=X\" for some score X, then the\n" + INDENT " attribute is set to its existing value plus X, where the\n" + INDENT " existing value and X are parsed and added as scores.\n\n" + + INDENT "Scores are integer values capped at INFINITY and -INFINITY.\n" + INDENT "Refer to Pacemaker Explained and to the char2score() function\n" + INDENT "for more details on scores, including how they're parsed and\n" + INDENT "added.", + NULL }, + { "wait", 'W', 0, G_OPTION_ARG_CALLBACK, wait_cb, "Wait for some event to occur before returning. Values are 'no' (wait\n" INDENT "only for the attribute daemon to acknowledge the request),\n" @@ -288,7 +328,7 @@ static GOptionEntry deprecated_entries[] = { NULL, NULL }, - { "attr-name", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.attr_name, + { "attr-name", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, attr_name_cb, NULL, NULL }, @@ -314,36 +354,39 @@ static GOptionEntry deprecated_entries[] = { static void get_node_name_from_local(void) { - char *hostname = pcmk_hostname(); + struct utsname hostinfo; g_free(options.dest_uname); - /* This silliness is so that dest_uname is always a glib-managed - * string so we know how to free it later. pcmk_hostname returns - * a newly allocated string via strdup. - */ - options.dest_uname = g_strdup(hostname); - free(hostname); + if (uname(&hostinfo) == 0) { + options.dest_uname = g_strdup(hostinfo.nodename); + } else { + options.dest_uname = NULL; + } } static int -send_attrd_update(char command, const char *attr_node, const char *attr_name, - const char *attr_value, const char *attr_set, - const char *attr_dampen, uint32_t attr_options) +send_attrd_update(enum attr_cmd command, const char *attr_node, + const char *attr_name, const char *attr_value, + const char *attr_set, const char *attr_dampen, + uint32_t attr_options) { int rc = pcmk_rc_ok; uint32_t opts = attr_options; switch (command) { - case 'D': + case attr_cmd_delete: rc = pcmk__attrd_api_delete(NULL, attr_node, attr_name, opts); break; - case 'u': + case attr_cmd_update: rc = pcmk__attrd_api_update(NULL, attr_node, attr_name, attr_value, NULL, attr_set, NULL, opts | pcmk__node_attr_value); break; + + default: + break; } if (rc != pcmk_rc_ok) { @@ -364,7 +407,7 @@ delete_attr_on_node(xmlNode *child, void *userdata) { struct delete_data_s *dd = (struct delete_data_s *) userdata; - const char *attr_name = crm_element_value(child, XML_NVPAIR_ATTR_NAME); + const char *attr_name = crm_element_value(child, PCMK_XA_NAME); int rc = pcmk_rc_ok; if (!pcmk__str_eq(attr_name, options.attr_pattern, pcmk__str_regex)) { @@ -383,6 +426,22 @@ delete_attr_on_node(xmlNode *child, void *userdata) return rc; } +static void +command_list(pcmk__output_t *out) +{ + if (pcmk__str_eq(options.opt_list, PCMK__VALUE_CLUSTER, pcmk__str_none)) { + exit_code = pcmk_rc2exitc(pcmk__list_cluster_options(out, options.all)); + + } else { + // @TODO Improve usage messages to reduce duplication + exit_code = CRM_EX_USAGE; + g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, + "Invalid --list-options value '%s'. Allowed values: " + PCMK__VALUE_CLUSTER, + pcmk__s(options.opt_list, "(BUG: none)")); + } +} + static int command_delete(pcmk__output_t *out, cib_t *cib) { @@ -405,10 +464,6 @@ command_delete(pcmk__output_t *out, cib_t *cib) rc = pcmk__xe_foreach_child(result, NULL, delete_attr_on_node, &dd); - if (rc != pcmk_rc_ok) { - goto done_deleting; - } - } else { rc = cib__delete_node_attr(out, cib, cib_opts, options.type, options.dest_node, options.set_type, options.set_name, options.attr_id, @@ -440,7 +495,7 @@ update_attr_on_node(xmlNode *child, void *userdata) { struct update_data_s *ud = (struct update_data_s *) userdata; - const char *attr_name = crm_element_value(child, XML_NVPAIR_ATTR_NAME); + const char *attr_name = crm_element_value(child, PCMK_XA_NAME); if (!pcmk__str_eq(attr_name, options.attr_pattern, pcmk__str_regex)) { return pcmk_rc_ok; @@ -450,7 +505,7 @@ update_attr_on_node(xmlNode *child, void *userdata) options.dest_node, options.set_type, options.set_name, options.attr_id, attr_name, options.attr_value, NULL, - ud->is_remote_node ? "remote" : NULL); + ud->is_remote_node? PCMK_VALUE_REMOTE : NULL); } static int @@ -461,9 +516,10 @@ command_update(pcmk__output_t *out, cib_t *cib, int is_remote_node) xmlNode *result = NULL; bool use_pattern = options.attr_pattern != NULL; - CRM_LOG_ASSERT(options.type != NULL); - CRM_LOG_ASSERT(options.attr_name != NULL); - CRM_LOG_ASSERT(options.attr_value != NULL); + /* @COMPAT When we drop default support for expansion in crm_attribute, + * guard with `if (options.score_update)` + */ + cib__set_call_options(cib_opts, crm_system_name, cib_score_update); /* See the comment in command_query regarding xpath and regular expressions. */ if (use_pattern) { @@ -479,16 +535,12 @@ command_update(pcmk__output_t *out, cib_t *cib, int is_remote_node) rc = pcmk__xe_foreach_child(result, NULL, update_attr_on_node, &ud); - if (rc != pcmk_rc_ok) { - goto done_updating; - } - } else { rc = cib__update_node_attr(out, cib, cib_opts, options.type, options.dest_node, options.set_type, options.set_name, options.attr_id, - options.attr_name, options.attr_value, - NULL, is_remote_node ? "remote" : NULL); + options.attr_name, options.attr_value, NULL, + is_remote_node? PCMK_VALUE_REMOTE : NULL); } done_updating: @@ -507,9 +559,8 @@ output_one_attribute(xmlNode *node, void *userdata) { struct output_data_s *od = (struct output_data_s *) userdata; - const char *name = crm_element_value(node, XML_NVPAIR_ATTR_NAME); - const char *value = crm_element_value(node, XML_NVPAIR_ATTR_VALUE); - const char *host = crm_element_value(node, PCMK__XA_ATTR_NODE_NAME); + const char *name = crm_element_value(node, PCMK_XA_NAME); + const char *value = crm_element_value(node, PCMK_XA_VALUE); const char *type = options.type; const char *attr_id = options.attr_id; @@ -518,7 +569,8 @@ output_one_attribute(xmlNode *node, void *userdata) return pcmk_rc_ok; } - od->out->message(od->out, "attribute", type, attr_id, name, value, host); + od->out->message(od->out, "attribute", type, attr_id, name, value, NULL, + od->out->quiet, true); od->did_output = true; crm_info("Read %s='%s' %s%s", pcmk__s(name, "<null>"), pcmk__s(value, ""), @@ -556,10 +608,9 @@ command_query(pcmk__output_t *out, cib_t *cib) const char *attr_id = options.attr_id; const char *attr_name = options.attr_name; const char *attr_default = options.attr_default; - const char *dest_uname = options.dest_uname; out->message(out, "attribute", type, attr_id, attr_name, attr_default, - dest_uname); + NULL, out->quiet, true); rc = pcmk_rc_ok; } else if (rc != pcmk_rc_ok) { @@ -589,22 +640,22 @@ set_type(void) if (options.type == NULL) { if (options.promotion_score) { // Updating a promotion score node attribute - options.type = g_strdup(XML_CIB_TAG_STATUS); + options.type = g_strdup(PCMK_XE_STATUS); } else if (options.dest_uname != NULL) { // Updating some other node attribute - options.type = g_strdup(XML_CIB_TAG_NODES); + options.type = g_strdup(PCMK_XE_NODES); } else { // Updating cluster options - options.type = g_strdup(XML_CIB_TAG_CRMCONFIG); + options.type = g_strdup(PCMK_XE_CRM_CONFIG); } } else if (pcmk__str_eq(options.type, "reboot", pcmk__str_casei)) { - options.type = g_strdup(XML_CIB_TAG_STATUS); + options.type = g_strdup(PCMK_XE_STATUS); } else if (pcmk__str_eq(options.type, "forever", pcmk__str_casei)) { - options.type = g_strdup(XML_CIB_TAG_NODES); + options.type = g_strdup(PCMK_XE_NODES); } } @@ -614,14 +665,16 @@ use_attrd(void) /* Only go through the attribute manager for transient attributes, and * then only if we're not using a file as the CIB. */ - return pcmk__str_eq(options.type, XML_CIB_TAG_STATUS, pcmk__str_casei) && + return pcmk__str_eq(options.type, PCMK_XE_STATUS, pcmk__str_casei) && getenv("CIB_file") == NULL && getenv("CIB_shadow") == NULL; } static bool try_ipc_update(void) { - return use_attrd() && (options.command == 'D' || options.command == 'u'); + return use_attrd() + && ((options.command == attr_cmd_delete) + || (options.command == attr_cmd_update)); } static bool @@ -630,13 +683,30 @@ pattern_used_correctly(void) /* --pattern can only be used with: * -G (query), -v (update), or -D (delete) */ - return options.command == 'G' || options.command == 'u' || options.command == 'D'; + switch (options.command) { + case attr_cmd_delete: + case attr_cmd_query: + case attr_cmd_update: + return true; + default: + return false; + } } static bool delete_used_correctly(void) { - return options.command != 'D' || options.attr_name != NULL || options.attr_pattern != NULL; + return (options.command != attr_cmd_delete) + || (options.attr_name != NULL) + || (options.attr_pattern != NULL); +} + +static bool +update_used_correctly(void) +{ + return (options.command != attr_cmd_update) + || (options.attr_name != NULL) + || (options.attr_pattern != NULL); } static GOptionContext * @@ -664,10 +734,14 @@ build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) { "\tcrm_attribute --node myhost --name location --update backoffice\n\n" "Delete the 'location' node attribute for host 'myhost':\n\n" "\tcrm_attribute --node myhost --name location --delete\n\n" - "Query the value of the 'cluster-delay' cluster option:\n\n" - "\tcrm_attribute --type crm_config --name cluster-delay --query\n\n" - "Query value of the 'cluster-delay' cluster option and print only the value:\n\n" - "\tcrm_attribute --type crm_config --name cluster-delay --query --quiet\n\n"; + "Query the value of the '" PCMK_OPT_CLUSTER_DELAY + "' cluster option:\n\n" + "\tcrm_attribute --type crm_config --name " + PCMK_OPT_CLUSTER_DELAY " --query\n\n" + "Query value of the '" PCMK_OPT_CLUSTER_DELAY + "' cluster option and print only the value:\n\n" + "\tcrm_attribute --type crm_config --name " + PCMK_OPT_CLUSTER_DELAY " --query --quiet\n\n"; context = pcmk__build_arg_context(args, "text (default), xml", group, NULL); pcmk__add_main_args(context, extra_prog_entries); @@ -717,7 +791,6 @@ main(int argc, char **argv) } pcmk__register_lib_messages(out); - pcmk__register_messages(out, fmt_functions); if (args->version) { out->version(out, false); @@ -726,6 +799,11 @@ main(int argc, char **argv) out->quiet = args->quiet; + if (options.command == attr_cmd_list) { + command_list(out); + goto done; + } + if (options.promotion_score && options.attr_name == NULL) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, @@ -753,8 +831,8 @@ main(int argc, char **argv) set_type(); // Use default node if not given (except for cluster options and tickets) - if (!pcmk__strcase_any_of(options.type, XML_CIB_TAG_CRMCONFIG, XML_CIB_TAG_TICKETS, - NULL)) { + if (!pcmk__strcase_any_of(options.type, + PCMK_XE_CRM_CONFIG, PCMK_XE_TICKETS, NULL)) { /* If we are being called from a resource agent via the cluster, * the correct local node name will be passed as an environment * variable. Otherwise, we have to ask the cluster. @@ -762,8 +840,23 @@ main(int argc, char **argv) const char *target = pcmk__node_attr_target(options.dest_uname); if (target != NULL) { - g_free(options.dest_uname); - options.dest_uname = g_strdup(target); + /* If options.dest_uname is "auto" or "localhost", then + * pcmk__node_attr_target() may return it, depending on environment + * variables. In that case, attribute lookups will fail for "auto" + * (unless there's a node named "auto"). attrd maps "localhost" to + * the true local node name for queries. + * + * @TODO + * * Investigate whether "localhost" is mapped to a real node name + * for non-query commands. If not, possibly modify it so that it + * is. + * * Map "auto" to "localhost" (probably). + */ + if (target != (const char *) options.dest_uname) { + g_free(options.dest_uname); + options.dest_uname = g_strdup(target); + } + } else if (getenv("CIB_file") != NULL && options.dest_uname == NULL) { get_node_name_from_local(); } @@ -800,6 +893,13 @@ main(int argc, char **argv) goto done; } + if (!update_used_correctly()) { + exit_code = CRM_EX_USAGE; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "Error: must specify attribute name or pattern to update"); + goto done; + } + if (options.attr_pattern) { if (options.attr_name) { exit_code = CRM_EX_USAGE; @@ -824,20 +924,26 @@ main(int argc, char **argv) options.attr_options |= pcmk__node_attr_remote; } - if (pcmk__str_eq(options.set_type, XML_TAG_UTILIZATION, pcmk__str_none)) { + if (pcmk__str_eq(options.set_type, PCMK_XE_UTILIZATION, pcmk__str_none)) { options.attr_options |= pcmk__node_attr_utilization; } if (try_ipc_update() && (send_attrd_update(options.command, options.dest_uname, options.attr_name, options.attr_value, options.set_name, NULL, options.attr_options) == pcmk_rc_ok)) { + + const char *update = options.attr_value; + + if (options.command == attr_cmd_delete) { + update = "<none>"; + } crm_info("Update %s=%s sent via pacemaker-attrd", - options.attr_name, ((options.command == 'D')? "<none>" : options.attr_value)); + options.attr_name, update); - } else if (options.command == 'D') { + } else if (options.command == attr_cmd_delete) { rc = command_delete(out, the_cib); - } else if (options.command == 'u') { + } else if (options.command == attr_cmd_update) { rc = command_update(out, the_cib, is_remote_node); } else { |