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