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

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