root/lib/pacemaker/pcmk_simulate.c

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

DEFINITIONS

This source file includes following definitions.
  1. create_action_name
  2. print_cluster_status
  3. print_transition_summary
  4. reset
  5. write_sim_dotfile
  6. profile_file
  7. pcmk__profile_dir
  8. set_effective_date
  9. simulate_pseudo_action
  10. simulate_resource_action
  11. simulate_cluster_action
  12. simulate_fencing_action
  13. pcmk__simulate_transition
  14. pcmk__simulate
  15. pcmk_simulate

   1 /*
   2  * Copyright 2021-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 Lesser General Public License
   7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 #include <crm/cib/internal.h>
  12 #include <crm/common/output.h>
  13 #include <crm/common/results.h>
  14 #include <crm/pengine/pe_types.h>
  15 #include <pacemaker-internal.h>
  16 #include <pacemaker.h>
  17 
  18 #include <stdint.h>
  19 #include <sys/types.h>
  20 #include <sys/stat.h>
  21 #include <unistd.h>
  22 
  23 #include "libpacemaker_private.h"
  24 
  25 static pcmk__output_t *out = NULL;
  26 static cib_t *fake_cib = NULL;
  27 static GList *fake_resource_list = NULL;
  28 static GList *fake_op_fail_list = NULL;
  29 
  30 static void set_effective_date(pe_working_set_t *data_set, bool print_original,
  31                                char *use_date);
  32 
  33 /*!
  34  * \internal
  35  * \brief Create an action name for use in a dot graph
  36  *
  37  * \param[in] action   Action to create name for
  38  * \param[in] verbose  If true, add action ID to name
  39  *
  40  * \return Newly allocated string with action name
  41  * \note It is the caller's responsibility to free the result.
  42  */
  43 static char *
  44 create_action_name(pe_action_t *action, bool verbose)
     /* [previous][next][first][last][top][bottom][index][help] */
  45 {
  46     char *action_name = NULL;
  47     const char *prefix = "";
  48     const char *action_host = NULL;
  49     const char *clone_name = NULL;
  50     const char *task = action->task;
  51 
  52     if (action->node != NULL) {
  53         action_host = action->node->details->uname;
  54     } else if (!pcmk_is_set(action->flags, pe_action_pseudo)) {
  55         action_host = "<none>";
  56     }
  57 
  58     if (pcmk__str_eq(action->task, RSC_CANCEL, pcmk__str_none)) {
  59         prefix = "Cancel ";
  60         task = action->cancel_task;
  61     }
  62 
  63     if (action->rsc != NULL) {
  64         clone_name = action->rsc->clone_name;
  65     }
  66 
  67     if (clone_name != NULL) {
  68         char *key = NULL;
  69         guint interval_ms = 0;
  70 
  71         if (pcmk__guint_from_hash(action->meta,
  72                                   XML_LRM_ATTR_INTERVAL_MS, 0,
  73                                   &interval_ms) != pcmk_rc_ok) {
  74             interval_ms = 0;
  75         }
  76 
  77         if (pcmk__strcase_any_of(action->task, RSC_NOTIFY, RSC_NOTIFIED,
  78                                  NULL)) {
  79             const char *n_type = g_hash_table_lookup(action->meta,
  80                                                      "notify_key_type");
  81             const char *n_task = g_hash_table_lookup(action->meta,
  82                                                      "notify_key_operation");
  83 
  84             CRM_ASSERT(n_type != NULL);
  85             CRM_ASSERT(n_task != NULL);
  86             key = pcmk__notify_key(clone_name, n_type, n_task);
  87         } else {
  88             key = pcmk__op_key(clone_name, task, interval_ms);
  89         }
  90 
  91         if (action_host != NULL) {
  92             action_name = crm_strdup_printf("%s%s %s",
  93                                             prefix, key, action_host);
  94         } else {
  95             action_name = crm_strdup_printf("%s%s", prefix, key);
  96         }
  97         free(key);
  98 
  99     } else if (pcmk__str_eq(action->task, CRM_OP_FENCE, pcmk__str_casei)) {
 100         const char *op = g_hash_table_lookup(action->meta, "stonith_action");
 101 
 102         action_name = crm_strdup_printf("%s%s '%s' %s",
 103                                         prefix, action->task, op, action_host);
 104 
 105     } else if (action->rsc && action_host) {
 106         action_name = crm_strdup_printf("%s%s %s",
 107                                         prefix, action->uuid, action_host);
 108 
 109     } else if (action_host) {
 110         action_name = crm_strdup_printf("%s%s %s",
 111                                         prefix, action->task, action_host);
 112 
 113     } else {
 114         action_name = crm_strdup_printf("%s", action->uuid);
 115     }
 116 
 117     if (verbose) {
 118         char *with_id = crm_strdup_printf("%s (%d)", action_name, action->id);
 119 
 120         free(action_name);
 121         action_name = with_id;
 122     }
 123     return action_name;
 124 }
 125 
 126 /*!
 127  * \internal
 128  * \brief Display the status of a cluster
 129  *
 130  * \param[in] data_set      Cluster working set
 131  * \param[in] show_opts     How to modify display (as pcmk_show_opt_e flags)
 132  * \param[in] section_opts  Sections to display (as pcmk_section_e flags)
 133  * \param[in] title         What to use as list title
 134  * \param[in] print_spacer  Whether to display a spacer first
 135  */
 136 static void
 137 print_cluster_status(pe_working_set_t *data_set, uint32_t show_opts,
     /* [previous][next][first][last][top][bottom][index][help] */
 138                      uint32_t section_opts, const char *title, bool print_spacer)
 139 {
 140     pcmk__output_t *out = data_set->priv;
 141     GList *all = NULL;
 142     crm_exit_t stonith_rc = 0;
 143 
 144     section_opts |= pcmk_section_nodes | pcmk_section_resources;
 145     show_opts |= pcmk_show_inactive_rscs | pcmk_show_failed_detail;
 146 
 147     all = g_list_prepend(all, (gpointer) "*");
 148 
 149     PCMK__OUTPUT_SPACER_IF(out, print_spacer);
 150     out->begin_list(out, NULL, NULL, "%s", title);
 151     out->message(out, "cluster-status", data_set, stonith_rc, NULL, false,
 152                  section_opts, show_opts, NULL, all, all);
 153     out->end_list(out);
 154 
 155     g_list_free(all);
 156 }
 157 
 158 /*!
 159  * \internal
 160  * \brief Display a summary of all actions scheduled in a transition
 161  *
 162  * \param[in] data_set      Cluster working set (fully scheduled)
 163  * \param[in] print_spacer  Whether to display a spacer first
 164  */
 165 static void
 166 print_transition_summary(pe_working_set_t *data_set, bool print_spacer)
     /* [previous][next][first][last][top][bottom][index][help] */
 167 {
 168     pcmk__output_t *out = data_set->priv;
 169 
 170     PCMK__OUTPUT_SPACER_IF(out, print_spacer);
 171     out->begin_list(out, NULL, NULL, "Transition Summary");
 172     pcmk__output_actions(data_set);
 173     out->end_list(out);
 174 }
 175 
 176 /*!
 177  * \internal
 178  * \brief Reset a cluster working set's input, output, date, and flags
 179  *
 180  * \param[in] data_set  Cluster working set
 181  * \param[in] input     What to set as cluster input
 182  * \param[in] out       What to set as cluster output object
 183  * \param[in] use_date  What to set as cluster's current timestamp
 184  * \param[in] flags     Cluster flags to add (pe_flag_*)
 185  */
 186 static void
 187 reset(pe_working_set_t *data_set, xmlNodePtr input, pcmk__output_t *out,
     /* [previous][next][first][last][top][bottom][index][help] */
 188       char *use_date, unsigned int flags)
 189 {
 190     data_set->input = input;
 191     data_set->priv = out;
 192     set_effective_date(data_set, true, use_date);
 193     if (pcmk_is_set(flags, pcmk_sim_sanitized)) {
 194         pe__set_working_set_flags(data_set, pe_flag_sanitized);
 195     }
 196     if (pcmk_is_set(flags, pcmk_sim_show_scores)) {
 197         pe__set_working_set_flags(data_set, pe_flag_show_scores);
 198     }
 199     if (pcmk_is_set(flags, pcmk_sim_show_utilization)) {
 200         pe__set_working_set_flags(data_set, pe_flag_show_utilization);
 201     }
 202 }
 203 
 204 /*!
 205  * \brief Write out a file in dot(1) format describing the actions that will
 206  *        be taken by the scheduler in response to an input CIB file.
 207  *
 208  * \param[in] data_set     Working set for the cluster
 209  * \param[in] dot_file     The filename to write
 210  * \param[in] all_actions  Write all actions, even those that are optional or
 211  *                         are on unmanaged resources
 212  * \param[in] verbose      Add extra information, such as action IDs, to the
 213  *                         output
 214  *
 215  * \return Standard Pacemaker return code
 216  */
 217 static int
 218 write_sim_dotfile(pe_working_set_t *data_set, const char *dot_file,
     /* [previous][next][first][last][top][bottom][index][help] */
 219                   bool all_actions, bool verbose)
 220 {
 221     GList *gIter = NULL;
 222     FILE *dot_strm = fopen(dot_file, "w");
 223 
 224     if (dot_strm == NULL) {
 225         return errno;
 226     }
 227 
 228     fprintf(dot_strm, " digraph \"g\" {\n");
 229     for (gIter = data_set->actions; gIter != NULL; gIter = gIter->next) {
 230         pe_action_t *action = (pe_action_t *) gIter->data;
 231         const char *style = "dashed";
 232         const char *font = "black";
 233         const char *color = "black";
 234         char *action_name = create_action_name(action, verbose);
 235 
 236         if (pcmk_is_set(action->flags, pe_action_pseudo)) {
 237             font = "orange";
 238         }
 239 
 240         if (pcmk_is_set(action->flags, pe_action_dumped)) {
 241             style = "bold";
 242             color = "green";
 243 
 244         } else if ((action->rsc != NULL)
 245                    && !pcmk_is_set(action->rsc->flags, pe_rsc_managed)) {
 246             color = "red";
 247             font = "purple";
 248             if (!all_actions) {
 249                 goto do_not_write;
 250             }
 251 
 252         } else if (pcmk_is_set(action->flags, pe_action_optional)) {
 253             color = "blue";
 254             if (!all_actions) {
 255                 goto do_not_write;
 256             }
 257 
 258         } else {
 259             color = "red";
 260             CRM_LOG_ASSERT(!pcmk_is_set(action->flags, pe_action_runnable));
 261         }
 262 
 263         pe__set_action_flags(action, pe_action_dumped);
 264         fprintf(dot_strm, "\"%s\" [ style=%s color=\"%s\" fontcolor=\"%s\"]\n",
 265                 action_name, style, color, font);
 266   do_not_write:
 267         free(action_name);
 268     }
 269 
 270     for (gIter = data_set->actions; gIter != NULL; gIter = gIter->next) {
 271         pe_action_t *action = (pe_action_t *) gIter->data;
 272 
 273         GList *gIter2 = NULL;
 274 
 275         for (gIter2 = action->actions_before; gIter2 != NULL; gIter2 = gIter2->next) {
 276             pe_action_wrapper_t *before = (pe_action_wrapper_t *) gIter2->data;
 277 
 278             char *before_name = NULL;
 279             char *after_name = NULL;
 280             const char *style = "dashed";
 281             bool optional = true;
 282 
 283             if (before->state == pe_link_dumped) {
 284                 optional = false;
 285                 style = "bold";
 286             } else if (before->type == pe_order_none) {
 287                 continue;
 288             } else if (pcmk_is_set(before->action->flags, pe_action_dumped)
 289                        && pcmk_is_set(action->flags, pe_action_dumped)
 290                        && before->type != pe_order_load) {
 291                 optional = false;
 292             }
 293 
 294             if (all_actions || !optional) {
 295                 before_name = create_action_name(before->action, verbose);
 296                 after_name = create_action_name(action, verbose);
 297                 fprintf(dot_strm, "\"%s\" -> \"%s\" [ style = %s]\n",
 298                         before_name, after_name, style);
 299                 free(before_name);
 300                 free(after_name);
 301             }
 302         }
 303     }
 304 
 305     fprintf(dot_strm, "}\n");
 306     fflush(dot_strm);
 307     fclose(dot_strm);
 308     return pcmk_rc_ok;
 309 }
 310 
 311 /*!
 312  * \brief Profile the configuration updates and scheduler actions in a single
 313  *        CIB file, printing the profiling timings.
 314  *
 315  * \note \p data_set->priv must have been set to a valid \p pcmk__output_t
 316  *       object before this function is called.
 317  *
 318  * \param[in] xml_file  The CIB file to profile
 319  * \param[in] repeat    Number of times to run
 320  * \param[in] data_set  Working set for the cluster
 321  * \param[in] use_date  The date to set the cluster's time to (may be NULL)
 322  */
 323 static void
 324 profile_file(const char *xml_file, long long repeat, pe_working_set_t *data_set,
     /* [previous][next][first][last][top][bottom][index][help] */
 325              char *use_date)
 326 {
 327     pcmk__output_t *out = data_set->priv;
 328     xmlNode *cib_object = NULL;
 329     clock_t start = 0;
 330     clock_t end;
 331     unsigned long long data_set_flags = pe_flag_no_compat;
 332 
 333     CRM_ASSERT(out != NULL);
 334 
 335     cib_object = filename2xml(xml_file);
 336     start = clock();
 337 
 338     if (pcmk_find_cib_element(cib_object, XML_CIB_TAG_STATUS) == NULL) {
 339         create_xml_node(cib_object, XML_CIB_TAG_STATUS);
 340     }
 341 
 342     if (cli_config_update(&cib_object, NULL, FALSE) == FALSE) {
 343         free_xml(cib_object);
 344         return;
 345     }
 346 
 347     if (validate_xml(cib_object, NULL, FALSE) != TRUE) {
 348         free_xml(cib_object);
 349         return;
 350     }
 351 
 352     if (pcmk_is_set(data_set->flags, pe_flag_show_scores)) {
 353         data_set_flags |= pe_flag_show_scores;
 354     }
 355     if (pcmk_is_set(data_set->flags, pe_flag_show_utilization)) {
 356         data_set_flags |= pe_flag_show_utilization;
 357     }
 358 
 359     for (int i = 0; i < repeat; ++i) {
 360         xmlNode *input = (repeat == 1)? cib_object : copy_xml(cib_object);
 361 
 362         data_set->input = input;
 363         set_effective_date(data_set, false, use_date);
 364         pcmk__schedule_actions(input, data_set_flags, data_set);
 365         pe_reset_working_set(data_set);
 366     }
 367 
 368     end = clock();
 369     out->message(out, "profile", xml_file, start, end);
 370 }
 371 
 372 void
 373 pcmk__profile_dir(const char *dir, long long repeat, pe_working_set_t *data_set, char *use_date)
     /* [previous][next][first][last][top][bottom][index][help] */
 374 {
 375     pcmk__output_t *out = data_set->priv;
 376     struct dirent **namelist;
 377 
 378     int file_num = scandir(dir, &namelist, 0, alphasort);
 379 
 380     CRM_ASSERT(out != NULL);
 381 
 382     if (file_num > 0) {
 383         struct stat prop;
 384         char buffer[FILENAME_MAX];
 385 
 386         out->begin_list(out, NULL, NULL, "Timings");
 387 
 388         while (file_num--) {
 389             if ('.' == namelist[file_num]->d_name[0]) {
 390                 free(namelist[file_num]);
 391                 continue;
 392 
 393             } else if (!pcmk__ends_with_ext(namelist[file_num]->d_name,
 394                                             ".xml")) {
 395                 free(namelist[file_num]);
 396                 continue;
 397             }
 398             snprintf(buffer, sizeof(buffer), "%s/%s", dir, namelist[file_num]->d_name);
 399             if (stat(buffer, &prop) == 0 && S_ISREG(prop.st_mode)) {
 400                 profile_file(buffer, repeat, data_set, use_date);
 401             }
 402             free(namelist[file_num]);
 403         }
 404         free(namelist);
 405 
 406         out->end_list(out);
 407     }
 408 }
 409 
 410 /*!
 411  * \brief Set the date of the cluster, either to the value given by
 412  *        \p use_date, or to the "execution-date" value in the CIB.
 413  *
 414  * \note \p data_set->priv must have been set to a valid \p pcmk__output_t
 415  *       object before this function is called.
 416  *
 417  * \param[in,out] data_set        Working set for the cluster
 418  * \param[in]     print_original  If \p true, the "execution-date" should
 419  *                                also be printed
 420  * \param[in]     use_date        The date to set the cluster's time to
 421  *                                (may be NULL)
 422  */
 423 static void
 424 set_effective_date(pe_working_set_t *data_set, bool print_original,
     /* [previous][next][first][last][top][bottom][index][help] */
 425                    char *use_date)
 426 {
 427     pcmk__output_t *out = data_set->priv;
 428     time_t original_date = 0;
 429 
 430     CRM_ASSERT(out != NULL);
 431 
 432     crm_element_value_epoch(data_set->input, "execution-date", &original_date);
 433 
 434     if (use_date) {
 435         data_set->now = crm_time_new(use_date);
 436         out->info(out, "Setting effective cluster time: %s", use_date);
 437         crm_time_log(LOG_NOTICE, "Pretending 'now' is", data_set->now,
 438                      crm_time_log_date | crm_time_log_timeofday);
 439 
 440     } else if (original_date) {
 441 
 442         data_set->now = crm_time_new(NULL);
 443         crm_time_set_timet(data_set->now, &original_date);
 444 
 445         if (print_original) {
 446             char *when = crm_time_as_string(data_set->now,
 447                             crm_time_log_date|crm_time_log_timeofday);
 448 
 449             out->info(out, "Using the original execution date of: %s", when);
 450             free(when);
 451         }
 452     }
 453 }
 454 
 455 /*!
 456  * \internal
 457  * \brief Simulate successfully executing a pseudo-action in a graph
 458  *
 459  * \param[in] graph   Graph to update with pseudo-action result
 460  * \param[in] action  Pseudo-action to simulate executing
 461  *
 462  * \return Standard Pacemaker return code
 463  */
 464 static int
 465 simulate_pseudo_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
     /* [previous][next][first][last][top][bottom][index][help] */
 466 {
 467     const char *node = crm_element_value(action->xml, XML_LRM_ATTR_TARGET);
 468     const char *task = crm_element_value(action->xml, XML_LRM_ATTR_TASK_KEY);
 469 
 470     pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
 471     out->message(out, "inject-pseudo-action", node, task);
 472 
 473     pcmk__update_graph(graph, action);
 474     return pcmk_rc_ok;
 475 }
 476 
 477 /*!
 478  * \internal
 479  * \brief Simulate executing a resource action in a graph
 480  *
 481  * \param[in] graph   Graph to update with resource action result
 482  * \param[in] action  Resource action to simulate executing
 483  *
 484  * \return Standard Pacemaker return code
 485  */
 486 static int
 487 simulate_resource_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
     /* [previous][next][first][last][top][bottom][index][help] */
 488 {
 489     int rc;
 490     lrmd_event_data_t *op = NULL;
 491     int target_outcome = PCMK_OCF_OK;
 492 
 493     const char *rtype = NULL;
 494     const char *rclass = NULL;
 495     const char *resource = NULL;
 496     const char *rprovider = NULL;
 497     const char *resource_config_name = NULL;
 498     const char *operation = crm_element_value(action->xml, "operation");
 499     const char *target_rc_s = crm_meta_value(action->params,
 500                                              XML_ATTR_TE_TARGET_RC);
 501 
 502     xmlNode *cib_node = NULL;
 503     xmlNode *cib_resource = NULL;
 504     xmlNode *action_rsc = first_named_child(action->xml, XML_CIB_TAG_RESOURCE);
 505 
 506     char *node = crm_element_value_copy(action->xml, XML_LRM_ATTR_TARGET);
 507     char *uuid = NULL;
 508     const char *router_node = crm_element_value(action->xml,
 509                                                 XML_LRM_ATTR_ROUTER_NODE);
 510 
 511     // Certain actions don't need to be displayed or history entries
 512     if (pcmk__str_eq(operation, CRM_OP_REPROBE, pcmk__str_none)) {
 513         crm_debug("No history injection for %s op on %s", operation, node);
 514         goto done; // Confirm action and update graph
 515     }
 516 
 517     if (action_rsc == NULL) { // Shouldn't be possible
 518         crm_log_xml_err(action->xml, "Bad");
 519         free(node);
 520         return EPROTO;
 521     }
 522 
 523     /* A resource might be known by different names in the configuration and in
 524      * the action (for example, a clone instance). Grab the configuration name
 525      * (which is preferred when writing history), and if necessary, the instance
 526      * name.
 527      */
 528     resource_config_name = crm_element_value(action_rsc, XML_ATTR_ID);
 529     if (resource_config_name == NULL) { // Shouldn't be possible
 530         crm_log_xml_err(action->xml, "No ID");
 531         free(node);
 532         return EPROTO;
 533     }
 534     resource = resource_config_name;
 535     if (pe_find_resource(fake_resource_list, resource) == NULL) {
 536         const char *longname = crm_element_value(action_rsc, XML_ATTR_ID_LONG);
 537 
 538         if ((longname != NULL)
 539             && (pe_find_resource(fake_resource_list, longname) != NULL)) {
 540             resource = longname;
 541         }
 542     }
 543 
 544     // Certain actions need to be displayed but don't need history entries
 545     if (pcmk__strcase_any_of(operation, "delete", RSC_METADATA, NULL)) {
 546         out->message(out, "inject-rsc-action", resource, operation, node,
 547                      (guint) 0);
 548         goto done; // Confirm action and update graph
 549     }
 550 
 551     rclass = crm_element_value(action_rsc, XML_AGENT_ATTR_CLASS);
 552     rtype = crm_element_value(action_rsc, XML_ATTR_TYPE);
 553     rprovider = crm_element_value(action_rsc, XML_AGENT_ATTR_PROVIDER);
 554 
 555     pcmk__scan_min_int(target_rc_s, &target_outcome, 0);
 556 
 557     CRM_ASSERT(fake_cib->cmds->query(fake_cib, NULL, NULL,
 558                                      cib_sync_call|cib_scope_local) == pcmk_ok);
 559 
 560     // Ensure the action node is in the CIB
 561     uuid = crm_element_value_copy(action->xml, XML_LRM_ATTR_TARGET_UUID);
 562     cib_node = pcmk__inject_node(fake_cib, node,
 563                                  ((router_node == NULL)? uuid: node));
 564     free(uuid);
 565     CRM_ASSERT(cib_node != NULL);
 566 
 567     // Add a history entry for the action
 568     cib_resource = pcmk__inject_resource_history(out, cib_node, resource,
 569                                                  resource_config_name,
 570                                                  rclass, rtype, rprovider);
 571     if (cib_resource == NULL) {
 572         crm_err("Could not simulate action %d history for resource %s",
 573                 action->id, resource);
 574         free(node);
 575         free_xml(cib_node);
 576         return EINVAL;
 577     }
 578 
 579     // Simulate and display an executor event for the action result
 580     op = pcmk__event_from_graph_action(cib_resource, action, PCMK_EXEC_DONE,
 581                                        target_outcome, "User-injected result");
 582     out->message(out, "inject-rsc-action", resource, op->op_type, node,
 583                  op->interval_ms);
 584 
 585     // Check whether action is in a list of desired simulated failures
 586     for (GList *iter = fake_op_fail_list; iter != NULL; iter = iter->next) {
 587         char *spec = (char *) iter->data;
 588         char *key = NULL;
 589         const char *match_name = NULL;
 590 
 591         // Allow user to specify anonymous clone with or without instance number
 592         key = crm_strdup_printf(PCMK__OP_FMT "@%s=", resource, op->op_type,
 593                                 op->interval_ms, node);
 594         if (strncasecmp(key, spec, strlen(key)) == 0) {
 595             match_name = resource;
 596         }
 597         free(key);
 598 
 599         // If not found, try the resource's name in the configuration
 600         if ((match_name == NULL)
 601             && (strcmp(resource, resource_config_name) != 0)) {
 602 
 603             key = crm_strdup_printf(PCMK__OP_FMT "@%s=", resource_config_name,
 604                                     op->op_type, op->interval_ms, node);
 605             if (strncasecmp(key, spec, strlen(key)) == 0) {
 606                 match_name = resource_config_name;
 607             }
 608             free(key);
 609         }
 610 
 611         if (match_name == NULL) {
 612             continue; // This failed action entry doesn't match
 613         }
 614 
 615         // ${match_name}_${task}_${interval_in_ms}@${node}=${rc}
 616         rc = sscanf(spec, "%*[^=]=%d", (int *) &op->rc);
 617         if (rc != 1) {
 618             out->err(out, "Invalid failed operation '%s' "
 619                           "(result code must be integer)", spec);
 620             continue; // Keep checking other list entries
 621         }
 622 
 623         out->info(out, "Pretending action %d failed with rc=%d",
 624                   action->id, op->rc);
 625         pcmk__set_graph_action_flags(action, pcmk__graph_action_failed);
 626         graph->abort_priority = INFINITY;
 627         pcmk__inject_failcount(out, cib_node, match_name, op->op_type,
 628                                op->interval_ms, op->rc);
 629         break;
 630     }
 631 
 632     pcmk__inject_action_result(cib_resource, op, target_outcome);
 633     lrmd_free_event(op);
 634     rc = fake_cib->cmds->modify(fake_cib, XML_CIB_TAG_STATUS, cib_node,
 635                                 cib_sync_call|cib_scope_local);
 636     CRM_ASSERT(rc == pcmk_ok);
 637 
 638   done:
 639     free(node);
 640     free_xml(cib_node);
 641     pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
 642     pcmk__update_graph(graph, action);
 643     return pcmk_rc_ok;
 644 }
 645 
 646 /*!
 647  * \internal
 648  * \brief Simulate successfully executing a cluster action
 649  *
 650  * \param[in] graph   Graph to update with action result
 651  * \param[in] action  Cluster action to simulate
 652  *
 653  * \return Standard Pacemaker return code
 654  */
 655 static int
 656 simulate_cluster_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
     /* [previous][next][first][last][top][bottom][index][help] */
 657 {
 658     const char *node = crm_element_value(action->xml, XML_LRM_ATTR_TARGET);
 659     const char *task = crm_element_value(action->xml, XML_LRM_ATTR_TASK);
 660     xmlNode *rsc = first_named_child(action->xml, XML_CIB_TAG_RESOURCE);
 661 
 662     pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
 663     out->message(out, "inject-cluster-action", node, task, rsc);
 664     pcmk__update_graph(graph, action);
 665     return pcmk_rc_ok;
 666 }
 667 
 668 /*!
 669  * \internal
 670  * \brief Simulate successfully executing a fencing action
 671  *
 672  * \param[in] graph   Graph to update with action result
 673  * \param[in] action  Fencing action to simulate
 674  *
 675  * \return Standard Pacemaker return code
 676  */
 677 static int
 678 simulate_fencing_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
     /* [previous][next][first][last][top][bottom][index][help] */
 679 {
 680     const char *op = crm_meta_value(action->params, "stonith_action");
 681     char *target = crm_element_value_copy(action->xml, XML_LRM_ATTR_TARGET);
 682 
 683     out->message(out, "inject-fencing-action", target, op);
 684 
 685     if (!pcmk__str_eq(op, "on", pcmk__str_casei)) {
 686         int rc = pcmk_ok;
 687         GString *xpath = g_string_sized_new(512);
 688 
 689         // Set node state to offline
 690         xmlNode *cib_node = pcmk__inject_node_state_change(fake_cib, target,
 691                                                            false);
 692 
 693         CRM_ASSERT(cib_node != NULL);
 694         crm_xml_add(cib_node, XML_ATTR_ORIGIN, __func__);
 695         rc = fake_cib->cmds->replace(fake_cib, XML_CIB_TAG_STATUS, cib_node,
 696                                      cib_sync_call|cib_scope_local);
 697         CRM_ASSERT(rc == pcmk_ok);
 698 
 699         // Simulate controller clearing node's resource history and attributes
 700         pcmk__g_strcat(xpath,
 701                        "//" XML_CIB_TAG_STATE
 702                        "[@" XML_ATTR_UNAME "='", target, "']/" XML_CIB_TAG_LRM,
 703                        NULL);
 704         fake_cib->cmds->remove(fake_cib, (const char *) xpath->str, NULL,
 705                                cib_xpath|cib_sync_call|cib_scope_local);
 706 
 707         g_string_truncate(xpath, 0);
 708         pcmk__g_strcat(xpath,
 709                        "//" XML_CIB_TAG_STATE
 710                        "[@" XML_ATTR_UNAME "='", target, "']"
 711                        "/" XML_TAG_TRANSIENT_NODEATTRS, NULL);
 712         fake_cib->cmds->remove(fake_cib, (const char *) xpath->str, NULL,
 713                                cib_xpath|cib_sync_call|cib_scope_local);
 714 
 715         free_xml(cib_node);
 716         g_string_free(xpath, TRUE);
 717     }
 718 
 719     pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
 720     pcmk__update_graph(graph, action);
 721     free(target);
 722     return pcmk_rc_ok;
 723 }
 724 
 725 enum pcmk__graph_status
 726 pcmk__simulate_transition(pe_working_set_t *data_set, cib_t *cib,
     /* [previous][next][first][last][top][bottom][index][help] */
 727                           GList *op_fail_list)
 728 {
 729     pcmk__graph_t *transition = NULL;
 730     enum pcmk__graph_status graph_rc;
 731 
 732     pcmk__graph_functions_t simulation_fns = {
 733         simulate_pseudo_action,
 734         simulate_resource_action,
 735         simulate_cluster_action,
 736         simulate_fencing_action,
 737     };
 738 
 739     out = data_set->priv;
 740 
 741     fake_cib = cib;
 742     fake_op_fail_list = op_fail_list;
 743 
 744     if (!out->is_quiet(out)) {
 745         out->begin_list(out, NULL, NULL, "Executing Cluster Transition");
 746     }
 747 
 748     pcmk__set_graph_functions(&simulation_fns);
 749     transition = pcmk__unpack_graph(data_set->graph, crm_system_name);
 750     pcmk__log_graph(LOG_DEBUG, transition);
 751 
 752     fake_resource_list = data_set->resources;
 753     do {
 754         graph_rc = pcmk__execute_graph(transition);
 755     } while (graph_rc == pcmk__graph_active);
 756     fake_resource_list = NULL;
 757 
 758     if (graph_rc != pcmk__graph_complete) {
 759         out->err(out, "Transition failed: %s",
 760                  pcmk__graph_status2text(graph_rc));
 761         pcmk__log_graph(LOG_ERR, transition);
 762         out->err(out, "An invalid transition was produced");
 763     }
 764     pcmk__free_graph(transition);
 765 
 766     if (!out->is_quiet(out)) {
 767         // If not quiet, we'll need the resulting CIB for later display
 768         xmlNode *cib_object = NULL;
 769         int rc = fake_cib->cmds->query(fake_cib, NULL, &cib_object,
 770                                        cib_sync_call|cib_scope_local);
 771 
 772         CRM_ASSERT(rc == pcmk_ok);
 773         pe_reset_working_set(data_set);
 774         data_set->input = cib_object;
 775         out->end_list(out);
 776     }
 777     return graph_rc;
 778 }
 779 
 780 int
 781 pcmk__simulate(pe_working_set_t *data_set, pcmk__output_t *out,
     /* [previous][next][first][last][top][bottom][index][help] */
 782                pcmk_injections_t *injections, unsigned int flags,
 783                uint32_t section_opts, char *use_date, char *input_file,
 784                char *graph_file, char *dot_file)
 785 {
 786     int printed = pcmk_rc_no_output;
 787     int rc = pcmk_rc_ok;
 788     xmlNodePtr input = NULL;
 789     cib_t *cib = NULL;
 790 
 791     rc = cib__signon_query(&cib, &input);
 792     if (rc != pcmk_rc_ok) {
 793         goto simulate_done;
 794     }
 795 
 796     reset(data_set, input, out, use_date, flags);
 797     cluster_status(data_set);
 798 
 799     if (!out->is_quiet(out)) {
 800         if (pcmk_is_set(data_set->flags, pe_flag_maintenance_mode)) {
 801             printed = out->message(out, "maint-mode", data_set->flags);
 802         }
 803 
 804         if (data_set->disabled_resources || data_set->blocked_resources) {
 805             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
 806             printed = out->info(out,
 807                                 "%d of %d resource instances DISABLED and "
 808                                 "%d BLOCKED from further action due to failure",
 809                                 data_set->disabled_resources,
 810                                 data_set->ninstances,
 811                                 data_set->blocked_resources);
 812         }
 813 
 814         /* Most formatted output headers use caps for each word, but this one
 815          * only has the first word capitalized for compatibility with pcs.
 816          */
 817         print_cluster_status(data_set,
 818                              pcmk_is_set(flags, pcmk_sim_show_pending)? pcmk_show_pending : 0,
 819                              section_opts, "Current cluster status",
 820                              (printed == pcmk_rc_ok));
 821         printed = pcmk_rc_ok;
 822     }
 823 
 824     // If the user requested any injections, handle them
 825     if ((injections->node_down != NULL)
 826         || (injections->node_fail != NULL)
 827         || (injections->node_up != NULL)
 828         || (injections->op_inject != NULL)
 829         || (injections->ticket_activate != NULL)
 830         || (injections->ticket_grant != NULL)
 831         || (injections->ticket_revoke != NULL)
 832         || (injections->ticket_standby != NULL)
 833         || (injections->watchdog != NULL)) {
 834 
 835         PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
 836         pcmk__inject_scheduler_input(data_set, cib, injections);
 837         printed = pcmk_rc_ok;
 838 
 839         rc = cib->cmds->query(cib, NULL, &input, cib_sync_call);
 840         if (rc != pcmk_rc_ok) {
 841             rc = pcmk_legacy2rc(rc);
 842             goto simulate_done;
 843         }
 844 
 845         cleanup_calculations(data_set);
 846         reset(data_set, input, out, use_date, flags);
 847         cluster_status(data_set);
 848     }
 849 
 850     if (input_file != NULL) {
 851         rc = write_xml_file(input, input_file, FALSE);
 852         if (rc < 0) {
 853             rc = pcmk_legacy2rc(rc);
 854             goto simulate_done;
 855         }
 856     }
 857 
 858     if (pcmk_any_flags_set(flags, pcmk_sim_process | pcmk_sim_simulate)) {
 859         pcmk__output_t *logger_out = NULL;
 860         unsigned long long data_set_flags = pe_flag_no_compat;
 861 
 862         if (pcmk_is_set(data_set->flags, pe_flag_show_scores)) {
 863             data_set_flags |= pe_flag_show_scores;
 864         }
 865         if (pcmk_is_set(data_set->flags, pe_flag_show_utilization)) {
 866             data_set_flags |= pe_flag_show_utilization;
 867         }
 868 
 869         if (pcmk_all_flags_set(data_set->flags,
 870                                pe_flag_show_scores|pe_flag_show_utilization)) {
 871             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
 872             out->begin_list(out, NULL, NULL,
 873                             "Allocation Scores and Utilization Information");
 874             printed = pcmk_rc_ok;
 875 
 876         } else if (pcmk_is_set(data_set->flags, pe_flag_show_scores)) {
 877             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
 878             out->begin_list(out, NULL, NULL, "Allocation Scores");
 879             printed = pcmk_rc_ok;
 880 
 881         } else if (pcmk_is_set(data_set->flags, pe_flag_show_utilization)) {
 882             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
 883             out->begin_list(out, NULL, NULL, "Utilization Information");
 884             printed = pcmk_rc_ok;
 885 
 886         } else {
 887             rc = pcmk__log_output_new(&logger_out);
 888             if (rc != pcmk_rc_ok) {
 889                 goto simulate_done;
 890             }
 891             pe__register_messages(logger_out);
 892             pcmk__register_lib_messages(logger_out);
 893             data_set->priv = logger_out;
 894         }
 895 
 896         pcmk__schedule_actions(input, data_set_flags, data_set);
 897 
 898         if (logger_out == NULL) {
 899             out->end_list(out);
 900         } else {
 901             logger_out->finish(logger_out, CRM_EX_OK, true, NULL);
 902             pcmk__output_free(logger_out);
 903             data_set->priv = out;
 904         }
 905 
 906         input = NULL;           /* Don't try and free it twice */
 907 
 908         if (graph_file != NULL) {
 909             rc = write_xml_file(data_set->graph, graph_file, FALSE);
 910             if (rc < 0) {
 911                 rc = pcmk_rc_graph_error;
 912                 goto simulate_done;
 913             }
 914         }
 915 
 916         if (dot_file != NULL) {
 917             rc = write_sim_dotfile(data_set, dot_file,
 918                                    pcmk_is_set(flags, pcmk_sim_all_actions),
 919                                    pcmk_is_set(flags, pcmk_sim_verbose));
 920             if (rc != pcmk_rc_ok) {
 921                 rc = pcmk_rc_dot_error;
 922                 goto simulate_done;
 923             }
 924         }
 925 
 926         if (!out->is_quiet(out)) {
 927             print_transition_summary(data_set, printed == pcmk_rc_ok);
 928         }
 929     }
 930 
 931     rc = pcmk_rc_ok;
 932 
 933     if (!pcmk_is_set(flags, pcmk_sim_simulate)) {
 934         goto simulate_done;
 935     }
 936 
 937     PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
 938     if (pcmk__simulate_transition(data_set, cib,
 939                                   injections->op_fail) != pcmk__graph_complete) {
 940         rc = pcmk_rc_invalid_transition;
 941     }
 942 
 943     if (out->is_quiet(out)) {
 944         goto simulate_done;
 945     }
 946 
 947     set_effective_date(data_set, true, use_date);
 948 
 949     if (pcmk_is_set(flags, pcmk_sim_show_scores)) {
 950         pe__set_working_set_flags(data_set, pe_flag_show_scores);
 951     }
 952     if (pcmk_is_set(flags, pcmk_sim_show_utilization)) {
 953         pe__set_working_set_flags(data_set, pe_flag_show_utilization);
 954     }
 955 
 956     cluster_status(data_set);
 957     print_cluster_status(data_set, 0, section_opts, "Revised Cluster Status",
 958                          true);
 959 
 960 simulate_done:
 961     cib__clean_up_connection(&cib);
 962     return rc;
 963 }
 964 
 965 int
 966 pcmk_simulate(xmlNodePtr *xml, pe_working_set_t *data_set,
     /* [previous][next][first][last][top][bottom][index][help] */
 967               pcmk_injections_t *injections, unsigned int flags,
 968               unsigned int section_opts, char *use_date, char *input_file,
 969               char *graph_file, char *dot_file)
 970 {
 971     pcmk__output_t *out = NULL;
 972     int rc = pcmk_rc_ok;
 973 
 974     rc = pcmk__xml_output_new(&out, xml);
 975     if (rc != pcmk_rc_ok) {
 976         return rc;
 977     }
 978 
 979     pe__register_messages(out);
 980     pcmk__register_lib_messages(out);
 981 
 982     rc = pcmk__simulate(data_set, out, injections, flags, section_opts,
 983                         use_date, input_file, graph_file, dot_file);
 984     pcmk__xml_output_finish(out, xml);
 985     return rc;
 986 }

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