root/tools/crm_node.c

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

DEFINITIONS

This source file includes following definitions.
  1. command_cb
  2. name_cb
  3. remove_cb
  4. sort_node
  5. controller_event_cb
  6. run_controller_mainloop
  7. print_node_name
  8. cib_remove_node
  9. controller_remove_node
  10. tools_remove_node_cache
  11. remove_node
  12. build_arg_context
  13. main

   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 <stdlib.h>
  14 #include <errno.h>
  15 #include <sys/types.h>
  16 
  17 #include <crm/crm.h>
  18 #include <crm/common/cmdline_internal.h>
  19 #include <crm/common/mainloop.h>
  20 #include <crm/msg_xml.h>
  21 #include <crm/cib.h>
  22 #include <crm/common/ipc_controld.h>
  23 #include <crm/common/attrd_internal.h>
  24 
  25 #define SUMMARY "crm_node - Tool for displaying low-level node information"
  26 
  27 struct {
  28     gboolean corosync;
  29     gboolean dangerous_cmd;
  30     gboolean force_flag;
  31     char command;
  32     int nodeid;
  33     char *target_uname;
  34 } options = {
  35     .command = '\0',
  36     .force_flag = FALSE
  37 };
  38 
  39 gboolean command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
  40 gboolean name_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
  41 gboolean remove_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
  42 
  43 static GMainLoop *mainloop = NULL;
  44 static crm_exit_t exit_code = CRM_EX_OK;
  45 
  46 #define INDENT "                           "
  47 
  48 static GOptionEntry command_entries[] = {
  49     { "cluster-id", 'i', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
  50       "Display this node's cluster id",
  51       NULL },
  52     { "list", 'l', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
  53       "Display all known members (past and present) of this cluster",
  54       NULL },
  55     { "name", 'n', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
  56       "Display the name used by the cluster for this node",
  57       NULL },
  58     { "partition", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
  59       "Display the members of this partition",
  60       NULL },
  61     { "quorum", 'q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
  62       "Display a 1 if our partition has quorum, 0 if not",
  63       NULL },
  64     { "name-for-id", 'N', 0, G_OPTION_ARG_CALLBACK, name_cb,
  65       "Display the name used by the cluster for the node with the specified ID",
  66       "ID" },
  67     { "remove", 'R', 0, G_OPTION_ARG_CALLBACK, remove_cb,
  68       "(Advanced) Remove the (stopped) node with the specified name from Pacemaker's\n"
  69       INDENT "configuration and caches (the node must already have been removed from\n"
  70       INDENT "the underlying cluster stack configuration",
  71       "NAME" },
  72 
  73     { NULL }
  74 };
  75 
  76 static GOptionEntry addl_entries[] = {
  77     { "force", 'f', 0, G_OPTION_ARG_NONE, &options.force_flag,
  78       NULL,
  79       NULL },
  80 #if SUPPORT_COROSYNC
  81     /* Unused and deprecated */
  82     { "corosync", 'C', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.corosync,
  83       NULL,
  84       NULL },
  85 #endif
  86 
  87     // @TODO add timeout option for when IPC replies are needed
  88 
  89     { NULL }
  90 };
  91 
  92 gboolean
  93 command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     /* [previous][next][first][last][top][bottom][index][help] */
  94     if (pcmk__str_eq("-i", option_name, pcmk__str_casei) || pcmk__str_eq("--cluster-id", option_name, pcmk__str_casei)) {
  95         options.command = 'i';
  96     } else if (pcmk__str_eq("-l", option_name, pcmk__str_casei) || pcmk__str_eq("--list", option_name, pcmk__str_casei)) {
  97         options.command = 'l';
  98     } else if (pcmk__str_eq("-n", option_name, pcmk__str_casei) || pcmk__str_eq("--name", option_name, pcmk__str_casei)) {
  99         options.command = 'n';
 100     } else if (pcmk__str_eq("-p", option_name, pcmk__str_casei) || pcmk__str_eq("--partition", option_name, pcmk__str_casei)) {
 101         options.command = 'p';
 102     } else if (pcmk__str_eq("-q", option_name, pcmk__str_casei) || pcmk__str_eq("--quorum", option_name, pcmk__str_casei)) {
 103         options.command = 'q';
 104     } else {
 105         g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_INVALID_PARAM, "Unknown param passed to command_cb: %s\n", option_name);
 106         return FALSE;
 107     }
 108 
 109     return TRUE;
 110 }
 111 
 112 gboolean
 113 name_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     /* [previous][next][first][last][top][bottom][index][help] */
 114     options.command = 'N';
 115     options.nodeid = crm_parse_int(optarg, NULL);
 116     return TRUE;
 117 }
 118 
 119 gboolean
 120 remove_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     /* [previous][next][first][last][top][bottom][index][help] */
 121     if (optarg == NULL) {
 122         crm_err("-R option requires an argument");
 123         g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_INVALID_PARAM, "-R option requires an argument");
 124         return FALSE;
 125     }
 126 
 127     options.command = 'R';
 128     options.dangerous_cmd = TRUE;
 129     options.target_uname = strdup(optarg);
 130     return TRUE;
 131 }
 132 
 133 static gint
 134 sort_node(gconstpointer a, gconstpointer b)
     /* [previous][next][first][last][top][bottom][index][help] */
 135 {
 136     const pcmk_controld_api_node_t *node_a = a;
 137     const pcmk_controld_api_node_t *node_b = b;
 138 
 139     return pcmk_numeric_strcasecmp((node_a->uname? node_a->uname : ""),
 140                                    (node_b->uname? node_b->uname : ""));
 141 }
 142 
 143 static void
 144 controller_event_cb(pcmk_ipc_api_t *controld_api,
     /* [previous][next][first][last][top][bottom][index][help] */
 145                     enum pcmk_ipc_event event_type, crm_exit_t status,
 146                     void *event_data, void *user_data)
 147 {
 148     pcmk_controld_api_reply_t *reply = event_data;
 149 
 150     switch (event_type) {
 151         case pcmk_ipc_event_disconnect:
 152             if (exit_code == CRM_EX_DISCONNECT) { // Unexpected
 153                 fprintf(stderr, "error: Lost connection to controller\n");
 154             }
 155             goto done;
 156             break;
 157 
 158         case pcmk_ipc_event_reply:
 159             break;
 160 
 161         default:
 162             return;
 163     }
 164 
 165     if (status != CRM_EX_OK) {
 166         fprintf(stderr, "error: Bad reply from controller: %s\n",
 167                 crm_exit_str(status));
 168         goto done;
 169     }
 170 
 171     // Parse desired info from reply and display to user
 172     switch (options.command) {
 173         case 'i':
 174             if (reply->reply_type != pcmk_controld_reply_info) {
 175                 fprintf(stderr,
 176                         "error: Unknown reply type %d from controller\n",
 177                         reply->reply_type);
 178                 goto done;
 179             }
 180             if (reply->data.node_info.id == 0) {
 181                 fprintf(stderr,
 182                         "error: Controller reply did not contain node ID\n");
 183                 exit_code = CRM_EX_PROTOCOL;
 184                 goto done;
 185             }
 186             printf("%d\n", reply->data.node_info.id);
 187             break;
 188 
 189         case 'n':
 190         case 'N':
 191             if (reply->reply_type != pcmk_controld_reply_info) {
 192                 fprintf(stderr,
 193                         "error: Unknown reply type %d from controller\n",
 194                         reply->reply_type);
 195                 goto done;
 196             }
 197             if (reply->data.node_info.uname == NULL) {
 198                 fprintf(stderr, "Node is not known to cluster\n");
 199                 exit_code = CRM_EX_NOHOST;
 200                 goto done;
 201             }
 202             printf("%s\n", reply->data.node_info.uname);
 203             break;
 204 
 205         case 'q':
 206             if (reply->reply_type != pcmk_controld_reply_info) {
 207                 fprintf(stderr,
 208                         "error: Unknown reply type %d from controller\n",
 209                         reply->reply_type);
 210                 goto done;
 211             }
 212             printf("%d\n", reply->data.node_info.have_quorum);
 213             if (!(reply->data.node_info.have_quorum)) {
 214                 exit_code = CRM_EX_QUORUM;
 215                 goto done;
 216             }
 217             break;
 218 
 219         case 'l':
 220         case 'p':
 221             if (reply->reply_type != pcmk_controld_reply_nodes) {
 222                 fprintf(stderr,
 223                         "error: Unknown reply type %d from controller\n",
 224                         reply->reply_type);
 225                 goto done;
 226             }
 227             reply->data.nodes = g_list_sort(reply->data.nodes, sort_node);
 228             for (GList *node_iter = reply->data.nodes;
 229                  node_iter != NULL; node_iter = node_iter->next) {
 230 
 231                 pcmk_controld_api_node_t *node = node_iter->data;
 232                 const char *uname = (node->uname? node->uname : "");
 233                 const char *state = (node->state? node->state : "");
 234 
 235                 if (options.command == 'l') {
 236                     printf("%lu %s %s\n",
 237                            (unsigned long) node->id, uname, state);
 238 
 239                 // i.e. CRM_NODE_MEMBER, but we don't want to include cluster.h
 240                 } else if (!strcmp(state, "member")) {
 241                     printf("%s ", uname);
 242                 }
 243             }
 244             if (options.command == 'p') {
 245                 printf("\n");
 246             }
 247             break;
 248 
 249         default:
 250             fprintf(stderr, "internal error: Controller reply not expected\n");
 251             exit_code = CRM_EX_SOFTWARE;
 252             goto done;
 253     }
 254 
 255     // Success
 256     exit_code = CRM_EX_OK;
 257 done:
 258     pcmk_disconnect_ipc(controld_api);
 259     pcmk_quit_main_loop(mainloop, 10);
 260 }
 261 
 262 static void
 263 run_controller_mainloop(uint32_t nodeid, bool list_nodes)
     /* [previous][next][first][last][top][bottom][index][help] */
 264 {
 265     pcmk_ipc_api_t *controld_api = NULL;
 266     int rc;
 267 
 268     // Set disconnect exit code to handle unexpected disconnects
 269     exit_code = CRM_EX_DISCONNECT;
 270 
 271     // Create controller IPC object
 272     rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld);
 273     if (rc != pcmk_rc_ok) {
 274         fprintf(stderr, "error: Could not connect to controller: %s\n",
 275                 pcmk_rc_str(rc));
 276         return;
 277     }
 278     pcmk_register_ipc_callback(controld_api, controller_event_cb, NULL);
 279 
 280     // Connect to controller
 281     rc = pcmk_connect_ipc(controld_api, pcmk_ipc_dispatch_main);
 282     if (rc != pcmk_rc_ok) {
 283         fprintf(stderr, "error: Could not connect to controller: %s\n",
 284                 pcmk_rc_str(rc));
 285         exit_code = pcmk_rc2exitc(rc);
 286         return;
 287     }
 288 
 289     if (list_nodes) {
 290         rc = pcmk_controld_api_list_nodes(controld_api);
 291     } else {
 292         rc = pcmk_controld_api_node_info(controld_api, nodeid);
 293     }
 294     if (rc != pcmk_rc_ok) {
 295         fprintf(stderr, "error: Could not ping controller: %s\n",
 296                 pcmk_rc_str(rc));
 297         pcmk_disconnect_ipc(controld_api);
 298         exit_code = pcmk_rc2exitc(rc);
 299         return;
 300     }
 301 
 302     // Run main loop to get controller reply via controller_event_cb()
 303     mainloop = g_main_loop_new(NULL, FALSE);
 304     g_main_loop_run(mainloop);
 305     g_main_loop_unref(mainloop);
 306     mainloop = NULL;
 307     pcmk_free_ipc_api(controld_api);
 308 }
 309 
 310 static void
 311 print_node_name(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 312 {
 313     // Check environment first (i.e. when called by resource agent)
 314     const char *name = getenv("OCF_RESKEY_" CRM_META "_" XML_LRM_ATTR_TARGET);
 315 
 316     if (name != NULL) {
 317         printf("%s\n", name);
 318         exit_code = CRM_EX_OK;
 319         return;
 320 
 321     } else {
 322         // Otherwise ask the controller
 323         run_controller_mainloop(0, false);
 324     }
 325 }
 326 
 327 static int
 328 cib_remove_node(long id, const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
 329 {
 330     int rc;
 331     cib_t *cib = NULL;
 332     xmlNode *node = NULL;
 333     xmlNode *node_state = NULL;
 334 
 335     crm_trace("Removing %s from the CIB", name);
 336 
 337     if(name == NULL && id == 0) {
 338         return -ENOTUNIQ;
 339     }
 340 
 341     node = create_xml_node(NULL, XML_CIB_TAG_NODE);
 342     node_state = create_xml_node(NULL, XML_CIB_TAG_STATE);
 343 
 344     crm_xml_add(node, XML_ATTR_UNAME, name);
 345     crm_xml_add(node_state, XML_ATTR_UNAME, name);
 346     if (id > 0) {
 347         crm_xml_set_id(node, "%ld", id);
 348         crm_xml_add(node_state, XML_ATTR_ID, ID(node));
 349     }
 350 
 351     cib = cib_new();
 352     cib->cmds->signon(cib, crm_system_name, cib_command);
 353 
 354     rc = cib->cmds->remove(cib, XML_CIB_TAG_NODES, node, cib_sync_call);
 355     if (rc != pcmk_ok) {
 356         printf("Could not remove %s[%ld] from " XML_CIB_TAG_NODES ": %s",
 357                 name, id, pcmk_strerror(rc));
 358     }
 359     rc = cib->cmds->remove(cib, XML_CIB_TAG_STATUS, node_state, cib_sync_call);
 360     if (rc != pcmk_ok) {
 361         printf("Could not remove %s[%ld] from " XML_CIB_TAG_STATUS ": %s",
 362                 name, id, pcmk_strerror(rc));
 363     }
 364 
 365     cib->cmds->signoff(cib);
 366     cib_delete(cib);
 367     return rc;
 368 }
 369 
 370 static int
 371 controller_remove_node(const char *node_name, long nodeid)
     /* [previous][next][first][last][top][bottom][index][help] */
 372 {
 373     pcmk_ipc_api_t *controld_api = NULL;
 374     int rc;
 375 
 376     // Create controller IPC object
 377     rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld);
 378     if (rc != pcmk_rc_ok) {
 379         fprintf(stderr, "error: Could not connect to controller: %s\n",
 380                 pcmk_rc_str(rc));
 381         return ENOTCONN;
 382     }
 383 
 384     // Connect to controller (without main loop)
 385     rc = pcmk_connect_ipc(controld_api, pcmk_ipc_dispatch_sync);
 386     if (rc != pcmk_rc_ok) {
 387         fprintf(stderr, "error: Could not connect to controller: %s\n",
 388                 pcmk_rc_str(rc));
 389         pcmk_free_ipc_api(controld_api);
 390         return rc;
 391     }
 392 
 393     rc = pcmk_ipc_purge_node(controld_api, node_name, nodeid);
 394     if (rc != pcmk_rc_ok) {
 395         fprintf(stderr,
 396                 "error: Could not clear node from controller's cache: %s\n",
 397                 pcmk_rc_str(rc));
 398     }
 399 
 400     pcmk_free_ipc_api(controld_api);
 401     return pcmk_rc_ok;
 402 }
 403 
 404 static int
 405 tools_remove_node_cache(const char *node_name, long nodeid, const char *target)
     /* [previous][next][first][last][top][bottom][index][help] */
 406 {
 407     int rc = -1;
 408     crm_ipc_t *conn = NULL;
 409     xmlNode *cmd = NULL;
 410 
 411     conn = crm_ipc_new(target, 0);
 412     if (!conn) {
 413         return -ENOTCONN;
 414     }
 415     if (!crm_ipc_connect(conn)) {
 416         crm_perror(LOG_ERR, "Connection to %s failed", target);
 417         crm_ipc_destroy(conn);
 418         return -ENOTCONN;
 419     }
 420 
 421     crm_trace("Removing %s[%ld] from the %s membership cache",
 422               node_name, nodeid, target);
 423 
 424     if(pcmk__str_eq(target, T_ATTRD, pcmk__str_casei)) {
 425         cmd = create_xml_node(NULL, __func__);
 426 
 427         crm_xml_add(cmd, F_TYPE, T_ATTRD);
 428         crm_xml_add(cmd, F_ORIG, crm_system_name);
 429 
 430         crm_xml_add(cmd, PCMK__XA_TASK, PCMK__ATTRD_CMD_PEER_REMOVE);
 431         crm_xml_add(cmd, PCMK__XA_ATTR_NODE_NAME, node_name);
 432 
 433         if (nodeid > 0) {
 434             crm_xml_add_int(cmd, PCMK__XA_ATTR_NODE_ID, (int) nodeid);
 435         }
 436 
 437     } else { // Fencer or pacemakerd
 438         cmd = create_request(CRM_OP_RM_NODE_CACHE, NULL, NULL, target,
 439                              crm_system_name, NULL);
 440         if (nodeid > 0) {
 441             crm_xml_set_id(cmd, "%ld", nodeid);
 442         }
 443         crm_xml_add(cmd, XML_ATTR_UNAME, node_name);
 444     }
 445 
 446     rc = crm_ipc_send(conn, cmd, 0, 0, NULL);
 447     crm_debug("%s peer cache cleanup for %s (%ld): %d",
 448               target, node_name, nodeid, rc);
 449 
 450     if (rc > 0) {
 451         // @TODO Should this be done just once after all the rest?
 452         rc = cib_remove_node(nodeid, node_name);
 453     }
 454 
 455     if (conn) {
 456         crm_ipc_close(conn);
 457         crm_ipc_destroy(conn);
 458     }
 459     free_xml(cmd);
 460     return rc > 0 ? 0 : rc;
 461 }
 462 
 463 static void
 464 remove_node(const char *target_uname)
     /* [previous][next][first][last][top][bottom][index][help] */
 465 {
 466     int rc;
 467     int d = 0;
 468     long nodeid = 0;
 469     const char *node_name = NULL;
 470     char *endptr = NULL;
 471     const char *daemons[] = {
 472         "stonith-ng",
 473         T_ATTRD,
 474         CRM_SYSTEM_MCP,
 475     };
 476 
 477     // Check whether node was specified by name or numeric ID
 478     errno = 0;
 479     nodeid = strtol(target_uname, &endptr, 10);
 480     if ((errno != 0) || (endptr == target_uname) || (*endptr != '\0')
 481         || (nodeid <= 0)) {
 482         // It's not a positive integer, so assume it's a node name
 483         nodeid = 0;
 484         node_name = target_uname;
 485     }
 486 
 487     rc = controller_remove_node(node_name, nodeid);
 488     if (rc != pcmk_rc_ok) {
 489         exit_code = pcmk_rc2exitc(rc);
 490         return;
 491     }
 492 
 493     for (d = 0; d < DIMOF(daemons); d++) {
 494         if (tools_remove_node_cache(node_name, nodeid, daemons[d])) {
 495             crm_err("Failed to connect to %s to remove node '%s'",
 496                     daemons[d], target_uname);
 497             exit_code = CRM_EX_ERROR;
 498             return;
 499         }
 500     }
 501     exit_code = CRM_EX_OK;
 502 }
 503 
 504 static GOptionContext *
 505 build_arg_context(pcmk__common_args_t *args, GOptionGroup *group) {
     /* [previous][next][first][last][top][bottom][index][help] */
 506     GOptionContext *context = NULL;
 507 
 508     GOptionEntry extra_prog_entries[] = {
 509         { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &(args->quiet),
 510           "Be less descriptive in output.",
 511           NULL },
 512 
 513         { NULL }
 514     };
 515 
 516     context = pcmk__build_arg_context(args, NULL, &group, NULL);
 517 
 518     /* Add the -q option, which cannot be part of the globally supported options
 519      * because some tools use that flag for something else.
 520      */
 521     pcmk__add_main_args(context, extra_prog_entries);
 522 
 523     pcmk__add_arg_group(context, "commands", "Commands:",
 524                         "Show command help", command_entries);
 525     pcmk__add_arg_group(context, "additional", "Additional Options:",
 526                         "Show additional options", addl_entries);
 527     return context;
 528 }
 529 
 530 int
 531 main(int argc, char **argv)
     /* [previous][next][first][last][top][bottom][index][help] */
 532 {
 533     pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
 534 
 535     GError *error = NULL;
 536     GOptionContext *context = NULL;
 537     GOptionGroup *output_group = NULL;
 538     gchar **processed_args = NULL;
 539 
 540     context = build_arg_context(args, output_group);
 541 
 542     crm_log_cli_init("crm_node");
 543 
 544     processed_args = pcmk__cmdline_preproc(argv, "NR");
 545 
 546     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
 547         fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
 548         exit_code = CRM_EX_USAGE;
 549         goto done;
 550     }
 551 
 552     for (int i = 0; i < args->verbosity; i++) {
 553         crm_bump_log_level(argc, argv);
 554     }
 555 
 556     if (args->version) {
 557         /* FIXME:  When crm_node is converted to use formatted output, this can go. */
 558         pcmk__cli_help('v', CRM_EX_USAGE);
 559     }
 560 
 561     if (optind > argc || options.command == 0) {
 562         char *help = g_option_context_get_help(context, TRUE, NULL);
 563 
 564         fprintf(stderr, "%s", help);
 565         g_free(help);
 566         exit_code = CRM_EX_USAGE;
 567         goto done;
 568     }
 569 
 570     if (options.dangerous_cmd && options.force_flag == FALSE) {
 571         fprintf(stderr, "The supplied command is considered dangerous."
 572                 "  To prevent accidental destruction of the cluster,"
 573                 " the --force flag is required in order to proceed.\n");
 574         exit_code = CRM_EX_USAGE;
 575         goto done;
 576     }
 577 
 578     switch (options.command) {
 579         case 'n':
 580             print_node_name();
 581             break;
 582         case 'R':
 583             remove_node(options.target_uname);
 584             break;
 585         case 'i':
 586         case 'q':
 587         case 'N':
 588             run_controller_mainloop(options.nodeid, false);
 589             break;
 590         case 'l':
 591         case 'p':
 592             run_controller_mainloop(0, true);
 593             break;
 594         default:
 595             break;
 596     }
 597 
 598 done:
 599     g_strfreev(processed_args);
 600     g_clear_error(&error);
 601     pcmk__free_arg_context(context);
 602     return crm_exit(exit_code);
 603 }

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