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

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