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

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