summaryrefslogtreecommitdiffstats
path: root/tools/attrd_updater.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/attrd_updater.c')
-rw-r--r--tools/attrd_updater.c520
1 files changed, 520 insertions, 0 deletions
diff --git a/tools/attrd_updater.c b/tools/attrd_updater.c
new file mode 100644
index 0000000..60e4cc7
--- /dev/null
+++ b/tools/attrd_updater.c
@@ -0,0 +1,520 @@
+/*
+ * Copyright 2004-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU General Public License version 2
+ * or later (GPLv2+) WITHOUT ANY WARRANTY.
+ */
+
+#include <crm_internal.h>
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <libgen.h>
+
+#include <sys/param.h>
+#include <sys/types.h>
+
+#include <crm/crm.h>
+#include <crm/msg_xml.h>
+#include <crm/common/ipc_attrd_internal.h>
+#include <crm/common/cmdline_internal.h>
+#include <crm/common/output_internal.h>
+#include <crm/common/xml_internal.h>
+
+#include <crm/common/attrd_internal.h>
+
+#include <pcmki/pcmki_output.h>
+
+#define SUMMARY "query and update Pacemaker node attributes"
+
+static pcmk__supported_format_t formats[] = {
+ PCMK__SUPPORTED_FORMAT_NONE,
+ PCMK__SUPPORTED_FORMAT_TEXT,
+ PCMK__SUPPORTED_FORMAT_XML,
+ { NULL, NULL, NULL }
+};
+
+GError *error = NULL;
+bool printed_values = false;
+
+struct {
+ char command;
+ gchar *attr_dampen;
+ gchar *attr_name;
+ gchar *attr_pattern;
+ gchar *attr_node;
+ gchar *attr_set;
+ char *attr_value;
+ uint32_t attr_options;
+ gboolean query_all;
+ gboolean quiet;
+} options = {
+ .attr_options = pcmk__node_attr_none,
+ .command = 'Q',
+};
+
+static gboolean
+command_cb (const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
+ pcmk__str_update(&options.attr_value, optarg);
+
+ if (pcmk__str_any_of(option_name, "--update-both", "-B", NULL)) {
+ options.command = 'B';
+ } else if (pcmk__str_any_of(option_name, "--delete", "-D", NULL)) {
+ options.command = 'D';
+ } else if (pcmk__str_any_of(option_name, "--query", "-Q", NULL)) {
+ options.command = 'Q';
+ } else if (pcmk__str_any_of(option_name, "--refresh", "-R", NULL)) {
+ options.command = 'R';
+ } else if (pcmk__str_any_of(option_name, "--update", "-U", "-v", NULL)) {
+ options.command = 'U';
+ } else if (pcmk__str_any_of(option_name, "--update-delay", "-Y", NULL)) {
+ options.command = 'Y';
+ }
+
+ return TRUE;
+}
+
+static gboolean
+private_cb (const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
+ pcmk__set_node_attr_flags(options.attr_options, pcmk__node_attr_private);
+ return TRUE;
+}
+
+static gboolean
+section_cb (const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
+ if (pcmk__str_any_of(optarg, "nodes", "forever", NULL)) {
+ pcmk__set_node_attr_flags(options.attr_options, pcmk__node_attr_perm);
+ } else if (pcmk__str_any_of(optarg, "status", "reboot", NULL)) {
+ pcmk__clear_node_attr_flags(options.attr_options, pcmk__node_attr_perm);
+ } else {
+ g_set_error(err, PCMK__EXITC_ERROR, CRM_EX_USAGE, "Unknown value for --lifetime: %s",
+ optarg);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+attr_set_type_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ if (pcmk__str_any_of(option_name, "-z", "--utilization", NULL)) {
+ pcmk__set_node_attr_flags(options.attr_options, pcmk__node_attr_utilization);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+wait_cb (const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
+ if (pcmk__str_eq(optarg, "no", pcmk__str_none)) {
+ pcmk__clear_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local | pcmk__node_attr_sync_cluster);
+ return TRUE;
+ } else if (pcmk__str_eq(optarg, PCMK__VALUE_LOCAL, pcmk__str_none)) {
+ pcmk__clear_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local | pcmk__node_attr_sync_cluster);
+ pcmk__set_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local);
+ return TRUE;
+ } else if (pcmk__str_eq(optarg, PCMK__VALUE_CLUSTER, pcmk__str_none)) {
+ pcmk__clear_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local | pcmk__node_attr_sync_cluster);
+ pcmk__set_node_attr_flags(options.attr_options, pcmk__node_attr_sync_cluster);
+ return TRUE;
+ } else {
+ g_set_error(err, PCMK__EXITC_ERROR, CRM_EX_USAGE,
+ "--wait= must be one of 'no', 'local', 'cluster'");
+ return FALSE;
+ }
+}
+
+#define INDENT " "
+
+static GOptionEntry required_entries[] = {
+ { "name", 'n', 0, G_OPTION_ARG_STRING, &options.attr_name,
+ "The attribute's name",
+ "NAME" },
+
+ { "pattern", 'P', 0, G_OPTION_ARG_STRING, &options.attr_pattern,
+ "Operate on all attributes matching this pattern\n"
+ INDENT "(with -B, -D, -U, or -Y)",
+ "PATTERN"
+ },
+
+ { NULL }
+};
+
+static GOptionEntry command_entries[] = {
+ { "update", 'U', 0, G_OPTION_ARG_CALLBACK, command_cb,
+ "Update attribute's value in pacemaker-attrd. If this causes the value\n"
+ INDENT "to change, it will also be updated in the cluster configuration.",
+ "VALUE" },
+
+ { "update-both", 'B', 0, G_OPTION_ARG_CALLBACK, command_cb,
+ "Update attribute's value and time to wait (dampening) in\n"
+ INDENT "pacemaker-attrd. If this causes the value or dampening to change,\n"
+ INDENT "the attribute will also be written to the cluster configuration,\n"
+ INDENT "so be aware that repeatedly changing the dampening reduces its\n"
+ INDENT "effectiveness.\n"
+ INDENT "Requires -d/--delay",
+ "VALUE" },
+
+ { "update-delay", 'Y', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
+ "Update attribute's dampening in pacemaker-attrd. If this causes\n"
+ INDENT "the dampening to change, the attribute will also be written\n"
+ INDENT "to the cluster configuration, so be aware that repeatedly\n"
+ INDENT "changing the dampening reduces its effectiveness.\n"
+ INDENT "Requires -d/--delay",
+ NULL },
+
+ { "query", 'Q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
+ "Query the attribute's value from pacemaker-attrd",
+ NULL },
+
+ { "delete", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
+ "Unset attribute from pacemaker-attrd. At the moment, there is no way\n"
+ INDENT "to remove an attribute. This option will instead set its value\n"
+ INDENT "to the empty string.",
+ NULL },
+
+ { "refresh", 'R', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
+ "(Advanced) Force the pacemaker-attrd daemon to resend all current\n"
+ INDENT "values to the CIB",
+ NULL },
+
+ { NULL }
+};
+
+static GOptionEntry addl_entries[] = {
+ { "delay", 'd', 0, G_OPTION_ARG_STRING, &options.attr_dampen,
+ "The time to wait (dampening) in seconds for further changes\n"
+ INDENT "before sending to the CIB",
+ "SECONDS" },
+
+ { "set", 's', 0, G_OPTION_ARG_STRING, &options.attr_set,
+ "(Advanced) The attribute set in which to place the value",
+ "SET" },
+
+ { "node", 'N', 0, G_OPTION_ARG_STRING, &options.attr_node,
+ "Set the attribute for the named node (instead of the local one)",
+ "NODE" },
+
+ { "all", 'A', 0, G_OPTION_ARG_NONE, &options.query_all,
+ "Show values of the attribute for all nodes (query only)",
+ NULL },
+
+ { "lifetime", 'l', 0, G_OPTION_ARG_CALLBACK, section_cb,
+ "(Not yet implemented) Lifetime of the node attribute (silently\n"
+ INDENT "ignored by cluster)",
+ "SECTION" },
+
+ { "private", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, private_cb,
+ "If this creates a new attribute, never write the attribute to CIB",
+ 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"
+ INDENT "'local' (wait until the change has propagated to where a local\n"
+ INDENT "query will return the request value, or the value set by a\n"
+ INDENT "later request), or 'cluster' (wait until the change has propagated\n"
+ INDENT "to where a query anywhere on the cluster will return the requested\n"
+ INDENT "value, or the value set by a later request). Default is 'no'.",
+ "UNTIL" },
+
+ { "utilization", 'z', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, attr_set_type_cb,
+ "When creating a new attribute, create it as a node utilization attribute\n"
+ INDENT "instead of an instance attribute. If the attribute already exists,\n"
+ INDENT "its existing type (utilization vs. instance) will be used regardless.\n"
+ INDENT "(with -B, -U, -Y)",
+ NULL },
+
+ { NULL }
+};
+
+static GOptionEntry deprecated_entries[] = {
+ { "quiet", 'q', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.quiet,
+ NULL,
+ NULL },
+
+ { "update", 'v', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, command_cb,
+ NULL,
+ NULL },
+
+ { "section", 'S', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, section_cb,
+ NULL,
+ NULL },
+
+ { NULL }
+};
+
+static int send_attrd_query(pcmk__output_t *out, const char *attr_name, const char *attr_node,
+ gboolean query_all);
+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);
+
+static bool
+pattern_used_correctly(void)
+{
+ /* --pattern can only be used with:
+ * -B (update-both), -D (delete), -U (update), or -Y (update-delay)
+ */
+ return options.command == 'B' || options.command == 'D' || options.command == 'U' || options.command == 'Y';
+}
+
+static GOptionContext *
+build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
+ GOptionContext *context = NULL;
+
+ context = pcmk__build_arg_context(args, "text (default), xml", group, NULL);
+
+ pcmk__add_arg_group(context, "required", "Required Arguments:",
+ "Show required arguments", required_entries);
+ pcmk__add_arg_group(context, "command", "Command:",
+ "Show command options (mutually exclusive)", command_entries);
+ pcmk__add_arg_group(context, "additional", "Additional Options:",
+ "Show additional options", addl_entries);
+ pcmk__add_arg_group(context, "deprecated", "Deprecated Options:",
+ "Show deprecated options", deprecated_entries);
+
+ return context;
+}
+
+int
+main(int argc, char **argv)
+{
+ int rc = pcmk_rc_ok;
+ crm_exit_t exit_code = CRM_EX_OK;
+
+ pcmk__output_t *out = NULL;
+
+ GOptionGroup *output_group = NULL;
+ pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
+ GOptionContext *context = build_arg_context(args, &output_group);
+ gchar **processed_args = pcmk__cmdline_preproc(argv, "dlnsvBNUS");
+
+ 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("attrd_updater", 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;
+ }
+
+ if (args->version) {
+ out->version(out, false);
+ goto done;
+ }
+
+ if (options.attr_pattern) {
+ if (options.attr_name) {
+ exit_code = CRM_EX_USAGE;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ "Error: --name and --pattern cannot be used at the same time");
+ goto done;
+ }
+
+ if (!pattern_used_correctly()) {
+ exit_code = CRM_EX_USAGE;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ "Error: pattern can only be used with delete or update");
+ goto done;
+ }
+
+ g_free(options.attr_name);
+ options.attr_name = options.attr_pattern;
+ options.attr_options |= pcmk__node_attr_pattern;
+ }
+
+ if (options.command != 'R' && options.attr_name == NULL) {
+ exit_code = CRM_EX_USAGE;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Command requires --name or --pattern argument");
+ goto done;
+ } else if ((options.command == 'B'|| options.command == 'Y') && options.attr_dampen == NULL) {
+ out->info(out, "Warning: '%c' command given without required --delay", options.command);
+ }
+
+ pcmk__register_lib_messages(out);
+
+ if (options.command == 'Q') {
+ int rc = send_attrd_query(out, options.attr_name, options.attr_node, options.query_all);
+ exit_code = pcmk_rc2exitc(rc);
+ } else {
+ /* @TODO We don't know whether the specified node is a Pacemaker Remote
+ * node or not, so we can't set pcmk__node_attr_remote when appropriate.
+ * However, it's not a big problem, because pacemaker-attrd will learn
+ * and remember a node's "remoteness".
+ */
+ int rc = send_attrd_update(options.command, options.attr_node,
+ options.attr_name, options.attr_value,
+ options.attr_set, options.attr_dampen,
+ options.attr_options);
+ exit_code = pcmk_rc2exitc(rc);
+ }
+
+done:
+ g_strfreev(processed_args);
+ pcmk__free_arg_context(context);
+ g_free(options.attr_dampen);
+ g_free(options.attr_name);
+ g_free(options.attr_node);
+ g_free(options.attr_set);
+ free(options.attr_value);
+
+ pcmk__output_and_clear_error(&error, out);
+
+ if (out != NULL) {
+ out->finish(out, exit_code, true, NULL);
+ pcmk__output_free(out);
+ }
+
+ pcmk__unregister_formats();
+ crm_exit(exit_code);
+}
+
+/*!
+ * \brief Print the attribute values in a pacemaker-attrd XML query reply
+ *
+ * \param[in,out] out Output object
+ * \param[in] reply List of attribute name/value pairs
+ *
+ * \return true if any values were printed
+ */
+static void
+print_attrd_values(pcmk__output_t *out, const GList *reply)
+{
+ for (const GList *iter = reply; iter != NULL; iter = iter->next) {
+ const pcmk__attrd_query_pair_t *pair = iter->data;
+
+ out->message(out, "attribute", NULL, NULL, pair->name, pair->value,
+ pair->node);
+ printed_values = true;
+ }
+}
+
+static void
+attrd_event_cb(pcmk_ipc_api_t *attrd_api, enum pcmk_ipc_event event_type,
+ crm_exit_t status, void *event_data, void *user_data)
+{
+ pcmk__output_t *out = (pcmk__output_t *) user_data;
+ pcmk__attrd_api_reply_t *reply = event_data;
+
+ if (event_type != pcmk_ipc_event_reply || status != CRM_EX_OK) {
+ return;
+ }
+
+ /* Print the values from the reply. */
+ if (reply->reply_type == pcmk__attrd_reply_query) {
+ print_attrd_values(out, reply->data.pairs);
+ }
+}
+
+/*!
+ * \brief Submit a query to pacemaker-attrd and print reply
+ *
+ * \param[in,out] out Output object
+ * \param[in] attr_name Name of attribute to be affected by request
+ * \param[in] attr_node Name of host to query for (or NULL for localhost)
+ * \param[in] query_all If TRUE, ignore attr_node and query all nodes
+ *
+ * \return Standard Pacemaker return code
+ */
+static int
+send_attrd_query(pcmk__output_t *out, const char *attr_name,
+ const char *attr_node, gboolean query_all)
+{
+ uint32_t options = pcmk__node_attr_none;
+ pcmk_ipc_api_t *attrd_api = NULL;
+ int rc = pcmk_rc_ok;
+
+ // Create attrd IPC object
+ rc = pcmk_new_ipc_api(&attrd_api, pcmk_ipc_attrd);
+ if (rc != pcmk_rc_ok) {
+ g_set_error(&error, PCMK__RC_ERROR, rc,
+ "Could not connect to attrd: %s", pcmk_rc_str(rc));
+ return ENOTCONN;
+ }
+
+ pcmk_register_ipc_callback(attrd_api, attrd_event_cb, out);
+
+ // Connect to attrd (without main loop)
+ rc = pcmk_connect_ipc(attrd_api, pcmk_ipc_dispatch_sync);
+ if (rc != pcmk_rc_ok) {
+ g_set_error(&error, PCMK__RC_ERROR, rc,
+ "Could not connect to attrd: %s", pcmk_rc_str(rc));
+ pcmk_free_ipc_api(attrd_api);
+ return rc;
+ }
+
+ /* Decide which node(s) to query */
+ if (query_all == TRUE) {
+ options |= pcmk__node_attr_query_all;
+ }
+
+ rc = pcmk__attrd_api_query(attrd_api, attr_node, attr_name, options);
+
+ if (rc != pcmk_rc_ok) {
+ g_set_error(&error, PCMK__RC_ERROR, rc, "Could not query value of %s: %s (%d)",
+ attr_name, pcmk_strerror(rc), rc);
+ } else if (!printed_values) {
+ rc = pcmk_rc_schema_validation;
+ g_set_error(&error, PCMK__RC_ERROR, rc,
+ "Could not query value of %s: attribute does not exist", attr_name);
+ }
+
+ pcmk_disconnect_ipc(attrd_api);
+ pcmk_free_ipc_api(attrd_api);
+
+ return rc;
+}
+
+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)
+{
+ int rc = pcmk_rc_ok;
+
+ switch (command) {
+ case 'B':
+ rc = pcmk__attrd_api_update(NULL, attr_node, attr_name, attr_value,
+ attr_dampen, attr_set, NULL,
+ attr_options | pcmk__node_attr_value | pcmk__node_attr_delay);
+ break;
+
+ case 'D':
+ rc = pcmk__attrd_api_delete(NULL, attr_node, attr_name, attr_options);
+ break;
+
+ case 'R':
+ rc = pcmk__attrd_api_refresh(NULL, attr_node);
+ break;
+
+ case 'U':
+ rc = pcmk__attrd_api_update(NULL, attr_node, attr_name, attr_value,
+ NULL, attr_set, NULL,
+ attr_options | pcmk__node_attr_value);
+ break;
+
+ case 'Y':
+ rc = pcmk__attrd_api_update(NULL, attr_node, attr_name, NULL,
+ attr_dampen, attr_set, NULL,
+ attr_options | pcmk__node_attr_delay);
+ break;
+ }
+
+ if (rc != pcmk_rc_ok) {
+ g_set_error(&error, PCMK__RC_ERROR, rc, "Could not update %s=%s: %s (%d)",
+ attr_name, attr_value, pcmk_rc_str(rc), rc);
+ }
+
+ return rc;
+}