root/lib/pacemaker/pcmk_simulate.c

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

DEFINITIONS

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

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

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