root/tools/crmadmin.c

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

DEFINITIONS

This source file includes following definitions.
  1. command_cb
  2. quit_main_loop
  3. controller_event_cb
  4. pacemakerd_event_cb
  5. list_nodes
  6. build_arg_context
  7. main
  8. do_work
  9. admin_message_timeout
  10. do_find_node_list

   1 /*
   2  * Copyright 2004-2020 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 <stdio.h>
  13 #include <stdbool.h>
  14 #include <stdlib.h>             // atoi()
  15 
  16 #include <glib.h>               // gboolean, GMainLoop, etc.
  17 #include <libxml/tree.h>        // xmlNode
  18 
  19 #include <crm/crm.h>
  20 #include <crm/cib.h>
  21 #include <crm/msg_xml.h>
  22 #include <crm/common/cmdline_internal.h>
  23 #include <crm/common/xml.h>
  24 #include <crm/common/iso8601.h>
  25 #include <crm/common/ipc_controld.h>
  26 #include <crm/common/ipc_pacemakerd.h>
  27 #include <crm/common/mainloop.h>
  28 
  29 #define SUMMARY "query and manage the Pacemaker controller"
  30 
  31 #define DEFAULT_MESSAGE_TIMEOUT_MS 30000
  32 
  33 static guint message_timer_id = 0;
  34 static guint message_timeout_ms = DEFAULT_MESSAGE_TIMEOUT_MS;
  35 static GMainLoop *mainloop = NULL;
  36 
  37 bool need_controld_api = true;
  38 bool need_pacemakerd_api = false;
  39 
  40 bool do_work(pcmk_ipc_api_t *api);
  41 void do_find_node_list(xmlNode *xml_node);
  42 static char *ipc_name = NULL;
  43 
  44 gboolean admin_message_timeout(gpointer data);
  45 
  46 static enum {
  47     cmd_none,
  48     cmd_shutdown,
  49     cmd_health,
  50     cmd_elect_dc,
  51     cmd_whois_dc,
  52     cmd_list_nodes,
  53     cmd_pacemakerd_health,
  54 } command = cmd_none;
  55 
  56 static gboolean BE_VERBOSE = FALSE;
  57 static gboolean BASH_EXPORT = FALSE;
  58 static gboolean BE_SILENT = FALSE;
  59 static char *dest_node = NULL;
  60 static crm_exit_t exit_code = CRM_EX_OK;
  61 
  62 
  63 struct {
  64     gboolean quiet;
  65     gboolean health;
  66     gint timeout;
  67 } options;
  68 
  69 gboolean command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
  70 
  71 static GOptionEntry command_options[] = {
  72     { "status", 'S', 0, G_OPTION_ARG_CALLBACK, command_cb,
  73       "Display the status of the specified node."
  74       "\n                          Result is state of node's internal finite state"
  75       "\n                          machine, which can be useful for debugging",
  76       NULL
  77     },
  78     { "pacemakerd", 'P', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
  79       "Display the status of local pacemakerd."
  80       "\n                          Result is the state of the sub-daemons watched"
  81       "\n                          by pacemakerd.",
  82       NULL
  83     },
  84     { "dc_lookup", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
  85       "Display the uname of the node co-ordinating the cluster."
  86       "\n                          This is an internal detail rarely useful to"
  87       "\n                          administrators except when deciding on which"
  88       "\n                          node to examine the logs.",
  89       NULL
  90     },
  91     { "nodes", 'N', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
  92       "Display the uname of all member nodes",
  93       NULL
  94     },
  95     { "election", 'E', G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
  96       "(Advanced) Start an election for the cluster co-ordinator",
  97       NULL
  98     },
  99     { "kill", 'K', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, command_cb,
 100       "(Advanced) Stop controller (not rest of cluster stack) on specified node",
 101       NULL
 102     },
 103     { "health", 'H', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.health,
 104       NULL,
 105       NULL
 106     },
 107 
 108     { NULL }
 109 };
 110 
 111 static GOptionEntry additional_options[] = {
 112     { "timeout", 't', 0, G_OPTION_ARG_INT, &options.timeout,
 113       "Time (in milliseconds) to wait before declaring the"
 114       "\n                          operation failed",
 115       NULL
 116     },
 117     { "bash-export", 'B', 0, G_OPTION_ARG_NONE, &BASH_EXPORT,
 118       "Display nodes as shell commands of the form 'export uname=uuid'"
 119       "\n                          (valid with -N/--nodes)",
 120     },
 121     { "ipc-name", 'i', 0, G_OPTION_ARG_STRING, &ipc_name,
 122       "Name to use for ipc instead of 'crmadmin' (with -P/--pacemakerd).",
 123       NULL
 124     },
 125 
 126     { NULL }
 127 };
 128 
 129 gboolean
 130 command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error)
     /* [previous][next][first][last][top][bottom][index][help] */
 131 {
 132     if (!strcmp(option_name, "--status") || !strcmp(option_name, "-S")) {
 133         command = cmd_health;
 134         crm_trace("Option %c => %s", 'S', optarg);
 135     }
 136 
 137     if (!strcmp(option_name, "--pacemakerd") || !strcmp(option_name, "-P")) {
 138         command = cmd_pacemakerd_health;
 139         need_pacemakerd_api = true;
 140         need_controld_api = false;
 141     }
 142 
 143     if (!strcmp(option_name, "--dc_lookup") || !strcmp(option_name, "-D")) {
 144         command = cmd_whois_dc;
 145     }
 146 
 147     if (!strcmp(option_name, "--nodes") || !strcmp(option_name, "-N")) {
 148         command = cmd_list_nodes;
 149         need_controld_api = false;
 150     }
 151 
 152     if (!strcmp(option_name, "--election") || !strcmp(option_name, "-E")) {
 153         command = cmd_elect_dc;
 154     }
 155 
 156     if (!strcmp(option_name, "--kill") || !strcmp(option_name, "-K")) {
 157         command = cmd_shutdown;
 158         crm_trace("Option %c => %s", 'K', optarg);
 159     }
 160 
 161     if (optarg) {
 162         if (dest_node != NULL) {
 163             free(dest_node);
 164         }
 165         dest_node = strdup(optarg);
 166     }
 167 
 168     return TRUE;
 169 }
 170 
 171 static void
 172 quit_main_loop(crm_exit_t ec)
     /* [previous][next][first][last][top][bottom][index][help] */
 173 {
 174     exit_code = ec;
 175     if (mainloop != NULL) {
 176         GMainLoop *mloop = mainloop;
 177 
 178         mainloop = NULL; // Don't re-enter this block
 179         pcmk_quit_main_loop(mloop, 10);
 180         g_main_loop_unref(mloop);
 181     }
 182 }
 183 
 184 static void
 185 controller_event_cb(pcmk_ipc_api_t *controld_api,
     /* [previous][next][first][last][top][bottom][index][help] */
 186                     enum pcmk_ipc_event event_type, crm_exit_t status,
 187                     void *event_data, void *user_data)
 188 {
 189     pcmk_controld_api_reply_t *reply = event_data;
 190 
 191     switch (event_type) {
 192         case pcmk_ipc_event_disconnect:
 193             if (exit_code == CRM_EX_DISCONNECT) { // Unexpected
 194                 fprintf(stderr, "error: Lost connection to controller\n");
 195             }
 196             goto done;
 197             break;
 198 
 199         case pcmk_ipc_event_reply:
 200             break;
 201 
 202         default:
 203             return;
 204     }
 205 
 206     if (message_timer_id != 0) {
 207         g_source_remove(message_timer_id);
 208         message_timer_id = 0;
 209     }
 210 
 211     if (status != CRM_EX_OK) {
 212         fprintf(stderr, "error: Bad reply from controller: %s",
 213                 crm_exit_str(status));
 214         exit_code = status;
 215         goto done;
 216     }
 217 
 218     if (reply->reply_type != pcmk_controld_reply_ping) {
 219         fprintf(stderr, "error: Unknown reply type %d from controller\n",
 220                 reply->reply_type);
 221         goto done;
 222     }
 223 
 224     // Parse desired information from reply
 225     switch (command) {
 226         case cmd_health:
 227             printf("Status of %s@%s: %s (%s)\n",
 228                    reply->data.ping.sys_from,
 229                    reply->host_from,
 230                    reply->data.ping.fsa_state,
 231                    reply->data.ping.result);
 232             if (BE_SILENT && (reply->data.ping.fsa_state != NULL)) {
 233                 fprintf(stderr, "%s\n", reply->data.ping.fsa_state);
 234             }
 235             exit_code = CRM_EX_OK;
 236             break;
 237 
 238         case cmd_whois_dc:
 239             printf("Designated Controller is: %s\n", reply->host_from);
 240             if (BE_SILENT && (reply->host_from != NULL)) {
 241                 fprintf(stderr, "%s\n", reply->host_from);
 242             }
 243             exit_code = CRM_EX_OK;
 244             break;
 245 
 246         default: // Not really possible here
 247             exit_code = CRM_EX_SOFTWARE;
 248             break;
 249     }
 250 
 251 done:
 252     pcmk_disconnect_ipc(controld_api);
 253     quit_main_loop(exit_code);
 254 }
 255 
 256 static void
 257 pacemakerd_event_cb(pcmk_ipc_api_t *pacemakerd_api,
     /* [previous][next][first][last][top][bottom][index][help] */
 258                     enum pcmk_ipc_event event_type, crm_exit_t status,
 259                     void *event_data, void *user_data)
 260 {
 261     pcmk_pacemakerd_api_reply_t *reply = event_data;
 262 
 263     switch (event_type) {
 264         case pcmk_ipc_event_disconnect:
 265             if (exit_code == CRM_EX_DISCONNECT) { // Unexpected
 266                 fprintf(stderr, "error: Lost connection to pacemakerd\n");
 267             }
 268             goto done;
 269             break;
 270 
 271         case pcmk_ipc_event_reply:
 272             break;
 273 
 274         default:
 275             return;
 276     }
 277 
 278     if (message_timer_id != 0) {
 279         g_source_remove(message_timer_id);
 280         message_timer_id = 0;
 281     }
 282 
 283     if (status != CRM_EX_OK) {
 284         fprintf(stderr, "error: Bad reply from pacemakerd: %s",
 285                 crm_exit_str(status));
 286         exit_code = status;
 287         goto done;
 288     }
 289 
 290     if (reply->reply_type != pcmk_pacemakerd_reply_ping) {
 291         fprintf(stderr, "error: Unknown reply type %d from pacemakerd\n",
 292                 reply->reply_type);
 293         goto done;
 294     }
 295 
 296     // Parse desired information from reply
 297     switch (command) {
 298         case cmd_pacemakerd_health:
 299             {
 300                 crm_time_t *crm_when = crm_time_new(NULL);
 301                 char *pinged_buf = NULL;
 302 
 303                 crm_time_set_timet(crm_when, &reply->data.ping.last_good);
 304                 pinged_buf = crm_time_as_string(crm_when,
 305                     crm_time_log_date | crm_time_log_timeofday |
 306                         crm_time_log_with_timezone);
 307 
 308                 printf("Status of %s: '%s' %s %s\n",
 309                     reply->data.ping.sys_from,
 310                     (reply->data.ping.status == pcmk_rc_ok)?
 311                         pcmk_pacemakerd_api_daemon_state_enum2text(
 312                             reply->data.ping.state):"query failed",
 313                     (reply->data.ping.status == pcmk_rc_ok)?"last updated":"",
 314                     (reply->data.ping.status == pcmk_rc_ok)?pinged_buf:"");
 315                 if (BE_SILENT &&
 316                     (reply->data.ping.state != pcmk_pacemakerd_state_invalid)) {
 317                     fprintf(stderr, "%s\n",
 318                         (reply->data.ping.status == pcmk_rc_ok)?
 319                         pcmk_pacemakerd_api_daemon_state_enum2text(
 320                             reply->data.ping.state):
 321                         "query failed");
 322                 }
 323                 exit_code = CRM_EX_OK;
 324                 free(pinged_buf);
 325             }
 326             break;
 327 
 328         default: // Not really possible here
 329             exit_code = CRM_EX_SOFTWARE;
 330             break;
 331     }
 332 
 333 done:
 334     pcmk_disconnect_ipc(pacemakerd_api);
 335     quit_main_loop(exit_code);
 336 }
 337 
 338 // \return Standard Pacemaker return code
 339 static int
 340 list_nodes()
     /* [previous][next][first][last][top][bottom][index][help] */
 341 {
 342     cib_t *the_cib = cib_new();
 343     xmlNode *output = NULL;
 344     int rc;
 345 
 346     if (the_cib == NULL) {
 347         return ENOMEM;
 348     }
 349     rc = the_cib->cmds->signon(the_cib, crm_system_name, cib_command);
 350     if (rc != pcmk_ok) {
 351         return pcmk_legacy2rc(rc);
 352     }
 353 
 354     rc = the_cib->cmds->query(the_cib, NULL, &output,
 355                               cib_scope_local | cib_sync_call);
 356     if (rc == pcmk_ok) {
 357         do_find_node_list(output);
 358         free_xml(output);
 359     }
 360     the_cib->cmds->signoff(the_cib);
 361     return pcmk_legacy2rc(rc);
 362 }
 363 
 364 static GOptionContext *
 365 build_arg_context(pcmk__common_args_t *args) {
     /* [previous][next][first][last][top][bottom][index][help] */
 366     GOptionContext *context = NULL;
 367 
 368     const char *description = "Report bugs to users@clusterlabs.org";
 369 
 370     GOptionEntry extra_prog_entries[] = {
 371         { "quiet", 'q', 0, G_OPTION_ARG_NONE, &options.quiet,
 372           "Display only the essential query information",
 373           NULL },
 374 
 375         { NULL }
 376     };
 377 
 378     context = pcmk__build_arg_context(args, NULL, NULL, NULL);
 379     g_option_context_set_description(context, description);
 380 
 381     /* Add the -q option, which cannot be part of the globally supported options
 382      * because some tools use that flag for something else.
 383      */
 384     pcmk__add_main_args(context, extra_prog_entries);
 385 
 386     pcmk__add_arg_group(context, "command", "Commands:",
 387                         "Show command options", command_options);
 388     pcmk__add_arg_group(context, "additional", "Additional Options:",
 389                         "Show additional options", additional_options);
 390     return context;
 391 }
 392 
 393 int
 394 main(int argc, char **argv)
     /* [previous][next][first][last][top][bottom][index][help] */
 395 {
 396     int argerr = 0;
 397     int rc;
 398     pcmk_ipc_api_t *controld_api = NULL;
 399     pcmk_ipc_api_t *pacemakerd_api = NULL;
 400 
 401     pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
 402 
 403     GError *error = NULL;
 404     GOptionContext *context = NULL;
 405     gchar **processed_args = NULL;
 406 
 407     context = build_arg_context(args);
 408 
 409     crm_log_cli_init("crmadmin");
 410 
 411     processed_args = pcmk__cmdline_preproc(argv, "itBDEHKNPS");
 412 
 413     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
 414         fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
 415         exit_code = CRM_EX_USAGE;
 416         goto done;
 417     }
 418 
 419     for (int i = 0; i < args->verbosity; i++) {
 420         BE_VERBOSE = TRUE;
 421         crm_bump_log_level(argc, argv);
 422     }
 423 
 424     if (args->version) {
 425         /* FIXME:  When crmadmin is converted to use formatted output, this can go. */
 426         pcmk__cli_help('v', CRM_EX_USAGE);
 427     }
 428 
 429     if (options.timeout) {
 430         message_timeout_ms = (guint) options.timeout;
 431         if (message_timeout_ms < 1) {
 432             message_timeout_ms = DEFAULT_MESSAGE_TIMEOUT_MS;
 433         }
 434     }
 435 
 436     if (options.quiet) {
 437         BE_SILENT = TRUE;
 438     }
 439 
 440     if (options.health) {
 441         fprintf(stderr, "Cluster-wide health option not supported\n");
 442         ++argerr;
 443     }
 444 
 445     if (optind > argc) {
 446         ++argerr;
 447     }
 448 
 449     if (command == cmd_none) {
 450         fprintf(stderr, "error: Must specify a command option\n\n");
 451         ++argerr;
 452     }
 453 
 454     if (argerr) {
 455         char *help = g_option_context_get_help(context, TRUE, NULL);
 456 
 457         fprintf(stderr, "%s", help);
 458         g_free(help);
 459         exit_code = CRM_EX_USAGE;
 460         goto done;
 461     }
 462 
 463     // Connect to the controller if needed
 464     if (need_controld_api) {
 465         rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld);
 466         if (controld_api == NULL) {
 467             fprintf(stderr, "error: Could not connect to controller: %s\n",
 468                     pcmk_rc_str(rc));
 469             exit_code = pcmk_rc2exitc(rc);
 470             goto done;
 471         }
 472         pcmk_register_ipc_callback(controld_api, controller_event_cb, NULL);
 473         rc = pcmk_connect_ipc(controld_api, pcmk_ipc_dispatch_main);
 474         if (rc != pcmk_rc_ok) {
 475             fprintf(stderr, "error: Could not connect to controller: %s\n",
 476                     pcmk_rc_str(rc));
 477             exit_code = pcmk_rc2exitc(rc);
 478             goto done;
 479         }
 480     }
 481 
 482     // Connect to pacemakerd if needed
 483     if (need_pacemakerd_api) {
 484         rc = pcmk_new_ipc_api(&pacemakerd_api, pcmk_ipc_pacemakerd);
 485         if (pacemakerd_api == NULL) {
 486             fprintf(stderr, "error: Could not connect to pacemakerd: %s\n",
 487                     pcmk_rc_str(rc));
 488             exit_code = pcmk_rc2exitc(rc);
 489             goto done;
 490         }
 491         pcmk_register_ipc_callback(pacemakerd_api, pacemakerd_event_cb, NULL);
 492         rc = pcmk_connect_ipc(pacemakerd_api, pcmk_ipc_dispatch_main);
 493         if (rc != pcmk_rc_ok) {
 494             fprintf(stderr, "error: Could not connect to pacemakerd: %s\n",
 495                     pcmk_rc_str(rc));
 496             exit_code = pcmk_rc2exitc(rc);
 497             goto done;
 498         }
 499     }
 500 
 501     if (do_work(controld_api?controld_api:pacemakerd_api)) {
 502         // A reply is needed from controller, so run main loop to get it
 503         exit_code = CRM_EX_DISCONNECT; // For unexpected disconnects
 504         mainloop = g_main_loop_new(NULL, FALSE);
 505         message_timer_id = g_timeout_add(message_timeout_ms,
 506                                          admin_message_timeout, NULL);
 507         g_main_loop_run(mainloop);
 508     }
 509 
 510 done:
 511 
 512     if (controld_api != NULL) {
 513         pcmk_ipc_api_t *capi = controld_api;
 514         controld_api = NULL; // Ensure we can't free this twice
 515         pcmk_free_ipc_api(capi);
 516     }
 517 
 518     if (pacemakerd_api != NULL) {
 519         pcmk_ipc_api_t *capi = pacemakerd_api;
 520         pacemakerd_api = NULL; // Ensure we can't free this twice
 521         pcmk_free_ipc_api(capi);
 522     }
 523 
 524     if (mainloop != NULL) {
 525         g_main_loop_unref(mainloop);
 526         mainloop = NULL;
 527     }
 528     g_strfreev(processed_args);
 529     g_clear_error(&error);
 530     pcmk__free_arg_context(context);
 531     return crm_exit(exit_code);
 532 
 533 }
 534 
 535 // \return True if reply from controller is needed
 536 bool
 537 do_work(pcmk_ipc_api_t *api)
     /* [previous][next][first][last][top][bottom][index][help] */
 538 {
 539     bool need_reply = false;
 540     int rc = pcmk_rc_ok;
 541 
 542     switch (command) {
 543         case cmd_shutdown:
 544             rc = pcmk_controld_api_shutdown(api, dest_node);
 545             break;
 546 
 547         case cmd_health:    // dest_node != NULL
 548         case cmd_whois_dc:  // dest_node == NULL
 549             rc = pcmk_controld_api_ping(api, dest_node);
 550             need_reply = true;
 551             break;
 552 
 553         case cmd_elect_dc:
 554             rc = pcmk_controld_api_start_election(api);
 555             break;
 556 
 557         case cmd_list_nodes:
 558             rc = list_nodes();
 559             break;
 560 
 561         case cmd_pacemakerd_health:
 562             rc = pcmk_pacemakerd_api_ping(api, ipc_name);
 563             need_reply = true;
 564             break;
 565 
 566         case cmd_none: // not actually possible here
 567             break;
 568     }
 569     if (rc != pcmk_rc_ok) {
 570         fprintf(stderr, "error: Command failed: %s", pcmk_rc_str(rc));
 571         exit_code = pcmk_rc2exitc(rc);
 572     }
 573     return need_reply;
 574 }
 575 
 576 gboolean
 577 admin_message_timeout(gpointer data)
     /* [previous][next][first][last][top][bottom][index][help] */
 578 {
 579     fprintf(stderr,
 580             "error: No reply received from controller before timeout (%dms)\n",
 581             message_timeout_ms);
 582     message_timer_id = 0;
 583     quit_main_loop(CRM_EX_TIMEOUT);
 584     return FALSE; // Tells glib to remove source
 585 }
 586 
 587 void
 588 do_find_node_list(xmlNode * xml_node)
     /* [previous][next][first][last][top][bottom][index][help] */
 589 {
 590     int found = 0;
 591     xmlNode *node = NULL;
 592     xmlNode *nodes = get_object_root(XML_CIB_TAG_NODES, xml_node);
 593 
 594     for (node = first_named_child(nodes, XML_CIB_TAG_NODE); node != NULL;
 595          node = crm_next_same_xml(node)) {
 596 
 597         if (BASH_EXPORT) {
 598             printf("export %s=%s\n",
 599                    crm_element_value(node, XML_ATTR_UNAME),
 600                    crm_element_value(node, XML_ATTR_ID));
 601         } else {
 602             const char *node_type = crm_element_value(node, XML_ATTR_TYPE);
 603 
 604             if (node_type == NULL) {
 605                 node_type = "member";
 606             }
 607             printf("%s node: %s (%s)\n", node_type,
 608                    crm_element_value(node, XML_ATTR_UNAME),
 609                    crm_element_value(node, XML_ATTR_ID));
 610         }
 611         found++;
 612     }
 613     // @TODO List Pacemaker Remote nodes that don't have a <node> entry
 614 
 615     if (found == 0) {
 616         printf("No nodes configured\n");
 617     }
 618 }

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