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

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