root/tools/attrd_updater.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. command_cb
  2. private_cb
  3. build_arg_context
  4. main
  5. send_attrd_query
  6. validate_attrd_reply
  7. print_attrd_values
  8. do_query
  9. do_update

   1 /*
   2  * Copyright 2004-2022 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU General Public License version 2
   7  * or later (GPLv2+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 
  12 #include <stdio.h>
  13 #include <unistd.h>
  14 #include <stdlib.h>
  15 #include <libgen.h>
  16 
  17 #include <sys/param.h>
  18 #include <sys/types.h>
  19 
  20 #include <crm/crm.h>
  21 #include <crm/msg_xml.h>
  22 #include <crm/common/cmdline_internal.h>
  23 #include <crm/common/output_internal.h>
  24 #include <crm/common/xml_internal.h>
  25 #include <crm/common/ipc.h>
  26 
  27 #include <crm/common/attrd_internal.h>
  28 
  29 #include <pcmki/pcmki_output.h>
  30 
  31 #define SUMMARY "query and update Pacemaker node attributes"
  32 
  33 static pcmk__supported_format_t formats[] = {
  34     PCMK__SUPPORTED_FORMAT_NONE,
  35     PCMK__SUPPORTED_FORMAT_TEXT,
  36     PCMK__SUPPORTED_FORMAT_XML,
  37     { NULL, NULL, NULL }
  38 };
  39 
  40 GError *error = NULL;
  41 
  42 struct {
  43     char command;
  44     gchar *attr_dampen;
  45     gchar *attr_name;
  46     gchar *attr_node;
  47     gchar *attr_section;
  48     gchar *attr_set;
  49     char *attr_value;
  50     int attr_options;
  51     gboolean query_all;
  52 } options = {
  53     .attr_options = pcmk__node_attr_none,
  54     .command = 'Q',
  55 };
  56 
  57 static gboolean
  58 command_cb (const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
  59     pcmk__str_update(&options.attr_value, optarg);
  60 
  61     if (pcmk__str_any_of(option_name, "--update-both", "-B", NULL)) {
  62         options.command = 'B';
  63     } else if (pcmk__str_any_of(option_name, "--delete", "-D", NULL)) {
  64         options.command = 'D';
  65     } else if (pcmk__str_any_of(option_name, "--query", "-Q", NULL)) {
  66         options.command = 'Q';
  67     } else if (pcmk__str_any_of(option_name, "--refresh", "-R", NULL)) {
  68         options.command = 'R';
  69     } else if (pcmk__str_any_of(option_name, "--update", "-U", "-v", NULL)) {
  70         options.command = 'U';
  71     } else if (pcmk__str_any_of(option_name, "--update-delay", "-Y", NULL)) {
  72         options.command = 'Y';
  73     }
  74 
  75     return TRUE;
  76 }
  77 
  78 static gboolean
  79 private_cb (const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
  80     pcmk__set_node_attr_flags(options.attr_options, pcmk__node_attr_private);
  81     return TRUE;
  82 }
  83 
  84 #define INDENT "                              "
  85 
  86 static GOptionEntry required_entries[] = {
  87     { "name", 'n', 0, G_OPTION_ARG_STRING, &options.attr_name,
  88       "The attribute's name",
  89       "NAME" },
  90 
  91     { NULL }
  92 };
  93 
  94 static GOptionEntry command_entries[] = {
  95     { "update", 'U', 0, G_OPTION_ARG_CALLBACK, command_cb,
  96       "Update attribute's value in pacemaker-attrd. If this causes the value\n"
  97       INDENT "to change, it will also be updated in the cluster configuration.",
  98       "VALUE" },
  99 
 100     { "update-both", 'B', 0, G_OPTION_ARG_CALLBACK, command_cb,
 101       "Update attribute's value and time to wait (dampening) in\n"
 102       INDENT "pacemaker-attrd. If this causes the value or dampening to change,\n"
 103       INDENT "the attribute will also be written to the cluster configuration,\n"
 104       INDENT "so be aware that repeatedly changing the dampening reduces its\n"
 105       INDENT "effectiveness.",
 106       "VALUE" },
 107 
 108     { "update-delay", 'Y', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 109       "Update attribute's dampening in pacemaker-attrd (requires\n"
 110       INDENT "-d/--delay). If this causes the dampening to change, the\n"
 111       INDENT "attribute will also be written to the cluster configuration, so\n"
 112       INDENT "be aware that repeatedly changing the dampening reduces its\n"
 113       INDENT "effectiveness.",
 114       NULL },
 115 
 116     { "query", 'Q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 117       "Query the attribute's value from pacemaker-attrd",
 118       NULL },
 119 
 120     { "delete", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 121       "Unset attribute from pacemaker-attrd. At the moment, there is no way\n"
 122       INDENT "to remove an attribute. This option will instead set its value\n"
 123       INDENT "to the empty string.",
 124       NULL },
 125 
 126     { "refresh", 'R', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 127       "(Advanced) Force the pacemaker-attrd daemon to resend all current\n"
 128       INDENT "values to the CIB",
 129       NULL },
 130 
 131     { NULL }
 132 };
 133 
 134 static GOptionEntry addl_entries[] = {
 135     { "delay", 'd', 0, G_OPTION_ARG_STRING, &options.attr_dampen,
 136       "The time to wait (dampening) in seconds for further changes\n"
 137       INDENT "before sending to the CIB",
 138       "SECONDS" },
 139 
 140     { "set", 's', 0, G_OPTION_ARG_STRING, &options.attr_set,
 141       "(Advanced) The attribute set in which to place the value",
 142       "SET" },
 143 
 144     { "node", 'N', 0, G_OPTION_ARG_STRING, &options.attr_node,
 145       "Set the attribute for the named node (instead of the local one)",
 146       "NODE" },
 147 
 148     { "all", 'A', 0, G_OPTION_ARG_NONE, &options.query_all,
 149       "Show values of the attribute for all nodes (query only)",
 150       NULL },
 151 
 152     { "lifetime", 'l', 0, G_OPTION_ARG_STRING, &options.attr_section,
 153       "(Not yet implemented) Lifetime of the node attribute (silently\n"
 154       INDENT "ignored by cluster)",
 155       "SECTION" },
 156 
 157     { "private", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, private_cb,
 158       "If this creates a new attribute, never write the attribute to CIB",
 159       NULL },
 160 
 161     { NULL }
 162 };
 163 
 164 static GOptionEntry deprecated_entries[] = {
 165     { "update", 'v', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, command_cb,
 166       NULL,
 167       NULL },
 168 
 169     { "section", 'S', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.attr_section,
 170       NULL,
 171       NULL },
 172 
 173     { NULL }
 174 };
 175 
 176 static int do_query(pcmk__output_t *out, const char *attr_name, const char *attr_node,
 177                     gboolean query_all);
 178 static int do_update(char command, const char *attr_node, const char *attr_name,
 179                      const char *attr_value, const char *attr_section,
 180                      const char *attr_set, const char *attr_dampen, int attr_options);
 181 
 182 static GOptionContext *
 183 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
     /* [previous][next][first][last][top][bottom][index][help] */
 184     GOptionContext *context = NULL;
 185 
 186     context = pcmk__build_arg_context(args, "text (default), xml", group, NULL);
 187 
 188     pcmk__add_arg_group(context, "required", "Required Arguments:",
 189                         "Show required arguments", required_entries);
 190     pcmk__add_arg_group(context, "command", "Command:",
 191                         "Show command options (mutually exclusive)", command_entries);
 192     pcmk__add_arg_group(context, "additional", "Additional Options:",
 193                         "Show additional options", addl_entries);
 194     pcmk__add_arg_group(context, "deprecated", "Deprecated Options:",
 195                         "Show deprecated options", deprecated_entries);
 196 
 197     return context;
 198 }
 199 
 200 int
 201 main(int argc, char **argv)
     /* [previous][next][first][last][top][bottom][index][help] */
 202 {
 203     int rc = pcmk_rc_ok;
 204     crm_exit_t exit_code = CRM_EX_OK;
 205 
 206     pcmk__output_t *out = NULL;
 207 
 208     GOptionGroup *output_group = NULL;
 209     pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
 210     GOptionContext *context = build_arg_context(args, &output_group);
 211     gchar **processed_args = pcmk__cmdline_preproc(argv, "dlnsvBNUS");
 212 
 213     pcmk__register_formats(output_group, formats);
 214     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
 215         exit_code = CRM_EX_USAGE;
 216         goto done;
 217     }
 218 
 219     pcmk__cli_init_logging("attrd_updater", args->verbosity);
 220 
 221     rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
 222     if (rc != pcmk_rc_ok) {
 223         exit_code = CRM_EX_ERROR;
 224         g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Error creating output format %s: %s",
 225                     args->output_ty, pcmk_rc_str(rc));
 226         goto done;
 227     }
 228 
 229     if (args->version) {
 230         out->version(out, false);
 231         goto done;
 232     }
 233 
 234     if (options.command != 'R' && options.attr_name == NULL) {
 235         exit_code = CRM_EX_USAGE;
 236         g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Command requires --name argument");
 237         goto done;
 238     }
 239 
 240     pcmk__register_lib_messages(out);
 241 
 242     if (options.command == 'Q') {
 243         int rc = do_query(out, options.attr_name, options.attr_node, options.query_all);
 244         exit_code = pcmk_rc2exitc(rc);
 245     } else {
 246         /* @TODO We don't know whether the specified node is a Pacemaker Remote
 247          * node or not, so we can't set pcmk__node_attr_remote when appropriate.
 248          * However, it's not a big problem, because pacemaker-attrd will learn
 249          * and remember a node's "remoteness".
 250          */
 251         const char *target = pcmk__node_attr_target(options.attr_node);
 252 
 253         exit_code = pcmk_rc2exitc(do_update(options.command,
 254                                             target == NULL ? options.attr_node : target,
 255                                             options.attr_name, options.attr_value,
 256                                             options.attr_section, options.attr_set,
 257                                             options.attr_dampen, options.attr_options));
 258     }
 259 
 260 done:
 261     g_strfreev(processed_args);
 262     pcmk__free_arg_context(context);
 263     g_free(options.attr_dampen);
 264     g_free(options.attr_name);
 265     g_free(options.attr_node);
 266     g_free(options.attr_section);
 267     g_free(options.attr_set);
 268     free(options.attr_value);
 269 
 270     pcmk__output_and_clear_error(error, out);
 271 
 272     if (out != NULL) {
 273         out->finish(out, exit_code, true, NULL);
 274         pcmk__output_free(out);
 275     }
 276 
 277     crm_exit(exit_code);
 278 }
 279 
 280 /*!
 281  * \internal
 282  * \brief Submit a query request to pacemaker-attrd and wait for reply
 283  *
 284  * \param[in] name    Name of attribute to query
 285  * \param[in] host    Query applies to this host only (or all hosts if NULL)
 286  * \param[out] reply  On success, will be set to new XML tree with reply
 287  *
 288  * \return Standard Pacemaker return code
 289  * \note On success, caller is responsible for freeing result via free_xml(*reply)
 290  */
 291 static int
 292 send_attrd_query(const char *name, const char *host, xmlNode **reply)
     /* [previous][next][first][last][top][bottom][index][help] */
 293 {
 294     int rc = pcmk_rc_ok;
 295     crm_ipc_t *ipc;
 296     xmlNode *query;
 297 
 298     /* Build the query XML */
 299     query = create_xml_node(NULL, __func__);
 300     if (query == NULL) {
 301         return ENOMEM;
 302     }
 303     crm_xml_add(query, F_TYPE, T_ATTRD);
 304     crm_xml_add(query, F_ORIG, crm_system_name);
 305     crm_xml_add(query, PCMK__XA_ATTR_NODE_NAME, host);
 306     crm_xml_add(query, PCMK__XA_TASK, PCMK__ATTRD_CMD_QUERY);
 307     crm_xml_add(query, PCMK__XA_ATTR_NAME, name);
 308 
 309     /* Connect to pacemaker-attrd, send query XML and get reply */
 310     crm_debug("Sending query for value of %s on %s", name, (host? host : "all nodes"));
 311     ipc = crm_ipc_new(T_ATTRD, 0);
 312     if (!crm_ipc_connect(ipc)) {
 313         crm_perror(LOG_ERR, "Connection to cluster attribute manager failed");
 314         rc = ENOTCONN;
 315     } else {
 316         rc = crm_ipc_send(ipc, query, crm_ipc_client_response, 0, reply);
 317         if (rc > 0) {
 318             rc = pcmk_rc_ok;
 319         }
 320         crm_ipc_close(ipc);
 321     }
 322     crm_ipc_destroy(ipc);
 323 
 324     free_xml(query);
 325     return(rc);
 326 }
 327 
 328 /*!
 329  * \brief Validate pacemaker-attrd's XML reply to an query
 330  *
 331  * param[in] reply      Root of reply XML tree to validate
 332  * param[in] attr_name  Name of attribute that was queried
 333  *
 334  * \return Standard Pacemaker return code
 335  * \note A return value of ENXIO means the requested attribute does not exist
 336  */
 337 static int
 338 validate_attrd_reply(xmlNode *reply, const char *attr_name)
     /* [previous][next][first][last][top][bottom][index][help] */
 339 {
 340     int rc = pcmk_rc_ok;
 341     const char *reply_attr;
 342 
 343     if (reply == NULL) {
 344         rc = pcmk_rc_schema_validation;
 345         g_set_error(&error, PCMK__RC_ERROR, rc,
 346                     "Could not query value of %s: reply did not contain valid XML",
 347                     attr_name);
 348         return rc;
 349     }
 350     crm_log_xml_trace(reply, "Reply");
 351 
 352     reply_attr = crm_element_value(reply, PCMK__XA_ATTR_NAME);
 353     if (reply_attr == NULL) {
 354         rc = ENXIO;
 355         g_set_error(&error, PCMK__RC_ERROR, rc,
 356                     "Could not query value of %s: attribute does not exist",
 357                     attr_name);
 358         return rc;
 359     }
 360 
 361     if (!pcmk__str_eq(crm_element_value(reply, F_TYPE), T_ATTRD, pcmk__str_casei)
 362         || (crm_element_value(reply, PCMK__XA_ATTR_VERSION) == NULL)
 363         || strcmp(reply_attr, attr_name)) {
 364             rc = pcmk_rc_schema_validation;
 365             g_set_error(&error, PCMK__RC_ERROR, rc,
 366                         "Could not query value of %s: reply did not contain expected identification",
 367                         attr_name);
 368             return rc;
 369     }
 370 
 371     return pcmk_rc_ok;
 372 }
 373 
 374 /*!
 375  * \brief Print the attribute values in a pacemaker-attrd XML query reply
 376  *
 377  * \param[in] reply     Root of XML tree with query reply
 378  * \param[in] attr_name Name of attribute that was queried
 379  *
 380  * \return true if any values were printed
 381  */
 382 static bool
 383 print_attrd_values(pcmk__output_t *out, xmlNode *reply, const char *attr_name)
     /* [previous][next][first][last][top][bottom][index][help] */
 384 {
 385     xmlNode *child;
 386     const char *reply_host, *reply_value;
 387     bool have_values = false;
 388 
 389     /* Iterate through reply's XML tags (a node tag for each host-value pair) */
 390     for (child = pcmk__xml_first_child(reply); child != NULL;
 391          child = pcmk__xml_next(child)) {
 392 
 393         if (!pcmk__str_eq((const char *)child->name, XML_CIB_TAG_NODE,
 394                           pcmk__str_casei)) {
 395             crm_warn("Ignoring unexpected %s tag in query reply", child->name);
 396         } else {
 397             reply_host = crm_element_value(child, PCMK__XA_ATTR_NODE_NAME);
 398             reply_value = crm_element_value(child, PCMK__XA_ATTR_VALUE);
 399 
 400             if (reply_host == NULL) {
 401                 crm_warn("Ignoring %s tag without %s attribute in query reply",
 402                          XML_CIB_TAG_NODE, PCMK__XA_ATTR_NODE_NAME);
 403             } else {
 404                 out->message(out, "attribute", NULL, NULL, attr_name, reply_value, reply_host);
 405                 have_values = true;
 406             }
 407         }
 408     }
 409 
 410     return have_values;
 411 }
 412 
 413 /*!
 414  * \brief Submit a query to pacemaker-attrd and print reply
 415  *
 416  * \param[in] attr_name  Name of attribute to be affected by request
 417  * \param[in] attr_node  Name of host to query for (or NULL for localhost)
 418  * \param[in] query_all  If TRUE, ignore attr_node and query all nodes instead
 419  *
 420  * \return Standard Pacemaker return code
 421  */
 422 static int
 423 do_query(pcmk__output_t *out, const char *attr_name, const char *attr_node, gboolean query_all)
     /* [previous][next][first][last][top][bottom][index][help] */
 424 {
 425     xmlNode *reply = NULL;
 426     int rc = pcmk_rc_ok;
 427 
 428     /* Decide which node(s) to query */
 429     if (query_all == TRUE) {
 430         attr_node = NULL;
 431     } else {
 432         const char *target = pcmk__node_attr_target(attr_node);
 433         if (target != NULL) {
 434             attr_node = target;
 435         }
 436     }
 437 
 438     /* Build and send pacemaker-attrd request, and get XML reply */
 439     rc = send_attrd_query(attr_name, attr_node, &reply);
 440     if (rc != pcmk_rc_ok) {
 441         g_set_error(&error, PCMK__RC_ERROR, rc, "Could not query value of %s: %s (%d)",
 442                     attr_name, pcmk_strerror(rc), rc);
 443         return rc;
 444     }
 445 
 446     /* Validate the XML reply */
 447     rc = validate_attrd_reply(reply, attr_name);
 448     if (rc != pcmk_rc_ok) {
 449         if (reply != NULL) {
 450             free_xml(reply);
 451         }
 452         return rc;
 453     }
 454 
 455     /* Print the values from the reply */
 456     if (!print_attrd_values(out, reply, attr_name)) {
 457         g_set_error(&error, PCMK__RC_ERROR, rc,
 458                     "Could not query value of %s: reply had attribute name but no host values",
 459                     attr_name);
 460         free_xml(reply);
 461         return pcmk_rc_schema_validation;
 462     }
 463 
 464     return pcmk_rc_ok;
 465 }
 466 
 467 static int
 468 do_update(char command, const char *attr_node, const char *attr_name,
     /* [previous][next][first][last][top][bottom][index][help] */
 469           const char *attr_value, const char *attr_section,
 470           const char *attr_set, const char *attr_dampen, int attr_options)
 471 {
 472     int rc = pcmk__node_attr_request(NULL, command, attr_node, attr_name,
 473                                      attr_value, attr_section, attr_set,
 474                                      attr_dampen, NULL, attr_options);
 475     if (rc != pcmk_rc_ok) {
 476         g_set_error(&error, PCMK__RC_ERROR, rc, "Could not update %s=%s: %s (%d)",
 477                     attr_name, attr_value, pcmk_rc_str(rc), rc);
 478     }
 479     return rc;
 480 }

/* [previous][next][first][last][top][bottom][index][help] */