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-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 <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          * FIXME: Use pcmk__query_node_name() after conversion to formatted
 326          * output.
 327          */
 328         run_controller_mainloop(0, false);
 329     }
 330 }
 331 
 332 static int
 333 cib_remove_node(long id, const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
 334 {
 335     int rc;
 336     cib_t *cib = NULL;
 337     xmlNode *node = NULL;
 338     xmlNode *node_state = NULL;
 339 
 340     crm_trace("Removing %s from the CIB", name);
 341 
 342     if(name == NULL && id == 0) {
 343         return -ENOTUNIQ;
 344     }
 345 
 346     node = create_xml_node(NULL, XML_CIB_TAG_NODE);
 347     node_state = create_xml_node(NULL, XML_CIB_TAG_STATE);
 348 
 349     crm_xml_add(node, XML_ATTR_UNAME, name);
 350     crm_xml_add(node_state, XML_ATTR_UNAME, name);
 351     if (id > 0) {
 352         crm_xml_set_id(node, "%ld", id);
 353         crm_xml_add(node_state, XML_ATTR_ID, ID(node));
 354     }
 355 
 356     cib = cib_new();
 357     cib->cmds->signon(cib, crm_system_name, cib_command);
 358 
 359     rc = cib->cmds->remove(cib, XML_CIB_TAG_NODES, node, cib_sync_call);
 360     if (rc != pcmk_ok) {
 361         printf("Could not remove %s[%ld] from " XML_CIB_TAG_NODES ": %s",
 362                 name, id, pcmk_strerror(rc));
 363     }
 364     rc = cib->cmds->remove(cib, XML_CIB_TAG_STATUS, node_state, cib_sync_call);
 365     if (rc != pcmk_ok) {
 366         printf("Could not remove %s[%ld] from " XML_CIB_TAG_STATUS ": %s",
 367                 name, id, pcmk_strerror(rc));
 368     }
 369 
 370     cib__clean_up_connection(&cib);
 371     return rc;
 372 }
 373 
 374 static int
 375 controller_remove_node(const char *node_name, long nodeid)
     /* [previous][next][first][last][top][bottom][index][help] */
 376 {
 377     pcmk_ipc_api_t *controld_api = NULL;
 378     int rc;
 379 
 380     // Create controller IPC object
 381     rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld);
 382     if (rc != pcmk_rc_ok) {
 383         fprintf(stderr, "error: Could not connect to controller: %s\n",
 384                 pcmk_rc_str(rc));
 385         return ENOTCONN;
 386     }
 387 
 388     // Connect to controller (without main loop)
 389     rc = pcmk_connect_ipc(controld_api, pcmk_ipc_dispatch_sync);
 390     if (rc != pcmk_rc_ok) {
 391         fprintf(stderr, "error: Could not connect to controller: %s\n",
 392                 pcmk_rc_str(rc));
 393         pcmk_free_ipc_api(controld_api);
 394         return rc;
 395     }
 396 
 397     rc = pcmk_ipc_purge_node(controld_api, node_name, nodeid);
 398     if (rc != pcmk_rc_ok) {
 399         fprintf(stderr,
 400                 "error: Could not clear node from controller's cache: %s\n",
 401                 pcmk_rc_str(rc));
 402     }
 403 
 404     pcmk_free_ipc_api(controld_api);
 405     return pcmk_rc_ok;
 406 }
 407 
 408 static int
 409 tools_remove_node_cache(const char *node_name, long nodeid, const char *target)
     /* [previous][next][first][last][top][bottom][index][help] */
 410 {
 411     int rc = -1;
 412     crm_ipc_t *conn = NULL;
 413     xmlNode *cmd = NULL;
 414 
 415     conn = crm_ipc_new(target, 0);
 416     if (!conn) {
 417         return -ENOTCONN;
 418     }
 419     if (!crm_ipc_connect(conn)) {
 420         crm_perror(LOG_ERR, "Connection to %s failed", target);
 421         crm_ipc_destroy(conn);
 422         return -ENOTCONN;
 423     }
 424 
 425     crm_trace("Removing %s[%ld] from the %s membership cache",
 426               node_name, nodeid, target);
 427 
 428     if(pcmk__str_eq(target, T_ATTRD, pcmk__str_casei)) {
 429         cmd = create_xml_node(NULL, __func__);
 430 
 431         crm_xml_add(cmd, F_TYPE, T_ATTRD);
 432         crm_xml_add(cmd, F_ORIG, crm_system_name);
 433 
 434         crm_xml_add(cmd, PCMK__XA_TASK, PCMK__ATTRD_CMD_PEER_REMOVE);
 435 
 436         pcmk__xe_add_node(cmd, node_name, nodeid);
 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');
 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             /* FIXME: Use pcmk__query_node_name() after conversion to formatted
 583              * output
 584              */
 585             run_controller_mainloop(options.nodeid, false);
 586             break;
 587         case 'l':
 588         case 'p':
 589             run_controller_mainloop(0, true);
 590             break;
 591         default:
 592             break;
 593     }
 594 
 595 done:
 596     g_strfreev(processed_args);
 597     pcmk__free_arg_context(context);
 598 
 599     pcmk__output_and_clear_error(&error, NULL);
 600     return crm_exit(exit_code);
 601 }

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