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