root/tools/crm_ticket.c

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

DEFINITIONS

This source file includes following definitions.
  1. attr_value_cb
  2. command_cb
  3. delete_attr_cb
  4. get_attr_cb
  5. grant_standby_cb
  6. set_attr_cb
  7. find_ticket
  8. print_date
  9. print_ticket
  10. print_ticket_list
  11. find_ticket_state
  12. find_ticket_constraints
  13. dump_ticket_xml
  14. dump_constraints
  15. get_ticket_state_attr
  16. ticket_warning
  17. allow_modification
  18. modify_ticket_state
  19. delete_ticket_state
  20. build_arg_context
  21. main

   1 /*
   2  * Copyright 2012-2022 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 <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) {
     /* [previous][next][first][last][top][bottom][index][help] */
  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) {
     /* [previous][next][first][last][top][bottom][index][help] */
  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) {
     /* [previous][next][first][last][top][bottom][index][help] */
 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) {
     /* [previous][next][first][last][top][bottom][index][help] */
 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) {
     /* [previous][next][first][last][top][bottom][index][help] */
 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) {
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 258 {
 259     return g_hash_table_lookup(data_set->tickets, ticket_id);
 260 }
 261 
 262 static void
 263 print_date(time_t time)
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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,
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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) {
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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         /* FIXME:  When crm_ticket is converted to use formatted output, this can go. */
 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     /* For recording the tickets that are referenced in rsc_ticket constraints
 817      * but have never been granted yet. */
 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         /* Correct usage was handled in the "if (modified)" block above, so
 957          * this is just for reporting usage errors
 958          */
 959 
 960         if (pcmk__str_empty(options.attr_name)) {
 961             // We only get here if ticket_cmd was left as default
 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 }

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