root/tools/crm_attribute.c

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

DEFINITIONS

This source file includes following definitions.
  1. list_cb
  2. delete_cb
  3. attr_name_cb
  4. promotion_cb
  5. update_cb
  6. utilization_cb
  7. value_cb
  8. wait_cb
  9. get_node_name_from_local
  10. send_attrd_update
  11. delete_attr_on_node
  12. command_list
  13. command_delete
  14. update_attr_on_node
  15. command_update
  16. output_one_attribute
  17. command_query
  18. set_type
  19. use_attrd
  20. try_ipc_update
  21. pattern_used_correctly
  22. delete_used_correctly
  23. update_used_correctly
  24. build_arg_context
  25. main

   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 <stdint.h>
  13 #include <stdio.h>
  14 #include <unistd.h>
  15 #include <stdlib.h>
  16 #include <errno.h>
  17 #include <fcntl.h>
  18 #include <libgen.h>
  19 #include <time.h>
  20 
  21 #include <sys/param.h>
  22 #include <sys/types.h>
  23 
  24 #include <crm/crm.h>
  25 #include <crm/common/xml.h>
  26 #include <crm/common/ipc.h>
  27 #include <crm/common/util.h>
  28 #include <crm/cluster.h>
  29 
  30 #include <crm/cib.h>
  31 #include <crm/cib/internal.h>
  32 #include <crm/common/attrs_internal.h>
  33 #include <crm/common/cmdline_internal.h>
  34 #include <crm/common/ipc_attrd_internal.h>
  35 #include <crm/common/ipc_controld.h>
  36 #include <crm/common/output_internal.h>
  37 #include <sys/utsname.h>
  38 
  39 #include <pacemaker-internal.h>
  40 
  41 #define SUMMARY "crm_attribute - query and update Pacemaker cluster options and node attributes"
  42 
  43 enum attr_cmd {
  44     attr_cmd_none,
  45     attr_cmd_delete,
  46     attr_cmd_list,
  47     attr_cmd_query,
  48     attr_cmd_update,
  49 };
  50 
  51 GError *error = NULL;
  52 crm_exit_t exit_code = CRM_EX_OK;
  53 uint64_t cib_opts = cib_sync_call;
  54 
  55 static pcmk__supported_format_t formats[] = {
  56     PCMK__SUPPORTED_FORMAT_NONE,
  57     PCMK__SUPPORTED_FORMAT_TEXT,
  58     PCMK__SUPPORTED_FORMAT_XML,
  59     { NULL, NULL, NULL }
  60 };
  61 
  62 struct {
  63     enum attr_cmd command;
  64     gchar *attr_default;
  65     gchar *attr_id;
  66     gchar *attr_name;
  67     uint32_t attr_options;
  68     gchar *attr_pattern;
  69     char *attr_value;
  70     char *dest_node;
  71     gchar *dest_uname;
  72     gboolean inhibit;
  73     gchar *set_name;
  74     char *set_type;
  75     gchar *type;
  76     char *opt_list;
  77     gboolean all;
  78     bool promotion_score;
  79     gboolean score_update;
  80 } options = {
  81     .command = attr_cmd_query,
  82 };
  83 
  84 #define INDENT "                               "
  85 
  86 static gboolean
  87 list_cb(const gchar *option_name, const gchar *optarg, gpointer data,
     /* [previous][next][first][last][top][bottom][index][help] */
  88         GError **error) {
  89     options.command = attr_cmd_list;
  90     pcmk__str_update(&options.opt_list, optarg);
  91     return TRUE;
  92 }
  93 
  94 static gboolean
  95 delete_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     /* [previous][next][first][last][top][bottom][index][help] */
  96     options.command = attr_cmd_delete;
  97     pcmk__str_update(&options.attr_value, NULL);
  98     return TRUE;
  99 }
 100 
 101 static gboolean
 102 attr_name_cb(const gchar *option_name, const gchar *optarg, gpointer data,
     /* [previous][next][first][last][top][bottom][index][help] */
 103              GError **error)
 104 {
 105     options.promotion_score = false;
 106 
 107     if (options.attr_name != NULL) {
 108         g_free(options.attr_name);
 109     }
 110     options.attr_name = g_strdup(optarg);
 111     return TRUE;
 112 }
 113 
 114 static gboolean
 115 promotion_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     /* [previous][next][first][last][top][bottom][index][help] */
 116     char *score_name = NULL;
 117 
 118     options.promotion_score = true;
 119 
 120     if (options.attr_name) {
 121         g_free(options.attr_name);
 122     }
 123 
 124     score_name = pcmk_promotion_score_name(optarg);
 125     if (score_name != NULL) {
 126         options.attr_name = g_strdup(score_name);
 127         free(score_name);
 128     } else {
 129         options.attr_name = NULL;
 130     }
 131 
 132     return TRUE;
 133 }
 134 
 135 static gboolean
 136 update_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     /* [previous][next][first][last][top][bottom][index][help] */
 137     options.command = attr_cmd_update;
 138     pcmk__str_update(&options.attr_value, optarg);
 139     return TRUE;
 140 }
 141 
 142 static gboolean
 143 utilization_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     /* [previous][next][first][last][top][bottom][index][help] */
 144     if (options.type) {
 145         g_free(options.type);
 146     }
 147 
 148     options.type = g_strdup(PCMK_XE_NODES);
 149     pcmk__str_update(&options.set_type, PCMK_XE_UTILIZATION);
 150     return TRUE;
 151 }
 152 
 153 static gboolean
 154 value_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     /* [previous][next][first][last][top][bottom][index][help] */
 155     options.command = attr_cmd_query;
 156     pcmk__str_update(&options.attr_value, NULL);
 157     return TRUE;
 158 }
 159 
 160 static gboolean
 161 wait_cb (const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
 162     if (pcmk__str_eq(optarg, "no", pcmk__str_none)) {
 163         pcmk__clear_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local | pcmk__node_attr_sync_cluster);
 164         return TRUE;
 165     } else if (pcmk__str_eq(optarg, PCMK__VALUE_LOCAL, pcmk__str_none)) {
 166         pcmk__clear_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local | pcmk__node_attr_sync_cluster);
 167         pcmk__set_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local);
 168         return TRUE;
 169     } else if (pcmk__str_eq(optarg, PCMK__VALUE_CLUSTER, pcmk__str_none)) {
 170         pcmk__clear_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local | pcmk__node_attr_sync_cluster);
 171         pcmk__set_node_attr_flags(options.attr_options, pcmk__node_attr_sync_cluster);
 172         return TRUE;
 173     } else {
 174         g_set_error(err, PCMK__EXITC_ERROR, CRM_EX_USAGE,
 175                     "--wait= must be one of 'no', 'local', 'cluster'");
 176         return FALSE;
 177     }
 178 }
 179 
 180 static GOptionEntry selecting_entries[] = {
 181     { "all", 'a', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.all,
 182       "With -L/--list-options, include advanced and deprecated options in the\n"
 183       INDENT "output. This is always treated as true when --output-as=xml is\n"
 184       INDENT "specified.",
 185       NULL,
 186     },
 187 
 188     { "id", 'i', 0, G_OPTION_ARG_STRING, &options.attr_id,
 189       "(Advanced) Operate on instance of specified attribute with this\n"
 190       INDENT "XML ID",
 191       "XML_ID"
 192     },
 193 
 194     { "name", 'n', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, attr_name_cb,
 195       "Operate on attribute or option with this name.  For queries, this\n"
 196       INDENT "is optional, in which case all matching attributes will be\n"
 197       INDENT "returned.",
 198       "NAME"
 199     },
 200 
 201     { "pattern", 'P', 0, G_OPTION_ARG_STRING, &options.attr_pattern,
 202       "Operate on all attributes matching this pattern\n"
 203       INDENT "(with -v, -D, or -G)",
 204       "PATTERN"
 205     },
 206 
 207     { "promotion", 'p', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, promotion_cb,
 208       "Operate on node attribute used as promotion score for specified\n"
 209       INDENT "resource, or resource given in OCF_RESOURCE_INSTANCE environment\n"
 210       INDENT "variable if none is specified; this also defaults -l/--lifetime\n"
 211       INDENT "to reboot (normally invoked from an OCF resource agent)",
 212       "RESOURCE"
 213     },
 214 
 215     { "set-name", 's', 0, G_OPTION_ARG_STRING, &options.set_name,
 216       "(Advanced) Operate on instance of specified attribute that is\n"
 217       INDENT "within set with this XML ID",
 218       "NAME"
 219     },
 220 
 221     { NULL }
 222 };
 223 
 224 static GOptionEntry command_entries[] = {
 225     { "list-options", 'L', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, list_cb,
 226       "List all available options of the given type.\n"
 227       INDENT "Allowed values: " PCMK__VALUE_CLUSTER,
 228       "TYPE"
 229     },
 230 
 231     { "delete", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, delete_cb,
 232       "Delete the attribute/option (with -n or -P)",
 233       NULL
 234     },
 235 
 236     { "query", 'G', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, value_cb,
 237       "Query the current value of the attribute/option.\n"
 238       INDENT "See also: -n, -P",
 239       NULL
 240     },
 241 
 242     { "update", 'v', 0, G_OPTION_ARG_CALLBACK, update_cb,
 243       "Update the value of the attribute/option (with -n or -P)",
 244       "VALUE"
 245     },
 246 
 247     { NULL }
 248 };
 249 
 250 static GOptionEntry addl_entries[] = {
 251     { "default", 'd', 0, G_OPTION_ARG_STRING, &options.attr_default,
 252       "(Advanced) Default value to display if none is found in configuration",
 253       "VALUE"
 254     },
 255 
 256     { "lifetime", 'l', 0, G_OPTION_ARG_STRING, &options.type,
 257       "Lifetime of the node attribute.\n"
 258       INDENT "Valid values: reboot, forever",
 259       "LIFETIME"
 260     },
 261 
 262     { "node", 'N', 0, G_OPTION_ARG_STRING, &options.dest_uname,
 263       "Set a node attribute for named node (instead of a cluster option).\n"
 264       INDENT "See also: -l",
 265       "NODE"
 266     },
 267 
 268     { "type", 't', 0, G_OPTION_ARG_STRING, &options.type,
 269       "Which part of the configuration to update/delete/query the option in.\n"
 270       INDENT "Valid values: crm_config, rsc_defaults, op_defaults, tickets",
 271       "SECTION"
 272     },
 273 
 274     { "score", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.score_update,
 275       "Treat new attribute values as atomic score updates where possible\n"
 276       INDENT "(with --update/-v, when running against a CIB file or updating\n"
 277       INDENT "an attribute outside the " PCMK_XE_STATUS " section; enabled\n"
 278       INDENT "by default if --promotion/-p is specified)\n\n"
 279 
 280       INDENT "This currently happens by default and cannot be disabled, but\n"
 281       INDENT "this default behavior is deprecated and will be removed in a\n"
 282       INDENT "future release (exception: this will remain the default with\n"
 283       INDENT "--promotion/-p). Set this flag if this behavior is desired.\n\n"
 284 
 285       INDENT "This option takes effect when updating XML attributes. For an\n"
 286       INDENT "attribute named \"name\", if the new value is \"name++\" or\n"
 287       INDENT "\"name+=X\" for some score X, the new value is set as follows:\n"
 288       INDENT " * If attribute \"name\" is not already set to some value in\n"
 289       INDENT "   the element being updated, the new value is set as a literal\n"
 290       INDENT "   string.\n"
 291       INDENT " * If the new value is \"name++\", then the attribute is set to\n"
 292       INDENT "   its existing value (parsed as a score) plus 1.\n"
 293       INDENT " * If the new value is \"name+=X\" for some score X, then the\n"
 294       INDENT "   attribute is set to its existing value plus X, where the\n"
 295       INDENT "   existing value and X are parsed and added as scores.\n\n"
 296 
 297       INDENT "Scores are integer values capped at INFINITY and -INFINITY.\n"
 298       INDENT "Refer to Pacemaker Explained and to the char2score() function\n"
 299       INDENT "for more details on scores, including how they're parsed and\n"
 300       INDENT "added.",
 301       NULL },
 302 
 303     { "wait", 'W', 0, G_OPTION_ARG_CALLBACK, wait_cb,
 304       "Wait for some event to occur before returning.  Values are 'no' (wait\n"
 305       INDENT "only for the attribute daemon to acknowledge the request),\n"
 306       INDENT "'local' (wait until the change has propagated to where a local\n"
 307       INDENT "query will return the request value, or the value set by a\n"
 308       INDENT "later request), or 'cluster' (wait until the change has propagated\n"
 309       INDENT "to where a query anywhere on the cluster will return the requested\n"
 310       INDENT "value, or the value set by a later request).  Default is 'no'.\n"
 311       INDENT "(with -N, and one of -D or -u)",
 312       "UNTIL" },
 313 
 314     { "utilization", 'z', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, utilization_cb,
 315       "Set an utilization attribute for the node.",
 316       NULL
 317     },
 318 
 319     { "inhibit-policy-engine", '!', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.inhibit,
 320       NULL, NULL
 321     },
 322 
 323     { NULL }
 324 };
 325 
 326 static GOptionEntry deprecated_entries[] = {
 327     { "attr-id", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.attr_id,
 328       NULL, NULL
 329     },
 330 
 331     { "attr-name", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, attr_name_cb,
 332       NULL, NULL
 333     },
 334 
 335     { "attr-value", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, update_cb,
 336       NULL, NULL
 337     },
 338 
 339     { "delete-attr", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, delete_cb,
 340       NULL, NULL
 341     },
 342 
 343     { "get-value", 0, G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, value_cb,
 344       NULL, NULL
 345     },
 346 
 347     { "node-uname", 'U', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.dest_uname,
 348       NULL, NULL
 349     },
 350 
 351     { NULL }
 352 };
 353 
 354 static void
 355 get_node_name_from_local(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 356 {
 357     struct utsname hostinfo;
 358 
 359     g_free(options.dest_uname);
 360 
 361     if (uname(&hostinfo) == 0) {
 362         options.dest_uname = g_strdup(hostinfo.nodename);
 363     } else {
 364         options.dest_uname = NULL;
 365     }
 366 }
 367 
 368 static int
 369 send_attrd_update(enum attr_cmd command, const char *attr_node,
     /* [previous][next][first][last][top][bottom][index][help] */
 370                   const char *attr_name, const char *attr_value,
 371                   const char *attr_set, const char *attr_dampen,
 372                   uint32_t attr_options)
 373 {
 374     int rc = pcmk_rc_ok;
 375     uint32_t opts = attr_options;
 376 
 377     switch (command) {
 378         case attr_cmd_delete:
 379             rc = pcmk__attrd_api_delete(NULL, attr_node, attr_name, opts);
 380             break;
 381 
 382         case attr_cmd_update:
 383             rc = pcmk__attrd_api_update(NULL, attr_node, attr_name,
 384                                         attr_value, NULL, attr_set, NULL,
 385                                         opts | pcmk__node_attr_value);
 386             break;
 387 
 388         default:
 389             break;
 390     }
 391 
 392     if (rc != pcmk_rc_ok) {
 393         g_set_error(&error, PCMK__RC_ERROR, rc, "Could not update %s=%s: %s (%d)",
 394                     attr_name, attr_value, pcmk_rc_str(rc), rc);
 395     }
 396 
 397     return rc;
 398 }
 399 
 400 struct delete_data_s {
 401     pcmk__output_t *out;
 402     cib_t *cib;
 403 };
 404 
 405 static int
 406 delete_attr_on_node(xmlNode *child, void *userdata)
     /* [previous][next][first][last][top][bottom][index][help] */
 407 {
 408     struct delete_data_s *dd = (struct delete_data_s *) userdata;
 409 
 410     const char *attr_name = crm_element_value(child, PCMK_XA_NAME);
 411     int rc = pcmk_rc_ok;
 412 
 413     if (!pcmk__str_eq(attr_name, options.attr_pattern, pcmk__str_regex)) {
 414         return pcmk_rc_ok;
 415     }
 416 
 417     rc = cib__delete_node_attr(dd->out, dd->cib, cib_opts, options.type,
 418                                options.dest_node, options.set_type,
 419                                options.set_name, options.attr_id,
 420                                attr_name, options.attr_value, NULL);
 421 
 422     if (rc == ENXIO) {
 423         rc = pcmk_rc_ok;
 424     }
 425 
 426     return rc;
 427 }
 428 
 429 static void
 430 command_list(pcmk__output_t *out)
     /* [previous][next][first][last][top][bottom][index][help] */
 431 {
 432     if (pcmk__str_eq(options.opt_list, PCMK__VALUE_CLUSTER, pcmk__str_none)) {
 433         exit_code = pcmk_rc2exitc(pcmk__list_cluster_options(out, options.all));
 434 
 435     } else {
 436         // @TODO Improve usage messages to reduce duplication
 437         exit_code = CRM_EX_USAGE;
 438         g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
 439                     "Invalid --list-options value '%s'. Allowed values: "
 440                     PCMK__VALUE_CLUSTER,
 441                     pcmk__s(options.opt_list, "(BUG: none)"));
 442     }
 443 }
 444 
 445 static int
 446 command_delete(pcmk__output_t *out, cib_t *cib)
     /* [previous][next][first][last][top][bottom][index][help] */
 447 {
 448     int rc = pcmk_rc_ok;
 449 
 450     xmlNode *result = NULL;
 451     bool use_pattern = options.attr_pattern != NULL;
 452 
 453     /* See the comment in command_query regarding xpath and regular expressions. */
 454     if (use_pattern) {
 455         struct delete_data_s dd = { out, cib };
 456 
 457         rc = cib__get_node_attrs(out, cib, options.type, options.dest_node,
 458                                  options.set_type, options.set_name, NULL, NULL,
 459                                  NULL, &result);
 460 
 461         if (rc != pcmk_rc_ok) {
 462             goto done_deleting;
 463         }
 464 
 465         rc = pcmk__xe_foreach_child(result, NULL, delete_attr_on_node, &dd);
 466 
 467     } else {
 468         rc = cib__delete_node_attr(out, cib, cib_opts, options.type, options.dest_node,
 469                                    options.set_type, options.set_name, options.attr_id,
 470                                    options.attr_name, options.attr_value, NULL);
 471     }
 472 
 473 done_deleting:
 474     free_xml(result);
 475 
 476     if (rc == ENXIO) {
 477         /* Nothing to delete...
 478          * which means it's not there...
 479          * which is what the admin wanted
 480          */
 481         rc = pcmk_rc_ok;
 482     }
 483 
 484     return rc;
 485 }
 486 
 487 struct update_data_s {
 488     pcmk__output_t *out;
 489     cib_t *cib;
 490     int is_remote_node;
 491 };
 492 
 493 static int
 494 update_attr_on_node(xmlNode *child, void *userdata)
     /* [previous][next][first][last][top][bottom][index][help] */
 495 {
 496     struct update_data_s *ud = (struct update_data_s *) userdata;
 497 
 498     const char *attr_name = crm_element_value(child, PCMK_XA_NAME);
 499 
 500     if (!pcmk__str_eq(attr_name, options.attr_pattern, pcmk__str_regex)) {
 501         return pcmk_rc_ok;
 502     }
 503 
 504     return cib__update_node_attr(ud->out, ud->cib, cib_opts, options.type,
 505                                  options.dest_node, options.set_type,
 506                                  options.set_name, options.attr_id,
 507                                  attr_name, options.attr_value, NULL,
 508                                  ud->is_remote_node? PCMK_VALUE_REMOTE : NULL);
 509 }
 510 
 511 static int
 512 command_update(pcmk__output_t *out, cib_t *cib, int is_remote_node)
     /* [previous][next][first][last][top][bottom][index][help] */
 513 {
 514     int rc = pcmk_rc_ok;
 515 
 516     xmlNode *result = NULL;
 517     bool use_pattern = options.attr_pattern != NULL;
 518 
 519     /* @COMPAT When we drop default support for expansion in crm_attribute,
 520      * guard with `if (options.score_update)`
 521      */
 522     cib__set_call_options(cib_opts, crm_system_name, cib_score_update);
 523 
 524     /* See the comment in command_query regarding xpath and regular expressions. */
 525     if (use_pattern) {
 526         struct update_data_s ud = { out, cib, is_remote_node };
 527 
 528         rc = cib__get_node_attrs(out, cib, options.type, options.dest_node,
 529                                  options.set_type, options.set_name, NULL, NULL,
 530                                  NULL, &result);
 531 
 532         if (rc != pcmk_rc_ok) {
 533             goto done_updating;
 534         }
 535 
 536         rc = pcmk__xe_foreach_child(result, NULL, update_attr_on_node, &ud);
 537 
 538     } else {
 539         rc = cib__update_node_attr(out, cib, cib_opts, options.type,
 540                                    options.dest_node, options.set_type,
 541                                    options.set_name, options.attr_id,
 542                                    options.attr_name, options.attr_value, NULL,
 543                                    is_remote_node? PCMK_VALUE_REMOTE : NULL);
 544     }
 545 
 546 done_updating:
 547     free_xml(result);
 548     return rc;
 549 }
 550 
 551 struct output_data_s {
 552     pcmk__output_t *out;
 553     bool use_pattern;
 554     bool did_output;
 555 };
 556 
 557 static int
 558 output_one_attribute(xmlNode *node, void *userdata)
     /* [previous][next][first][last][top][bottom][index][help] */
 559 {
 560     struct output_data_s *od = (struct output_data_s *) userdata;
 561 
 562     const char *name = crm_element_value(node, PCMK_XA_NAME);
 563     const char *value = crm_element_value(node, PCMK_XA_VALUE);
 564 
 565     const char *type = options.type;
 566     const char *attr_id = options.attr_id;
 567 
 568     if (od->use_pattern && !pcmk__str_eq(name, options.attr_pattern, pcmk__str_regex)) {
 569         return pcmk_rc_ok;
 570     }
 571 
 572     od->out->message(od->out, "attribute", type, attr_id, name, value, NULL,
 573                      od->out->quiet, true);
 574     od->did_output = true;
 575     crm_info("Read %s='%s' %s%s",
 576              pcmk__s(name, "<null>"), pcmk__s(value, ""),
 577              options.set_name ? "in " : "", options.set_name ? options.set_name : "");
 578 
 579     return pcmk_rc_ok;
 580 }
 581 
 582 static int
 583 command_query(pcmk__output_t *out, cib_t *cib)
     /* [previous][next][first][last][top][bottom][index][help] */
 584 {
 585     int rc = pcmk_rc_ok;
 586 
 587     xmlNode *result = NULL;
 588     bool use_pattern = options.attr_pattern != NULL;
 589 
 590     /* libxml2 doesn't support regular expressions in xpath queries (which is how
 591      * cib__get_node_attrs -> find_attr finds attributes).  So instead, we'll just
 592      * find all the attributes for a given node here by passing NULL for attr_id
 593      * and attr_name, and then later see if they match the given pattern.
 594      */
 595     if (use_pattern) {
 596         rc = cib__get_node_attrs(out, cib, options.type, options.dest_node,
 597                                  options.set_type, options.set_name, NULL,
 598                                  NULL, NULL, &result);
 599     } else {
 600         rc = cib__get_node_attrs(out, cib, options.type, options.dest_node,
 601                                  options.set_type, options.set_name, options.attr_id,
 602                                  options.attr_name, NULL, &result);
 603     }
 604 
 605     if (rc == ENXIO && options.attr_default) {
 606         /* Make static analysis happy */
 607         const char *type = options.type;
 608         const char *attr_id = options.attr_id;
 609         const char *attr_name = options.attr_name;
 610         const char *attr_default = options.attr_default;
 611 
 612         out->message(out, "attribute", type, attr_id, attr_name, attr_default,
 613                      NULL, out->quiet, true);
 614         rc = pcmk_rc_ok;
 615 
 616     } else if (rc != pcmk_rc_ok) {
 617         // Don't do anything.
 618 
 619     } else if (result->children != NULL) {
 620         struct output_data_s od = { out, use_pattern, false };
 621 
 622         pcmk__xe_foreach_child(result, NULL, output_one_attribute, &od);
 623 
 624         if (!od.did_output) {
 625             rc = ENXIO;
 626         }
 627 
 628     } else {
 629         struct output_data_s od = { out, use_pattern, false };
 630         output_one_attribute(result, &od);
 631     }
 632 
 633     free_xml(result);
 634     return rc;
 635 }
 636 
 637 static void
 638 set_type(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 639 {
 640     if (options.type == NULL) {
 641         if (options.promotion_score) {
 642             // Updating a promotion score node attribute
 643             options.type = g_strdup(PCMK_XE_STATUS);
 644 
 645         } else if (options.dest_uname != NULL) {
 646             // Updating some other node attribute
 647             options.type = g_strdup(PCMK_XE_NODES);
 648 
 649         } else {
 650             // Updating cluster options
 651             options.type = g_strdup(PCMK_XE_CRM_CONFIG);
 652         }
 653 
 654     } else if (pcmk__str_eq(options.type, "reboot", pcmk__str_casei)) {
 655         options.type = g_strdup(PCMK_XE_STATUS);
 656 
 657     } else if (pcmk__str_eq(options.type, "forever", pcmk__str_casei)) {
 658         options.type = g_strdup(PCMK_XE_NODES);
 659     }
 660 }
 661 
 662 static bool
 663 use_attrd(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 664 {
 665     /* Only go through the attribute manager for transient attributes, and
 666      * then only if we're not using a file as the CIB.
 667      */
 668     return pcmk__str_eq(options.type, PCMK_XE_STATUS, pcmk__str_casei) &&
 669            getenv("CIB_file") == NULL && getenv("CIB_shadow") == NULL;
 670 }
 671 
 672 static bool
 673 try_ipc_update(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 674 {
 675     return use_attrd()
 676            && ((options.command == attr_cmd_delete)
 677                || (options.command == attr_cmd_update));
 678 }
 679 
 680 static bool
 681 pattern_used_correctly(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 682 {
 683     /* --pattern can only be used with:
 684      * -G (query), -v (update), or -D (delete)
 685      */
 686     switch (options.command) {
 687         case attr_cmd_delete:
 688         case attr_cmd_query:
 689         case attr_cmd_update:
 690             return true;
 691         default:
 692             return false;
 693     }
 694 }
 695 
 696 static bool
 697 delete_used_correctly(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 698 {
 699     return (options.command != attr_cmd_delete)
 700            || (options.attr_name != NULL)
 701            || (options.attr_pattern != NULL);
 702 }
 703 
 704 static bool
 705 update_used_correctly(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 706 {
 707     return (options.command != attr_cmd_update)
 708            || (options.attr_name != NULL)
 709            || (options.attr_pattern != NULL);
 710 }
 711 
 712 static GOptionContext *
 713 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
     /* [previous][next][first][last][top][bottom][index][help] */
 714     GOptionContext *context = NULL;
 715 
 716     GOptionEntry extra_prog_entries[] = {
 717         { "quiet", 'q', 0, G_OPTION_ARG_NONE, &(args->quiet),
 718           "Print only the value on stdout",
 719           NULL },
 720 
 721         { "quiet", 'Q', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &(args->quiet),
 722           NULL, NULL
 723         },
 724 
 725         { NULL }
 726     };
 727 
 728     const char *description = "Examples:\n\n"
 729                               "Add new node attribute called 'location' with the value of 'office' for host 'myhost':\n\n"
 730                               "\tcrm_attribute --node myhost --name location --update office\n\n"
 731                               "Query the value of the 'location' node attribute for host 'myhost':\n\n"
 732                               "\tcrm_attribute --node myhost --name location --query\n\n"
 733                               "Change the value of the 'location' node attribute for host 'myhost':\n\n"
 734                               "\tcrm_attribute --node myhost --name location --update backoffice\n\n"
 735                               "Delete the 'location' node attribute for host 'myhost':\n\n"
 736                               "\tcrm_attribute --node myhost --name location --delete\n\n"
 737                               "Query the value of the '" PCMK_OPT_CLUSTER_DELAY
 738                                 "' cluster option:\n\n"
 739                               "\tcrm_attribute --type crm_config --name "
 740                                 PCMK_OPT_CLUSTER_DELAY " --query\n\n"
 741                               "Query value of the '" PCMK_OPT_CLUSTER_DELAY
 742                                 "' cluster option and print only the value:\n\n"
 743                               "\tcrm_attribute --type crm_config --name "
 744                                 PCMK_OPT_CLUSTER_DELAY " --query --quiet\n\n";
 745 
 746     context = pcmk__build_arg_context(args, "text (default), xml", group, NULL);
 747     pcmk__add_main_args(context, extra_prog_entries);
 748     g_option_context_set_description(context, description);
 749 
 750     pcmk__add_arg_group(context, "selections", "Selecting attributes:",
 751                         "Show selecting options", selecting_entries);
 752     pcmk__add_arg_group(context, "command", "Commands:",
 753                         "Show command options", command_entries);
 754     pcmk__add_arg_group(context, "additional", "Additional options:",
 755                         "Show additional options", addl_entries);
 756     pcmk__add_arg_group(context, "deprecated", "Deprecated Options:",
 757                         "Show deprecated options", deprecated_entries);
 758 
 759     return context;
 760 }
 761 
 762 int
 763 main(int argc, char **argv)
     /* [previous][next][first][last][top][bottom][index][help] */
 764 {
 765     cib_t *the_cib = NULL;
 766     int is_remote_node = 0;
 767 
 768     int rc = pcmk_rc_ok;
 769 
 770     pcmk__output_t *out = NULL;
 771 
 772     GOptionGroup *output_group = NULL;
 773     pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
 774     gchar **processed_args = pcmk__cmdline_preproc(argv, "NPUdilnpstv");
 775     GOptionContext *context = build_arg_context(args, &output_group);
 776 
 777     pcmk__register_formats(output_group, formats);
 778     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
 779         exit_code = CRM_EX_USAGE;
 780         goto done;
 781     }
 782 
 783     pcmk__cli_init_logging("crm_attribute", args->verbosity);
 784 
 785     rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
 786     if (rc != pcmk_rc_ok) {
 787         exit_code = CRM_EX_ERROR;
 788         g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Error creating output format %s: %s",
 789                     args->output_ty, pcmk_rc_str(rc));
 790         goto done;
 791     }
 792 
 793     pcmk__register_lib_messages(out);
 794 
 795     if (args->version) {
 796         out->version(out, false);
 797         goto done;
 798     }
 799 
 800     out->quiet = args->quiet;
 801 
 802     if (options.command == attr_cmd_list) {
 803         command_list(out);
 804         goto done;
 805     }
 806 
 807     if (options.promotion_score && options.attr_name == NULL) {
 808         exit_code = CRM_EX_USAGE;
 809         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 810                     "-p/--promotion must be called from an OCF resource agent "
 811                     "or with a resource ID specified");
 812         goto done;
 813     }
 814 
 815     if (options.inhibit) {
 816         crm_warn("Inhibiting notifications for this update");
 817         cib__set_call_options(cib_opts, crm_system_name, cib_inhibit_notify);
 818     }
 819 
 820     the_cib = cib_new();
 821     rc = cib__signon_attempts(the_cib, crm_system_name, cib_command, 5);
 822     rc = pcmk_legacy2rc(rc);
 823 
 824     if (rc != pcmk_rc_ok) {
 825         exit_code = pcmk_rc2exitc(rc);
 826         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 827                     "Could not connect to the CIB: %s", pcmk_rc_str(rc));
 828         goto done;
 829     }
 830 
 831     set_type();
 832 
 833     // Use default node if not given (except for cluster options and tickets)
 834     if (!pcmk__strcase_any_of(options.type,
 835                               PCMK_XE_CRM_CONFIG, PCMK_XE_TICKETS, NULL)) {
 836         /* If we are being called from a resource agent via the cluster,
 837          * the correct local node name will be passed as an environment
 838          * variable. Otherwise, we have to ask the cluster.
 839          */
 840         const char *target = pcmk__node_attr_target(options.dest_uname);
 841 
 842         if (target != NULL) {
 843             /* If options.dest_uname is "auto" or "localhost", then
 844              * pcmk__node_attr_target() may return it, depending on environment
 845              * variables. In that case, attribute lookups will fail for "auto"
 846              * (unless there's a node named "auto"). attrd maps "localhost" to
 847              * the true local node name for queries.
 848              *
 849              * @TODO
 850              * * Investigate whether "localhost" is mapped to a real node name
 851              *   for non-query commands. If not, possibly modify it so that it
 852              *   is.
 853              * * Map "auto" to "localhost" (probably).
 854              */
 855             if (target != (const char *) options.dest_uname) {
 856                 g_free(options.dest_uname);
 857                 options.dest_uname = g_strdup(target);
 858             }
 859 
 860         } else if (getenv("CIB_file") != NULL && options.dest_uname == NULL) {
 861             get_node_name_from_local();
 862         }
 863 
 864         if (options.dest_uname == NULL) {
 865             char *node_name = NULL;
 866 
 867             rc = pcmk__query_node_name(out, 0, &node_name, 0);
 868 
 869             if (rc != pcmk_rc_ok) {
 870                 exit_code = pcmk_rc2exitc(rc);
 871                 free(node_name);
 872                 goto done;
 873             }
 874             options.dest_uname = g_strdup(node_name);
 875             free(node_name);
 876         }
 877 
 878         rc = query_node_uuid(the_cib, options.dest_uname, &options.dest_node, &is_remote_node);
 879         rc = pcmk_legacy2rc(rc);
 880 
 881         if (rc != pcmk_rc_ok) {
 882             exit_code = pcmk_rc2exitc(rc);
 883             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 884                         "Could not map name=%s to a UUID", options.dest_uname);
 885             goto done;
 886         }
 887     }
 888 
 889     if (!delete_used_correctly()) {
 890         exit_code = CRM_EX_USAGE;
 891         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 892                     "Error: must specify attribute name or pattern to delete");
 893         goto done;
 894     }
 895 
 896     if (!update_used_correctly()) {
 897         exit_code = CRM_EX_USAGE;
 898         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 899                     "Error: must specify attribute name or pattern to update");
 900         goto done;
 901     }
 902 
 903     if (options.attr_pattern) {
 904         if (options.attr_name) {
 905             exit_code = CRM_EX_USAGE;
 906             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 907                         "Error: --name and --pattern cannot be used at the same time");
 908             goto done;
 909         }
 910 
 911         if (!pattern_used_correctly()) {
 912             exit_code = CRM_EX_USAGE;
 913             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 914                         "Error: pattern can only be used with delete, query, or update");
 915             goto done;
 916         }
 917 
 918         g_free(options.attr_name);
 919         options.attr_name = options.attr_pattern;
 920         options.attr_options |= pcmk__node_attr_pattern;
 921     }
 922 
 923     if (is_remote_node) {
 924         options.attr_options |= pcmk__node_attr_remote;
 925     }
 926 
 927     if (pcmk__str_eq(options.set_type, PCMK_XE_UTILIZATION, pcmk__str_none)) {
 928         options.attr_options |= pcmk__node_attr_utilization;
 929     }
 930 
 931     if (try_ipc_update() &&
 932         (send_attrd_update(options.command, options.dest_uname, options.attr_name,
 933                            options.attr_value, options.set_name, NULL, options.attr_options) == pcmk_rc_ok)) {
 934 
 935         const char *update = options.attr_value;
 936 
 937         if (options.command == attr_cmd_delete) {
 938             update = "<none>";
 939         }
 940         crm_info("Update %s=%s sent via pacemaker-attrd",
 941                  options.attr_name, update);
 942 
 943     } else if (options.command == attr_cmd_delete) {
 944         rc = command_delete(out, the_cib);
 945 
 946     } else if (options.command == attr_cmd_update) {
 947         rc = command_update(out, the_cib, is_remote_node);
 948 
 949     } else {
 950         rc = command_query(out, the_cib);
 951     }
 952 
 953     if (rc == ENOTUNIQ) {
 954         exit_code = pcmk_rc2exitc(rc);
 955         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 956                     "Please choose from one of the matches below and supply the 'id' with --attr-id");
 957 
 958     } else if (rc != pcmk_rc_ok) {
 959         exit_code = pcmk_rc2exitc(rc);
 960         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 961                     "Error performing operation: %s", pcmk_rc_str(rc));
 962     }
 963 
 964 done:
 965     g_strfreev(processed_args);
 966     pcmk__free_arg_context(context);
 967 
 968     free(options.attr_default);
 969     g_free(options.attr_id);
 970     g_free(options.attr_name);
 971     free(options.attr_value);
 972     free(options.dest_node);
 973     g_free(options.dest_uname);
 974     g_free(options.set_name);
 975     free(options.set_type);
 976     g_free(options.type);
 977 
 978     cib__clean_up_connection(&the_cib);
 979 
 980     pcmk__output_and_clear_error(&error, out);
 981 
 982     if (out != NULL) {
 983         out->finish(out, exit_code, true, NULL);
 984         pcmk__output_free(out);
 985     }
 986 
 987     pcmk__unregister_formats();
 988     return crm_exit(exit_code);
 989 }

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