summaryrefslogtreecommitdiffstats
path: root/tools/crm_attribute.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tools/crm_attribute.c316
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 {