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_filter
  7. profile_file
  8. pcmk__profile_dir
  9. set_effective_date
  10. simulate_pseudo_action
  11. simulate_resource_action
  12. simulate_cluster_action
  13. simulate_fencing_action
  14. pcmk__simulate_transition
  15. pcmk__simulate
  16. pcmk_simulate

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

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