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