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 *history_id = NULL;
  50     const char *task = action->task;
  51 
  52     if (action->node != NULL) {
  53         action_host = action->node->priv->name;
  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         history_id = action->rsc->priv->history_id;
  65     }
  66 
  67     if (history_id != 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) && (n_task != NULL));
  84             key = pcmk__notify_key(history_id, n_type, n_task);
  85         } else {
  86             key = pcmk__op_key(history_id, task, interval_ms);
  87         }
  88 
  89         if (action_host != NULL) {
  90             action_name = crm_strdup_printf("%s%s %s",
  91                                             prefix, key, action_host);
  92         } else {
  93             action_name = crm_strdup_printf("%s%s", prefix, key);
  94         }
  95         free(key);
  96 
  97     } else if (pcmk__str_eq(action->task, PCMK_ACTION_STONITH,
  98                             pcmk__str_none)) {
  99         const char *op = g_hash_table_lookup(action->meta,
 100                                              PCMK__META_STONITH_ACTION);
 101 
 102         action_name = crm_strdup_printf("%s%s '%s' %s",
 103                                         prefix, action->task, op, action_host);
 104 
 105     } else if (action->rsc && action_host) {
 106         action_name = crm_strdup_printf("%s%s %s",
 107                                         prefix, action->uuid, action_host);
 108 
 109     } else if (action_host) {
 110         action_name = crm_strdup_printf("%s%s %s",
 111                                         prefix, action->task, action_host);
 112 
 113     } else {
 114         action_name = crm_strdup_printf("%s", action->uuid);
 115     }
 116 
 117     if (verbose) {
 118         char *with_id = crm_strdup_printf("%s (%d)", action_name, action->id);
 119 
 120         free(action_name);
 121         action_name = with_id;
 122     }
 123     return action_name;
 124 }
 125 
 126 /*!
 127  * \internal
 128  * \brief Display the status of a cluster
 129  *
 130  * \param[in,out] scheduler     Scheduler data
 131  * \param[in]     show_opts     How to modify display (as pcmk_show_opt_e flags)
 132  * \param[in]     section_opts  Sections to display (as pcmk_section_e flags)
 133  * \param[in]     title         What to use as list title
 134  * \param[in]     print_spacer  Whether to display a spacer first
 135  */
 136 static void
 137 print_cluster_status(pcmk_scheduler_t *scheduler, uint32_t show_opts,
     /* [previous][next][first][last][top][bottom][index][help] */
 138                      uint32_t section_opts, const char *title,
 139                      bool print_spacer)
 140 {
 141     pcmk__output_t *out = scheduler->priv->out;
 142     GList *all = NULL;
 143     crm_exit_t stonith_rc = 0;
 144     enum pcmk_pacemakerd_state state = pcmk_pacemakerd_state_invalid;
 145 
 146     section_opts |= pcmk_section_nodes | pcmk_section_resources;
 147     show_opts |= pcmk_show_inactive_rscs | pcmk_show_failed_detail;
 148 
 149     all = g_list_prepend(all, (gpointer) "*");
 150 
 151     PCMK__OUTPUT_SPACER_IF(out, print_spacer);
 152     out->begin_list(out, NULL, NULL, "%s", title);
 153     out->message(out, "cluster-status",
 154                  scheduler, state, stonith_rc, NULL,
 155                  pcmk__fence_history_none, section_opts, show_opts, NULL,
 156                  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->out;
 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 = out;
 196     set_effective_date(scheduler, true, use_date);
 197     if (pcmk_is_set(flags, pcmk_sim_sanitized)) {
 198         pcmk__set_scheduler_flags(scheduler, pcmk__sched_sanitized);
 199     }
 200     if (pcmk_is_set(flags, pcmk_sim_show_scores)) {
 201         pcmk__set_scheduler_flags(scheduler, pcmk__sched_output_scores);
 202     }
 203     if (pcmk_is_set(flags, pcmk_sim_show_utilization)) {
 204         pcmk__set_scheduler_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->priv->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 = PCMK__VALUE_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         pcmk__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->priv->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->graphed) {
 288                 optional = false;
 289                 style = PCMK__VALUE_BOLD;
 290             } else if (before->flags == 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                        && before->flags != 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->out 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->out;
 333     xmlNode *cib_object = NULL;
 334     clock_t start = 0;
 335     clock_t end;
 336     unsigned long long scheduler_flags = pcmk__sched_none;
 337 
 338     pcmk__assert(out != NULL);
 339 
 340     cib_object = pcmk__xml_read(xml_file);
 341     start = clock();
 342 
 343     if (pcmk_find_cib_element(cib_object, PCMK_XE_STATUS) == NULL) {
 344         pcmk__xe_create(cib_object, PCMK_XE_STATUS);
 345     }
 346 
 347     if (pcmk__update_configured_schema(&cib_object, false) != pcmk_rc_ok) {
 348         pcmk__xml_free(cib_object);
 349         return;
 350     }
 351 
 352     if (!pcmk__validate_xml(cib_object, NULL, NULL, NULL)) {
 353         pcmk__xml_free(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 = cib_object;
 366 
 367         if (repeat > 1) {
 368             input = pcmk__xml_copy(NULL, cib_object);
 369         }
 370         scheduler->input = input;
 371         set_effective_date(scheduler, false, use_date);
 372         pcmk__schedule_actions(input, scheduler_flags, scheduler);
 373         pe_reset_working_set(scheduler);
 374     }
 375 
 376     end = clock();
 377     out->message(out, "profile", xml_file, start, end);
 378 }
 379 
 380 void
 381 pcmk__profile_dir(const char *dir, long long repeat,
     /* [previous][next][first][last][top][bottom][index][help] */
 382                   pcmk_scheduler_t *scheduler, const char *use_date)
 383 {
 384     pcmk__output_t *out = scheduler->priv->out;
 385     struct dirent **namelist;
 386 
 387     int file_num = scandir(dir, &namelist, 0, alphasort);
 388 
 389     pcmk__assert(out != NULL);
 390 
 391     if (file_num > 0) {
 392         struct stat prop;
 393         char buffer[FILENAME_MAX];
 394 
 395         out->begin_list(out, NULL, NULL, "Timings");
 396 
 397         while (file_num--) {
 398             if ('.' == namelist[file_num]->d_name[0]) {
 399                 free(namelist[file_num]);
 400                 continue;
 401 
 402             } else if (!pcmk__ends_with_ext(namelist[file_num]->d_name,
 403                                             ".xml")) {
 404                 free(namelist[file_num]);
 405                 continue;
 406             }
 407             snprintf(buffer, sizeof(buffer), "%s/%s",
 408                      dir, namelist[file_num]->d_name);
 409             if (stat(buffer, &prop) == 0 && S_ISREG(prop.st_mode)) {
 410                 profile_file(buffer, repeat, scheduler, use_date);
 411             }
 412             free(namelist[file_num]);
 413         }
 414         free(namelist);
 415 
 416         out->end_list(out);
 417     }
 418 }
 419 
 420 /*!
 421  * \brief Set the date of the cluster, either to the value given by
 422  *        \p use_date, or to the \c PCMK_XA_EXECUTION_DATE value in the CIB.
 423  *
 424  * \note \p scheduler->priv->out must have been set to a valid \p pcmk__output_t
 425  *       object before this function is called.
 426  *
 427  * \param[in,out] scheduler       Scheduler data
 428  * \param[in]     print_original  If \p true, the \c PCMK_XA_EXECUTION_DATE
 429  *                                should also be printed
 430  * \param[in]     use_date        The date to set the cluster's time to
 431  *                                (may be NULL)
 432  */
 433 static void
 434 set_effective_date(pcmk_scheduler_t *scheduler, bool print_original,
     /* [previous][next][first][last][top][bottom][index][help] */
 435                    const char *use_date)
 436 {
 437     pcmk__output_t *out = scheduler->priv->out;
 438     time_t original_date = 0;
 439 
 440     pcmk__assert(out != NULL);
 441 
 442     crm_element_value_epoch(scheduler->input, PCMK_XA_EXECUTION_DATE,
 443                             &original_date);
 444 
 445     if (use_date) {
 446         scheduler->priv->now = crm_time_new(use_date);
 447         out->info(out, "Setting effective cluster time: %s", use_date);
 448         crm_time_log(LOG_NOTICE, "Pretending 'now' is", scheduler->priv->now,
 449                      crm_time_log_date | crm_time_log_timeofday);
 450 
 451     } else if (original_date != 0) {
 452         scheduler->priv->now = pcmk__copy_timet(original_date);
 453 
 454         if (print_original) {
 455             char *when = crm_time_as_string(scheduler->priv->now,
 456                                             crm_time_log_date
 457                                             |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) == 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         pcmk__xml_free(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         const char *offset = NULL;
 604 
 605         // Allow user to specify anonymous clone with or without instance number
 606         key = crm_strdup_printf(PCMK__OP_FMT "@%s=", resource, op->op_type,
 607                                 op->interval_ms, node);
 608         if (strncasecmp(key, spec, strlen(key)) == 0) {
 609             match_name = resource;
 610         }
 611         free(key);
 612 
 613         // If not found, try the resource's name in the configuration
 614         if ((match_name == NULL)
 615             && (strcmp(resource, resource_config_name) != 0)) {
 616 
 617             key = crm_strdup_printf(PCMK__OP_FMT "@%s=", resource_config_name,
 618                                     op->op_type, op->interval_ms, node);
 619             if (strncasecmp(key, spec, strlen(key)) == 0) {
 620                 match_name = resource_config_name;
 621             }
 622             free(key);
 623         }
 624 
 625         if (match_name == NULL) {
 626             continue; // This failed action entry doesn't match
 627         }
 628 
 629         // ${match_name}_${task}_${interval_in_ms}@${node}=${rc}
 630         rc = sscanf(spec, "%*[^=]=%d", (int *) &op->rc);
 631         if (rc != 1) {
 632             out->err(out, "Invalid failed operation '%s' "
 633                           "(result code must be integer)", spec);
 634             continue; // Keep checking other list entries
 635         }
 636 
 637         out->info(out, "Pretending action %d failed with rc=%d",
 638                   action->id, op->rc);
 639         pcmk__set_graph_action_flags(action, pcmk__graph_action_failed);
 640         graph->abort_priority = PCMK_SCORE_INFINITY;
 641 
 642         if (pcmk__str_eq(op->op_type, PCMK_ACTION_START, pcmk__str_none)) {
 643             offset = pcmk__s(graph->failed_start_offset, PCMK_VALUE_INFINITY);
 644 
 645         } else if (pcmk__str_eq(op->op_type, PCMK_ACTION_STOP,
 646                                 pcmk__str_none)) {
 647             offset = pcmk__s(graph->failed_stop_offset, PCMK_VALUE_INFINITY);
 648         }
 649 
 650         pcmk__inject_failcount(out, fake_cib, cib_node, match_name, op->op_type,
 651                                op->interval_ms, op->rc,
 652                                pcmk_str_is_infinity(offset));
 653         break;
 654     }
 655 
 656     pcmk__inject_action_result(cib_resource, op, node, target_outcome);
 657     lrmd_free_event(op);
 658     rc = fake_cib->cmds->modify(fake_cib, PCMK_XE_STATUS, cib_node,
 659                                 cib_sync_call);
 660     pcmk__assert(rc == pcmk_ok);
 661 
 662   done:
 663     free(node);
 664     pcmk__xml_free(cib_node);
 665     pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
 666     pcmk__update_graph(graph, action);
 667     return pcmk_rc_ok;
 668 }
 669 
 670 /*!
 671  * \internal
 672  * \brief Simulate successfully executing a cluster action
 673  *
 674  * \param[in,out] graph   Graph to update with action result
 675  * \param[in,out] action  Cluster action to simulate
 676  *
 677  * \return Standard Pacemaker return code
 678  */
 679 static int
 680 simulate_cluster_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
     /* [previous][next][first][last][top][bottom][index][help] */
 681 {
 682     const char *node = crm_element_value(action->xml, PCMK__META_ON_NODE);
 683     const char *task = crm_element_value(action->xml, PCMK_XA_OPERATION);
 684     xmlNode *rsc = pcmk__xe_first_child(action->xml, PCMK_XE_PRIMITIVE, NULL,
 685                                         NULL);
 686 
 687     pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
 688     out->message(out, "inject-cluster-action", node, task, rsc);
 689     pcmk__update_graph(graph, action);
 690     return pcmk_rc_ok;
 691 }
 692 
 693 /*!
 694  * \internal
 695  * \brief Simulate successfully executing a fencing action
 696  *
 697  * \param[in,out] graph   Graph to update with action result
 698  * \param[in,out] action  Fencing action to simulate
 699  *
 700  * \return Standard Pacemaker return code
 701  */
 702 static int
 703 simulate_fencing_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
     /* [previous][next][first][last][top][bottom][index][help] */
 704 {
 705     const char *op = crm_meta_value(action->params, PCMK__META_STONITH_ACTION);
 706     char *target = crm_element_value_copy(action->xml, PCMK__META_ON_NODE);
 707 
 708     out->message(out, "inject-fencing-action", target, op);
 709 
 710     if (!pcmk__str_eq(op, PCMK_ACTION_ON, pcmk__str_casei)) {
 711         int rc = pcmk_ok;
 712         GString *xpath = g_string_sized_new(512);
 713 
 714         // Set node state to offline
 715         xmlNode *cib_node = pcmk__inject_node_state_change(fake_cib, target,
 716                                                            false);
 717 
 718         pcmk__assert(cib_node != NULL);
 719         crm_xml_add(cib_node, PCMK_XA_CRM_DEBUG_ORIGIN, __func__);
 720         rc = fake_cib->cmds->replace(fake_cib, PCMK_XE_STATUS, cib_node,
 721                                      cib_sync_call);
 722         pcmk__assert(rc == pcmk_ok);
 723 
 724         // Simulate controller clearing node's resource history and attributes
 725         pcmk__g_strcat(xpath,
 726                        "//" PCMK__XE_NODE_STATE
 727                        "[@" PCMK_XA_UNAME "='", target, "']/" PCMK__XE_LRM,
 728                        NULL);
 729         fake_cib->cmds->remove(fake_cib, (const char *) xpath->str, NULL,
 730                                cib_xpath|cib_sync_call);
 731 
 732         g_string_truncate(xpath, 0);
 733         pcmk__g_strcat(xpath,
 734                        "//" PCMK__XE_NODE_STATE
 735                        "[@" PCMK_XA_UNAME "='", target, "']"
 736                        "/" PCMK__XE_TRANSIENT_ATTRIBUTES, NULL);
 737         fake_cib->cmds->remove(fake_cib, (const char *) xpath->str, NULL,
 738                                cib_xpath|cib_sync_call);
 739 
 740         pcmk__xml_free(cib_node);
 741         g_string_free(xpath, TRUE);
 742     }
 743 
 744     pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
 745     pcmk__update_graph(graph, action);
 746     free(target);
 747     return pcmk_rc_ok;
 748 }
 749 
 750 enum pcmk__graph_status
 751 pcmk__simulate_transition(pcmk_scheduler_t *scheduler, cib_t *cib,
     /* [previous][next][first][last][top][bottom][index][help] */
 752                           const GList *op_fail_list)
 753 {
 754     pcmk__graph_t *transition = NULL;
 755     enum pcmk__graph_status graph_rc;
 756 
 757     pcmk__graph_functions_t simulation_fns = {
 758         simulate_pseudo_action,
 759         simulate_resource_action,
 760         simulate_cluster_action,
 761         simulate_fencing_action,
 762     };
 763 
 764     out = scheduler->priv->out;
 765 
 766     fake_cib = cib;
 767     fake_op_fail_list = op_fail_list;
 768 
 769     if (!out->is_quiet(out)) {
 770         out->begin_list(out, NULL, NULL, "Executing Cluster Transition");
 771     }
 772 
 773     pcmk__set_graph_functions(&simulation_fns);
 774     transition = pcmk__unpack_graph(scheduler->priv->graph, crm_system_name);
 775     pcmk__log_graph(LOG_DEBUG, transition);
 776 
 777     fake_resource_list = scheduler->priv->resources;
 778     do {
 779         graph_rc = pcmk__execute_graph(transition);
 780     } while (graph_rc == pcmk__graph_active);
 781     fake_resource_list = NULL;
 782 
 783     if (graph_rc != pcmk__graph_complete) {
 784         out->err(out, "Transition failed: %s",
 785                  pcmk__graph_status2text(graph_rc));
 786         pcmk__log_graph(LOG_ERR, transition);
 787         out->err(out, "An invalid transition was produced");
 788     }
 789     pcmk__free_graph(transition);
 790 
 791     if (!out->is_quiet(out)) {
 792         // If not quiet, we'll need the resulting CIB for later display
 793         xmlNode *cib_object = NULL;
 794         int rc = fake_cib->cmds->query(fake_cib, NULL, &cib_object,
 795                                        cib_sync_call);
 796 
 797         pcmk__assert(rc == pcmk_ok);
 798         pe_reset_working_set(scheduler);
 799         scheduler->input = cib_object;
 800         out->end_list(out);
 801     }
 802     return graph_rc;
 803 }
 804 
 805 int
 806 pcmk__simulate(pcmk_scheduler_t *scheduler, pcmk__output_t *out,
     /* [previous][next][first][last][top][bottom][index][help] */
 807                const pcmk_injections_t *injections, unsigned int flags,
 808                uint32_t section_opts, const char *use_date,
 809                const char *input_file, const char *graph_file,
 810                const char *dot_file)
 811 {
 812     int printed = pcmk_rc_no_output;
 813     int rc = pcmk_rc_ok;
 814     xmlNodePtr input = NULL;
 815     cib_t *cib = NULL;
 816 
 817     rc = cib__signon_query(out, &cib, &input);
 818     if (rc != pcmk_rc_ok) {
 819         goto simulate_done;
 820     }
 821 
 822     reset(scheduler, input, out, use_date, flags);
 823     cluster_status(scheduler);
 824 
 825     if (!out->is_quiet(out)) {
 826         const bool show_pending = pcmk_is_set(flags, pcmk_sim_show_pending);
 827 
 828         if (pcmk_is_set(scheduler->flags, pcmk__sched_in_maintenance)) {
 829             printed = out->message(out, "maint-mode", scheduler->flags);
 830         }
 831 
 832         if ((scheduler->priv->disabled_resources > 0)
 833             || (scheduler->priv->blocked_resources > 0)) {
 834 
 835             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
 836             printed = out->info(out,
 837                                 "%d of %d resource instances DISABLED and "
 838                                 "%d BLOCKED from further action due to failure",
 839                                 scheduler->priv->disabled_resources,
 840                                 scheduler->priv->ninstances,
 841                                 scheduler->priv->blocked_resources);
 842         }
 843 
 844         /* Most formatted output headers use caps for each word, but this one
 845          * only has the first word capitalized for compatibility with pcs.
 846          */
 847         print_cluster_status(scheduler, (show_pending? pcmk_show_pending : 0),
 848                              section_opts, "Current cluster status",
 849                              (printed == pcmk_rc_ok));
 850         printed = pcmk_rc_ok;
 851     }
 852 
 853     // If the user requested any injections, handle them
 854     if ((injections->node_down != NULL)
 855         || (injections->node_fail != NULL)
 856         || (injections->node_up != NULL)
 857         || (injections->op_inject != NULL)
 858         || (injections->ticket_activate != NULL)
 859         || (injections->ticket_grant != NULL)
 860         || (injections->ticket_revoke != NULL)
 861         || (injections->ticket_standby != NULL)
 862         || (injections->watchdog != NULL)) {
 863 
 864         PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
 865         pcmk__inject_scheduler_input(scheduler, cib, injections);
 866         printed = pcmk_rc_ok;
 867 
 868         rc = cib->cmds->query(cib, NULL, &input, cib_sync_call);
 869         if (rc != pcmk_rc_ok) {
 870             rc = pcmk_legacy2rc(rc);
 871             goto simulate_done;
 872         }
 873 
 874         cleanup_calculations(scheduler);
 875         reset(scheduler, input, out, use_date, flags);
 876         cluster_status(scheduler);
 877     }
 878 
 879     if (input_file != NULL) {
 880         rc = pcmk__xml_write_file(input, input_file, false);
 881         if (rc != pcmk_rc_ok) {
 882             goto simulate_done;
 883         }
 884     }
 885 
 886     if (pcmk_any_flags_set(flags, pcmk_sim_process | pcmk_sim_simulate)) {
 887         pcmk__output_t *logger_out = NULL;
 888         unsigned long long scheduler_flags = pcmk__sched_none;
 889 
 890         if (pcmk_is_set(scheduler->flags, pcmk__sched_output_scores)) {
 891             scheduler_flags |= pcmk__sched_output_scores;
 892         }
 893         if (pcmk_is_set(scheduler->flags, pcmk__sched_show_utilization)) {
 894             scheduler_flags |= pcmk__sched_show_utilization;
 895         }
 896 
 897         if (pcmk_all_flags_set(scheduler->flags,
 898                                pcmk__sched_output_scores
 899                                |pcmk__sched_show_utilization)) {
 900             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
 901             out->begin_list(out, NULL, NULL,
 902                             "Assignment Scores and Utilization Information");
 903             printed = pcmk_rc_ok;
 904 
 905         } else if (pcmk_is_set(scheduler->flags, pcmk__sched_output_scores)) {
 906             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
 907             out->begin_list(out, NULL, NULL, "Assignment Scores");
 908             printed = pcmk_rc_ok;
 909 
 910         } else if (pcmk_is_set(scheduler->flags, pcmk__sched_show_utilization)) {
 911             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
 912             out->begin_list(out, NULL, NULL, "Utilization Information");
 913             printed = pcmk_rc_ok;
 914 
 915         } else {
 916             rc = pcmk__log_output_new(&logger_out);
 917             if (rc != pcmk_rc_ok) {
 918                 goto simulate_done;
 919             }
 920             pe__register_messages(logger_out);
 921             pcmk__register_lib_messages(logger_out);
 922             scheduler->priv->out = logger_out;
 923         }
 924 
 925         pcmk__schedule_actions(input, scheduler_flags, scheduler);
 926 
 927         if (logger_out == NULL) {
 928             out->end_list(out);
 929         } else {
 930             logger_out->finish(logger_out, CRM_EX_OK, true, NULL);
 931             pcmk__output_free(logger_out);
 932             scheduler->priv->out = out;
 933         }
 934 
 935         input = NULL;           /* Don't try and free it twice */
 936 
 937         if (graph_file != NULL) {
 938             rc = pcmk__xml_write_file(scheduler->priv->graph, graph_file,
 939                                       false);
 940             if (rc != pcmk_rc_ok) {
 941                 rc = pcmk_rc_graph_error;
 942                 goto simulate_done;
 943             }
 944         }
 945 
 946         if (dot_file != NULL) {
 947             rc = write_sim_dotfile(scheduler, dot_file,
 948                                    pcmk_is_set(flags, pcmk_sim_all_actions),
 949                                    pcmk_is_set(flags, pcmk_sim_verbose));
 950             if (rc != pcmk_rc_ok) {
 951                 rc = pcmk_rc_dot_error;
 952                 goto simulate_done;
 953             }
 954         }
 955 
 956         if (!out->is_quiet(out)) {
 957             print_transition_summary(scheduler, printed == pcmk_rc_ok);
 958         }
 959     }
 960 
 961     rc = pcmk_rc_ok;
 962 
 963     if (!pcmk_is_set(flags, pcmk_sim_simulate)) {
 964         goto simulate_done;
 965     }
 966 
 967     PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
 968     if (pcmk__simulate_transition(scheduler, cib, injections->op_fail)
 969             != pcmk__graph_complete) {
 970         rc = pcmk_rc_invalid_transition;
 971     }
 972 
 973     if (out->is_quiet(out)) {
 974         goto simulate_done;
 975     }
 976 
 977     set_effective_date(scheduler, true, use_date);
 978 
 979     if (pcmk_is_set(flags, pcmk_sim_show_scores)) {
 980         pcmk__set_scheduler_flags(scheduler, pcmk__sched_output_scores);
 981     }
 982     if (pcmk_is_set(flags, pcmk_sim_show_utilization)) {
 983         pcmk__set_scheduler_flags(scheduler, pcmk__sched_show_utilization);
 984     }
 985 
 986     cluster_status(scheduler);
 987     print_cluster_status(scheduler, 0, section_opts, "Revised Cluster Status",
 988                          true);
 989 
 990 simulate_done:
 991     cib__clean_up_connection(&cib);
 992     return rc;
 993 }
 994 
 995 int
 996 pcmk_simulate(xmlNodePtr *xml, pcmk_scheduler_t *scheduler,
     /* [previous][next][first][last][top][bottom][index][help] */
 997               const pcmk_injections_t *injections, unsigned int flags,
 998               unsigned int section_opts, const char *use_date,
 999               const char *input_file, const char *graph_file,
1000               const char *dot_file)
1001 {
1002     pcmk__output_t *out = NULL;
1003     int rc = pcmk_rc_ok;
1004 
1005     rc = pcmk__xml_output_new(&out, xml);
1006     if (rc != pcmk_rc_ok) {
1007         return rc;
1008     }
1009 
1010     pe__register_messages(out);
1011     pcmk__register_lib_messages(out);
1012 
1013     rc = pcmk__simulate(scheduler, out, injections, flags, section_opts,
1014                         use_date, input_file, graph_file, dot_file);
1015     pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
1016     return rc;
1017 }

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