This source file includes following definitions.
- all_actions_cb
- attrs_cb
- failcounts_cb
- in_place_cb
- live_check_cb
- node_down_cb
- node_fail_cb
- node_up_cb
- op_fail_cb
- op_inject_cb
- pending_cb
- process_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
- setup_input
- 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/cib/internal.h>
25 #include <crm/common/cmdline_internal.h>
26 #include <crm/common/output_internal.h>
27 #include <crm/common/output.h>
28 #include <crm/common/util.h>
29 #include <crm/common/iso8601.h>
30 #include <crm/pengine/status.h>
31 #include <pacemaker-internal.h>
32 #include <pacemaker.h>
33
34 #define SUMMARY "crm_simulate - simulate a Pacemaker cluster's response to events"
35
36 struct {
37 char *dot_file;
38 char *graph_file;
39 gchar *input_file;
40 pcmk_injections_t *injections;
41 unsigned int flags;
42 gchar *output_file;
43 long long repeat;
44 gboolean store;
45 gchar *test_dir;
46 char *use_date;
47 char *xml_file;
48 } options = {
49 .flags = pcmk_sim_show_pending | pcmk_sim_sanitized,
50 .repeat = 1
51 };
52
53 unsigned int section_opts = 0;
54 char *temp_shadow = NULL;
55 extern gboolean bringing_nodes_online;
56 crm_exit_t exit_code = CRM_EX_OK;
57
58 #define INDENT " "
59
60 static pcmk__supported_format_t formats[] = {
61 PCMK__SUPPORTED_FORMAT_NONE,
62 PCMK__SUPPORTED_FORMAT_TEXT,
63 PCMK__SUPPORTED_FORMAT_XML,
64 { NULL, NULL, NULL }
65 };
66
67 static gboolean
68 all_actions_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
69 options.flags |= pcmk_sim_all_actions;
70 return TRUE;
71 }
72
73 static gboolean
74 attrs_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
75 section_opts |= pcmk_section_attributes;
76 return TRUE;
77 }
78
79 static gboolean
80 failcounts_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
81 section_opts |= pcmk_section_failcounts | pcmk_section_failures;
82 return TRUE;
83 }
84
85 static gboolean
86 in_place_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
87 options.store = TRUE;
88 options.flags |= pcmk_sim_process | pcmk_sim_simulate;
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 options.flags &= ~pcmk_sim_sanitized;
100 return TRUE;
101 }
102
103 static gboolean
104 node_down_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
105 options.injections->node_down = g_list_append(options.injections->node_down, 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.injections->node_fail = g_list_append(options.injections->node_fail, 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 bringing_nodes_online = TRUE;
118 options.injections->node_up = g_list_append(options.injections->node_up, g_strdup(optarg));
119 return TRUE;
120 }
121
122 static gboolean
123 op_fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
124 options.flags |= pcmk_sim_process | pcmk_sim_simulate;
125 options.injections->op_fail = g_list_append(options.injections->op_fail, 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.injections->op_inject = g_list_append(options.injections->op_inject, g_strdup(optarg));
132 return TRUE;
133 }
134
135 static gboolean
136 pending_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
137 options.flags |= pcmk_sim_show_pending;
138 return TRUE;
139 }
140
141 static gboolean
142 process_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
143 options.flags |= pcmk_sim_process;
144 return TRUE;
145 }
146
147 static gboolean
148 quorum_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
149 if (options.injections->quorum) {
150 free(options.injections->quorum);
151 }
152
153 options.injections->quorum = strdup(optarg);
154 return TRUE;
155 }
156
157 static gboolean
158 save_dotfile_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
159 if (options.dot_file) {
160 free(options.dot_file);
161 }
162
163 options.flags |= pcmk_sim_process;
164 options.dot_file = strdup(optarg);
165 return TRUE;
166 }
167
168 static gboolean
169 save_graph_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
170 if (options.graph_file) {
171 free(options.graph_file);
172 }
173
174 options.flags |= pcmk_sim_process;
175 options.graph_file = strdup(optarg);
176 return TRUE;
177 }
178
179 static gboolean
180 show_scores_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
181 options.flags |= pcmk_sim_process | pcmk_sim_show_scores;
182 return TRUE;
183 }
184
185 static gboolean
186 simulate_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
187 options.flags |= pcmk_sim_process | pcmk_sim_simulate;
188 return TRUE;
189 }
190
191 static gboolean
192 ticket_activate_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
193 options.injections->ticket_activate = g_list_append(options.injections->ticket_activate, g_strdup(optarg));
194 return TRUE;
195 }
196
197 static gboolean
198 ticket_grant_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
199 options.injections->ticket_grant = g_list_append(options.injections->ticket_grant, g_strdup(optarg));
200 return TRUE;
201 }
202
203 static gboolean
204 ticket_revoke_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
205 options.injections->ticket_revoke = g_list_append(options.injections->ticket_revoke, g_strdup(optarg));
206 return TRUE;
207 }
208
209 static gboolean
210 ticket_standby_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
211 options.injections->ticket_standby = g_list_append(options.injections->ticket_standby, g_strdup(optarg));
212 return TRUE;
213 }
214
215 static gboolean
216 utilization_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
217 options.flags |= pcmk_sim_process | pcmk_sim_show_utilization;
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.injections->watchdog) {
224 free(options.injections->watchdog);
225 }
226
227 options.injections->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 options.flags |= pcmk_sim_sanitized;
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 options.flags |= pcmk_sim_sanitized;
250 return TRUE;
251 }
252
253 static GOptionEntry operation_entries[] = {
254 { "run", 'R', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, process_cb,
255 "Process the supplied input and show what actions the cluster will take in response",
256 NULL },
257 { "simulate", 'S', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, simulate_cb,
258 "Like --run, but also simulate taking those actions and show the resulting new status",
259 NULL },
260 { "in-place", 'X', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, in_place_cb,
261 "Like --simulate, but also store the results back to the input file",
262 NULL },
263 { "show-attrs", 'A', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, attrs_cb,
264 "Show node attributes",
265 NULL },
266 { "show-failcounts", 'c', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, failcounts_cb,
267 "Show resource fail counts",
268 NULL },
269 { "show-scores", 's', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_scores_cb,
270 "Show allocation scores",
271 NULL },
272 { "show-utilization", 'U', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, utilization_cb,
273 "Show utilization information",
274 NULL },
275 { "profile", 'P', 0, G_OPTION_ARG_FILENAME, &options.test_dir,
276 "Process all the XML files in the named directory to create profiling data",
277 "DIR" },
278 { "repeat", 'N', 0, G_OPTION_ARG_INT, &options.repeat,
279 "With --profile, repeat each test N times and print timings",
280 "N" },
281
282 { "pending", 'j', G_OPTION_FLAG_NO_ARG|G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, pending_cb,
283 "Display pending state if 'record-pending' is enabled",
284 NULL },
285
286 { NULL }
287 };
288
289 static GOptionEntry synthetic_entries[] = {
290 { "node-up", 'u', 0, G_OPTION_ARG_CALLBACK, node_up_cb,
291 "Simulate bringing a node online",
292 "NODE" },
293 { "node-down", 'd', 0, G_OPTION_ARG_CALLBACK, node_down_cb,
294 "Simulate taking a node offline",
295 "NODE" },
296 { "node-fail", 'f', 0, G_OPTION_ARG_CALLBACK, node_fail_cb,
297 "Simulate a node failing",
298 "NODE" },
299 { "op-inject", 'i', 0, G_OPTION_ARG_CALLBACK, op_inject_cb,
300 "Generate a failure for the cluster to react to in the simulation.\n"
301 INDENT "See `Operation Specification` help for more information.",
302 "OPSPEC" },
303 { "op-fail", 'F', 0, G_OPTION_ARG_CALLBACK, op_fail_cb,
304 "If the specified task occurs during the simulation, have it fail with return code ${rc}.\n"
305 INDENT "The transition will normally stop at the failed action.\n"
306 INDENT "Save the result with --save-output and re-run with --xml-file.\n"
307 INDENT "See `Operation Specification` help for more information.",
308 "OPSPEC" },
309 { "set-datetime", 't', 0, G_OPTION_ARG_STRING, &options.use_date,
310 "Set date/time (ISO 8601 format, see https://en.wikipedia.org/wiki/ISO_8601)",
311 "DATETIME" },
312 { "quorum", 'q', 0, G_OPTION_ARG_CALLBACK, quorum_cb,
313 "Set to '1' (or 'true') to indicate cluster has quorum",
314 "QUORUM" },
315 { "watchdog", 'w', 0, G_OPTION_ARG_CALLBACK, watchdog_cb,
316 "Set to '1' (or 'true') to indicate cluster has an active watchdog device",
317 "DEVICE" },
318 { "ticket-grant", 'g', 0, G_OPTION_ARG_CALLBACK, ticket_grant_cb,
319 "Simulate granting a ticket",
320 "TICKET" },
321 { "ticket-revoke", 'r', 0, G_OPTION_ARG_CALLBACK, ticket_revoke_cb,
322 "Simulate revoking a ticket",
323 "TICKET" },
324 { "ticket-standby", 'b', 0, G_OPTION_ARG_CALLBACK, ticket_standby_cb,
325 "Simulate making a ticket standby",
326 "TICKET" },
327 { "ticket-activate", 'e', 0, G_OPTION_ARG_CALLBACK, ticket_activate_cb,
328 "Simulate activating a ticket",
329 "TICKET" },
330
331 { NULL }
332 };
333
334 static GOptionEntry artifact_entries[] = {
335 { "save-input", 'I', 0, G_OPTION_ARG_FILENAME, &options.input_file,
336 "Save the input configuration to the named file",
337 "FILE" },
338 { "save-output", 'O', 0, G_OPTION_ARG_FILENAME, &options.output_file,
339 "Save the output configuration to the named file",
340 "FILE" },
341 { "save-graph", 'G', 0, G_OPTION_ARG_CALLBACK, save_graph_cb,
342 "Save the transition graph (XML format) to the named file",
343 "FILE" },
344 { "save-dotfile", 'D', 0, G_OPTION_ARG_CALLBACK, save_dotfile_cb,
345 "Save the transition graph (DOT format) to the named file",
346 "FILE" },
347 { "all-actions", 'a', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, all_actions_cb,
348 "Display all possible actions in DOT graph (even if not part of transition)",
349 NULL },
350
351 { NULL }
352 };
353
354 static GOptionEntry source_entries[] = {
355 { "live-check", 'L', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, live_check_cb,
356 "Connect to CIB manager and use the current CIB contents as input",
357 NULL },
358 { "xml-file", 'x', 0, G_OPTION_ARG_CALLBACK, xml_file_cb,
359 "Retrieve XML from the named file",
360 "FILE" },
361 { "xml-pipe", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, xml_pipe_cb,
362 "Retrieve XML from stdin",
363 NULL },
364
365 { NULL }
366 };
367
368 static int
369 setup_input(const char *input, const char *output, GError **error)
370 {
371 int rc = pcmk_rc_ok;
372 xmlNode *cib_object = NULL;
373 char *local_output = NULL;
374
375 if (input == NULL) {
376
377 rc = cib__signon_query(NULL, &cib_object);
378 if (rc != pcmk_rc_ok) {
379 g_set_error(error, PCMK__RC_ERROR, rc,
380 "CIB query failed: %s", pcmk_rc_str(rc));
381 return rc;
382 }
383
384 } else if (pcmk__str_eq(input, "-", pcmk__str_casei)) {
385 cib_object = filename2xml(NULL);
386
387 } else {
388 cib_object = filename2xml(input);
389 }
390
391 if (get_object_root(XML_CIB_TAG_STATUS, cib_object) == NULL) {
392 create_xml_node(cib_object, XML_CIB_TAG_STATUS);
393 }
394
395 if (cli_config_update(&cib_object, NULL, FALSE) == FALSE) {
396 free_xml(cib_object);
397 return pcmk_rc_transform_failed;
398 }
399
400 if (validate_xml(cib_object, NULL, FALSE) != TRUE) {
401 free_xml(cib_object);
402 return pcmk_rc_schema_validation;
403 }
404
405 if (output == NULL) {
406 char *pid = pcmk__getpid_s();
407
408 local_output = get_shadow_file(pid);
409 temp_shadow = strdup(local_output);
410 output = local_output;
411 free(pid);
412 }
413
414 rc = write_xml_file(cib_object, output, FALSE);
415 free_xml(cib_object);
416 cib_object = NULL;
417
418 if (rc < 0) {
419 rc = pcmk_legacy2rc(rc);
420 g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_CANTCREAT,
421 "Could not create '%s': %s", output, pcmk_rc_str(rc));
422 return rc;
423 } else {
424 setenv("CIB_file", output, 1);
425 free(local_output);
426 return pcmk_rc_ok;
427 }
428 }
429
430 static GOptionContext *
431 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
432 GOptionContext *context = NULL;
433
434 GOptionEntry extra_prog_entries[] = {
435 { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &(args->quiet),
436 "Display only essential output",
437 NULL },
438
439 { NULL }
440 };
441
442 const char *description = "Operation Specification:\n\n"
443 "The OPSPEC in any command line option is of the form\n"
444 "${resource}_${task}_${interval_in_ms}@${node}=${rc}\n"
445 "(memcached_monitor_20000@bart.example.com=7, for example).\n"
446 "${rc} is an OCF return code. For more information on these\n"
447 "return codes, refer to https://clusterlabs.org/pacemaker/doc/2.1/Pacemaker_Administration/html/agents.html#ocf-return-codes\n\n"
448 "Examples:\n\n"
449 "Pretend a recurring monitor action found memcached stopped on node\n"
450 "fred.example.com and, during recovery, that the memcached stop\n"
451 "action failed:\n\n"
452 "\tcrm_simulate -LS --op-inject memcached:0_monitor_20000@bart.example.com=7 "
453 "--op-fail memcached:0_stop_0@fred.example.com=1 --save-output /tmp/memcached-test.xml\n\n"
454 "Now see what the reaction to the stop failed would be:\n\n"
455 "\tcrm_simulate -S --xml-file /tmp/memcached-test.xml\n\n";
456
457 context = pcmk__build_arg_context(args, "text (default), xml", group, NULL);
458 pcmk__add_main_args(context, extra_prog_entries);
459 g_option_context_set_description(context, description);
460
461 pcmk__add_arg_group(context, "operations", "Operations:",
462 "Show operations options", operation_entries);
463 pcmk__add_arg_group(context, "synthetic", "Synthetic Cluster Events:",
464 "Show synthetic cluster event options", synthetic_entries);
465 pcmk__add_arg_group(context, "artifact", "Artifact Options:",
466 "Show artifact options", artifact_entries);
467 pcmk__add_arg_group(context, "source", "Data Source:",
468 "Show data source options", source_entries);
469
470 return context;
471 }
472
473 int
474 main(int argc, char **argv)
475 {
476 int rc = pcmk_rc_ok;
477 pe_working_set_t *data_set = NULL;
478 pcmk__output_t *out = NULL;
479
480 GError *error = NULL;
481
482 GOptionGroup *output_group = NULL;
483 pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
484 gchar **processed_args = pcmk__cmdline_preproc(argv, "bdefgiqrtuwxDFGINOP");
485 GOptionContext *context = build_arg_context(args, &output_group);
486
487 options.injections = calloc(1, sizeof(pcmk_injections_t));
488 if (options.injections == NULL) {
489 rc = ENOMEM;
490 goto done;
491 }
492
493
494 options.xml_file = strdup("-");
495
496 pcmk__register_formats(output_group, formats);
497 if (!g_option_context_parse_strv(context, &processed_args, &error)) {
498 exit_code = CRM_EX_USAGE;
499 goto done;
500 }
501
502 pcmk__cli_init_logging("crm_simulate", args->verbosity);
503
504 rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
505 if (rc != pcmk_rc_ok) {
506 fprintf(stderr, "Error creating output format %s: %s\n",
507 args->output_ty, pcmk_rc_str(rc));
508 exit_code = CRM_EX_ERROR;
509 goto done;
510 }
511
512 if (pcmk__str_eq(args->output_ty, "text", pcmk__str_null_matches) &&
513 !pcmk_is_set(options.flags, pcmk_sim_show_scores) &&
514 !pcmk_is_set(options.flags, pcmk_sim_show_utilization)) {
515 pcmk__force_args(context, &error, "%s --text-fancy", g_get_prgname());
516 } else if (pcmk__str_eq(args->output_ty, "xml", pcmk__str_none)) {
517 pcmk__force_args(context, &error, "%s --xml-simple-list --xml-substitute", g_get_prgname());
518 }
519
520 pe__register_messages(out);
521 pcmk__register_lib_messages(out);
522
523 out->quiet = args->quiet;
524
525 if (args->version) {
526 out->version(out, false);
527 goto done;
528 }
529
530 if (args->verbosity > 0) {
531 options.flags |= pcmk_sim_verbose;
532
533 #ifdef PCMK__COMPAT_2_0
534
535 close(STDERR_FILENO);
536 dup2(STDOUT_FILENO, STDERR_FILENO);
537 #endif
538 }
539
540 data_set = pe_new_working_set();
541 if (data_set == NULL) {
542 rc = ENOMEM;
543 g_set_error(&error, PCMK__RC_ERROR, rc, "Could not allocate working set");
544 goto done;
545 }
546
547 if (pcmk_is_set(options.flags, pcmk_sim_show_scores)) {
548 pe__set_working_set_flags(data_set, pe_flag_show_scores);
549 }
550 if (pcmk_is_set(options.flags, pcmk_sim_show_utilization)) {
551 pe__set_working_set_flags(data_set, pe_flag_show_utilization);
552 }
553 pe__set_working_set_flags(data_set, pe_flag_no_compat);
554
555 if (options.test_dir != NULL) {
556 data_set->priv = out;
557 pcmk__profile_dir(options.test_dir, options.repeat, data_set, options.use_date);
558 rc = pcmk_rc_ok;
559 goto done;
560 }
561
562 rc = setup_input(options.xml_file, options.store ? options.xml_file : options.output_file, &error);
563 if (rc != pcmk_rc_ok) {
564 goto done;
565 }
566
567 rc = pcmk__simulate(data_set, out, options.injections, options.flags, section_opts,
568 options.use_date, options.input_file, options.graph_file,
569 options.dot_file);
570
571 done:
572 pcmk__output_and_clear_error(error, NULL);
573
574
575 free(options.dot_file);
576 free(options.graph_file);
577 g_free(options.input_file);
578 g_free(options.output_file);
579 g_free(options.test_dir);
580 free(options.use_date);
581 free(options.xml_file);
582
583 pcmk_free_injections(options.injections);
584 pcmk__free_arg_context(context);
585 g_strfreev(processed_args);
586
587 if (data_set) {
588 pe_free_working_set(data_set);
589 }
590
591 fflush(stderr);
592
593 if (temp_shadow) {
594 unlink(temp_shadow);
595 free(temp_shadow);
596 }
597
598 if (rc != pcmk_rc_ok) {
599 exit_code = pcmk_rc2exitc(rc);
600 }
601
602 if (out != NULL) {
603 out->finish(out, exit_code, true, NULL);
604 pcmk__output_free(out);
605 }
606
607 crm_exit(exit_code);
608 }