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

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