This source file includes following definitions.
- attr_value_cb
- command_cb
- delete_attr_cb
- get_attr_cb
- grant_standby_cb
- set_attr_cb
- find_ticket
- print_date
- print_ticket
- print_ticket_list
- find_ticket_state
- find_ticket_constraints
- dump_ticket_xml
- dump_constraints
- get_ticket_state_attr
- ticket_warning
- allow_modification
- modify_ticket_state
- delete_ticket_state
- build_arg_context
- main
1
2
3
4
5
6
7
8
9
10 #include <crm_internal.h>
11
12 #include <sys/param.h>
13
14 #include <crm/crm.h>
15
16 #include <stdio.h>
17 #include <sys/types.h>
18 #include <unistd.h>
19
20 #include <stdlib.h>
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <libgen.h>
24
25 #include <crm/msg_xml.h>
26 #include <crm/common/xml.h>
27 #include <crm/common/ipc.h>
28 #include <crm/common/cmdline_internal.h>
29
30 #include <crm/cib.h>
31 #include <crm/cib/internal.h>
32 #include <crm/pengine/rules.h>
33 #include <crm/pengine/status.h>
34
35 #include <pacemaker-internal.h>
36
37 GError *error = NULL;
38
39 #define SUMMARY "Perform tasks related to cluster tickets\n\n" \
40 "Allows ticket attributes to be queried, modified and deleted."
41
42 struct {
43 gchar *attr_default;
44 gchar *attr_id;
45 char *attr_name;
46 char *attr_value;
47 gboolean force;
48 char *get_attr_name;
49 gboolean quiet;
50 gchar *set_name;
51 char ticket_cmd;
52 gchar *ticket_id;
53 gchar *xml_file;
54 } options = {
55 .ticket_cmd = 'S'
56 };
57
58 GList *attr_delete;
59 GHashTable *attr_set;
60 bool modified = false;
61 int cib_options = cib_sync_call;
62
63 #define INDENT " "
64
65 static gboolean
66 attr_value_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
67 pcmk__str_update(&options.attr_value, optarg);
68
69 if (!options.attr_name || !options.attr_value) {
70 return TRUE;
71 }
72
73 g_hash_table_insert(attr_set, strdup(options.attr_name), strdup(options.attr_value));
74 pcmk__str_update(&options.attr_name, NULL);
75 pcmk__str_update(&options.attr_value, NULL);
76
77 modified = true;
78
79 return TRUE;
80 }
81
82 static gboolean
83 command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
84 if (pcmk__str_any_of(option_name, "--info", "-l", NULL)) {
85 options.ticket_cmd = 'l';
86 } else if (pcmk__str_any_of(option_name, "--details", "-L", NULL)) {
87 options.ticket_cmd = 'L';
88 } else if (pcmk__str_any_of(option_name, "--raw", "-w", NULL)) {
89 options.ticket_cmd = 'w';
90 } else if (pcmk__str_any_of(option_name, "--query-xml", "-q", NULL)) {
91 options.ticket_cmd = 'q';
92 } else if (pcmk__str_any_of(option_name, "--constraints", "-c", NULL)) {
93 options.ticket_cmd = 'c';
94 } else if (pcmk__str_any_of(option_name, "--cleanup", "-C", NULL)) {
95 options.ticket_cmd = 'C';
96 }
97
98 return TRUE;
99 }
100
101 static gboolean
102 delete_attr_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
103 attr_delete = g_list_append(attr_delete, strdup(optarg));
104 modified = true;
105 return TRUE;
106 }
107
108 static gboolean
109 get_attr_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
110 pcmk__str_update(&options.get_attr_name, optarg);
111 options.ticket_cmd = 'G';
112 return TRUE;
113 }
114
115 static gboolean
116 grant_standby_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
117 if (pcmk__str_any_of(option_name, "--grant", "-g", NULL)) {
118 g_hash_table_insert(attr_set, strdup("granted"), strdup("true"));
119 modified = true;
120 } else if (pcmk__str_any_of(option_name, "--revoke", "-r", NULL)) {
121 g_hash_table_insert(attr_set, strdup("granted"), strdup("false"));
122 modified = true;
123 } else if (pcmk__str_any_of(option_name, "--standby", "-s", NULL)) {
124 g_hash_table_insert(attr_set, strdup("standby"), strdup("true"));
125 modified = true;
126 } else if (pcmk__str_any_of(option_name, "--activate", "-a", NULL)) {
127 g_hash_table_insert(attr_set, strdup("standby"), strdup("false"));
128 modified = true;
129 }
130
131 return TRUE;
132 }
133
134 static gboolean
135 set_attr_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
136 pcmk__str_update(&options.attr_name, optarg);
137
138 if (!options.attr_name || !options.attr_value) {
139 return TRUE;
140 }
141
142 g_hash_table_insert(attr_set, strdup(options.attr_name), strdup(options.attr_value));
143 pcmk__str_update(&options.attr_name, NULL);
144 pcmk__str_update(&options.attr_value, NULL);
145
146 modified = true;
147
148 return TRUE;
149 }
150
151 static GOptionEntry query_entries[] = {
152 { "info", 'l', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
153 "Display the information of ticket(s)",
154 NULL },
155
156 { "details", 'L', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
157 "Display the details of ticket(s)",
158 NULL },
159
160 { "raw", 'w', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
161 "Display the IDs of ticket(s)",
162 NULL },
163
164 { "query-xml", 'q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
165 "Query the XML of ticket(s)",
166 NULL },
167
168 { "constraints", 'c', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
169 "Display the rsc_ticket constraints that apply to ticket(s)",
170 NULL },
171
172 { NULL }
173 };
174
175 static GOptionEntry command_entries[] = {
176 { "grant", 'g', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, grant_standby_cb,
177 "Grant a ticket to this cluster site",
178 NULL },
179
180 { "revoke", 'r', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, grant_standby_cb,
181 "Revoke a ticket from this cluster site",
182 NULL },
183
184 { "standby", 's', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, grant_standby_cb,
185 "Tell this cluster site this ticket is standby",
186 NULL },
187
188 { "activate", 'a', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, grant_standby_cb,
189 "Tell this cluster site this ticket is active",
190 NULL },
191
192 { NULL }
193 };
194
195 static GOptionEntry advanced_entries[] = {
196 { "get-attr", 'G', 0, G_OPTION_ARG_CALLBACK, get_attr_cb,
197 "Display the named attribute for a ticket",
198 "ATTRIBUTE" },
199
200 { "set-attr", 'S', 0, G_OPTION_ARG_CALLBACK, set_attr_cb,
201 "Set the named attribute for a ticket",
202 "ATTRIBUTE" },
203
204 { "delete-attr", 'D', 0, G_OPTION_ARG_CALLBACK, delete_attr_cb,
205 "Delete the named attribute for a ticket",
206 "ATTRIBUTE" },
207
208 { "cleanup", 'C', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
209 "Delete all state of a ticket at this cluster site",
210 NULL },
211
212 { NULL}
213 };
214
215 static GOptionEntry addl_entries[] = {
216 { "attr-value", 'v', 0, G_OPTION_ARG_CALLBACK, attr_value_cb,
217 "Attribute value to use with -S",
218 "VALUE" },
219
220 { "default", 'd', 0, G_OPTION_ARG_STRING, &options.attr_default,
221 "(Advanced) Default attribute value to display if none is found\n"
222 INDENT "(for use with -G)",
223 "VALUE" },
224
225 { "force", 'f', 0, G_OPTION_ARG_NONE, &options.force,
226 "(Advanced) Force the action to be performed",
227 NULL },
228
229 { "ticket", 't', 0, G_OPTION_ARG_STRING, &options.ticket_id,
230 "Ticket ID",
231 "ID" },
232
233 { "xml-file", 'x', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.xml_file,
234 NULL,
235 NULL },
236
237 { NULL }
238 };
239
240 static GOptionEntry deprecated_entries[] = {
241 { "set-name", 'n', 0, G_OPTION_ARG_STRING, &options.set_name,
242 "(Advanced) ID of the instance_attributes object to change",
243 "ID" },
244
245 { "nvpair", 'i', 0, G_OPTION_ARG_STRING, &options.attr_id,
246 "(Advanced) ID of the nvpair object to change/delete",
247 "ID" },
248
249 { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &options.quiet,
250 "Print only the value on stdout",
251 NULL },
252
253 { NULL }
254 };
255
256 static pe_ticket_t *
257 find_ticket(gchar *ticket_id, pe_working_set_t * data_set)
258 {
259 return g_hash_table_lookup(data_set->tickets, ticket_id);
260 }
261
262 static void
263 print_date(time_t time)
264 {
265 int lpc = 0;
266 char date_str[26];
267
268 asctime_r(localtime(&time), date_str);
269 for (; lpc < 26; lpc++) {
270 if (date_str[lpc] == '\n') {
271 date_str[lpc] = 0;
272 }
273 }
274 fprintf(stdout, "'%s'", date_str);
275 }
276
277 static void
278 print_ticket(pe_ticket_t * ticket, bool raw, bool details)
279 {
280 if (raw) {
281 fprintf(stdout, "%s\n", ticket->id);
282 return;
283 }
284
285 fprintf(stdout, "%s\t%s %s",
286 ticket->id, ticket->granted ? "granted" : "revoked",
287 ticket->standby ? "[standby]" : " ");
288
289 if (details && g_hash_table_size(ticket->state) > 0) {
290 GHashTableIter iter;
291 const char *name = NULL;
292 const char *value = NULL;
293 int lpc = 0;
294
295 fprintf(stdout, " (");
296
297 g_hash_table_iter_init(&iter, ticket->state);
298 while (g_hash_table_iter_next(&iter, (void **)&name, (void **)&value)) {
299 if (lpc > 0) {
300 fprintf(stdout, ", ");
301 }
302 fprintf(stdout, "%s=", name);
303 if (pcmk__str_any_of(name, "last-granted", "expires", NULL)) {
304 long long time_ll;
305
306 pcmk__scan_ll(value, &time_ll, 0);
307 print_date((time_t) time_ll);
308 } else {
309 fprintf(stdout, "%s", value);
310 }
311 lpc++;
312 }
313
314 fprintf(stdout, ")\n");
315
316 } else {
317 if (ticket->last_granted > -1) {
318 fprintf(stdout, " last-granted=");
319 print_date(ticket->last_granted);
320 }
321 fprintf(stdout, "\n");
322 }
323
324 return;
325 }
326
327 static void
328 print_ticket_list(pe_working_set_t * data_set, bool raw, bool details)
329 {
330 GHashTableIter iter;
331 pe_ticket_t *ticket = NULL;
332
333 g_hash_table_iter_init(&iter, data_set->tickets);
334
335 while (g_hash_table_iter_next(&iter, NULL, (void **)&ticket)) {
336 print_ticket(ticket, raw, details);
337 }
338 }
339
340 #define XPATH_MAX 1024
341
342 static int
343 find_ticket_state(cib_t * the_cib, gchar *ticket_id, xmlNode ** ticket_state_xml)
344 {
345 int offset = 0;
346 int rc = pcmk_rc_ok;
347 xmlNode *xml_search = NULL;
348
349 char *xpath_string = NULL;
350
351 CRM_ASSERT(ticket_state_xml != NULL);
352 *ticket_state_xml = NULL;
353
354 xpath_string = calloc(1, XPATH_MAX);
355 offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "%s", "/cib/status/tickets");
356
357 if (ticket_id) {
358 offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "/%s[@id=\"%s\"]",
359 XML_CIB_TAG_TICKET_STATE, ticket_id);
360 }
361
362 CRM_LOG_ASSERT(offset > 0);
363 rc = the_cib->cmds->query(the_cib, xpath_string, &xml_search,
364 cib_sync_call | cib_scope_local | cib_xpath);
365 rc = pcmk_legacy2rc(rc);
366
367 if (rc != pcmk_rc_ok) {
368 goto done;
369 }
370
371 crm_log_xml_debug(xml_search, "Match");
372 if (xml_has_children(xml_search)) {
373 if (ticket_id) {
374 fprintf(stdout, "Multiple ticket_states match ticket_id=%s\n", ticket_id);
375 }
376 *ticket_state_xml = xml_search;
377 } else {
378 *ticket_state_xml = xml_search;
379 }
380
381 done:
382 free(xpath_string);
383 return rc;
384 }
385
386 static int
387 find_ticket_constraints(cib_t * the_cib, gchar *ticket_id, xmlNode ** ticket_cons_xml)
388 {
389 int offset = 0;
390 int rc = pcmk_rc_ok;
391 xmlNode *xml_search = NULL;
392
393 char *xpath_string = NULL;
394 const char *xpath_base = NULL;
395
396 CRM_ASSERT(ticket_cons_xml != NULL);
397 *ticket_cons_xml = NULL;
398
399 xpath_base = pcmk_cib_xpath_for(XML_CIB_TAG_CONSTRAINTS);
400 if (xpath_base == NULL) {
401 crm_err(XML_CIB_TAG_CONSTRAINTS " CIB element not known (bug?)");
402 return -ENOMSG;
403 }
404
405 xpath_string = calloc(1, XPATH_MAX);
406 offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "%s/%s",
407 xpath_base, XML_CONS_TAG_RSC_TICKET);
408
409 if (ticket_id) {
410 offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "[@ticket=\"%s\"]",
411 ticket_id);
412 }
413
414 CRM_LOG_ASSERT(offset > 0);
415 rc = the_cib->cmds->query(the_cib, xpath_string, &xml_search,
416 cib_sync_call | cib_scope_local | cib_xpath);
417 rc = pcmk_legacy2rc(rc);
418
419 if (rc != pcmk_rc_ok) {
420 goto done;
421 }
422
423 crm_log_xml_debug(xml_search, "Match");
424 *ticket_cons_xml = xml_search;
425
426 done:
427 free(xpath_string);
428 return rc;
429 }
430
431 static int
432 dump_ticket_xml(cib_t * the_cib, gchar *ticket_id)
433 {
434 int rc = pcmk_rc_ok;
435 xmlNode *state_xml = NULL;
436
437 rc = find_ticket_state(the_cib, ticket_id, &state_xml);
438
439 if (state_xml == NULL) {
440 return rc;
441 }
442
443 fprintf(stdout, "State XML:\n");
444 if (state_xml) {
445 char *state_xml_str = NULL;
446
447 state_xml_str = dump_xml_formatted(state_xml);
448 fprintf(stdout, "\n%s\n", crm_str(state_xml_str));
449 free_xml(state_xml);
450 free(state_xml_str);
451 }
452
453 return rc;
454 }
455
456 static int
457 dump_constraints(cib_t * the_cib, gchar *ticket_id)
458 {
459 int rc = pcmk_rc_ok;
460 xmlNode *cons_xml = NULL;
461 char *cons_xml_str = NULL;
462
463 rc = find_ticket_constraints(the_cib, ticket_id, &cons_xml);
464
465 if (cons_xml == NULL) {
466 return rc;
467 }
468
469 cons_xml_str = dump_xml_formatted(cons_xml);
470 fprintf(stdout, "Constraints XML:\n\n%s\n", crm_str(cons_xml_str));
471 free_xml(cons_xml);
472 free(cons_xml_str);
473
474 return rc;
475 }
476
477 static int
478 get_ticket_state_attr(gchar *ticket_id, const char *attr_name, const char **attr_value,
479 pe_working_set_t * data_set)
480 {
481 pe_ticket_t *ticket = NULL;
482
483 CRM_ASSERT(attr_value != NULL);
484 *attr_value = NULL;
485
486 ticket = g_hash_table_lookup(data_set->tickets, ticket_id);
487 if (ticket == NULL) {
488 return ENXIO;
489 }
490
491 *attr_value = g_hash_table_lookup(ticket->state, attr_name);
492 if (*attr_value == NULL) {
493 return ENXIO;
494 }
495
496 return pcmk_rc_ok;
497 }
498
499 static void
500 ticket_warning(gchar *ticket_id, const char *action)
501 {
502 int offset = 0;
503 static int text_max = 1024;
504
505 char *warning = NULL;
506 const char *word = NULL;
507
508 warning = calloc(1, text_max);
509 if (pcmk__str_eq(action, "grant", pcmk__str_casei)) {
510 offset += snprintf(warning + offset, text_max - offset,
511 "This command cannot help you verify whether '%s' has been already granted elsewhere.\n",
512 ticket_id);
513 word = "to";
514
515 } else {
516 offset += snprintf(warning + offset, text_max - offset,
517 "Revoking '%s' can trigger the specified 'loss-policy'(s) relating to '%s'.\n\n",
518 ticket_id, ticket_id);
519
520 offset += snprintf(warning + offset, text_max - offset,
521 "You can check that with:\ncrm_ticket --ticket %s --constraints\n\n",
522 ticket_id);
523
524 offset += snprintf(warning + offset, text_max - offset,
525 "Otherwise before revoking '%s', you may want to make '%s' standby with:\ncrm_ticket --ticket %s --standby\n\n",
526 ticket_id, ticket_id, ticket_id);
527 word = "from";
528 }
529
530 offset += snprintf(warning + offset, text_max - offset,
531 "If you really want to %s '%s' %s this site now, and you know what you are doing,\n",
532 action, ticket_id, word);
533
534 offset += snprintf(warning + offset, text_max - offset,
535 "please specify --force.");
536
537 CRM_LOG_ASSERT(offset > 0);
538 fprintf(stdout, "%s\n", warning);
539
540 free(warning);
541 }
542
543 static bool
544 allow_modification(gchar *ticket_id)
545 {
546 const char *value = NULL;
547 GList *list_iter = NULL;
548
549 if (options.force) {
550 return true;
551 }
552
553 if (g_hash_table_lookup_extended(attr_set, "granted", NULL, (gpointer *) & value)) {
554 if (crm_is_true(value)) {
555 ticket_warning(ticket_id, "grant");
556 return false;
557
558 } else {
559 ticket_warning(ticket_id, "revoke");
560 return false;
561 }
562 }
563
564 for(list_iter = attr_delete; list_iter; list_iter = list_iter->next) {
565 const char *key = (const char *)list_iter->data;
566
567 if (pcmk__str_eq(key, "granted", pcmk__str_casei)) {
568 ticket_warning(ticket_id, "revoke");
569 return false;
570 }
571 }
572
573 return true;
574 }
575
576 static int
577 modify_ticket_state(gchar * ticket_id, cib_t * cib, pe_working_set_t * data_set)
578 {
579 int rc = pcmk_rc_ok;
580 xmlNode *xml_top = NULL;
581 xmlNode *ticket_state_xml = NULL;
582 bool found = false;
583
584 GList *list_iter = NULL;
585 GHashTableIter hash_iter;
586
587 char *key = NULL;
588 char *value = NULL;
589
590 pe_ticket_t *ticket = NULL;
591
592 rc = find_ticket_state(cib, ticket_id, &ticket_state_xml);
593 if (rc == pcmk_rc_ok) {
594 crm_debug("Found a match state for ticket: id=%s", ticket_id);
595 xml_top = ticket_state_xml;
596 found = true;
597
598 } else if (rc != ENXIO) {
599 return rc;
600
601 } else if (g_hash_table_size(attr_set) == 0){
602 return pcmk_rc_ok;
603
604 } else {
605 xmlNode *xml_obj = NULL;
606
607 xml_top = create_xml_node(NULL, XML_CIB_TAG_STATUS);
608 xml_obj = create_xml_node(xml_top, XML_CIB_TAG_TICKETS);
609 ticket_state_xml = create_xml_node(xml_obj, XML_CIB_TAG_TICKET_STATE);
610 crm_xml_add(ticket_state_xml, XML_ATTR_ID, ticket_id);
611 }
612
613 for(list_iter = attr_delete; list_iter; list_iter = list_iter->next) {
614 const char *key = (const char *)list_iter->data;
615 xml_remove_prop(ticket_state_xml, key);
616 }
617
618 ticket = find_ticket(ticket_id, data_set);
619
620 g_hash_table_iter_init(&hash_iter, attr_set);
621 while (g_hash_table_iter_next(&hash_iter, (gpointer *) & key, (gpointer *) & value)) {
622 crm_xml_add(ticket_state_xml, key, value);
623
624 if (pcmk__str_eq(key, "granted", pcmk__str_casei)
625 && (ticket == NULL || ticket->granted == FALSE)
626 && crm_is_true(value)) {
627
628 char *now = pcmk__ttoa(time(NULL));
629
630 crm_xml_add(ticket_state_xml, "last-granted", now);
631 free(now);
632 }
633 }
634
635 if (found && (attr_delete != NULL)) {
636 crm_log_xml_debug(xml_top, "Replace");
637 rc = cib->cmds->replace(cib, XML_CIB_TAG_STATUS, ticket_state_xml, cib_options);
638 rc = pcmk_legacy2rc(rc);
639
640 } else {
641 crm_log_xml_debug(xml_top, "Update");
642 rc = cib->cmds->modify(cib, XML_CIB_TAG_STATUS, xml_top, cib_options);
643 rc = pcmk_legacy2rc(rc);
644 }
645
646 free_xml(xml_top);
647 return rc;
648 }
649
650 static int
651 delete_ticket_state(gchar *ticket_id, cib_t * cib)
652 {
653 xmlNode *ticket_state_xml = NULL;
654
655 int rc = pcmk_rc_ok;
656
657 rc = find_ticket_state(cib, ticket_id, &ticket_state_xml);
658
659 if (rc == ENXIO) {
660 return pcmk_rc_ok;
661
662 } else if (rc != pcmk_rc_ok) {
663 return rc;
664 }
665
666 crm_log_xml_debug(ticket_state_xml, "Delete");
667
668 rc = cib->cmds->remove(cib, XML_CIB_TAG_STATUS, ticket_state_xml, cib_options);
669 rc = pcmk_legacy2rc(rc);
670
671 if (rc == pcmk_rc_ok) {
672 fprintf(stdout, "Cleaned up %s\n", ticket_id);
673 }
674
675 free_xml(ticket_state_xml);
676 return rc;
677 }
678
679 static GOptionContext *
680 build_arg_context(pcmk__common_args_t *args) {
681 GOptionContext *context = NULL;
682
683 const char *description = "Examples:\n\n"
684 "Display the info of tickets:\n\n"
685 "\tcrm_ticket --info\n\n"
686 "Display the detailed info of tickets:\n\n"
687 "\tcrm_ticket --details\n\n"
688 "Display the XML of 'ticketA':\n\n"
689 "\tcrm_ticket --ticket ticketA --query-xml\n\n"
690 "Display the rsc_ticket constraints that apply to 'ticketA':\n\n"
691 "\tcrm_ticket --ticket ticketA --constraints\n\n"
692 "Grant 'ticketA' to this cluster site:\n\n"
693 "\tcrm_ticket --ticket ticketA --grant\n\n"
694 "Revoke 'ticketA' from this cluster site:\n\n"
695 "\tcrm_ticket --ticket ticketA --revoke\n\n"
696 "Make 'ticketA' standby (the cluster site will treat a granted\n"
697 "'ticketA' as 'standby', and the dependent resources will be\n"
698 "stopped or demoted gracefully without triggering loss-policies):\n\n"
699 "\tcrm_ticket --ticket ticketA --standby\n\n"
700 "Activate 'ticketA' from being standby:\n\n"
701 "\tcrm_ticket --ticket ticketA --activate\n\n"
702 "Get the value of the 'granted' attribute for 'ticketA':\n\n"
703 "\tcrm_ticket --ticket ticketA --get-attr granted\n\n"
704 "Set the value of the 'standby' attribute for 'ticketA':\n\n"
705 "\tcrm_ticket --ticket ticketA --set-attr standby --attr-value true\n\n"
706 "Delete the 'granted' attribute for 'ticketA':\n\n"
707 "\tcrm_ticket --ticket ticketA --delete-attr granted\n\n"
708 "Erase the operation history of 'ticketA' at this cluster site,\n"
709 "causing the cluster site to 'forget' the existing ticket state:\n\n"
710 "\tcrm_ticket --ticket ticketA --cleanup\n\n";
711
712 context = pcmk__build_arg_context(args, NULL, NULL, NULL);
713 g_option_context_set_description(context, description);
714
715 pcmk__add_arg_group(context, "queries", "Queries:",
716 "Show queries", query_entries);
717 pcmk__add_arg_group(context, "commands", "Commands:",
718 "Show command options", command_entries);
719 pcmk__add_arg_group(context, "advanced", "Advanced Options:",
720 "Show advanced options", advanced_entries);
721 pcmk__add_arg_group(context, "additional", "Additional Options:",
722 "Show additional options", addl_entries);
723 pcmk__add_arg_group(context, "deprecated", "Deprecated Options:",
724 "Show deprecated options", deprecated_entries);
725
726 return context;
727 }
728
729 int
730 main(int argc, char **argv)
731 {
732 pe_working_set_t *data_set = NULL;
733 xmlNode *cib_xml_copy = NULL;
734
735 cib_t *cib_conn = NULL;
736 crm_exit_t exit_code = CRM_EX_OK;
737 int rc = pcmk_rc_ok;
738
739 pcmk__common_args_t *args = NULL;
740 GOptionContext *context = NULL;
741 gchar **processed_args = NULL;
742
743 attr_set = pcmk__strkey_table(free, free);
744 attr_delete = NULL;
745
746 args = pcmk__new_common_args(SUMMARY);
747 context = build_arg_context(args);
748 processed_args = pcmk__cmdline_preproc(argv, "dintvxCDGS");
749
750 if (!g_option_context_parse_strv(context, &processed_args, &error)) {
751 exit_code = CRM_EX_USAGE;
752 goto done;
753 }
754
755 pcmk__cli_init_logging("crm_ticket", args->verbosity);
756
757 if (args->version) {
758 g_strfreev(processed_args);
759 pcmk__free_arg_context(context);
760
761 pcmk__cli_help('v', CRM_EX_OK);
762 }
763
764 data_set = pe_new_working_set();
765 if (data_set == NULL) {
766 crm_perror(LOG_CRIT, "Could not allocate working set");
767 exit_code = CRM_EX_OSERR;
768 goto done;
769 }
770 pe__set_working_set_flags(data_set, pe_flag_no_counts|pe_flag_no_compat);
771
772 cib_conn = cib_new();
773 if (cib_conn == NULL) {
774 exit_code = CRM_EX_DISCONNECT;
775 g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not connect to the CIB manager");
776 goto done;
777 }
778
779 rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command);
780 rc = pcmk_legacy2rc(rc);
781
782 if (rc != pcmk_rc_ok) {
783 exit_code = pcmk_rc2exitc(rc);
784 g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not connect to the CIB: %s",
785 pcmk_rc_str(rc));
786 goto done;
787 }
788
789 if (options.xml_file != NULL) {
790 cib_xml_copy = filename2xml(options.xml_file);
791
792 } else {
793 rc = cib_conn->cmds->query(cib_conn, NULL, &cib_xml_copy, cib_scope_local | cib_sync_call);
794 rc = pcmk_legacy2rc(rc);
795
796 if (rc != pcmk_rc_ok) {
797 exit_code = pcmk_rc2exitc(rc);
798 g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not get local CIB: %s",
799 pcmk_rc_str(rc));
800 goto done;
801 }
802 }
803
804 if (!cli_config_update(&cib_xml_copy, NULL, FALSE)) {
805 exit_code = CRM_EX_CONFIG;
806 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
807 "Could not update local CIB to latest schema version");
808 goto done;
809 }
810
811 data_set->input = cib_xml_copy;
812 data_set->now = crm_time_new(NULL);
813
814 cluster_status(data_set);
815
816
817
818 pcmk__unpack_constraints(data_set);
819
820 if (options.ticket_cmd == 'l' || options.ticket_cmd == 'L' || options.ticket_cmd == 'w') {
821 bool raw = false;
822 bool details = false;
823
824 if (options.ticket_cmd == 'L') {
825 details = true;
826 } else if (options.ticket_cmd == 'w') {
827 raw = true;
828 }
829
830 if (options.ticket_id) {
831 pe_ticket_t *ticket = find_ticket(options.ticket_id, data_set);
832
833 if (ticket == NULL) {
834 exit_code = CRM_EX_NOSUCH;
835 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
836 "No such ticket '%s'", options.ticket_id);
837 goto done;
838 }
839 print_ticket(ticket, raw, details);
840
841 } else {
842 print_ticket_list(data_set, raw, details);
843 }
844
845 } else if (options.ticket_cmd == 'q') {
846 rc = dump_ticket_xml(cib_conn, options.ticket_id);
847 exit_code = pcmk_rc2exitc(rc);
848
849 if (rc != pcmk_rc_ok) {
850 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
851 "Could not query ticket XML: %s", pcmk_rc_str(rc));
852 }
853
854 } else if (options.ticket_cmd == 'c') {
855 rc = dump_constraints(cib_conn, options.ticket_id);
856 exit_code = pcmk_rc2exitc(rc);
857
858 if (rc != pcmk_rc_ok) {
859 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
860 "Could not show ticket constraints: %s", pcmk_rc_str(rc));
861 }
862
863 } else if (options.ticket_cmd == 'G') {
864 const char *value = NULL;
865
866 if (options.ticket_id == NULL) {
867 exit_code = CRM_EX_NOSUCH;
868 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
869 "Must supply ticket ID with -t");
870 goto done;
871 }
872
873 rc = get_ticket_state_attr(options.ticket_id, options.get_attr_name, &value, data_set);
874 if (rc == pcmk_rc_ok) {
875 fprintf(stdout, "%s\n", value);
876 } else if (rc == ENXIO && options.attr_default) {
877 fprintf(stdout, "%s\n", options.attr_default);
878 rc = pcmk_rc_ok;
879 }
880 exit_code = pcmk_rc2exitc(rc);
881
882 } else if (options.ticket_cmd == 'C') {
883 if (options.ticket_id == NULL) {
884 exit_code = CRM_EX_USAGE;
885 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
886 "Must supply ticket ID with -t");
887 goto done;
888 }
889
890 if (options.force == FALSE) {
891 pe_ticket_t *ticket = NULL;
892
893 ticket = find_ticket(options.ticket_id, data_set);
894 if (ticket == NULL) {
895 exit_code = CRM_EX_NOSUCH;
896 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
897 "No such ticket '%s'", options.ticket_id);
898 goto done;
899 }
900
901 if (ticket->granted) {
902 ticket_warning(options.ticket_id, "revoke");
903 exit_code = CRM_EX_INSUFFICIENT_PRIV;
904 goto done;
905 }
906 }
907
908 rc = delete_ticket_state(options.ticket_id, cib_conn);
909 exit_code = pcmk_rc2exitc(rc);
910
911 if (rc != pcmk_rc_ok) {
912 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
913 "Could not clean up ticket: %s", pcmk_rc_str(rc));
914 }
915
916 } else if (modified) {
917 if (options.ticket_id == NULL) {
918 exit_code = CRM_EX_USAGE;
919 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
920 "Must supply ticket ID with -t");
921 goto done;
922 }
923
924 if (options.attr_value
925 && (pcmk__str_empty(options.attr_name))) {
926 exit_code = CRM_EX_USAGE;
927 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
928 "Must supply attribute name with -S for -v %s", options.attr_value);
929 goto done;
930 }
931
932 if (options.attr_name
933 && (pcmk__str_empty(options.attr_value))) {
934 exit_code = CRM_EX_USAGE;
935 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
936 "Must supply attribute value with -v for -S %s", options.attr_value);
937 goto done;
938 }
939
940 if (!allow_modification(options.ticket_id)) {
941 exit_code = CRM_EX_INSUFFICIENT_PRIV;
942 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
943 "Ticket modification not allowed");
944 goto done;
945 }
946
947 rc = modify_ticket_state(options.ticket_id, cib_conn, data_set);
948 exit_code = pcmk_rc2exitc(rc);
949
950 if (rc != pcmk_rc_ok) {
951 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
952 "Could not modify ticket: %s", pcmk_rc_str(rc));
953 }
954
955 } else if (options.ticket_cmd == 'S') {
956
957
958
959
960 if (pcmk__str_empty(options.attr_name)) {
961
962 exit_code = CRM_EX_USAGE;
963 g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Must supply a command");
964 goto done;
965 }
966
967 if (options.ticket_id == NULL) {
968 exit_code = CRM_EX_USAGE;
969 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
970 "Must supply ticket ID with -t");
971 goto done;
972 }
973
974 if (pcmk__str_empty(options.attr_value)) {
975 exit_code = CRM_EX_USAGE;
976 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
977 "Must supply value with -v for -S %s", options.attr_name);
978 goto done;
979 }
980
981 } else {
982 exit_code = CRM_EX_USAGE;
983 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
984 "Unknown command: %c", options.ticket_cmd);
985 }
986
987 done:
988 if (attr_set) {
989 g_hash_table_destroy(attr_set);
990 }
991 attr_set = NULL;
992
993 if (attr_delete) {
994 g_list_free_full(attr_delete, free);
995 }
996 attr_delete = NULL;
997
998 pe_free_working_set(data_set);
999 data_set = NULL;
1000
1001 cib__clean_up_connection(&cib_conn);
1002
1003 if (rc == pcmk_rc_no_quorum) {
1004 g_set_error(&error, PCMK__RC_ERROR, rc, "Use --force to ignore quorum");
1005 }
1006
1007 g_strfreev(processed_args);
1008 pcmk__free_arg_context(context);
1009 g_free(options.attr_default);
1010 g_free(options.attr_id);
1011 free(options.attr_name);
1012 free(options.attr_value);
1013 free(options.get_attr_name);
1014 g_free(options.set_name);
1015 g_free(options.ticket_id);
1016 g_free(options.xml_file);
1017
1018 pcmk__output_and_clear_error(error, NULL);
1019
1020 crm_exit(exit_code);
1021 }