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 
 433         pcmk__xe_add_node(cmd, node_name, nodeid);
 434 
 435     } else { // Fencer or pacemakerd
 436         cmd = create_request(CRM_OP_RM_NODE_CACHE, NULL, NULL, target,
 437                              crm_system_name, NULL);
 438         if (nodeid > 0) {
 439             crm_xml_set_id(cmd, "%ld", nodeid);
 440         }
 441         crm_xml_add(cmd, XML_ATTR_UNAME, node_name);
 442     }
 443 
 444     rc = crm_ipc_send(conn, cmd, 0, 0, NULL);
 445     crm_debug("%s peer cache cleanup for %s (%ld): %d",
 446               target, node_name, nodeid, rc);
 447 
 448     if (rc > 0) {
 449         // @TODO Should this be done just once after all the rest?
 450         rc = cib_remove_node(nodeid, node_name);
 451     }
 452 
 453     if (conn) {
 454         crm_ipc_close(conn);
 455         crm_ipc_destroy(conn);
 456     }
 457     free_xml(cmd);
 458     return rc > 0 ? 0 : rc;
 459 }
 460 
 461 static void
 462 remove_node(const char *target_uname)
     /* [previous][next][first][last][top][bottom][index][help] */
 463 {
 464     int rc;
 465     int d = 0;
 466     long nodeid = 0;
 467     const char *node_name = NULL;
 468     char *endptr = NULL;
 469     const char *daemons[] = {
 470         "stonith-ng",
 471         T_ATTRD,
 472         CRM_SYSTEM_MCP,
 473     };
 474 
 475     // Check whether node was specified by name or numeric ID
 476     errno = 0;
 477     nodeid = strtol(target_uname, &endptr, 10);
 478     if ((errno != 0) || (endptr == target_uname) || (*endptr != '\0')
 479         || (nodeid <= 0)) {
 480         // It's not a positive integer, so assume it's a node name
 481         nodeid = 0;
 482         node_name = target_uname;
 483     }
 484 
 485     rc = controller_remove_node(node_name, nodeid);
 486     if (rc != pcmk_rc_ok) {
 487         exit_code = pcmk_rc2exitc(rc);
 488         return;
 489     }
 490 
 491     for (d = 0; d < PCMK__NELEM(daemons); d++) {
 492         if (tools_remove_node_cache(node_name, nodeid, daemons[d])) {
 493             crm_err("Failed to connect to %s to remove node '%s'",
 494                     daemons[d], target_uname);
 495             exit_code = CRM_EX_ERROR;
 496             return;
 497         }
 498     }
 499     exit_code = CRM_EX_OK;
 500 }
 501 
 502 static GOptionContext *
 503 build_arg_context(pcmk__common_args_t *args, GOptionGroup *group) {
     /* [previous][next][first][last][top][bottom][index][help] */
 504     GOptionContext *context = NULL;
 505 
 506     GOptionEntry extra_prog_entries[] = {
 507         { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &(args->quiet),
 508           "Be less descriptive in output.",
 509           NULL },
 510 
 511         { NULL }
 512     };
 513 
 514     context = pcmk__build_arg_context(args, NULL, &group, NULL);
 515 
 516     /* Add the -q option, which cannot be part of the globally supported options
 517      * because some tools use that flag for something else.
 518      */
 519     pcmk__add_main_args(context, extra_prog_entries);
 520 
 521     pcmk__add_arg_group(context, "commands", "Commands:",
 522                         "Show command help", command_entries);
 523     pcmk__add_arg_group(context, "additional", "Additional Options:",
 524                         "Show additional options", addl_entries);
 525     return context;
 526 }
 527 
 528 int
 529 main(int argc, char **argv)
     /* [previous][next][first][last][top][bottom][index][help] */
 530 {
 531     GError *error = NULL;
 532 
 533     GOptionGroup *output_group = NULL;
 534     pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
 535     gchar **processed_args = pcmk__cmdline_preproc(argv, "NR");
 536     GOptionContext *context = build_arg_context(args, output_group);
 537 
 538     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
 539         exit_code = CRM_EX_USAGE;
 540         goto done;
 541     }
 542 
 543     pcmk__cli_init_logging("crm_node", args->verbosity);
 544 
 545     if (args->version) {
 546         g_strfreev(processed_args);
 547         pcmk__free_arg_context(context);
 548         /* FIXME:  When crm_node is converted to use formatted output, this can go. */
 549         pcmk__cli_help('v', CRM_EX_OK);
 550     }
 551 
 552     if (options.command == 0) {
 553         char *help = g_option_context_get_help(context, TRUE, NULL);
 554 
 555         fprintf(stderr, "%s", help);
 556         g_free(help);
 557         exit_code = CRM_EX_USAGE;
 558         goto done;
 559     }
 560 
 561     if (options.dangerous_cmd && options.force_flag == FALSE) {
 562         fprintf(stderr, "The supplied command is considered dangerous."
 563                 "  To prevent accidental destruction of the cluster,"
 564                 " the --force flag is required in order to proceed.\n");
 565         exit_code = CRM_EX_USAGE;
 566         goto done;
 567     }
 568 
 569     switch (options.command) {
 570         case 'n':
 571             print_node_name();
 572             break;
 573         case 'R':
 574             remove_node(options.target_uname);
 575             break;
 576         case 'i':
 577         case 'q':
 578         case 'N':
 579             run_controller_mainloop(options.nodeid, false);
 580             break;
 581         case 'l':
 582         case 'p':
 583             run_controller_mainloop(0, true);
 584             break;
 585         default:
 586             break;
 587     }
 588 
 589 done:
 590     g_strfreev(processed_args);
 591     pcmk__free_arg_context(context);
 592 
 593     pcmk__output_and_clear_error(error, NULL);
 594     return crm_exit(exit_code);
 595 }

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