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. section_cb
  4. attr_set_type_cb
  5. wait_cb
  6. pattern_used_correctly
  7. build_arg_context
  8. main
  9. print_attrd_values
  10. attrd_event_cb
  11. send_attrd_query
  12. send_attrd_update

   1 /*
   2  * Copyright 2004-2023 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/ipc_attrd_internal.h>
  23 #include <crm/common/cmdline_internal.h>
  24 #include <crm/common/output_internal.h>
  25 #include <crm/common/xml_internal.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 bool printed_values = false;
  42 
  43 struct {
  44     char command;
  45     gchar *attr_dampen;
  46     gchar *attr_name;
  47     gchar *attr_pattern;
  48     gchar *attr_node;
  49     gchar *attr_set;
  50     char *attr_value;
  51     uint32_t attr_options;
  52     gboolean query_all;
  53     gboolean quiet;
  54 } options = {
  55     .attr_options = pcmk__node_attr_none,
  56     .command = 'Q',
  57 };
  58 
  59 static gboolean
  60 command_cb (const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
  61     pcmk__str_update(&options.attr_value, optarg);
  62 
  63     if (pcmk__str_any_of(option_name, "--update-both", "-B", NULL)) {
  64         options.command = 'B';
  65     } else if (pcmk__str_any_of(option_name, "--delete", "-D", NULL)) {
  66         options.command = 'D';
  67     } else if (pcmk__str_any_of(option_name, "--query", "-Q", NULL)) {
  68         options.command = 'Q';
  69     } else if (pcmk__str_any_of(option_name, "--refresh", "-R", NULL)) {
  70         options.command = 'R';
  71     } else if (pcmk__str_any_of(option_name, "--update", "-U", "-v", NULL)) {
  72         options.command = 'U';
  73     } else if (pcmk__str_any_of(option_name, "--update-delay", "-Y", NULL)) {
  74         options.command = 'Y';
  75     }
  76 
  77     return TRUE;
  78 }
  79 
  80 static gboolean
  81 private_cb (const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
  82     pcmk__set_node_attr_flags(options.attr_options, pcmk__node_attr_private);
  83     return TRUE;
  84 }
  85 
  86 static gboolean
  87 section_cb (const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
  88     if (pcmk__str_any_of(optarg, "nodes", "forever", NULL)) {
  89         pcmk__set_node_attr_flags(options.attr_options, pcmk__node_attr_perm);
  90     } else if (pcmk__str_any_of(optarg, "status", "reboot", NULL)) {
  91         pcmk__clear_node_attr_flags(options.attr_options, pcmk__node_attr_perm);
  92     } else {
  93         g_set_error(err, PCMK__EXITC_ERROR, CRM_EX_USAGE, "Unknown value for --lifetime: %s",
  94                     optarg);
  95         return FALSE;
  96     }
  97 
  98     return TRUE;
  99 }
 100 
 101 static gboolean
 102 attr_set_type_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     /* [previous][next][first][last][top][bottom][index][help] */
 103     if (pcmk__str_any_of(option_name, "-z", "--utilization", NULL)) {
 104         pcmk__set_node_attr_flags(options.attr_options, pcmk__node_attr_utilization);
 105     }
 106 
 107     return TRUE;
 108 }
 109 
 110 static gboolean
 111 wait_cb (const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
 112     if (pcmk__str_eq(optarg, "no", pcmk__str_none)) {
 113         pcmk__clear_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local | pcmk__node_attr_sync_cluster);
 114         return TRUE;
 115     } else if (pcmk__str_eq(optarg, PCMK__VALUE_LOCAL, pcmk__str_none)) {
 116         pcmk__clear_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local | pcmk__node_attr_sync_cluster);
 117         pcmk__set_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local);
 118         return TRUE;
 119     } else if (pcmk__str_eq(optarg, PCMK__VALUE_CLUSTER, pcmk__str_none)) {
 120         pcmk__clear_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local | pcmk__node_attr_sync_cluster);
 121         pcmk__set_node_attr_flags(options.attr_options, pcmk__node_attr_sync_cluster);
 122         return TRUE;
 123     } else {
 124         g_set_error(err, PCMK__EXITC_ERROR, CRM_EX_USAGE,
 125                     "--wait= must be one of 'no', 'local', 'cluster'");
 126         return FALSE;
 127     }
 128 }
 129 
 130 #define INDENT "                              "
 131 
 132 static GOptionEntry required_entries[] = {
 133     { "name", 'n', 0, G_OPTION_ARG_STRING, &options.attr_name,
 134       "The attribute's name",
 135       "NAME" },
 136 
 137     { "pattern", 'P', 0, G_OPTION_ARG_STRING, &options.attr_pattern,
 138       "Operate on all attributes matching this pattern\n"
 139       INDENT "(with -B, -D, -U, or -Y)",
 140       "PATTERN"
 141     },
 142 
 143     { NULL }
 144 };
 145 
 146 static GOptionEntry command_entries[] = {
 147     { "update", 'U', 0, G_OPTION_ARG_CALLBACK, command_cb,
 148       "Update attribute's value in pacemaker-attrd. If this causes the value\n"
 149       INDENT "to change, it will also be updated in the cluster configuration.",
 150       "VALUE" },
 151 
 152     { "update-both", 'B', 0, G_OPTION_ARG_CALLBACK, command_cb,
 153       "Update attribute's value and time to wait (dampening) in\n"
 154       INDENT "pacemaker-attrd. If this causes the value or dampening to change,\n"
 155       INDENT "the attribute will also be written to the cluster configuration,\n"
 156       INDENT "so be aware that repeatedly changing the dampening reduces its\n"
 157       INDENT "effectiveness.\n"
 158       INDENT "Requires -d/--delay",
 159       "VALUE" },
 160 
 161     { "update-delay", 'Y', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 162       "Update attribute's dampening in pacemaker-attrd. If this causes\n"
 163       INDENT "the dampening to change, the attribute will also be written\n"
 164       INDENT "to the cluster configuration, so be aware that repeatedly\n"
 165       INDENT "changing the dampening reduces its effectiveness.\n"
 166       INDENT "Requires -d/--delay",
 167       NULL },
 168 
 169     { "query", 'Q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 170       "Query the attribute's value from pacemaker-attrd",
 171       NULL },
 172 
 173     { "delete", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 174       "Unset attribute from pacemaker-attrd. At the moment, there is no way\n"
 175       INDENT "to remove an attribute. This option will instead set its value\n"
 176       INDENT "to the empty string.",
 177       NULL },
 178 
 179     { "refresh", 'R', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 180       "(Advanced) Force the pacemaker-attrd daemon to resend all current\n"
 181       INDENT "values to the CIB",
 182       NULL },
 183 
 184     { NULL }
 185 };
 186 
 187 static GOptionEntry addl_entries[] = {
 188     { "delay", 'd', 0, G_OPTION_ARG_STRING, &options.attr_dampen,
 189       "The time to wait (dampening) in seconds for further changes\n"
 190       INDENT "before sending to the CIB",
 191       "SECONDS" },
 192 
 193     { "set", 's', 0, G_OPTION_ARG_STRING, &options.attr_set,
 194       "(Advanced) The attribute set in which to place the value",
 195       "SET" },
 196 
 197     { "node", 'N', 0, G_OPTION_ARG_STRING, &options.attr_node,
 198       "Set the attribute for the named node (instead of the local one)",
 199       "NODE" },
 200 
 201     { "all", 'A', 0, G_OPTION_ARG_NONE, &options.query_all,
 202       "Show values of the attribute for all nodes (query only)",
 203       NULL },
 204 
 205     { "lifetime", 'l', 0, G_OPTION_ARG_CALLBACK, section_cb,
 206       "(Not yet implemented) Lifetime of the node attribute (silently\n"
 207       INDENT "ignored by cluster)",
 208       "SECTION" },
 209 
 210     { "private", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, private_cb,
 211       "If this creates a new attribute, never write the attribute to CIB",
 212       NULL },
 213 
 214     { "wait", 'W', 0, G_OPTION_ARG_CALLBACK, wait_cb,
 215       "Wait for some event to occur before returning.  Values are 'no' (wait\n"
 216       INDENT "only for the attribute daemon to acknowledge the request),\n"
 217       INDENT "'local' (wait until the change has propagated to where a local\n"
 218       INDENT "query will return the request value, or the value set by a\n"
 219       INDENT "later request), or 'cluster' (wait until the change has propagated\n"
 220       INDENT "to where a query anywhere on the cluster will return the requested\n"
 221       INDENT "value, or the value set by a later request).  Default is 'no'.",
 222       "UNTIL" },
 223 
 224     { "utilization", 'z', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, attr_set_type_cb,
 225       "When creating a new attribute, create it as a node utilization attribute\n"
 226       INDENT "instead of an instance attribute.  If the attribute already exists,\n"
 227       INDENT "its existing type (utilization vs. instance) will be used regardless.\n"
 228       INDENT "(with -B, -U, -Y)",
 229       NULL },
 230 
 231     { NULL }
 232 };
 233 
 234 static GOptionEntry deprecated_entries[] = {
 235     { "quiet", 'q', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.quiet,
 236       NULL,
 237       NULL },
 238 
 239     { "update", 'v', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, command_cb,
 240       NULL,
 241       NULL },
 242 
 243     { "section", 'S', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, section_cb,
 244       NULL,
 245       NULL },
 246 
 247     { NULL }
 248 };
 249 
 250 static int send_attrd_query(pcmk__output_t *out, const char *attr_name, const char *attr_node,
 251                     gboolean query_all);
 252 static int send_attrd_update(char command, const char *attr_node, const char *attr_name,
 253                              const char *attr_value, const char *attr_set,
 254                              const char *attr_dampen, uint32_t attr_options);
 255 
 256 static bool
 257 pattern_used_correctly(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 258 {
 259     /* --pattern can only be used with:
 260      * -B (update-both), -D (delete), -U (update), or -Y (update-delay)
 261      */
 262     return options.command == 'B' || options.command == 'D' || options.command == 'U' || options.command == 'Y';
 263 }
 264 
 265 static GOptionContext *
 266 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
     /* [previous][next][first][last][top][bottom][index][help] */
 267     GOptionContext *context = NULL;
 268 
 269     context = pcmk__build_arg_context(args, "text (default), xml", group, NULL);
 270 
 271     pcmk__add_arg_group(context, "required", "Required Arguments:",
 272                         "Show required arguments", required_entries);
 273     pcmk__add_arg_group(context, "command", "Command:",
 274                         "Show command options (mutually exclusive)", command_entries);
 275     pcmk__add_arg_group(context, "additional", "Additional Options:",
 276                         "Show additional options", addl_entries);
 277     pcmk__add_arg_group(context, "deprecated", "Deprecated Options:",
 278                         "Show deprecated options", deprecated_entries);
 279 
 280     return context;
 281 }
 282 
 283 int
 284 main(int argc, char **argv)
     /* [previous][next][first][last][top][bottom][index][help] */
 285 {
 286     int rc = pcmk_rc_ok;
 287     crm_exit_t exit_code = CRM_EX_OK;
 288 
 289     pcmk__output_t *out = NULL;
 290 
 291     GOptionGroup *output_group = NULL;
 292     pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
 293     GOptionContext *context = build_arg_context(args, &output_group);
 294     gchar **processed_args = pcmk__cmdline_preproc(argv, "dlnsvBNUS");
 295 
 296     pcmk__register_formats(output_group, formats);
 297     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
 298         exit_code = CRM_EX_USAGE;
 299         goto done;
 300     }
 301 
 302     pcmk__cli_init_logging("attrd_updater", args->verbosity);
 303 
 304     rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
 305     if (rc != pcmk_rc_ok) {
 306         exit_code = CRM_EX_ERROR;
 307         g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Error creating output format %s: %s",
 308                     args->output_ty, pcmk_rc_str(rc));
 309         goto done;
 310     }
 311 
 312     if (args->version) {
 313         out->version(out, false);
 314         goto done;
 315     }
 316 
 317     if (options.attr_pattern) {
 318         if (options.attr_name) {
 319             exit_code = CRM_EX_USAGE;
 320             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 321                         "Error: --name and --pattern cannot be used at the same time");
 322             goto done;
 323         }
 324 
 325         if (!pattern_used_correctly()) {
 326             exit_code = CRM_EX_USAGE;
 327             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 328                         "Error: pattern can only be used with delete or update");
 329             goto done;
 330         }
 331 
 332         g_free(options.attr_name);
 333         options.attr_name = options.attr_pattern;
 334         options.attr_options |= pcmk__node_attr_pattern;
 335     }
 336 
 337     if (options.command != 'R' && options.attr_name == NULL) {
 338         exit_code = CRM_EX_USAGE;
 339         g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Command requires --name or --pattern argument");
 340         goto done;
 341     } else if ((options.command == 'B'|| options.command == 'Y') && options.attr_dampen == NULL) {
 342         out->info(out, "Warning: '%c' command given without required --delay", options.command);
 343     }
 344 
 345     pcmk__register_lib_messages(out);
 346 
 347     if (options.command == 'Q') {
 348         int rc = send_attrd_query(out, options.attr_name, options.attr_node, options.query_all);
 349         exit_code = pcmk_rc2exitc(rc);
 350     } else {
 351         /* @TODO We don't know whether the specified node is a Pacemaker Remote
 352          * node or not, so we can't set pcmk__node_attr_remote when appropriate.
 353          * However, it's not a big problem, because pacemaker-attrd will learn
 354          * and remember a node's "remoteness".
 355          */
 356         int rc = send_attrd_update(options.command, options.attr_node,
 357                                    options.attr_name, options.attr_value,
 358                                    options.attr_set, options.attr_dampen,
 359                                    options.attr_options);
 360         exit_code = pcmk_rc2exitc(rc);
 361     }
 362 
 363 done:
 364     g_strfreev(processed_args);
 365     pcmk__free_arg_context(context);
 366     g_free(options.attr_dampen);
 367     g_free(options.attr_name);
 368     g_free(options.attr_node);
 369     g_free(options.attr_set);
 370     free(options.attr_value);
 371 
 372     pcmk__output_and_clear_error(&error, out);
 373 
 374     if (out != NULL) {
 375         out->finish(out, exit_code, true, NULL);
 376         pcmk__output_free(out);
 377     }
 378 
 379     pcmk__unregister_formats();
 380     crm_exit(exit_code);
 381 }
 382 
 383 /*!
 384  * \brief Print the attribute values in a pacemaker-attrd XML query reply
 385  *
 386  * \param[in,out] out    Output object
 387  * \param[in]     reply  List of attribute name/value pairs
 388  *
 389  * \return true if any values were printed
 390  */
 391 static void
 392 print_attrd_values(pcmk__output_t *out, const GList *reply)
     /* [previous][next][first][last][top][bottom][index][help] */
 393 {
 394     for (const GList *iter = reply; iter != NULL; iter = iter->next) {
 395         const pcmk__attrd_query_pair_t *pair = iter->data;
 396 
 397         out->message(out, "attribute", NULL, NULL, pair->name, pair->value,
 398                      pair->node);
 399         printed_values = true;
 400     }
 401 }
 402 
 403 static void
 404 attrd_event_cb(pcmk_ipc_api_t *attrd_api, enum pcmk_ipc_event event_type,
     /* [previous][next][first][last][top][bottom][index][help] */
 405                crm_exit_t status, void *event_data, void *user_data)
 406 {
 407     pcmk__output_t *out = (pcmk__output_t *) user_data;
 408     pcmk__attrd_api_reply_t *reply = event_data;
 409 
 410     if (event_type != pcmk_ipc_event_reply || status != CRM_EX_OK) {
 411         return;
 412     }
 413 
 414     /* Print the values from the reply. */
 415     if (reply->reply_type == pcmk__attrd_reply_query) {
 416         print_attrd_values(out, reply->data.pairs);
 417     }
 418 }
 419 
 420 /*!
 421  * \brief Submit a query to pacemaker-attrd and print reply
 422  *
 423  * \param[in,out] out  Output object
 424  * \param[in]     attr_name  Name of attribute to be affected by request
 425  * \param[in]     attr_node  Name of host to query for (or NULL for localhost)
 426  * \param[in]     query_all  If TRUE, ignore attr_node and query all nodes
 427  *
 428  * \return Standard Pacemaker return code
 429  */
 430 static int
 431 send_attrd_query(pcmk__output_t *out, const char *attr_name,
     /* [previous][next][first][last][top][bottom][index][help] */
 432                  const char *attr_node, gboolean query_all)
 433 {
 434     uint32_t options = pcmk__node_attr_none;
 435     pcmk_ipc_api_t *attrd_api = NULL;
 436     int rc = pcmk_rc_ok;
 437 
 438     // Create attrd IPC object
 439     rc = pcmk_new_ipc_api(&attrd_api, pcmk_ipc_attrd);
 440     if (rc != pcmk_rc_ok) {
 441         g_set_error(&error, PCMK__RC_ERROR, rc,
 442                     "Could not connect to attrd: %s", pcmk_rc_str(rc));
 443         return ENOTCONN;
 444     }
 445 
 446     pcmk_register_ipc_callback(attrd_api, attrd_event_cb, out);
 447 
 448     // Connect to attrd (without main loop)
 449     rc = pcmk_connect_ipc(attrd_api, pcmk_ipc_dispatch_sync);
 450     if (rc != pcmk_rc_ok) {
 451         g_set_error(&error, PCMK__RC_ERROR, rc,
 452                     "Could not connect to attrd: %s", pcmk_rc_str(rc));
 453         pcmk_free_ipc_api(attrd_api);
 454         return rc;
 455     }
 456 
 457     /* Decide which node(s) to query */
 458     if (query_all == TRUE) {
 459         options |= pcmk__node_attr_query_all;
 460     }
 461 
 462     rc = pcmk__attrd_api_query(attrd_api, attr_node, attr_name, options);
 463 
 464     if (rc != pcmk_rc_ok) {
 465         g_set_error(&error, PCMK__RC_ERROR, rc, "Could not query value of %s: %s (%d)",
 466                     attr_name, pcmk_strerror(rc), rc);
 467     } else if (!printed_values) {
 468         rc = pcmk_rc_schema_validation;
 469         g_set_error(&error, PCMK__RC_ERROR, rc,
 470                     "Could not query value of %s: attribute does not exist", attr_name);
 471     }
 472 
 473     pcmk_disconnect_ipc(attrd_api);
 474     pcmk_free_ipc_api(attrd_api);
 475 
 476     return rc;
 477 }
 478 
 479 static int
 480 send_attrd_update(char command, const char *attr_node, const char *attr_name,
     /* [previous][next][first][last][top][bottom][index][help] */
 481                   const char *attr_value, const char *attr_set,
 482                   const char *attr_dampen, uint32_t attr_options)
 483 {
 484     int rc = pcmk_rc_ok;
 485 
 486     switch (command) {
 487         case 'B':
 488             rc = pcmk__attrd_api_update(NULL, attr_node, attr_name, attr_value,
 489                                         attr_dampen, attr_set, NULL,
 490                                         attr_options | pcmk__node_attr_value | pcmk__node_attr_delay);
 491             break;
 492 
 493         case 'D':
 494             rc = pcmk__attrd_api_delete(NULL, attr_node, attr_name, attr_options);
 495             break;
 496 
 497         case 'R':
 498             rc = pcmk__attrd_api_refresh(NULL, attr_node);
 499             break;
 500 
 501         case 'U':
 502             rc = pcmk__attrd_api_update(NULL, attr_node, attr_name, attr_value,
 503                                         NULL, attr_set, NULL,
 504                                         attr_options | pcmk__node_attr_value);
 505             break;
 506 
 507         case 'Y':
 508             rc = pcmk__attrd_api_update(NULL, attr_node, attr_name, NULL,
 509                                         attr_dampen, attr_set, NULL,
 510                                         attr_options | pcmk__node_attr_delay);
 511             break;
 512     }
 513 
 514     if (rc != pcmk_rc_ok) {
 515         g_set_error(&error, PCMK__RC_ERROR, rc, "Could not update %s=%s: %s (%d)",
 516                     attr_name, attr_value, pcmk_rc_str(rc), rc);
 517     }
 518 
 519     return rc;
 520 }

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