This source file includes following definitions.
- in_place_cb
- live_check_cb
- node_down_cb
- node_fail_cb
- node_up_cb
- op_fail_cb
- op_inject_cb
- quorum_cb
- save_dotfile_cb
- save_graph_cb
- show_scores_cb
- simulate_cb
- ticket_activate_cb
- ticket_grant_cb
- ticket_revoke_cb
- ticket_standby_cb
- utilization_cb
- watchdog_cb
- xml_file_cb
- xml_pipe_cb
- get_date
- print_cluster_status
- create_action_name
- create_dotfile
- setup_input
- profile_one
- profile_all
- build_arg_context
- main
1
2
3
4
5
6
7
8
9
10 #include <crm_internal.h>
11
12 #include <stdio.h>
13 #include <unistd.h>
14 #include <stdlib.h>
15 #include <time.h>
16
17 #include <sys/stat.h>
18 #include <sys/param.h>
19 #include <sys/types.h>
20 #include <dirent.h>
21
22 #include <crm/crm.h>
23 #include <crm/cib.h>
24 #include <crm/common/cmdline_internal.h>
25 #include <crm/common/util.h>
26 #include <crm/common/iso8601.h>
27 #include <crm/pengine/status.h>
28 #include <pacemaker-internal.h>
29
30 #define SUMMARY "crm_simulate - simulate a Pacemaker cluster's response to events"
31
32
33
34
35
36 struct {
37 gboolean all_actions;
38 char *dot_file;
39 char *graph_file;
40 gchar *input_file;
41 guint modified;
42 GListPtr node_up;
43 GListPtr node_down;
44 GListPtr node_fail;
45 GListPtr op_fail;
46 GListPtr op_inject;
47 gchar *output_file;
48 gboolean print_pending;
49 gboolean process;
50 char *quorum;
51 long long repeat;
52 gboolean simulate;
53 gboolean store;
54 gchar *test_dir;
55 GListPtr ticket_grant;
56 GListPtr ticket_revoke;
57 GListPtr ticket_standby;
58 GListPtr ticket_activate;
59 char *use_date;
60 char *watchdog;
61 char *xml_file;
62 } options = {
63 .print_pending = TRUE,
64 .repeat = 1
65 };
66
67 cib_t *global_cib = NULL;
68 bool action_numbers = FALSE;
69 gboolean quiet = FALSE;
70 char *temp_shadow = NULL;
71 extern gboolean bringing_nodes_online;
72
73 #define quiet_log(fmt, args...) do { \
74 if(quiet == FALSE) { \
75 printf(fmt , ##args); \
76 } \
77 } while(0)
78
79 #define INDENT " "
80
81 static gboolean
82 in_place_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
83 options.store = TRUE;
84 options.process = TRUE;
85 options.simulate = TRUE;
86 return TRUE;
87 }
88
89 static gboolean
90 live_check_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
91 if (options.xml_file) {
92 free(options.xml_file);
93 }
94
95 options.xml_file = NULL;
96 return TRUE;
97 }
98
99 static gboolean
100 node_down_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
101 options.modified++;
102 options.node_down = g_list_append(options.node_down, (gchar *) g_strdup(optarg));
103 return TRUE;
104 }
105
106 static gboolean
107 node_fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
108 options.modified++;
109 options.node_fail = g_list_append(options.node_fail, (gchar *) g_strdup(optarg));
110 return TRUE;
111 }
112
113 static gboolean
114 node_up_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
115 options.modified++;
116 bringing_nodes_online = TRUE;
117 options.node_up = g_list_append(options.node_up, (gchar *) g_strdup(optarg));
118 return TRUE;
119 }
120
121 static gboolean
122 op_fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
123 options.process = TRUE;
124 options.simulate = TRUE;
125 options.op_fail = g_list_append(options.op_fail, (gchar *) g_strdup(optarg));
126 return TRUE;
127 }
128
129 static gboolean
130 op_inject_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
131 options.modified++;
132 options.op_inject = g_list_append(options.op_inject, (gchar *) g_strdup(optarg));
133 return TRUE;
134 }
135
136 static gboolean
137 quorum_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
138 if (options.quorum) {
139 free(options.quorum);
140 }
141
142 options.modified++;
143 options.quorum = strdup(optarg);
144 return TRUE;
145 }
146
147 static gboolean
148 save_dotfile_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
149 if (options.dot_file) {
150 free(options.dot_file);
151 }
152
153 options.process = TRUE;
154 options.dot_file = strdup(optarg);
155 return TRUE;
156 }
157
158 static gboolean
159 save_graph_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
160 if (options.graph_file) {
161 free(options.graph_file);
162 }
163
164 options.process = TRUE;
165 options.graph_file = strdup(optarg);
166 return TRUE;
167 }
168
169 static gboolean
170 show_scores_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
171 options.process = TRUE;
172 show_scores = TRUE;
173 return TRUE;
174 }
175
176 static gboolean
177 simulate_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
178 options.process = TRUE;
179 options.simulate = TRUE;
180 return TRUE;
181 }
182
183 static gboolean
184 ticket_activate_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
185 options.modified++;
186 options.ticket_activate = g_list_append(options.ticket_activate, (gchar *) g_strdup(optarg));
187 return TRUE;
188 }
189
190 static gboolean
191 ticket_grant_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
192 options.modified++;
193 options.ticket_grant = g_list_append(options.ticket_grant, (gchar *) g_strdup(optarg));
194 return TRUE;
195 }
196
197 static gboolean
198 ticket_revoke_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
199 options.modified++;
200 options.ticket_revoke = g_list_append(options.ticket_revoke, (gchar *) g_strdup(optarg));
201 return TRUE;
202 }
203
204 static gboolean
205 ticket_standby_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
206 options.modified++;
207 options.ticket_standby = g_list_append(options.ticket_standby, (gchar *) g_strdup(optarg));
208 return TRUE;
209 }
210
211 static gboolean
212 utilization_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
213 options.process = TRUE;
214 show_utilization = TRUE;
215 return TRUE;
216 }
217
218 static gboolean
219 watchdog_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
220 if (options.watchdog) {
221 free(options.watchdog);
222 }
223
224 options.modified++;
225 options.watchdog = strdup(optarg);
226 return TRUE;
227 }
228
229 static gboolean
230 xml_file_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
231 if (options.xml_file) {
232 free(options.xml_file);
233 }
234
235 options.xml_file = strdup(optarg);
236 return TRUE;
237 }
238
239 static gboolean
240 xml_pipe_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
241 if (options.xml_file) {
242 free(options.xml_file);
243 }
244
245 options.xml_file = strdup("-");
246 return TRUE;
247 }
248
249 static GOptionEntry operation_entries[] = {
250 { "run", 'R', 0, G_OPTION_ARG_NONE, &options.process,
251 "Determine cluster's response to the given configuration and status",
252 NULL },
253 { "simulate", 'S', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, simulate_cb,
254 "Simulate transition's execution and display resulting cluster status",
255 NULL },
256 { "in-place", 'X', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, in_place_cb,
257 "Simulate transition's execution and store result back to input file",
258 NULL },
259 { "show-scores", 's', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_scores_cb,
260 "Show allocation scores",
261 NULL },
262 { "show-utilization", 'U', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, utilization_cb,
263 "Show utilization information",
264 NULL },
265 { "profile", 'P', 0, G_OPTION_ARG_FILENAME, &options.test_dir,
266 "Run all tests in the named directory to create profiling data",
267 NULL },
268 { "repeat", 'N', 0, G_OPTION_ARG_INT, &options.repeat,
269 "With --profile, repeat each test N times and print timings",
270 "N" },
271 { "pending", 'j', 0, G_OPTION_ARG_NONE, &options.print_pending,
272 "Display pending state if 'record-pending' is enabled",
273 NULL },
274
275 { NULL }
276 };
277
278 static GOptionEntry synthetic_entries[] = {
279 { "node-up", 'u', 0, G_OPTION_ARG_CALLBACK, node_up_cb,
280 "Bring a node online",
281 "NODE" },
282 { "node-down", 'd', 0, G_OPTION_ARG_CALLBACK, node_down_cb,
283 "Take a node offline",
284 "NODE" },
285 { "node-fail", 'f', 0, G_OPTION_ARG_CALLBACK, node_fail_cb,
286 "Mark a node as failed",
287 "NODE" },
288 { "op-inject", 'i', 0, G_OPTION_ARG_CALLBACK, op_inject_cb,
289 "Generate a failure for the cluster to react to in the simulation.\n"
290 INDENT "See `Operation Specification` help for more information.",
291 "OPSPEC" },
292 { "op-fail", 'F', 0, G_OPTION_ARG_CALLBACK, op_fail_cb,
293 "If the specified task occurs during the simulation, have it fail with return code ${rc}.\n"
294 INDENT "The transition will normally stop at the failed action.\n"
295 INDENT "Save the result with --save-output and re-run with --xml-file.\n"
296 INDENT "See `Operation Specification` help for more information.",
297 "OPSPEC" },
298 { "set-datetime", 't', 0, G_OPTION_ARG_STRING, &options.use_date,
299 "Set date/time (ISO 8601 format, see https://en.wikipedia.org/wiki/ISO_8601)",
300 "DATETIME" },
301 { "quorum", 'q', 0, G_OPTION_ARG_CALLBACK, quorum_cb,
302 "Specify a value for quorum",
303 "QUORUM" },
304 { "watchdog", 'w', 0, G_OPTION_ARG_CALLBACK, watchdog_cb,
305 "Assume a watchdog device is active",
306 "DEVICE" },
307 { "ticket-grant", 'g', 0, G_OPTION_ARG_CALLBACK, ticket_grant_cb,
308 "Grant a ticket",
309 "TICKET" },
310 { "ticket-revoke", 'r', 0, G_OPTION_ARG_CALLBACK, ticket_revoke_cb,
311 "Revoke a ticket",
312 "TICKET" },
313 { "ticket-standby", 'b', 0, G_OPTION_ARG_CALLBACK, ticket_standby_cb,
314 "Make a ticket standby",
315 "TICKET" },
316 { "ticket-activate", 'e', 0, G_OPTION_ARG_CALLBACK, ticket_activate_cb,
317 "Activate a ticket",
318 "TICKET" },
319
320 { NULL }
321 };
322
323 static GOptionEntry output_entries[] = {
324 { "save-input", 'I', 0, G_OPTION_ARG_FILENAME, &options.input_file,
325 "Save the input configuration to the named file",
326 "FILE" },
327 { "save-output", 'O', 0, G_OPTION_ARG_FILENAME, &options.output_file,
328 "Save the output configuration to the named file",
329 "FILE" },
330 { "save-graph", 'G', 0, G_OPTION_ARG_CALLBACK, save_graph_cb,
331 "Save the transition graph (XML format) to the named file",
332 "FILE" },
333 { "save-dotfile", 'D', 0, G_OPTION_ARG_CALLBACK, save_dotfile_cb,
334 "Save the transition graph (DOT format) to the named file",
335 "FILE" },
336 { "all-actions", 'a', 0, G_OPTION_ARG_NONE, &options.all_actions,
337 "Display all possible actions in DOT graph (even if not part of transition)",
338 NULL },
339
340 { NULL }
341 };
342
343 static GOptionEntry source_entries[] = {
344 { "live-check", 'L', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, live_check_cb,
345 "Connect to CIB manager and use the current CIB contents as input",
346 NULL },
347 { "xml-file", 'x', 0, G_OPTION_ARG_CALLBACK, xml_file_cb,
348 "Retrieve XML from the named file",
349 "FILE" },
350 { "xml-pipe", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, xml_pipe_cb,
351 "Retrieve XML from stdin",
352 NULL },
353
354 { NULL }
355 };
356
357 static void
358 get_date(pe_working_set_t *data_set, bool print_original, char *use_date)
359 {
360 time_t original_date = 0;
361
362 crm_element_value_epoch(data_set->input, "execution-date", &original_date);
363
364 if (use_date) {
365 data_set->now = crm_time_new(use_date);
366 quiet_log(" + Setting effective cluster time: %s", use_date);
367 crm_time_log(LOG_NOTICE, "Pretending 'now' is", data_set->now,
368 crm_time_log_date | crm_time_log_timeofday);
369
370
371 } else if (original_date) {
372
373 data_set->now = crm_time_new(NULL);
374 crm_time_set_timet(data_set->now, &original_date);
375
376 if (print_original) {
377 char *when = crm_time_as_string(data_set->now,
378 crm_time_log_date|crm_time_log_timeofday);
379
380 printf("Using the original execution date of: %s\n", when);
381 free(when);
382 }
383 }
384 }
385
386 static void
387 print_cluster_status(pe_working_set_t * data_set, long options)
388 {
389 char *online_nodes = NULL;
390 char *online_remote_nodes = NULL;
391 char *online_guest_nodes = NULL;
392 char *offline_nodes = NULL;
393 char *offline_remote_nodes = NULL;
394
395 size_t online_nodes_len = 0;
396 size_t online_remote_nodes_len = 0;
397 size_t online_guest_nodes_len = 0;
398 size_t offline_nodes_len = 0;
399 size_t offline_remote_nodes_len = 0;
400
401 GListPtr gIter = NULL;
402
403 for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
404 pe_node_t *node = (pe_node_t *) gIter->data;
405 const char *node_mode = NULL;
406 char *node_name = NULL;
407
408 if (pe__is_guest_node(node)) {
409 node_name = crm_strdup_printf("%s:%s", node->details->uname, node->details->remote_rsc->container->id);
410 } else {
411 node_name = crm_strdup_printf("%s", node->details->uname);
412 }
413
414 if (node->details->unclean) {
415 if (node->details->online && node->details->unclean) {
416 node_mode = "UNCLEAN (online)";
417
418 } else if (node->details->pending) {
419 node_mode = "UNCLEAN (pending)";
420
421 } else {
422 node_mode = "UNCLEAN (offline)";
423 }
424
425 } else if (node->details->pending) {
426 node_mode = "pending";
427
428 } else if (node->details->standby_onfail && node->details->online) {
429 node_mode = "standby (on-fail)";
430
431 } else if (node->details->standby) {
432 if (node->details->online) {
433 node_mode = "standby";
434 } else {
435 node_mode = "OFFLINE (standby)";
436 }
437
438 } else if (node->details->maintenance) {
439 if (node->details->online) {
440 node_mode = "maintenance";
441 } else {
442 node_mode = "OFFLINE (maintenance)";
443 }
444
445 } else if (node->details->online) {
446 if (pe__is_guest_node(node)) {
447 pcmk__add_word(&online_guest_nodes, &online_guest_nodes_len,
448 node_name);
449 } else if (pe__is_remote_node(node)) {
450 pcmk__add_word(&online_remote_nodes, &online_remote_nodes_len,
451 node_name);
452 } else {
453 pcmk__add_word(&online_nodes, &online_nodes_len, node_name);
454 }
455 free(node_name);
456 continue;
457
458 } else {
459 if (pe__is_remote_node(node)) {
460 pcmk__add_word(&offline_remote_nodes, &offline_remote_nodes_len,
461 node_name);
462 } else if (pe__is_guest_node(node)) {
463
464 } else {
465 pcmk__add_word(&offline_nodes, &offline_nodes_len, node_name);
466 }
467 free(node_name);
468 continue;
469 }
470
471 if (pe__is_guest_node(node)) {
472 printf("GuestNode %s: %s\n", node_name, node_mode);
473 } else if (pe__is_remote_node(node)) {
474 printf("RemoteNode %s: %s\n", node_name, node_mode);
475 } else if (pcmk__str_eq(node->details->uname, node->details->id, pcmk__str_casei)) {
476 printf("Node %s: %s\n", node_name, node_mode);
477 } else {
478 printf("Node %s (%s): %s\n", node_name, node->details->id, node_mode);
479 }
480
481 free(node_name);
482 }
483
484 if (online_nodes) {
485 printf("Online: [ %s ]\n", online_nodes);
486 free(online_nodes);
487 }
488 if (offline_nodes) {
489 printf("OFFLINE: [ %s ]\n", offline_nodes);
490 free(offline_nodes);
491 }
492 if (online_remote_nodes) {
493 printf("RemoteOnline: [ %s ]\n", online_remote_nodes);
494 free(online_remote_nodes);
495 }
496 if (offline_remote_nodes) {
497 printf("RemoteOFFLINE: [ %s ]\n", offline_remote_nodes);
498 free(offline_remote_nodes);
499 }
500 if (online_guest_nodes) {
501 printf("GuestOnline: [ %s ]\n", online_guest_nodes);
502 free(online_guest_nodes);
503 }
504
505 fprintf(stdout, "\n");
506 for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
507 pe_resource_t *rsc = (pe_resource_t *) gIter->data;
508
509 if (pcmk_is_set(rsc->flags, pe_rsc_orphan)
510 && rsc->role == RSC_ROLE_STOPPED) {
511 continue;
512 }
513 rsc->fns->print(rsc, NULL, pe_print_printf | options, stdout);
514 }
515 fprintf(stdout, "\n");
516 }
517
518 static char *
519 create_action_name(pe_action_t *action)
520 {
521 char *action_name = NULL;
522 const char *prefix = "";
523 const char *action_host = NULL;
524 const char *clone_name = NULL;
525 const char *task = action->task;
526
527 if (action->node) {
528 action_host = action->node->details->uname;
529 } else if (!pcmk_is_set(action->flags, pe_action_pseudo)) {
530 action_host = "<none>";
531 }
532
533 if (pcmk__str_eq(action->task, RSC_CANCEL, pcmk__str_casei)) {
534 prefix = "Cancel ";
535 task = action->cancel_task;
536 }
537
538 if (action->rsc && action->rsc->clone_name) {
539 clone_name = action->rsc->clone_name;
540 }
541
542 if (clone_name) {
543 char *key = NULL;
544 guint interval_ms = 0;
545
546 if (pcmk__guint_from_hash(action->meta,
547 XML_LRM_ATTR_INTERVAL_MS, 0,
548 &interval_ms) != pcmk_rc_ok) {
549 interval_ms = 0;
550 }
551
552 if (pcmk__strcase_any_of(action->task, RSC_NOTIFY, RSC_NOTIFIED, NULL)) {
553 const char *n_type = g_hash_table_lookup(action->meta, "notify_key_type");
554 const char *n_task = g_hash_table_lookup(action->meta, "notify_key_operation");
555
556 CRM_ASSERT(n_type != NULL);
557 CRM_ASSERT(n_task != NULL);
558 key = pcmk__notify_key(clone_name, n_type, n_task);
559
560 } else {
561 key = pcmk__op_key(clone_name, task, interval_ms);
562 }
563
564 if (action_host) {
565 action_name = crm_strdup_printf("%s%s %s", prefix, key, action_host);
566 } else {
567 action_name = crm_strdup_printf("%s%s", prefix, key);
568 }
569 free(key);
570
571 } else if (pcmk__str_eq(action->task, CRM_OP_FENCE, pcmk__str_casei)) {
572 const char *op = g_hash_table_lookup(action->meta, "stonith_action");
573
574 action_name = crm_strdup_printf("%s%s '%s' %s", prefix, action->task, op, action_host);
575
576 } else if (action->rsc && action_host) {
577 action_name = crm_strdup_printf("%s%s %s", prefix, action->uuid, action_host);
578
579 } else if (action_host) {
580 action_name = crm_strdup_printf("%s%s %s", prefix, action->task, action_host);
581
582 } else {
583 action_name = crm_strdup_printf("%s", action->uuid);
584 }
585
586 if (action_numbers) {
587 char *with_id = crm_strdup_printf("%s (%d)", action_name, action->id);
588
589 free(action_name);
590 action_name = with_id;
591 }
592 return action_name;
593 }
594
595 static bool
596 create_dotfile(pe_working_set_t * data_set, const char *dot_file, gboolean all_actions,
597 GError **error)
598 {
599 GListPtr gIter = NULL;
600 FILE *dot_strm = fopen(dot_file, "w");
601
602 if (dot_strm == NULL) {
603 g_set_error(error, PCMK__RC_ERROR, errno,
604 "Could not open %s for writing: %s", dot_file,
605 pcmk_rc_str(errno));
606 return false;
607 }
608
609 fprintf(dot_strm, " digraph \"g\" {\n");
610 for (gIter = data_set->actions; gIter != NULL; gIter = gIter->next) {
611 pe_action_t *action = (pe_action_t *) gIter->data;
612 const char *style = "dashed";
613 const char *font = "black";
614 const char *color = "black";
615 char *action_name = create_action_name(action);
616
617 crm_trace("Action %d: %s %s %p", action->id, action_name, action->uuid, action);
618
619 if (pcmk_is_set(action->flags, pe_action_pseudo)) {
620 font = "orange";
621 }
622
623 if (pcmk_is_set(action->flags, pe_action_dumped)) {
624 style = "bold";
625 color = "green";
626
627 } else if ((action->rsc != NULL)
628 && !pcmk_is_set(action->rsc->flags, pe_rsc_managed)) {
629 color = "red";
630 font = "purple";
631 if (all_actions == FALSE) {
632 goto do_not_write;
633 }
634
635 } else if (pcmk_is_set(action->flags, pe_action_optional)) {
636 color = "blue";
637 if (all_actions == FALSE) {
638 goto do_not_write;
639 }
640
641 } else {
642 color = "red";
643 CRM_CHECK(!pcmk_is_set(action->flags, pe_action_runnable), ;);
644 }
645
646 pe__set_action_flags(action, pe_action_dumped);
647 crm_trace("\"%s\" [ style=%s color=\"%s\" fontcolor=\"%s\"]",
648 action_name, style, color, font);
649 fprintf(dot_strm, "\"%s\" [ style=%s color=\"%s\" fontcolor=\"%s\"]\n",
650 action_name, style, color, font);
651 do_not_write:
652 free(action_name);
653 }
654
655 for (gIter = data_set->actions; gIter != NULL; gIter = gIter->next) {
656 pe_action_t *action = (pe_action_t *) gIter->data;
657
658 GListPtr gIter2 = NULL;
659
660 for (gIter2 = action->actions_before; gIter2 != NULL; gIter2 = gIter2->next) {
661 pe_action_wrapper_t *before = (pe_action_wrapper_t *) gIter2->data;
662
663 char *before_name = NULL;
664 char *after_name = NULL;
665 const char *style = "dashed";
666 gboolean optional = TRUE;
667
668 if (before->state == pe_link_dumped) {
669 optional = FALSE;
670 style = "bold";
671 } else if (pcmk_is_set(action->flags, pe_action_pseudo)
672 && (before->type & pe_order_stonith_stop)) {
673 continue;
674 } else if (before->type == pe_order_none) {
675 continue;
676 } else if (pcmk_is_set(before->action->flags, pe_action_dumped)
677 && pcmk_is_set(action->flags, pe_action_dumped)
678 && before->type != pe_order_load) {
679 optional = FALSE;
680 }
681
682 if (all_actions || optional == FALSE) {
683 before_name = create_action_name(before->action);
684 after_name = create_action_name(action);
685 crm_trace("\"%s\" -> \"%s\" [ style = %s]",
686 before_name, after_name, style);
687 fprintf(dot_strm, "\"%s\" -> \"%s\" [ style = %s]\n",
688 before_name, after_name, style);
689 free(before_name);
690 free(after_name);
691 }
692 }
693 }
694
695 fprintf(dot_strm, "}\n");
696 fflush(dot_strm);
697 fclose(dot_strm);
698 return true;
699 }
700
701 static int
702 setup_input(const char *input, const char *output, GError **error)
703 {
704 int rc = pcmk_rc_ok;
705 cib_t *cib_conn = NULL;
706 xmlNode *cib_object = NULL;
707 char *local_output = NULL;
708
709 if (input == NULL) {
710
711 cib_conn = cib_new();
712 rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command);
713 rc = pcmk_legacy2rc(rc);
714
715 if (rc == pcmk_rc_ok) {
716 rc = cib_conn->cmds->query(cib_conn, NULL, &cib_object, cib_scope_local | cib_sync_call);
717 }
718
719 cib_conn->cmds->signoff(cib_conn);
720 cib_delete(cib_conn);
721 cib_conn = NULL;
722
723 if (rc != pcmk_rc_ok) {
724 rc = pcmk_legacy2rc(rc);
725 g_set_error(error, PCMK__RC_ERROR, rc,
726 "Live CIB query failed: %s (%d)", pcmk_rc_str(rc), rc);
727 return rc;
728
729 } else if (cib_object == NULL) {
730 g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_NOINPUT,
731 "Live CIB query failed: empty result");
732 return pcmk_rc_no_input;
733 }
734
735 } else if (pcmk__str_eq(input, "-", pcmk__str_casei)) {
736 cib_object = filename2xml(NULL);
737
738 } else {
739 cib_object = filename2xml(input);
740 }
741
742 if (get_object_root(XML_CIB_TAG_STATUS, cib_object) == NULL) {
743 create_xml_node(cib_object, XML_CIB_TAG_STATUS);
744 }
745
746 if (cli_config_update(&cib_object, NULL, FALSE) == FALSE) {
747 free_xml(cib_object);
748 return pcmk_rc_transform_failed;
749 }
750
751 if (validate_xml(cib_object, NULL, FALSE) != TRUE) {
752 free_xml(cib_object);
753 return pcmk_rc_schema_validation;
754 }
755
756 if (output == NULL) {
757 char *pid = pcmk__getpid_s();
758
759 local_output = get_shadow_file(pid);
760 temp_shadow = strdup(local_output);
761 output = local_output;
762 free(pid);
763 }
764
765 rc = write_xml_file(cib_object, output, FALSE);
766 free_xml(cib_object);
767 cib_object = NULL;
768
769 if (rc < 0) {
770 rc = pcmk_legacy2rc(rc);
771 g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_CANTCREAT,
772 "Could not create '%s': %s", output, pcmk_rc_str(rc));
773 return rc;
774 } else {
775 setenv("CIB_file", output, 1);
776 free(local_output);
777 return pcmk_rc_ok;
778 }
779 }
780
781 static void
782 profile_one(const char *xml_file, long long repeat, pe_working_set_t *data_set, char *use_date)
783 {
784 xmlNode *cib_object = NULL;
785 clock_t start = 0;
786
787 printf("* Testing %s ...", xml_file);
788 fflush(stdout);
789
790 cib_object = filename2xml(xml_file);
791 start = clock();
792
793 if (get_object_root(XML_CIB_TAG_STATUS, cib_object) == NULL) {
794 create_xml_node(cib_object, XML_CIB_TAG_STATUS);
795 }
796
797
798 if (cli_config_update(&cib_object, NULL, FALSE) == FALSE) {
799 free_xml(cib_object);
800 return;
801 }
802
803 if (validate_xml(cib_object, NULL, FALSE) != TRUE) {
804 free_xml(cib_object);
805 return;
806 }
807
808 for (int i = 0; i < repeat; ++i) {
809 xmlNode *input = (repeat == 1)? cib_object : copy_xml(cib_object);
810
811 data_set->input = input;
812 get_date(data_set, false, use_date);
813 pcmk__schedule_actions(data_set, input, NULL);
814 pe_reset_working_set(data_set);
815 }
816 printf(" %.2f secs\n", (clock() - start) / (float) CLOCKS_PER_SEC);
817 }
818
819 #ifndef FILENAME_MAX
820 # define FILENAME_MAX 512
821 #endif
822
823 static void
824 profile_all(const char *dir, long long repeat, pe_working_set_t *data_set, char *use_date)
825 {
826 struct dirent **namelist;
827
828 int file_num = scandir(dir, &namelist, 0, alphasort);
829
830 if (file_num > 0) {
831 struct stat prop;
832 char buffer[FILENAME_MAX];
833
834 while (file_num--) {
835 if ('.' == namelist[file_num]->d_name[0]) {
836 free(namelist[file_num]);
837 continue;
838
839 } else if (!pcmk__ends_with_ext(namelist[file_num]->d_name,
840 ".xml")) {
841 free(namelist[file_num]);
842 continue;
843 }
844 snprintf(buffer, sizeof(buffer), "%s/%s", dir, namelist[file_num]->d_name);
845 if (stat(buffer, &prop) == 0 && S_ISREG(prop.st_mode)) {
846 profile_one(buffer, repeat, data_set, use_date);
847 }
848 free(namelist[file_num]);
849 }
850 free(namelist);
851 }
852 }
853
854 static GOptionContext *
855 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
856 GOptionContext *context = NULL;
857
858 GOptionEntry extra_prog_entries[] = {
859 { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &(args->quiet),
860 "Display only essential output",
861 NULL },
862
863 { NULL }
864 };
865
866 const char *description = "Operation Specification:\n\n"
867 "The OPSPEC in any command line option is of the form ${resource}_${task}_${interval_in_ms}@${node}=${rc} "
868 "memcached_monitor_20000@bart.example.com=7, for example). ${rc} is an OCF return code. For more "
869 "information on these return codes, refer to "
870 "https://clusterlabs.org/pacemaker/doc/en-US/Pacemaker/2.0/html/Pacemaker_Administration/s-ocf-return-codes.html\n\n"
871 "Examples:\n\n"
872 "Pretend a recurring monitor action found memcached stopped on node "
873 "fred.example.com and, during recovery, that the memcached stop "
874 "action failed:\n\n"
875 "\tcrm_simulate -LS --op-inject memcached:0_monitor_20000@bart.example.com=7 "
876 "--op-fail memcached:0_stop_0@fred.example.com=1 --save-output /tmp/memcached-test.xml\n\n"
877 "Now see what the reaction to the stop failed would be:\n\n"
878 "\tcrm_simulate -S --xml-file /tmp/memcached-test.xml\n\n";
879
880 context = pcmk__build_arg_context(args, NULL, group, NULL);
881 pcmk__add_main_args(context, extra_prog_entries);
882 g_option_context_set_description(context, description);
883
884 pcmk__add_arg_group(context, "operations", "Operations:",
885 "Show operations options", operation_entries);
886 pcmk__add_arg_group(context, "synthetic", "Synthetic Cluster Events:",
887 "Show synthetic cluster event options", synthetic_entries);
888 pcmk__add_arg_group(context, "output", "Output Options:",
889 "Show output options", output_entries);
890 pcmk__add_arg_group(context, "source", "Data Source:",
891 "Show data source options", source_entries);
892
893 return context;
894 }
895
896 int
897 main(int argc, char **argv)
898 {
899 GError *error = NULL;
900 GOptionGroup *output_group = NULL;
901 gchar **processed_args = NULL;
902 pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
903 GOptionContext *context = build_arg_context(args, &output_group);
904
905 int rc = pcmk_rc_ok;
906 pe_working_set_t *data_set = NULL;
907
908 xmlNode *input = NULL;
909
910 options.xml_file = strdup("-");
911
912 crm_log_cli_init("crm_simulate");
913
914 processed_args = pcmk__cmdline_preproc(argv, "bdefgiqrtuwxDFGINO");
915
916 if (!g_option_context_parse_strv(context, &processed_args, &error)) {
917 fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
918 goto done;
919 }
920
921 for (int i = 0; i < args->verbosity; i++) {
922 crm_bump_log_level(argc, argv);
923 }
924
925 if (args->version) {
926
927 pcmk__cli_help('v', CRM_EX_USAGE);
928 goto done;
929 }
930
931 if (optind > argc) {
932 gchar *help = g_option_context_get_help(context, TRUE, NULL);
933 fprintf(stderr, "%s", help);
934 g_free(help);
935 goto done;
936 }
937
938 if (args->verbosity > 0) {
939
940 close(STDERR_FILENO);
941 dup2(STDOUT_FILENO, STDERR_FILENO);
942 action_numbers = TRUE;
943 }
944
945 if (args->quiet) {
946 quiet = TRUE;
947 }
948
949 data_set = pe_new_working_set();
950 if (data_set == NULL) {
951 rc = ENOMEM;
952 g_set_error(&error, PCMK__RC_ERROR, rc, "Could not allocate working set");
953 goto done;
954 }
955 pe__set_working_set_flags(data_set, pe_flag_no_compat);
956
957 if (options.test_dir != NULL) {
958 profile_all(options.test_dir, options.repeat, data_set, options.use_date);
959 rc = pcmk_rc_ok;
960 goto done;
961 }
962
963 rc = setup_input(options.xml_file, options.store ? options.xml_file : options.output_file, &error);
964 if (rc != pcmk_rc_ok) {
965 goto done;
966 }
967
968 global_cib = cib_new();
969 rc = global_cib->cmds->signon(global_cib, crm_system_name, cib_command);
970 if (rc != pcmk_rc_ok) {
971 rc = pcmk_legacy2rc(rc);
972 g_set_error(&error, PCMK__RC_ERROR, rc,
973 "Could not connect to the CIB: %s", pcmk_rc_str(rc));
974 goto done;
975 }
976
977 rc = global_cib->cmds->query(global_cib, NULL, &input, cib_sync_call | cib_scope_local);
978 if (rc != pcmk_rc_ok) {
979 rc = pcmk_legacy2rc(rc);
980 g_set_error(&error, PCMK__RC_ERROR, rc,
981 "Could not get local CIB: %s", pcmk_rc_str(rc));
982 goto done;
983 }
984
985 data_set->input = input;
986 get_date(data_set, true, options.use_date);
987 if(options.xml_file) {
988 pe__set_working_set_flags(data_set, pe_flag_sanitized);
989 }
990 pe__set_working_set_flags(data_set, pe_flag_stdout);
991 cluster_status(data_set);
992
993 if (quiet == FALSE) {
994 int opts = options.print_pending ? pe_print_pending : 0;
995
996 if (pcmk_is_set(data_set->flags, pe_flag_maintenance_mode)) {
997 quiet_log("\n *** Resource management is DISABLED ***");
998 quiet_log("\n The cluster will not attempt to start, stop or recover services");
999 quiet_log("\n");
1000 }
1001
1002 if (data_set->disabled_resources || data_set->blocked_resources) {
1003 quiet_log("%d of %d resource instances DISABLED and %d BLOCKED "
1004 "from further action due to failure\n",
1005 data_set->disabled_resources, data_set->ninstances,
1006 data_set->blocked_resources);
1007 }
1008
1009 quiet_log("\nCurrent cluster status:\n");
1010 print_cluster_status(data_set, opts);
1011 }
1012
1013 if (options.modified) {
1014 quiet_log("Performing requested modifications\n");
1015 modify_configuration(data_set, global_cib, options.quorum, options.watchdog, options.node_up,
1016 options.node_down, options.node_fail, options.op_inject,
1017 options.ticket_grant, options.ticket_revoke, options.ticket_standby,
1018 options.ticket_activate);
1019
1020 rc = global_cib->cmds->query(global_cib, NULL, &input, cib_sync_call);
1021 if (rc != pcmk_rc_ok) {
1022 rc = pcmk_legacy2rc(rc);
1023 g_set_error(&error, PCMK__RC_ERROR, rc,
1024 "Could not get modified CIB: %s", pcmk_rc_str(rc));
1025 goto done;
1026 }
1027
1028 cleanup_calculations(data_set);
1029 data_set->input = input;
1030 get_date(data_set, true, options.use_date);
1031
1032 if(options.xml_file) {
1033 pe__set_working_set_flags(data_set, pe_flag_sanitized);
1034 }
1035 pe__set_working_set_flags(data_set, pe_flag_stdout);
1036 cluster_status(data_set);
1037 }
1038
1039 if (options.input_file != NULL) {
1040 rc = write_xml_file(input, options.input_file, FALSE);
1041 if (rc < 0) {
1042 rc = pcmk_legacy2rc(rc);
1043 g_set_error(&error, PCMK__RC_ERROR, rc,
1044 "Could not create '%s': %s", options.input_file, pcmk_rc_str(rc));
1045 goto done;
1046 }
1047 }
1048
1049 if (options.process || options.simulate) {
1050 crm_time_t *local_date = NULL;
1051
1052 if (show_scores && show_utilization) {
1053 printf("Allocation scores and utilization information:\n");
1054 } else if (show_scores) {
1055 fprintf(stdout, "Allocation scores:\n");
1056 } else if (show_utilization) {
1057 printf("Utilization information:\n");
1058 }
1059
1060 pcmk__schedule_actions(data_set, input, local_date);
1061 input = NULL;
1062
1063 if (options.graph_file != NULL) {
1064 write_xml_file(data_set->graph, options.graph_file, FALSE);
1065 }
1066
1067 if (options.dot_file != NULL) {
1068 if (!create_dotfile(data_set, options.dot_file, options.all_actions, &error)) {
1069 goto done;
1070 }
1071 }
1072
1073 if (quiet == FALSE) {
1074 GListPtr gIter = NULL;
1075
1076 quiet_log("%sTransition Summary:\n", show_scores || show_utilization
1077 || options.modified ? "\n" : "");
1078 fflush(stdout);
1079
1080 LogNodeActions(data_set, TRUE);
1081 for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
1082 pe_resource_t *rsc = (pe_resource_t *) gIter->data;
1083
1084 LogActions(rsc, data_set, TRUE);
1085 }
1086 }
1087 }
1088
1089 rc = pcmk_rc_ok;
1090
1091 if (options.simulate) {
1092 if (run_simulation(data_set, global_cib, options.op_fail, quiet) != pcmk_rc_ok) {
1093 rc = pcmk_rc_error;
1094 }
1095 if(quiet == FALSE) {
1096 get_date(data_set, true, options.use_date);
1097
1098 quiet_log("\nRevised cluster status:\n");
1099 pe__set_working_set_flags(data_set, pe_flag_stdout);
1100 cluster_status(data_set);
1101 print_cluster_status(data_set, 0);
1102 }
1103 }
1104
1105 done:
1106 if (error != NULL) {
1107 fprintf(stderr, "%s\n", error->message);
1108 g_clear_error(&error);
1109 }
1110
1111
1112 free(options.dot_file);
1113 free(options.graph_file);
1114 g_free(options.input_file);
1115 g_list_free_full(options.node_up, g_free);
1116 g_list_free_full(options.node_down, g_free);
1117 g_list_free_full(options.node_fail, g_free);
1118 g_list_free_full(options.op_fail, g_free);
1119 g_list_free_full(options.op_inject, g_free);
1120 g_free(options.output_file);
1121 free(options.quorum);
1122 g_free(options.test_dir);
1123 g_list_free_full(options.ticket_grant, g_free);
1124 g_list_free_full(options.ticket_revoke, g_free);
1125 g_list_free_full(options.ticket_standby, g_free);
1126 g_list_free_full(options.ticket_activate, g_free);
1127 free(options.use_date);
1128 free(options.watchdog);
1129 free(options.xml_file);
1130
1131 pcmk__free_arg_context(context);
1132 g_strfreev(processed_args);
1133
1134 if (data_set) {
1135 pe_free_working_set(data_set);
1136 }
1137
1138 if (global_cib) {
1139 global_cib->cmds->signoff(global_cib);
1140 cib_delete(global_cib);
1141 }
1142
1143 fflush(stderr);
1144
1145 if (temp_shadow) {
1146 unlink(temp_shadow);
1147 free(temp_shadow);
1148 }
1149 crm_exit(pcmk_rc2exitc(rc));
1150 }