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-2021 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/common/ipc_controld.h>
  24 #include <crm/common/attrd_internal.h>
  25 
  26 #define SUMMARY "crm_node - Tool for displaying low-level node information"
  27 
  28 struct {
  29     gboolean corosync;
  30     gboolean dangerous_cmd;
  31     gboolean force_flag;
  32     char command;
  33     int nodeid;
  34     char *target_uname;
  35 } options = {
  36     .command = '\0',
  37     .force_flag = FALSE
  38 };
  39 
  40 gboolean command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
  41 gboolean name_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
  42 gboolean remove_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
  43 
  44 static GMainLoop *mainloop = NULL;
  45 static crm_exit_t exit_code = CRM_EX_OK;
  46 
  47 #define INDENT "                           "
  48 
  49 static GOptionEntry command_entries[] = {
  50     { "cluster-id", 'i', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
  51       "Display this node's cluster id",
  52       NULL },
  53     { "list", 'l', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
  54       "Display all known members (past and present) of this cluster",
  55       NULL },
  56     { "name", 'n', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
  57       "Display the name used by the cluster for this node",
  58       NULL },
  59     { "partition", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
  60       "Display the members of this partition",
  61       NULL },
  62     { "quorum", 'q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
  63       "Display a 1 if our partition has quorum, 0 if not",
  64       NULL },
  65     { "name-for-id", 'N', 0, G_OPTION_ARG_CALLBACK, name_cb,
  66       "Display the name used by the cluster for the node with the specified ID",
  67       "ID" },
  68     { "remove", 'R', 0, G_OPTION_ARG_CALLBACK, remove_cb,
  69       "(Advanced) Remove the (stopped) node with the specified name from Pacemaker's\n"
  70       INDENT "configuration and caches (the node must already have been removed from\n"
  71       INDENT "the underlying cluster stack configuration",
  72       "NAME" },
  73 
  74     { NULL }
  75 };
  76 
  77 static GOptionEntry addl_entries[] = {
  78     { "force", 'f', 0, G_OPTION_ARG_NONE, &options.force_flag,
  79       NULL,
  80       NULL },
  81 #if SUPPORT_COROSYNC
  82     /* Unused and deprecated */
  83     { "corosync", 'C', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.corosync,
  84       NULL,
  85       NULL },
  86 #endif
  87 
  88     // @TODO add timeout option for when IPC replies are needed
  89 
  90     { NULL }
  91 };
  92 
  93 gboolean
  94 command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     /* [previous][next][first][last][top][bottom][index][help] */
  95     if (pcmk__str_eq("-i", option_name, pcmk__str_casei) || pcmk__str_eq("--cluster-id", option_name, pcmk__str_casei)) {
  96         options.command = 'i';
  97     } else if (pcmk__str_eq("-l", option_name, pcmk__str_casei) || pcmk__str_eq("--list", option_name, pcmk__str_casei)) {
  98         options.command = 'l';
  99     } else if (pcmk__str_eq("-n", option_name, pcmk__str_casei) || pcmk__str_eq("--name", option_name, pcmk__str_casei)) {
 100         options.command = 'n';
 101     } else if (pcmk__str_eq("-p", option_name, pcmk__str_casei) || pcmk__str_eq("--partition", option_name, pcmk__str_casei)) {
 102         options.command = 'p';
 103     } else if (pcmk__str_eq("-q", option_name, pcmk__str_casei) || pcmk__str_eq("--quorum", option_name, pcmk__str_casei)) {
 104         options.command = 'q';
 105     } else {
 106         g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_INVALID_PARAM, "Unknown param passed to command_cb: %s\n", option_name);
 107         return FALSE;
 108     }
 109 
 110     return TRUE;
 111 }
 112 
 113 gboolean
 114 name_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     /* [previous][next][first][last][top][bottom][index][help] */
 115     options.command = 'N';
 116     pcmk__scan_min_int(optarg, &(options.nodeid), 0);
 117     return TRUE;
 118 }
 119 
 120 gboolean
 121 remove_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     /* [previous][next][first][last][top][bottom][index][help] */
 122     if (optarg == NULL) {
 123         crm_err("-R option requires an argument");
 124         g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_INVALID_PARAM, "-R option requires an argument");
 125         return FALSE;
 126     }
 127 
 128     options.command = 'R';
 129     options.dangerous_cmd = TRUE;
 130     options.target_uname = strdup(optarg);
 131     return TRUE;
 132 }
 133 
 134 static gint
 135 sort_node(gconstpointer a, gconstpointer b)
     /* [previous][next][first][last][top][bottom][index][help] */
 136 {
 137     const pcmk_controld_api_node_t *node_a = a;
 138     const pcmk_controld_api_node_t *node_b = b;
 139 
 140     return pcmk__numeric_strcasecmp((node_a->uname? node_a->uname : ""),
 141                                     (node_b->uname? node_b->uname : ""));
 142 }
 143 
 144 static void
 145 controller_event_cb(pcmk_ipc_api_t *controld_api,
     /* [previous][next][first][last][top][bottom][index][help] */
 146                     enum pcmk_ipc_event event_type, crm_exit_t status,
 147                     void *event_data, void *user_data)
 148 {
 149     pcmk_controld_api_reply_t *reply = event_data;
 150 
 151     switch (event_type) {
 152         case pcmk_ipc_event_disconnect:
 153             if (exit_code == CRM_EX_DISCONNECT) { // Unexpected
 154                 fprintf(stderr, "error: Lost connection to controller\n");
 155             }
 156             goto done;
 157             break;
 158 
 159         case pcmk_ipc_event_reply:
 160             break;
 161 
 162         default:
 163             return;
 164     }
 165 
 166     if (status != CRM_EX_OK) {
 167         fprintf(stderr, "error: Bad reply from controller: %s\n",
 168                 crm_exit_str(status));
 169         goto done;
 170     }
 171 
 172     // Parse desired info from reply and display to user
 173     switch (options.command) {
 174         case 'i':
 175             if (reply->reply_type != pcmk_controld_reply_info) {
 176                 fprintf(stderr,
 177                         "error: Unknown reply type %d from controller\n",
 178                         reply->reply_type);
 179                 goto done;
 180             }
 181             if (reply->data.node_info.id == 0) {
 182                 fprintf(stderr,
 183                         "error: Controller reply did not contain node ID\n");
 184                 exit_code = CRM_EX_PROTOCOL;
 185                 goto done;
 186             }
 187             printf("%d\n", reply->data.node_info.id);
 188             break;
 189 
 190         case 'n':
 191         case 'N':
 192             if (reply->reply_type != pcmk_controld_reply_info) {
 193                 fprintf(stderr,
 194                         "error: Unknown reply type %d from controller\n",
 195                         reply->reply_type);
 196                 goto done;
 197             }
 198             if (reply->data.node_info.uname == NULL) {
 199                 fprintf(stderr, "Node is not known to cluster\n");
 200                 exit_code = CRM_EX_NOHOST;
 201                 goto done;
 202             }
 203             printf("%s\n", reply->data.node_info.uname);
 204             break;
 205 
 206         case 'q':
 207             if (reply->reply_type != pcmk_controld_reply_info) {
 208                 fprintf(stderr,
 209                         "error: Unknown reply type %d from controller\n",
 210                         reply->reply_type);
 211                 goto done;
 212             }
 213             printf("%d\n", reply->data.node_info.have_quorum);
 214             if (!(reply->data.node_info.have_quorum)) {
 215                 exit_code = CRM_EX_QUORUM;
 216                 goto done;
 217             }
 218             break;
 219 
 220         case 'l':
 221         case 'p':
 222             if (reply->reply_type != pcmk_controld_reply_nodes) {
 223                 fprintf(stderr,
 224                         "error: Unknown reply type %d from controller\n",
 225                         reply->reply_type);
 226                 goto done;
 227             }
 228             reply->data.nodes = g_list_sort(reply->data.nodes, sort_node);
 229             for (GList *node_iter = reply->data.nodes;
 230                  node_iter != NULL; node_iter = node_iter->next) {
 231 
 232                 pcmk_controld_api_node_t *node = node_iter->data;
 233                 const char *uname = (node->uname? node->uname : "");
 234                 const char *state = (node->state? node->state : "");
 235 
 236                 if (options.command == 'l') {
 237                     printf("%lu %s %s\n",
 238                            (unsigned long) node->id, uname, state);
 239 
 240                 // i.e. CRM_NODE_MEMBER, but we don't want to include cluster.h
 241                 } else if (!strcmp(state, "member")) {
 242                     printf("%s ", uname);
 243                 }
 244             }
 245             if (options.command == 'p') {
 246                 printf("\n");
 247             }
 248             break;
 249 
 250         default:
 251             fprintf(stderr, "internal error: Controller reply not expected\n");
 252             exit_code = CRM_EX_SOFTWARE;
 253             goto done;
 254     }
 255 
 256     // Success
 257     exit_code = CRM_EX_OK;
 258 done:
 259     pcmk_disconnect_ipc(controld_api);
 260     pcmk_quit_main_loop(mainloop, 10);
 261 }
 262 
 263 static void
 264 run_controller_mainloop(uint32_t nodeid, bool list_nodes)
     /* [previous][next][first][last][top][bottom][index][help] */
 265 {
 266     pcmk_ipc_api_t *controld_api = NULL;
 267     int rc;
 268 
 269     // Set disconnect exit code to handle unexpected disconnects
 270     exit_code = CRM_EX_DISCONNECT;
 271 
 272     // Create controller IPC object
 273     rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld);
 274     if (rc != pcmk_rc_ok) {
 275         fprintf(stderr, "error: Could not connect to controller: %s\n",
 276                 pcmk_rc_str(rc));
 277         return;
 278     }
 279     pcmk_register_ipc_callback(controld_api, controller_event_cb, NULL);
 280 
 281     // Connect to controller
 282     rc = pcmk_connect_ipc(controld_api, pcmk_ipc_dispatch_main);
 283     if (rc != pcmk_rc_ok) {
 284         fprintf(stderr, "error: Could not connect to controller: %s\n",
 285                 pcmk_rc_str(rc));
 286         exit_code = pcmk_rc2exitc(rc);
 287         return;
 288     }
 289 
 290     if (list_nodes) {
 291         rc = pcmk_controld_api_list_nodes(controld_api);
 292     } else {
 293         rc = pcmk_controld_api_node_info(controld_api, nodeid);
 294     }
 295     if (rc != pcmk_rc_ok) {
 296         fprintf(stderr, "error: Could not ping controller: %s\n",
 297                 pcmk_rc_str(rc));
 298         pcmk_disconnect_ipc(controld_api);
 299         exit_code = pcmk_rc2exitc(rc);
 300         return;
 301     }
 302 
 303     // Run main loop to get controller reply via controller_event_cb()
 304     mainloop = g_main_loop_new(NULL, FALSE);
 305     g_main_loop_run(mainloop);
 306     g_main_loop_unref(mainloop);
 307     mainloop = NULL;
 308     pcmk_free_ipc_api(controld_api);
 309 }
 310 
 311 static void
 312 print_node_name(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 313 {
 314     // Check environment first (i.e. when called by resource agent)
 315     const char *name = getenv("OCF_RESKEY_" CRM_META "_" XML_LRM_ATTR_TARGET);
 316 
 317     if (name != NULL) {
 318         printf("%s\n", name);
 319         exit_code = CRM_EX_OK;
 320         return;
 321 
 322     } else {
 323         // Otherwise ask the controller
 324         run_controller_mainloop(0, false);
 325     }
 326 }
 327 
 328 static int
 329 cib_remove_node(long id, const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
 330 {
 331     int rc;
 332     cib_t *cib = NULL;
 333     xmlNode *node = NULL;
 334     xmlNode *node_state = NULL;
 335 
 336     crm_trace("Removing %s from the CIB", name);
 337 
 338     if(name == NULL && id == 0) {
 339         return -ENOTUNIQ;
 340     }
 341 
 342     node = create_xml_node(NULL, XML_CIB_TAG_NODE);
 343     node_state = create_xml_node(NULL, XML_CIB_TAG_STATE);
 344 
 345     crm_xml_add(node, XML_ATTR_UNAME, name);
 346     crm_xml_add(node_state, XML_ATTR_UNAME, name);
 347     if (id > 0) {
 348         crm_xml_set_id(node, "%ld", id);
 349         crm_xml_add(node_state, XML_ATTR_ID, ID(node));
 350     }
 351 
 352     cib = cib_new();
 353     cib->cmds->signon(cib, crm_system_name, cib_command);
 354 
 355     rc = cib->cmds->remove(cib, XML_CIB_TAG_NODES, node, cib_sync_call);
 356     if (rc != pcmk_ok) {
 357         printf("Could not remove %s[%ld] from " XML_CIB_TAG_NODES ": %s",
 358                 name, id, pcmk_strerror(rc));
 359     }
 360     rc = cib->cmds->remove(cib, XML_CIB_TAG_STATUS, node_state, cib_sync_call);
 361     if (rc != pcmk_ok) {
 362         printf("Could not remove %s[%ld] from " XML_CIB_TAG_STATUS ": %s",
 363                 name, id, pcmk_strerror(rc));
 364     }
 365 
 366     cib->cmds->signoff(cib);
 367     cib_delete(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_USAGE);
 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] */