This source file includes following definitions.
- PCMK__OUTPUT_ARGS
- delete_cb
- promotion_cb
- update_cb
- utilization_cb
- value_cb
- controller_event_cb
- get_node_name_from_local
- get_node_name_from_controller
- build_arg_context
- main
1
2
3
4
5
6
7
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/msg_xml.h>
26 #include <crm/common/xml.h>
27 #include <crm/common/ipc.h>
28 #include <crm/common/util.h>
29 #include <crm/cluster.h>
30
31 #include <crm/cib.h>
32 #include <crm/cib/internal.h>
33 #include <crm/common/attrd_internal.h>
34 #include <crm/common/cmdline_internal.h>
35 #include <crm/common/ipc_controld.h>
36 #include <crm/common/output_internal.h>
37 #include <sys/utsname.h>
38
39 #include <pcmki/pcmki_output.h>
40
41 #define SUMMARY "crm_attribute - query and update Pacemaker cluster options and node attributes"
42
43 GError *error = NULL;
44 crm_exit_t exit_code = CRM_EX_OK;
45 uint64_t cib_opts = cib_sync_call;
46
47 PCMK__OUTPUT_ARGS("attribute", "char *", "char *", "char *", "char *")
48 static int
49 attribute_text(pcmk__output_t *out, va_list args)
50 {
51 char *scope = va_arg(args, char *);
52 char *instance = va_arg(args, char *);
53 char *name = va_arg(args, char *);
54 char *value = va_arg(args, char *);
55 char *host G_GNUC_UNUSED = va_arg(args, char *);
56
57 if (out->quiet) {
58 pcmk__formatted_printf(out, "%s\n", value);
59 } else {
60 out->info(out, "%s%s %s%s %s%s value=%s",
61 scope ? "scope=" : "", scope ? scope : "",
62 instance ? "id=" : "", instance ? instance : "",
63 name ? "name=" : "", name ? name : "",
64 value ? value : "(null)");
65 }
66
67 return pcmk_rc_ok;
68 }
69
70 static pcmk__supported_format_t formats[] = {
71 PCMK__SUPPORTED_FORMAT_NONE,
72 PCMK__SUPPORTED_FORMAT_TEXT,
73 PCMK__SUPPORTED_FORMAT_XML,
74 { NULL, NULL, NULL }
75 };
76
77 static pcmk__message_entry_t fmt_functions[] = {
78 { "attribute", "text", attribute_text },
79
80 { NULL, NULL, NULL }
81 };
82
83 struct {
84 char command;
85 gchar *attr_default;
86 gchar *attr_id;
87 gchar *attr_name;
88 gchar *attr_pattern;
89 char *attr_value;
90 char *dest_node;
91 gchar *dest_uname;
92 gboolean inhibit;
93 gchar *set_name;
94 char *set_type;
95 gchar *type;
96 gboolean promotion_score;
97 } options = {
98 .command = 'G',
99 .promotion_score = FALSE
100 };
101
102 #define INDENT " "
103
104 static gboolean
105 delete_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
106 options.command = 'D';
107
108 if (options.attr_value) {
109 free(options.attr_value);
110 }
111
112 options.attr_value = NULL;
113 return TRUE;
114 }
115
116 static gboolean
117 promotion_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
118 char *score_name = NULL;
119
120 options.promotion_score = TRUE;
121
122 if (options.attr_name) {
123 g_free(options.attr_name);
124 }
125
126 score_name = pcmk_promotion_score_name(optarg);
127 if (score_name != NULL) {
128 options.attr_name = g_strdup(score_name);
129 free(score_name);
130 } else {
131 options.attr_name = NULL;
132 }
133
134 return TRUE;
135 }
136
137 static gboolean
138 update_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
139 options.command = 'v';
140 pcmk__str_update(&options.attr_value, optarg);
141 return TRUE;
142 }
143
144 static gboolean
145 utilization_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
146 if (options.type) {
147 g_free(options.type);
148 }
149
150 options.type = g_strdup(XML_CIB_TAG_NODES);
151
152 if (options.set_type) {
153 free(options.set_type);
154 }
155
156 options.set_type = strdup(XML_TAG_UTILIZATION);
157 return TRUE;
158 }
159
160 static gboolean
161 value_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
162 options.command = 'G';
163
164 if (options.attr_value) {
165 free(options.attr_value);
166 }
167
168 options.attr_value = NULL;
169 return TRUE;
170 }
171
172 static GOptionEntry selecting_entries[] = {
173 { "id", 'i', 0, G_OPTION_ARG_STRING, &options.attr_id,
174 "(Advanced) Operate on instance of specified attribute with this\n"
175 INDENT "XML ID",
176 "XML_ID"
177 },
178
179 { "name", 'n', 0, G_OPTION_ARG_STRING, &options.attr_name,
180 "Operate on attribute or option with this name",
181 "NAME"
182 },
183
184 { "pattern", 'P', 0, G_OPTION_ARG_STRING, &options.attr_pattern,
185 "Operate on all attributes matching this pattern\n"
186 INDENT "(with -v/-D and -l reboot)",
187 "PATTERN"
188 },
189
190 { "promotion", 'p', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, promotion_cb,
191 "Operate on node attribute used as promotion score for specified\n"
192 INDENT "resource, or resource given in OCF_RESOURCE_INSTANCE environment\n"
193 INDENT "variable if none is specified; this also defaults -l/--lifetime\n"
194 INDENT "to reboot (normally invoked from an OCF resource agent)",
195 "RESOURCE"
196 },
197
198 { "set-name", 's', 0, G_OPTION_ARG_STRING, &options.set_name,
199 "(Advanced) Operate on instance of specified attribute that is\n"
200 INDENT "within set with this XML ID",
201 "NAME"
202 },
203
204 { NULL }
205 };
206
207 static GOptionEntry command_entries[] = {
208 { "delete", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, delete_cb,
209 "Delete the attribute/option",
210 NULL
211 },
212
213 { "query", 'G', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, value_cb,
214 "Query the current value of the attribute/option",
215 NULL
216 },
217
218 { "update", 'v', 0, G_OPTION_ARG_CALLBACK, update_cb,
219 "Update the value of the attribute/option",
220 "VALUE"
221 },
222
223 { NULL }
224 };
225
226 static GOptionEntry addl_entries[] = {
227 { "default", 'd', 0, G_OPTION_ARG_STRING, &options.attr_default,
228 "(Advanced) Default value to display if none is found in configuration",
229 "VALUE"
230 },
231
232 { "lifetime", 'l', 0, G_OPTION_ARG_STRING, &options.type,
233 "Lifetime of the node attribute.\n"
234 INDENT "Valid values: reboot, forever",
235 "LIFETIME"
236 },
237
238 { "node", 'N', 0, G_OPTION_ARG_STRING, &options.dest_uname,
239 "Set a node attribute for named node (instead of a cluster option).\n"
240 INDENT "See also: -l",
241 "NODE"
242 },
243
244 { "type", 't', 0, G_OPTION_ARG_STRING, &options.type,
245 "Which part of the configuration to update/delete/query the option in.\n"
246 INDENT "Valid values: crm_config, rsc_defaults, op_defaults, tickets",
247 "SECTION"
248 },
249
250 { "utilization", 'z', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, utilization_cb,
251 "Set an utilization attribute for the node.",
252 NULL
253 },
254
255 { "inhibit-policy-engine", '!', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.inhibit,
256 NULL, NULL
257 },
258
259 { NULL }
260 };
261
262 static GOptionEntry deprecated_entries[] = {
263 { "attr-id", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.attr_id,
264 NULL, NULL
265 },
266
267 { "attr-name", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.attr_name,
268 NULL, NULL
269 },
270
271 { "attr-value", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, update_cb,
272 NULL, NULL
273 },
274
275 { "delete-attr", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, delete_cb,
276 NULL, NULL
277 },
278
279 { "get-value", 0, G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, value_cb,
280 NULL, NULL
281 },
282
283 { "node-uname", 'U', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.dest_uname,
284 NULL, NULL
285 },
286
287 { NULL }
288 };
289
290 static void
291 controller_event_cb(pcmk_ipc_api_t *controld_api,
292 enum pcmk_ipc_event event_type, crm_exit_t status,
293 void *event_data, void *user_data)
294 {
295 pcmk_controld_api_reply_t *reply = event_data;
296
297 if (event_type != pcmk_ipc_event_reply) {
298 return;
299 }
300
301 if (status != CRM_EX_OK) {
302 exit_code = status;
303 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
304 "Bad reply from controller: %s", crm_exit_str(exit_code));
305 return;
306 }
307
308 if (reply->reply_type != pcmk_controld_reply_info) {
309 exit_code = CRM_EX_PROTOCOL;
310 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
311 "Unknown reply type %d from controller", reply->reply_type);
312 return;
313 }
314
315 if (reply->data.node_info.uname == NULL) {
316 exit_code = CRM_EX_NOHOST;
317 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
318 "Node is not known to cluster");
319 }
320
321 exit_code = CRM_EX_OK;
322 pcmk__str_update(&options.dest_uname, reply->data.node_info.uname);
323 }
324
325 static void
326 get_node_name_from_local(void)
327 {
328 char *hostname = pcmk_hostname();
329
330 g_free(options.dest_uname);
331
332
333
334
335
336 options.dest_uname = g_strdup(hostname);
337 free(hostname);
338 }
339
340 static int
341 get_node_name_from_controller(void)
342 {
343 int rc = pcmk_rc_ok;
344 pcmk_ipc_api_t *controld_api = NULL;
345
346 rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld);
347 if (controld_api == NULL) {
348 g_set_error(&error, PCMK__RC_ERROR, rc, "Could not connect to controller: %s",
349 pcmk_rc_str(rc));
350 return rc;
351 }
352
353 pcmk_register_ipc_callback(controld_api, controller_event_cb, NULL);
354
355 rc = pcmk_connect_ipc(controld_api, pcmk_ipc_dispatch_sync);
356 if (rc != pcmk_rc_ok) {
357 g_set_error(&error, PCMK__RC_ERROR, rc, "Could not connect to controller: %s",
358 pcmk_rc_str(rc));
359 pcmk_free_ipc_api(controld_api);
360 return rc;
361 }
362
363 rc = pcmk_controld_api_node_info(controld_api, 0);
364
365 if (rc != pcmk_rc_ok) {
366 g_set_error(&error, PCMK__RC_ERROR, rc, "Could not ping controller: %s",
367 pcmk_rc_str(rc));
368 }
369
370
371
372
373
374
375 if (exit_code != CRM_EX_OK) {
376 rc = pcmk_rc_error;
377 }
378
379 pcmk_disconnect_ipc(controld_api);
380 pcmk_free_ipc_api(controld_api);
381 return rc;
382 }
383
384 static GOptionContext *
385 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
386 GOptionContext *context = NULL;
387
388 GOptionEntry extra_prog_entries[] = {
389 { "quiet", 'q', 0, G_OPTION_ARG_NONE, &(args->quiet),
390 "Print only the value on stdout",
391 NULL },
392
393 { "quiet", 'Q', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &(args->quiet),
394 NULL, NULL
395 },
396
397 { NULL }
398 };
399
400 const char *description = "Examples:\n\n"
401 "Add new node attribute called 'location' with the value of 'office' for host 'myhost':\n\n"
402 "\tcrm_attribute --node myhost --name location --update office\n\n"
403 "Query the value of the 'location' node attribute for host 'myhost':\n\n"
404 "\tcrm_attribute --node myhost --name location --query\n\n"
405 "Change the value of the 'location' node attribute for host 'myhost':\n\n"
406 "\tcrm_attribute --node myhost --name location --update backoffice\n\n"
407 "Delete the 'location' node attribute for host 'myhost':\n\n"
408 "\tcrm_attribute --node myhost --name location --delete\n\n"
409 "Query the value of the 'cluster-delay' cluster option:\n\n"
410 "\tcrm_attribute --type crm_config --name cluster-delay --query\n\n"
411 "Query value of the 'cluster-delay' cluster option and print only the value:\n\n"
412 "\tcrm_attribute --type crm_config --name cluster-delay --query --quiet\n\n";
413
414 context = pcmk__build_arg_context(args, "text (default), xml", group, NULL);
415 pcmk__add_main_args(context, extra_prog_entries);
416 g_option_context_set_description(context, description);
417
418 pcmk__add_arg_group(context, "selections", "Selecting attributes:",
419 "Show selecting options", selecting_entries);
420 pcmk__add_arg_group(context, "command", "Commands:",
421 "Show command options", command_entries);
422 pcmk__add_arg_group(context, "additional", "Additional options:",
423 "Show additional options", addl_entries);
424 pcmk__add_arg_group(context, "deprecated", "Deprecated Options:",
425 "Show deprecated options", deprecated_entries);
426
427 return context;
428 }
429
430 int
431 main(int argc, char **argv)
432 {
433 cib_t *the_cib = NULL;
434 int is_remote_node = 0;
435 bool try_attrd = true;
436 int attrd_opts = pcmk__node_attr_none;
437
438 int rc = pcmk_rc_ok;
439
440 pcmk__output_t *out = NULL;
441
442 GOptionGroup *output_group = NULL;
443 pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
444 gchar **processed_args = pcmk__cmdline_preproc(argv, "NPUdilnpstv");
445 GOptionContext *context = build_arg_context(args, &output_group);
446
447 pcmk__register_formats(output_group, formats);
448 if (!g_option_context_parse_strv(context, &processed_args, &error)) {
449 exit_code = CRM_EX_USAGE;
450 goto done;
451 }
452
453 pcmk__cli_init_logging("crm_attribute", args->verbosity);
454
455 rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
456 if (rc != pcmk_rc_ok) {
457 exit_code = CRM_EX_ERROR;
458 g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Error creating output format %s: %s",
459 args->output_ty, pcmk_rc_str(rc));
460 goto done;
461 }
462
463 pcmk__register_lib_messages(out);
464 pcmk__register_messages(out, fmt_functions);
465
466 if (args->version) {
467 out->version(out, false);
468 goto done;
469 }
470
471 out->quiet = args->quiet;
472
473 if (options.promotion_score && options.attr_name == NULL) {
474 exit_code = CRM_EX_USAGE;
475 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
476 "-p/--promotion must be called from an OCF resource agent "
477 "or with a resource ID specified");
478 goto done;
479 }
480
481 if (options.inhibit) {
482 crm_warn("Inhibiting notifications for this update");
483 cib__set_call_options(cib_opts, crm_system_name, cib_inhibit_notify);
484 }
485
486 the_cib = cib_new();
487 rc = the_cib->cmds->signon(the_cib, crm_system_name, cib_command);
488 rc = pcmk_legacy2rc(rc);
489
490 if (rc != pcmk_rc_ok) {
491 exit_code = pcmk_rc2exitc(rc);
492 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
493 "Could not connect to the CIB: %s", pcmk_rc_str(rc));
494 goto done;
495 }
496
497
498 if (options.type == NULL) {
499 if (options.promotion_score) {
500
501 options.type = g_strdup(XML_CIB_TAG_STATUS);
502
503 } else if (options.dest_uname != NULL) {
504
505 options.type = g_strdup(XML_CIB_TAG_NODES);
506
507 } else {
508
509 options.type = g_strdup(XML_CIB_TAG_CRMCONFIG);
510 }
511 } else if (pcmk__str_eq(options.type, "reboot", pcmk__str_casei)) {
512 options.type = g_strdup(XML_CIB_TAG_STATUS);
513
514 } else if (pcmk__str_eq(options.type, "forever", pcmk__str_casei)) {
515 options.type = g_strdup(XML_CIB_TAG_NODES);
516 }
517
518
519 if (!pcmk__strcase_any_of(options.type, XML_CIB_TAG_CRMCONFIG, XML_CIB_TAG_TICKETS,
520 NULL)) {
521
522
523
524
525 const char *target = pcmk__node_attr_target(options.dest_uname);
526
527 if (target != NULL) {
528 g_free(options.dest_uname);
529 options.dest_uname = g_strdup(target);
530 } else if (getenv("CIB_file") != NULL && options.dest_uname == NULL) {
531 get_node_name_from_local();
532 }
533
534 if (options.dest_uname == NULL) {
535 rc = get_node_name_from_controller();
536
537 if (rc == pcmk_rc_error) {
538
539
540
541 goto done;
542 } else if (rc != pcmk_rc_ok) {
543
544
545
546 exit_code = pcmk_rc2exitc(rc);
547 goto done;
548 }
549 }
550
551 rc = query_node_uuid(the_cib, options.dest_uname, &options.dest_node, &is_remote_node);
552 rc = pcmk_legacy2rc(rc);
553
554 if (rc != pcmk_rc_ok) {
555 exit_code = pcmk_rc2exitc(rc);
556 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
557 "Could not map name=%s to a UUID", options.dest_uname);
558 goto done;
559 }
560 }
561
562 if ((options.command == 'D') && (options.attr_name == NULL) && (options.attr_pattern == NULL)) {
563 exit_code = CRM_EX_USAGE;
564 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
565 "Error: must specify attribute name or pattern to delete");
566 goto done;
567 }
568
569 if (options.attr_pattern) {
570 if (((options.command != 'v') && (options.command != 'D'))
571 || !pcmk__str_eq(options.type, XML_CIB_TAG_STATUS, pcmk__str_casei)) {
572
573 exit_code = CRM_EX_USAGE;
574 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
575 "Error: pattern can only be used with till-reboot update or delete");
576 goto done;
577 }
578 options.command = 'u';
579 g_free(options.attr_name);
580 options.attr_name = options.attr_pattern;
581 }
582
583
584 try_attrd = pcmk__str_eq(options.type, XML_CIB_TAG_STATUS, pcmk__str_casei);
585
586
587 if (getenv("CIB_file") || getenv("CIB_shadow")) {
588 try_attrd = false;
589 }
590
591 if (is_remote_node) {
592 attrd_opts = pcmk__node_attr_remote;
593 }
594 if (((options.command == 'v') || (options.command == 'D') || (options.command == 'u')) && try_attrd
595 && (pcmk__node_attr_request(NULL, options.command, options.dest_uname, options.attr_name,
596 options.attr_value, options.type, options.set_name, NULL, NULL,
597 attrd_opts) == pcmk_rc_ok)) {
598 crm_info("Update %s=%s sent via pacemaker-attrd",
599 options.attr_name, ((options.command == 'D')? "<none>" : options.attr_value));
600
601 } else if (options.command == 'D') {
602 rc = cib__delete_node_attr(out, the_cib, cib_opts, options.type, options.dest_node,
603 options.set_type, options.set_name, options.attr_id,
604 options.attr_name, options.attr_value, NULL);
605
606 if (rc == ENXIO) {
607
608
609
610
611 rc = pcmk_rc_ok;
612 }
613
614 } else if (options.command == 'v') {
615 CRM_LOG_ASSERT(options.type != NULL);
616 CRM_LOG_ASSERT(options.attr_name != NULL);
617 CRM_LOG_ASSERT(options.attr_value != NULL);
618
619 rc = cib__update_node_attr(out, the_cib, cib_opts, options.type, options.dest_node,
620 options.set_type, options.set_name, options.attr_id,
621 options.attr_name, options.attr_value, NULL,
622 is_remote_node ? "remote" : NULL);
623
624 } else {
625
626 char *read_value = NULL;
627
628 if (options.attr_id == NULL && options.attr_name == NULL) {
629 exit_code = CRM_EX_USAGE;
630 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
631 "Error: must specify attribute name or pattern to query");
632 goto done;
633 }
634
635 rc = cib__read_node_attr(out, the_cib, options.type, options.dest_node,
636 options.set_type, options.set_name, options.attr_id,
637 options.attr_name, &read_value, NULL);
638
639 if (rc == ENXIO && options.attr_default) {
640 read_value = strdup(options.attr_default);
641 rc = pcmk_rc_ok;
642 }
643
644 crm_info("Read %s=%s %s%s",
645 options.attr_name, crm_str(read_value), options.set_name ? "in " : "", options.set_name ? options.set_name : "");
646
647 if (rc == ENOTUNIQ) {
648
649 rc = pcmk_rc_ok;
650 } else {
651 out->message(out, "attribute", options.type, options.attr_id,
652 options.attr_name, read_value);
653 }
654
655 free(read_value);
656 }
657
658 if (rc == ENOTUNIQ) {
659 exit_code = pcmk_rc2exitc(rc);
660 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
661 "Please choose from one of the matches below and supply the 'id' with --attr-id");
662
663 } else if (rc != pcmk_rc_ok) {
664 exit_code = pcmk_rc2exitc(rc);
665 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
666 "Error performing operation: %s", pcmk_strerror(rc));
667 }
668
669 done:
670 g_strfreev(processed_args);
671 pcmk__free_arg_context(context);
672
673 free(options.attr_default);
674 g_free(options.attr_id);
675 g_free(options.attr_name);
676 free(options.attr_value);
677 free(options.dest_node);
678 g_free(options.dest_uname);
679 g_free(options.set_name);
680 free(options.set_type);
681 g_free(options.type);
682
683 cib__clean_up_connection(&the_cib);
684
685 pcmk__output_and_clear_error(error, out);
686
687 if (out != NULL) {
688 out->finish(out, exit_code, true, NULL);
689 pcmk__output_free(out);
690 }
691
692 return crm_exit(exit_code);
693 }